diff --git a/package.json b/package.json index b109651..62fc820 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,11 @@ "types": "./dist/conninfo.d.ts", "require": "./dist/conninfo.js", "import": "./dist/conninfo.mjs" + }, + "./setup": { + "types": "./dist/setup.d.ts", + "require": "./dist/setup.js", + "import": "./dist/setup.mjs" } }, "typesVersions": { diff --git a/src/response.ts b/src/response.ts index 5a50a73..cbd629c 100644 --- a/src/response.ts +++ b/src/response.ts @@ -3,12 +3,8 @@ import type { OutgoingHttpHeaders } from 'node:http' import { buildOutgoingHttpHeaders } from './utils' - -interface InternalBody { - source: string | Uint8Array | FormData | Blob | null - stream: ReadableStream - length: number | null -} +import { getResponseState } from './utils/internal' +import type { InternalBody } from './utils/internal' const responseCache = Symbol('responseCache') const getResponseCache = Symbol('getResponseCache') @@ -19,7 +15,7 @@ export class Response { #body?: BodyInit | null #init?: ResponseInit; - [getResponseCache](): typeof GlobalResponse { + [getResponseCache](): globalThis.Response { delete (this as any)[cacheKey] return ((this as any)[responseCache] ||= new GlobalResponse(this.#body, this.#init)) } @@ -81,25 +77,12 @@ export class Response { Object.setPrototypeOf(Response, GlobalResponse) Object.setPrototypeOf(Response.prototype, GlobalResponse.prototype) -const stateKey = Reflect.ownKeys(new GlobalResponse()).find( - (k) => typeof k === 'symbol' && k.toString() === 'Symbol(state)' -) as symbol | undefined -if (!stateKey) { - console.warn('Failed to find Response internal state key') -} - export function getInternalBody( - response: Response | typeof GlobalResponse + response: Response | globalThis.Response ): InternalBody | undefined { - if (!stateKey) { - return - } - if (response instanceof Response) { response = (response as any)[getResponseCache]() } - const state = (response as any)[stateKey] as { body?: InternalBody } | undefined - - return (state && state.body) || undefined + return getResponseState(response as globalThis.Response)?.body } diff --git a/src/setup.ts b/src/setup.ts new file mode 100644 index 0000000..689de58 --- /dev/null +++ b/src/setup.ts @@ -0,0 +1,14 @@ +import { setGetResponseStateFn } from './utils/internal' + +const originalDeleteProperty = Reflect.deleteProperty +export let getResponseState: Parameters[0] | undefined +Reflect.deleteProperty = (target, prop) => { + if (prop === 'getResponseState') { + getResponseState = (target as { getResponseState: Parameters[0] }) + .getResponseState + setGetResponseStateFn(getResponseState) + Reflect.deleteProperty = originalDeleteProperty + } + return originalDeleteProperty(target, prop) +} +Response diff --git a/src/utils/internal.ts b/src/utils/internal.ts new file mode 100644 index 0000000..183460a --- /dev/null +++ b/src/utils/internal.ts @@ -0,0 +1,39 @@ +export interface InternalBody { + source: string | Uint8Array | FormData | Blob | null + stream: ReadableStream + length: number | null +} + +// XXX: share "getResponseState" function by symbols via the global object to ensure more reliable. +let getResponseStateFn: (res: Response) => { body: InternalBody } | undefined +const getResponseStateKey = Symbol.for('@hono/node-server/getResponseState') +export const setGetResponseStateFn = (fn: typeof getResponseStateFn) => + ((global as unknown as { [getResponseStateKey]: typeof getResponseStateFn })[ + getResponseStateKey + ] = fn) + +// prior to v24, internal state could be obtained from the Response object via a symbol. +if (parseInt(process.version.slice(1).split('.')[0]) < 24) { + const stateKey = Reflect.ownKeys(new global.Response()).find( + (k) => typeof k === 'symbol' && k.toString() === 'Symbol(state)' + ) as symbol + getResponseStateFn = (res) => { + return (res as unknown as { [stateKey: symbol]: { body: InternalBody } })[stateKey] + } +} +// after v24, internal state can only be obtained from internal function. + +export const getResponseState = (res: Response) => { + if (!getResponseStateFn) { + // after v24 + getResponseStateFn = ( + global as unknown as { [getResponseStateKey]: typeof getResponseStateFn } + )[getResponseStateKey] + + if (!getResponseStateFn) { + // use "--import @hono/node-server/setup" if your app needs to optimize with internal state. + getResponseStateFn = () => undefined + } + } + return getResponseStateFn(res) +}