Files
makelore/src/lib/host-events.ts
brother7 5a275b93a7 feat: remove legacy OpenCode runtime
Cut product flows over to Coding/Pi and retain only the migration-owned v1 boundary. Promote supported native optional packages because electron-builder omitted pnpm transitive optional closure from the packaged ASAR.
2026-08-24 12:17:43 +08:00

72 lines
2.2 KiB
TypeScript

import { createHostEventSource } from './host-api';
let eventSource: EventSource | null = null;
const HOST_EVENT_TO_IPC_CHANNEL: Record<string, string> = {
'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',
'lifecycle:pause': 'lifecycle:pause',
'lifecycle:sleep': 'lifecycle:sleep',
'release-job:status': 'release-job:status',
};
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);
};
}