@athenaintel/react 0.27.0 → 0.28.0
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.
- package/README.md +17 -1
- package/dist/chat/StatewireApprovalCard.cjs +19 -0
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.js +19 -0
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/message-time.cjs.map +1 -1
- package/dist/chat/message-time.d.ts +4 -3
- package/dist/chat/message-time.js.map +1 -1
- package/dist/composer/QuoteContext.cjs +13 -1
- package/dist/composer/QuoteContext.cjs.map +1 -1
- package/dist/composer/QuoteContext.d.ts +11 -1
- package/dist/composer/QuoteContext.js +13 -1
- package/dist/composer/QuoteContext.js.map +1 -1
- package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
- package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
- package/dist/diagnostics/athena-diagnostics.js.map +1 -1
- package/dist/lib/athena-urls.cjs +1 -1
- package/dist/lib/athena-urls.cjs.map +1 -1
- package/dist/lib/athena-urls.js +1 -1
- package/dist/lib/athena-urls.js.map +1 -1
- package/dist/lib/posthog/mask-sensitive-input.cjs +34 -0
- package/dist/lib/posthog/mask-sensitive-input.cjs.map +1 -0
- package/dist/lib/posthog/mask-sensitive-input.d.ts +9 -0
- package/dist/lib/posthog/mask-sensitive-input.js +34 -0
- package/dist/lib/posthog/mask-sensitive-input.js.map +1 -0
- package/dist/packages/agent-runtime/src/conversation-index.cjs +2 -1
- package/dist/packages/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-index.js +2 -1
- package/dist/packages/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +2 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +2 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.cjs +26 -8
- package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.js +26 -8
- package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/packages/agent-runtime/src/messages.cjs +70 -14
- package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/messages.js +71 -15
- package/dist/packages/agent-runtime/src/messages.js.map +1 -1
- package/dist/packages/agent-runtime/src/tool-metadata.cjs +75 -0
- package/dist/packages/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/tool-metadata.js +75 -0
- package/dist/packages/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/packages/agent-runtime/src/tool-result.cjs +19 -4
- package/dist/packages/agent-runtime/src/tool-result.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/tool-result.js +19 -4
- package/dist/packages/agent-runtime/src/tool-result.js.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs +23 -4
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.js +24 -5
- package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/PostHogProvider.cjs +3 -1
- package/dist/provider/PostHogProvider.cjs.map +1 -1
- package/dist/provider/PostHogProvider.js +3 -1
- package/dist/provider/PostHogProvider.js.map +1 -1
- package/dist/runtime/auth.cjs +88 -4
- package/dist/runtime/auth.cjs.map +1 -1
- package/dist/runtime/auth.d.ts +72 -5
- package/dist/runtime/auth.js +88 -4
- package/dist/runtime/auth.js.map +1 -1
- package/dist/styles.css +1 -1
- 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 * Check whether a PostMessage origin is trusted.\n * Accepts:\n * - explicit trusted origins passed by the host app\n * - the current iframe origin (for same-origin wrappers like Marathon)\n * - any *.athenaintel.com subdomain and localhost (legacy/default behavior)\n */\nfunction isTrustedOrigin({\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 return (\n hostname === 'athenaintel.com' ||\n hostname.endsWith('.athenaintel.com') ||\n hostname === 'localhost'\n );\n } catch {\n return false;\n }\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 an `athena-config` postMessage payload over the current URL set. A\n * field is taken only when it is a string; anything else keeps the previous\n * value. Keys outside {@link BridgeUrlField} (a legacy parent's `apiUrl` /\n * `backendUrl` among 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/**\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.\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 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 if (!isTrustedOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })) {\n return;\n }\n if (!event.data || typeof event.data !== 'object') return;\n\n if (event.data.type === 'athena-config') {\n configReceived.current = true;\n setState((prev) => ({\n ...prev,\n ...mergeBridgeConfigMessage(prev, event.data),\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 { 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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"}
|
package/dist/runtime/auth.d.ts
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
import type { AthenaTokenRenewer } from '../auth/credential-source';
|
|
2
2
|
import { AthenaSdkError } from '../diagnostics/errors';
|
|
3
|
+
/** Whether `hostname` serves customer apps (see {@link CUSTOMER_APP_HOST}). */
|
|
4
|
+
export declare function isCustomerAppHost(hostname: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Check whether a PostMessage origin is trusted as the parent.
|
|
7
|
+
* Accepts:
|
|
8
|
+
* - explicit trusted origins passed by the host app
|
|
9
|
+
* - the current iframe origin (for same-origin wrappers like Marathon)
|
|
10
|
+
* - Athena's own hosts under athenaintel.com and localhost (default
|
|
11
|
+
* behavior), except the customer-app hosts Marathon serves
|
|
12
|
+
*/
|
|
13
|
+
export declare function isTrustedParentOrigin({ origin, trustedOrigins, }: {
|
|
14
|
+
origin: string;
|
|
15
|
+
trustedOrigins: string[];
|
|
16
|
+
}): boolean;
|
|
17
|
+
/** Why {@link isAcceptableBridgeUrl} refused a URL. */
|
|
18
|
+
export type BridgeUrlRefusal = 'invalid' | 'insecure' | 'customer_app_host' | 'other_domain';
|
|
19
|
+
/**
|
|
20
|
+
* Whether a URL a trusted parent sent in `athena-config` may become the
|
|
21
|
+
* SDK's backend, statewire mount or app URL. The SDK sends its bearer token
|
|
22
|
+
* to the first two, so a URL is taken only when it is `https:` (plain
|
|
23
|
+
* `http:` only between loopback hosts, for local development), carries no
|
|
24
|
+
* credentials, is not a customer-code host ({@link CUSTOMER_APP_HOST}), and
|
|
25
|
+
* is one of Athena's own hosts or, for a parent outside `athenaintel.com`,
|
|
26
|
+
* the parent's own host or a subdomain of it. Sibling hosts are not taken:
|
|
27
|
+
* without a public-suffix list `portal.example.co.uk` and `tenant.github.io`
|
|
28
|
+
* cannot be told apart from a private domain, so a wrapper whose backend is
|
|
29
|
+
* elsewhere passes its URLs through `config`. Returns the refusal, or `null`
|
|
30
|
+
* when the URL is acceptable.
|
|
31
|
+
*/
|
|
32
|
+
export declare function refuseBridgeUrl({ url, parentOrigin, }: {
|
|
33
|
+
url: string;
|
|
34
|
+
parentOrigin: string;
|
|
35
|
+
}): BridgeUrlRefusal | null;
|
|
36
|
+
/** {@link refuseBridgeUrl} as a predicate. */
|
|
37
|
+
export declare function isAcceptableBridgeUrl(options: {
|
|
38
|
+
url: string;
|
|
39
|
+
parentOrigin: string;
|
|
40
|
+
}): boolean;
|
|
3
41
|
export interface ParentBridgeState {
|
|
4
42
|
/** PropelAuth access token from the parent window. */
|
|
5
43
|
token: string | null;
|
|
@@ -54,12 +92,37 @@ type BridgeUrlField = (typeof BRIDGE_URL_FIELDS)[number];
|
|
|
54
92
|
/** The URL half of {@link ParentBridgeState} — what a host can hand the SDK. */
|
|
55
93
|
export type ParentBridgeUrls = Pick<ParentBridgeState, BridgeUrlField>;
|
|
56
94
|
/**
|
|
57
|
-
* Fold
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
95
|
+
* Fold a URL payload over the current URL set without vetting it (the
|
|
96
|
+
* same-origin `/_athena/auth` mint body; a parent's `athena-config` goes
|
|
97
|
+
* through {@link acceptBridgeConfigMessage}). A field is taken only when it is
|
|
98
|
+
* a string; anything else keeps the previous value. Keys outside
|
|
99
|
+
* {@link BridgeUrlField} (a legacy parent's `apiUrl` / `backendUrl` among
|
|
100
|
+
* them) are dropped.
|
|
61
101
|
*/
|
|
62
102
|
export declare function mergeBridgeConfigMessage(previous: ParentBridgeUrls, payload: Partial<Record<BridgeUrlField, unknown>>): ParentBridgeUrls;
|
|
103
|
+
/** A URL field of an `athena-config` message that was not taken, and why. */
|
|
104
|
+
export interface RejectedBridgeUrl {
|
|
105
|
+
field: BridgeUrlField;
|
|
106
|
+
reason: BridgeUrlRefusal | 'changed';
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Fold an `athena-config` message from a trusted parent over the current URL
|
|
110
|
+
* set, taking only what is safe to point the SDK's bearer at: each string
|
|
111
|
+
* field must pass {@link refuseBridgeUrl} against the parent's origin. The
|
|
112
|
+
* first config message sets the URLs; once `locked` (a config message was
|
|
113
|
+
* already taken) a value that differs from the held one, including a field
|
|
114
|
+
* the first message left unset, is refused (`changed`), so a later message
|
|
115
|
+
* cannot repoint a running SDK. Resending the same value is fine.
|
|
116
|
+
*/
|
|
117
|
+
export declare function acceptBridgeConfigMessage({ previous, payload, parentOrigin, locked, }: {
|
|
118
|
+
previous: ParentBridgeUrls;
|
|
119
|
+
payload: Partial<Record<BridgeUrlField, unknown>>;
|
|
120
|
+
parentOrigin: string;
|
|
121
|
+
locked?: boolean;
|
|
122
|
+
}): {
|
|
123
|
+
urls: ParentBridgeUrls;
|
|
124
|
+
rejected: RejectedBridgeUrl[];
|
|
125
|
+
};
|
|
63
126
|
/**
|
|
64
127
|
* Read the URL set out of a `/_athena/auth` mint body. Non-string fields are
|
|
65
128
|
* `null`.
|
|
@@ -137,7 +200,11 @@ export declare function mintFailureError({ failure, attempts, phase, }: {
|
|
|
137
200
|
*
|
|
138
201
|
* 1. **Iframe (legacy / Olympus inline preview)**: a parent window posts
|
|
139
202
|
* `athena-config` and `athena-auth` via PostMessage. The hook also posts
|
|
140
|
-
* `athena-auth-ready` so the parent knows when to (re-)send.
|
|
203
|
+
* `athena-auth-ready` so the parent knows when to (re-)send. Only the
|
|
204
|
+
* window that frames the app (`event.source === window.parent`) on a
|
|
205
|
+
* trusted origin ({@link isTrustedParentOrigin}) is heard, and an
|
|
206
|
+
* `athena-config` URL is taken only once and only when it passes
|
|
207
|
+
* {@link refuseBridgeUrl}.
|
|
141
208
|
*
|
|
142
209
|
* 2. **Standalone (Marathon-served deploy URL)**: top-level navigation, no
|
|
143
210
|
* parent. The hook fetches `/_athena/auth` on its own origin. Marathon
|
package/dist/runtime/auth.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { useMemo, useState, useRef, useEffect, useCallback } from "react";
|
|
2
2
|
import { athenaDiagnostics } from "../diagnostics/athena-diagnostics.js";
|
|
3
3
|
import { AthenaSdkError } from "../diagnostics/errors.js";
|
|
4
|
-
|
|
4
|
+
const CUSTOMER_APP_HOST = /^.+\.(?:(?:[a-z0-9-]+-)?app(?:\.[a-z0-9-]+)*|computer(?:-[a-z0-9-]+)?\.stg|code-sandpack(?:\.[a-z0-9-]+)*)\.athenaintel\.com$/i;
|
|
5
|
+
function isCustomerAppHost(hostname) {
|
|
6
|
+
return CUSTOMER_APP_HOST.test(hostname);
|
|
7
|
+
}
|
|
8
|
+
function isLoopbackHost(hostname) {
|
|
9
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
10
|
+
}
|
|
11
|
+
function isAthenaHost(hostname) {
|
|
12
|
+
return hostname === "athenaintel.com" || hostname.endsWith(".athenaintel.com");
|
|
13
|
+
}
|
|
14
|
+
function isTrustedParentOrigin({
|
|
5
15
|
origin,
|
|
6
16
|
trustedOrigins
|
|
7
17
|
}) {
|
|
@@ -11,11 +21,37 @@ function isTrustedOrigin({
|
|
|
11
21
|
return true;
|
|
12
22
|
}
|
|
13
23
|
const { hostname } = new URL(normalizedOrigin);
|
|
14
|
-
|
|
24
|
+
if (isCustomerAppHost(hostname)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return isAthenaHost(hostname) || hostname === "localhost";
|
|
15
28
|
} catch {
|
|
16
29
|
return false;
|
|
17
30
|
}
|
|
18
31
|
}
|
|
32
|
+
function refuseBridgeUrl({
|
|
33
|
+
url,
|
|
34
|
+
parentOrigin
|
|
35
|
+
}) {
|
|
36
|
+
let parsed;
|
|
37
|
+
let parentHostname;
|
|
38
|
+
try {
|
|
39
|
+
parsed = new URL(url);
|
|
40
|
+
parentHostname = new URL(parentOrigin).hostname;
|
|
41
|
+
} catch {
|
|
42
|
+
return "invalid";
|
|
43
|
+
}
|
|
44
|
+
if (parsed.username || parsed.password) return "invalid";
|
|
45
|
+
const { hostname } = parsed;
|
|
46
|
+
const loopbackPair = isLoopbackHost(hostname) && isLoopbackHost(parentHostname);
|
|
47
|
+
if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopbackPair)) {
|
|
48
|
+
return "insecure";
|
|
49
|
+
}
|
|
50
|
+
if (isCustomerAppHost(hostname)) return "customer_app_host";
|
|
51
|
+
if (loopbackPair || isAthenaHost(hostname)) return null;
|
|
52
|
+
if (isAthenaHost(parentHostname) || isLoopbackHost(parentHostname)) return "other_domain";
|
|
53
|
+
return hostname === parentHostname || hostname.endsWith(`.${parentHostname}`) ? null : "other_domain";
|
|
54
|
+
}
|
|
19
55
|
const BRIDGE_URL_FIELDS = ["statewireSyncUrl", "backendBaseUrl", "appUrl"];
|
|
20
56
|
const EMPTY_BRIDGE_URLS = {
|
|
21
57
|
statewireSyncUrl: null,
|
|
@@ -36,6 +72,35 @@ function mergeBridgeConfigMessage(previous, payload) {
|
|
|
36
72
|
}
|
|
37
73
|
return next;
|
|
38
74
|
}
|
|
75
|
+
function acceptBridgeConfigMessage({
|
|
76
|
+
previous,
|
|
77
|
+
payload,
|
|
78
|
+
parentOrigin,
|
|
79
|
+
locked = false
|
|
80
|
+
}) {
|
|
81
|
+
const urls = {
|
|
82
|
+
statewireSyncUrl: previous.statewireSyncUrl,
|
|
83
|
+
backendBaseUrl: previous.backendBaseUrl,
|
|
84
|
+
appUrl: previous.appUrl
|
|
85
|
+
};
|
|
86
|
+
const rejected = [];
|
|
87
|
+
for (const field of BRIDGE_URL_FIELDS) {
|
|
88
|
+
const value = payload[field];
|
|
89
|
+
if (typeof value !== "string") continue;
|
|
90
|
+
const held = previous[field];
|
|
91
|
+
if (locked || held !== null) {
|
|
92
|
+
if (held !== value) rejected.push({ field, reason: "changed" });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const refusal = refuseBridgeUrl({ url: value, parentOrigin });
|
|
96
|
+
if (refusal !== null) {
|
|
97
|
+
rejected.push({ field, reason: refusal });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
urls[field] = value;
|
|
101
|
+
}
|
|
102
|
+
return { urls, rejected };
|
|
103
|
+
}
|
|
39
104
|
function readAuthMintUrls(data) {
|
|
40
105
|
return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);
|
|
41
106
|
}
|
|
@@ -161,6 +226,7 @@ function useParentBridge({
|
|
|
161
226
|
mintRetryPolicyRef.current = mintRetryPolicy;
|
|
162
227
|
const readySignalSent = useRef(false);
|
|
163
228
|
const configReceived = useRef(false);
|
|
229
|
+
const parentUrlsRef = useRef(EMPTY_BRIDGE_URLS);
|
|
164
230
|
const tokenRef = useRef(state.token);
|
|
165
231
|
tokenRef.current = state.token;
|
|
166
232
|
const renewerRef = useRef(null);
|
|
@@ -177,15 +243,29 @@ function useParentBridge({
|
|
|
177
243
|
};
|
|
178
244
|
const tokenWaiters = /* @__PURE__ */ new Set();
|
|
179
245
|
const handler = (event) => {
|
|
180
|
-
if (
|
|
246
|
+
if (event.source !== window.parent) return;
|
|
247
|
+
if (!isTrustedParentOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })) {
|
|
181
248
|
return;
|
|
182
249
|
}
|
|
183
250
|
if (!event.data || typeof event.data !== "object") return;
|
|
184
251
|
if (event.data.type === "athena-config") {
|
|
252
|
+
const { urls, rejected } = acceptBridgeConfigMessage({
|
|
253
|
+
previous: parentUrlsRef.current,
|
|
254
|
+
payload: event.data,
|
|
255
|
+
parentOrigin: event.origin,
|
|
256
|
+
locked: configReceived.current
|
|
257
|
+
});
|
|
185
258
|
configReceived.current = true;
|
|
259
|
+
parentUrlsRef.current = urls;
|
|
260
|
+
for (const { field, reason } of rejected) {
|
|
261
|
+
athenaDiagnostics.emit("auth.bridge.config_rejected", {
|
|
262
|
+
level: "warn",
|
|
263
|
+
data: { mode: "iframe", field, reason }
|
|
264
|
+
});
|
|
265
|
+
}
|
|
186
266
|
setState((prev) => ({
|
|
187
267
|
...prev,
|
|
188
|
-
...
|
|
268
|
+
...urls,
|
|
189
269
|
hostAnswered: true
|
|
190
270
|
// Don't set ready here — wait for athena-auth (token) or the timeout.
|
|
191
271
|
// Setting ready on config alone causes a race: the thread list fires
|
|
@@ -449,12 +529,16 @@ function useParentAuth() {
|
|
|
449
529
|
}
|
|
450
530
|
export {
|
|
451
531
|
DEFAULT_MINT_RETRY_POLICY,
|
|
532
|
+
acceptBridgeConfigMessage,
|
|
452
533
|
getMintRetryDelay,
|
|
453
534
|
getTokenRefreshDelay,
|
|
535
|
+
isCustomerAppHost,
|
|
454
536
|
isRetryableMintStatus,
|
|
537
|
+
isTrustedParentOrigin,
|
|
455
538
|
mergeBridgeConfigMessage,
|
|
456
539
|
mintFailureError,
|
|
457
540
|
readAuthMintUrls,
|
|
541
|
+
refuseBridgeUrl,
|
|
458
542
|
useParentAuth,
|
|
459
543
|
useParentBridge
|
|
460
544
|
};
|