From 8624bd00696a51d39267a20d79379c178a962547 Mon Sep 17 00:00:00 2001 From: Derpedyea Date: Mon, 4 May 2026 22:14:46 -0400 Subject: [PATCH 1/2] Refresh worker auth after transcription and health checks - Poll worker auth in the desktop app after transcriptions and on a timer - Fetch live billing on `/health/auth` while keeping cached billing for normal auth --- apps/desktop/main.cjs | 66 ++++++++++++++++++++++++++++++++++++++++ apps/worker/src/index.ts | 12 ++++++-- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/apps/desktop/main.cjs b/apps/desktop/main.cjs index 62edfc8..378e65d 100644 --- a/apps/desktop/main.cjs +++ b/apps/desktop/main.cjs @@ -39,6 +39,9 @@ let activeHotkeyBinding = null; let updateCheckPromise = null; let updateReadyToInstall = false; let updateInstallRequested = false; +let workerAuthPollTimer = null; +let workerAuthPollInFlight = false; +let usageCreditRefreshTimers = []; let desktopAuth = { token: "", account: null, @@ -52,6 +55,8 @@ const MAX_DICTIONARY_ENTRIES = 1000; const MAX_DICTIONARY_SEND_ENTRIES = 200; const MAX_DICTIONARY_PHRASE_LENGTH = 60; const MAX_DICTIONARY_REPLACEMENT_LENGTH = 120; +const WORKER_AUTH_POLL_INTERVAL_MS = 5 * 60 * 1000; +const POST_TRANSCRIPTION_USAGE_REFRESH_DELAYS_MS = [5_000, 20_000, 60_000]; const hotkeyState = { ctrlDown: false, @@ -131,6 +136,7 @@ if (!singleInstanceLock) { createTray(); registerNativeHotkey(); configureAutoUpdates(); + configureWorkerAuthPolling(); void refreshWorkerAuth(); }); } @@ -161,6 +167,11 @@ app.on("will-quit", () => { } logSink?.close(); logSink = null; + clearUsageCreditRefreshTimers(); + if (workerAuthPollTimer) { + clearInterval(workerAuthPollTimer); + workerAuthPollTimer = null; + } }); function createWindow() { @@ -569,15 +580,68 @@ function registerIpc() { } exitDictationWindowMode(); patchStatus({ state: "idle", message: "Ready", lastTranscript: result }); + scheduleUsageCreditRefresh("transcription-complete"); } else { logWarn("transcription:submit:empty-text", summarizeTranscriptionResult(result)); exitDictationWindowMode(); patchStatus({ state: "idle", message: "No speech detected" }); + scheduleUsageCreditRefresh("transcription-empty"); } return result; }); } +function configureWorkerAuthPolling() { + if (workerAuthPollTimer) { + return; + } + + workerAuthPollTimer = setInterval(() => { + void refreshWorkerAuthFromPoll("interval"); + }, WORKER_AUTH_POLL_INTERVAL_MS); + workerAuthPollTimer.unref?.(); +} + +async function refreshWorkerAuthFromPoll(reason) { + if (!desktopAuth.token || workerAuthPollInFlight) { + return; + } + + if (status.state === "recording" || status.state === "transcribing" || status.state === "pasting") { + return; + } + + workerAuthPollInFlight = true; + try { + logInfo("worker:auth-poll:start", { reason }); + await refreshWorkerAuth(); + } catch (error) { + logWarn("worker:auth-poll:failed", { reason, error: formatErrorForLog(error) }); + } finally { + workerAuthPollInFlight = false; + } +} + +function scheduleUsageCreditRefresh(reason) { + clearUsageCreditRefreshTimers(); + + for (const delayMs of POST_TRANSCRIPTION_USAGE_REFRESH_DELAYS_MS) { + const timer = setTimeout(() => { + usageCreditRefreshTimers = usageCreditRefreshTimers.filter((candidate) => candidate !== timer); + void refreshWorkerAuthFromPoll(reason); + }, delayMs); + timer.unref?.(); + usageCreditRefreshTimers.push(timer); + } +} + +function clearUsageCreditRefreshTimers() { + for (const timer of usageCreditRefreshTimers) { + clearTimeout(timer); + } + usageCreditRefreshTimers = []; +} + function configureAutoUpdates() { if (!app.isPackaged) { logInfo("updates:skip-unpackaged"); @@ -848,6 +912,7 @@ async function startDeviceLogin() { } desktopAuth = { token: "", account: null, billing: null }; + clearUsageCreditRefreshTimers(); authGeneration += 1; saveDesktopAuth(); patchStatus({ @@ -917,6 +982,7 @@ async function pollDeviceLogin(deviceCode, deviceName) { async function logoutDevice() { desktopAuth = { token: "", account: null, billing: null }; + clearUsageCreditRefreshTimers(); authGeneration += 1; saveDesktopAuth(); patchStatus({ diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index cb92b71..cdea6f3 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -286,7 +286,7 @@ app.get("/health", (c) => ); app.get("/health/auth", async (c) => { - const authorized = await authorizeDesktop(c.req.raw, c.env); + const authorized = await authorizeDesktop(c.req.raw, c.env, { liveBilling: true }); if (!authorized) { return c.json({ error: "Unauthorized" }, 401); } @@ -947,7 +947,11 @@ function isAuthSession(value: unknown): value is AuthSession { return isRecord(value) && isRecord(value.user) && typeof value.user.id === "string" && typeof value.user.email === "string" && typeof value.user.name === "string"; } -async function authorizeDesktop(request: Request, env: Env): Promise { +async function authorizeDesktop( + request: Request, + env: Env, + options: { liveBilling?: boolean } = {} +): Promise { const authorization = request.headers.get("authorization") ?? ""; const token = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length).trim() : ""; if (!token) { @@ -967,7 +971,9 @@ async function authorizeDesktop(request: Request, env: Env): Promise Date: Mon, 4 May 2026 22:44:58 -0400 Subject: [PATCH 2/2] Defer usage credit refresh until auth polling is idle - Skip desktop usage refresh while dictation or auth polling is busy - Add a live billing timeout with cached fallback for desktop auth --- apps/desktop/main.cjs | 33 ++++++++++++++++++++++++++------- apps/worker/src/index.ts | 24 +++++++++++++++++++++++- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/apps/desktop/main.cjs b/apps/desktop/main.cjs index 378e65d..f8cb9c0 100644 --- a/apps/desktop/main.cjs +++ b/apps/desktop/main.cjs @@ -57,6 +57,7 @@ const MAX_DICTIONARY_PHRASE_LENGTH = 60; const MAX_DICTIONARY_REPLACEMENT_LENGTH = 120; const WORKER_AUTH_POLL_INTERVAL_MS = 5 * 60 * 1000; const POST_TRANSCRIPTION_USAGE_REFRESH_DELAYS_MS = [5_000, 20_000, 60_000]; +const DEFERRED_USAGE_REFRESH_RETRY_MS = 5_000; const hotkeyState = { ctrlDown: false, @@ -607,7 +608,7 @@ async function refreshWorkerAuthFromPoll(reason) { return; } - if (status.state === "recording" || status.state === "transcribing" || status.state === "pasting") { + if (isDictationBusy()) { return; } @@ -626,12 +627,7 @@ function scheduleUsageCreditRefresh(reason) { clearUsageCreditRefreshTimers(); for (const delayMs of POST_TRANSCRIPTION_USAGE_REFRESH_DELAYS_MS) { - const timer = setTimeout(() => { - usageCreditRefreshTimers = usageCreditRefreshTimers.filter((candidate) => candidate !== timer); - void refreshWorkerAuthFromPoll(reason); - }, delayMs); - timer.unref?.(); - usageCreditRefreshTimers.push(timer); + scheduleUsageCreditRefreshAttempt(reason, delayMs); } } @@ -642,6 +638,29 @@ function clearUsageCreditRefreshTimers() { usageCreditRefreshTimers = []; } +function scheduleUsageCreditRefreshAttempt(reason, delayMs) { + const timer = setTimeout(() => { + usageCreditRefreshTimers = usageCreditRefreshTimers.filter((candidate) => candidate !== timer); + + if (!desktopAuth.token) { + return; + } + + if (workerAuthPollInFlight || isDictationBusy()) { + scheduleUsageCreditRefreshAttempt(reason, DEFERRED_USAGE_REFRESH_RETRY_MS); + return; + } + + void refreshWorkerAuthFromPoll(reason); + }, delayMs); + timer.unref?.(); + usageCreditRefreshTimers.push(timer); +} + +function isDictationBusy() { + return status.state === "recording" || status.state === "transcribing" || status.state === "pasting"; +} + function configureAutoUpdates() { if (!app.isPackaged) { logInfo("updates:skip-unpackaged"); diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts index cdea6f3..79c5810 100644 --- a/apps/worker/src/index.ts +++ b/apps/worker/src/index.ts @@ -203,6 +203,7 @@ const CLEANUP_USER_PROMPT_SUFFIX = ` `; const app = new Hono<{ Bindings: Env }>(); +const DESKTOP_AUTH_LIVE_BILLING_TIMEOUT_MS = 1_500; app.use( "/health/*", @@ -972,7 +973,7 @@ async function authorizeDesktop( } const billing = options.liveBilling - ? await getBilling(env, device.user_id) + ? await getDesktopAuthLiveBilling(env, device.user_id) : await getCachedBilling(env, device.user_id); return { @@ -983,6 +984,27 @@ async function authorizeDesktop( }; } +async function getDesktopAuthLiveBilling(env: Env, userId: string): Promise { + try { + return await withTimeout( + getBilling(env, userId), + DESKTOP_AUTH_LIVE_BILLING_TIMEOUT_MS, + `desktop auth billing timed out after ${DESKTOP_AUTH_LIVE_BILLING_TIMEOUT_MS}ms` + ); + } catch (error) { + console.warn( + JSON.stringify({ + level: "warn", + event: "desktop-auth:live-billing-fallback", + userId, + timeoutMs: DESKTOP_AUTH_LIVE_BILLING_TIMEOUT_MS, + error: formatError(error) + }) + ); + return getCachedBilling(env, userId); + } +} + async function getBilling(env: Env, userId: string): Promise { const billing = await ensureBillingProfile(env, userId); if (!isPolarConfigured(env)) {