@athenaintel/react 0.26.6 → 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.
Files changed (75) hide show
  1. package/README.md +32 -1
  2. package/dist/chat/StatewireApprovalCard.cjs +19 -0
  3. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  4. package/dist/chat/StatewireApprovalCard.js +19 -0
  5. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  6. package/dist/chat/message-time.cjs.map +1 -1
  7. package/dist/chat/message-time.d.ts +4 -3
  8. package/dist/chat/message-time.js.map +1 -1
  9. package/dist/composer/QuoteContext.cjs +13 -1
  10. package/dist/composer/QuoteContext.cjs.map +1 -1
  11. package/dist/composer/QuoteContext.d.ts +11 -1
  12. package/dist/composer/QuoteContext.js +13 -1
  13. package/dist/composer/QuoteContext.js.map +1 -1
  14. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  15. package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
  16. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  17. package/dist/diagnostics/errors.cjs +11 -2
  18. package/dist/diagnostics/errors.cjs.map +1 -1
  19. package/dist/diagnostics/errors.d.ts +11 -2
  20. package/dist/diagnostics/errors.js +11 -2
  21. package/dist/diagnostics/errors.js.map +1 -1
  22. package/dist/lib/athena-urls.cjs +1 -1
  23. package/dist/lib/athena-urls.cjs.map +1 -1
  24. package/dist/lib/athena-urls.js +1 -1
  25. package/dist/lib/athena-urls.js.map +1 -1
  26. package/dist/lib/posthog/mask-sensitive-input.cjs +34 -0
  27. package/dist/lib/posthog/mask-sensitive-input.cjs.map +1 -0
  28. package/dist/lib/posthog/mask-sensitive-input.d.ts +9 -0
  29. package/dist/lib/posthog/mask-sensitive-input.js +34 -0
  30. package/dist/lib/posthog/mask-sensitive-input.js.map +1 -0
  31. package/dist/packages/agent-runtime/src/conversation-index.cjs +2 -1
  32. package/dist/packages/agent-runtime/src/conversation-index.cjs.map +1 -1
  33. package/dist/packages/agent-runtime/src/conversation-index.js +2 -1
  34. package/dist/packages/agent-runtime/src/conversation-index.js.map +1 -1
  35. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +2 -0
  36. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  37. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +2 -0
  38. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  39. package/dist/packages/agent-runtime/src/interrupts.cjs +26 -8
  40. package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
  41. package/dist/packages/agent-runtime/src/interrupts.js +26 -8
  42. package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
  43. package/dist/packages/agent-runtime/src/messages.cjs +70 -14
  44. package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
  45. package/dist/packages/agent-runtime/src/messages.js +71 -15
  46. package/dist/packages/agent-runtime/src/messages.js.map +1 -1
  47. package/dist/packages/agent-runtime/src/tool-metadata.cjs +75 -0
  48. package/dist/packages/agent-runtime/src/tool-metadata.cjs.map +1 -1
  49. package/dist/packages/agent-runtime/src/tool-metadata.js +75 -0
  50. package/dist/packages/agent-runtime/src/tool-metadata.js.map +1 -1
  51. package/dist/packages/agent-runtime/src/tool-result.cjs +19 -4
  52. package/dist/packages/agent-runtime/src/tool-result.cjs.map +1 -1
  53. package/dist/packages/agent-runtime/src/tool-result.js +19 -4
  54. package/dist/packages/agent-runtime/src/tool-result.js.map +1 -1
  55. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs +23 -4
  56. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  57. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js +24 -5
  58. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  59. package/dist/packages/react/package.json.cjs +1 -1
  60. package/dist/packages/react/package.json.js +1 -1
  61. package/dist/provider/AthenaProvider.cjs +3 -0
  62. package/dist/provider/AthenaProvider.cjs.map +1 -1
  63. package/dist/provider/AthenaProvider.js +3 -0
  64. package/dist/provider/AthenaProvider.js.map +1 -1
  65. package/dist/provider/PostHogProvider.cjs +3 -1
  66. package/dist/provider/PostHogProvider.cjs.map +1 -1
  67. package/dist/provider/PostHogProvider.js +3 -1
  68. package/dist/provider/PostHogProvider.js.map +1 -1
  69. package/dist/runtime/auth.cjs +307 -108
  70. package/dist/runtime/auth.cjs.map +1 -1
  71. package/dist/runtime/auth.d.ts +151 -10
  72. package/dist/runtime/auth.js +307 -108
  73. package/dist/runtime/auth.js.map +1 -1
  74. package/dist/styles.css +1 -1
  75. package/package.json +1 -1
@@ -2,7 +2,18 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const react = require("react");
4
4
  const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
5
- function isTrustedOrigin({
5
+ const errors = require("../diagnostics/errors.cjs");
6
+ const CUSTOMER_APP_HOST = /^.+\.(?:(?:[a-z0-9-]+-)?app(?:\.[a-z0-9-]+)*|computer(?:-[a-z0-9-]+)?\.stg|code-sandpack(?:\.[a-z0-9-]+)*)\.athenaintel\.com$/i;
7
+ function isCustomerAppHost(hostname) {
8
+ return CUSTOMER_APP_HOST.test(hostname);
9
+ }
10
+ function isLoopbackHost(hostname) {
11
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
12
+ }
13
+ function isAthenaHost(hostname) {
14
+ return hostname === "athenaintel.com" || hostname.endsWith(".athenaintel.com");
15
+ }
16
+ function isTrustedParentOrigin({
6
17
  origin,
7
18
  trustedOrigins
8
19
  }) {
@@ -12,11 +23,37 @@ function isTrustedOrigin({
12
23
  return true;
13
24
  }
14
25
  const { hostname } = new URL(normalizedOrigin);
15
- return hostname === "athenaintel.com" || hostname.endsWith(".athenaintel.com") || hostname === "localhost";
26
+ if (isCustomerAppHost(hostname)) {
27
+ return false;
28
+ }
29
+ return isAthenaHost(hostname) || hostname === "localhost";
16
30
  } catch {
17
31
  return false;
18
32
  }
19
33
  }
34
+ function refuseBridgeUrl({
35
+ url,
36
+ parentOrigin
37
+ }) {
38
+ let parsed;
39
+ let parentHostname;
40
+ try {
41
+ parsed = new URL(url);
42
+ parentHostname = new URL(parentOrigin).hostname;
43
+ } catch {
44
+ return "invalid";
45
+ }
46
+ if (parsed.username || parsed.password) return "invalid";
47
+ const { hostname } = parsed;
48
+ const loopbackPair = isLoopbackHost(hostname) && isLoopbackHost(parentHostname);
49
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopbackPair)) {
50
+ return "insecure";
51
+ }
52
+ if (isCustomerAppHost(hostname)) return "customer_app_host";
53
+ if (loopbackPair || isAthenaHost(hostname)) return null;
54
+ if (isAthenaHost(parentHostname) || isLoopbackHost(parentHostname)) return "other_domain";
55
+ return hostname === parentHostname || hostname.endsWith(`.${parentHostname}`) ? null : "other_domain";
56
+ }
20
57
  const BRIDGE_URL_FIELDS = ["statewireSyncUrl", "backendBaseUrl", "appUrl"];
21
58
  const EMPTY_BRIDGE_URLS = {
22
59
  statewireSyncUrl: null,
@@ -37,16 +74,51 @@ function mergeBridgeConfigMessage(previous, payload) {
37
74
  }
38
75
  return next;
39
76
  }
77
+ function acceptBridgeConfigMessage({
78
+ previous,
79
+ payload,
80
+ parentOrigin,
81
+ locked = false
82
+ }) {
83
+ const urls = {
84
+ statewireSyncUrl: previous.statewireSyncUrl,
85
+ backendBaseUrl: previous.backendBaseUrl,
86
+ appUrl: previous.appUrl
87
+ };
88
+ const rejected = [];
89
+ for (const field of BRIDGE_URL_FIELDS) {
90
+ const value = payload[field];
91
+ if (typeof value !== "string") continue;
92
+ const held = previous[field];
93
+ if (locked || held !== null) {
94
+ if (held !== value) rejected.push({ field, reason: "changed" });
95
+ continue;
96
+ }
97
+ const refusal = refuseBridgeUrl({ url: value, parentOrigin });
98
+ if (refusal !== null) {
99
+ rejected.push({ field, reason: refusal });
100
+ continue;
101
+ }
102
+ urls[field] = value;
103
+ }
104
+ return { urls, rejected };
105
+ }
40
106
  function readAuthMintUrls(data) {
41
107
  return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);
42
108
  }
43
109
  const BRIDGE_TIMEOUT_MS = 2e3;
44
110
  const PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5e3;
45
111
  const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
46
- const TOKEN_REFRESH_RETRY_BASE_MS = 60 * 1e3;
47
- const TOKEN_REFRESH_RETRY_MAX_MS = 5 * 60 * 1e3;
112
+ const TOKEN_REFRESH_MIN_DELAY_MS = 60 * 1e3;
48
113
  const DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1e3;
49
114
  const EMPTY_TRUSTED_ORIGINS = [];
115
+ const DEFAULT_MINT_RETRY_POLICY = {
116
+ maxAttempts: 4,
117
+ baseDelayMs: 500,
118
+ maxDelayMs: 4e3,
119
+ timeoutMs: BRIDGE_TIMEOUT_MS,
120
+ random: Math.random
121
+ };
50
122
  const normalizeOrigin = (value) => {
51
123
  try {
52
124
  return new URL(value).origin;
@@ -62,14 +134,74 @@ const getTokenRefreshDelay = (expiresAt, nowMs) => {
62
134
  if (!Number.isFinite(expiresAtMs)) {
63
135
  return DEFAULT_TOKEN_REFRESH_MS;
64
136
  }
65
- return Math.max(TOKEN_REFRESH_RETRY_BASE_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);
66
- };
67
- const getAuthRetryDelay = (retryAttempt) => {
68
- const safeAttempt = Number.isFinite(retryAttempt) ? Math.max(0, Math.floor(retryAttempt)) : 0;
69
- return Math.min(TOKEN_REFRESH_RETRY_MAX_MS, TOKEN_REFRESH_RETRY_BASE_MS * 2 ** safeAttempt);
137
+ return Math.max(TOKEN_REFRESH_MIN_DELAY_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);
70
138
  };
139
+ function getMintRetryDelay({
140
+ retry,
141
+ policy = DEFAULT_MINT_RETRY_POLICY
142
+ }) {
143
+ const safeRetry = Number.isFinite(retry) ? Math.max(1, Math.floor(retry)) : 1;
144
+ const ceiling = Math.min(policy.maxDelayMs, policy.baseDelayMs * 2 ** (safeRetry - 1));
145
+ const jitter = Math.min(1, Math.max(0, policy.random()));
146
+ return Math.round(ceiling / 2 + ceiling / 2 * jitter);
147
+ }
148
+ function isRetryableMintStatus(status) {
149
+ return status >= 500 || status === 408 || status === 429;
150
+ }
151
+ async function readHostError(resp) {
152
+ try {
153
+ const body = await resp.json();
154
+ if (body && typeof body === "object" && "error" in body) {
155
+ const value = body.error;
156
+ return typeof value === "string" ? value.slice(0, 100) : null;
157
+ }
158
+ } catch {
159
+ }
160
+ return null;
161
+ }
162
+ const MINT_RETRY_NOTE = "The SDK stopped retrying and does not connect without a credential. Remount the provider, or call renewToken({ force: true }), to try again.";
163
+ function mintFailureHint(failure) {
164
+ if (failure.kind === "transient") {
165
+ return `The host could not mint a viewer token. Check the Marathon and Agora logs. ${MINT_RETRY_NOTE}`;
166
+ }
167
+ if (failure.reason === "invalid_body") {
168
+ 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}`;
169
+ }
170
+ if (failure.status === 401) {
171
+ return `The host has no session for this viewer. Sign in again through the app URL. ${MINT_RETRY_NOTE}`;
172
+ }
173
+ if (failure.status === 403) {
174
+ return `The host does not give this viewer access to the app. ${MINT_RETRY_NOTE}`;
175
+ }
176
+ return `The host refused the mint request. ${MINT_RETRY_NOTE}`;
177
+ }
178
+ function mintFailureError({
179
+ failure,
180
+ attempts,
181
+ phase
182
+ }) {
183
+ const timedOut = failure.kind === "transient" && failure.reason === "timeout";
184
+ const answer = failure.status !== null ? `HTTP ${failure.status}` : timedOut ? "a timeout" : "a network error";
185
+ const message = failure.kind === "transient" ? `The host auth bridge (/_athena/auth) failed ${attempts} time${attempts === 1 ? "" : "s"} (last: ${answer}).` : failure.reason === "invalid_body" ? `The host auth bridge (/_athena/auth) answered ${answer} without a JSON mint body.` : `The host auth bridge (/_athena/auth) refused the mint (${answer}).`;
186
+ return new errors.AthenaSdkError({
187
+ code: timedOut ? "auth_bridge_timeout" : "auth_bridge_failed",
188
+ message,
189
+ hint: mintFailureHint(failure),
190
+ ...failure.status !== null && { status: failure.status },
191
+ ...failure.hostError !== null && { detail: { code: failure.hostError } },
192
+ ...failure.kind === "transient" && failure.cause !== void 0 && { cause: failure.cause },
193
+ context: {
194
+ mode: "marathon",
195
+ phase,
196
+ attempts,
197
+ reason: failure.reason,
198
+ host_error: failure.hostError
199
+ }
200
+ });
201
+ }
71
202
  function useParentBridge({
72
- trustedOrigins = EMPTY_TRUSTED_ORIGINS
203
+ trustedOrigins = EMPTY_TRUSTED_ORIGINS,
204
+ mintRetryPolicy = DEFAULT_MINT_RETRY_POLICY
73
205
  } = {}) {
74
206
  const isInIframe = typeof window !== "undefined" && window.parent !== window;
75
207
  const runtimeTrustedOrigins = react.useMemo(() => {
@@ -89,10 +221,14 @@ function useParentBridge({
89
221
  token: null,
90
222
  ...EMPTY_BRIDGE_URLS,
91
223
  hostAnswered: false,
92
- ready: false
224
+ ready: false,
225
+ error: null
93
226
  });
227
+ const mintRetryPolicyRef = react.useRef(mintRetryPolicy);
228
+ mintRetryPolicyRef.current = mintRetryPolicy;
94
229
  const readySignalSent = react.useRef(false);
95
230
  const configReceived = react.useRef(false);
231
+ const parentUrlsRef = react.useRef(EMPTY_BRIDGE_URLS);
96
232
  const tokenRef = react.useRef(state.token);
97
233
  tokenRef.current = state.token;
98
234
  const renewerRef = react.useRef(null);
@@ -109,15 +245,29 @@ function useParentBridge({
109
245
  };
110
246
  const tokenWaiters = /* @__PURE__ */ new Set();
111
247
  const handler = (event) => {
112
- if (!isTrustedOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })) {
248
+ if (event.source !== window.parent) return;
249
+ if (!isTrustedParentOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })) {
113
250
  return;
114
251
  }
115
252
  if (!event.data || typeof event.data !== "object") return;
116
253
  if (event.data.type === "athena-config") {
254
+ const { urls, rejected } = acceptBridgeConfigMessage({
255
+ previous: parentUrlsRef.current,
256
+ payload: event.data,
257
+ parentOrigin: event.origin,
258
+ locked: configReceived.current
259
+ });
117
260
  configReceived.current = true;
261
+ parentUrlsRef.current = urls;
262
+ for (const { field, reason } of rejected) {
263
+ athenaDiagnostics.athenaDiagnostics.emit("auth.bridge.config_rejected", {
264
+ level: "warn",
265
+ data: { mode: "iframe", field, reason }
266
+ });
267
+ }
118
268
  setState((prev) => ({
119
269
  ...prev,
120
- ...mergeBridgeConfigMessage(prev, event.data),
270
+ ...urls,
121
271
  hostAnswered: true
122
272
  // Don't set ready here — wait for athena-auth (token) or the timeout.
123
273
  // Setting ready on config alone causes a race: the thread list fires
@@ -181,147 +331,189 @@ function useParentBridge({
181
331
  let refreshTimer = null;
182
332
  let controller = null;
183
333
  let inFlight = null;
184
- let retryAttempt = 0;
334
+ let retryWait = null;
185
335
  const clearRefreshTimer = () => {
186
336
  if (refreshTimer) {
187
337
  clearTimeout(refreshTimer);
188
338
  refreshTimer = null;
189
339
  }
190
340
  };
191
- const markReady = () => {
192
- if (cancelled) return;
193
- setState((prev) => prev.ready ? prev : { ...prev, ready: true });
341
+ const endRetryWait = () => {
342
+ if (!retryWait) return;
343
+ clearTimeout(retryWait.timer);
344
+ const { resolve } = retryWait;
345
+ retryWait = null;
346
+ resolve();
194
347
  };
348
+ const waitBeforeRetry = (delayMs) => new Promise((resolve) => {
349
+ retryWait = { timer: setTimeout(endRetryWait, delayMs), resolve };
350
+ });
195
351
  const scheduleRefresh = (expiresAt) => {
196
352
  clearRefreshTimer();
197
- retryAttempt = 0;
198
353
  const delay = getTokenRefreshDelay(expiresAt, Date.now());
199
354
  refreshTimer = setTimeout(() => {
200
- void fetchAuth({ markReadyOnFailure: false });
201
- }, delay);
202
- };
203
- const scheduleRetry = () => {
204
- clearRefreshTimer();
205
- const delay = getAuthRetryDelay(retryAttempt);
206
- retryAttempt += 1;
207
- refreshTimer = setTimeout(() => {
208
- void fetchAuth({ markReadyOnFailure: false });
355
+ refreshTimer = null;
356
+ void fetchAuth({ initial: false });
209
357
  }, delay);
210
358
  };
211
- const mintAuth = async ({
212
- markReadyOnFailure
213
- }) => {
359
+ const mintOnce = async (timeoutMs) => {
214
360
  const requestController = new AbortController();
215
361
  controller = requestController;
216
- const isInitial = markReadyOnFailure;
217
- const rawEndSpan = isInitial ? athenaDiagnostics.athenaDiagnostics.span("auth.bridge", "auth.bridge.start", { mode: "marathon" }) : null;
218
- let spanEnded = false;
219
- const endBridgeSpan = rawEndSpan ? (name, options) => {
220
- if (spanEnded) return 0;
221
- spanEnded = true;
222
- return rawEndSpan(name, options);
223
- } : null;
362
+ let timedOut = false;
224
363
  const requestTimer = setTimeout(() => {
364
+ timedOut = true;
225
365
  requestController.abort();
226
- endBridgeSpan == null ? void 0 : endBridgeSpan("auth.bridge.timeout", {
227
- data: { mode: "marathon", timeout_ms: BRIDGE_TIMEOUT_MS }
228
- });
229
- if (markReadyOnFailure) {
230
- markReady();
231
- }
232
- }, BRIDGE_TIMEOUT_MS);
366
+ }, timeoutMs);
233
367
  try {
234
368
  const resp = await fetch("/_athena/auth", {
235
369
  credentials: "include",
236
370
  headers: { Accept: "application/json" },
237
371
  signal: requestController.signal
238
372
  });
239
- if (cancelled) return null;
373
+ if (cancelled) return { kind: "cancelled" };
374
+ if (resp.status === 404) return { kind: "absent", status: resp.status };
240
375
  if (!resp.ok) {
241
- if (resp.status === 404) {
242
- endBridgeSpan == null ? void 0 : endBridgeSpan("auth.bridge.absent", {
243
- data: { mode: "marathon", status: resp.status }
244
- });
245
- markReady();
246
- } else {
247
- const failure = {
248
- level: "warn",
249
- data: { mode: "marathon", status: resp.status }
250
- };
251
- if (endBridgeSpan) {
252
- endBridgeSpan("auth.bridge.error", failure);
253
- } else {
254
- athenaDiagnostics.athenaDiagnostics.emit("auth.token.refresh_failed", failure);
255
- }
256
- if (markReadyOnFailure) {
257
- markReady();
258
- }
259
- scheduleRetry();
376
+ const hostError = await readHostError(resp);
377
+ if (cancelled) return { kind: "cancelled" };
378
+ if (timedOut && isRetryableMintStatus(resp.status)) {
379
+ return { kind: "transient", status: null, reason: "timeout", hostError: null };
260
380
  }
261
- return null;
262
- }
263
- const data = await resp.json();
264
- if (cancelled) return null;
265
- if (endBridgeSpan) {
266
- endBridgeSpan("auth.bridge.ready", {
267
- data: { mode: "marathon", has_token: typeof data.token === "string" }
268
- });
269
- athenaDiagnostics.athenaDiagnostics.emit("auth.token.acquired", {
270
- level: "debug",
271
- data: { source: "marathon", expires_at: data.expires_at }
272
- });
273
- } else {
274
- athenaDiagnostics.athenaDiagnostics.emit("auth.token.refreshed", {
275
- level: "debug",
276
- data: { source: "marathon", expires_at: data.expires_at }
277
- });
381
+ return isRetryableMintStatus(resp.status) ? { kind: "transient", status: resp.status, reason: "status", hostError } : { kind: "refused", status: resp.status, reason: "status", hostError };
278
382
  }
279
- const token = typeof data.token === "string" ? data.token : null;
280
- setState({
281
- token,
282
- ...readAuthMintUrls(data),
283
- hostAnswered: true,
284
- ready: true
285
- });
286
- tokenRef.current = token;
287
- scheduleRefresh(data.expires_at);
288
- return token;
289
- } catch (error) {
290
- if (!cancelled && !(error instanceof DOMException && error.name === "AbortError")) {
291
- const failure = { level: "warn", data: { mode: "marathon" }, error };
292
- if (endBridgeSpan) {
293
- endBridgeSpan("auth.bridge.error", failure);
294
- } else {
295
- athenaDiagnostics.athenaDiagnostics.emit("auth.token.refresh_failed", failure);
383
+ let data;
384
+ try {
385
+ data = await resp.json();
386
+ } catch (error) {
387
+ if (cancelled) return { kind: "cancelled" };
388
+ if (timedOut) {
389
+ return { kind: "transient", status: null, reason: "timeout", hostError: null, cause: error };
296
390
  }
391
+ return { kind: "refused", status: resp.status, reason: "invalid_body", hostError: null };
297
392
  }
298
- if (markReadyOnFailure) {
299
- markReady();
393
+ if (cancelled) return { kind: "cancelled" };
394
+ const token = data && typeof data === "object" ? data.token : null;
395
+ if (typeof token !== "string" || token === "") {
396
+ return { kind: "refused", status: resp.status, reason: "invalid_body", hostError: null };
300
397
  }
301
- if (!cancelled) {
302
- scheduleRetry();
398
+ return { kind: "minted", data };
399
+ } catch (error) {
400
+ if (cancelled) return { kind: "cancelled" };
401
+ if (timedOut) {
402
+ return { kind: "transient", status: null, reason: "timeout", hostError: null, cause: error };
303
403
  }
304
- return null;
404
+ return { kind: "transient", status: null, reason: "network", hostError: null, cause: error };
305
405
  } finally {
306
406
  clearTimeout(requestTimer);
307
407
  if (controller === requestController) controller = null;
308
408
  }
309
409
  };
410
+ const runMintCycle = async ({ initial }) => {
411
+ const policy = mintRetryPolicyRef.current;
412
+ const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));
413
+ const phase = initial ? "initial" : "renewal";
414
+ const endBridgeSpan = initial ? athenaDiagnostics.athenaDiagnostics.span("auth.bridge", "auth.bridge.start", { mode: "marathon" }) : null;
415
+ let attempts = 0;
416
+ let lastFailure = null;
417
+ while (attempts < maxAttempts) {
418
+ attempts += 1;
419
+ const outcome = await mintOnce(policy.timeoutMs);
420
+ if (cancelled || outcome.kind === "cancelled") return null;
421
+ if (outcome.kind === "absent") {
422
+ endBridgeSpan == null ? void 0 : endBridgeSpan("auth.bridge.absent", {
423
+ data: { mode: "marathon", status: outcome.status }
424
+ });
425
+ setState(
426
+ (prev) => prev.ready && prev.error == null ? prev : { ...prev, ready: true, error: null }
427
+ );
428
+ return null;
429
+ }
430
+ if (outcome.kind === "minted") {
431
+ const { data } = outcome;
432
+ if (endBridgeSpan) {
433
+ endBridgeSpan("auth.bridge.ready", {
434
+ data: { mode: "marathon", has_token: typeof data.token === "string", attempts }
435
+ });
436
+ athenaDiagnostics.athenaDiagnostics.emit("auth.token.acquired", {
437
+ level: "debug",
438
+ data: { source: "marathon", expires_at: data.expires_at }
439
+ });
440
+ } else {
441
+ athenaDiagnostics.athenaDiagnostics.emit("auth.token.refreshed", {
442
+ level: "debug",
443
+ data: { source: "marathon", expires_at: data.expires_at, attempts }
444
+ });
445
+ }
446
+ const token = typeof data.token === "string" ? data.token : null;
447
+ setState({
448
+ token,
449
+ ...readAuthMintUrls(data),
450
+ hostAnswered: true,
451
+ ready: true,
452
+ error: null
453
+ });
454
+ tokenRef.current = token;
455
+ scheduleRefresh(data.expires_at);
456
+ return token;
457
+ }
458
+ lastFailure = outcome;
459
+ if (outcome.kind === "refused" || attempts >= maxAttempts) break;
460
+ const delayMs = getMintRetryDelay({ retry: attempts, policy });
461
+ athenaDiagnostics.athenaDiagnostics.emit("auth.bridge.retry", {
462
+ level: "warn",
463
+ data: {
464
+ mode: "marathon",
465
+ phase,
466
+ status: outcome.status,
467
+ reason: outcome.reason,
468
+ host_error: outcome.hostError,
469
+ attempt: attempts,
470
+ max_attempts: maxAttempts,
471
+ delay_ms: delayMs
472
+ }
473
+ });
474
+ await waitBeforeRetry(delayMs);
475
+ if (cancelled) return null;
476
+ }
477
+ if (!lastFailure) return null;
478
+ const error = mintFailureError({ failure: lastFailure, attempts, phase });
479
+ const failureData = {
480
+ mode: "marathon",
481
+ phase,
482
+ status: lastFailure.status,
483
+ reason: lastFailure.reason,
484
+ attempts
485
+ };
486
+ if (endBridgeSpan) {
487
+ endBridgeSpan(
488
+ error.code === "auth_bridge_timeout" ? "auth.bridge.timeout" : "auth.bridge.error",
489
+ { data: failureData, error }
490
+ );
491
+ } else {
492
+ athenaDiagnostics.athenaDiagnostics.emit("auth.token.refresh_failed", {
493
+ level: "error",
494
+ data: failureData,
495
+ error
496
+ });
497
+ }
498
+ setState((prev) => ({ ...prev, error, ready: true }));
499
+ return null;
500
+ };
310
501
  const fetchAuth = (options) => {
311
- inFlight ?? (inFlight = mintAuth(options).finally(() => {
502
+ inFlight ?? (inFlight = runMintCycle(options).finally(() => {
312
503
  inFlight = null;
313
504
  }));
314
505
  return inFlight;
315
506
  };
316
507
  renewerRef.current = ({ force }) => {
317
508
  if (!force) return Promise.resolve(tokenRef.current);
318
- return fetchAuth({ markReadyOnFailure: false }).then((token) => token ?? tokenRef.current);
509
+ return fetchAuth({ initial: false }).then((token) => token ?? tokenRef.current);
319
510
  };
320
- void fetchAuth({ markReadyOnFailure: true });
511
+ void fetchAuth({ initial: true });
321
512
  return () => {
322
513
  cancelled = true;
323
514
  controller == null ? void 0 : controller.abort();
324
515
  clearRefreshTimer();
516
+ endRetryWait();
325
517
  renewerRef.current = null;
326
518
  };
327
519
  }, [isInIframe]);
@@ -337,10 +529,17 @@ function useParentBridge({
337
529
  function useParentAuth() {
338
530
  return useParentBridge().token;
339
531
  }
340
- exports.getAuthRetryDelay = getAuthRetryDelay;
532
+ exports.DEFAULT_MINT_RETRY_POLICY = DEFAULT_MINT_RETRY_POLICY;
533
+ exports.acceptBridgeConfigMessage = acceptBridgeConfigMessage;
534
+ exports.getMintRetryDelay = getMintRetryDelay;
341
535
  exports.getTokenRefreshDelay = getTokenRefreshDelay;
536
+ exports.isCustomerAppHost = isCustomerAppHost;
537
+ exports.isRetryableMintStatus = isRetryableMintStatus;
538
+ exports.isTrustedParentOrigin = isTrustedParentOrigin;
342
539
  exports.mergeBridgeConfigMessage = mergeBridgeConfigMessage;
540
+ exports.mintFailureError = mintFailureError;
343
541
  exports.readAuthMintUrls = readAuthMintUrls;
542
+ exports.refuseBridgeUrl = refuseBridgeUrl;
344
543
  exports.useParentAuth = useParentAuth;
345
544
  exports.useParentBridge = useParentBridge;
346
545
  //# sourceMappingURL=auth.cjs.map