@athenaintel/react 0.56.0 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +75 -1
  2. package/dist/agent-runtime/src/inbound-channel.cjs +96 -0
  3. package/dist/agent-runtime/src/inbound-channel.cjs.map +1 -0
  4. package/dist/agent-runtime/src/inbound-channel.d.ts +75 -0
  5. package/dist/agent-runtime/src/inbound-channel.js +96 -0
  6. package/dist/agent-runtime/src/inbound-channel.js.map +1 -0
  7. package/dist/agent-runtime/src/index.d.ts +2 -1
  8. package/dist/agent-runtime/src/interrupts.cjs +45 -3
  9. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  10. package/dist/agent-runtime/src/interrupts.d.ts +51 -4
  11. package/dist/agent-runtime/src/interrupts.js +46 -4
  12. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  13. package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
  14. package/dist/agent-runtime/src/offline-status.d.ts +25 -0
  15. package/dist/agent-runtime/src/offline-status.js.map +1 -1
  16. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +8 -8
  17. package/dist/agent-runtime/src/tool-metadata.cjs +8 -0
  18. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  19. package/dist/agent-runtime/src/tool-metadata.js +8 -0
  20. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  21. package/dist/auth/AthenaSSOProvider.cjs +54 -12
  22. package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
  23. package/dist/auth/AthenaSSOProvider.d.ts +23 -2
  24. package/dist/auth/AthenaSSOProvider.js +54 -12
  25. package/dist/auth/AthenaSSOProvider.js.map +1 -1
  26. package/dist/auth/index.cjs +2 -0
  27. package/dist/auth/index.cjs.map +1 -1
  28. package/dist/auth/index.d.ts +1 -0
  29. package/dist/auth/index.js +2 -0
  30. package/dist/auth/index.js.map +1 -1
  31. package/dist/auth/sign-back-in.cjs +141 -0
  32. package/dist/auth/sign-back-in.cjs.map +1 -0
  33. package/dist/auth/sign-back-in.d.ts +117 -0
  34. package/dist/auth/sign-back-in.js +141 -0
  35. package/dist/auth/sign-back-in.js.map +1 -0
  36. package/dist/chat/AthenaChat.cjs +21 -3
  37. package/dist/chat/AthenaChat.cjs.map +1 -1
  38. package/dist/chat/AthenaChat.js +21 -3
  39. package/dist/chat/AthenaChat.js.map +1 -1
  40. package/dist/chat/StatewireApprovalCard.cjs +6 -2
  41. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  42. package/dist/chat/StatewireApprovalCard.d.ts +8 -1
  43. package/dist/chat/StatewireApprovalCard.js +7 -3
  44. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  45. package/dist/chat-ui/index.cjs +5 -0
  46. package/dist/chat-ui/index.cjs.map +1 -1
  47. package/dist/chat-ui/index.d.ts +1 -0
  48. package/dist/chat-ui/index.js +6 -1
  49. package/dist/chat-ui/index.js.map +1 -1
  50. package/dist/chat-ui/messages/inbound-channel-message.cjs +242 -0
  51. package/dist/chat-ui/messages/inbound-channel-message.cjs.map +1 -0
  52. package/dist/chat-ui/messages/inbound-channel-message.d.ts +53 -0
  53. package/dist/chat-ui/messages/inbound-channel-message.js +242 -0
  54. package/dist/chat-ui/messages/inbound-channel-message.js.map +1 -0
  55. package/dist/chat-ui/messages/inbound-channel-reader.cjs +184 -0
  56. package/dist/chat-ui/messages/inbound-channel-reader.cjs.map +1 -0
  57. package/dist/chat-ui/messages/inbound-channel-reader.d.ts +65 -0
  58. package/dist/chat-ui/messages/inbound-channel-reader.js +184 -0
  59. package/dist/chat-ui/messages/inbound-channel-reader.js.map +1 -0
  60. package/dist/chat-ui/messages/message-components.cjs +32 -15
  61. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  62. package/dist/chat-ui/messages/message-components.d.ts +7 -0
  63. package/dist/chat-ui/messages/message-components.js +33 -16
  64. package/dist/chat-ui/messages/message-components.js.map +1 -1
  65. package/dist/composer/sign-back-in-draft.cjs +62 -0
  66. package/dist/composer/sign-back-in-draft.cjs.map +1 -0
  67. package/dist/composer/sign-back-in-draft.d.ts +34 -0
  68. package/dist/composer/sign-back-in-draft.js +62 -0
  69. package/dist/composer/sign-back-in-draft.js.map +1 -0
  70. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  71. package/dist/diagnostics/athena-diagnostics.d.ts +6 -1
  72. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  73. package/dist/index.cjs +9 -0
  74. package/dist/index.cjs.map +1 -1
  75. package/dist/index.d.ts +2 -0
  76. package/dist/index.js +9 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/provider/AthenaProvider.cjs +48 -3
  79. package/dist/provider/AthenaProvider.cjs.map +1 -1
  80. package/dist/provider/AthenaProvider.js +49 -4
  81. package/dist/provider/AthenaProvider.js.map +1 -1
  82. package/dist/react/package.json.cjs +1 -1
  83. package/dist/react/package.json.js +1 -1
  84. package/dist/runtime/auth.cjs +11 -0
  85. package/dist/runtime/auth.cjs.map +1 -1
  86. package/dist/runtime/auth.d.ts +18 -1
  87. package/dist/runtime/auth.js +11 -0
  88. package/dist/runtime/auth.js.map +1 -1
  89. package/dist/styles.css +1 -1
  90. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"auth.cjs","sources":["../../src/runtime/auth.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { AthenaTokenRenewer } from '../auth/credential-source';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Hosts under `athenaintel.com` that serve customer-written code:\n * - Marathon's computer and application apps: `{id}.app.athenaintel.com`,\n * `{id}.staging-app.athenaintel.com`, `{id}.canary-app.athenaintel.com`, an\n * environment's `{id}.app.{env}.athenaintel.com`, and the staging wildcard\n * `{id}.computer.stg.athenaintel.com` (and `{id}.computer-canary.stg.athenaintel.com`);\n * - the Sandpack bundler's User Interface previews:\n * `{id}-sandpack.code-sandpack.athenaintel.com` (and `.code-sandpack.{env}.`).\n * They are never a trusted parent and never a backend the bridge points the\n * SDK at.\n */\nconst CUSTOMER_APP_HOST =\n /^.+\\.(?:(?:[a-z0-9-]+-)?app(?:\\.[a-z0-9-]+)*|computer(?:-[a-z0-9-]+)?\\.stg|code-sandpack(?:\\.[a-z0-9-]+)*)\\.athenaintel\\.com$/i;\n\n/** Whether `hostname` serves customer apps (see {@link CUSTOMER_APP_HOST}). */\nexport function isCustomerAppHost(hostname: string): boolean {\n return CUSTOMER_APP_HOST.test(hostname);\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';\n}\n\nfunction isAthenaHost(hostname: string): boolean {\n return hostname === 'athenaintel.com' || hostname.endsWith('.athenaintel.com');\n}\n\n/**\n * Check whether a PostMessage origin is trusted as the parent.\n * Accepts:\n * - explicit trusted origins passed by the host app\n * - the current iframe origin (for same-origin wrappers like Marathon)\n * - Athena's own hosts under athenaintel.com and localhost (default\n * behavior), except the customer-app hosts Marathon serves\n */\nexport function isTrustedParentOrigin({\n origin,\n trustedOrigins,\n}: {\n origin: string;\n trustedOrigins: string[];\n}): boolean {\n try {\n const normalizedOrigin = new URL(origin).origin;\n if (trustedOrigins.includes(normalizedOrigin)) {\n return true;\n }\n\n const { hostname } = new URL(normalizedOrigin);\n if (isCustomerAppHost(hostname)) {\n return false;\n }\n return isAthenaHost(hostname) || hostname === 'localhost';\n } catch {\n return false;\n }\n}\n\n/** Why {@link isAcceptableBridgeUrl} refused a URL. */\nexport type BridgeUrlRefusal = 'invalid' | 'insecure' | 'customer_app_host' | 'other_domain';\n\n/**\n * Whether a URL a trusted parent sent in `athena-config` may become the\n * SDK's backend, statewire mount or app URL. The SDK sends its bearer token\n * to the first two, so a URL is taken only when it is `https:` (plain\n * `http:` only between loopback hosts, for local development), carries no\n * credentials, is not a customer-code host ({@link CUSTOMER_APP_HOST}), and\n * is one of Athena's own hosts or, for a parent outside `athenaintel.com`,\n * the parent's own host or a subdomain of it. Sibling hosts are not taken:\n * without a public-suffix list `portal.example.co.uk` and `tenant.github.io`\n * cannot be told apart from a private domain, so a wrapper whose backend is\n * elsewhere passes its URLs through `config`. Returns the refusal, or `null`\n * when the URL is acceptable.\n */\nexport function refuseBridgeUrl({\n url,\n parentOrigin,\n}: {\n url: string;\n parentOrigin: string;\n}): BridgeUrlRefusal | null {\n let parsed: URL;\n let parentHostname: string;\n try {\n parsed = new URL(url);\n parentHostname = new URL(parentOrigin).hostname;\n } catch {\n return 'invalid';\n }\n if (parsed.username || parsed.password) return 'invalid';\n const { hostname } = parsed;\n const loopbackPair = isLoopbackHost(hostname) && isLoopbackHost(parentHostname);\n if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopbackPair)) {\n return 'insecure';\n }\n if (isCustomerAppHost(hostname)) return 'customer_app_host';\n if (loopbackPair || isAthenaHost(hostname)) return null;\n // Athena and loopback parents point only at Athena's own hosts (above).\n if (isAthenaHost(parentHostname) || isLoopbackHost(parentHostname)) return 'other_domain';\n return hostname === parentHostname || hostname.endsWith(`.${parentHostname}`)\n ? null\n : 'other_domain';\n}\n\n/** {@link refuseBridgeUrl} as a predicate. */\nexport function isAcceptableBridgeUrl(options: { url: string; parentOrigin: string }): boolean {\n return refuseBridgeUrl(options) === null;\n}\n\nexport interface ParentBridgeState {\n /** PropelAuth access token from the parent window. */\n token: string | null;\n /**\n * Statewire sync mount (`…/v2/threads`) minted by the host. `null` when the\n * host minted none; the provider then derives it from the configured\n * `apiUrl`'s origin (or the environment preset).\n */\n statewireSyncUrl: string | null;\n /**\n * Bare Agora API base (no path) minted by the host. `null` when the host\n * minted none; the provider then uses the configured `backendUrl` (or the\n * environment preset).\n */\n backendBaseUrl: string | null;\n /** Athena frontend origin provided by the parent wrapper. */\n appUrl: string | null;\n /**\n * True once a host answered the bridge — a `/_athena/auth` mint or an\n * `athena-config` message — whether or not it minted the statewire fields.\n * Lets the provider tell an outdated host (answered, no mount) from no host\n * at all (the environment preset applies).\n */\n hostAnswered: boolean;\n /** True once config has been received from the parent (or timeout fired). */\n ready: boolean;\n /**\n * Set when the Marathon mint (`/_athena/auth`) gave up. It gives up when it\n * gets a refusal (401, 403, or any 4xx other than 404), a body it cannot\n * read, or a transient failure (5xx, 408, 429, a network error, a timeout)\n * on every attempt of one bounded retry cycle. `code` is\n * `auth_bridge_failed`, or `auth_bridge_timeout` when the last attempt\n * timed out. After that the bridge does not mint again in the background. A\n * forced renewal (`renewToken({ force: true })`) starts one new bounded\n * cycle. A token that an earlier mint handed out is kept (the server still\n * validates it), and the bridge never puts an empty token in its place. The\n * next successful mint clears it. Absent or `null` otherwise.\n */\n error?: AthenaSdkError | null;\n}\n\n/**\n * {@link ParentBridgeState} plus the bridge's token renewal: `force` (the\n * token was refused) asks the host for a new one — a fresh `/_athena/auth`\n * mint on a Marathon origin, or an `athena-auth-ready` request answered by the\n * parent window's next `athena-auth` — and resolves the held token otherwise.\n */\nexport type ParentBridge = ParentBridgeState & { renewToken: AthenaTokenRenewer };\n\nconst BRIDGE_URL_FIELDS = ['statewireSyncUrl', 'backendBaseUrl', 'appUrl'] as const;\n\ntype BridgeUrlField = (typeof BRIDGE_URL_FIELDS)[number];\n\n/** The URL half of {@link ParentBridgeState} — what a host can hand the SDK. */\nexport type ParentBridgeUrls = Pick<ParentBridgeState, BridgeUrlField>;\n\nconst EMPTY_BRIDGE_URLS: ParentBridgeUrls = {\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n};\n\n/**\n * Fold a URL payload over the current URL set without vetting it (the\n * same-origin `/_athena/auth` mint body; a parent's `athena-config` goes\n * through {@link acceptBridgeConfigMessage}). A field is taken only when it is\n * a string; anything else keeps the previous value. Keys outside\n * {@link BridgeUrlField} (a legacy parent's `apiUrl` / `backendUrl` among\n * them) are dropped.\n */\nexport function mergeBridgeConfigMessage(\n previous: ParentBridgeUrls,\n payload: Partial<Record<BridgeUrlField, unknown>>,\n): ParentBridgeUrls {\n const next: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value === 'string') {\n next[field] = value;\n }\n }\n return next;\n}\n\n/** A URL field of an `athena-config` message that was not taken, and why. */\nexport interface RejectedBridgeUrl {\n field: BridgeUrlField;\n reason: BridgeUrlRefusal | 'changed';\n}\n\n/**\n * Fold an `athena-config` message from a trusted parent over the current URL\n * set, taking only what is safe to point the SDK's bearer at: each string\n * field must pass {@link refuseBridgeUrl} against the parent's origin. The\n * first config message sets the URLs; once `locked` (a config message was\n * already taken) a value that differs from the held one, including a field\n * the first message left unset, is refused (`changed`), so a later message\n * cannot repoint a running SDK. Resending the same value is fine.\n */\nexport function acceptBridgeConfigMessage({\n previous,\n payload,\n parentOrigin,\n locked = false,\n}: {\n previous: ParentBridgeUrls;\n payload: Partial<Record<BridgeUrlField, unknown>>;\n parentOrigin: string;\n locked?: boolean;\n}): { urls: ParentBridgeUrls; rejected: RejectedBridgeUrl[] } {\n const urls: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n const rejected: RejectedBridgeUrl[] = [];\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value !== 'string') continue;\n const held = previous[field];\n if (locked || held !== null) {\n if (held !== value) rejected.push({ field, reason: 'changed' });\n continue;\n }\n const refusal = refuseBridgeUrl({ url: value, parentOrigin });\n if (refusal !== null) {\n rejected.push({ field, reason: refusal });\n continue;\n }\n urls[field] = value;\n }\n return { urls, rejected };\n}\n\n/**\n * Read the URL set out of a `/_athena/auth` mint body. Non-string fields are\n * `null`.\n */\nexport function readAuthMintUrls(data: Partial<Record<BridgeUrlField, unknown>>): ParentBridgeUrls {\n return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);\n}\n\nexport interface ParentBridgeOptions {\n /**\n * Exact parent origins that are allowed to send auth/config via postMessage.\n * Useful for private-cloud or custom-domain deployments.\n */\n trustedOrigins?: string[];\n /** @internal Overrides {@link DEFAULT_MINT_RETRY_POLICY}; used by tests. */\n mintRetryPolicy?: MintRetryPolicy;\n}\n\n/** How long to wait for the parent bridge before falling back to defaults. */\nconst BRIDGE_TIMEOUT_MS = 2000;\n/** How long a renewal waits for the parent window to answer `athena-auth-ready`. */\nconst PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5000;\nconst TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;\nconst TOKEN_REFRESH_MIN_DELAY_MS = 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\n\n/**\n * How one Marathon mint cycle retries. A cycle is the initial acquisition, a\n * scheduled refresh, or a forced renewal. It sends at most `maxAttempts`\n * requests. It retries only a transient failure (5xx, 408, 429, a network\n * error, a timeout), after a capped exponential backoff with jitter, and then\n * it gives up. Nothing retries in the background after a cycle gave up.\n */\nexport interface MintRetryPolicy {\n /** Requests per cycle, the first one included. */\n maxAttempts: number;\n /** Ceiling of the delay before the first retry; it doubles for each retry. */\n baseDelayMs: number;\n /** Largest delay before any retry. */\n maxDelayMs: number;\n /** How long one request can take before it is aborted and counted as a timeout. */\n timeoutMs: number;\n /** Source of jitter in `[0, 1)`. */\n random: () => number;\n}\n\nexport const DEFAULT_MINT_RETRY_POLICY: MintRetryPolicy = {\n maxAttempts: 4,\n baseDelayMs: 500,\n maxDelayMs: 4000,\n timeoutMs: BRIDGE_TIMEOUT_MS,\n random: Math.random,\n};\n\n/** Untrusted shape of the `/_athena/auth` mint body; every field is type-guarded on read. */\ninterface AthenaAuthResponse {\n token?: unknown;\n statewireSyncUrl?: unknown;\n backendBaseUrl?: unknown;\n appUrl?: unknown;\n expires_at?: unknown;\n}\n\nconst normalizeOrigin = (value: string): string | null => {\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nexport const getTokenRefreshDelay = (expiresAt: unknown, nowMs: number): number => {\n if (typeof expiresAt !== 'string') {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n const expiresAtMs = Date.parse(expiresAt);\n if (!Number.isFinite(expiresAtMs)) {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n return Math.max(TOKEN_REFRESH_MIN_DELAY_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\n/**\n * The delay before retry number `retry` of a mint cycle (1 is the first\n * retry). The ceiling is `min(maxDelayMs, baseDelayMs * 2^(retry - 1))`, and\n * equal jitter puts the wait between half of that ceiling and all of it, so\n * viewers whose mint failed at the same time do not retry at the same time.\n */\nexport function getMintRetryDelay({\n retry,\n policy = DEFAULT_MINT_RETRY_POLICY,\n}: {\n retry: number;\n policy?: Pick<MintRetryPolicy, 'baseDelayMs' | 'maxDelayMs' | 'random'>;\n}): number {\n const safeRetry = Number.isFinite(retry) ? Math.max(1, Math.floor(retry)) : 1;\n const ceiling = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (safeRetry - 1));\n const jitter = Math.min(1, Math.max(0, policy.random()));\n return Math.round(ceiling / 2 + (ceiling / 2) * jitter);\n}\n\n/** A mint status worth another attempt: the host, or something in front of it, is briefly unwell. */\nexport function isRetryableMintStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\n/** A failed `/_athena/auth` request that is worth another attempt. */\ninterface TransientMintFailure {\n kind: 'transient';\n status: number | null;\n reason: 'status' | 'network' | 'timeout';\n hostError: string | null;\n cause?: unknown;\n}\n\n/** A failed `/_athena/auth` request that ends its cycle at once. */\ninterface RefusedMintFailure {\n kind: 'refused';\n status: number;\n /** `status`: a 4xx other than 404, 408 and 429. `invalid_body`: a 2xx whose body is not a JSON object. */\n reason: 'status' | 'invalid_body';\n hostError: string | null;\n}\n\nexport type MintFailure = TransientMintFailure | RefusedMintFailure;\n\n/** The outcome of one `/_athena/auth` request. */\ntype MintAttempt =\n | { kind: 'minted'; data: AthenaAuthResponse }\n /** 404: this is not a Marathon origin. */\n | { kind: 'absent'; status: number }\n | MintFailure\n | { kind: 'cancelled' };\n\n/** The `error` string of a Marathon failure body (`{\"error\":\"mint_failed\"}`), if any. */\nasync function readHostError(resp: Response): Promise<string | null> {\n try {\n const body: unknown = await resp.json();\n if (body && typeof body === 'object' && 'error' in body) {\n const value = (body as { error?: unknown }).error;\n return typeof value === 'string' ? value.slice(0, 100) : null;\n }\n } catch {\n // An empty or non-JSON error body tells nothing more than the status.\n }\n return null;\n}\n\nconst MINT_RETRY_NOTE =\n 'The SDK stopped retrying and does not connect without a credential. Remount the provider, or call renewToken({ force: true }), to try again.';\n\nfunction mintFailureHint(failure: MintFailure): string {\n if (failure.kind === 'transient') {\n return `The host could not mint a viewer token. Check the Marathon and Agora logs. ${MINT_RETRY_NOTE}`;\n }\n if (failure.reason === 'invalid_body') {\n return `The host answered /_athena/auth without a JSON mint body, so this origin is not a Marathon bridge. Pass token, getToken or apiKey. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 401) {\n return `The host has no session for this viewer. Sign in again through the app URL. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 403) {\n return `The host does not give this viewer access to the app. ${MINT_RETRY_NOTE}`;\n }\n return `The host refused the mint request. ${MINT_RETRY_NOTE}`;\n}\n\n/** The typed error that a mint cycle reports when it gives up. */\nexport function mintFailureError({\n failure,\n attempts,\n phase,\n}: {\n failure: MintFailure;\n attempts: number;\n phase: 'initial' | 'renewal';\n}): AthenaSdkError {\n const timedOut = failure.kind === 'transient' && failure.reason === 'timeout';\n const answer =\n failure.status !== null ? `HTTP ${failure.status}` : timedOut ? 'a timeout' : 'a network error';\n const message =\n failure.kind === 'transient'\n ? `The host auth bridge (/_athena/auth) failed ${attempts} time${attempts === 1 ? '' : 's'} (last: ${answer}).`\n : failure.reason === 'invalid_body'\n ? `The host auth bridge (/_athena/auth) answered ${answer} without a JSON mint body.`\n : `The host auth bridge (/_athena/auth) refused the mint (${answer}).`;\n return new AthenaSdkError({\n code: timedOut ? 'auth_bridge_timeout' : 'auth_bridge_failed',\n message,\n hint: mintFailureHint(failure),\n ...(failure.status !== null && { status: failure.status }),\n ...(failure.hostError !== null && { detail: { code: failure.hostError } }),\n ...(failure.kind === 'transient' && failure.cause !== undefined && { cause: failure.cause }),\n context: {\n mode: 'marathon',\n phase,\n attempts,\n reason: failure.reason,\n host_error: failure.hostError,\n },\n });\n}\n\n/**\n * Listens for auth tokens AND environment config from the host environment.\n *\n * Two transport modes, picked automatically:\n *\n * 1. **Iframe (legacy / Olympus inline preview)**: a parent window posts\n * `athena-config` and `athena-auth` via PostMessage. The hook also posts\n * `athena-auth-ready` so the parent knows when to (re-)send. Only the\n * window that frames the app (`event.source === window.parent`) on a\n * trusted origin ({@link isTrustedParentOrigin}) is heard, and an\n * `athena-config` URL is taken only once and only when it passes\n * {@link refuseBridgeUrl}.\n *\n * 2. **Standalone (Marathon-served deploy URL)**: top-level navigation, no\n * parent. The hook fetches `/_athena/auth` on its own origin. Marathon\n * validates its session cookie (sent by the browser automatically) and\n * returns `{ token, statewireSyncUrl, backendBaseUrl, appUrl }` — the\n * statewire mount, the bare Agora base and the app origin. A 404 means\n * we're not on a Marathon origin — the hook falls through with empty\n * state. Any other failure fails closed: one bounded retry cycle\n * ({@link MintRetryPolicy}) for a transient failure, none for a refusal,\n * and then `error` is set and `ready` flips with no token — never an\n * empty or anonymous one.\n *\n * In both modes the app never blocks indefinitely: the iframe bridge flips\n * `ready: true` after a timeout, and a Marathon mint cycle ends after at most\n * `maxAttempts` requests.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\n mintRetryPolicy = DEFAULT_MINT_RETRY_POLICY,\n}: ParentBridgeOptions = {}): ParentBridge {\n const isInIframe = typeof window !== 'undefined' && window.parent !== window;\n const runtimeTrustedOrigins = useMemo(() => {\n const origins = new Set<string>();\n\n if (typeof window !== 'undefined') {\n origins.add(window.location.origin);\n }\n\n for (const trustedOrigin of trustedOrigins) {\n const normalizedOrigin = normalizeOrigin(trustedOrigin);\n if (normalizedOrigin) {\n origins.add(normalizedOrigin);\n }\n }\n\n return [...origins];\n }, [trustedOrigins]);\n\n const [state, setState] = useState<ParentBridgeState>({\n token: null,\n ...EMPTY_BRIDGE_URLS,\n hostAnswered: false,\n ready: false,\n error: null,\n });\n // Read when a mint cycle starts, so a caller's inline policy object does not\n // restart the bridge on every render.\n const mintRetryPolicyRef = useRef(mintRetryPolicy);\n mintRetryPolicyRef.current = mintRetryPolicy;\n const readySignalSent = useRef(false);\n const configReceived = useRef(false);\n // The URLs the parent has set so far: a later `athena-config` may not change them.\n const parentUrlsRef = useRef<ParentBridgeUrls>(EMPTY_BRIDGE_URLS);\n const tokenRef = useRef<string | null>(state.token);\n tokenRef.current = state.token;\n // Installed by whichever transport mode is active (iframe or Marathon).\n const renewerRef = useRef<AthenaTokenRenewer | null>(null);\n\n useEffect(() => {\n if (!isInIframe) return;\n\n const endBridgeSpan = athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', {\n mode: 'iframe',\n });\n let spanEnded = false;\n const endOnce: typeof endBridgeSpan = (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return endBridgeSpan(name, options);\n };\n\n // Renewals waiting for the parent's next `athena-auth`.\n const tokenWaiters = new Set<(token: string | null) => void>();\n\n const handler = (event: MessageEvent) => {\n // Only the window that frames this app speaks for the host: a popup,\n // an opener or a sibling frame on a trusted origin does not.\n if (event.source !== window.parent) return;\n if (\n !isTrustedParentOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })\n ) {\n return;\n }\n if (!event.data || typeof event.data !== 'object') return;\n\n if (event.data.type === 'athena-config') {\n const { urls, rejected } = acceptBridgeConfigMessage({\n previous: parentUrlsRef.current,\n payload: event.data,\n parentOrigin: event.origin,\n locked: configReceived.current,\n });\n configReceived.current = true;\n parentUrlsRef.current = urls;\n for (const { field, reason } of rejected) {\n athenaDiagnostics.emit('auth.bridge.config_rejected', {\n level: 'warn',\n data: { mode: 'iframe', field, reason },\n });\n }\n setState((prev) => ({\n ...prev,\n ...urls,\n hostAnswered: true,\n // Don't set ready here — wait for athena-auth (token) or the timeout.\n // Setting ready on config alone causes a race: the thread list fires\n // before the token arrives, falling back to X-API-KEY.\n }));\n }\n\n if (event.data.type === 'athena-auth' && typeof event.data.token === 'string') {\n endOnce('auth.bridge.ready', { data: { mode: 'iframe' } });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'iframe-postmessage' },\n });\n setState((prev) => ({\n ...prev,\n token: event.data.token,\n ready: true,\n }));\n tokenRef.current = event.data.token;\n for (const resolve of [...tokenWaiters]) resolve(event.data.token);\n }\n };\n window.addEventListener('message', handler);\n\n // A renewal asks the parent to send its current token again — the same\n // `athena-auth-ready` handshake a parent already answers — and settles on\n // the next `athena-auth`, or on the held token when none arrives in time.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return new Promise<string | null>((resolve) => {\n const settle = (token: string | null) => {\n clearTimeout(timer);\n tokenWaiters.delete(settle);\n resolve(token ?? tokenRef.current);\n };\n const timer = setTimeout(() => settle(null), PARENT_TOKEN_RENEWAL_TIMEOUT_MS);\n tokenWaiters.add(settle);\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n });\n };\n\n if (!readySignalSent.current) {\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n readySignalSent.current = true;\n }\n\n const timer = setTimeout(() => {\n endOnce('auth.bridge.timeout', {\n data: {\n mode: 'iframe',\n timeout_ms: BRIDGE_TIMEOUT_MS,\n config_received: configReceived.current,\n },\n });\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n }, BRIDGE_TIMEOUT_MS);\n\n return () => {\n window.removeEventListener('message', handler);\n clearTimeout(timer);\n renewerRef.current = null;\n for (const resolve of [...tokenWaiters]) resolve(null);\n };\n }, [isInIframe, runtimeTrustedOrigins]);\n\n useEffect(() => {\n if (isInIframe) return;\n if (typeof window === 'undefined') return;\n\n let cancelled = false;\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n let inFlight: Promise<string | null> | null = null;\n let retryWait: { timer: ReturnType<typeof setTimeout>; resolve: () => void } | null = null;\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const endRetryWait = () => {\n if (!retryWait) return;\n clearTimeout(retryWait.timer);\n const { resolve } = retryWait;\n retryWait = null;\n resolve();\n };\n\n /** The backoff between two attempts of one cycle; unmounting ends it at once. */\n const waitBeforeRetry = (delayMs: number) =>\n new Promise<void>((resolve) => {\n retryWait = { timer: setTimeout(endRetryWait, delayMs), resolve };\n });\n\n // The only background mint: once per token, shortly before it expires. It\n // is not rescheduled when its cycle gives up. A forced renewal that gives\n // up leaves it in place on purpose: the held token is still live, and\n // this one bounded cycle keeps a viewer connected past a brief outage.\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n refreshTimer = null;\n void fetchAuth({ initial: false });\n }, delay);\n };\n\n /** One `/_athena/auth` request, classified. */\n const mintOnce = async (timeoutMs: number): Promise<MintAttempt> => {\n const requestController = new AbortController();\n controller = requestController;\n let timedOut = false;\n const requestTimer = setTimeout(() => {\n timedOut = true;\n requestController.abort();\n }, timeoutMs);\n\n try {\n const resp = await fetch('/_athena/auth', {\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: requestController.signal,\n });\n if (cancelled) return { kind: 'cancelled' };\n // Not a Marathon origin: expected for standalone apps and local dev.\n if (resp.status === 404) return { kind: 'absent', status: resp.status };\n if (!resp.ok) {\n const hostError = await readHostError(resp);\n if (cancelled) return { kind: 'cancelled' };\n // `readHostError` swallows the abort of a stalled error body. Only a\n // retryable status becomes a timeout: a refusal (401, 403, other\n // 4xx) is known from its status and still fails after one request.\n if (timedOut && isRetryableMintStatus(resp.status)) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null };\n }\n return isRetryableMintStatus(resp.status)\n ? { kind: 'transient', status: resp.status, reason: 'status', hostError }\n : { kind: 'refused', status: resp.status, reason: 'status', hostError };\n }\n\n let data: unknown;\n try {\n data = await resp.json();\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n if (cancelled) return { kind: 'cancelled' };\n // Marathon's 200 always carries a token. A 2xx without one is a\n // broken mint: it must not replace a held token or count as success.\n const token = data && typeof data === 'object' ? (data as { token?: unknown }).token : null;\n if (typeof token !== 'string' || token === '') {\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n return { kind: 'minted', data: data as AthenaAuthResponse };\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'transient', status: null, reason: 'network', hostError: null, cause: error };\n } finally {\n clearTimeout(requestTimer);\n if (controller === requestController) controller = null;\n }\n };\n\n /**\n * One bounded mint cycle: the initial acquisition (a span the provider\n * waits on) or a renewal (the scheduled refresh, a forced renewal). It\n * retries a transient failure up to `maxAttempts` requests in all, with\n * a capped, jittered backoff. On a refusal, or after the last attempt, it\n * gives up: it reports one typed error, sets `error`, keeps any token an\n * earlier mint handed out, and schedules nothing.\n */\n const runMintCycle = async ({ initial }: { initial: boolean }): Promise<string | null> => {\n const policy = mintRetryPolicyRef.current;\n const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));\n const phase = initial ? 'initial' : 'renewal';\n const endBridgeSpan = initial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n\n let attempts = 0;\n let lastFailure: MintFailure | null = null;\n while (attempts < maxAttempts) {\n attempts += 1;\n const outcome = await mintOnce(policy.timeoutMs);\n if (cancelled || outcome.kind === 'cancelled') return null;\n\n if (outcome.kind === 'absent') {\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: outcome.status },\n });\n // 404 means no bridge here, so an earlier mint failure no longer\n // applies; the provider falls back to its other credentials.\n setState((prev) =>\n prev.ready && prev.error == null ? prev : { ...prev, ready: true, error: null },\n );\n return null;\n }\n\n if (outcome.kind === 'minted') {\n const { data } = outcome;\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string', attempts },\n });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at },\n });\n } else {\n athenaDiagnostics.emit('auth.token.refreshed', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at, attempts },\n });\n }\n const token = typeof data.token === 'string' ? data.token : null;\n setState({\n token,\n ...readAuthMintUrls(data),\n hostAnswered: true,\n ready: true,\n error: null,\n });\n tokenRef.current = token;\n scheduleRefresh(data.expires_at);\n return token;\n }\n\n lastFailure = outcome;\n if (outcome.kind === 'refused' || attempts >= maxAttempts) break;\n\n const delayMs = getMintRetryDelay({ retry: attempts, policy });\n athenaDiagnostics.emit('auth.bridge.retry', {\n level: 'warn',\n data: {\n mode: 'marathon',\n phase,\n status: outcome.status,\n reason: outcome.reason,\n host_error: outcome.hostError,\n attempt: attempts,\n max_attempts: maxAttempts,\n delay_ms: delayMs,\n },\n });\n await waitBeforeRetry(delayMs);\n if (cancelled) return null;\n }\n\n if (!lastFailure) return null;\n const error = mintFailureError({ failure: lastFailure, attempts, phase });\n const failureData = {\n mode: 'marathon',\n phase,\n status: lastFailure.status,\n reason: lastFailure.reason,\n attempts,\n };\n if (endBridgeSpan) {\n endBridgeSpan(\n error.code === 'auth_bridge_timeout' ? 'auth.bridge.timeout' : 'auth.bridge.error',\n { data: failureData, error },\n );\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', {\n level: 'error',\n data: failureData,\n error,\n });\n }\n setState((prev) => ({ ...prev, error, ready: true }));\n return null;\n };\n\n // One mint cycle at a time. The initial acquisition, a scheduled refresh\n // and forced renewals that overlap share the cycle in flight (its\n // requests, their timeouts and its backoff) instead of starting their own,\n // so concurrent callers never multiply the requests.\n const fetchAuth = (options: { initial: boolean }): Promise<string | null> => {\n inFlight ??= runMintCycle(options).finally(() => {\n inFlight = null;\n });\n return inFlight;\n };\n\n // A forced renewal mints now instead of waiting for the scheduled refresh\n // (joining a cycle already in flight), and may start a new bounded cycle\n // after one gave up. The schedule restarts from the new token's expiry.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return fetchAuth({ initial: false }).then((token) => token ?? tokenRef.current);\n };\n\n void fetchAuth({ initial: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRefreshTimer();\n endRetryWait();\n renewerRef.current = null;\n };\n }, [isInIframe]);\n\n const renewToken = useCallback<AthenaTokenRenewer>(\n (options) => renewerRef.current?.(options) ?? Promise.resolve(tokenRef.current),\n [],\n );\n\n return useMemo(() => ({ ...state, renewToken }), [state, renewToken]);\n}\n\n/**\n * Legacy hook — returns just the auth token from the parent bridge.\n * Prefer useParentBridge() for new code.\n */\nexport function useParentAuth(): string | null {\n return useParentBridge().token;\n}\n"],"names":["AthenaSdkError","useMemo","useState","useRef","useEffect","athenaDiagnostics","timer","useCallback"],"mappings":"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"}
1
+ {"version":3,"file":"auth.cjs","sources":["../../src/runtime/auth.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { AthenaTokenRenewer } from '../auth/credential-source';\nimport { signBackIn } from '../auth/sign-back-in';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Hosts under `athenaintel.com` that serve customer-written code:\n * - Marathon's computer and application apps: `{id}.app.athenaintel.com`,\n * `{id}.staging-app.athenaintel.com`, `{id}.canary-app.athenaintel.com`, an\n * environment's `{id}.app.{env}.athenaintel.com`, and the staging wildcard\n * `{id}.computer.stg.athenaintel.com` (and `{id}.computer-canary.stg.athenaintel.com`);\n * - the Sandpack bundler's User Interface previews:\n * `{id}-sandpack.code-sandpack.athenaintel.com` (and `.code-sandpack.{env}.`).\n * They are never a trusted parent and never a backend the bridge points the\n * SDK at.\n */\nconst CUSTOMER_APP_HOST =\n /^.+\\.(?:(?:[a-z0-9-]+-)?app(?:\\.[a-z0-9-]+)*|computer(?:-[a-z0-9-]+)?\\.stg|code-sandpack(?:\\.[a-z0-9-]+)*)\\.athenaintel\\.com$/i;\n\n/** Whether `hostname` serves customer apps (see {@link CUSTOMER_APP_HOST}). */\nexport function isCustomerAppHost(hostname: string): boolean {\n return CUSTOMER_APP_HOST.test(hostname);\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';\n}\n\nfunction isAthenaHost(hostname: string): boolean {\n return hostname === 'athenaintel.com' || hostname.endsWith('.athenaintel.com');\n}\n\n/**\n * Check whether a PostMessage origin is trusted as the parent.\n * Accepts:\n * - explicit trusted origins passed by the host app\n * - the current iframe origin (for same-origin wrappers like Marathon)\n * - Athena's own hosts under athenaintel.com and localhost (default\n * behavior), except the customer-app hosts Marathon serves\n */\nexport function isTrustedParentOrigin({\n origin,\n trustedOrigins,\n}: {\n origin: string;\n trustedOrigins: string[];\n}): boolean {\n try {\n const normalizedOrigin = new URL(origin).origin;\n if (trustedOrigins.includes(normalizedOrigin)) {\n return true;\n }\n\n const { hostname } = new URL(normalizedOrigin);\n if (isCustomerAppHost(hostname)) {\n return false;\n }\n return isAthenaHost(hostname) || hostname === 'localhost';\n } catch {\n return false;\n }\n}\n\n/** Why {@link isAcceptableBridgeUrl} refused a URL. */\nexport type BridgeUrlRefusal = 'invalid' | 'insecure' | 'customer_app_host' | 'other_domain';\n\n/**\n * Whether a URL a trusted parent sent in `athena-config` may become the\n * SDK's backend, statewire mount or app URL. The SDK sends its bearer token\n * to the first two, so a URL is taken only when it is `https:` (plain\n * `http:` only between loopback hosts, for local development), carries no\n * credentials, is not a customer-code host ({@link CUSTOMER_APP_HOST}), and\n * is one of Athena's own hosts or, for a parent outside `athenaintel.com`,\n * the parent's own host or a subdomain of it. Sibling hosts are not taken:\n * without a public-suffix list `portal.example.co.uk` and `tenant.github.io`\n * cannot be told apart from a private domain, so a wrapper whose backend is\n * elsewhere passes its URLs through `config`. Returns the refusal, or `null`\n * when the URL is acceptable.\n */\nexport function refuseBridgeUrl({\n url,\n parentOrigin,\n}: {\n url: string;\n parentOrigin: string;\n}): BridgeUrlRefusal | null {\n let parsed: URL;\n let parentHostname: string;\n try {\n parsed = new URL(url);\n parentHostname = new URL(parentOrigin).hostname;\n } catch {\n return 'invalid';\n }\n if (parsed.username || parsed.password) return 'invalid';\n const { hostname } = parsed;\n const loopbackPair = isLoopbackHost(hostname) && isLoopbackHost(parentHostname);\n if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopbackPair)) {\n return 'insecure';\n }\n if (isCustomerAppHost(hostname)) return 'customer_app_host';\n if (loopbackPair || isAthenaHost(hostname)) return null;\n // Athena and loopback parents point only at Athena's own hosts (above).\n if (isAthenaHost(parentHostname) || isLoopbackHost(parentHostname)) return 'other_domain';\n return hostname === parentHostname || hostname.endsWith(`.${parentHostname}`)\n ? null\n : 'other_domain';\n}\n\n/** {@link refuseBridgeUrl} as a predicate. */\nexport function isAcceptableBridgeUrl(options: { url: string; parentOrigin: string }): boolean {\n return refuseBridgeUrl(options) === null;\n}\n\nexport interface ParentBridgeState {\n /** PropelAuth access token from the parent window. */\n token: string | null;\n /**\n * Statewire sync mount (`…/v2/threads`) minted by the host. `null` when the\n * host minted none; the provider then derives it from the configured\n * `apiUrl`'s origin (or the environment preset).\n */\n statewireSyncUrl: string | null;\n /**\n * Bare Agora API base (no path) minted by the host. `null` when the host\n * minted none; the provider then uses the configured `backendUrl` (or the\n * environment preset).\n */\n backendBaseUrl: string | null;\n /** Athena frontend origin provided by the parent wrapper. */\n appUrl: string | null;\n /**\n * True once a host answered the bridge — a `/_athena/auth` mint or an\n * `athena-config` message — whether or not it minted the statewire fields.\n * Lets the provider tell an outdated host (answered, no mount) from no host\n * at all (the environment preset applies).\n */\n hostAnswered: boolean;\n /** True once config has been received from the parent (or timeout fired). */\n ready: boolean;\n /**\n * Set when the Marathon mint (`/_athena/auth`) gave up. It gives up when it\n * gets a refusal (401, 403, or any 4xx other than 404), a body it cannot\n * read, or a transient failure (5xx, 408, 429, a network error, a timeout)\n * on every attempt of one bounded retry cycle. `code` is\n * `auth_bridge_failed`, or `auth_bridge_timeout` when the last attempt\n * timed out. After that the bridge does not mint again in the background. A\n * forced renewal (`renewToken({ force: true })`) starts one new bounded\n * cycle. A token that an earlier mint handed out is kept (the server still\n * validates it), and the bridge never puts an empty token in its place. The\n * next successful mint clears it. Absent or `null` otherwise.\n */\n error?: AthenaSdkError | null;\n}\n\n/**\n * {@link ParentBridgeState} plus the bridge's token renewal: `force` (the\n * token was refused) asks the host for a new one — a fresh `/_athena/auth`\n * mint on a Marathon origin, or an `athena-auth-ready` request answered by the\n * parent window's next `athena-auth` — and resolves the held token otherwise.\n */\nexport type ParentBridge = ParentBridgeState & { renewToken: AthenaTokenRenewer };\n\nconst BRIDGE_URL_FIELDS = ['statewireSyncUrl', 'backendBaseUrl', 'appUrl'] as const;\n\ntype BridgeUrlField = (typeof BRIDGE_URL_FIELDS)[number];\n\n/** The URL half of {@link ParentBridgeState} — what a host can hand the SDK. */\nexport type ParentBridgeUrls = Pick<ParentBridgeState, BridgeUrlField>;\n\nconst EMPTY_BRIDGE_URLS: ParentBridgeUrls = {\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n};\n\n/**\n * Fold a URL payload over the current URL set without vetting it (the\n * same-origin `/_athena/auth` mint body; a parent's `athena-config` goes\n * through {@link acceptBridgeConfigMessage}). A field is taken only when it is\n * a string; anything else keeps the previous value. Keys outside\n * {@link BridgeUrlField} (a legacy parent's `apiUrl` / `backendUrl` among\n * them) are dropped.\n */\nexport function mergeBridgeConfigMessage(\n previous: ParentBridgeUrls,\n payload: Partial<Record<BridgeUrlField, unknown>>,\n): ParentBridgeUrls {\n const next: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value === 'string') {\n next[field] = value;\n }\n }\n return next;\n}\n\n/** A URL field of an `athena-config` message that was not taken, and why. */\nexport interface RejectedBridgeUrl {\n field: BridgeUrlField;\n reason: BridgeUrlRefusal | 'changed';\n}\n\n/**\n * Fold an `athena-config` message from a trusted parent over the current URL\n * set, taking only what is safe to point the SDK's bearer at: each string\n * field must pass {@link refuseBridgeUrl} against the parent's origin. The\n * first config message sets the URLs; once `locked` (a config message was\n * already taken) a value that differs from the held one, including a field\n * the first message left unset, is refused (`changed`), so a later message\n * cannot repoint a running SDK. Resending the same value is fine.\n */\nexport function acceptBridgeConfigMessage({\n previous,\n payload,\n parentOrigin,\n locked = false,\n}: {\n previous: ParentBridgeUrls;\n payload: Partial<Record<BridgeUrlField, unknown>>;\n parentOrigin: string;\n locked?: boolean;\n}): { urls: ParentBridgeUrls; rejected: RejectedBridgeUrl[] } {\n const urls: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n const rejected: RejectedBridgeUrl[] = [];\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value !== 'string') continue;\n const held = previous[field];\n if (locked || held !== null) {\n if (held !== value) rejected.push({ field, reason: 'changed' });\n continue;\n }\n const refusal = refuseBridgeUrl({ url: value, parentOrigin });\n if (refusal !== null) {\n rejected.push({ field, reason: refusal });\n continue;\n }\n urls[field] = value;\n }\n return { urls, rejected };\n}\n\n/**\n * Read the URL set out of a `/_athena/auth` mint body. Non-string fields are\n * `null`.\n */\nexport function readAuthMintUrls(data: Partial<Record<BridgeUrlField, unknown>>): ParentBridgeUrls {\n return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);\n}\n\nexport interface ParentBridgeOptions {\n /**\n * Exact parent origins that are allowed to send auth/config via postMessage.\n * Useful for private-cloud or custom-domain deployments.\n */\n trustedOrigins?: string[];\n /**\n * When a renewal of a credential this page already minted is refused with\n * a 401 (the viewer signed out of Athena elsewhere, or Marathon's session\n * for this app ended), send the page through Marathon's sign-in\n * ({@link MARATHON_SIGN_IN_PATH}) once, returning to the page\n * (`auth/sign-back-in.ts`). Never for a 403: that is Marathon refusing this\n * viewer the app (`not_authorized`, `unknown_asset`), which signing in again\n * cannot change, so the bridge error stays. Never for the first mint: a page\n * Marathon served on a preview grant alone has no session to renew.\n * `AthenaProvider` turns it on only when the bridge's token is the chat's\n * credential (`bridgeOwnsCredential`): no sign-in provider above it, which\n * otherwise decides, and no host `token`, `getToken` or `apiKey`. Defaults\n * to `false`.\n */\n signBackInOnSessionEnd?: boolean;\n /** @internal Overrides {@link DEFAULT_MINT_RETRY_POLICY}; used by tests. */\n mintRetryPolicy?: MintRetryPolicy;\n}\n\n/** Marathon's sign-in entry on every app host: it returns to `return_to` signed in. */\nexport const MARATHON_SIGN_IN_PATH = '/api/sso/initiate';\n\n/** How long to wait for the parent bridge before falling back to defaults. */\nconst BRIDGE_TIMEOUT_MS = 2000;\n/** How long a renewal waits for the parent window to answer `athena-auth-ready`. */\nconst PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5000;\nconst TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;\nconst TOKEN_REFRESH_MIN_DELAY_MS = 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\n\n/**\n * How one Marathon mint cycle retries. A cycle is the initial acquisition, a\n * scheduled refresh, or a forced renewal. It sends at most `maxAttempts`\n * requests. It retries only a transient failure (5xx, 408, 429, a network\n * error, a timeout), after a capped exponential backoff with jitter, and then\n * it gives up. Nothing retries in the background after a cycle gave up.\n */\nexport interface MintRetryPolicy {\n /** Requests per cycle, the first one included. */\n maxAttempts: number;\n /** Ceiling of the delay before the first retry; it doubles for each retry. */\n baseDelayMs: number;\n /** Largest delay before any retry. */\n maxDelayMs: number;\n /** How long one request can take before it is aborted and counted as a timeout. */\n timeoutMs: number;\n /** Source of jitter in `[0, 1)`. */\n random: () => number;\n}\n\nexport const DEFAULT_MINT_RETRY_POLICY: MintRetryPolicy = {\n maxAttempts: 4,\n baseDelayMs: 500,\n maxDelayMs: 4000,\n timeoutMs: BRIDGE_TIMEOUT_MS,\n random: Math.random,\n};\n\n/** Untrusted shape of the `/_athena/auth` mint body; every field is type-guarded on read. */\ninterface AthenaAuthResponse {\n token?: unknown;\n statewireSyncUrl?: unknown;\n backendBaseUrl?: unknown;\n appUrl?: unknown;\n expires_at?: unknown;\n}\n\nconst normalizeOrigin = (value: string): string | null => {\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nexport const getTokenRefreshDelay = (expiresAt: unknown, nowMs: number): number => {\n if (typeof expiresAt !== 'string') {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n const expiresAtMs = Date.parse(expiresAt);\n if (!Number.isFinite(expiresAtMs)) {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n return Math.max(TOKEN_REFRESH_MIN_DELAY_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\n/**\n * The delay before retry number `retry` of a mint cycle (1 is the first\n * retry). The ceiling is `min(maxDelayMs, baseDelayMs * 2^(retry - 1))`, and\n * equal jitter puts the wait between half of that ceiling and all of it, so\n * viewers whose mint failed at the same time do not retry at the same time.\n */\nexport function getMintRetryDelay({\n retry,\n policy = DEFAULT_MINT_RETRY_POLICY,\n}: {\n retry: number;\n policy?: Pick<MintRetryPolicy, 'baseDelayMs' | 'maxDelayMs' | 'random'>;\n}): number {\n const safeRetry = Number.isFinite(retry) ? Math.max(1, Math.floor(retry)) : 1;\n const ceiling = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (safeRetry - 1));\n const jitter = Math.min(1, Math.max(0, policy.random()));\n return Math.round(ceiling / 2 + (ceiling / 2) * jitter);\n}\n\n/** A mint status worth another attempt: the host, or something in front of it, is briefly unwell. */\nexport function isRetryableMintStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\n/** A failed `/_athena/auth` request that is worth another attempt. */\ninterface TransientMintFailure {\n kind: 'transient';\n status: number | null;\n reason: 'status' | 'network' | 'timeout';\n hostError: string | null;\n cause?: unknown;\n}\n\n/** A failed `/_athena/auth` request that ends its cycle at once. */\ninterface RefusedMintFailure {\n kind: 'refused';\n status: number;\n /** `status`: a 4xx other than 404, 408 and 429. `invalid_body`: a 2xx whose body is not a JSON object. */\n reason: 'status' | 'invalid_body';\n hostError: string | null;\n}\n\nexport type MintFailure = TransientMintFailure | RefusedMintFailure;\n\n/** The outcome of one `/_athena/auth` request. */\ntype MintAttempt =\n | { kind: 'minted'; data: AthenaAuthResponse }\n /** 404: this is not a Marathon origin. */\n | { kind: 'absent'; status: number }\n | MintFailure\n | { kind: 'cancelled' };\n\n/** The `error` string of a Marathon failure body (`{\"error\":\"mint_failed\"}`), if any. */\nasync function readHostError(resp: Response): Promise<string | null> {\n try {\n const body: unknown = await resp.json();\n if (body && typeof body === 'object' && 'error' in body) {\n const value = (body as { error?: unknown }).error;\n return typeof value === 'string' ? value.slice(0, 100) : null;\n }\n } catch {\n // An empty or non-JSON error body tells nothing more than the status.\n }\n return null;\n}\n\nconst MINT_RETRY_NOTE =\n 'The SDK stopped retrying and does not connect without a credential. Remount the provider, or call renewToken({ force: true }), to try again.';\n\nfunction mintFailureHint(failure: MintFailure): string {\n if (failure.kind === 'transient') {\n return `The host could not mint a viewer token. Check the Marathon and Agora logs. ${MINT_RETRY_NOTE}`;\n }\n if (failure.reason === 'invalid_body') {\n return `The host answered /_athena/auth without a JSON mint body, so this origin is not a Marathon bridge. Pass token, getToken or apiKey. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 401) {\n return `The host has no session for this viewer. Sign in again through the app URL. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 403) {\n return `The host does not give this viewer access to the app. ${MINT_RETRY_NOTE}`;\n }\n return `The host refused the mint request. ${MINT_RETRY_NOTE}`;\n}\n\n/** The typed error that a mint cycle reports when it gives up. */\nexport function mintFailureError({\n failure,\n attempts,\n phase,\n}: {\n failure: MintFailure;\n attempts: number;\n phase: 'initial' | 'renewal';\n}): AthenaSdkError {\n const timedOut = failure.kind === 'transient' && failure.reason === 'timeout';\n const answer =\n failure.status !== null ? `HTTP ${failure.status}` : timedOut ? 'a timeout' : 'a network error';\n const message =\n failure.kind === 'transient'\n ? `The host auth bridge (/_athena/auth) failed ${attempts} time${attempts === 1 ? '' : 's'} (last: ${answer}).`\n : failure.reason === 'invalid_body'\n ? `The host auth bridge (/_athena/auth) answered ${answer} without a JSON mint body.`\n : `The host auth bridge (/_athena/auth) refused the mint (${answer}).`;\n return new AthenaSdkError({\n code: timedOut ? 'auth_bridge_timeout' : 'auth_bridge_failed',\n message,\n hint: mintFailureHint(failure),\n ...(failure.status !== null && { status: failure.status }),\n ...(failure.hostError !== null && { detail: { code: failure.hostError } }),\n ...(failure.kind === 'transient' && failure.cause !== undefined && { cause: failure.cause }),\n context: {\n mode: 'marathon',\n phase,\n attempts,\n reason: failure.reason,\n host_error: failure.hostError,\n },\n });\n}\n\n/**\n * Listens for auth tokens AND environment config from the host environment.\n *\n * Two transport modes, picked automatically:\n *\n * 1. **Iframe (legacy / Olympus inline preview)**: a parent window posts\n * `athena-config` and `athena-auth` via PostMessage. The hook also posts\n * `athena-auth-ready` so the parent knows when to (re-)send. Only the\n * window that frames the app (`event.source === window.parent`) on a\n * trusted origin ({@link isTrustedParentOrigin}) is heard, and an\n * `athena-config` URL is taken only once and only when it passes\n * {@link refuseBridgeUrl}.\n *\n * 2. **Standalone (Marathon-served deploy URL)**: top-level navigation, no\n * parent. The hook fetches `/_athena/auth` on its own origin. Marathon\n * validates its session cookie (sent by the browser automatically) and\n * returns `{ token, statewireSyncUrl, backendBaseUrl, appUrl }` — the\n * statewire mount, the bare Agora base and the app origin. A 404 means\n * we're not on a Marathon origin — the hook falls through with empty\n * state. Any other failure fails closed: one bounded retry cycle\n * ({@link MintRetryPolicy}) for a transient failure, none for a refusal,\n * and then `error` is set and `ready` flips with no token — never an\n * empty or anonymous one.\n *\n * In both modes the app never blocks indefinitely: the iframe bridge flips\n * `ready: true` after a timeout, and a Marathon mint cycle ends after at most\n * `maxAttempts` requests.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\n signBackInOnSessionEnd = false,\n mintRetryPolicy = DEFAULT_MINT_RETRY_POLICY,\n}: ParentBridgeOptions = {}): ParentBridge {\n const isInIframe = typeof window !== 'undefined' && window.parent !== window;\n const runtimeTrustedOrigins = useMemo(() => {\n const origins = new Set<string>();\n\n if (typeof window !== 'undefined') {\n origins.add(window.location.origin);\n }\n\n for (const trustedOrigin of trustedOrigins) {\n const normalizedOrigin = normalizeOrigin(trustedOrigin);\n if (normalizedOrigin) {\n origins.add(normalizedOrigin);\n }\n }\n\n return [...origins];\n }, [trustedOrigins]);\n\n const [state, setState] = useState<ParentBridgeState>({\n token: null,\n ...EMPTY_BRIDGE_URLS,\n hostAnswered: false,\n ready: false,\n error: null,\n });\n // Read when a mint cycle starts, so a caller's inline policy object does not\n // restart the bridge on every render.\n const mintRetryPolicyRef = useRef(mintRetryPolicy);\n mintRetryPolicyRef.current = mintRetryPolicy;\n // Read when a renewal is refused, so the provider can flip it without\n // restarting the bridge.\n const signBackInRef = useRef(signBackInOnSessionEnd);\n signBackInRef.current = signBackInOnSessionEnd;\n const readySignalSent = useRef(false);\n const configReceived = useRef(false);\n // The URLs the parent has set so far: a later `athena-config` may not change them.\n const parentUrlsRef = useRef<ParentBridgeUrls>(EMPTY_BRIDGE_URLS);\n const tokenRef = useRef<string | null>(state.token);\n tokenRef.current = state.token;\n // Installed by whichever transport mode is active (iframe or Marathon).\n const renewerRef = useRef<AthenaTokenRenewer | null>(null);\n\n useEffect(() => {\n if (!isInIframe) return;\n\n const endBridgeSpan = athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', {\n mode: 'iframe',\n });\n let spanEnded = false;\n const endOnce: typeof endBridgeSpan = (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return endBridgeSpan(name, options);\n };\n\n // Renewals waiting for the parent's next `athena-auth`.\n const tokenWaiters = new Set<(token: string | null) => void>();\n\n const handler = (event: MessageEvent) => {\n // Only the window that frames this app speaks for the host: a popup,\n // an opener or a sibling frame on a trusted origin does not.\n if (event.source !== window.parent) return;\n if (\n !isTrustedParentOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })\n ) {\n return;\n }\n if (!event.data || typeof event.data !== 'object') return;\n\n if (event.data.type === 'athena-config') {\n const { urls, rejected } = acceptBridgeConfigMessage({\n previous: parentUrlsRef.current,\n payload: event.data,\n parentOrigin: event.origin,\n locked: configReceived.current,\n });\n configReceived.current = true;\n parentUrlsRef.current = urls;\n for (const { field, reason } of rejected) {\n athenaDiagnostics.emit('auth.bridge.config_rejected', {\n level: 'warn',\n data: { mode: 'iframe', field, reason },\n });\n }\n setState((prev) => ({\n ...prev,\n ...urls,\n hostAnswered: true,\n // Don't set ready here — wait for athena-auth (token) or the timeout.\n // Setting ready on config alone causes a race: the thread list fires\n // before the token arrives, falling back to X-API-KEY.\n }));\n }\n\n if (event.data.type === 'athena-auth' && typeof event.data.token === 'string') {\n endOnce('auth.bridge.ready', { data: { mode: 'iframe' } });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'iframe-postmessage' },\n });\n setState((prev) => ({\n ...prev,\n token: event.data.token,\n ready: true,\n }));\n tokenRef.current = event.data.token;\n for (const resolve of [...tokenWaiters]) resolve(event.data.token);\n }\n };\n window.addEventListener('message', handler);\n\n // A renewal asks the parent to send its current token again — the same\n // `athena-auth-ready` handshake a parent already answers — and settles on\n // the next `athena-auth`, or on the held token when none arrives in time.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return new Promise<string | null>((resolve) => {\n const settle = (token: string | null) => {\n clearTimeout(timer);\n tokenWaiters.delete(settle);\n resolve(token ?? tokenRef.current);\n };\n const timer = setTimeout(() => settle(null), PARENT_TOKEN_RENEWAL_TIMEOUT_MS);\n tokenWaiters.add(settle);\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n });\n };\n\n if (!readySignalSent.current) {\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n readySignalSent.current = true;\n }\n\n const timer = setTimeout(() => {\n endOnce('auth.bridge.timeout', {\n data: {\n mode: 'iframe',\n timeout_ms: BRIDGE_TIMEOUT_MS,\n config_received: configReceived.current,\n },\n });\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n }, BRIDGE_TIMEOUT_MS);\n\n return () => {\n window.removeEventListener('message', handler);\n clearTimeout(timer);\n renewerRef.current = null;\n for (const resolve of [...tokenWaiters]) resolve(null);\n };\n }, [isInIframe, runtimeTrustedOrigins]);\n\n useEffect(() => {\n if (isInIframe) return;\n if (typeof window === 'undefined') return;\n\n let cancelled = false;\n // Whether a mint succeeded on this page: only then does a refused renewal\n // mean a session ended under it.\n let minted = false;\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n let inFlight: Promise<string | null> | null = null;\n let retryWait: { timer: ReturnType<typeof setTimeout>; resolve: () => void } | null = null;\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const endRetryWait = () => {\n if (!retryWait) return;\n clearTimeout(retryWait.timer);\n const { resolve } = retryWait;\n retryWait = null;\n resolve();\n };\n\n /** The backoff between two attempts of one cycle; unmounting ends it at once. */\n const waitBeforeRetry = (delayMs: number) =>\n new Promise<void>((resolve) => {\n retryWait = { timer: setTimeout(endRetryWait, delayMs), resolve };\n });\n\n // The only background mint: once per token, shortly before it expires. It\n // is not rescheduled when its cycle gives up. A forced renewal that gives\n // up leaves it in place on purpose: the held token is still live, and\n // this one bounded cycle keeps a viewer connected past a brief outage.\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n refreshTimer = null;\n void fetchAuth({ initial: false });\n }, delay);\n };\n\n /** One `/_athena/auth` request, classified. */\n const mintOnce = async (timeoutMs: number): Promise<MintAttempt> => {\n const requestController = new AbortController();\n controller = requestController;\n let timedOut = false;\n const requestTimer = setTimeout(() => {\n timedOut = true;\n requestController.abort();\n }, timeoutMs);\n\n try {\n const resp = await fetch('/_athena/auth', {\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: requestController.signal,\n });\n if (cancelled) return { kind: 'cancelled' };\n // Not a Marathon origin: expected for standalone apps and local dev.\n if (resp.status === 404) return { kind: 'absent', status: resp.status };\n if (!resp.ok) {\n const hostError = await readHostError(resp);\n if (cancelled) return { kind: 'cancelled' };\n // `readHostError` swallows the abort of a stalled error body. Only a\n // retryable status becomes a timeout: a refusal (401, 403, other\n // 4xx) is known from its status and still fails after one request.\n if (timedOut && isRetryableMintStatus(resp.status)) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null };\n }\n return isRetryableMintStatus(resp.status)\n ? { kind: 'transient', status: resp.status, reason: 'status', hostError }\n : { kind: 'refused', status: resp.status, reason: 'status', hostError };\n }\n\n let data: unknown;\n try {\n data = await resp.json();\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n if (cancelled) return { kind: 'cancelled' };\n // Marathon's 200 always carries a token. A 2xx without one is a\n // broken mint: it must not replace a held token or count as success.\n const token = data && typeof data === 'object' ? (data as { token?: unknown }).token : null;\n if (typeof token !== 'string' || token === '') {\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n return { kind: 'minted', data: data as AthenaAuthResponse };\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'transient', status: null, reason: 'network', hostError: null, cause: error };\n } finally {\n clearTimeout(requestTimer);\n if (controller === requestController) controller = null;\n }\n };\n\n /**\n * One bounded mint cycle: the initial acquisition (a span the provider\n * waits on) or a renewal (the scheduled refresh, a forced renewal). It\n * retries a transient failure up to `maxAttempts` requests in all, with\n * a capped, jittered backoff. On a refusal, or after the last attempt, it\n * gives up: it reports one typed error, sets `error`, keeps any token an\n * earlier mint handed out, and schedules nothing.\n */\n const runMintCycle = async ({ initial }: { initial: boolean }): Promise<string | null> => {\n const policy = mintRetryPolicyRef.current;\n const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));\n const phase = initial ? 'initial' : 'renewal';\n const endBridgeSpan = initial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n\n let attempts = 0;\n let lastFailure: MintFailure | null = null;\n while (attempts < maxAttempts) {\n attempts += 1;\n const outcome = await mintOnce(policy.timeoutMs);\n if (cancelled || outcome.kind === 'cancelled') return null;\n\n if (outcome.kind === 'absent') {\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: outcome.status },\n });\n // 404 means no bridge here, so an earlier mint failure no longer\n // applies; the provider falls back to its other credentials.\n setState((prev) =>\n prev.ready && prev.error == null ? prev : { ...prev, ready: true, error: null },\n );\n return null;\n }\n\n if (outcome.kind === 'minted') {\n const { data } = outcome;\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string', attempts },\n });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at },\n });\n } else {\n athenaDiagnostics.emit('auth.token.refreshed', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at, attempts },\n });\n }\n const token = typeof data.token === 'string' ? data.token : null;\n minted = true;\n setState({\n token,\n ...readAuthMintUrls(data),\n hostAnswered: true,\n ready: true,\n error: null,\n });\n tokenRef.current = token;\n scheduleRefresh(data.expires_at);\n return token;\n }\n\n lastFailure = outcome;\n if (outcome.kind === 'refused' || attempts >= maxAttempts) break;\n\n const delayMs = getMintRetryDelay({ retry: attempts, policy });\n athenaDiagnostics.emit('auth.bridge.retry', {\n level: 'warn',\n data: {\n mode: 'marathon',\n phase,\n status: outcome.status,\n reason: outcome.reason,\n host_error: outcome.hostError,\n attempt: attempts,\n max_attempts: maxAttempts,\n delay_ms: delayMs,\n },\n });\n await waitBeforeRetry(delayMs);\n if (cancelled) return null;\n }\n\n if (!lastFailure) return null;\n const error = mintFailureError({ failure: lastFailure, attempts, phase });\n const failureData = {\n mode: 'marathon',\n phase,\n status: lastFailure.status,\n reason: lastFailure.reason,\n attempts,\n };\n if (endBridgeSpan) {\n endBridgeSpan(\n error.code === 'auth_bridge_timeout' ? 'auth.bridge.timeout' : 'auth.bridge.error',\n { data: failureData, error },\n );\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', {\n level: 'error',\n data: failureData,\n error,\n });\n }\n setState((prev) => ({ ...prev, error, ready: true }));\n // A renewal refused because the session ended (401; a 403 is an access\n // refusal that signing in again cannot change). The held token keeps the\n // app on screen while the page leaves, so it keeps what it needs first.\n if (\n !initial &&\n minted &&\n signBackInRef.current &&\n lastFailure.kind === 'refused' &&\n lastFailure.status === 401\n ) {\n signBackIn({ loginUrl: MARATHON_SIGN_IN_PATH, source: 'bridge', status: lastFailure.status });\n }\n return null;\n };\n\n // One mint cycle at a time. The initial acquisition, a scheduled refresh\n // and forced renewals that overlap share the cycle in flight (its\n // requests, their timeouts and its backoff) instead of starting their own,\n // so concurrent callers never multiply the requests.\n const fetchAuth = (options: { initial: boolean }): Promise<string | null> => {\n inFlight ??= runMintCycle(options).finally(() => {\n inFlight = null;\n });\n return inFlight;\n };\n\n // A forced renewal mints now instead of waiting for the scheduled refresh\n // (joining a cycle already in flight), and may start a new bounded cycle\n // after one gave up. The schedule restarts from the new token's expiry.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return fetchAuth({ initial: false }).then((token) => token ?? tokenRef.current);\n };\n\n void fetchAuth({ initial: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRefreshTimer();\n endRetryWait();\n renewerRef.current = null;\n };\n }, [isInIframe]);\n\n const renewToken = useCallback<AthenaTokenRenewer>(\n (options) => renewerRef.current?.(options) ?? Promise.resolve(tokenRef.current),\n [],\n );\n\n return useMemo(() => ({ ...state, renewToken }), [state, renewToken]);\n}\n\n/**\n * Legacy hook — returns just the auth token from the parent bridge.\n * Prefer useParentBridge() for new code.\n */\nexport function useParentAuth(): string | null {\n return useParentBridge().token;\n}\n"],"names":["AthenaSdkError","useMemo","useState","useRef","useEffect","athenaDiagnostics","timer","signBackIn","useCallback"],"mappings":"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"}
@@ -134,9 +134,26 @@ export interface ParentBridgeOptions {
134
134
  * Useful for private-cloud or custom-domain deployments.
135
135
  */
136
136
  trustedOrigins?: string[];
137
+ /**
138
+ * When a renewal of a credential this page already minted is refused with
139
+ * a 401 (the viewer signed out of Athena elsewhere, or Marathon's session
140
+ * for this app ended), send the page through Marathon's sign-in
141
+ * ({@link MARATHON_SIGN_IN_PATH}) once, returning to the page
142
+ * (`auth/sign-back-in.ts`). Never for a 403: that is Marathon refusing this
143
+ * viewer the app (`not_authorized`, `unknown_asset`), which signing in again
144
+ * cannot change, so the bridge error stays. Never for the first mint: a page
145
+ * Marathon served on a preview grant alone has no session to renew.
146
+ * `AthenaProvider` turns it on only when the bridge's token is the chat's
147
+ * credential (`bridgeOwnsCredential`): no sign-in provider above it, which
148
+ * otherwise decides, and no host `token`, `getToken` or `apiKey`. Defaults
149
+ * to `false`.
150
+ */
151
+ signBackInOnSessionEnd?: boolean;
137
152
  /** @internal Overrides {@link DEFAULT_MINT_RETRY_POLICY}; used by tests. */
138
153
  mintRetryPolicy?: MintRetryPolicy;
139
154
  }
155
+ /** Marathon's sign-in entry on every app host: it returns to `return_to` signed in. */
156
+ export declare const MARATHON_SIGN_IN_PATH = "/api/sso/initiate";
140
157
  /**
141
158
  * How one Marathon mint cycle retries. A cycle is the initial acquisition, a
142
159
  * scheduled refresh, or a forced renewal. It sends at most `maxAttempts`
@@ -221,7 +238,7 @@ export declare function mintFailureError({ failure, attempts, phase, }: {
221
238
  * `ready: true` after a timeout, and a Marathon mint cycle ends after at most
222
239
  * `maxAttempts` requests.
223
240
  */
224
- export declare function useParentBridge({ trustedOrigins, mintRetryPolicy, }?: ParentBridgeOptions): ParentBridge;
241
+ export declare function useParentBridge({ trustedOrigins, signBackInOnSessionEnd, mintRetryPolicy, }?: ParentBridgeOptions): ParentBridge;
225
242
  /**
226
243
  * Legacy hook — returns just the auth token from the parent bridge.
227
244
  * Prefer useParentBridge() for new code.
@@ -1,4 +1,5 @@
1
1
  import { useMemo, useState, useRef, useEffect, useCallback } from "react";
2
+ import { signBackIn } from "../auth/sign-back-in.js";
2
3
  import { athenaDiagnostics } from "../diagnostics/athena-diagnostics.js";
3
4
  import { AthenaSdkError } from "../diagnostics/errors.js";
4
5
  const CUSTOMER_APP_HOST = /^.+\.(?:(?:[a-z0-9-]+-)?app(?:\.[a-z0-9-]+)*|computer(?:-[a-z0-9-]+)?\.stg|code-sandpack(?:\.[a-z0-9-]+)*)\.athenaintel\.com$/i;
@@ -104,6 +105,7 @@ function acceptBridgeConfigMessage({
104
105
  function readAuthMintUrls(data) {
105
106
  return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);
106
107
  }
108
+ const MARATHON_SIGN_IN_PATH = "/api/sso/initiate";
107
109
  const BRIDGE_TIMEOUT_MS = 2e3;
108
110
  const PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5e3;
109
111
  const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
@@ -199,6 +201,7 @@ function mintFailureError({
199
201
  }
200
202
  function useParentBridge({
201
203
  trustedOrigins = EMPTY_TRUSTED_ORIGINS,
204
+ signBackInOnSessionEnd = false,
202
205
  mintRetryPolicy = DEFAULT_MINT_RETRY_POLICY
203
206
  } = {}) {
204
207
  const isInIframe = typeof window !== "undefined" && window.parent !== window;
@@ -224,6 +227,8 @@ function useParentBridge({
224
227
  });
225
228
  const mintRetryPolicyRef = useRef(mintRetryPolicy);
226
229
  mintRetryPolicyRef.current = mintRetryPolicy;
230
+ const signBackInRef = useRef(signBackInOnSessionEnd);
231
+ signBackInRef.current = signBackInOnSessionEnd;
227
232
  const readySignalSent = useRef(false);
228
233
  const configReceived = useRef(false);
229
234
  const parentUrlsRef = useRef(EMPTY_BRIDGE_URLS);
@@ -326,6 +331,7 @@ function useParentBridge({
326
331
  if (isInIframe) return;
327
332
  if (typeof window === "undefined") return;
328
333
  let cancelled = false;
334
+ let minted = false;
329
335
  let refreshTimer = null;
330
336
  let controller = null;
331
337
  let inFlight = null;
@@ -442,6 +448,7 @@ function useParentBridge({
442
448
  });
443
449
  }
444
450
  const token = typeof data.token === "string" ? data.token : null;
451
+ minted = true;
445
452
  setState({
446
453
  token,
447
454
  ...readAuthMintUrls(data),
@@ -494,6 +501,9 @@ function useParentBridge({
494
501
  });
495
502
  }
496
503
  setState((prev) => ({ ...prev, error, ready: true }));
504
+ if (!initial && minted && signBackInRef.current && lastFailure.kind === "refused" && lastFailure.status === 401) {
505
+ signBackIn({ loginUrl: MARATHON_SIGN_IN_PATH, source: "bridge", status: lastFailure.status });
506
+ }
497
507
  return null;
498
508
  };
499
509
  const fetchAuth = (options) => {
@@ -529,6 +539,7 @@ function useParentAuth() {
529
539
  }
530
540
  export {
531
541
  DEFAULT_MINT_RETRY_POLICY,
542
+ MARATHON_SIGN_IN_PATH,
532
543
  acceptBridgeConfigMessage,
533
544
  getMintRetryDelay,
534
545
  getTokenRefreshDelay,
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sources":["../../src/runtime/auth.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { AthenaTokenRenewer } from '../auth/credential-source';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Hosts under `athenaintel.com` that serve customer-written code:\n * - Marathon's computer and application apps: `{id}.app.athenaintel.com`,\n * `{id}.staging-app.athenaintel.com`, `{id}.canary-app.athenaintel.com`, an\n * environment's `{id}.app.{env}.athenaintel.com`, and the staging wildcard\n * `{id}.computer.stg.athenaintel.com` (and `{id}.computer-canary.stg.athenaintel.com`);\n * - the Sandpack bundler's User Interface previews:\n * `{id}-sandpack.code-sandpack.athenaintel.com` (and `.code-sandpack.{env}.`).\n * They are never a trusted parent and never a backend the bridge points the\n * SDK at.\n */\nconst CUSTOMER_APP_HOST =\n /^.+\\.(?:(?:[a-z0-9-]+-)?app(?:\\.[a-z0-9-]+)*|computer(?:-[a-z0-9-]+)?\\.stg|code-sandpack(?:\\.[a-z0-9-]+)*)\\.athenaintel\\.com$/i;\n\n/** Whether `hostname` serves customer apps (see {@link CUSTOMER_APP_HOST}). */\nexport function isCustomerAppHost(hostname: string): boolean {\n return CUSTOMER_APP_HOST.test(hostname);\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';\n}\n\nfunction isAthenaHost(hostname: string): boolean {\n return hostname === 'athenaintel.com' || hostname.endsWith('.athenaintel.com');\n}\n\n/**\n * Check whether a PostMessage origin is trusted as the parent.\n * Accepts:\n * - explicit trusted origins passed by the host app\n * - the current iframe origin (for same-origin wrappers like Marathon)\n * - Athena's own hosts under athenaintel.com and localhost (default\n * behavior), except the customer-app hosts Marathon serves\n */\nexport function isTrustedParentOrigin({\n origin,\n trustedOrigins,\n}: {\n origin: string;\n trustedOrigins: string[];\n}): boolean {\n try {\n const normalizedOrigin = new URL(origin).origin;\n if (trustedOrigins.includes(normalizedOrigin)) {\n return true;\n }\n\n const { hostname } = new URL(normalizedOrigin);\n if (isCustomerAppHost(hostname)) {\n return false;\n }\n return isAthenaHost(hostname) || hostname === 'localhost';\n } catch {\n return false;\n }\n}\n\n/** Why {@link isAcceptableBridgeUrl} refused a URL. */\nexport type BridgeUrlRefusal = 'invalid' | 'insecure' | 'customer_app_host' | 'other_domain';\n\n/**\n * Whether a URL a trusted parent sent in `athena-config` may become the\n * SDK's backend, statewire mount or app URL. The SDK sends its bearer token\n * to the first two, so a URL is taken only when it is `https:` (plain\n * `http:` only between loopback hosts, for local development), carries no\n * credentials, is not a customer-code host ({@link CUSTOMER_APP_HOST}), and\n * is one of Athena's own hosts or, for a parent outside `athenaintel.com`,\n * the parent's own host or a subdomain of it. Sibling hosts are not taken:\n * without a public-suffix list `portal.example.co.uk` and `tenant.github.io`\n * cannot be told apart from a private domain, so a wrapper whose backend is\n * elsewhere passes its URLs through `config`. Returns the refusal, or `null`\n * when the URL is acceptable.\n */\nexport function refuseBridgeUrl({\n url,\n parentOrigin,\n}: {\n url: string;\n parentOrigin: string;\n}): BridgeUrlRefusal | null {\n let parsed: URL;\n let parentHostname: string;\n try {\n parsed = new URL(url);\n parentHostname = new URL(parentOrigin).hostname;\n } catch {\n return 'invalid';\n }\n if (parsed.username || parsed.password) return 'invalid';\n const { hostname } = parsed;\n const loopbackPair = isLoopbackHost(hostname) && isLoopbackHost(parentHostname);\n if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopbackPair)) {\n return 'insecure';\n }\n if (isCustomerAppHost(hostname)) return 'customer_app_host';\n if (loopbackPair || isAthenaHost(hostname)) return null;\n // Athena and loopback parents point only at Athena's own hosts (above).\n if (isAthenaHost(parentHostname) || isLoopbackHost(parentHostname)) return 'other_domain';\n return hostname === parentHostname || hostname.endsWith(`.${parentHostname}`)\n ? null\n : 'other_domain';\n}\n\n/** {@link refuseBridgeUrl} as a predicate. */\nexport function isAcceptableBridgeUrl(options: { url: string; parentOrigin: string }): boolean {\n return refuseBridgeUrl(options) === null;\n}\n\nexport interface ParentBridgeState {\n /** PropelAuth access token from the parent window. */\n token: string | null;\n /**\n * Statewire sync mount (`…/v2/threads`) minted by the host. `null` when the\n * host minted none; the provider then derives it from the configured\n * `apiUrl`'s origin (or the environment preset).\n */\n statewireSyncUrl: string | null;\n /**\n * Bare Agora API base (no path) minted by the host. `null` when the host\n * minted none; the provider then uses the configured `backendUrl` (or the\n * environment preset).\n */\n backendBaseUrl: string | null;\n /** Athena frontend origin provided by the parent wrapper. */\n appUrl: string | null;\n /**\n * True once a host answered the bridge — a `/_athena/auth` mint or an\n * `athena-config` message — whether or not it minted the statewire fields.\n * Lets the provider tell an outdated host (answered, no mount) from no host\n * at all (the environment preset applies).\n */\n hostAnswered: boolean;\n /** True once config has been received from the parent (or timeout fired). */\n ready: boolean;\n /**\n * Set when the Marathon mint (`/_athena/auth`) gave up. It gives up when it\n * gets a refusal (401, 403, or any 4xx other than 404), a body it cannot\n * read, or a transient failure (5xx, 408, 429, a network error, a timeout)\n * on every attempt of one bounded retry cycle. `code` is\n * `auth_bridge_failed`, or `auth_bridge_timeout` when the last attempt\n * timed out. After that the bridge does not mint again in the background. A\n * forced renewal (`renewToken({ force: true })`) starts one new bounded\n * cycle. A token that an earlier mint handed out is kept (the server still\n * validates it), and the bridge never puts an empty token in its place. The\n * next successful mint clears it. Absent or `null` otherwise.\n */\n error?: AthenaSdkError | null;\n}\n\n/**\n * {@link ParentBridgeState} plus the bridge's token renewal: `force` (the\n * token was refused) asks the host for a new one — a fresh `/_athena/auth`\n * mint on a Marathon origin, or an `athena-auth-ready` request answered by the\n * parent window's next `athena-auth` — and resolves the held token otherwise.\n */\nexport type ParentBridge = ParentBridgeState & { renewToken: AthenaTokenRenewer };\n\nconst BRIDGE_URL_FIELDS = ['statewireSyncUrl', 'backendBaseUrl', 'appUrl'] as const;\n\ntype BridgeUrlField = (typeof BRIDGE_URL_FIELDS)[number];\n\n/** The URL half of {@link ParentBridgeState} — what a host can hand the SDK. */\nexport type ParentBridgeUrls = Pick<ParentBridgeState, BridgeUrlField>;\n\nconst EMPTY_BRIDGE_URLS: ParentBridgeUrls = {\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n};\n\n/**\n * Fold a URL payload over the current URL set without vetting it (the\n * same-origin `/_athena/auth` mint body; a parent's `athena-config` goes\n * through {@link acceptBridgeConfigMessage}). A field is taken only when it is\n * a string; anything else keeps the previous value. Keys outside\n * {@link BridgeUrlField} (a legacy parent's `apiUrl` / `backendUrl` among\n * them) are dropped.\n */\nexport function mergeBridgeConfigMessage(\n previous: ParentBridgeUrls,\n payload: Partial<Record<BridgeUrlField, unknown>>,\n): ParentBridgeUrls {\n const next: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value === 'string') {\n next[field] = value;\n }\n }\n return next;\n}\n\n/** A URL field of an `athena-config` message that was not taken, and why. */\nexport interface RejectedBridgeUrl {\n field: BridgeUrlField;\n reason: BridgeUrlRefusal | 'changed';\n}\n\n/**\n * Fold an `athena-config` message from a trusted parent over the current URL\n * set, taking only what is safe to point the SDK's bearer at: each string\n * field must pass {@link refuseBridgeUrl} against the parent's origin. The\n * first config message sets the URLs; once `locked` (a config message was\n * already taken) a value that differs from the held one, including a field\n * the first message left unset, is refused (`changed`), so a later message\n * cannot repoint a running SDK. Resending the same value is fine.\n */\nexport function acceptBridgeConfigMessage({\n previous,\n payload,\n parentOrigin,\n locked = false,\n}: {\n previous: ParentBridgeUrls;\n payload: Partial<Record<BridgeUrlField, unknown>>;\n parentOrigin: string;\n locked?: boolean;\n}): { urls: ParentBridgeUrls; rejected: RejectedBridgeUrl[] } {\n const urls: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n const rejected: RejectedBridgeUrl[] = [];\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value !== 'string') continue;\n const held = previous[field];\n if (locked || held !== null) {\n if (held !== value) rejected.push({ field, reason: 'changed' });\n continue;\n }\n const refusal = refuseBridgeUrl({ url: value, parentOrigin });\n if (refusal !== null) {\n rejected.push({ field, reason: refusal });\n continue;\n }\n urls[field] = value;\n }\n return { urls, rejected };\n}\n\n/**\n * Read the URL set out of a `/_athena/auth` mint body. Non-string fields are\n * `null`.\n */\nexport function readAuthMintUrls(data: Partial<Record<BridgeUrlField, unknown>>): ParentBridgeUrls {\n return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);\n}\n\nexport interface ParentBridgeOptions {\n /**\n * Exact parent origins that are allowed to send auth/config via postMessage.\n * Useful for private-cloud or custom-domain deployments.\n */\n trustedOrigins?: string[];\n /** @internal Overrides {@link DEFAULT_MINT_RETRY_POLICY}; used by tests. */\n mintRetryPolicy?: MintRetryPolicy;\n}\n\n/** How long to wait for the parent bridge before falling back to defaults. */\nconst BRIDGE_TIMEOUT_MS = 2000;\n/** How long a renewal waits for the parent window to answer `athena-auth-ready`. */\nconst PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5000;\nconst TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;\nconst TOKEN_REFRESH_MIN_DELAY_MS = 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\n\n/**\n * How one Marathon mint cycle retries. A cycle is the initial acquisition, a\n * scheduled refresh, or a forced renewal. It sends at most `maxAttempts`\n * requests. It retries only a transient failure (5xx, 408, 429, a network\n * error, a timeout), after a capped exponential backoff with jitter, and then\n * it gives up. Nothing retries in the background after a cycle gave up.\n */\nexport interface MintRetryPolicy {\n /** Requests per cycle, the first one included. */\n maxAttempts: number;\n /** Ceiling of the delay before the first retry; it doubles for each retry. */\n baseDelayMs: number;\n /** Largest delay before any retry. */\n maxDelayMs: number;\n /** How long one request can take before it is aborted and counted as a timeout. */\n timeoutMs: number;\n /** Source of jitter in `[0, 1)`. */\n random: () => number;\n}\n\nexport const DEFAULT_MINT_RETRY_POLICY: MintRetryPolicy = {\n maxAttempts: 4,\n baseDelayMs: 500,\n maxDelayMs: 4000,\n timeoutMs: BRIDGE_TIMEOUT_MS,\n random: Math.random,\n};\n\n/** Untrusted shape of the `/_athena/auth` mint body; every field is type-guarded on read. */\ninterface AthenaAuthResponse {\n token?: unknown;\n statewireSyncUrl?: unknown;\n backendBaseUrl?: unknown;\n appUrl?: unknown;\n expires_at?: unknown;\n}\n\nconst normalizeOrigin = (value: string): string | null => {\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nexport const getTokenRefreshDelay = (expiresAt: unknown, nowMs: number): number => {\n if (typeof expiresAt !== 'string') {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n const expiresAtMs = Date.parse(expiresAt);\n if (!Number.isFinite(expiresAtMs)) {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n return Math.max(TOKEN_REFRESH_MIN_DELAY_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\n/**\n * The delay before retry number `retry` of a mint cycle (1 is the first\n * retry). The ceiling is `min(maxDelayMs, baseDelayMs * 2^(retry - 1))`, and\n * equal jitter puts the wait between half of that ceiling and all of it, so\n * viewers whose mint failed at the same time do not retry at the same time.\n */\nexport function getMintRetryDelay({\n retry,\n policy = DEFAULT_MINT_RETRY_POLICY,\n}: {\n retry: number;\n policy?: Pick<MintRetryPolicy, 'baseDelayMs' | 'maxDelayMs' | 'random'>;\n}): number {\n const safeRetry = Number.isFinite(retry) ? Math.max(1, Math.floor(retry)) : 1;\n const ceiling = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (safeRetry - 1));\n const jitter = Math.min(1, Math.max(0, policy.random()));\n return Math.round(ceiling / 2 + (ceiling / 2) * jitter);\n}\n\n/** A mint status worth another attempt: the host, or something in front of it, is briefly unwell. */\nexport function isRetryableMintStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\n/** A failed `/_athena/auth` request that is worth another attempt. */\ninterface TransientMintFailure {\n kind: 'transient';\n status: number | null;\n reason: 'status' | 'network' | 'timeout';\n hostError: string | null;\n cause?: unknown;\n}\n\n/** A failed `/_athena/auth` request that ends its cycle at once. */\ninterface RefusedMintFailure {\n kind: 'refused';\n status: number;\n /** `status`: a 4xx other than 404, 408 and 429. `invalid_body`: a 2xx whose body is not a JSON object. */\n reason: 'status' | 'invalid_body';\n hostError: string | null;\n}\n\nexport type MintFailure = TransientMintFailure | RefusedMintFailure;\n\n/** The outcome of one `/_athena/auth` request. */\ntype MintAttempt =\n | { kind: 'minted'; data: AthenaAuthResponse }\n /** 404: this is not a Marathon origin. */\n | { kind: 'absent'; status: number }\n | MintFailure\n | { kind: 'cancelled' };\n\n/** The `error` string of a Marathon failure body (`{\"error\":\"mint_failed\"}`), if any. */\nasync function readHostError(resp: Response): Promise<string | null> {\n try {\n const body: unknown = await resp.json();\n if (body && typeof body === 'object' && 'error' in body) {\n const value = (body as { error?: unknown }).error;\n return typeof value === 'string' ? value.slice(0, 100) : null;\n }\n } catch {\n // An empty or non-JSON error body tells nothing more than the status.\n }\n return null;\n}\n\nconst MINT_RETRY_NOTE =\n 'The SDK stopped retrying and does not connect without a credential. Remount the provider, or call renewToken({ force: true }), to try again.';\n\nfunction mintFailureHint(failure: MintFailure): string {\n if (failure.kind === 'transient') {\n return `The host could not mint a viewer token. Check the Marathon and Agora logs. ${MINT_RETRY_NOTE}`;\n }\n if (failure.reason === 'invalid_body') {\n return `The host answered /_athena/auth without a JSON mint body, so this origin is not a Marathon bridge. Pass token, getToken or apiKey. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 401) {\n return `The host has no session for this viewer. Sign in again through the app URL. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 403) {\n return `The host does not give this viewer access to the app. ${MINT_RETRY_NOTE}`;\n }\n return `The host refused the mint request. ${MINT_RETRY_NOTE}`;\n}\n\n/** The typed error that a mint cycle reports when it gives up. */\nexport function mintFailureError({\n failure,\n attempts,\n phase,\n}: {\n failure: MintFailure;\n attempts: number;\n phase: 'initial' | 'renewal';\n}): AthenaSdkError {\n const timedOut = failure.kind === 'transient' && failure.reason === 'timeout';\n const answer =\n failure.status !== null ? `HTTP ${failure.status}` : timedOut ? 'a timeout' : 'a network error';\n const message =\n failure.kind === 'transient'\n ? `The host auth bridge (/_athena/auth) failed ${attempts} time${attempts === 1 ? '' : 's'} (last: ${answer}).`\n : failure.reason === 'invalid_body'\n ? `The host auth bridge (/_athena/auth) answered ${answer} without a JSON mint body.`\n : `The host auth bridge (/_athena/auth) refused the mint (${answer}).`;\n return new AthenaSdkError({\n code: timedOut ? 'auth_bridge_timeout' : 'auth_bridge_failed',\n message,\n hint: mintFailureHint(failure),\n ...(failure.status !== null && { status: failure.status }),\n ...(failure.hostError !== null && { detail: { code: failure.hostError } }),\n ...(failure.kind === 'transient' && failure.cause !== undefined && { cause: failure.cause }),\n context: {\n mode: 'marathon',\n phase,\n attempts,\n reason: failure.reason,\n host_error: failure.hostError,\n },\n });\n}\n\n/**\n * Listens for auth tokens AND environment config from the host environment.\n *\n * Two transport modes, picked automatically:\n *\n * 1. **Iframe (legacy / Olympus inline preview)**: a parent window posts\n * `athena-config` and `athena-auth` via PostMessage. The hook also posts\n * `athena-auth-ready` so the parent knows when to (re-)send. Only the\n * window that frames the app (`event.source === window.parent`) on a\n * trusted origin ({@link isTrustedParentOrigin}) is heard, and an\n * `athena-config` URL is taken only once and only when it passes\n * {@link refuseBridgeUrl}.\n *\n * 2. **Standalone (Marathon-served deploy URL)**: top-level navigation, no\n * parent. The hook fetches `/_athena/auth` on its own origin. Marathon\n * validates its session cookie (sent by the browser automatically) and\n * returns `{ token, statewireSyncUrl, backendBaseUrl, appUrl }` — the\n * statewire mount, the bare Agora base and the app origin. A 404 means\n * we're not on a Marathon origin — the hook falls through with empty\n * state. Any other failure fails closed: one bounded retry cycle\n * ({@link MintRetryPolicy}) for a transient failure, none for a refusal,\n * and then `error` is set and `ready` flips with no token — never an\n * empty or anonymous one.\n *\n * In both modes the app never blocks indefinitely: the iframe bridge flips\n * `ready: true` after a timeout, and a Marathon mint cycle ends after at most\n * `maxAttempts` requests.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\n mintRetryPolicy = DEFAULT_MINT_RETRY_POLICY,\n}: ParentBridgeOptions = {}): ParentBridge {\n const isInIframe = typeof window !== 'undefined' && window.parent !== window;\n const runtimeTrustedOrigins = useMemo(() => {\n const origins = new Set<string>();\n\n if (typeof window !== 'undefined') {\n origins.add(window.location.origin);\n }\n\n for (const trustedOrigin of trustedOrigins) {\n const normalizedOrigin = normalizeOrigin(trustedOrigin);\n if (normalizedOrigin) {\n origins.add(normalizedOrigin);\n }\n }\n\n return [...origins];\n }, [trustedOrigins]);\n\n const [state, setState] = useState<ParentBridgeState>({\n token: null,\n ...EMPTY_BRIDGE_URLS,\n hostAnswered: false,\n ready: false,\n error: null,\n });\n // Read when a mint cycle starts, so a caller's inline policy object does not\n // restart the bridge on every render.\n const mintRetryPolicyRef = useRef(mintRetryPolicy);\n mintRetryPolicyRef.current = mintRetryPolicy;\n const readySignalSent = useRef(false);\n const configReceived = useRef(false);\n // The URLs the parent has set so far: a later `athena-config` may not change them.\n const parentUrlsRef = useRef<ParentBridgeUrls>(EMPTY_BRIDGE_URLS);\n const tokenRef = useRef<string | null>(state.token);\n tokenRef.current = state.token;\n // Installed by whichever transport mode is active (iframe or Marathon).\n const renewerRef = useRef<AthenaTokenRenewer | null>(null);\n\n useEffect(() => {\n if (!isInIframe) return;\n\n const endBridgeSpan = athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', {\n mode: 'iframe',\n });\n let spanEnded = false;\n const endOnce: typeof endBridgeSpan = (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return endBridgeSpan(name, options);\n };\n\n // Renewals waiting for the parent's next `athena-auth`.\n const tokenWaiters = new Set<(token: string | null) => void>();\n\n const handler = (event: MessageEvent) => {\n // Only the window that frames this app speaks for the host: a popup,\n // an opener or a sibling frame on a trusted origin does not.\n if (event.source !== window.parent) return;\n if (\n !isTrustedParentOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })\n ) {\n return;\n }\n if (!event.data || typeof event.data !== 'object') return;\n\n if (event.data.type === 'athena-config') {\n const { urls, rejected } = acceptBridgeConfigMessage({\n previous: parentUrlsRef.current,\n payload: event.data,\n parentOrigin: event.origin,\n locked: configReceived.current,\n });\n configReceived.current = true;\n parentUrlsRef.current = urls;\n for (const { field, reason } of rejected) {\n athenaDiagnostics.emit('auth.bridge.config_rejected', {\n level: 'warn',\n data: { mode: 'iframe', field, reason },\n });\n }\n setState((prev) => ({\n ...prev,\n ...urls,\n hostAnswered: true,\n // Don't set ready here — wait for athena-auth (token) or the timeout.\n // Setting ready on config alone causes a race: the thread list fires\n // before the token arrives, falling back to X-API-KEY.\n }));\n }\n\n if (event.data.type === 'athena-auth' && typeof event.data.token === 'string') {\n endOnce('auth.bridge.ready', { data: { mode: 'iframe' } });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'iframe-postmessage' },\n });\n setState((prev) => ({\n ...prev,\n token: event.data.token,\n ready: true,\n }));\n tokenRef.current = event.data.token;\n for (const resolve of [...tokenWaiters]) resolve(event.data.token);\n }\n };\n window.addEventListener('message', handler);\n\n // A renewal asks the parent to send its current token again — the same\n // `athena-auth-ready` handshake a parent already answers — and settles on\n // the next `athena-auth`, or on the held token when none arrives in time.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return new Promise<string | null>((resolve) => {\n const settle = (token: string | null) => {\n clearTimeout(timer);\n tokenWaiters.delete(settle);\n resolve(token ?? tokenRef.current);\n };\n const timer = setTimeout(() => settle(null), PARENT_TOKEN_RENEWAL_TIMEOUT_MS);\n tokenWaiters.add(settle);\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n });\n };\n\n if (!readySignalSent.current) {\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n readySignalSent.current = true;\n }\n\n const timer = setTimeout(() => {\n endOnce('auth.bridge.timeout', {\n data: {\n mode: 'iframe',\n timeout_ms: BRIDGE_TIMEOUT_MS,\n config_received: configReceived.current,\n },\n });\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n }, BRIDGE_TIMEOUT_MS);\n\n return () => {\n window.removeEventListener('message', handler);\n clearTimeout(timer);\n renewerRef.current = null;\n for (const resolve of [...tokenWaiters]) resolve(null);\n };\n }, [isInIframe, runtimeTrustedOrigins]);\n\n useEffect(() => {\n if (isInIframe) return;\n if (typeof window === 'undefined') return;\n\n let cancelled = false;\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n let inFlight: Promise<string | null> | null = null;\n let retryWait: { timer: ReturnType<typeof setTimeout>; resolve: () => void } | null = null;\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const endRetryWait = () => {\n if (!retryWait) return;\n clearTimeout(retryWait.timer);\n const { resolve } = retryWait;\n retryWait = null;\n resolve();\n };\n\n /** The backoff between two attempts of one cycle; unmounting ends it at once. */\n const waitBeforeRetry = (delayMs: number) =>\n new Promise<void>((resolve) => {\n retryWait = { timer: setTimeout(endRetryWait, delayMs), resolve };\n });\n\n // The only background mint: once per token, shortly before it expires. It\n // is not rescheduled when its cycle gives up. A forced renewal that gives\n // up leaves it in place on purpose: the held token is still live, and\n // this one bounded cycle keeps a viewer connected past a brief outage.\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n refreshTimer = null;\n void fetchAuth({ initial: false });\n }, delay);\n };\n\n /** One `/_athena/auth` request, classified. */\n const mintOnce = async (timeoutMs: number): Promise<MintAttempt> => {\n const requestController = new AbortController();\n controller = requestController;\n let timedOut = false;\n const requestTimer = setTimeout(() => {\n timedOut = true;\n requestController.abort();\n }, timeoutMs);\n\n try {\n const resp = await fetch('/_athena/auth', {\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: requestController.signal,\n });\n if (cancelled) return { kind: 'cancelled' };\n // Not a Marathon origin: expected for standalone apps and local dev.\n if (resp.status === 404) return { kind: 'absent', status: resp.status };\n if (!resp.ok) {\n const hostError = await readHostError(resp);\n if (cancelled) return { kind: 'cancelled' };\n // `readHostError` swallows the abort of a stalled error body. Only a\n // retryable status becomes a timeout: a refusal (401, 403, other\n // 4xx) is known from its status and still fails after one request.\n if (timedOut && isRetryableMintStatus(resp.status)) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null };\n }\n return isRetryableMintStatus(resp.status)\n ? { kind: 'transient', status: resp.status, reason: 'status', hostError }\n : { kind: 'refused', status: resp.status, reason: 'status', hostError };\n }\n\n let data: unknown;\n try {\n data = await resp.json();\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n if (cancelled) return { kind: 'cancelled' };\n // Marathon's 200 always carries a token. A 2xx without one is a\n // broken mint: it must not replace a held token or count as success.\n const token = data && typeof data === 'object' ? (data as { token?: unknown }).token : null;\n if (typeof token !== 'string' || token === '') {\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n return { kind: 'minted', data: data as AthenaAuthResponse };\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'transient', status: null, reason: 'network', hostError: null, cause: error };\n } finally {\n clearTimeout(requestTimer);\n if (controller === requestController) controller = null;\n }\n };\n\n /**\n * One bounded mint cycle: the initial acquisition (a span the provider\n * waits on) or a renewal (the scheduled refresh, a forced renewal). It\n * retries a transient failure up to `maxAttempts` requests in all, with\n * a capped, jittered backoff. On a refusal, or after the last attempt, it\n * gives up: it reports one typed error, sets `error`, keeps any token an\n * earlier mint handed out, and schedules nothing.\n */\n const runMintCycle = async ({ initial }: { initial: boolean }): Promise<string | null> => {\n const policy = mintRetryPolicyRef.current;\n const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));\n const phase = initial ? 'initial' : 'renewal';\n const endBridgeSpan = initial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n\n let attempts = 0;\n let lastFailure: MintFailure | null = null;\n while (attempts < maxAttempts) {\n attempts += 1;\n const outcome = await mintOnce(policy.timeoutMs);\n if (cancelled || outcome.kind === 'cancelled') return null;\n\n if (outcome.kind === 'absent') {\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: outcome.status },\n });\n // 404 means no bridge here, so an earlier mint failure no longer\n // applies; the provider falls back to its other credentials.\n setState((prev) =>\n prev.ready && prev.error == null ? prev : { ...prev, ready: true, error: null },\n );\n return null;\n }\n\n if (outcome.kind === 'minted') {\n const { data } = outcome;\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string', attempts },\n });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at },\n });\n } else {\n athenaDiagnostics.emit('auth.token.refreshed', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at, attempts },\n });\n }\n const token = typeof data.token === 'string' ? data.token : null;\n setState({\n token,\n ...readAuthMintUrls(data),\n hostAnswered: true,\n ready: true,\n error: null,\n });\n tokenRef.current = token;\n scheduleRefresh(data.expires_at);\n return token;\n }\n\n lastFailure = outcome;\n if (outcome.kind === 'refused' || attempts >= maxAttempts) break;\n\n const delayMs = getMintRetryDelay({ retry: attempts, policy });\n athenaDiagnostics.emit('auth.bridge.retry', {\n level: 'warn',\n data: {\n mode: 'marathon',\n phase,\n status: outcome.status,\n reason: outcome.reason,\n host_error: outcome.hostError,\n attempt: attempts,\n max_attempts: maxAttempts,\n delay_ms: delayMs,\n },\n });\n await waitBeforeRetry(delayMs);\n if (cancelled) return null;\n }\n\n if (!lastFailure) return null;\n const error = mintFailureError({ failure: lastFailure, attempts, phase });\n const failureData = {\n mode: 'marathon',\n phase,\n status: lastFailure.status,\n reason: lastFailure.reason,\n attempts,\n };\n if (endBridgeSpan) {\n endBridgeSpan(\n error.code === 'auth_bridge_timeout' ? 'auth.bridge.timeout' : 'auth.bridge.error',\n { data: failureData, error },\n );\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', {\n level: 'error',\n data: failureData,\n error,\n });\n }\n setState((prev) => ({ ...prev, error, ready: true }));\n return null;\n };\n\n // One mint cycle at a time. The initial acquisition, a scheduled refresh\n // and forced renewals that overlap share the cycle in flight (its\n // requests, their timeouts and its backoff) instead of starting their own,\n // so concurrent callers never multiply the requests.\n const fetchAuth = (options: { initial: boolean }): Promise<string | null> => {\n inFlight ??= runMintCycle(options).finally(() => {\n inFlight = null;\n });\n return inFlight;\n };\n\n // A forced renewal mints now instead of waiting for the scheduled refresh\n // (joining a cycle already in flight), and may start a new bounded cycle\n // after one gave up. The schedule restarts from the new token's expiry.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return fetchAuth({ initial: false }).then((token) => token ?? tokenRef.current);\n };\n\n void fetchAuth({ initial: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRefreshTimer();\n endRetryWait();\n renewerRef.current = null;\n };\n }, [isInIframe]);\n\n const renewToken = useCallback<AthenaTokenRenewer>(\n (options) => renewerRef.current?.(options) ?? Promise.resolve(tokenRef.current),\n [],\n );\n\n return useMemo(() => ({ ...state, renewToken }), [state, renewToken]);\n}\n\n/**\n * Legacy hook — returns just the auth token from the parent bridge.\n * Prefer useParentBridge() for new code.\n */\nexport function useParentAuth(): string | null {\n return useParentBridge().token;\n}\n"],"names":["timer"],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sources":["../../src/runtime/auth.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { AthenaTokenRenewer } from '../auth/credential-source';\nimport { signBackIn } from '../auth/sign-back-in';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/**\n * Hosts under `athenaintel.com` that serve customer-written code:\n * - Marathon's computer and application apps: `{id}.app.athenaintel.com`,\n * `{id}.staging-app.athenaintel.com`, `{id}.canary-app.athenaintel.com`, an\n * environment's `{id}.app.{env}.athenaintel.com`, and the staging wildcard\n * `{id}.computer.stg.athenaintel.com` (and `{id}.computer-canary.stg.athenaintel.com`);\n * - the Sandpack bundler's User Interface previews:\n * `{id}-sandpack.code-sandpack.athenaintel.com` (and `.code-sandpack.{env}.`).\n * They are never a trusted parent and never a backend the bridge points the\n * SDK at.\n */\nconst CUSTOMER_APP_HOST =\n /^.+\\.(?:(?:[a-z0-9-]+-)?app(?:\\.[a-z0-9-]+)*|computer(?:-[a-z0-9-]+)?\\.stg|code-sandpack(?:\\.[a-z0-9-]+)*)\\.athenaintel\\.com$/i;\n\n/** Whether `hostname` serves customer apps (see {@link CUSTOMER_APP_HOST}). */\nexport function isCustomerAppHost(hostname: string): boolean {\n return CUSTOMER_APP_HOST.test(hostname);\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '[::1]';\n}\n\nfunction isAthenaHost(hostname: string): boolean {\n return hostname === 'athenaintel.com' || hostname.endsWith('.athenaintel.com');\n}\n\n/**\n * Check whether a PostMessage origin is trusted as the parent.\n * Accepts:\n * - explicit trusted origins passed by the host app\n * - the current iframe origin (for same-origin wrappers like Marathon)\n * - Athena's own hosts under athenaintel.com and localhost (default\n * behavior), except the customer-app hosts Marathon serves\n */\nexport function isTrustedParentOrigin({\n origin,\n trustedOrigins,\n}: {\n origin: string;\n trustedOrigins: string[];\n}): boolean {\n try {\n const normalizedOrigin = new URL(origin).origin;\n if (trustedOrigins.includes(normalizedOrigin)) {\n return true;\n }\n\n const { hostname } = new URL(normalizedOrigin);\n if (isCustomerAppHost(hostname)) {\n return false;\n }\n return isAthenaHost(hostname) || hostname === 'localhost';\n } catch {\n return false;\n }\n}\n\n/** Why {@link isAcceptableBridgeUrl} refused a URL. */\nexport type BridgeUrlRefusal = 'invalid' | 'insecure' | 'customer_app_host' | 'other_domain';\n\n/**\n * Whether a URL a trusted parent sent in `athena-config` may become the\n * SDK's backend, statewire mount or app URL. The SDK sends its bearer token\n * to the first two, so a URL is taken only when it is `https:` (plain\n * `http:` only between loopback hosts, for local development), carries no\n * credentials, is not a customer-code host ({@link CUSTOMER_APP_HOST}), and\n * is one of Athena's own hosts or, for a parent outside `athenaintel.com`,\n * the parent's own host or a subdomain of it. Sibling hosts are not taken:\n * without a public-suffix list `portal.example.co.uk` and `tenant.github.io`\n * cannot be told apart from a private domain, so a wrapper whose backend is\n * elsewhere passes its URLs through `config`. Returns the refusal, or `null`\n * when the URL is acceptable.\n */\nexport function refuseBridgeUrl({\n url,\n parentOrigin,\n}: {\n url: string;\n parentOrigin: string;\n}): BridgeUrlRefusal | null {\n let parsed: URL;\n let parentHostname: string;\n try {\n parsed = new URL(url);\n parentHostname = new URL(parentOrigin).hostname;\n } catch {\n return 'invalid';\n }\n if (parsed.username || parsed.password) return 'invalid';\n const { hostname } = parsed;\n const loopbackPair = isLoopbackHost(hostname) && isLoopbackHost(parentHostname);\n if (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && loopbackPair)) {\n return 'insecure';\n }\n if (isCustomerAppHost(hostname)) return 'customer_app_host';\n if (loopbackPair || isAthenaHost(hostname)) return null;\n // Athena and loopback parents point only at Athena's own hosts (above).\n if (isAthenaHost(parentHostname) || isLoopbackHost(parentHostname)) return 'other_domain';\n return hostname === parentHostname || hostname.endsWith(`.${parentHostname}`)\n ? null\n : 'other_domain';\n}\n\n/** {@link refuseBridgeUrl} as a predicate. */\nexport function isAcceptableBridgeUrl(options: { url: string; parentOrigin: string }): boolean {\n return refuseBridgeUrl(options) === null;\n}\n\nexport interface ParentBridgeState {\n /** PropelAuth access token from the parent window. */\n token: string | null;\n /**\n * Statewire sync mount (`…/v2/threads`) minted by the host. `null` when the\n * host minted none; the provider then derives it from the configured\n * `apiUrl`'s origin (or the environment preset).\n */\n statewireSyncUrl: string | null;\n /**\n * Bare Agora API base (no path) minted by the host. `null` when the host\n * minted none; the provider then uses the configured `backendUrl` (or the\n * environment preset).\n */\n backendBaseUrl: string | null;\n /** Athena frontend origin provided by the parent wrapper. */\n appUrl: string | null;\n /**\n * True once a host answered the bridge — a `/_athena/auth` mint or an\n * `athena-config` message — whether or not it minted the statewire fields.\n * Lets the provider tell an outdated host (answered, no mount) from no host\n * at all (the environment preset applies).\n */\n hostAnswered: boolean;\n /** True once config has been received from the parent (or timeout fired). */\n ready: boolean;\n /**\n * Set when the Marathon mint (`/_athena/auth`) gave up. It gives up when it\n * gets a refusal (401, 403, or any 4xx other than 404), a body it cannot\n * read, or a transient failure (5xx, 408, 429, a network error, a timeout)\n * on every attempt of one bounded retry cycle. `code` is\n * `auth_bridge_failed`, or `auth_bridge_timeout` when the last attempt\n * timed out. After that the bridge does not mint again in the background. A\n * forced renewal (`renewToken({ force: true })`) starts one new bounded\n * cycle. A token that an earlier mint handed out is kept (the server still\n * validates it), and the bridge never puts an empty token in its place. The\n * next successful mint clears it. Absent or `null` otherwise.\n */\n error?: AthenaSdkError | null;\n}\n\n/**\n * {@link ParentBridgeState} plus the bridge's token renewal: `force` (the\n * token was refused) asks the host for a new one — a fresh `/_athena/auth`\n * mint on a Marathon origin, or an `athena-auth-ready` request answered by the\n * parent window's next `athena-auth` — and resolves the held token otherwise.\n */\nexport type ParentBridge = ParentBridgeState & { renewToken: AthenaTokenRenewer };\n\nconst BRIDGE_URL_FIELDS = ['statewireSyncUrl', 'backendBaseUrl', 'appUrl'] as const;\n\ntype BridgeUrlField = (typeof BRIDGE_URL_FIELDS)[number];\n\n/** The URL half of {@link ParentBridgeState} — what a host can hand the SDK. */\nexport type ParentBridgeUrls = Pick<ParentBridgeState, BridgeUrlField>;\n\nconst EMPTY_BRIDGE_URLS: ParentBridgeUrls = {\n statewireSyncUrl: null,\n backendBaseUrl: null,\n appUrl: null,\n};\n\n/**\n * Fold a URL payload over the current URL set without vetting it (the\n * same-origin `/_athena/auth` mint body; a parent's `athena-config` goes\n * through {@link acceptBridgeConfigMessage}). A field is taken only when it is\n * a string; anything else keeps the previous value. Keys outside\n * {@link BridgeUrlField} (a legacy parent's `apiUrl` / `backendUrl` among\n * them) are dropped.\n */\nexport function mergeBridgeConfigMessage(\n previous: ParentBridgeUrls,\n payload: Partial<Record<BridgeUrlField, unknown>>,\n): ParentBridgeUrls {\n const next: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value === 'string') {\n next[field] = value;\n }\n }\n return next;\n}\n\n/** A URL field of an `athena-config` message that was not taken, and why. */\nexport interface RejectedBridgeUrl {\n field: BridgeUrlField;\n reason: BridgeUrlRefusal | 'changed';\n}\n\n/**\n * Fold an `athena-config` message from a trusted parent over the current URL\n * set, taking only what is safe to point the SDK's bearer at: each string\n * field must pass {@link refuseBridgeUrl} against the parent's origin. The\n * first config message sets the URLs; once `locked` (a config message was\n * already taken) a value that differs from the held one, including a field\n * the first message left unset, is refused (`changed`), so a later message\n * cannot repoint a running SDK. Resending the same value is fine.\n */\nexport function acceptBridgeConfigMessage({\n previous,\n payload,\n parentOrigin,\n locked = false,\n}: {\n previous: ParentBridgeUrls;\n payload: Partial<Record<BridgeUrlField, unknown>>;\n parentOrigin: string;\n locked?: boolean;\n}): { urls: ParentBridgeUrls; rejected: RejectedBridgeUrl[] } {\n const urls: ParentBridgeUrls = {\n statewireSyncUrl: previous.statewireSyncUrl,\n backendBaseUrl: previous.backendBaseUrl,\n appUrl: previous.appUrl,\n };\n const rejected: RejectedBridgeUrl[] = [];\n for (const field of BRIDGE_URL_FIELDS) {\n const value = payload[field];\n if (typeof value !== 'string') continue;\n const held = previous[field];\n if (locked || held !== null) {\n if (held !== value) rejected.push({ field, reason: 'changed' });\n continue;\n }\n const refusal = refuseBridgeUrl({ url: value, parentOrigin });\n if (refusal !== null) {\n rejected.push({ field, reason: refusal });\n continue;\n }\n urls[field] = value;\n }\n return { urls, rejected };\n}\n\n/**\n * Read the URL set out of a `/_athena/auth` mint body. Non-string fields are\n * `null`.\n */\nexport function readAuthMintUrls(data: Partial<Record<BridgeUrlField, unknown>>): ParentBridgeUrls {\n return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);\n}\n\nexport interface ParentBridgeOptions {\n /**\n * Exact parent origins that are allowed to send auth/config via postMessage.\n * Useful for private-cloud or custom-domain deployments.\n */\n trustedOrigins?: string[];\n /**\n * When a renewal of a credential this page already minted is refused with\n * a 401 (the viewer signed out of Athena elsewhere, or Marathon's session\n * for this app ended), send the page through Marathon's sign-in\n * ({@link MARATHON_SIGN_IN_PATH}) once, returning to the page\n * (`auth/sign-back-in.ts`). Never for a 403: that is Marathon refusing this\n * viewer the app (`not_authorized`, `unknown_asset`), which signing in again\n * cannot change, so the bridge error stays. Never for the first mint: a page\n * Marathon served on a preview grant alone has no session to renew.\n * `AthenaProvider` turns it on only when the bridge's token is the chat's\n * credential (`bridgeOwnsCredential`): no sign-in provider above it, which\n * otherwise decides, and no host `token`, `getToken` or `apiKey`. Defaults\n * to `false`.\n */\n signBackInOnSessionEnd?: boolean;\n /** @internal Overrides {@link DEFAULT_MINT_RETRY_POLICY}; used by tests. */\n mintRetryPolicy?: MintRetryPolicy;\n}\n\n/** Marathon's sign-in entry on every app host: it returns to `return_to` signed in. */\nexport const MARATHON_SIGN_IN_PATH = '/api/sso/initiate';\n\n/** How long to wait for the parent bridge before falling back to defaults. */\nconst BRIDGE_TIMEOUT_MS = 2000;\n/** How long a renewal waits for the parent window to answer `athena-auth-ready`. */\nconst PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5000;\nconst TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;\nconst TOKEN_REFRESH_MIN_DELAY_MS = 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\n\n/**\n * How one Marathon mint cycle retries. A cycle is the initial acquisition, a\n * scheduled refresh, or a forced renewal. It sends at most `maxAttempts`\n * requests. It retries only a transient failure (5xx, 408, 429, a network\n * error, a timeout), after a capped exponential backoff with jitter, and then\n * it gives up. Nothing retries in the background after a cycle gave up.\n */\nexport interface MintRetryPolicy {\n /** Requests per cycle, the first one included. */\n maxAttempts: number;\n /** Ceiling of the delay before the first retry; it doubles for each retry. */\n baseDelayMs: number;\n /** Largest delay before any retry. */\n maxDelayMs: number;\n /** How long one request can take before it is aborted and counted as a timeout. */\n timeoutMs: number;\n /** Source of jitter in `[0, 1)`. */\n random: () => number;\n}\n\nexport const DEFAULT_MINT_RETRY_POLICY: MintRetryPolicy = {\n maxAttempts: 4,\n baseDelayMs: 500,\n maxDelayMs: 4000,\n timeoutMs: BRIDGE_TIMEOUT_MS,\n random: Math.random,\n};\n\n/** Untrusted shape of the `/_athena/auth` mint body; every field is type-guarded on read. */\ninterface AthenaAuthResponse {\n token?: unknown;\n statewireSyncUrl?: unknown;\n backendBaseUrl?: unknown;\n appUrl?: unknown;\n expires_at?: unknown;\n}\n\nconst normalizeOrigin = (value: string): string | null => {\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n};\n\nexport const getTokenRefreshDelay = (expiresAt: unknown, nowMs: number): number => {\n if (typeof expiresAt !== 'string') {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n const expiresAtMs = Date.parse(expiresAt);\n if (!Number.isFinite(expiresAtMs)) {\n return DEFAULT_TOKEN_REFRESH_MS;\n }\n\n return Math.max(TOKEN_REFRESH_MIN_DELAY_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\n/**\n * The delay before retry number `retry` of a mint cycle (1 is the first\n * retry). The ceiling is `min(maxDelayMs, baseDelayMs * 2^(retry - 1))`, and\n * equal jitter puts the wait between half of that ceiling and all of it, so\n * viewers whose mint failed at the same time do not retry at the same time.\n */\nexport function getMintRetryDelay({\n retry,\n policy = DEFAULT_MINT_RETRY_POLICY,\n}: {\n retry: number;\n policy?: Pick<MintRetryPolicy, 'baseDelayMs' | 'maxDelayMs' | 'random'>;\n}): number {\n const safeRetry = Number.isFinite(retry) ? Math.max(1, Math.floor(retry)) : 1;\n const ceiling = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (safeRetry - 1));\n const jitter = Math.min(1, Math.max(0, policy.random()));\n return Math.round(ceiling / 2 + (ceiling / 2) * jitter);\n}\n\n/** A mint status worth another attempt: the host, or something in front of it, is briefly unwell. */\nexport function isRetryableMintStatus(status: number): boolean {\n return status >= 500 || status === 408 || status === 429;\n}\n\n/** A failed `/_athena/auth` request that is worth another attempt. */\ninterface TransientMintFailure {\n kind: 'transient';\n status: number | null;\n reason: 'status' | 'network' | 'timeout';\n hostError: string | null;\n cause?: unknown;\n}\n\n/** A failed `/_athena/auth` request that ends its cycle at once. */\ninterface RefusedMintFailure {\n kind: 'refused';\n status: number;\n /** `status`: a 4xx other than 404, 408 and 429. `invalid_body`: a 2xx whose body is not a JSON object. */\n reason: 'status' | 'invalid_body';\n hostError: string | null;\n}\n\nexport type MintFailure = TransientMintFailure | RefusedMintFailure;\n\n/** The outcome of one `/_athena/auth` request. */\ntype MintAttempt =\n | { kind: 'minted'; data: AthenaAuthResponse }\n /** 404: this is not a Marathon origin. */\n | { kind: 'absent'; status: number }\n | MintFailure\n | { kind: 'cancelled' };\n\n/** The `error` string of a Marathon failure body (`{\"error\":\"mint_failed\"}`), if any. */\nasync function readHostError(resp: Response): Promise<string | null> {\n try {\n const body: unknown = await resp.json();\n if (body && typeof body === 'object' && 'error' in body) {\n const value = (body as { error?: unknown }).error;\n return typeof value === 'string' ? value.slice(0, 100) : null;\n }\n } catch {\n // An empty or non-JSON error body tells nothing more than the status.\n }\n return null;\n}\n\nconst MINT_RETRY_NOTE =\n 'The SDK stopped retrying and does not connect without a credential. Remount the provider, or call renewToken({ force: true }), to try again.';\n\nfunction mintFailureHint(failure: MintFailure): string {\n if (failure.kind === 'transient') {\n return `The host could not mint a viewer token. Check the Marathon and Agora logs. ${MINT_RETRY_NOTE}`;\n }\n if (failure.reason === 'invalid_body') {\n return `The host answered /_athena/auth without a JSON mint body, so this origin is not a Marathon bridge. Pass token, getToken or apiKey. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 401) {\n return `The host has no session for this viewer. Sign in again through the app URL. ${MINT_RETRY_NOTE}`;\n }\n if (failure.status === 403) {\n return `The host does not give this viewer access to the app. ${MINT_RETRY_NOTE}`;\n }\n return `The host refused the mint request. ${MINT_RETRY_NOTE}`;\n}\n\n/** The typed error that a mint cycle reports when it gives up. */\nexport function mintFailureError({\n failure,\n attempts,\n phase,\n}: {\n failure: MintFailure;\n attempts: number;\n phase: 'initial' | 'renewal';\n}): AthenaSdkError {\n const timedOut = failure.kind === 'transient' && failure.reason === 'timeout';\n const answer =\n failure.status !== null ? `HTTP ${failure.status}` : timedOut ? 'a timeout' : 'a network error';\n const message =\n failure.kind === 'transient'\n ? `The host auth bridge (/_athena/auth) failed ${attempts} time${attempts === 1 ? '' : 's'} (last: ${answer}).`\n : failure.reason === 'invalid_body'\n ? `The host auth bridge (/_athena/auth) answered ${answer} without a JSON mint body.`\n : `The host auth bridge (/_athena/auth) refused the mint (${answer}).`;\n return new AthenaSdkError({\n code: timedOut ? 'auth_bridge_timeout' : 'auth_bridge_failed',\n message,\n hint: mintFailureHint(failure),\n ...(failure.status !== null && { status: failure.status }),\n ...(failure.hostError !== null && { detail: { code: failure.hostError } }),\n ...(failure.kind === 'transient' && failure.cause !== undefined && { cause: failure.cause }),\n context: {\n mode: 'marathon',\n phase,\n attempts,\n reason: failure.reason,\n host_error: failure.hostError,\n },\n });\n}\n\n/**\n * Listens for auth tokens AND environment config from the host environment.\n *\n * Two transport modes, picked automatically:\n *\n * 1. **Iframe (legacy / Olympus inline preview)**: a parent window posts\n * `athena-config` and `athena-auth` via PostMessage. The hook also posts\n * `athena-auth-ready` so the parent knows when to (re-)send. Only the\n * window that frames the app (`event.source === window.parent`) on a\n * trusted origin ({@link isTrustedParentOrigin}) is heard, and an\n * `athena-config` URL is taken only once and only when it passes\n * {@link refuseBridgeUrl}.\n *\n * 2. **Standalone (Marathon-served deploy URL)**: top-level navigation, no\n * parent. The hook fetches `/_athena/auth` on its own origin. Marathon\n * validates its session cookie (sent by the browser automatically) and\n * returns `{ token, statewireSyncUrl, backendBaseUrl, appUrl }` — the\n * statewire mount, the bare Agora base and the app origin. A 404 means\n * we're not on a Marathon origin — the hook falls through with empty\n * state. Any other failure fails closed: one bounded retry cycle\n * ({@link MintRetryPolicy}) for a transient failure, none for a refusal,\n * and then `error` is set and `ready` flips with no token — never an\n * empty or anonymous one.\n *\n * In both modes the app never blocks indefinitely: the iframe bridge flips\n * `ready: true` after a timeout, and a Marathon mint cycle ends after at most\n * `maxAttempts` requests.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\n signBackInOnSessionEnd = false,\n mintRetryPolicy = DEFAULT_MINT_RETRY_POLICY,\n}: ParentBridgeOptions = {}): ParentBridge {\n const isInIframe = typeof window !== 'undefined' && window.parent !== window;\n const runtimeTrustedOrigins = useMemo(() => {\n const origins = new Set<string>();\n\n if (typeof window !== 'undefined') {\n origins.add(window.location.origin);\n }\n\n for (const trustedOrigin of trustedOrigins) {\n const normalizedOrigin = normalizeOrigin(trustedOrigin);\n if (normalizedOrigin) {\n origins.add(normalizedOrigin);\n }\n }\n\n return [...origins];\n }, [trustedOrigins]);\n\n const [state, setState] = useState<ParentBridgeState>({\n token: null,\n ...EMPTY_BRIDGE_URLS,\n hostAnswered: false,\n ready: false,\n error: null,\n });\n // Read when a mint cycle starts, so a caller's inline policy object does not\n // restart the bridge on every render.\n const mintRetryPolicyRef = useRef(mintRetryPolicy);\n mintRetryPolicyRef.current = mintRetryPolicy;\n // Read when a renewal is refused, so the provider can flip it without\n // restarting the bridge.\n const signBackInRef = useRef(signBackInOnSessionEnd);\n signBackInRef.current = signBackInOnSessionEnd;\n const readySignalSent = useRef(false);\n const configReceived = useRef(false);\n // The URLs the parent has set so far: a later `athena-config` may not change them.\n const parentUrlsRef = useRef<ParentBridgeUrls>(EMPTY_BRIDGE_URLS);\n const tokenRef = useRef<string | null>(state.token);\n tokenRef.current = state.token;\n // Installed by whichever transport mode is active (iframe or Marathon).\n const renewerRef = useRef<AthenaTokenRenewer | null>(null);\n\n useEffect(() => {\n if (!isInIframe) return;\n\n const endBridgeSpan = athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', {\n mode: 'iframe',\n });\n let spanEnded = false;\n const endOnce: typeof endBridgeSpan = (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return endBridgeSpan(name, options);\n };\n\n // Renewals waiting for the parent's next `athena-auth`.\n const tokenWaiters = new Set<(token: string | null) => void>();\n\n const handler = (event: MessageEvent) => {\n // Only the window that frames this app speaks for the host: a popup,\n // an opener or a sibling frame on a trusted origin does not.\n if (event.source !== window.parent) return;\n if (\n !isTrustedParentOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })\n ) {\n return;\n }\n if (!event.data || typeof event.data !== 'object') return;\n\n if (event.data.type === 'athena-config') {\n const { urls, rejected } = acceptBridgeConfigMessage({\n previous: parentUrlsRef.current,\n payload: event.data,\n parentOrigin: event.origin,\n locked: configReceived.current,\n });\n configReceived.current = true;\n parentUrlsRef.current = urls;\n for (const { field, reason } of rejected) {\n athenaDiagnostics.emit('auth.bridge.config_rejected', {\n level: 'warn',\n data: { mode: 'iframe', field, reason },\n });\n }\n setState((prev) => ({\n ...prev,\n ...urls,\n hostAnswered: true,\n // Don't set ready here — wait for athena-auth (token) or the timeout.\n // Setting ready on config alone causes a race: the thread list fires\n // before the token arrives, falling back to X-API-KEY.\n }));\n }\n\n if (event.data.type === 'athena-auth' && typeof event.data.token === 'string') {\n endOnce('auth.bridge.ready', { data: { mode: 'iframe' } });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'iframe-postmessage' },\n });\n setState((prev) => ({\n ...prev,\n token: event.data.token,\n ready: true,\n }));\n tokenRef.current = event.data.token;\n for (const resolve of [...tokenWaiters]) resolve(event.data.token);\n }\n };\n window.addEventListener('message', handler);\n\n // A renewal asks the parent to send its current token again — the same\n // `athena-auth-ready` handshake a parent already answers — and settles on\n // the next `athena-auth`, or on the held token when none arrives in time.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return new Promise<string | null>((resolve) => {\n const settle = (token: string | null) => {\n clearTimeout(timer);\n tokenWaiters.delete(settle);\n resolve(token ?? tokenRef.current);\n };\n const timer = setTimeout(() => settle(null), PARENT_TOKEN_RENEWAL_TIMEOUT_MS);\n tokenWaiters.add(settle);\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n });\n };\n\n if (!readySignalSent.current) {\n window.parent.postMessage({ type: 'athena-auth-ready' }, '*');\n readySignalSent.current = true;\n }\n\n const timer = setTimeout(() => {\n endOnce('auth.bridge.timeout', {\n data: {\n mode: 'iframe',\n timeout_ms: BRIDGE_TIMEOUT_MS,\n config_received: configReceived.current,\n },\n });\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n }, BRIDGE_TIMEOUT_MS);\n\n return () => {\n window.removeEventListener('message', handler);\n clearTimeout(timer);\n renewerRef.current = null;\n for (const resolve of [...tokenWaiters]) resolve(null);\n };\n }, [isInIframe, runtimeTrustedOrigins]);\n\n useEffect(() => {\n if (isInIframe) return;\n if (typeof window === 'undefined') return;\n\n let cancelled = false;\n // Whether a mint succeeded on this page: only then does a refused renewal\n // mean a session ended under it.\n let minted = false;\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n let inFlight: Promise<string | null> | null = null;\n let retryWait: { timer: ReturnType<typeof setTimeout>; resolve: () => void } | null = null;\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const endRetryWait = () => {\n if (!retryWait) return;\n clearTimeout(retryWait.timer);\n const { resolve } = retryWait;\n retryWait = null;\n resolve();\n };\n\n /** The backoff between two attempts of one cycle; unmounting ends it at once. */\n const waitBeforeRetry = (delayMs: number) =>\n new Promise<void>((resolve) => {\n retryWait = { timer: setTimeout(endRetryWait, delayMs), resolve };\n });\n\n // The only background mint: once per token, shortly before it expires. It\n // is not rescheduled when its cycle gives up. A forced renewal that gives\n // up leaves it in place on purpose: the held token is still live, and\n // this one bounded cycle keeps a viewer connected past a brief outage.\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n refreshTimer = null;\n void fetchAuth({ initial: false });\n }, delay);\n };\n\n /** One `/_athena/auth` request, classified. */\n const mintOnce = async (timeoutMs: number): Promise<MintAttempt> => {\n const requestController = new AbortController();\n controller = requestController;\n let timedOut = false;\n const requestTimer = setTimeout(() => {\n timedOut = true;\n requestController.abort();\n }, timeoutMs);\n\n try {\n const resp = await fetch('/_athena/auth', {\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: requestController.signal,\n });\n if (cancelled) return { kind: 'cancelled' };\n // Not a Marathon origin: expected for standalone apps and local dev.\n if (resp.status === 404) return { kind: 'absent', status: resp.status };\n if (!resp.ok) {\n const hostError = await readHostError(resp);\n if (cancelled) return { kind: 'cancelled' };\n // `readHostError` swallows the abort of a stalled error body. Only a\n // retryable status becomes a timeout: a refusal (401, 403, other\n // 4xx) is known from its status and still fails after one request.\n if (timedOut && isRetryableMintStatus(resp.status)) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null };\n }\n return isRetryableMintStatus(resp.status)\n ? { kind: 'transient', status: resp.status, reason: 'status', hostError }\n : { kind: 'refused', status: resp.status, reason: 'status', hostError };\n }\n\n let data: unknown;\n try {\n data = await resp.json();\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n if (cancelled) return { kind: 'cancelled' };\n // Marathon's 200 always carries a token. A 2xx without one is a\n // broken mint: it must not replace a held token or count as success.\n const token = data && typeof data === 'object' ? (data as { token?: unknown }).token : null;\n if (typeof token !== 'string' || token === '') {\n return { kind: 'refused', status: resp.status, reason: 'invalid_body', hostError: null };\n }\n return { kind: 'minted', data: data as AthenaAuthResponse };\n } catch (error) {\n if (cancelled) return { kind: 'cancelled' };\n if (timedOut) {\n return { kind: 'transient', status: null, reason: 'timeout', hostError: null, cause: error };\n }\n return { kind: 'transient', status: null, reason: 'network', hostError: null, cause: error };\n } finally {\n clearTimeout(requestTimer);\n if (controller === requestController) controller = null;\n }\n };\n\n /**\n * One bounded mint cycle: the initial acquisition (a span the provider\n * waits on) or a renewal (the scheduled refresh, a forced renewal). It\n * retries a transient failure up to `maxAttempts` requests in all, with\n * a capped, jittered backoff. On a refusal, or after the last attempt, it\n * gives up: it reports one typed error, sets `error`, keeps any token an\n * earlier mint handed out, and schedules nothing.\n */\n const runMintCycle = async ({ initial }: { initial: boolean }): Promise<string | null> => {\n const policy = mintRetryPolicyRef.current;\n const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));\n const phase = initial ? 'initial' : 'renewal';\n const endBridgeSpan = initial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n\n let attempts = 0;\n let lastFailure: MintFailure | null = null;\n while (attempts < maxAttempts) {\n attempts += 1;\n const outcome = await mintOnce(policy.timeoutMs);\n if (cancelled || outcome.kind === 'cancelled') return null;\n\n if (outcome.kind === 'absent') {\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: outcome.status },\n });\n // 404 means no bridge here, so an earlier mint failure no longer\n // applies; the provider falls back to its other credentials.\n setState((prev) =>\n prev.ready && prev.error == null ? prev : { ...prev, ready: true, error: null },\n );\n return null;\n }\n\n if (outcome.kind === 'minted') {\n const { data } = outcome;\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string', attempts },\n });\n athenaDiagnostics.emit('auth.token.acquired', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at },\n });\n } else {\n athenaDiagnostics.emit('auth.token.refreshed', {\n level: 'debug',\n data: { source: 'marathon', expires_at: data.expires_at, attempts },\n });\n }\n const token = typeof data.token === 'string' ? data.token : null;\n minted = true;\n setState({\n token,\n ...readAuthMintUrls(data),\n hostAnswered: true,\n ready: true,\n error: null,\n });\n tokenRef.current = token;\n scheduleRefresh(data.expires_at);\n return token;\n }\n\n lastFailure = outcome;\n if (outcome.kind === 'refused' || attempts >= maxAttempts) break;\n\n const delayMs = getMintRetryDelay({ retry: attempts, policy });\n athenaDiagnostics.emit('auth.bridge.retry', {\n level: 'warn',\n data: {\n mode: 'marathon',\n phase,\n status: outcome.status,\n reason: outcome.reason,\n host_error: outcome.hostError,\n attempt: attempts,\n max_attempts: maxAttempts,\n delay_ms: delayMs,\n },\n });\n await waitBeforeRetry(delayMs);\n if (cancelled) return null;\n }\n\n if (!lastFailure) return null;\n const error = mintFailureError({ failure: lastFailure, attempts, phase });\n const failureData = {\n mode: 'marathon',\n phase,\n status: lastFailure.status,\n reason: lastFailure.reason,\n attempts,\n };\n if (endBridgeSpan) {\n endBridgeSpan(\n error.code === 'auth_bridge_timeout' ? 'auth.bridge.timeout' : 'auth.bridge.error',\n { data: failureData, error },\n );\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', {\n level: 'error',\n data: failureData,\n error,\n });\n }\n setState((prev) => ({ ...prev, error, ready: true }));\n // A renewal refused because the session ended (401; a 403 is an access\n // refusal that signing in again cannot change). The held token keeps the\n // app on screen while the page leaves, so it keeps what it needs first.\n if (\n !initial &&\n minted &&\n signBackInRef.current &&\n lastFailure.kind === 'refused' &&\n lastFailure.status === 401\n ) {\n signBackIn({ loginUrl: MARATHON_SIGN_IN_PATH, source: 'bridge', status: lastFailure.status });\n }\n return null;\n };\n\n // One mint cycle at a time. The initial acquisition, a scheduled refresh\n // and forced renewals that overlap share the cycle in flight (its\n // requests, their timeouts and its backoff) instead of starting their own,\n // so concurrent callers never multiply the requests.\n const fetchAuth = (options: { initial: boolean }): Promise<string | null> => {\n inFlight ??= runMintCycle(options).finally(() => {\n inFlight = null;\n });\n return inFlight;\n };\n\n // A forced renewal mints now instead of waiting for the scheduled refresh\n // (joining a cycle already in flight), and may start a new bounded cycle\n // after one gave up. The schedule restarts from the new token's expiry.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return fetchAuth({ initial: false }).then((token) => token ?? tokenRef.current);\n };\n\n void fetchAuth({ initial: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRefreshTimer();\n endRetryWait();\n renewerRef.current = null;\n };\n }, [isInIframe]);\n\n const renewToken = useCallback<AthenaTokenRenewer>(\n (options) => renewerRef.current?.(options) ?? Promise.resolve(tokenRef.current),\n [],\n );\n\n return useMemo(() => ({ ...state, renewToken }), [state, renewToken]);\n}\n\n/**\n * Legacy hook — returns just the auth token from the parent bridge.\n * Prefer useParentBridge() for new code.\n */\nexport function useParentAuth(): string | null {\n return useParentBridge().token;\n}\n"],"names":["timer"],"mappings":"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"}