Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
27 changes: 5 additions & 22 deletions src/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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))
}
Expand Down Expand Up @@ -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
}
14 changes: 14 additions & 0 deletions src/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { setGetResponseStateFn } from './utils/internal'

const originalDeleteProperty = Reflect.deleteProperty
export let getResponseState: Parameters<typeof setGetResponseStateFn>[0] | undefined
Reflect.deleteProperty = (target, prop) => {
if (prop === 'getResponseState') {
getResponseState = (target as { getResponseState: Parameters<typeof setGetResponseStateFn>[0] })
.getResponseState
setGetResponseStateFn(getResponseState)
Reflect.deleteProperty = originalDeleteProperty
}
return originalDeleteProperty(target, prop)
}
Response
39 changes: 39 additions & 0 deletions src/utils/internal.ts
Original file line number Diff line number Diff line change
@@ -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)
}