Created
March 10, 2026 15:03
-
-
Save karthik2804/322d4358138442929f82bad590f4a3d1 to your computer and use it in GitHub Desktop.
Spin JS `response.clone` patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const OriginalResponse = globalThis.Response; | |
| interface PatchedResponseReadOnlyProps { | |
| url?: string; | |
| redirected?: boolean; | |
| type?: ResponseType; | |
| } | |
| class PatchedResponse extends OriginalResponse { | |
| private _clonableStream: BodyInit | null; | |
| private _init: ResponseInit | undefined; | |
| private _readOnlyProps: PatchedResponseReadOnlyProps; | |
| constructor(body: BodyInit | null, init?: ResponseInit, readOnlyProps?: PatchedResponseReadOnlyProps) { | |
| // 1. If body is a stream, tee it immediately | |
| if (body instanceof ReadableStream) { | |
| const [stream1, stream2]: [ReadableStream, ReadableStream] = body.tee(); | |
| super(stream1, init); | |
| this._clonableStream = stream2; | |
| } else { | |
| super(body, init); | |
| this._clonableStream = body; | |
| } | |
| this._init = init; | |
| this._readOnlyProps = readOnlyProps ?? {}; | |
| // Forward read-only properties that can't be set via ResponseInit | |
| if (readOnlyProps) { | |
| const descriptors: PropertyDescriptorMap = {}; | |
| if (readOnlyProps.url !== undefined) { | |
| descriptors.url = { value: readOnlyProps.url, enumerable: true, configurable: true }; | |
| } | |
| if (readOnlyProps.redirected !== undefined) { | |
| descriptors.redirected = { value: readOnlyProps.redirected, enumerable: true, configurable: true }; | |
| } | |
| if (readOnlyProps.type !== undefined) { | |
| descriptors.type = { value: readOnlyProps.type, enumerable: true, configurable: true }; | |
| } | |
| Object.defineProperties(this, descriptors); | |
| } | |
| } | |
| clone(): PatchedResponse { | |
| // 2. When clone() is called, create a new branch from the saved stream | |
| if (this._clonableStream instanceof ReadableStream) { | |
| const [s1, s2]: [ReadableStream, ReadableStream] = this._clonableStream.tee(); | |
| this._clonableStream = s1; // Keep one for the next possible clone() | |
| return new PatchedResponse(s2, this._init, this._readOnlyProps); | |
| } | |
| // For non-stream bodies (strings, blobs), just pass it through | |
| return new PatchedResponse(this._clonableStream, this._init, this._readOnlyProps); | |
| } | |
| } | |
| // 3. Overwrite the global constructor | |
| globalThis.Response = PatchedResponse as unknown as typeof Response; | |
| const originalFetch = globalThis.fetch; | |
| globalThis.fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => { | |
| const res = await originalFetch(...args); | |
| return new PatchedResponse( | |
| res.body, | |
| { | |
| status: res.status, | |
| statusText: res.statusText, | |
| headers: new Headers(res.headers), | |
| }, | |
| { | |
| url: res.url, | |
| redirected: res.redirected, | |
| type: res.type, | |
| }, | |
| ); | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment