{"record":{"id":"670a32cdc0d5af0a","repo":"Stirling-Tools/Stirling-PDF","slug":"no-authentication-token-available","errorCode":null,"errorMessage":"No authentication token available","messagePattern":"No authentication token available","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"frontend/editor/src/desktop/services/billing.ts","lineNumber":42,"sourceCode":"export type {\n  CheckoutParams,\n  CheckoutSession,\n  PortalParams,\n  PortalSession,\n} from \"@cloud/services/billing\";\n\n/**\n * Deep-link the SaaS billing backend uses as Stripe's success/cancel/return\n * URL on desktop. The OS routes it back to the running app, which the deep-link\n * handler picks up to refresh the wallet after checkout/portal.\n */\nconst DESKTOP_BILLING_RETURN_URL = \"stirlingpdf://billing/return\";\n\n/** Resolve the desktop JWT, throwing a friendly error when signed out. */\nasync function requireToken(): Promise<string> {\n  const token = await authService.getAuthToken();\n  if (!token) {\n    throw new Error(\"No authentication token available\");\n  }\n  return token;\n}\n\n/**\n * Create a Stripe Checkout Session for the PAYG subscription via the\n * {@code create-checkout-session} edge function (see StripeCheckoutPanel),\n * routed through Tauri (explicit bearer, deep-link callback). The Tauri webview\n * has no CSP, so the component mounts the embedded Stripe iframe from the\n * returned clientSecret (falling back to the hosted url otherwise).\n */\nexport async function createCheckoutSession(\n  params: CheckoutParams,\n): Promise<CheckoutSession> {\n  const token = await requireToken();\n\n  const { data, error } = await supabase.functions.invoke<{\n    client_secret?: string;","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/Stirling-Tools/Stirling-PDF/blob/9ef20dcab80b85041912f045e17a6aea1d08f969/frontend/editor/src/desktop/services/billing.ts#L24-L60","documentation":"Thrown by requireToken() in the desktop billing service when authService.getAuthToken() resolves to a falsy value. The user is not signed in, so there is no desktop JWT to attach as the Bearer header for the Stripe edge-function calls (create-checkout-session / create-customer-portal-session).","triggerScenarios":"Calling createCheckoutSession() or createPortalSession() while signed out: no token in the Tauri store, the session expired and refresh failed, or logout just ran. getAuthToken() returns null/empty.","commonSituations":"Billing/wallet UI rendered before login completes; JWT expired and the refresh path failed silently; user navigated to upgrade flow after signing out in another window.","solutions":["Gate the billing UI behind an authenticated check and prompt sign-in before calling these functions.","Call authService.ensureSession() / await a valid token before invoking billing, retrying once after a refresh.","If the token genuinely expired, route the user to the login screen before billing."],"exampleFix":"// before\nconst session = await createCheckoutSession(params);\n\n// after: ensure a token exists first\nconst token = await authService.getAuthToken();\nif (!token) { navigateToLogin(); return; }\nconst session = await createCheckoutSession(params);","handlingStrategy":"validation","validationCode":"const token = await authService.getAuthToken();\nif (!token) { navigateToLogin(); return; }","typeGuard":"function isMissingToken(e: unknown): e is Error {\n  return e instanceof Error && e.message === 'No authentication token available';\n}","tryCatchPattern":"try { await createCheckoutSession(params); }\ncatch (e) {\n  if (isMissingToken(e)) { navigateToLogin(); return; }\n  throw e;\n}","preventionTips":["Gate billing/wallet UI behind an authenticated check.","Call getAuthToken() once and pass it down instead of re-resolving per call.","Re-check auth before any Stripe flow since tokens expire."],"tags":["authentication","billing","stripe","token","desktop"],"backgroundTag":null,"analyzedSha":"9ef20dcab80b85041912f045e17a6aea1d08f969","analyzedAt":"2026-08-13T22:11:39.827Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}