@flamingo-stack/openframe-frontend-core 0.0.217 → 0.0.218
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/dist/{chunk-L6IBKPVM.js → chunk-EKBM4FHK.js} +2 -2
- package/dist/{chunk-SWZUZYWR.js → chunk-EWA2NFUR.js} +2 -2
- package/dist/{chunk-TYIBMDUZ.cjs → chunk-FZZBCRID.cjs} +7 -7
- package/dist/{chunk-TYIBMDUZ.cjs.map → chunk-FZZBCRID.cjs.map} +1 -1
- package/dist/{chunk-G2HHSZ3S.cjs → chunk-GE64T3JT.cjs} +9 -9
- package/dist/{chunk-G2HHSZ3S.cjs.map → chunk-GE64T3JT.cjs.map} +1 -1
- package/dist/{chunk-YWDC5BXM.cjs → chunk-L5RSJE2I.cjs} +1940 -915
- package/dist/chunk-L5RSJE2I.cjs.map +1 -0
- package/dist/{chunk-BVFRD34B.js → chunk-OHOUSDAY.js} +2 -2
- package/dist/{chunk-MVQ3OODK.cjs → chunk-S4SVD5JI.cjs} +9 -9
- package/dist/{chunk-MVQ3OODK.cjs.map → chunk-S4SVD5JI.cjs.map} +1 -1
- package/dist/{chunk-N5IKPYRL.js → chunk-SWIR5EB2.js} +2 -2
- package/dist/{chunk-6DCKL73F.cjs → chunk-TCJ5B2ZD.cjs} +24 -24
- package/dist/{chunk-6DCKL73F.cjs.map → chunk-TCJ5B2ZD.cjs.map} +1 -1
- package/dist/{chunk-ENBGG2K2.js → chunk-V5JY5RSY.js} +2954 -1929
- package/dist/chunk-V5JY5RSY.js.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +13 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +104 -10
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-slash-commands.d.ts +6 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +81 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/index.cjs +73 -51
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +26 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/app-header.d.ts +7 -0
- package/dist/components/navigation/app-header.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts +65 -0
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -0
- package/dist/components/navigation/app-layout.d.ts +9 -1
- package/dist/components/navigation/app-layout.d.ts.map +1 -1
- package/dist/components/navigation/header-mingo-button.d.ts +21 -0
- package/dist/components/navigation/header-mingo-button.d.ts.map +1 -0
- package/dist/components/navigation/index.cjs +24 -2
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.d.ts +5 -1
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +23 -1
- package/dist/components/onboarding-guides/index.cjs +18 -18
- package/dist/components/onboarding-guides/index.js +3 -3
- package/dist/components/tickets/hooks/use-ticket-engagements.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +80 -66
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +20 -6
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +26 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +25 -1
- package/dist/utils/embed-authed-fetch.d.ts +80 -0
- package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
- package/dist/utils/index.cjs +70 -5
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +70 -6
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/chat/embeddable-chat.tsx +154 -37
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +601 -23
- package/src/components/chat/hooks/use-slash-commands.ts +10 -1
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +45 -0
- package/src/components/chat/hooks/use-unified-chat.ts +59 -0
- package/src/components/chat/types/unified-chat-state.types.ts +116 -0
- package/src/components/navigation/app-header.tsx +23 -0
- package/src/components/navigation/app-layout-drawer.tsx +620 -0
- package/src/components/navigation/app-layout.tsx +65 -26
- package/src/components/navigation/header-mingo-button.tsx +58 -0
- package/src/components/navigation/index.ts +17 -1
- package/src/components/tickets/hooks/use-ticket-engagements.ts +24 -4
- package/src/stories/AppLayoutDrawer.stories.tsx +228 -0
- package/src/utils/.embed-authed-fetch.md +7 -0
- package/src/utils/__tests__/embed-authed-fetch.test.ts +103 -1
- package/src/utils/embed-authed-fetch.ts +247 -7
- package/src/utils/index.ts +5 -1
- package/dist/chunk-ENBGG2K2.js.map +0 -1
- package/dist/chunk-YWDC5BXM.cjs.map +0 -1
- /package/dist/{chunk-L6IBKPVM.js.map → chunk-EKBM4FHK.js.map} +0 -0
- /package/dist/{chunk-SWZUZYWR.js.map → chunk-EWA2NFUR.js.map} +0 -0
- /package/dist/{chunk-BVFRD34B.js.map → chunk-OHOUSDAY.js.map} +0 -0
- /package/dist/{chunk-N5IKPYRL.js.map → chunk-SWIR5EB2.js.map} +0 -0
|
@@ -22,6 +22,117 @@
|
|
|
22
22
|
|
|
23
23
|
import { applyProxyAuth } from './embed-proxy-auth-storage'
|
|
24
24
|
|
|
25
|
+
// =============================================================================
|
|
26
|
+
// Host-supplied auth adapter (opt-in)
|
|
27
|
+
// =============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hosts that have their own auth model (cookie sessions, app-specific
|
|
31
|
+
* JWT in localStorage, OAuth access tokens, …) can register an adapter
|
|
32
|
+
* to override the lib's default `embedProxyAuth` flow. When set, the
|
|
33
|
+
* adapter's `getHeaders()` result is merged onto every `embedAuthedFetch`
|
|
34
|
+
* call AFTER the default proxy-auth header step (so adapter headers
|
|
35
|
+
* win over both caller and proxy values), and `credentials` overrides
|
|
36
|
+
* the default `'same-origin'` behaviour.
|
|
37
|
+
*
|
|
38
|
+
* Default (no adapter): MPH-style proxy-impersonation — bearer + act-as
|
|
39
|
+
* read from localStorage, `credentials: 'same-origin'`. No consumer
|
|
40
|
+
* needs to touch this unless they want a different auth model.
|
|
41
|
+
*
|
|
42
|
+
* Use cases:
|
|
43
|
+
* - openframe-frontend has its own JWT in `localStorage.of_access_token`
|
|
44
|
+
* and cookie-based session; register an adapter to attach the JWT
|
|
45
|
+
* and request `credentials: 'include'` so cookies travel cross-origin
|
|
46
|
+
* to the openframe gateway.
|
|
47
|
+
* - Future embed hosts with OAuth access tokens, signed URLs, etc.
|
|
48
|
+
*
|
|
49
|
+
* Lifetime: setter is module-level (intentionally — `embedAuthedFetch`
|
|
50
|
+
* is a plain utility, not a hook, so it can't read React context). Host
|
|
51
|
+
* runtime providers should call `setEmbedAuthAdapter(...)` on mount and
|
|
52
|
+
* `setEmbedAuthAdapter(null)` on unmount. Multiple hosts registering at
|
|
53
|
+
* once is a programming error (one chat panel per app).
|
|
54
|
+
*/
|
|
55
|
+
export interface EmbedAuthAdapter {
|
|
56
|
+
/** Headers merged onto every embedded-fetch call. Return `{}` to add
|
|
57
|
+
* nothing. Called per-request so reactive token refresh sees the latest
|
|
58
|
+
* value from your auth store / storage. Values typed as
|
|
59
|
+
* `string | undefined` so the common narrowed shape
|
|
60
|
+
* `{ Authorization: token ? 'Bearer …' : undefined }` (or a conditional
|
|
61
|
+
* `token ? { Authorization: … } : {}`) assigns cleanly — `undefined`
|
|
62
|
+
* values are filtered before being merged into the request headers. */
|
|
63
|
+
getHeaders?: () => Record<string, string | undefined>
|
|
64
|
+
/** `RequestInit.credentials` mode. Default when no adapter: callers'
|
|
65
|
+
* `init.credentials` or `'same-origin'`. Use `'include'` for cookie
|
|
66
|
+
* auth against a different origin (CORS + `SameSite=None` required). */
|
|
67
|
+
credentials?: RequestCredentials
|
|
68
|
+
/**
|
|
69
|
+
* Optional 401 self-heal. When a request comes back `401`,
|
|
70
|
+
* `embedAuthedFetch` calls this once, and — if it resolves `true` —
|
|
71
|
+
* retries the SAME request exactly once with freshly-recomputed
|
|
72
|
+
* headers (so a rotated bearer from `getHeaders()` is picked up).
|
|
73
|
+
* Resolve `false` to surface the 401 to the caller unchanged.
|
|
74
|
+
*
|
|
75
|
+
* This is the capability the openframe `apiClient` has had all along
|
|
76
|
+
* (refresh-the-access-token-then-retry); registering it here gives the
|
|
77
|
+
* embedded chat/ticket surfaces the same self-healing auth instead of
|
|
78
|
+
* dying on an expired token. Concurrent 401s are de-duplicated by the
|
|
79
|
+
* wrapper, so this fires at most once per refresh cycle even when a
|
|
80
|
+
* stampede of chat requests all expire together — your implementation
|
|
81
|
+
* does NOT need its own in-flight guard (though a token-refresh manager
|
|
82
|
+
* that already dedups is harmless).
|
|
83
|
+
*
|
|
84
|
+
* Keep it idempotent and side-effect-light: on failure the wrapper just
|
|
85
|
+
* returns the original 401 — logout/redirect decisions belong to the
|
|
86
|
+
* host's own auth layer, not to this fetch wrapper.
|
|
87
|
+
*/
|
|
88
|
+
refresh?: () => Promise<boolean>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The registered adapter is parked on `globalThis`, NOT in a module-private
|
|
93
|
+
* `let`. Reason: this lib ships multiple entry points (`/utils`,
|
|
94
|
+
* `/components/chat`, …) and a consumer's bundler can inline this file into
|
|
95
|
+
* more than one chunk — giving each chunk its OWN module scope. If the host
|
|
96
|
+
* calls `setEmbedAuthAdapter` from the `/utils` copy while the chat's
|
|
97
|
+
* `embedAuthedFetch` runs from the `/components/chat` copy, a module-local
|
|
98
|
+
* `let` would be set on one copy and read as `null` on the other (the exact
|
|
99
|
+
* "credentials: same-origin, no Bearer, no refresh" symptom). A single
|
|
100
|
+
* `globalThis` slot is shared across every copy, so registration always
|
|
101
|
+
* reaches the fetch path.
|
|
102
|
+
*/
|
|
103
|
+
const ADAPTER_GLOBAL_KEY = '__embedAuthedFetchAdapter__'
|
|
104
|
+
|
|
105
|
+
function getRegisteredAuthAdapter(): EmbedAuthAdapter | null {
|
|
106
|
+
if (typeof globalThis === 'undefined') return null
|
|
107
|
+
return (globalThis as Record<string, unknown>)[ADAPTER_GLOBAL_KEY] as EmbedAuthAdapter | null ?? null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function storeRegisteredAuthAdapter(adapter: EmbedAuthAdapter | null): void {
|
|
111
|
+
if (typeof globalThis === 'undefined') return
|
|
112
|
+
;(globalThis as Record<string, unknown>)[ADAPTER_GLOBAL_KEY] = adapter
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Register a host-owned auth adapter for `embedAuthedFetch`. Pass `null`
|
|
117
|
+
* to clear (typically on provider unmount).
|
|
118
|
+
*
|
|
119
|
+
* Module-level state — there is one chat panel per app, so a single
|
|
120
|
+
* registration is sufficient. Calling this twice with different non-null
|
|
121
|
+
* adapters replaces the previous one (the most recent registration wins);
|
|
122
|
+
* a `console.warn` flags the overwrite so duplicate-provider mounts get
|
|
123
|
+
* caught in dev.
|
|
124
|
+
*/
|
|
125
|
+
export function setEmbedAuthAdapter(adapter: EmbedAuthAdapter | null): void {
|
|
126
|
+
if (adapter && getRegisteredAuthAdapter() && process.env.NODE_ENV !== 'production') {
|
|
127
|
+
console.warn(
|
|
128
|
+
'[setEmbedAuthAdapter] overwriting a previously-registered auth ' +
|
|
129
|
+
'adapter. Two chat-runtime providers should not coexist — verify ' +
|
|
130
|
+
'mount order and pass `null` from the unmounting provider.',
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
storeRegisteredAuthAdapter(adapter)
|
|
134
|
+
}
|
|
135
|
+
|
|
25
136
|
/**
|
|
26
137
|
* `fetch` wrapper that attaches embed-proxy bearer headers (when
|
|
27
138
|
* present in sessionStorage) and forces `credentials: 'same-origin'`
|
|
@@ -40,13 +151,26 @@ import { applyProxyAuth } from './embed-proxy-auth-storage'
|
|
|
40
151
|
* the current window's origin; cross-origin URLs throw before the bearer
|
|
41
152
|
* leaves the page. This is a defense-in-depth guard for future call sites
|
|
42
153
|
* — there is no legitimate cross-origin use of this fetch wrapper.
|
|
154
|
+
*
|
|
155
|
+
* **401 self-heal:** when a registered adapter supplies `refresh`, a `401`
|
|
156
|
+
* response triggers a single token refresh + retry of the same request
|
|
157
|
+
* (see `EmbedAuthAdapter.refresh`). This is the openframe `apiClient`'s
|
|
158
|
+
* refresh-then-retry behaviour, lifted into the lib so embedded surfaces
|
|
159
|
+
* no longer need a host-side `window.fetch` monkey-patch to survive an
|
|
160
|
+
* expired access token mid-chat. With no adapter (or no `refresh`), the
|
|
161
|
+
* 401 passes straight through unchanged.
|
|
43
162
|
*/
|
|
44
163
|
export function embedAuthedFetch(url: string, init: RequestInit = {}): Promise<Response> {
|
|
164
|
+
// Same-origin guard runs SYNCHRONOUSLY (not awaited inside the async
|
|
165
|
+
// helper below) so a bearer-leaking cross-origin URL throws before any
|
|
166
|
+
// promise is created — callers and tests rely on the synchronous throw.
|
|
45
167
|
assertSameOrigin(url)
|
|
46
168
|
|
|
47
169
|
// `applyProxyAuth` accepts `Record<string, string>`; normalize the
|
|
48
|
-
// caller's headers to that shape. RequestInit accepts
|
|
49
|
-
// which is broader (Headers instance OR array of tuples).
|
|
170
|
+
// caller's headers to that shape ONCE, up front. RequestInit accepts
|
|
171
|
+
// `HeadersInit` which is broader (Headers instance OR array of tuples).
|
|
172
|
+
// We re-derive the per-request headers from this base on every attempt
|
|
173
|
+
// (initial + post-refresh retry) so a rotated bearer is picked up.
|
|
50
174
|
//
|
|
51
175
|
// When the caller passes no headers, fall back to the same default
|
|
52
176
|
// `applyProxyAuth` uses internally — `Content-Type: application/json` —
|
|
@@ -69,14 +193,114 @@ export function embedAuthedFetch(url: string, init: RequestInit = {}): Promise<R
|
|
|
69
193
|
}
|
|
70
194
|
}
|
|
71
195
|
|
|
72
|
-
|
|
73
|
-
|
|
196
|
+
return fetchWithRefresh(url, init, baseHeaders, false)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Single in-flight refresh shared across all concurrent `embedAuthedFetch`
|
|
201
|
+
* callers. A stampede of chat requests that all 401 at the same moment must
|
|
202
|
+
* trigger the adapter's `refresh()` ONCE, not N times — otherwise an
|
|
203
|
+
* expiring session fires a thundering herd of refresh calls at the auth
|
|
204
|
+
* server. Resets to `null` once settled so the next genuine expiry can
|
|
205
|
+
* refresh again.
|
|
206
|
+
*/
|
|
207
|
+
// Stored on `globalThis` rather than a module-local so the "single refresh"
|
|
208
|
+
// guarantee survives module duplication. Bundlers can ship more than one copy
|
|
209
|
+
// of this module (e.g. across chunks or a host + embedded build); a per-module
|
|
210
|
+
// variable would let each copy run its own refresh cycle, re-creating the
|
|
211
|
+
// thundering-herd this dedupe exists to prevent.
|
|
212
|
+
const IN_FLIGHT_REFRESH_GLOBAL_KEY = '__embedAuthedFetchInFlightRefresh__'
|
|
213
|
+
|
|
214
|
+
function getInFlightRefresh(): Promise<boolean> | null {
|
|
215
|
+
if (typeof globalThis === 'undefined') return null
|
|
216
|
+
return (
|
|
217
|
+
((globalThis as Record<string, unknown>)[IN_FLIGHT_REFRESH_GLOBAL_KEY] as
|
|
218
|
+
| Promise<boolean>
|
|
219
|
+
| null
|
|
220
|
+
| undefined) ?? null
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function setInFlightRefresh(refresh: Promise<boolean> | null): void {
|
|
225
|
+
if (typeof globalThis === 'undefined') return
|
|
226
|
+
;(globalThis as Record<string, unknown>)[IN_FLIGHT_REFRESH_GLOBAL_KEY] = refresh
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function dedupedRefresh(): Promise<boolean> {
|
|
230
|
+
const adapter = getRegisteredAuthAdapter()
|
|
231
|
+
if (!adapter?.refresh) return Promise.resolve(false)
|
|
232
|
+
let inFlightRefresh = getInFlightRefresh()
|
|
233
|
+
if (!inFlightRefresh) {
|
|
234
|
+
// Wrap in `Promise.resolve` so an adapter that throws synchronously
|
|
235
|
+
// (rather than rejecting) still funnels through the shared slot and
|
|
236
|
+
// clears it. A rejected refresh is treated as "could not refresh".
|
|
237
|
+
inFlightRefresh = Promise.resolve()
|
|
238
|
+
.then(() => adapter.refresh!())
|
|
239
|
+
.catch(() => false)
|
|
240
|
+
.finally(() => {
|
|
241
|
+
setInFlightRefresh(null)
|
|
242
|
+
})
|
|
243
|
+
setInFlightRefresh(inFlightRefresh)
|
|
244
|
+
}
|
|
245
|
+
return inFlightRefresh
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Core fetch path: merge proxy-auth + adapter headers, issue the request,
|
|
250
|
+
* and — on a `401` with a refresh-capable adapter — refresh once and retry
|
|
251
|
+
* the identical request a single time. Mirrors the openframe `apiClient`'s
|
|
252
|
+
* refresh-then-retry contract (`isRetry` guards against infinite loops).
|
|
253
|
+
*/
|
|
254
|
+
async function fetchWithRefresh(
|
|
255
|
+
url: string,
|
|
256
|
+
init: RequestInit,
|
|
257
|
+
baseHeaders: Record<string, string>,
|
|
258
|
+
isRetry: boolean,
|
|
259
|
+
): Promise<Response> {
|
|
260
|
+
// Re-run the merge each attempt: `applyProxyAuth` reads the latest stored
|
|
261
|
+
// proxy creds and `getHeaders()` reads the latest bearer, so a retry after
|
|
262
|
+
// refresh carries the rotated token rather than the stale one. `{...baseHeaders}`
|
|
263
|
+
// keeps the caller's normalized headers immutable across attempts.
|
|
264
|
+
const { url: authedUrl, headers } = applyProxyAuth(url, { ...baseHeaders })
|
|
265
|
+
|
|
266
|
+
// Host-supplied auth adapter layer. Runs AFTER the proxy-auth merge so
|
|
267
|
+
// adapter headers override both caller and proxy values — the adapter
|
|
268
|
+
// is the host's explicit "this is my auth model" override, intentionally
|
|
269
|
+
// last-writer-wins. When no adapter is registered, this is a zero-cost
|
|
270
|
+
// no-op (object spread of `{}`).
|
|
271
|
+
const adapter = getRegisteredAuthAdapter()
|
|
272
|
+
if (adapter?.getHeaders) {
|
|
273
|
+
// Filter `undefined` values — the adapter type allows them so consumers
|
|
274
|
+
// don't have to narrow `{ Authorization: token ? '…' : undefined }`-shaped
|
|
275
|
+
// returns, but `fetch` headers must be strings.
|
|
276
|
+
for (const [k, v] of Object.entries(adapter.getHeaders())) {
|
|
277
|
+
if (v !== undefined) headers[k] = v
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const credentials = adapter?.credentials ?? init.credentials ?? 'same-origin'
|
|
281
|
+
|
|
282
|
+
const response = await fetch(authedUrl, {
|
|
74
283
|
...init,
|
|
75
284
|
headers,
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
credentials:
|
|
285
|
+
// Default `same-origin` carries Supabase cookies for the MPH proxy-
|
|
286
|
+
// auth model. Hosts on different origins (openframe-frontend ↔
|
|
287
|
+
// openframe gateway) register `credentials: 'include'` via the
|
|
288
|
+
// adapter to make their own cookies travel cross-origin (CORS +
|
|
289
|
+
// `SameSite=None` must be configured server-side for that to work).
|
|
290
|
+
credentials,
|
|
79
291
|
})
|
|
292
|
+
|
|
293
|
+
// 401 self-heal: refresh the token once and retry. Only when an adapter
|
|
294
|
+
// opted into `refresh`, and only on the first attempt — a 401 on the
|
|
295
|
+
// retry means the fresh token is also unauthorized, so surface it.
|
|
296
|
+
if (response.status === 401 && !isRetry && adapter?.refresh) {
|
|
297
|
+
const refreshed = await dedupedRefresh()
|
|
298
|
+
if (refreshed) {
|
|
299
|
+
return fetchWithRefresh(url, init, baseHeaders, true)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return response
|
|
80
304
|
}
|
|
81
305
|
|
|
82
306
|
/**
|
|
@@ -118,6 +342,22 @@ function assertSameOrigin(url: string): void {
|
|
|
118
342
|
)
|
|
119
343
|
}
|
|
120
344
|
if (target.origin !== pageOrigin) {
|
|
345
|
+
// Dev-mode escape hatch — embedded apps (e.g. openframe-frontend)
|
|
346
|
+
// run on a different origin from their gateway during local dev,
|
|
347
|
+
// and forcing a Next.js `rewrites()` workaround is more error-prone
|
|
348
|
+
// than relaxing the guard for the dev build. In production
|
|
349
|
+
// (`NODE_ENV === 'production'`) the guard stays absolute — same
|
|
350
|
+
// defense-in-depth bearer-leak protection as before. The check is
|
|
351
|
+
// baked at build time by Next/webpack/Turbopack so prod bundles
|
|
352
|
+
// contain only the throwing branch (no dev string in the artifact).
|
|
353
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
354
|
+
console.warn(
|
|
355
|
+
`[embedAuthedFetch] cross-origin fetch to ${target.origin} ` +
|
|
356
|
+
`allowed in dev (NODE_ENV !== 'production'). Production builds ` +
|
|
357
|
+
`will reject this — wire a same-origin proxy before shipping.`,
|
|
358
|
+
)
|
|
359
|
+
return
|
|
360
|
+
}
|
|
121
361
|
throw new Error(
|
|
122
362
|
`embedAuthedFetch: refusing cross-origin fetch to ${target.origin} — pass a relative /api/* path instead`,
|
|
123
363
|
)
|
package/src/utils/index.ts
CHANGED
|
@@ -120,7 +120,11 @@ export {
|
|
|
120
120
|
// surfaces from importing a chat-prefixed symbol. Old chat-prefixed
|
|
121
121
|
// aliases are kept as @deprecated re-exports at
|
|
122
122
|
// `components/chat/utils/chat-authed-fetch.ts` + `chat-proxy-auth-storage.ts`.
|
|
123
|
-
export {
|
|
123
|
+
export {
|
|
124
|
+
embedAuthedFetch,
|
|
125
|
+
setEmbedAuthAdapter,
|
|
126
|
+
type EmbedAuthAdapter,
|
|
127
|
+
} from './embed-authed-fetch'
|
|
124
128
|
export {
|
|
125
129
|
type EmbedProxyAuth,
|
|
126
130
|
getEmbedProxyAuth,
|