Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
import { abortableDelay } from "./async.js";
|
|
|
|
export type ModelOAuthCompletion =
|
|
| { status: "pending" }
|
|
| { status: "ready" }
|
|
| { status: "error"; error: string };
|
|
|
|
type OAuthControllerRef = { current: AbortController | null };
|
|
|
|
export function cancelModelOAuthAttempt(ref: OAuthControllerRef, reset: () => void) {
|
|
const controller = ref.current;
|
|
controller?.abort();
|
|
if (ref.current !== controller) return;
|
|
ref.current = null;
|
|
reset();
|
|
}
|
|
|
|
export function finishModelOAuthAttempt(
|
|
ref: OAuthControllerRef,
|
|
controller: AbortController,
|
|
resetBusy: () => void,
|
|
) {
|
|
if (ref.current !== controller) return;
|
|
ref.current = null;
|
|
resetBusy();
|
|
}
|
|
|
|
export async function waitForModelOAuthCompletion(
|
|
complete: () => Promise<ModelOAuthCompletion>,
|
|
options: { signal?: AbortSignal; attempts?: number; pollIntervalMs?: number } = {},
|
|
): Promise<{ status: "ready" }> {
|
|
const attempts = options.attempts ?? 180;
|
|
const pollIntervalMs = options.pollIntervalMs ?? 5000;
|
|
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
throwIfAborted(options.signal);
|
|
const result = await complete();
|
|
throwIfAborted(options.signal);
|
|
if (result.status === "ready") return result;
|
|
if (result.status === "error") throw new Error(result.error);
|
|
if (attempt < attempts) await abortableDelay(pollIntervalMs, options.signal);
|
|
}
|
|
throw new Error("Sign-in timed out. Try again.");
|
|
}
|
|
|
|
function throwIfAborted(signal?: AbortSignal) {
|
|
if (signal?.aborted) throw signal.reason ?? new Error("OAuth polling cancelled");
|
|
}
|