修复AGC自定义服务器重启后仍需重新登录
- 启动恢复登录态前先读取落盘的服务器选择,用自定义 origin 调用 read_client_auth_state - 删除与认证恢复竞态的独立服务器选择水合 effect,登录表单状态改由认证恢复链路统一设置 - 新增回归测试,断言自定义服务器会话恢复提交的 expectedApiBaseUrl 不再落到渠道默认地址
This commit is contained in:
@@ -196,20 +196,6 @@ export function AuthenticatedClient({
|
||||
setServerSelection(next);
|
||||
setLoginStatus(`已选择 ${preset} 服务器`);
|
||||
}
|
||||
// 服务器选择是 Rust 客户端偏好:本地 debug 启动时读一次,打包产物始终用渠道默认值。
|
||||
useEffect(() => {
|
||||
if (!clientServerSelectionEnabled) return;
|
||||
let disposed = false;
|
||||
void hydrateClientServerSelection().then((selection) => {
|
||||
if (disposed) return;
|
||||
setServerSelection(selection);
|
||||
setCustomServerUrl(selection.customBaseUrl);
|
||||
});
|
||||
return () => {
|
||||
disposed = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const uninstallWebviewLogBridge = installWebviewLogBridge();
|
||||
const handleError = (event: ErrorEvent) => {
|
||||
@@ -238,8 +224,14 @@ export function AuthenticatedClient({
|
||||
setAuthCheckError('');
|
||||
setAuthCheckStage('refresh');
|
||||
try {
|
||||
// 服务器选择先于认证读取落盘值:Rust 按 `expectedApiBaseUrl` 判定凭据归属,竞态下用
|
||||
// 渠道默认值会被当成「凭据属于另一个 origin」而误报未登录,自定义服务器每次启动都要重登。
|
||||
const selection = await hydrateClientServerSelection();
|
||||
if (!isActiveRun()) return;
|
||||
setServerSelection(selection);
|
||||
setCustomServerUrl(selection.customBaseUrl);
|
||||
const state = await withAuthCheckTimeout(
|
||||
readClientAuthState(getClientServerBaseUrl()),
|
||||
readClientAuthState(getClientServerBaseUrl(selection)),
|
||||
AUTH_CHECK_REQUEST_TIMEOUT_MS,
|
||||
'检查登录状态超时,请检查服务器地址和网络后重试',
|
||||
);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/** @vitest-environment jsdom */
|
||||
/**
|
||||
* 启动恢复登录态必须使用已落盘的服务器选择。
|
||||
*
|
||||
* 症状:本地 debug 选了 custom 服务器并登录成功后,重启客户端又被要求重新输密码。
|
||||
* 凭据并没有丢:Rust 侧按 `expectedApiBaseUrl` 与凭据归属 origin 比对,不一致就只报告
|
||||
* 未登录、不跨 origin 续期。因此渲染层必须在发起 `read_client_auth_state` 之前先读完
|
||||
* `read_workspace_preferences`,否则预期地址会停在渠道默认值,自定义服务器的会话永远恢复不了。
|
||||
*/
|
||||
import { cleanup, render, screen } from '@testing-library/react';
|
||||
import { createElement } from 'react';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('@tauri-apps/api/core', () => ({
|
||||
invoke: vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
import { AuthenticatedClient } from '../src/app/AuthenticatedClient';
|
||||
import { resetPlatformSessionForTests } from '../src/services/platformSession';
|
||||
import { installTauriRuntime, resetTauriRuntime } from './tauriRuntimeFake';
|
||||
|
||||
const CUSTOM_SERVER = 'https://custom.example.com';
|
||||
|
||||
describe('启动时用已落盘的服务器选择恢复登录态', () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
resetPlatformSessionForTests();
|
||||
resetTauriRuntime();
|
||||
});
|
||||
|
||||
it('自定义服务器的会话恢复带自定义 origin,而不是渠道默认地址', async () => {
|
||||
const authCalls: Array<{
|
||||
command: string;
|
||||
args?: Record<string, unknown>;
|
||||
}> = [];
|
||||
installTauriRuntime({
|
||||
core: {
|
||||
invoke: async (command, args) => {
|
||||
authCalls.push({ command, args });
|
||||
if (command === 'read_workspace_preferences') {
|
||||
return {
|
||||
clientServerSelection: {
|
||||
preset: 'custom',
|
||||
customBaseUrl: CUSTOM_SERVER,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (command === 'read_client_auth_state') {
|
||||
return { status: 'unauthenticated' };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
render(
|
||||
createElement(AuthenticatedClient, {
|
||||
children: () => createElement('main', { 'aria-label': '已登录' }),
|
||||
}),
|
||||
);
|
||||
|
||||
await screen.findByRole('main', { name: '登录' });
|
||||
const authCall = authCalls.find(
|
||||
(call) => call.command === 'read_client_auth_state',
|
||||
);
|
||||
expect(authCall?.args).toEqual({ expectedApiBaseUrl: CUSTOM_SERVER });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user