// @effect-diagnostics globalTimers:off -- This protocol broker owns cancellable request deadlines outside the Effect runtime. import { DESKTOP_APP_ACTIVATION_PROTOCOL_VERSION, type DesktopAppActivationFailure, type DesktopAppActivationRequest, type DesktopAppActivationResponse, } from "@t3tools/contracts"; interface PendingActivation { readonly request: DesktopAppActivationRequest; readonly resolve: (response: DesktopAppActivationResponse) => void; readonly timeout: ReturnType; dispatched: boolean; } type RendererSender = (request: DesktopAppActivationRequest) => void; function failure( requestId: string, code: DesktopAppActivationFailure["code"], message: string, ): DesktopAppActivationFailure { return { version: DESKTOP_APP_ACTIVATION_PROTOCOL_VERSION, requestId, ok: false, code, message, }; } /** Holds CLI requests until the real desktop renderer is ready to handle them. */ export class DesktopAppActivationBroker { readonly #pending = new Map(); readonly #requestTimeoutMs: number; readonly #activate: () => void; #renderer: RendererSender | null = null; #closed = false; constructor(input: { readonly requestTimeoutMs: number; readonly activate: () => void }) { this.#requestTimeoutMs = input.requestTimeoutMs; this.#activate = input.activate; } request(request: DesktopAppActivationRequest): Promise { if (this.#closed) { return Promise.resolve( failure(request.requestId, "renderer-unavailable", "T3 Code is shutting down."), ); } if (this.#pending.has(request.requestId)) { return Promise.resolve( failure(request.requestId, "invalid-request", "The request id is already in use."), ); } const response = new Promise((resolve) => { const timeout = setTimeout(() => { this.#settle( failure( request.requestId, "request-timeout", "The desktop app did not finish opening the project in time.", ), ); }, this.#requestTimeoutMs); this.#pending.set(request.requestId, { request, resolve, timeout, dispatched: false, }); }); this.#activate(); this.#flush(); return response; } registerRenderer(send: RendererSender): void { this.#renderer = send; this.#flush(); } clearRenderer(): void { this.#renderer = null; for (const pending of this.#pending.values()) { if (pending.dispatched) { this.#settle( failure( pending.request.requestId, "renderer-unavailable", "The T3 Code window closed before it opened the project.", ), ); } } } complete(response: DesktopAppActivationResponse): void { this.#settle(response); } cancel(requestId: string): void { this.#settle( failure(requestId, "renderer-unavailable", "The command closed before T3 Code was ready."), ); } close(): void { this.#closed = true; this.#renderer = null; for (const pending of this.#pending.values()) { this.#settle( failure(pending.request.requestId, "renderer-unavailable", "T3 Code is shutting down."), ); } } #flush(): void { const renderer = this.#renderer; if (renderer === null) return; if ([...this.#pending.values()].some((pending) => pending.dispatched)) return; for (const pending of this.#pending.values()) { if (pending.dispatched) continue; try { pending.dispatched = true; renderer(pending.request); } catch { pending.dispatched = false; this.#renderer = null; } return; } } #settle(response: DesktopAppActivationResponse): void { const pending = this.#pending.get(response.requestId); if (!pending) return; clearTimeout(pending.timeout); this.#pending.delete(response.requestId); pending.resolve(response); this.#flush(); } }