实现客户端登录七天滑动续期
需求:解决短效访问令牌到期后客户端一小时掉登录的问题。 实现:由 Electron Main 加密管理并轮换刷新凭据,按真实用户活动续期,七天闲置后清理会话,并补齐并发、迁移和终态回归测试。
This commit is contained in:
103
electron/services/works-square-runtime.ts
Normal file
103
electron/services/works-square-runtime.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { NIANCODE_USER_MODEL_ACCOUNT_ID } from '../../shared/user-model-config';
|
||||
import { logger } from '../utils/logger';
|
||||
import { clearWorksSquareAIGatewayCredential } from './works-square-ai-gateway';
|
||||
import { getProviderService } from './providers/provider-service';
|
||||
|
||||
export type WorksSquareRuntimeContext = {
|
||||
opencodeManager: {
|
||||
stop(): Promise<unknown>;
|
||||
};
|
||||
imageWorkspace?: {
|
||||
closeEventSessions?(options?: {
|
||||
accessToken?: string;
|
||||
tolerateRemoteFailure?: boolean;
|
||||
}): Promise<unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
let cleanupFlight: Promise<void> | null = null;
|
||||
let cleanupRequired = false;
|
||||
|
||||
async function runManagedWorksSquareRuntimeCleanup(
|
||||
ctx: WorksSquareRuntimeContext,
|
||||
accessToken?: string,
|
||||
tolerateRemoteFailure = false,
|
||||
): Promise<void> {
|
||||
// Revoke the in-memory derived credential synchronously; slower resource cleanup follows.
|
||||
clearWorksSquareAIGatewayCredential();
|
||||
|
||||
const results = await Promise.allSettled([
|
||||
(async () => await ctx.imageWorkspace?.closeEventSessions?.({
|
||||
accessToken,
|
||||
tolerateRemoteFailure,
|
||||
}))(),
|
||||
(async () => await ctx.opencodeManager.stop())(),
|
||||
(async () => {
|
||||
const deleted = await getProviderService().deleteAccountApiKey(
|
||||
NIANCODE_USER_MODEL_ACCOUNT_ID,
|
||||
);
|
||||
if (!deleted) throw new Error('provider API key storage rejected deletion');
|
||||
})(),
|
||||
]);
|
||||
const errors = results
|
||||
.filter((result): result is PromiseRejectedResult => result.status === 'rejected')
|
||||
.map((result) => (
|
||||
result.reason instanceof Error ? result.reason.message : String(result.reason)
|
||||
));
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Failed to clear managed Works Square runtime state: ${errors.join('; ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearManagedWorksSquareRuntime(
|
||||
ctx: WorksSquareRuntimeContext,
|
||||
accessToken?: string,
|
||||
tolerateRemoteFailure = false,
|
||||
): Promise<void> {
|
||||
if (cleanupFlight) return cleanupFlight;
|
||||
cleanupRequired = true;
|
||||
const flight = runManagedWorksSquareRuntimeCleanup(
|
||||
ctx,
|
||||
accessToken,
|
||||
tolerateRemoteFailure,
|
||||
)
|
||||
.then(() => {
|
||||
cleanupRequired = false;
|
||||
})
|
||||
.finally(() => {
|
||||
if (cleanupFlight === flight) cleanupFlight = null;
|
||||
});
|
||||
cleanupFlight = flight;
|
||||
return flight;
|
||||
}
|
||||
|
||||
export async function ensureManagedWorksSquareRuntimeClean(
|
||||
ctx: WorksSquareRuntimeContext,
|
||||
): Promise<void> {
|
||||
if (cleanupFlight) {
|
||||
try {
|
||||
await cleanupFlight;
|
||||
} catch {
|
||||
// Retry below with the current runtime context.
|
||||
}
|
||||
}
|
||||
if (cleanupRequired) await clearManagedWorksSquareRuntime(ctx);
|
||||
}
|
||||
|
||||
export async function clearManagedWorksSquareRuntimeBestEffort(
|
||||
ctx: WorksSquareRuntimeContext,
|
||||
reason: string,
|
||||
accessToken?: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await clearManagedWorksSquareRuntime(ctx, accessToken, true);
|
||||
} catch (error) {
|
||||
logger.error(`[auth] Failed to clear managed runtime after ${reason}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
export function resetManagedWorksSquareRuntimeForTests(): void {
|
||||
cleanupFlight = null;
|
||||
cleanupRequired = false;
|
||||
}
|
||||
Reference in New Issue
Block a user