@athenaintel/react 0.24.0 → 0.25.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 +29 -4
- package/dist/assets/AssetPanel.cjs +3 -2
- package/dist/assets/AssetPanel.cjs.map +1 -1
- package/dist/assets/AssetPanel.js +3 -2
- package/dist/assets/AssetPanel.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs +1 -0
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.js +1 -0
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs +22 -23
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +9 -0
- package/dist/assets/use-asset-embed.js +22 -23
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/auth/AthenaAuthProvider.cjs +32 -4
- package/dist/auth/AthenaAuthProvider.cjs.map +1 -1
- package/dist/auth/AthenaAuthProvider.d.ts +21 -0
- package/dist/auth/AthenaAuthProvider.js +33 -5
- package/dist/auth/AthenaAuthProvider.js.map +1 -1
- package/dist/auth/AthenaCredentialSourceContext.cjs +21 -0
- package/dist/auth/AthenaCredentialSourceContext.cjs.map +1 -0
- package/dist/auth/AthenaCredentialSourceContext.d.ts +14 -0
- package/dist/auth/AthenaCredentialSourceContext.js +21 -0
- package/dist/auth/AthenaCredentialSourceContext.js.map +1 -0
- package/dist/auth/AthenaSSOProvider.cjs +71 -47
- package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
- package/dist/auth/AthenaSSOProvider.js +71 -47
- package/dist/auth/AthenaSSOProvider.js.map +1 -1
- package/dist/auth/credential-source.cjs +120 -0
- package/dist/auth/credential-source.cjs.map +1 -0
- package/dist/auth/credential-source.d.ts +68 -0
- package/dist/auth/credential-source.js +120 -0
- package/dist/auth/credential-source.js.map +1 -0
- package/dist/auth/types.d.ts +9 -0
- package/dist/chat/AthenaChat.cjs +8 -0
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +8 -0
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/StatewireApprovalCard.cjs +31 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +34 -4
- package/dist/chat/StatewireApprovalCard.js +33 -8
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +20 -11
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +11 -2
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +20 -11
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/statewire.d.ts +1 -1
- package/dist/collab/mint.cjs +6 -15
- package/dist/collab/mint.cjs.map +1 -1
- package/dist/collab/mint.d.ts +8 -0
- package/dist/collab/mint.js +6 -15
- package/dist/collab/mint.js.map +1 -1
- package/dist/collab/presence-mint.cjs +20 -16
- package/dist/collab/presence-mint.cjs.map +1 -1
- package/dist/collab/presence-mint.js +21 -17
- package/dist/collab/presence-mint.js.map +1 -1
- package/dist/collab/presence-react.cjs +12 -5
- package/dist/collab/presence-react.cjs.map +1 -1
- package/dist/collab/presence-react.d.ts +3 -1
- package/dist/collab/presence-react.js +12 -5
- package/dist/collab/presence-react.js.map +1 -1
- package/dist/collab/react.cjs +5 -2
- package/dist/collab/react.cjs.map +1 -1
- package/dist/collab/react.js +5 -2
- package/dist/collab/react.js.map +1 -1
- package/dist/dictation/use-athena-dictation-engine.cjs +12 -11
- package/dist/dictation/use-athena-dictation-engine.cjs.map +1 -1
- package/dist/dictation/use-athena-dictation-engine.d.ts +4 -3
- package/dist/dictation/use-athena-dictation-engine.js +12 -11
- package/dist/dictation/use-athena-dictation-engine.js.map +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +1 -0
- package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.cjs +22 -2
- package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/interrupts.js +22 -2
- package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/packages/agent-runtime/src/messages.cjs +2 -1
- package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/messages.js +2 -1
- package/dist/packages/agent-runtime/src/messages.js.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.cjs +6 -0
- package/dist/packages/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/packages/agent-runtime/src/voice-presentation.js +6 -0
- package/dist/packages/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs +68 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.cjs.map +1 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.js +68 -0
- package/dist/packages/agent-runtime/src/voice-turn-grouping.js.map +1 -0
- package/dist/packages/react/package.json.cjs +1 -1
- package/dist/packages/react/package.json.js +1 -1
- package/dist/provider/AthenaContext.cjs.map +1 -1
- package/dist/provider/AthenaContext.d.ts +7 -1
- package/dist/provider/AthenaContext.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +24 -2
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +24 -2
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/theme-style-context.cjs +12 -0
- package/dist/provider/theme-style-context.cjs.map +1 -0
- package/dist/provider/theme-style-context.d.ts +8 -0
- package/dist/provider/theme-style-context.js +12 -0
- package/dist/provider/theme-style-context.js.map +1 -0
- package/dist/runtime/auth-refusal-restart.cjs +66 -0
- package/dist/runtime/auth-refusal-restart.cjs.map +1 -0
- package/dist/runtime/auth-refusal-restart.d.ts +35 -0
- package/dist/runtime/auth-refusal-restart.js +66 -0
- package/dist/runtime/auth-refusal-restart.js.map +1 -0
- package/dist/runtime/auth.cjs +58 -21
- package/dist/runtime/auth.cjs.map +1 -1
- package/dist/runtime/auth.d.ts +11 -1
- package/dist/runtime/auth.js +59 -22
- package/dist/runtime/auth.js.map +1 -1
- package/dist/runtime/credential-renewal.cjs +29 -0
- package/dist/runtime/credential-renewal.cjs.map +1 -0
- package/dist/runtime/credential-renewal.d.ts +35 -0
- package/dist/runtime/credential-renewal.js +29 -0
- package/dist/runtime/credential-renewal.js.map +1 -0
- package/dist/search/useAthenaRepoSearch.cjs +3 -5
- package/dist/search/useAthenaRepoSearch.cjs.map +1 -1
- package/dist/search/useAthenaRepoSearch.d.ts +7 -7
- package/dist/search/useAthenaRepoSearch.js +3 -5
- package/dist/search/useAthenaRepoSearch.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/SessionList.cjs +87 -16
- package/dist/threads/SessionList.cjs.map +1 -1
- package/dist/threads/SessionList.js +88 -17
- package/dist/threads/SessionList.js.map +1 -1
- package/dist/threads/api.cjs +5 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.js +5 -1
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +5 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +6 -5
- package/dist/threads/conversation-list-fetcher.js +5 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/session-list-model.cjs +13 -5
- package/dist/threads/session-list-model.cjs.map +1 -1
- package/dist/threads/session-list-model.d.ts +10 -3
- package/dist/threads/session-list-model.js +13 -5
- package/dist/threads/session-list-model.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +13 -14
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.js +13 -14
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.cjs +6 -2
- package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +3 -1
- package/dist/threads/use-rest-conversation-history.js +7 -3
- package/dist/threads/use-rest-conversation-history.js.map +1 -1
- package/dist/threads/use-statewire-conversation-history.cjs +9 -2
- package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
- package/dist/threads/use-statewire-conversation-history.d.ts +3 -1
- package/dist/threads/use-statewire-conversation-history.js +10 -3
- package/dist/threads/use-statewire-conversation-history.js.map +1 -1
- package/dist/threads/use-thread-archive.cjs +2 -1
- package/dist/threads/use-thread-archive.cjs.map +1 -1
- package/dist/threads/use-thread-archive.d.ts +3 -1
- package/dist/threads/use-thread-archive.js +2 -1
- package/dist/threads/use-thread-archive.js.map +1 -1
- package/dist/threads/use-thread-favorite.cjs.map +1 -1
- package/dist/threads/use-thread-favorite.d.ts +3 -1
- package/dist/threads/use-thread-favorite.js.map +1 -1
- package/dist/threads/use-thread-field-override.cjs +2 -1
- package/dist/threads/use-thread-field-override.cjs.map +1 -1
- package/dist/threads/use-thread-field-override.d.ts +3 -1
- package/dist/threads/use-thread-field-override.js +2 -1
- package/dist/threads/use-thread-field-override.js.map +1 -1
- package/dist/threads/use-thread-rename.cjs.map +1 -1
- package/dist/threads/use-thread-rename.d.ts +3 -1
- package/dist/threads/use-thread-rename.js.map +1 -1
- package/dist/tools/tool-uis.cjs +11 -15
- package/dist/tools/tool-uis.cjs.map +1 -1
- package/dist/tools/tool-uis.js +11 -15
- package/dist/tools/tool-uis.js.map +1 -1
- package/dist/ui/popover.cjs +46 -0
- package/dist/ui/popover.cjs.map +1 -0
- package/dist/ui/popover.d.ts +6 -0
- package/dist/ui/popover.js +46 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/voice/use-athena-voice-session.cjs +17 -5
- package/dist/voice/use-athena-voice-session.cjs.map +1 -1
- package/dist/voice/use-athena-voice-session.d.ts +2 -2
- package/dist/voice/use-athena-voice-session.js +17 -5
- package/dist/voice/use-athena-voice-session.js.map +1 -1
- package/package.json +2 -2
package/dist/runtime/auth.cjs
CHANGED
|
@@ -41,6 +41,7 @@ function readAuthMintUrls(data) {
|
|
|
41
41
|
return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);
|
|
42
42
|
}
|
|
43
43
|
const BRIDGE_TIMEOUT_MS = 2e3;
|
|
44
|
+
const PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5e3;
|
|
44
45
|
const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
45
46
|
const TOKEN_REFRESH_RETRY_BASE_MS = 60 * 1e3;
|
|
46
47
|
const TOKEN_REFRESH_RETRY_MAX_MS = 5 * 60 * 1e3;
|
|
@@ -92,6 +93,9 @@ function useParentBridge({
|
|
|
92
93
|
});
|
|
93
94
|
const readySignalSent = react.useRef(false);
|
|
94
95
|
const configReceived = react.useRef(false);
|
|
96
|
+
const tokenRef = react.useRef(state.token);
|
|
97
|
+
tokenRef.current = state.token;
|
|
98
|
+
const renewerRef = react.useRef(null);
|
|
95
99
|
react.useEffect(() => {
|
|
96
100
|
if (!isInIframe) return;
|
|
97
101
|
const endBridgeSpan = athenaDiagnostics.athenaDiagnostics.span("auth.bridge", "auth.bridge.start", {
|
|
@@ -103,6 +107,7 @@ function useParentBridge({
|
|
|
103
107
|
spanEnded = true;
|
|
104
108
|
return endBridgeSpan(name, options);
|
|
105
109
|
};
|
|
110
|
+
const tokenWaiters = /* @__PURE__ */ new Set();
|
|
106
111
|
const handler = (event) => {
|
|
107
112
|
if (!isTrustedOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })) {
|
|
108
113
|
return;
|
|
@@ -130,9 +135,24 @@ function useParentBridge({
|
|
|
130
135
|
token: event.data.token,
|
|
131
136
|
ready: true
|
|
132
137
|
}));
|
|
138
|
+
tokenRef.current = event.data.token;
|
|
139
|
+
for (const resolve of [...tokenWaiters]) resolve(event.data.token);
|
|
133
140
|
}
|
|
134
141
|
};
|
|
135
142
|
window.addEventListener("message", handler);
|
|
143
|
+
renewerRef.current = ({ force }) => {
|
|
144
|
+
if (!force) return Promise.resolve(tokenRef.current);
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const settle = (token) => {
|
|
147
|
+
clearTimeout(timer2);
|
|
148
|
+
tokenWaiters.delete(settle);
|
|
149
|
+
resolve(token ?? tokenRef.current);
|
|
150
|
+
};
|
|
151
|
+
const timer2 = setTimeout(() => settle(null), PARENT_TOKEN_RENEWAL_TIMEOUT_MS);
|
|
152
|
+
tokenWaiters.add(settle);
|
|
153
|
+
window.parent.postMessage({ type: "athena-auth-ready" }, "*");
|
|
154
|
+
});
|
|
155
|
+
};
|
|
136
156
|
if (!readySignalSent.current) {
|
|
137
157
|
window.parent.postMessage({ type: "athena-auth-ready" }, "*");
|
|
138
158
|
readySignalSent.current = true;
|
|
@@ -150,22 +170,18 @@ function useParentBridge({
|
|
|
150
170
|
return () => {
|
|
151
171
|
window.removeEventListener("message", handler);
|
|
152
172
|
clearTimeout(timer);
|
|
173
|
+
renewerRef.current = null;
|
|
174
|
+
for (const resolve of [...tokenWaiters]) resolve(null);
|
|
153
175
|
};
|
|
154
176
|
}, [isInIframe, runtimeTrustedOrigins]);
|
|
155
177
|
react.useEffect(() => {
|
|
156
178
|
if (isInIframe) return;
|
|
157
179
|
if (typeof window === "undefined") return;
|
|
158
180
|
let cancelled = false;
|
|
159
|
-
let requestTimer = null;
|
|
160
181
|
let refreshTimer = null;
|
|
161
182
|
let controller = null;
|
|
183
|
+
let inFlight = null;
|
|
162
184
|
let retryAttempt = 0;
|
|
163
|
-
const clearRequestTimer = () => {
|
|
164
|
-
if (requestTimer) {
|
|
165
|
-
clearTimeout(requestTimer);
|
|
166
|
-
requestTimer = null;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
185
|
const clearRefreshTimer = () => {
|
|
170
186
|
if (refreshTimer) {
|
|
171
187
|
clearTimeout(refreshTimer);
|
|
@@ -192,11 +208,11 @@ function useParentBridge({
|
|
|
192
208
|
void fetchAuth({ markReadyOnFailure: false });
|
|
193
209
|
}, delay);
|
|
194
210
|
};
|
|
195
|
-
const
|
|
211
|
+
const mintAuth = async ({
|
|
196
212
|
markReadyOnFailure
|
|
197
213
|
}) => {
|
|
198
|
-
|
|
199
|
-
controller =
|
|
214
|
+
const requestController = new AbortController();
|
|
215
|
+
controller = requestController;
|
|
200
216
|
const isInitial = markReadyOnFailure;
|
|
201
217
|
const rawEndSpan = isInitial ? athenaDiagnostics.athenaDiagnostics.span("auth.bridge", "auth.bridge.start", { mode: "marathon" }) : null;
|
|
202
218
|
let spanEnded = false;
|
|
@@ -205,9 +221,8 @@ function useParentBridge({
|
|
|
205
221
|
spanEnded = true;
|
|
206
222
|
return rawEndSpan(name, options);
|
|
207
223
|
} : null;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
controller == null ? void 0 : controller.abort();
|
|
224
|
+
const requestTimer = setTimeout(() => {
|
|
225
|
+
requestController.abort();
|
|
211
226
|
endBridgeSpan == null ? void 0 : endBridgeSpan("auth.bridge.timeout", {
|
|
212
227
|
data: { mode: "marathon", timeout_ms: BRIDGE_TIMEOUT_MS }
|
|
213
228
|
});
|
|
@@ -219,9 +234,9 @@ function useParentBridge({
|
|
|
219
234
|
const resp = await fetch("/_athena/auth", {
|
|
220
235
|
credentials: "include",
|
|
221
236
|
headers: { Accept: "application/json" },
|
|
222
|
-
signal:
|
|
237
|
+
signal: requestController.signal
|
|
223
238
|
});
|
|
224
|
-
if (cancelled) return;
|
|
239
|
+
if (cancelled) return null;
|
|
225
240
|
if (!resp.ok) {
|
|
226
241
|
if (resp.status === 404) {
|
|
227
242
|
endBridgeSpan == null ? void 0 : endBridgeSpan("auth.bridge.absent", {
|
|
@@ -243,10 +258,10 @@ function useParentBridge({
|
|
|
243
258
|
}
|
|
244
259
|
scheduleRetry();
|
|
245
260
|
}
|
|
246
|
-
return;
|
|
261
|
+
return null;
|
|
247
262
|
}
|
|
248
263
|
const data = await resp.json();
|
|
249
|
-
if (cancelled) return;
|
|
264
|
+
if (cancelled) return null;
|
|
250
265
|
if (endBridgeSpan) {
|
|
251
266
|
endBridgeSpan("auth.bridge.ready", {
|
|
252
267
|
data: { mode: "marathon", has_token: typeof data.token === "string" }
|
|
@@ -261,13 +276,16 @@ function useParentBridge({
|
|
|
261
276
|
data: { source: "marathon", expires_at: data.expires_at }
|
|
262
277
|
});
|
|
263
278
|
}
|
|
279
|
+
const token = typeof data.token === "string" ? data.token : null;
|
|
264
280
|
setState({
|
|
265
|
-
token
|
|
281
|
+
token,
|
|
266
282
|
...readAuthMintUrls(data),
|
|
267
283
|
hostAnswered: true,
|
|
268
284
|
ready: true
|
|
269
285
|
});
|
|
286
|
+
tokenRef.current = token;
|
|
270
287
|
scheduleRefresh(data.expires_at);
|
|
288
|
+
return token;
|
|
271
289
|
} catch (error) {
|
|
272
290
|
if (!cancelled && !(error instanceof DOMException && error.name === "AbortError")) {
|
|
273
291
|
const failure = { level: "warn", data: { mode: "marathon" }, error };
|
|
@@ -283,19 +301,38 @@ function useParentBridge({
|
|
|
283
301
|
if (!cancelled) {
|
|
284
302
|
scheduleRetry();
|
|
285
303
|
}
|
|
304
|
+
return null;
|
|
286
305
|
} finally {
|
|
287
|
-
|
|
306
|
+
clearTimeout(requestTimer);
|
|
307
|
+
if (controller === requestController) controller = null;
|
|
288
308
|
}
|
|
289
309
|
};
|
|
310
|
+
const fetchAuth = (options) => {
|
|
311
|
+
inFlight ?? (inFlight = mintAuth(options).finally(() => {
|
|
312
|
+
inFlight = null;
|
|
313
|
+
}));
|
|
314
|
+
return inFlight;
|
|
315
|
+
};
|
|
316
|
+
renewerRef.current = ({ force }) => {
|
|
317
|
+
if (!force) return Promise.resolve(tokenRef.current);
|
|
318
|
+
return fetchAuth({ markReadyOnFailure: false }).then((token) => token ?? tokenRef.current);
|
|
319
|
+
};
|
|
290
320
|
void fetchAuth({ markReadyOnFailure: true });
|
|
291
321
|
return () => {
|
|
292
322
|
cancelled = true;
|
|
293
323
|
controller == null ? void 0 : controller.abort();
|
|
294
|
-
clearRequestTimer();
|
|
295
324
|
clearRefreshTimer();
|
|
325
|
+
renewerRef.current = null;
|
|
296
326
|
};
|
|
297
327
|
}, [isInIframe]);
|
|
298
|
-
|
|
328
|
+
const renewToken = react.useCallback(
|
|
329
|
+
(options) => {
|
|
330
|
+
var _a;
|
|
331
|
+
return ((_a = renewerRef.current) == null ? void 0 : _a.call(renewerRef, options)) ?? Promise.resolve(tokenRef.current);
|
|
332
|
+
},
|
|
333
|
+
[]
|
|
334
|
+
);
|
|
335
|
+
return react.useMemo(() => ({ ...state, renewToken }), [state, renewToken]);
|
|
299
336
|
}
|
|
300
337
|
function useParentAuth() {
|
|
301
338
|
return useParentBridge().token;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.cjs","sources":["../../src/runtime/auth.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\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\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}\n\n/** How long to wait for the parent bridge before falling back to defaults. */\nconst BRIDGE_TIMEOUT_MS = 2000;\nconst TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;\nconst TOKEN_REFRESH_RETRY_BASE_MS = 60 * 1000;\nconst TOKEN_REFRESH_RETRY_MAX_MS = 5 * 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\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_RETRY_BASE_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\nexport const getAuthRetryDelay = (retryAttempt: number): number => {\n const safeAttempt = Number.isFinite(retryAttempt) ? Math.max(0, Math.floor(retryAttempt)) : 0;\n return Math.min(TOKEN_REFRESH_RETRY_MAX_MS, TOKEN_REFRESH_RETRY_BASE_MS * 2 ** safeAttempt);\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.\n *\n * In both modes a timeout safety net flips `ready: true` so the app never\n * blocks indefinitely.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\n}: ParentBridgeOptions = {}): ParentBridgeState {\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 });\n const readySignalSent = useRef(false);\n const configReceived = useRef(false);\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 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 }\n };\n window.addEventListener('message', handler);\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 };\n }, [isInIframe, runtimeTrustedOrigins]);\n\n useEffect(() => {\n if (isInIframe) return;\n if (typeof window === 'undefined') return;\n\n let cancelled = false;\n let requestTimer: ReturnType<typeof setTimeout> | null = null;\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n let retryAttempt = 0;\n\n const clearRequestTimer = () => {\n if (requestTimer) {\n clearTimeout(requestTimer);\n requestTimer = null;\n }\n };\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const markReady = () => {\n if (cancelled) return;\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n };\n\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n retryAttempt = 0;\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const scheduleRetry = () => {\n clearRefreshTimer();\n const delay = getAuthRetryDelay(retryAttempt);\n retryAttempt += 1;\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const fetchAuth = async ({\n markReadyOnFailure,\n }: {\n markReadyOnFailure: boolean;\n }) => {\n controller?.abort();\n controller = new AbortController();\n\n // `markReadyOnFailure` distinguishes the initial acquisition (a span the\n // provider blocks on) from a background refresh. Idempotent: the abort\n // timer and the catch path can both reach an end call.\n const isInitial = markReadyOnFailure;\n const rawEndSpan = isInitial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n let spanEnded = false;\n const endBridgeSpan: typeof rawEndSpan = rawEndSpan\n ? (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return rawEndSpan(name, options);\n }\n : null;\n\n clearRequestTimer();\n requestTimer = setTimeout(() => {\n controller?.abort();\n endBridgeSpan?.('auth.bridge.timeout', {\n data: { mode: 'marathon', timeout_ms: BRIDGE_TIMEOUT_MS },\n });\n if (markReadyOnFailure) {\n markReady();\n }\n }, BRIDGE_TIMEOUT_MS);\n\n try {\n const resp = await fetch('/_athena/auth', {\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: controller.signal,\n });\n if (cancelled) return;\n\n if (!resp.ok) {\n if (resp.status === 404) {\n // Not a Marathon origin — expected for standalone/local dev.\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: resp.status },\n });\n markReady();\n } else {\n const failure = {\n level: 'warn' as const,\n data: { mode: 'marathon', status: resp.status },\n };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n if (markReadyOnFailure) {\n markReady();\n }\n scheduleRetry();\n }\n return;\n }\n\n const data = (await resp.json()) as AthenaAuthResponse;\n if (cancelled) return;\n\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string' },\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 },\n });\n }\n\n setState({\n token: typeof data.token === 'string' ? data.token : null,\n ...readAuthMintUrls(data),\n hostAnswered: true,\n ready: true,\n });\n scheduleRefresh(data.expires_at);\n } catch (error) {\n if (!cancelled && !(error instanceof DOMException && error.name === 'AbortError')) {\n const failure = { level: 'warn' as const, data: { mode: 'marathon' }, error };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n }\n if (markReadyOnFailure) {\n markReady();\n }\n if (!cancelled) {\n scheduleRetry();\n }\n } finally {\n clearRequestTimer();\n }\n };\n\n void fetchAuth({ markReadyOnFailure: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRequestTimer();\n clearRefreshTimer();\n };\n }, [isInIframe]);\n\n return state;\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":["useMemo","useState","useRef","useEffect","athenaDiagnostics"],"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';\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\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}\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_RETRY_BASE_MS = 60 * 1000;\nconst TOKEN_REFRESH_RETRY_MAX_MS = 5 * 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\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_RETRY_BASE_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\nexport const getAuthRetryDelay = (retryAttempt: number): number => {\n const safeAttempt = Number.isFinite(retryAttempt) ? Math.max(0, Math.floor(retryAttempt)) : 0;\n return Math.min(TOKEN_REFRESH_RETRY_MAX_MS, TOKEN_REFRESH_RETRY_BASE_MS * 2 ** safeAttempt);\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.\n *\n * In both modes a timeout safety net flips `ready: true` so the app never\n * blocks indefinitely.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\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 });\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 retryAttempt = 0;\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const markReady = () => {\n if (cancelled) return;\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n };\n\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n retryAttempt = 0;\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const scheduleRetry = () => {\n clearRefreshTimer();\n const delay = getAuthRetryDelay(retryAttempt);\n retryAttempt += 1;\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const mintAuth = async ({\n markReadyOnFailure,\n }: {\n markReadyOnFailure: boolean;\n }): Promise<string | null> => {\n const requestController = new AbortController();\n controller = requestController;\n\n // `markReadyOnFailure` distinguishes the initial acquisition (a span the\n // provider blocks on) from a background refresh. Idempotent: the abort\n // timer and the catch path can both reach an end call.\n const isInitial = markReadyOnFailure;\n const rawEndSpan = isInitial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n let spanEnded = false;\n const endBridgeSpan: typeof rawEndSpan = rawEndSpan\n ? (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return rawEndSpan(name, options);\n }\n : null;\n\n const requestTimer = setTimeout(() => {\n requestController.abort();\n endBridgeSpan?.('auth.bridge.timeout', {\n data: { mode: 'marathon', timeout_ms: BRIDGE_TIMEOUT_MS },\n });\n if (markReadyOnFailure) {\n markReady();\n }\n }, BRIDGE_TIMEOUT_MS);\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 null;\n\n if (!resp.ok) {\n if (resp.status === 404) {\n // Not a Marathon origin — expected for standalone/local dev.\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: resp.status },\n });\n markReady();\n } else {\n const failure = {\n level: 'warn' as const,\n data: { mode: 'marathon', status: resp.status },\n };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n if (markReadyOnFailure) {\n markReady();\n }\n scheduleRetry();\n }\n return null;\n }\n\n const data = (await resp.json()) as AthenaAuthResponse;\n if (cancelled) return null;\n\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string' },\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 },\n });\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 });\n tokenRef.current = token;\n scheduleRefresh(data.expires_at);\n return token;\n } catch (error) {\n if (!cancelled && !(error instanceof DOMException && error.name === 'AbortError')) {\n const failure = { level: 'warn' as const, data: { mode: 'marathon' }, error };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n }\n if (markReadyOnFailure) {\n markReady();\n }\n if (!cancelled) {\n scheduleRetry();\n }\n return null;\n } finally {\n clearTimeout(requestTimer);\n if (controller === requestController) controller = null;\n }\n };\n\n // One mint at a time. The initial acquisition, a scheduled refresh, a\n // retry and a forced renewal that overlap share the request in flight\n // instead of aborting it, so each request keeps its own timeout and the\n // schedule never cancels a renewal a connection is waiting on.\n const fetchAuth = (options: { markReadyOnFailure: boolean }): Promise<string | null> => {\n inFlight ??= mintAuth(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 mint already in flight); the schedule restarts from the new\n // token's expiry.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return fetchAuth({ markReadyOnFailure: false }).then((token) => token ?? tokenRef.current);\n };\n\n void fetchAuth({ markReadyOnFailure: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRefreshTimer();\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":["useMemo","useState","useRef","useEffect","athenaDiagnostics","timer","useCallback"],"mappings":"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"}
|
package/dist/runtime/auth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AthenaTokenRenewer } from '../auth/credential-source';
|
|
1
2
|
export interface ParentBridgeState {
|
|
2
3
|
/** PropelAuth access token from the parent window. */
|
|
3
4
|
token: string | null;
|
|
@@ -25,6 +26,15 @@ export interface ParentBridgeState {
|
|
|
25
26
|
/** True once config has been received from the parent (or timeout fired). */
|
|
26
27
|
ready: boolean;
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* {@link ParentBridgeState} plus the bridge's token renewal: `force` (the
|
|
31
|
+
* token was refused) asks the host for a new one — a fresh `/_athena/auth`
|
|
32
|
+
* mint on a Marathon origin, or an `athena-auth-ready` request answered by the
|
|
33
|
+
* parent window's next `athena-auth` — and resolves the held token otherwise.
|
|
34
|
+
*/
|
|
35
|
+
export type ParentBridge = ParentBridgeState & {
|
|
36
|
+
renewToken: AthenaTokenRenewer;
|
|
37
|
+
};
|
|
28
38
|
declare const BRIDGE_URL_FIELDS: readonly ['statewireSyncUrl', 'backendBaseUrl', 'appUrl'];
|
|
29
39
|
type BridgeUrlField = (typeof BRIDGE_URL_FIELDS)[number];
|
|
30
40
|
/** The URL half of {@link ParentBridgeState} — what a host can hand the SDK. */
|
|
@@ -70,7 +80,7 @@ export declare const getAuthRetryDelay: (retryAttempt: number) => number;
|
|
|
70
80
|
* In both modes a timeout safety net flips `ready: true` so the app never
|
|
71
81
|
* blocks indefinitely.
|
|
72
82
|
*/
|
|
73
|
-
export declare function useParentBridge({ trustedOrigins, }?: ParentBridgeOptions):
|
|
83
|
+
export declare function useParentBridge({ trustedOrigins, }?: ParentBridgeOptions): ParentBridge;
|
|
74
84
|
/**
|
|
75
85
|
* Legacy hook — returns just the auth token from the parent bridge.
|
|
76
86
|
* Prefer useParentBridge() for new code.
|
package/dist/runtime/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useMemo, useState, useRef, useEffect } from "react";
|
|
1
|
+
import { useMemo, useState, useRef, useEffect, useCallback } from "react";
|
|
2
2
|
import { athenaDiagnostics } from "../diagnostics/athena-diagnostics.js";
|
|
3
3
|
function isTrustedOrigin({
|
|
4
4
|
origin,
|
|
@@ -39,6 +39,7 @@ function readAuthMintUrls(data) {
|
|
|
39
39
|
return mergeBridgeConfigMessage(EMPTY_BRIDGE_URLS, data);
|
|
40
40
|
}
|
|
41
41
|
const BRIDGE_TIMEOUT_MS = 2e3;
|
|
42
|
+
const PARENT_TOKEN_RENEWAL_TIMEOUT_MS = 5e3;
|
|
42
43
|
const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1e3;
|
|
43
44
|
const TOKEN_REFRESH_RETRY_BASE_MS = 60 * 1e3;
|
|
44
45
|
const TOKEN_REFRESH_RETRY_MAX_MS = 5 * 60 * 1e3;
|
|
@@ -90,6 +91,9 @@ function useParentBridge({
|
|
|
90
91
|
});
|
|
91
92
|
const readySignalSent = useRef(false);
|
|
92
93
|
const configReceived = useRef(false);
|
|
94
|
+
const tokenRef = useRef(state.token);
|
|
95
|
+
tokenRef.current = state.token;
|
|
96
|
+
const renewerRef = useRef(null);
|
|
93
97
|
useEffect(() => {
|
|
94
98
|
if (!isInIframe) return;
|
|
95
99
|
const endBridgeSpan = athenaDiagnostics.span("auth.bridge", "auth.bridge.start", {
|
|
@@ -101,6 +105,7 @@ function useParentBridge({
|
|
|
101
105
|
spanEnded = true;
|
|
102
106
|
return endBridgeSpan(name, options);
|
|
103
107
|
};
|
|
108
|
+
const tokenWaiters = /* @__PURE__ */ new Set();
|
|
104
109
|
const handler = (event) => {
|
|
105
110
|
if (!isTrustedOrigin({ origin: event.origin, trustedOrigins: runtimeTrustedOrigins })) {
|
|
106
111
|
return;
|
|
@@ -128,9 +133,24 @@ function useParentBridge({
|
|
|
128
133
|
token: event.data.token,
|
|
129
134
|
ready: true
|
|
130
135
|
}));
|
|
136
|
+
tokenRef.current = event.data.token;
|
|
137
|
+
for (const resolve of [...tokenWaiters]) resolve(event.data.token);
|
|
131
138
|
}
|
|
132
139
|
};
|
|
133
140
|
window.addEventListener("message", handler);
|
|
141
|
+
renewerRef.current = ({ force }) => {
|
|
142
|
+
if (!force) return Promise.resolve(tokenRef.current);
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
const settle = (token) => {
|
|
145
|
+
clearTimeout(timer2);
|
|
146
|
+
tokenWaiters.delete(settle);
|
|
147
|
+
resolve(token ?? tokenRef.current);
|
|
148
|
+
};
|
|
149
|
+
const timer2 = setTimeout(() => settle(null), PARENT_TOKEN_RENEWAL_TIMEOUT_MS);
|
|
150
|
+
tokenWaiters.add(settle);
|
|
151
|
+
window.parent.postMessage({ type: "athena-auth-ready" }, "*");
|
|
152
|
+
});
|
|
153
|
+
};
|
|
134
154
|
if (!readySignalSent.current) {
|
|
135
155
|
window.parent.postMessage({ type: "athena-auth-ready" }, "*");
|
|
136
156
|
readySignalSent.current = true;
|
|
@@ -148,22 +168,18 @@ function useParentBridge({
|
|
|
148
168
|
return () => {
|
|
149
169
|
window.removeEventListener("message", handler);
|
|
150
170
|
clearTimeout(timer);
|
|
171
|
+
renewerRef.current = null;
|
|
172
|
+
for (const resolve of [...tokenWaiters]) resolve(null);
|
|
151
173
|
};
|
|
152
174
|
}, [isInIframe, runtimeTrustedOrigins]);
|
|
153
175
|
useEffect(() => {
|
|
154
176
|
if (isInIframe) return;
|
|
155
177
|
if (typeof window === "undefined") return;
|
|
156
178
|
let cancelled = false;
|
|
157
|
-
let requestTimer = null;
|
|
158
179
|
let refreshTimer = null;
|
|
159
180
|
let controller = null;
|
|
181
|
+
let inFlight = null;
|
|
160
182
|
let retryAttempt = 0;
|
|
161
|
-
const clearRequestTimer = () => {
|
|
162
|
-
if (requestTimer) {
|
|
163
|
-
clearTimeout(requestTimer);
|
|
164
|
-
requestTimer = null;
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
183
|
const clearRefreshTimer = () => {
|
|
168
184
|
if (refreshTimer) {
|
|
169
185
|
clearTimeout(refreshTimer);
|
|
@@ -190,11 +206,11 @@ function useParentBridge({
|
|
|
190
206
|
void fetchAuth({ markReadyOnFailure: false });
|
|
191
207
|
}, delay);
|
|
192
208
|
};
|
|
193
|
-
const
|
|
209
|
+
const mintAuth = async ({
|
|
194
210
|
markReadyOnFailure
|
|
195
211
|
}) => {
|
|
196
|
-
|
|
197
|
-
controller =
|
|
212
|
+
const requestController = new AbortController();
|
|
213
|
+
controller = requestController;
|
|
198
214
|
const isInitial = markReadyOnFailure;
|
|
199
215
|
const rawEndSpan = isInitial ? athenaDiagnostics.span("auth.bridge", "auth.bridge.start", { mode: "marathon" }) : null;
|
|
200
216
|
let spanEnded = false;
|
|
@@ -203,9 +219,8 @@ function useParentBridge({
|
|
|
203
219
|
spanEnded = true;
|
|
204
220
|
return rawEndSpan(name, options);
|
|
205
221
|
} : null;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
controller == null ? void 0 : controller.abort();
|
|
222
|
+
const requestTimer = setTimeout(() => {
|
|
223
|
+
requestController.abort();
|
|
209
224
|
endBridgeSpan == null ? void 0 : endBridgeSpan("auth.bridge.timeout", {
|
|
210
225
|
data: { mode: "marathon", timeout_ms: BRIDGE_TIMEOUT_MS }
|
|
211
226
|
});
|
|
@@ -217,9 +232,9 @@ function useParentBridge({
|
|
|
217
232
|
const resp = await fetch("/_athena/auth", {
|
|
218
233
|
credentials: "include",
|
|
219
234
|
headers: { Accept: "application/json" },
|
|
220
|
-
signal:
|
|
235
|
+
signal: requestController.signal
|
|
221
236
|
});
|
|
222
|
-
if (cancelled) return;
|
|
237
|
+
if (cancelled) return null;
|
|
223
238
|
if (!resp.ok) {
|
|
224
239
|
if (resp.status === 404) {
|
|
225
240
|
endBridgeSpan == null ? void 0 : endBridgeSpan("auth.bridge.absent", {
|
|
@@ -241,10 +256,10 @@ function useParentBridge({
|
|
|
241
256
|
}
|
|
242
257
|
scheduleRetry();
|
|
243
258
|
}
|
|
244
|
-
return;
|
|
259
|
+
return null;
|
|
245
260
|
}
|
|
246
261
|
const data = await resp.json();
|
|
247
|
-
if (cancelled) return;
|
|
262
|
+
if (cancelled) return null;
|
|
248
263
|
if (endBridgeSpan) {
|
|
249
264
|
endBridgeSpan("auth.bridge.ready", {
|
|
250
265
|
data: { mode: "marathon", has_token: typeof data.token === "string" }
|
|
@@ -259,13 +274,16 @@ function useParentBridge({
|
|
|
259
274
|
data: { source: "marathon", expires_at: data.expires_at }
|
|
260
275
|
});
|
|
261
276
|
}
|
|
277
|
+
const token = typeof data.token === "string" ? data.token : null;
|
|
262
278
|
setState({
|
|
263
|
-
token
|
|
279
|
+
token,
|
|
264
280
|
...readAuthMintUrls(data),
|
|
265
281
|
hostAnswered: true,
|
|
266
282
|
ready: true
|
|
267
283
|
});
|
|
284
|
+
tokenRef.current = token;
|
|
268
285
|
scheduleRefresh(data.expires_at);
|
|
286
|
+
return token;
|
|
269
287
|
} catch (error) {
|
|
270
288
|
if (!cancelled && !(error instanceof DOMException && error.name === "AbortError")) {
|
|
271
289
|
const failure = { level: "warn", data: { mode: "marathon" }, error };
|
|
@@ -281,19 +299,38 @@ function useParentBridge({
|
|
|
281
299
|
if (!cancelled) {
|
|
282
300
|
scheduleRetry();
|
|
283
301
|
}
|
|
302
|
+
return null;
|
|
284
303
|
} finally {
|
|
285
|
-
|
|
304
|
+
clearTimeout(requestTimer);
|
|
305
|
+
if (controller === requestController) controller = null;
|
|
286
306
|
}
|
|
287
307
|
};
|
|
308
|
+
const fetchAuth = (options) => {
|
|
309
|
+
inFlight ?? (inFlight = mintAuth(options).finally(() => {
|
|
310
|
+
inFlight = null;
|
|
311
|
+
}));
|
|
312
|
+
return inFlight;
|
|
313
|
+
};
|
|
314
|
+
renewerRef.current = ({ force }) => {
|
|
315
|
+
if (!force) return Promise.resolve(tokenRef.current);
|
|
316
|
+
return fetchAuth({ markReadyOnFailure: false }).then((token) => token ?? tokenRef.current);
|
|
317
|
+
};
|
|
288
318
|
void fetchAuth({ markReadyOnFailure: true });
|
|
289
319
|
return () => {
|
|
290
320
|
cancelled = true;
|
|
291
321
|
controller == null ? void 0 : controller.abort();
|
|
292
|
-
clearRequestTimer();
|
|
293
322
|
clearRefreshTimer();
|
|
323
|
+
renewerRef.current = null;
|
|
294
324
|
};
|
|
295
325
|
}, [isInIframe]);
|
|
296
|
-
|
|
326
|
+
const renewToken = useCallback(
|
|
327
|
+
(options) => {
|
|
328
|
+
var _a;
|
|
329
|
+
return ((_a = renewerRef.current) == null ? void 0 : _a.call(renewerRef, options)) ?? Promise.resolve(tokenRef.current);
|
|
330
|
+
},
|
|
331
|
+
[]
|
|
332
|
+
);
|
|
333
|
+
return useMemo(() => ({ ...state, renewToken }), [state, renewToken]);
|
|
297
334
|
}
|
|
298
335
|
function useParentAuth() {
|
|
299
336
|
return useParentBridge().token;
|
package/dist/runtime/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sources":["../../src/runtime/auth.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\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\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}\n\n/** How long to wait for the parent bridge before falling back to defaults. */\nconst BRIDGE_TIMEOUT_MS = 2000;\nconst TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000;\nconst TOKEN_REFRESH_RETRY_BASE_MS = 60 * 1000;\nconst TOKEN_REFRESH_RETRY_MAX_MS = 5 * 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\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_RETRY_BASE_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\nexport const getAuthRetryDelay = (retryAttempt: number): number => {\n const safeAttempt = Number.isFinite(retryAttempt) ? Math.max(0, Math.floor(retryAttempt)) : 0;\n return Math.min(TOKEN_REFRESH_RETRY_MAX_MS, TOKEN_REFRESH_RETRY_BASE_MS * 2 ** safeAttempt);\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.\n *\n * In both modes a timeout safety net flips `ready: true` so the app never\n * blocks indefinitely.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\n}: ParentBridgeOptions = {}): ParentBridgeState {\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 });\n const readySignalSent = useRef(false);\n const configReceived = useRef(false);\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 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 }\n };\n window.addEventListener('message', handler);\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 };\n }, [isInIframe, runtimeTrustedOrigins]);\n\n useEffect(() => {\n if (isInIframe) return;\n if (typeof window === 'undefined') return;\n\n let cancelled = false;\n let requestTimer: ReturnType<typeof setTimeout> | null = null;\n let refreshTimer: ReturnType<typeof setTimeout> | null = null;\n let controller: AbortController | null = null;\n let retryAttempt = 0;\n\n const clearRequestTimer = () => {\n if (requestTimer) {\n clearTimeout(requestTimer);\n requestTimer = null;\n }\n };\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const markReady = () => {\n if (cancelled) return;\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n };\n\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n retryAttempt = 0;\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const scheduleRetry = () => {\n clearRefreshTimer();\n const delay = getAuthRetryDelay(retryAttempt);\n retryAttempt += 1;\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const fetchAuth = async ({\n markReadyOnFailure,\n }: {\n markReadyOnFailure: boolean;\n }) => {\n controller?.abort();\n controller = new AbortController();\n\n // `markReadyOnFailure` distinguishes the initial acquisition (a span the\n // provider blocks on) from a background refresh. Idempotent: the abort\n // timer and the catch path can both reach an end call.\n const isInitial = markReadyOnFailure;\n const rawEndSpan = isInitial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n let spanEnded = false;\n const endBridgeSpan: typeof rawEndSpan = rawEndSpan\n ? (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return rawEndSpan(name, options);\n }\n : null;\n\n clearRequestTimer();\n requestTimer = setTimeout(() => {\n controller?.abort();\n endBridgeSpan?.('auth.bridge.timeout', {\n data: { mode: 'marathon', timeout_ms: BRIDGE_TIMEOUT_MS },\n });\n if (markReadyOnFailure) {\n markReady();\n }\n }, BRIDGE_TIMEOUT_MS);\n\n try {\n const resp = await fetch('/_athena/auth', {\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: controller.signal,\n });\n if (cancelled) return;\n\n if (!resp.ok) {\n if (resp.status === 404) {\n // Not a Marathon origin — expected for standalone/local dev.\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: resp.status },\n });\n markReady();\n } else {\n const failure = {\n level: 'warn' as const,\n data: { mode: 'marathon', status: resp.status },\n };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n if (markReadyOnFailure) {\n markReady();\n }\n scheduleRetry();\n }\n return;\n }\n\n const data = (await resp.json()) as AthenaAuthResponse;\n if (cancelled) return;\n\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string' },\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 },\n });\n }\n\n setState({\n token: typeof data.token === 'string' ? data.token : null,\n ...readAuthMintUrls(data),\n hostAnswered: true,\n ready: true,\n });\n scheduleRefresh(data.expires_at);\n } catch (error) {\n if (!cancelled && !(error instanceof DOMException && error.name === 'AbortError')) {\n const failure = { level: 'warn' as const, data: { mode: 'marathon' }, error };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n }\n if (markReadyOnFailure) {\n markReady();\n }\n if (!cancelled) {\n scheduleRetry();\n }\n } finally {\n clearRequestTimer();\n }\n };\n\n void fetchAuth({ markReadyOnFailure: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRequestTimer();\n clearRefreshTimer();\n };\n }, [isInIframe]);\n\n return state;\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":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.js","sources":["../../src/runtime/auth.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { AthenaTokenRenewer } from '../auth/credential-source';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\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\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}\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_RETRY_BASE_MS = 60 * 1000;\nconst TOKEN_REFRESH_RETRY_MAX_MS = 5 * 60 * 1000;\nconst DEFAULT_TOKEN_REFRESH_MS = 55 * 60 * 1000;\nconst EMPTY_TRUSTED_ORIGINS: string[] = [];\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_RETRY_BASE_MS, expiresAtMs - nowMs - TOKEN_REFRESH_BUFFER_MS);\n};\n\nexport const getAuthRetryDelay = (retryAttempt: number): number => {\n const safeAttempt = Number.isFinite(retryAttempt) ? Math.max(0, Math.floor(retryAttempt)) : 0;\n return Math.min(TOKEN_REFRESH_RETRY_MAX_MS, TOKEN_REFRESH_RETRY_BASE_MS * 2 ** safeAttempt);\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.\n *\n * In both modes a timeout safety net flips `ready: true` so the app never\n * blocks indefinitely.\n */\nexport function useParentBridge({\n trustedOrigins = EMPTY_TRUSTED_ORIGINS,\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 });\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 retryAttempt = 0;\n\n const clearRefreshTimer = () => {\n if (refreshTimer) {\n clearTimeout(refreshTimer);\n refreshTimer = null;\n }\n };\n\n const markReady = () => {\n if (cancelled) return;\n setState((prev) => (prev.ready ? prev : { ...prev, ready: true }));\n };\n\n const scheduleRefresh = (expiresAt: unknown) => {\n clearRefreshTimer();\n retryAttempt = 0;\n const delay = getTokenRefreshDelay(expiresAt, Date.now());\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const scheduleRetry = () => {\n clearRefreshTimer();\n const delay = getAuthRetryDelay(retryAttempt);\n retryAttempt += 1;\n refreshTimer = setTimeout(() => {\n void fetchAuth({ markReadyOnFailure: false });\n }, delay);\n };\n\n const mintAuth = async ({\n markReadyOnFailure,\n }: {\n markReadyOnFailure: boolean;\n }): Promise<string | null> => {\n const requestController = new AbortController();\n controller = requestController;\n\n // `markReadyOnFailure` distinguishes the initial acquisition (a span the\n // provider blocks on) from a background refresh. Idempotent: the abort\n // timer and the catch path can both reach an end call.\n const isInitial = markReadyOnFailure;\n const rawEndSpan = isInitial\n ? athenaDiagnostics.span('auth.bridge', 'auth.bridge.start', { mode: 'marathon' })\n : null;\n let spanEnded = false;\n const endBridgeSpan: typeof rawEndSpan = rawEndSpan\n ? (name, options) => {\n if (spanEnded) return 0;\n spanEnded = true;\n return rawEndSpan(name, options);\n }\n : null;\n\n const requestTimer = setTimeout(() => {\n requestController.abort();\n endBridgeSpan?.('auth.bridge.timeout', {\n data: { mode: 'marathon', timeout_ms: BRIDGE_TIMEOUT_MS },\n });\n if (markReadyOnFailure) {\n markReady();\n }\n }, BRIDGE_TIMEOUT_MS);\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 null;\n\n if (!resp.ok) {\n if (resp.status === 404) {\n // Not a Marathon origin — expected for standalone/local dev.\n endBridgeSpan?.('auth.bridge.absent', {\n data: { mode: 'marathon', status: resp.status },\n });\n markReady();\n } else {\n const failure = {\n level: 'warn' as const,\n data: { mode: 'marathon', status: resp.status },\n };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n if (markReadyOnFailure) {\n markReady();\n }\n scheduleRetry();\n }\n return null;\n }\n\n const data = (await resp.json()) as AthenaAuthResponse;\n if (cancelled) return null;\n\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.ready', {\n data: { mode: 'marathon', has_token: typeof data.token === 'string' },\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 },\n });\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 });\n tokenRef.current = token;\n scheduleRefresh(data.expires_at);\n return token;\n } catch (error) {\n if (!cancelled && !(error instanceof DOMException && error.name === 'AbortError')) {\n const failure = { level: 'warn' as const, data: { mode: 'marathon' }, error };\n if (endBridgeSpan) {\n endBridgeSpan('auth.bridge.error', failure);\n } else {\n athenaDiagnostics.emit('auth.token.refresh_failed', failure);\n }\n }\n if (markReadyOnFailure) {\n markReady();\n }\n if (!cancelled) {\n scheduleRetry();\n }\n return null;\n } finally {\n clearTimeout(requestTimer);\n if (controller === requestController) controller = null;\n }\n };\n\n // One mint at a time. The initial acquisition, a scheduled refresh, a\n // retry and a forced renewal that overlap share the request in flight\n // instead of aborting it, so each request keeps its own timeout and the\n // schedule never cancels a renewal a connection is waiting on.\n const fetchAuth = (options: { markReadyOnFailure: boolean }): Promise<string | null> => {\n inFlight ??= mintAuth(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 mint already in flight); the schedule restarts from the new\n // token's expiry.\n renewerRef.current = ({ force }) => {\n if (!force) return Promise.resolve(tokenRef.current);\n return fetchAuth({ markReadyOnFailure: false }).then((token) => token ?? tokenRef.current);\n };\n\n void fetchAuth({ markReadyOnFailure: true });\n\n return () => {\n cancelled = true;\n controller?.abort();\n clearRefreshTimer();\n renewerRef.current = null;\n };\n }, [isInIframe]);\n\n const renewToken = useCallback<AthenaTokenRenewer>(\n (options) => renewerRef.current?.(options) ?? Promise.resolve(tokenRef.current),\n [],\n );\n\n return useMemo(() => ({ ...state, renewToken }), [state, renewToken]);\n}\n\n/**\n * Legacy hook — returns just the auth token from the parent bridge.\n * Prefer useParentBridge() for new code.\n */\nexport function useParentAuth(): string | null {\n return useParentBridge().token;\n}\n"],"names":["timer"],"mappings":"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"}
|