实现客户端登录七天滑动续期

需求:解决短效访问令牌到期后客户端一小时掉登录的问题。

实现:由 Electron Main 加密管理并轮换刷新凭据,按真实用户活动续期,七天闲置后清理会话,并补齐并发、迁移和终态回归测试。
This commit is contained in:
2026-08-07 16:11:10 +08:00
parent 7b23cce67a
commit 86ece3a430
20 changed files with 2941 additions and 326 deletions

View File

@@ -29,6 +29,7 @@ import { useOpencodeStore } from './stores/opencode';
import { useProjectConfigStore } from './stores/project-config';
import { AI_MODULE_SELECTION_PATH } from './lib/ai-modules';
import { useUserSyncStore } from './stores/user-sync';
import { subscribeHostEvent } from '@/lib/host-events';
/**
@@ -243,6 +244,7 @@ function App() {
const initAuth = useAuthStore((state) => state.init);
const authInitialized = useAuthStore((state) => state.initialized);
const authenticated = useAuthStore((state) => state.isAuthenticated());
const authAccessToken = useAuthStore((state) => state.accessToken);
const bootstrapUserSync = useUserSyncStore((state) => state.bootstrap);
const setupReady = setupComplete || skipSetupForE2E || rendererOnlyPreview;
const authRequired = !skipSetupForE2E && !rendererOnlyPreview;
@@ -252,6 +254,35 @@ function App() {
void initAuth();
}, [initAuth]);
useEffect(() => {
const unsubscribe = subscribeHostEvent('auth:session-changed', (session) => {
useAuthStore.getState().applyMainSession(session);
});
return unsubscribe;
}, []);
useEffect(() => {
if (rendererOnlyPreview || !authInitialized || !authAccessToken) return;
const recordActivity = (event: Event) => {
if (!event.isTrusted) return;
void useAuthStore.getState().markActivity();
};
window.addEventListener('pointerdown', recordActivity, { passive: true });
window.addEventListener('touchstart', recordActivity, { passive: true });
window.addEventListener('wheel', recordActivity, { passive: true });
window.addEventListener('keydown', recordActivity);
window.addEventListener('focus', recordActivity);
return () => {
window.removeEventListener('pointerdown', recordActivity);
window.removeEventListener('touchstart', recordActivity);
window.removeEventListener('wheel', recordActivity);
window.removeEventListener('keydown', recordActivity);
window.removeEventListener('focus', recordActivity);
};
}, [authAccessToken, authInitialized, rendererOnlyPreview]);
useEffect(() => {
initSettings();
}, [initSettings]);