Merge branch 'codex/login-sync-main' into release/v1.1.3

# 请输入一个提交信息以解释此合并的必要性,尤其是将一个更新后的上游分支
# 合并到主题分支。
#
# 以 '#' 开始的行将被忽略,而空的提交说明将终止提交。
This commit is contained in:
Haitao Pan 2026-05-30 12:09:50 +08:00
commit c2e675f390
2 changed files with 100 additions and 0 deletions

View File

@ -1016,6 +1016,16 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
}
Uri? resolveBridgeAcpEndpointInternal() {
final selfHosted =
settingsControllerInternal.snapshot.acpBridgeServerModeConfig.selfHosted;
final selfHostedUrl = selfHosted.serverUrl.trim();
if (selfHosted.isConfigured && selfHostedUrl.isNotEmpty) {
final uri = Uri.tryParse(selfHostedUrl);
if (uri != null && uri.hasScheme && uri.host.trim().isNotEmpty) {
return uri.replace(query: null, fragment: null);
}
}
final uri = Uri.parse(kManagedBridgeServerUrl);
return uri.replace(query: null, fragment: null);
}
@ -1029,6 +1039,11 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
if (bridgeEndpoint == null) {
return false;
}
final selfHosted =
settingsControllerInternal.snapshot.acpBridgeServerModeConfig.selfHosted;
if (selfHosted.isConfigured) {
return true;
}
final accountSyncState = settingsControllerInternal.accountSyncState;
if (settingsControllerInternal.accountSignedIn &&
accountSyncState?.tokenConfigured.bridge == true) {
@ -1072,6 +1087,10 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
normalizedHost == bridgeHost &&
(bridgePort <= 0 || endpoint.port == bridgePort);
if (matchesBridgeEndpoint) {
final manualBridgeToken = await _resolveManualBridgeAuthTokenInternal();
if (manualBridgeToken != null && manualBridgeToken.isNotEmpty) {
return manualBridgeToken;
}
final bridgeToken = await _resolveManagedBridgeAuthTokenInternal();
if (bridgeToken != null && bridgeToken.isNotEmpty) {
return bridgeToken;
@ -1097,6 +1116,22 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
return null;
}
Future<String?> _resolveManualBridgeAuthTokenInternal() async {
final selfHosted =
settingsControllerInternal.snapshot.acpBridgeServerModeConfig.selfHosted;
if (!selfHosted.isConfigured) {
return null;
}
final passwordRef = selfHosted.passwordRef.trim();
if (passwordRef.isEmpty) {
return null;
}
final token = (await storeInternal.loadSecretValueByRef(
passwordRef,
))?.trim();
return token?.isNotEmpty == true ? token : null;
}
Future<String?> _resolveManagedBridgeAuthTokenInternal() async {
final accountSyncState = settingsControllerInternal.accountSyncState;
if (settingsControllerInternal.accountSignedIn &&

View File

@ -441,6 +441,71 @@ void main() {
},
);
test('manual bridge config becomes the runtime ACP source', () async {
final storeRoot = await Directory.systemTemp.createTemp(
'xworkmate-manual-bridge-runtime-',
);
addTearDown(() async {
if (await storeRoot.exists()) {
try {
await storeRoot.delete(recursive: true);
} on FileSystemException {
// Temp cleanup is best effort here. The controller may still be
// releasing files when teardown starts.
}
}
});
final store = SecureConfigStore(
secretRootPathResolver: () async => '${storeRoot.path}/secrets',
appDataRootPathResolver: () async => '${storeRoot.path}/app-data',
supportRootPathResolver: () async => '${storeRoot.path}/support',
enableSecureStorage: false,
);
await store.initialize();
await store.saveSettingsSnapshot(
SettingsSnapshot.defaults().copyWith(
acpBridgeServerModeConfig: AcpBridgeServerModeConfig.defaults()
.copyWith(
selfHosted: AcpBridgeServerModeConfig.defaults().selfHosted
.copyWith(
serverUrl: 'https://private-bridge.svc.plus',
username: 'admin',
),
),
),
);
await store.saveSecretValueByRef(
AcpBridgeServerSelfHostedConfig.defaults().passwordRef,
'manual-bridge-token',
);
final controller = AppController(
environmentOverride: const <String, String>{},
store: store,
);
addTearDown(controller.dispose);
await controller.settingsControllerInternal.initialize();
expect(
controller.resolveGatewayAcpEndpointInternal()?.toString(),
'https://private-bridge.svc.plus',
);
expect(controller.isBridgeAcpRuntimeConfiguredInternal(), isTrue);
expect(
await controller.resolveGatewayAcpAuthorizationHeaderInternal(
Uri.parse('https://private-bridge.svc.plus/acp/rpc'),
),
'manual-bridge-token',
);
expect(
await controller.resolveGatewayAcpAuthorizationHeaderInternal(
Uri.parse('$kManagedBridgeServerUrl/acp/rpc'),
),
isNull,
);
});
test(
'syncAccountSettings succeeds when bridge url metadata is missing',
() async {