fix(frontend): binder le fetch global à globalThis (pairing Firefox) (#13)

Firefox exige que fetch soit appelé sur Window : passer la référence nue
`fetch` en dépendance déclenchait « 'fetch' called on an object that does
not implement interface Window » au pairing. Nouveau helper defaultFetch()
qui binde globalThis.fetch, utilisé par webSession.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 08:47:00 +02:00
parent c9ce3d7c4e
commit de9c90966d
4 changed files with 141 additions and 7 deletions

View File

@ -41,6 +41,29 @@ export type FetchLike = (
text(): Promise<string>;
}>;
/**
* Resolves the default `fetch`, **bound to `globalThis`**.
*
* WHATWG `fetch` is a method of the global object and checks its receiver: once
* stored in a field and called as `this.fetchImpl(…)`, its `this` is the owning
* adapter instance, not `window`. A real browser rejects that — Firefox with
* `TypeError: 'fetch' called on an object that does not implement interface
* Window`, Chrome with "Illegal invocation". jsdom does not enforce the receiver,
* so the unit tests never caught it; the bind is what keeps the fallback callable
* from a field. An *injected* `fetchImpl` is left untouched (a test stub is a
* plain function and needs no receiver).
*
* When there is no global `fetch` (SSR / bare Node), the value is returned as-is
* so the failure still happens at call time, exactly as before, rather than
* throwing during construction.
*/
export function defaultFetch(): FetchLike {
const globalFetch = globalThis.fetch;
return (
typeof globalFetch === "function" ? globalFetch.bind(globalThis) : globalFetch
) as unknown as FetchLike;
}
/** Configuration for the HTTP invoker. */
export interface HttpInvokerConfig {
/** Absolute base URL of the backend, e.g. `https://host:port`. No trailing slash. */
@ -85,10 +108,8 @@ export class HttpInvoker {
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
this.token = config.token;
this.onUnauthorized = config.onUnauthorized;
// `globalThis.fetch` exists in the browser (and jsdom); the cast narrows it
// to the minimal shape used here.
this.fetchImpl =
config.fetchImpl ?? (globalThis.fetch as unknown as FetchLike);
// The global fallback must stay bound to `globalThis` — see `defaultFetch`.
this.fetchImpl = config.fetchImpl ?? defaultFetch();
}
/**