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

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

实现:由 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

@@ -70,6 +70,15 @@ import { AgentBrowserModule, ElectronAgentBrowserAdapter } from '../agent-browse
import { browserOAuthManager } from '../utils/browser-oauth';
import { createProjectProgressSync } from '../services/project-progress-sync';
import { createWorksCloudDeployment } from '../services/works-cloud-deployment';
import {
consumeWorksSquareStartupRuntimeCleanupRequired,
getWorksSquareSessionRestoreStatus,
getWorksSquareSessionSnapshot,
initializeWorksSquareSession,
subscribeWorksSquareSession,
type WorksSquareSessionChangeReason,
} from '../services/works-square-session';
import { clearManagedWorksSquareRuntimeBestEffort } from '../services/works-square-runtime';
import { getPort } from '../utils/config';
import { initializeMeowaGameAssetsCredential } from '../api/routes/meowa-game-assets';
import {
@@ -318,6 +327,26 @@ function registerMakeloreProtocolClient(): void {
function createMainWindow(): BrowserWindow {
const win = createWindow();
const sendAuthSession = (
session: ReturnType<typeof getWorksSquareSessionSnapshot>,
reason: WorksSquareSessionChangeReason = 'changed',
previousSession: ReturnType<typeof getWorksSquareSessionSnapshot> = null,
) => {
if (!session && getWorksSquareSessionRestoreStatus() === 'unavailable') return;
if (!win.isDestroyed() && !win.webContents.isDestroyed()) {
win.webContents.send('auth:session-changed', session);
}
if (!session && reason === 'terminal') {
void clearManagedWorksSquareRuntimeBestEffort({
opencodeManager,
imageWorkspace: imageWorkspaceModule ?? undefined,
}, 'terminal session invalidation', previousSession?.accessToken);
}
};
const unsubscribeAuthSession = subscribeWorksSquareSession(sendAuthSession);
win.webContents.on('did-finish-load', () => {
sendAuthSession(getWorksSquareSessionSnapshot());
});
const closeAgentBrowserForHostRenderer = (reason: string): void => {
void agentBrowser?.close().catch((error) => {
@@ -358,6 +387,7 @@ function createMainWindow(): BrowserWindow {
});
win.on('closed', () => {
unsubscribeAuthSession();
const browser = agentBrowser;
agentBrowser = null;
void browser?.dispose().catch((error) => {
@@ -409,14 +439,15 @@ async function initialize(): Promise<void> {
logger.info('Running in E2E mode: startup side effects minimized');
}
// Restore authentication before any background service or renderer can request a token.
await initializeWorksSquareSession();
opencodeProjectStore = createProjectStore(await createElectronProjectStorage());
if (!isE2EMode) {
projectProgressSync = createProjectProgressSync(opencodeProjectStore);
void projectProgressSync.start();
}
worksCloudDeployment = createWorksCloudDeployment(opencodeProjectStore);
if (!isE2EMode) void worksCloudDeployment.start();
const localImageWorkspaceEnabled = isLocalImageWorkspaceDevelopmentEnabled({
isPackaged: app.isPackaged,
@@ -432,6 +463,12 @@ async function initialize(): Promise<void> {
},
});
imageWorkspaceModule = imageWorkspace;
if (consumeWorksSquareStartupRuntimeCleanupRequired()) {
await clearManagedWorksSquareRuntimeBestEffort({
opencodeManager,
imageWorkspace,
}, 'expired persisted session during startup');
}
if (localImageWorkspaceEnabled) {
logger.info('AI painting workspace is using local development storage');
} else {
@@ -444,6 +481,10 @@ async function initialize(): Promise<void> {
// Create the main window
const window = createMainWindow();
agentBrowser = new AgentBrowserModule(new ElectronAgentBrowserAdapter(window));
if (!isE2EMode) {
void projectProgressSync?.start();
void worksCloudDeployment.start();
}
// Create system tray
if (!isE2EMode) {