diff --git a/.changeset/dull-tomatoes-wink.md b/.changeset/dull-tomatoes-wink.md new file mode 100644 index 00000000000..51954f41618 --- /dev/null +++ b/.changeset/dull-tomatoes-wink.md @@ -0,0 +1,6 @@ +--- +"@smithy/fetch-http-handler": patch +"@smithy/types": patch +--- + +add requestInit options to fetch diff --git a/packages/fetch-http-handler/src/fetch-http-handler.spec.ts b/packages/fetch-http-handler/src/fetch-http-handler.spec.ts index fbb9e960390..1ef8fcced37 100644 --- a/packages/fetch-http-handler/src/fetch-http-handler.spec.ts +++ b/packages/fetch-http-handler/src/fetch-http-handler.spec.ts @@ -9,6 +9,7 @@ let timeoutSpy: jest.SpyInstance; (global as any).Request = mockRequest; (global as any).Headers = jest.fn(); +const globalFetch = global.fetch; describe(FetchHttpHandler.name, () => { beforeEach(() => { @@ -23,6 +24,10 @@ describe(FetchHttpHandler.name, () => { } }); + afterAll(() => { + global.fetch = globalFetch; + }); + it("makes requests using fetch", async () => { const mockResponse = { headers: { @@ -454,6 +459,73 @@ describe(FetchHttpHandler.name, () => { }); }); + describe("custom requestInit", () => { + it("should allow setting cache requestInit", async () => { + const mockResponse = { + headers: { + entries() { + return []; + }, + }, + blob: jest.fn().mockResolvedValue(new Blob()), + }; + const mockFetch = jest.fn().mockResolvedValue(mockResponse); + (global as any).fetch = mockFetch; + + const fetchHttpHandler = new FetchHttpHandler({ + cache: "no-store", + }); + + await fetchHttpHandler.handle({} as any, {}); + + expect(mockRequest.mock.calls[0][1].cache).toBe("no-store"); + }); + + it("should allow setting custom requestInit", async () => { + const mockResponse = { + headers: { + entries() { + return []; + }, + }, + blob: jest.fn().mockResolvedValue(new Blob()), + }; + const mockFetch = jest.fn().mockResolvedValue(mockResponse); + (global as any).fetch = mockFetch; + + const fetchHttpHandler = new FetchHttpHandler({ + requestInit(req) { + return { + referrer: "me", + cache: "reload", + headers: { + a: "a", + b: req.headers.b, + }, + }; + }, + }); + + await fetchHttpHandler.handle( + { + headers: { + b: "b", + }, + } as any, + {} + ); + + expect(mockRequest.mock.calls[0][1]).toEqual({ + referrer: "me", + cache: "reload", + headers: { + a: "a", + b: "b", + }, + }); + }); + }); + // The Blob implementation does not implement Blob.text, so we deal with it here. async function blobToText(blob: Blob): Promise { const reader = new FileReader(); diff --git a/packages/fetch-http-handler/src/fetch-http-handler.ts b/packages/fetch-http-handler/src/fetch-http-handler.ts index 81995ce978d..2d24b4dae5c 100644 --- a/packages/fetch-http-handler/src/fetch-http-handler.ts +++ b/packages/fetch-http-handler/src/fetch-http-handler.ts @@ -112,7 +112,9 @@ export class FetchHttpHandler implements HttpHandler { headers: new Headers(request.headers), method: method, credentials, + cache: this.config!.cache ?? "default", }; + if (body) { requestOptions.duplex = "half"; } @@ -127,6 +129,10 @@ export class FetchHttpHandler implements HttpHandler { requestOptions.keepalive = keepAlive; } + if (typeof this.config.requestInit === "function") { + Object.assign(requestOptions, this.config.requestInit(request)); + } + let removeSignalEventListener = () => {}; const fetchRequest = new Request(url, requestOptions); diff --git a/packages/types/src/http/httpHandlerInitialization.ts b/packages/types/src/http/httpHandlerInitialization.ts index b14a9c36154..4bc56843c62 100644 --- a/packages/types/src/http/httpHandlerInitialization.ts +++ b/packages/types/src/http/httpHandlerInitialization.ts @@ -1,6 +1,7 @@ import type { Agent as hAgent, AgentOptions as hAgentOptions } from "http"; import type { Agent as hsAgent, AgentOptions as hsAgentOptions } from "https"; +import { HttpRequest as IHttpRequest } from "../http"; import { Logger } from "../logger"; /** @@ -97,4 +98,30 @@ export interface FetchHttpHandlerOptions { * @see https://developer.mozilla.org/en-US/docs/Web/API/Request/credentials */ credentials?: "include" | "omit" | "same-origin" | undefined | string; + + /** + * Cache settings for fetch. + * @see https://developer.mozilla.org/en-US/docs/Web/API/Request/cache + */ + cache?: "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload"; + + /** + * An optional function that produces additional RequestInit + * parameters for each httpRequest. + * + * This is applied last via merging with Object.assign() and overwrites other values + * set from other sources. + * + * @example + * ```js + * new Client({ + * requestHandler: { + * requestInit(httpRequest) { + * return { cache: "no-store" }; + * } + * } + * }); + * ``` + */ + requestInit?: (httpRequest: IHttpRequest) => RequestInit; }