需求:解决短效访问令牌到期后客户端一小时掉登录的问题。 实现:由 Electron Main 加密管理并轮换刷新凭据,按真实用户活动续期,七天闲置后清理会话,并补齐并发、迁移和终态回归测试。
71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
import { createHostEventSource } from './host-api';
|
|
|
|
let eventSource: EventSource | null = null;
|
|
|
|
const HOST_EVENT_TO_IPC_CHANNEL: Record<string, string> = {
|
|
'opencode:status': 'opencode:status',
|
|
'opencode:stderr': 'opencode:stderr',
|
|
'oauth:code': 'oauth:code',
|
|
'oauth:success': 'oauth:success',
|
|
'oauth:error': 'oauth:error',
|
|
'agent-browser:show': 'agent-browser:show',
|
|
'agent-browser:state': 'agent-browser:state',
|
|
'auth:session-changed': 'auth:session-changed',
|
|
};
|
|
|
|
function getEventSource(): EventSource {
|
|
if (!eventSource) {
|
|
eventSource = createHostEventSource();
|
|
}
|
|
return eventSource;
|
|
}
|
|
|
|
function allowSseFallback(): boolean {
|
|
try {
|
|
return window.localStorage.getItem('niancode:allow-sse-fallback') === '1';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function subscribeHostEvent<T = unknown>(
|
|
eventName: string,
|
|
handler: (payload: T) => void,
|
|
): () => void {
|
|
const ipc = window.electron?.ipcRenderer;
|
|
const ipcChannel = HOST_EVENT_TO_IPC_CHANNEL[eventName];
|
|
if (ipcChannel && ipc?.on && ipc?.off) {
|
|
const listener = (payload: unknown) => {
|
|
handler(payload as T);
|
|
};
|
|
// preload's `on()` wraps the callback in an internal subscription function
|
|
// and returns a cleanup function that removes that exact wrapper. We MUST
|
|
// use the returned cleanup rather than calling `off(channel, listener)`,
|
|
// because `listener` !== the internal wrapper and removeListener would be
|
|
// a no-op, leaking the subscription.
|
|
const unsubscribe = ipc.on(ipcChannel, listener);
|
|
if (typeof unsubscribe === 'function') {
|
|
return unsubscribe;
|
|
}
|
|
// Fallback for environments where on() doesn't return cleanup
|
|
return () => {
|
|
ipc.off(ipcChannel, listener);
|
|
};
|
|
}
|
|
|
|
if (!allowSseFallback()) {
|
|
console.warn(`[host-events] no IPC mapping for event "${eventName}", SSE fallback disabled`);
|
|
return () => {};
|
|
}
|
|
|
|
const source = getEventSource();
|
|
const listener = (event: Event) => {
|
|
const payload = JSON.parse((event as MessageEvent).data) as T;
|
|
handler(payload);
|
|
};
|
|
source.addEventListener(eventName, listener);
|
|
return () => {
|
|
source.removeEventListener(eventName, listener);
|
|
};
|
|
}
|