// Cầu nối main thread ↔ worker: gọi lệnh dạng Promise và phát sự kiện cho giao diện. import type { WorkerAction, WorkerEvent } from '../shared/protocol'; export class WorkerError extends Error { code?: string; constructor(message: string, name?: string, code?: string) { super(message); this.name = name ?? 'Error'; this.code = code; } } type Listener = (event: WorkerEvent) => void; export class AgentClient { private worker: Worker; private nextId = 1; private pending = new Map void; reject: (e: Error) => void }>(); private listeners = new Set(); /** Kết quả `initialized` đầu tiên — giao diện chờ nó trước khi cho phép thao tác. */ readonly ready: Promise>; constructor() { this.worker = new Worker(new URL('../worker/agent.worker.ts', import.meta.url), { type: 'module' }); let resolveReady!: (e: Extract) => void; this.ready = new Promise((resolve) => (resolveReady = resolve)); this.worker.onmessage = ({ data }: MessageEvent) => { if (data.type === 'reply') { const p = this.pending.get(data.id); if (!p) return; this.pending.delete(data.id); if (data.error !== undefined) p.reject(new WorkerError(data.error, data.errorName, data.errorCode)); else p.resolve(data.value); return; } if (data.type === 'initialized') resolveReady(data); this.listeners.forEach((l) => l(data)); }; const fail = (message: string) => { const error = new WorkerError(message); this.pending.forEach((p) => p.reject(error)); this.pending.clear(); this.listeners.forEach((l) => l({ type: 'fatal', error: message })); }; this.worker.onerror = (e) => fail(e.message || 'The background worker stopped unexpectedly. Reload the page.'); this.worker.onmessageerror = () => fail('The background worker sent an unreadable message.'); } call(action: WorkerAction['action'], args?: Record): Promise { const id = this.nextId++; return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); this.worker.postMessage({ id, action, args }); }); } subscribe(listener: Listener): () => void { this.listeners.add(listener); return () => this.listeners.delete(listener); } } let singleton: AgentClient | undefined; /** Một worker duy nhất cho cả ứng dụng (an toàn với React StrictMode). */ export function getClient(): AgentClient { return (singleton ??= new AgentClient()); }