diff --git a/src/fetch.ts b/src/fetch.ts index 10c91583..4811f892 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -109,6 +109,12 @@ export function createFetch(globalOptions: CreateFetchOptions = {}): $Fetch { if (context.options.onRequest) { await callHooks(context, context.options.onRequest); + // A hook may reassign `headers` to a plain object or array (a valid + // `HeadersInit`), so normalize it back to a `Headers` instance before the + // code below calls `.get()`/`.set()`/`.has()` on it. + if (!(context.options.headers instanceof Headers)) { + context.options.headers = new Headers(context.options.headers || {}); + } } if (typeof context.request === "string") { @@ -143,8 +149,6 @@ export function createFetch(globalOptions: CreateFetchOptions = {}): $Fetch { // Set Content-Type and Accept headers to application/json by default // for JSON serializable request bodies. - // Pass empty object as older browsers don't support undefined. - context.options.headers = new Headers(context.options.headers || {}); if (!contentType) { context.options.headers.set("content-type", "application/json"); } diff --git a/test/index.test.ts b/test/index.test.ts index 5ac20b07..15520cc3 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -397,6 +397,32 @@ describe("ofetch", () => { ).toMatchObject({ foo: "2", bar: "3" }); }); + it("normalizes headers reassigned in onRequest hook", async () => { + // A `Headers` instance reassigned in the hook is sent as-is. + expect( + await $fetch(getURL("/echo"), { + method: "POST", + body: { num: 42 }, + onRequest(ctx) { + ctx.options.headers = new Headers({ "x-foo": "bar" }); + }, + }).then((r) => r.headers) + ).toMatchObject({ "x-foo": "bar" }); + + // A plain object reassigned in the hook is normalized back to `Headers` + // instead of throwing `headers.get is not a function` before the request. + expect( + await $fetch(getURL("/echo"), { + method: "POST", + body: { num: 42 }, + onRequest(ctx) { + // @ts-expect-error backwards compatibility for object headers + ctx.options.headers = { "x-foo": "bar" }; + }, + }).then((r) => r.headers) + ).toMatchObject({ "x-foo": "bar" }); + }); + it("hook errors", async () => { // onRequest await expect(