Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions apps/desktop/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -52,6 +55,9 @@ 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 DEFERRED_USAGE_REFRESH_RETRY_MS = 5_000;

const hotkeyState = {
ctrlDown: false,
Expand Down Expand Up @@ -131,6 +137,7 @@ if (!singleInstanceLock) {
createTray();
registerNativeHotkey();
configureAutoUpdates();
configureWorkerAuthPolling();
void refreshWorkerAuth();
});
}
Expand Down Expand Up @@ -161,6 +168,11 @@ app.on("will-quit", () => {
}
logSink?.close();
logSink = null;
clearUsageCreditRefreshTimers();
if (workerAuthPollTimer) {
clearInterval(workerAuthPollTimer);
workerAuthPollTimer = null;
}
});

function createWindow() {
Expand Down Expand Up @@ -569,15 +581,86 @@ 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 (isDictationBusy()) {
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) {
scheduleUsageCreditRefreshAttempt(reason, delayMs);
}
}

function clearUsageCreditRefreshTimers() {
for (const timer of usageCreditRefreshTimers) {
clearTimeout(timer);
}
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");
Expand Down Expand Up @@ -848,6 +931,7 @@ async function startDeviceLogin() {
}

desktopAuth = { token: "", account: null, billing: null };
clearUsageCreditRefreshTimers();
authGeneration += 1;
saveDesktopAuth();
patchStatus({
Expand Down Expand Up @@ -917,6 +1001,7 @@ async function pollDeviceLogin(deviceCode, deviceName) {

async function logoutDevice() {
desktopAuth = { token: "", account: null, billing: null };
clearUsageCreditRefreshTimers();
authGeneration += 1;
saveDesktopAuth();
patchStatus({
Expand Down
34 changes: 31 additions & 3 deletions apps/worker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ const CLEANUP_USER_PROMPT_SUFFIX = `
</transcript>`;

const app = new Hono<{ Bindings: Env }>();
const DESKTOP_AUTH_LIVE_BILLING_TIMEOUT_MS = 1_500;

app.use(
"/health/*",
Expand Down Expand Up @@ -286,7 +287,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<TranscriptionError>({ error: "Unauthorized" }, 401);
}
Expand Down Expand Up @@ -947,7 +948,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<DesktopAuthorization | null> {
async function authorizeDesktop(
request: Request,
env: Env,
options: { liveBilling?: boolean } = {}
): Promise<DesktopAuthorization | null> {
const authorization = request.headers.get("authorization") ?? "";
const token = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length).trim() : "";
if (!token) {
Expand All @@ -967,7 +972,9 @@ async function authorizeDesktop(request: Request, env: Env): Promise<DesktopAuth
return null;
}

const billing = await getCachedBilling(env, device.user_id);
const billing = options.liveBilling
? await getDesktopAuthLiveBilling(env, device.user_id)
: await getCachedBilling(env, device.user_id);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return {
deviceId: device.id,
Expand All @@ -977,6 +984,27 @@ async function authorizeDesktop(request: Request, env: Env): Promise<DesktopAuth
};
}

async function getDesktopAuthLiveBilling(env: Env, userId: string): Promise<AccountBillingStatus> {
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<AccountBillingStatus> {
const billing = await ensureBillingProfile(env, userId);
if (!isPolarConfigured(env)) {
Expand Down
Loading