From 6e3d33777ae9bc3eb415fe581687ed7a2e383404 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 11 Feb 2026 01:23:19 +0800 Subject: [PATCH 01/24] refactor: simplify code --- src/middleware/web-outgoing.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index 168de65..d1b17d9 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -116,13 +116,12 @@ export const writeHeaders = defineProxyOutgoingMiddleware((req, res, proxyRes, o */ export const writeStatusCode = defineProxyOutgoingMiddleware((req, res, proxyRes) => { // From Node.js docs: response.writeHead(statusCode[, statusMessage][, headers]) + + // @ts-expect-error + res.statusCode = proxyRes.statusCode; + if (proxyRes.statusMessage) { - // @ts-expect-error - res.statusCode = proxyRes.statusCode; res.statusMessage = proxyRes.statusMessage; - } else { - // @ts-expect-error - res.statusCode = proxyRes.statusCode; } }); From 3d54aaf066f0d5d02e8b086987111169477025ed Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 11 Feb 2026 01:24:22 +0800 Subject: [PATCH 02/24] feat: handle connection header --- src/middleware/web-outgoing.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index d1b17d9..586c2cc 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -15,12 +15,17 @@ export const removeChunked = defineProxyOutgoingMiddleware((req, res, proxyRes) /** * If is a HTTP 1.0 request, set the correct connection header * or if connection header not present, then use `keep-alive` + * + * If is a HTTP/2 request, remove connection header no matter what, + * this avoids sending connection header to the underlying http2 client */ export const setConnection = defineProxyOutgoingMiddleware((req, res, proxyRes) => { if (req.httpVersion === "1.0") { proxyRes.headers.connection = req.headers.connection || "close"; - } else if (req.httpVersion !== "2.0" && !proxyRes.headers.connection) { + } else if (req.httpVersionMajor < 2 && !proxyRes.headers.connection) { proxyRes.headers.connection = req.headers.connection || "keep-alive"; + } else if (req.httpVersionMajor >= 2) { + delete proxyRes.headers.connection; } }); From fe5c360763cf83518466455257959bb2aa265c32 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 11 Feb 2026 01:29:09 +0800 Subject: [PATCH 03/24] feat: ignore conflicting http/2 pseudo headers --- src/_utils.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/_utils.ts b/src/_utils.ts index f378b67..7208eda 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -11,6 +11,20 @@ const upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i; */ export const isSSL = /^https|wss/; +/** + * Node.js HTTP/2 accepts pseudo headers and it may conflict + * with request options. + * + * Let's just blacklist those potential conflicting pseudo + * headers. + */ +const HTTP2_HEADER_BLACKLIST = [ + ':method', + ':path', + ':scheme', + ':authority', +] + /** * Copies the right headers from `options` and `req` to * `outgoing` which is then used to fire the proxied @@ -68,6 +82,13 @@ export function setupOutgoing( outgoing.headers = { ...outgoing.headers, ...options.headers }; } + if (req.httpVersionMajor > 1) { + // ignore potential conflicting HTTP/2 pseudo headers + for (const header of HTTP2_HEADER_BLACKLIST) { + delete outgoing.headers[header]; + } + } + if (options.auth) { outgoing.auth = options.auth; } From 29bb3a07cee6d8f5e617962ac2dd828e3d9adef9 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Thu, 12 Feb 2026 18:25:40 +0800 Subject: [PATCH 04/24] feat: ignore statusMessage on HTTP/2 --- src/middleware/web-outgoing.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index 586c2cc..565fff2 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -125,7 +125,11 @@ export const writeStatusCode = defineProxyOutgoingMiddleware((req, res, proxyRes // @ts-expect-error res.statusCode = proxyRes.statusCode; - if (proxyRes.statusMessage) { + if ( + proxyRes.statusMessage && + // Only HTTP/1.0 and HTTP/1.1 support statusMessage + req.httpVersionMajor < 2 + ) { res.statusMessage = proxyRes.statusMessage; } }); From 4f850588cd29e329db8eecee74be74848a409443 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Thu, 12 Feb 2026 17:56:53 +0800 Subject: [PATCH 05/24] feat: implement HTTP/2 listener --- src/_utils.ts | 16 ++++---- src/middleware/_utils.ts | 17 +++++---- src/server.ts | 56 +++++++++++++++++++--------- src/types.ts | 6 ++- test/middleware/web-outgoing.test.ts | 36 ++++++++++++++++++ 5 files changed, 95 insertions(+), 36 deletions(-) diff --git a/src/_utils.ts b/src/_utils.ts index 7208eda..0cbc177 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -3,6 +3,7 @@ import httpsNative from "node:https"; import net from "node:net"; import type tls from "node:tls"; import type { ProxyAddr, ProxyServerOptions, ProxyTarget, ProxyTargetDetailed } from "./types.ts"; +import type { Http2ServerRequest } from "node:http2"; const upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i; @@ -18,12 +19,7 @@ export const isSSL = /^https|wss/; * Let's just blacklist those potential conflicting pseudo * headers. */ -const HTTP2_HEADER_BLACKLIST = [ - ':method', - ':path', - ':scheme', - ':authority', -] +const HTTP2_HEADER_BLACKLIST = [":method", ":path", ":scheme", ":authority"]; /** * Copies the right headers from `options` and `req` to @@ -52,7 +48,7 @@ export function setupOutgoing( ca?: string; method?: string; }, - req: httpNative.IncomingMessage, + req: httpNative.IncomingMessage | Http2ServerRequest, forward?: "forward" | "target", ): httpNative.RequestOptions | httpsNative.RequestOptions { outgoing.port = @@ -202,7 +198,7 @@ export function setupSocket(socket: net.Socket): net.Socket { * * @api private */ -export function getPort(req: httpNative.IncomingMessage): string { +export function getPort(req: httpNative.IncomingMessage | Http2ServerRequest): string { const res = req.headers.host ? req.headers.host.match(/:(\d+)/) : ""; if (res) { return res[1]!; @@ -219,7 +215,9 @@ export function getPort(req: httpNative.IncomingMessage): string { * * @api private */ -export function hasEncryptedConnection(req: httpNative.IncomingMessage): boolean { +export function hasEncryptedConnection( + req: httpNative.IncomingMessage | Http2ServerRequest, +): boolean { return Boolean( // req.connection.pair probably does not exist anymore (req.connection as tls.TLSSocket).encrypted || (req.connection as any).pair, diff --git a/src/middleware/_utils.ts b/src/middleware/_utils.ts index 3704c58..66b5ac6 100644 --- a/src/middleware/_utils.ts +++ b/src/middleware/_utils.ts @@ -2,25 +2,26 @@ import type { IncomingMessage, ServerResponse } from "node:http"; import type { Socket } from "node:net"; import type { ProxyServer } from "../server.ts"; import type { ProxyServerOptions, ProxyTargetDetailed } from "../types.ts"; +import type { Http2ServerRequest, Http2ServerResponse } from "node:http2"; export type ResOfType = T extends "ws" ? T extends "web" - ? ServerResponse | Socket + ? ServerResponse | Http2ServerResponse | Socket : Socket : T extends "web" - ? ServerResponse + ? ServerResponse | Http2ServerResponse : never; -export type ProxyMiddleware = ( - req: IncomingMessage, +export type ProxyMiddleware = ( + req: IncomingMessage | Http2ServerRequest, res: T, opts: ProxyServerOptions & { target: URL | ProxyTargetDetailed; forward: URL; }, - server: ProxyServer, + server: ProxyServer, head?: Buffer, - callback?: (err: any, req: IncomingMessage, socket: T, url?: any) => void, + callback?: (err: any, req: IncomingMessage | Http2ServerRequest, socket: T, url?: any) => void, ) => void | true; export function defineProxyMiddleware( @@ -30,8 +31,8 @@ export function defineProxyMiddleware { error: [err: Error, req?: Req, res?: Res | net.Socket, target?: URL | ProxyTarget]; start: [req: Req, res: Res, target: URL | ProxyTarget]; @@ -32,22 +33,31 @@ export interface ProxyServerEventMap< // eslint-disable-next-line unicorn/prefer-event-target export class ProxyServer< - Req extends http.IncomingMessage = http.IncomingMessage, - Res extends http.ServerResponse = http.ServerResponse, + Req extends http.IncomingMessage | http2.Http2ServerRequest = http.IncomingMessage, + Res extends http.ServerResponse | http2.Http2ServerResponse = http.ServerResponse, > extends EventEmitter> { - private _server?: http.Server | https.Server; + // we use http2.Http2Server to handle HTTP/1.1 HTTPS as well (with allowHTTP1 enabled) + private _server?: http.Server | http2.Http2SecureServer; _webPasses: ProxyMiddleware[] = [...webIncomingMiddleware]; _wsPasses: ProxyMiddleware[] = [...websocketIncomingMiddleware]; options: ProxyServerOptions; - web: ( - req: http.IncomingMessage, - res: http.ServerResponse, - opts?: ProxyServerOptions, - head?: any, - ) => Promise; + web: { + ( + req: http.IncomingMessage, + res: http.ServerResponse, + opts?: ProxyServerOptions, + head?: any, + ): Promise; + ( + req: http2.Http2ServerRequest, + res: http2.Http2ServerResponse, + opts?: ProxyServerOptions, + head?: any, + ): Promise; + }; ws: ( req: http.IncomingMessage, @@ -76,17 +86,27 @@ export class ProxyServer< * @param hostname - The hostname to listen on */ listen(port: number, hostname?: string) { - const closure = (req: http.IncomingMessage, res: http.ServerResponse) => { - this.web(req, res); + interface ListenerCallback { + ( + req: http.IncomingMessage | http2.Http2ServerRequest, + res: http.ServerResponse | http2.Http2ServerResponse, + ): Promise; + } + + const closure: ListenerCallback = (req, res) => { + return this.web(req as any, res as any); }; - this._server = this.options.ssl - ? https.createServer(this.options.ssl, closure) - : http.createServer(closure); + if (this.options.http2) { + this._server = http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, closure); + } else if (this.options.ssl) { + this._server = https.createServer(this.options.ssl, closure); + } else { + this._server = http.createServer(closure); + } if (this.options.ws) { this._server.on("upgrade", (req, socket, head) => { - // @ts-expect-error this.ws(req, socket, head).catch(() => {}); }); } @@ -185,7 +205,7 @@ function _createProxyFn(type: Type, server: ProxyServ type Res = ResOfType; return function ( this: ProxyServer, - req: http.IncomingMessage, + req: http.IncomingMessage | http2.Http2ServerRequest, res: Res, opts?: ProxyServerOptions, head?: any, diff --git a/src/types.ts b/src/types.ts index 5146b00..9bf4f16 100644 --- a/src/types.ts +++ b/src/types.ts @@ -29,7 +29,11 @@ export interface ProxyServerOptions { forward?: ProxyTarget; /** Object to be passed to http(s).request. */ agent?: any; - /** Object to be passed to https.createServer(). */ + /** Enable HTTP/2 listener, default is `false` */ + http2?: boolean; + /** Object to be passed to https.createServer() + * or http2.createSecureServer() if the `http2` option is enabled + */ ssl?: any; /** If you want to proxy websockets. */ ws?: boolean; diff --git a/test/middleware/web-outgoing.test.ts b/test/middleware/web-outgoing.test.ts index 593de73..0c5905b 100644 --- a/test/middleware/web-outgoing.test.ts +++ b/test/middleware/web-outgoing.test.ts @@ -274,6 +274,42 @@ describe("middleware:web-outgoing", () => { expect(proxyRes.headers.connection).to.eql("hola"); }); + it("set the right connection (HTTP/1.1) - req.connection", () => { + const proxyRes = { headers: {} as any }; + webOutgoing.setConnection( + { + httpVersion: "1.0", + httpVersionMajor: 1, + headers: { + connection: "hola", + }, + } as any, + {} as any, + proxyRes as any, + {} as any, + ); + + expect(proxyRes.headers.connection).to.eql("hola"); + }); + + it("set the right connection (HTTP/2) - req.connection", () => { + const proxyRes = { headers: {} as any }; + webOutgoing.setConnection( + { + httpVersion: "2.0", + httpVersionMajor: 2, + headers: { + connection: "hola", + }, + } as any, + {} as any, + proxyRes as any, + {} as any, + ); + + expect(proxyRes.headers.connection).to.eql(undefined); + }); + it("set the right connection - `keep-alive`", () => { const proxyRes = stubIncomingMessage({ headers: {} }); webOutgoing.setConnection( From 4e514dfd523e07882965511321cecbac0e99e7bc Mon Sep 17 00:00:00 2001 From: SukkaW Date: Thu, 12 Feb 2026 18:48:14 +0800 Subject: [PATCH 06/24] refactor: proper determine connection is encrypted --- src/_utils.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/_utils.ts b/src/_utils.ts index 0cbc177..527a2b2 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -218,10 +218,26 @@ export function getPort(req: httpNative.IncomingMessage | Http2ServerRequest): s export function hasEncryptedConnection( req: httpNative.IncomingMessage | Http2ServerRequest, ): boolean { - return Boolean( - // req.connection.pair probably does not exist anymore - (req.connection as tls.TLSSocket).encrypted || (req.connection as any).pair, - ); + // req.connection.pair probably does not exist anymore + if ("connection" in req) { + if ("encrypted" in req.connection) { + return req.connection.encrypted; + } + if ("pair" in req.connection) { + return !!req.connection.pair; + } + } + // Since Node.js v16 we now have req.socket + if ("socket" in req) { + if ("encrypted" in req.socket) { + return req.socket.encrypted; + } + if ("pair" in req.socket) { + return !!req.socket.pair; + } + } + + return false; } /** From 811a25d0b55bea74a33dc1bea92b66c32a63002f Mon Sep 17 00:00:00 2001 From: SukkaW Date: Thu, 12 Feb 2026 19:28:02 +0800 Subject: [PATCH 07/24] refactor: handle `:authority` and `transfer-encoding` --- src/_utils.ts | 8 +++++++- src/middleware/web-incoming.ts | 3 ++- src/middleware/web-outgoing.ts | 11 ++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/_utils.ts b/src/_utils.ts index 527a2b2..e1a4ed5 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -74,6 +74,11 @@ export function setupOutgoing( outgoing.method = options.method || req.method; outgoing.headers = { ...req.headers }; + // before clean up HTTP/2 blacklist header, we might wanna override host first + if (req.headers?.[":authority"]) { + outgoing.headers.host = req.headers[":authority"] as string; + } + if (options.headers) { outgoing.headers = { ...outgoing.headers, ...options.headers }; } @@ -199,7 +204,8 @@ export function setupSocket(socket: net.Socket): net.Socket { * @api private */ export function getPort(req: httpNative.IncomingMessage | Http2ServerRequest): string { - const res = req.headers.host ? req.headers.host.match(/:(\d+)/) : ""; + const hostHeader = (req.headers[":authority"] as string | undefined) || req.headers.host; + const res = hostHeader ? hostHeader.match(/:(\d+)/) : ""; if (res) { return res[1]!; } diff --git a/src/middleware/web-incoming.ts b/src/middleware/web-incoming.ts index b82a142..6b38539 100644 --- a/src/middleware/web-incoming.ts +++ b/src/middleware/web-incoming.ts @@ -52,7 +52,8 @@ export const XHeaders = defineProxyMiddleware((req, res, options) => { values[header]; } - req.headers["x-forwarded-host"] = req.headers["x-forwarded-host"] || req.headers.host || ""; + req.headers["x-forwarded-host"] = + req.headers["x-forwarded-host"] || req.headers[":authority"] || req.headers.host || ""; }); /** diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index 565fff2..b7aae38 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -7,7 +7,8 @@ const redirectRegex = /^201|30([1278])$/; * If is a HTTP 1.0 request, remove chunk headers */ export const removeChunked = defineProxyOutgoingMiddleware((req, res, proxyRes) => { - if (req.httpVersion === "1.0") { + // HTTP/1.0 and HTTP/2 do not have transfer-encoding: chunked + if (req.httpVersion !== "1.1") { delete proxyRes.headers["transfer-encoding"]; } }); @@ -47,8 +48,12 @@ export const setRedirectHostRewrite = defineProxyOutgoingMiddleware( if (options.hostRewrite) { u.host = options.hostRewrite; - } else if (options.autoRewrite && req.headers.host) { - u.host = req.headers.host; + } else if (options.autoRewrite) { + if (req.headers[":authority"]) { + u.host = req.headers[":authority"] as string; + } else if (req.headers.host) { + u.host = req.headers.host; + } } if (options.protocolRewrite) { u.protocol = options.protocolRewrite; From 45d3a020ecbfd6333dc02eb82ff364603050b2b0 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Thu, 12 Feb 2026 19:48:27 +0800 Subject: [PATCH 08/24] test: add http2 listener test --- package.json | 1 + pnpm-lock.yaml | 9 +++ test/http2-proxy.test.ts | 144 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 test/http2-proxy.test.ts diff --git a/package.json b/package.json index 8139186..696e1a8 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "sse": "^0.0.8", "typescript": "^5.9.3", "unbuild": "^3.6.1", + "undici": "^7.21.0", "vitest": "^4.0.18", "ws": "^8.19.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e120945..6050230 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: unbuild: specifier: ^3.6.1 version: 3.6.1(typescript@5.9.3) + undici: + specifier: ^7.21.0 + version: 7.21.0 vitest: specifier: ^4.0.18 version: 4.0.18(@types/node@25.2.3)(jiti@2.6.1) @@ -2439,6 +2442,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici@7.21.0: + resolution: {integrity: sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==} + engines: {node: '>=20.18.1'} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} @@ -4967,6 +4974,8 @@ snapshots: undici-types@7.16.0: {} + undici@7.21.0: {} + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 diff --git a/test/http2-proxy.test.ts b/test/http2-proxy.test.ts new file mode 100644 index 0000000..1b8b92d --- /dev/null +++ b/test/http2-proxy.test.ts @@ -0,0 +1,144 @@ +import * as http from "node:http"; +import * as https from "node:https"; +import * as httpProxy from "../src"; +import * as path from "node:path"; +import * as fs from "node:fs"; +import { describe, it, expect, beforeAll, afterAll } from "vitest"; + +import { Agent, fetch } from "undici"; + +let initialPort = 4096; +const getPort = () => initialPort++; + +const http1Agent = new Agent({ + allowH2: false, + connect: { + // Allow to use SSL self signed + rejectUnauthorized: false, + }, +}); +const http2Agent = new Agent({ + allowH2: true, + connect: { + // Allow to use SSL self signed + rejectUnauthorized: false, + }, +}); + +describe("http/2 listener", () => { + describe("http2 -> http", () => { + const httpPort = getPort(); + const proxyPort = getPort(); + + const source = http + .createServer((_req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.write("hello httpxy\n"); + res.end(); + }) + .listen(httpPort); + + const proxy = httpProxy + .createProxyServer({ + target: { + host: "localhost", + port: httpPort, + }, + ssl: { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + }, + http2: true, + // Allow to use SSL self signed + secure: false, + }) + .listen(proxyPort); + + it("target http server should be working", async () => { + const r = await ( + await fetch(`http://localhost:${httpPort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).toContain("hello httpxy"); + }); + + it("fetch proxy server over http1", async () => { + const r = await ( + await fetch(`https://localhost:${proxyPort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).toContain("hello httpxy"); + }); + + it("fetch proxy server over http2", async () => { + const resp = await fetch(`https://localhost:${proxyPort}`, { dispatcher: http2Agent }); + const r = await resp.text(); + expect(r).toContain("hello httpxy"); + }); + + afterAll(async () => { + // cleans up + await new Promise((resolve) => proxy.close(resolve)); + source.close(); + }); + }); + + // TODO: fix this test + describe.skip("http2 -> https", () => { + const httpsPort = getPort(); + const proxyPort = getPort(); + + const source = https + .createServer( + { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + }, + function (req, res) { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("hello httpxy"); + }, + ) + .listen(httpsPort); + + const proxy = httpProxy + .createProxyServer({ + target: { + host: "localhost", + port: httpsPort, + }, + ssl: { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + }, + http2: true, + // Allow to use SSL self signed + secure: false, + }) + .listen(proxyPort); + + it("target https server should be working", async () => { + const r = await ( + await fetch(`https://localhost:${httpsPort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).toContain("hello httpxy"); + }); + + it("fetch proxy server over http1", async () => { + const r = await ( + await fetch(`https://localhost:${proxyPort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).toContain("hello httpxy"); + }); + + it("fetch proxy server over http2", async () => { + const resp = await fetch(`https://localhost:${proxyPort}`, { dispatcher: http2Agent }); + const r = await resp.text(); + expect(r).toContain("hello httpxy"); + }); + + afterAll(async () => { + // cleans up + await new Promise((resolve) => proxy.close(resolve)); + source.close(); + }); + }); +}); From 9a92849ed756afa20da1d24e50978b2fff8d5a41 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 20 Feb 2026 18:09:41 +0800 Subject: [PATCH 09/24] refactor: types and test --- src/server.ts | 42 ++++++++++------------------ test/_stubs.ts | 11 ++++++-- test/http2-proxy.test.ts | 2 +- test/middleware/web-outgoing.test.ts | 2 ++ 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/server.ts b/src/server.ts index 131bf50..bb2f20a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -44,27 +44,9 @@ export class ProxyServer< options: ProxyServerOptions; - web: { - ( - req: http.IncomingMessage, - res: http.ServerResponse, - opts?: ProxyServerOptions, - head?: any, - ): Promise; - ( - req: http2.Http2ServerRequest, - res: http2.Http2ServerResponse, - opts?: ProxyServerOptions, - head?: any, - ): Promise; - }; + web: (req: Req, res: Res, opts?: ProxyServerOptions, head?: any) => Promise; - ws: ( - req: http.IncomingMessage, - socket: net.Socket, - opts: ProxyServerOptions, - head?: any, - ) => Promise; + ws: (req: Req, socket: net.Socket, opts: ProxyServerOptions, head?: any) => Promise; /** * Creates the proxy server with specified options. @@ -201,12 +183,15 @@ export function createProxyServer(options: ProxyServerOptions = {}) { // --- Internal --- -function _createProxyFn(type: Type, server: ProxyServer) { - type Res = ResOfType; +function _createProxyFn< + Type extends "web" | "ws", + ProxyServerReq extends http.IncomingMessage | http2.Http2ServerRequest, + ProxyServerRes extends http.ServerResponse | http2.Http2ServerResponse, +>(type: Type, server: ProxyServer) { return function ( - this: ProxyServer, - req: http.IncomingMessage | http2.Http2ServerRequest, - res: Res, + this: ProxyServer, + req: ProxyServerReq, + res: ResOfType, opts?: ProxyServerOptions, head?: any, ): Promise { @@ -242,11 +227,14 @@ function _createProxyFn(type: Type, server: ProxyServ req, res, requestOptions as ProxyServerOptions & { target: URL; forward: URL }, - server, + server as ProxyServer< + http.IncomingMessage | http2.Http2ServerRequest, + http.ServerResponse | http2.Http2ServerResponse + >, head, (error) => { if (server.listenerCount("error") > 0) { - server.emit("error", error, req, res); + server.emit("error", error, req, res as ProxyServerRes | net.Socket); _resolve(); } else { _reject(error); diff --git a/test/_stubs.ts b/test/_stubs.ts index 2623a4f..5f1ed01 100644 --- a/test/_stubs.ts +++ b/test/_stubs.ts @@ -22,7 +22,14 @@ export function createOutgoing(): OutgoingOptions { // --- IncomingMessage stubs --- export function stubIncomingMessage(overrides: Record = {}): IncomingMessage { - return { method: "GET", url: "/", headers: {}, ...overrides } as unknown as IncomingMessage; + return { + method: "GET", + url: "/", + headers: {}, + httpVersion: "1.1", + httpVersionMajor: 1, + ...overrides, + } as unknown as IncomingMessage; } // --- ServerResponse stub --- @@ -50,6 +57,6 @@ export function stubMiddlewareOptions(overrides: Record = {}): // --- ProxyServer stub --- -export function stubProxyServer(overrides: Record = {}): ProxyServer { +export function stubProxyServer(overrides: Record = {}): ProxyServer { return overrides as unknown as ProxyServer; } diff --git a/test/http2-proxy.test.ts b/test/http2-proxy.test.ts index 1b8b92d..76a9564 100644 --- a/test/http2-proxy.test.ts +++ b/test/http2-proxy.test.ts @@ -1,6 +1,6 @@ import * as http from "node:http"; import * as https from "node:https"; -import * as httpProxy from "../src"; +import * as httpProxy from "../src/index.ts"; import * as path from "node:path"; import * as fs from "node:fs"; import { describe, it, expect, beforeAll, afterAll } from "vitest"; diff --git a/test/middleware/web-outgoing.test.ts b/test/middleware/web-outgoing.test.ts index 0c5905b..083a0fa 100644 --- a/test/middleware/web-outgoing.test.ts +++ b/test/middleware/web-outgoing.test.ts @@ -330,6 +330,7 @@ describe("middleware:web-outgoing", () => { webOutgoing.setConnection( stubIncomingMessage({ httpVersion: "2.0", + httpVersionMajor: 2, headers: { connection: "namstey" }, }), stubServerResponse(), @@ -345,6 +346,7 @@ describe("middleware:web-outgoing", () => { webOutgoing.setConnection( stubIncomingMessage({ httpVersion: "2.0", + httpVersionMajor: 2, headers: {}, }), stubServerResponse(), From 9d401acaa6154331c9414159dfadb6c245fea148 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 20 Feb 2026 18:15:57 +0800 Subject: [PATCH 10/24] test: update test to re-use util --- test/http2-proxy.test.ts | 178 ++++++++++++++++++++------------------- test/https-proxy.test.ts | 6 +- 2 files changed, 96 insertions(+), 88 deletions(-) diff --git a/test/http2-proxy.test.ts b/test/http2-proxy.test.ts index 76a9564..37488fc 100644 --- a/test/http2-proxy.test.ts +++ b/test/http2-proxy.test.ts @@ -3,12 +3,11 @@ import * as https from "node:https"; import * as httpProxy from "../src/index.ts"; import * as path from "node:path"; import * as fs from "node:fs"; -import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import { describe, it, expect, afterAll } from "vitest"; import { Agent, fetch } from "undici"; - -let initialPort = 4096; -const getPort = () => initialPort++; +import { listenOn, proxyListen } from "./https-proxy.test.ts"; +import { inspect } from "node:util"; const http1Agent = new Agent({ allowH2: false, @@ -26,52 +25,55 @@ const http2Agent = new Agent({ }); describe("http/2 listener", () => { - describe("http2 -> http", () => { - const httpPort = getPort(); - const proxyPort = getPort(); - - const source = http - .createServer((_req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.write("hello httpxy\n"); - res.end(); - }) - .listen(httpPort); - - const proxy = httpProxy - .createProxyServer({ - target: { - host: "localhost", - port: httpPort, - }, - ssl: { - key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), - cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), - }, - http2: true, - // Allow to use SSL self signed - secure: false, - }) - .listen(proxyPort); + describe("http2 -> http", async () => { + const source = http.createServer((_req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello from " + sourcePort); + }); + const sourcePort = await listenOn(source); + + const proxy = httpProxy.createProxyServer({ + target: "http://127.0.0.1:" + sourcePort, + ssl: { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + }, + http2: true, + // Allow to use SSL self signed + secure: false, + }); + const proxyPort = await proxyListen(proxy); it("target http server should be working", async () => { - const r = await ( - await fetch(`http://localhost:${httpPort}`, { dispatcher: http1Agent }) - ).text(); - expect(r).toContain("hello httpxy"); + try { + const r = await ( + await fetch(`http://127.0.0.1:${sourcePort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).to.eql("Hello from " + sourcePort); + } catch (err) { + expect.fail("Failed to fetch target server: " + inspect(err)); + } }); it("fetch proxy server over http1", async () => { - const r = await ( - await fetch(`https://localhost:${proxyPort}`, { dispatcher: http1Agent }) - ).text(); - expect(r).toContain("hello httpxy"); + try { + const r = await ( + await fetch(`https://127.0.0.1:${proxyPort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).to.eql("Hello from " + sourcePort); + } catch (err) { + expect.fail("Failed to fetch target server: " + inspect(err)); + } }); it("fetch proxy server over http2", async () => { - const resp = await fetch(`https://localhost:${proxyPort}`, { dispatcher: http2Agent }); - const r = await resp.text(); - expect(r).toContain("hello httpxy"); + try { + const resp = await fetch(`https://127.0.0.1:${proxyPort}`, { dispatcher: http2Agent }); + const r = await resp.text(); + expect(r).to.eql("Hello from " + sourcePort); + } catch (err) { + expect.fail("Failed to fetch target server: " + inspect(err)); + } }); afterAll(async () => { @@ -81,58 +83,62 @@ describe("http/2 listener", () => { }); }); - // TODO: fix this test - describe.skip("http2 -> https", () => { - const httpsPort = getPort(); - const proxyPort = getPort(); - - const source = https - .createServer( - { - key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), - cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), - }, - function (req, res) { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("hello httpxy"); - }, - ) - .listen(httpsPort); - - const proxy = httpProxy - .createProxyServer({ - target: { - host: "localhost", - port: httpsPort, - }, - ssl: { - key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), - cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), - }, - http2: true, - // Allow to use SSL self signed - secure: false, - }) - .listen(proxyPort); + describe("http2 -> https", async () => { + const source = https.createServer( + { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + ciphers: "AES128-GCM-SHA256", + }, + function (req, res) { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello from " + sourcePort); + }, + ); + const sourcePort = await listenOn(source); + + const proxy = httpProxy.createProxyServer({ + target: "https://127.0.0.1:" + sourcePort, + ssl: { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + }, + http2: true, + // Allow to use SSL self signed + secure: false, + }); + const proxyPort = await proxyListen(proxy); it("target https server should be working", async () => { - const r = await ( - await fetch(`https://localhost:${httpsPort}`, { dispatcher: http1Agent }) - ).text(); - expect(r).toContain("hello httpxy"); + try { + const r = await ( + await fetch(`https://127.0.0.1:${sourcePort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).to.eql("Hello from " + sourcePort); + } catch (err) { + expect.fail("Failed to fetch target server: " + inspect(err)); + } }); it("fetch proxy server over http1", async () => { - const r = await ( - await fetch(`https://localhost:${proxyPort}`, { dispatcher: http1Agent }) - ).text(); - expect(r).toContain("hello httpxy"); + try { + const r = await ( + await fetch(`https://127.0.0.1:${proxyPort}`, { dispatcher: http1Agent }) + ).text(); + expect(r).to.eql("Hello from " + sourcePort); + } catch (err) { + expect.fail("Failed to fetch target server: " + inspect(err)); + } }); it("fetch proxy server over http2", async () => { - const resp = await fetch(`https://localhost:${proxyPort}`, { dispatcher: http2Agent }); - const r = await resp.text(); - expect(r).toContain("hello httpxy"); + try { + const resp = await fetch(`https://127.0.0.1:${proxyPort}`, { dispatcher: http2Agent }); + const r = await resp.text(); + expect(r).to.eql("Hello from " + sourcePort); + } catch (err) { + expect.fail("Failed to fetch target server: " + inspect(err)); + } }); afterAll(async () => { diff --git a/test/https-proxy.test.ts b/test/https-proxy.test.ts index 71aba33..873b867 100644 --- a/test/https-proxy.test.ts +++ b/test/https-proxy.test.ts @@ -9,7 +9,7 @@ import type { AddressInfo } from "node:net"; // Source: https://github.com/http-party/node-http-proxy/blob/master/test/lib-https-proxy-test.js -function listenOn(server: http.Server | https.Server | net.Server): Promise { +export function listenOn(server: http.Server | https.Server | net.Server): Promise { return new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", () => { @@ -18,7 +18,9 @@ function listenOn(server: http.Server | https.Server | net.Server): Promise): Promise { +export function proxyListen( + proxy: ReturnType, +): Promise { return new Promise((resolve, reject) => { proxy.listen(0, "127.0.0.1"); const server = (proxy as any)._server as net.Server; From 84d4c02e111a4b170492c1f837be47153ae808db Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 20 Feb 2026 18:59:16 +0800 Subject: [PATCH 11/24] test: improve coverage --- src/_utils.ts | 1 - test/middleware/web-outgoing.test.ts | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/_utils.ts b/src/_utils.ts index e1a4ed5..4047aa7 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -1,7 +1,6 @@ import httpNative from "node:http"; import httpsNative from "node:https"; import net from "node:net"; -import type tls from "node:tls"; import type { ProxyAddr, ProxyServerOptions, ProxyTarget, ProxyTargetDetailed } from "./types.ts"; import type { Http2ServerRequest } from "node:http2"; diff --git a/test/middleware/web-outgoing.test.ts b/test/middleware/web-outgoing.test.ts index 083a0fa..77152b8 100644 --- a/test/middleware/web-outgoing.test.ts +++ b/test/middleware/web-outgoing.test.ts @@ -104,6 +104,7 @@ describe("middleware:web-outgoing", () => { describe("rewrites location host with autoRewrite", () => { beforeEach(() => { ctx.options.autoRewrite = true; + delete ctx.req.headers[":authority"]; }); for (const code of [201, 301, 302, 307, 308]) { it("on " + code, () => { @@ -118,6 +119,17 @@ describe("middleware:web-outgoing", () => { }); } + it("with HTTP/2 :authority", () => { + ctx.req.headers[":authority"] = "ext-auto.com"; + webOutgoing.setRedirectHostRewrite( + ctx.req, + stubServerResponse(), + ctx.proxyRes, + ctx.options, + ); + expect(ctx.proxyRes.headers.location).to.eql("http://ext-auto.com/"); + }); + it("not on 200", () => { ctx.proxyRes.statusCode = 200; webOutgoing.setRedirectHostRewrite( From 68a2f02ef36a7711341f5cb84d00c7382fca8e3c Mon Sep 17 00:00:00 2001 From: SukkaW Date: Fri, 20 Feb 2026 19:05:56 +0800 Subject: [PATCH 12/24] chore: add comment --- src/_utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_utils.ts b/src/_utils.ts index 4047aa7..72e505b 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -77,6 +77,7 @@ export function setupOutgoing( if (req.headers?.[":authority"]) { outgoing.headers.host = req.headers[":authority"] as string; } + // host override must happen before composing/merging the final outgoing headers if (options.headers) { outgoing.headers = { ...outgoing.headers, ...options.headers }; From ebfd52dc8bccefc420c383c7c4fb88a3b69dd303 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Tue, 24 Feb 2026 00:17:37 +0800 Subject: [PATCH 13/24] test: fix describe --- test/http2-proxy.test.ts | 96 +++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/test/http2-proxy.test.ts b/test/http2-proxy.test.ts index 37488fc..1259bfd 100644 --- a/test/http2-proxy.test.ts +++ b/test/http2-proxy.test.ts @@ -3,7 +3,7 @@ import * as https from "node:https"; import * as httpProxy from "../src/index.ts"; import * as path from "node:path"; import * as fs from "node:fs"; -import { describe, it, expect, afterAll } from "vitest"; +import { describe, it, expect, afterAll, beforeAll } from "vitest"; import { Agent, fetch } from "undici"; import { listenOn, proxyListen } from "./https-proxy.test.ts"; @@ -26,23 +26,30 @@ const http2Agent = new Agent({ describe("http/2 listener", () => { describe("http2 -> http", async () => { - const source = http.createServer((_req, res) => { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello from " + sourcePort); - }); - const sourcePort = await listenOn(source); - - const proxy = httpProxy.createProxyServer({ - target: "http://127.0.0.1:" + sourcePort, - ssl: { - key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), - cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), - }, - http2: true, - // Allow to use SSL self signed - secure: false, + let source: http.Server; + let sourcePort: number; + let proxy: httpProxy.ProxyServer; + let proxyPort: number; + + beforeAll(async () => { + source = http.createServer((_req, res) => { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello from " + sourcePort); + }); + sourcePort = await listenOn(source); + + proxy = httpProxy.createProxyServer({ + target: "http://127.0.0.1:" + sourcePort, + ssl: { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + }, + http2: true, + // Allow to use SSL self signed + secure: false, + }); + proxyPort = await proxyListen(proxy); }); - const proxyPort = await proxyListen(proxy); it("target http server should be working", async () => { try { @@ -84,30 +91,39 @@ describe("http/2 listener", () => { }); describe("http2 -> https", async () => { - const source = https.createServer( - { - key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), - cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), - ciphers: "AES128-GCM-SHA256", - }, - function (req, res) { - res.writeHead(200, { "Content-Type": "text/plain" }); - res.end("Hello from " + sourcePort); - }, - ); - const sourcePort = await listenOn(source); - - const proxy = httpProxy.createProxyServer({ - target: "https://127.0.0.1:" + sourcePort, - ssl: { - key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), - cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), - }, - http2: true, - // Allow to use SSL self signed - secure: false, + let source: https.Server; + let sourcePort: number; + let proxy: httpProxy.ProxyServer; + let proxyPort: number; + + beforeAll(async () => { + source = https.createServer( + { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + ciphers: "AES128-GCM-SHA256", + }, + function (req, res) { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("Hello from " + sourcePort); + }, + ); + + sourcePort = await listenOn(source); + + proxy = httpProxy.createProxyServer({ + target: "https://127.0.0.1:" + sourcePort, + ssl: { + key: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-key.pem")), + cert: fs.readFileSync(path.join(__dirname, "fixtures", "agent2-cert.pem")), + }, + http2: true, + // Allow to use SSL self signed + secure: false, + }); + + proxyPort = await proxyListen(proxy); }); - const proxyPort = await proxyListen(proxy); it("target https server should be working", async () => { try { From 795359df64bf848664de6d91502a7948e935330d Mon Sep 17 00:00:00 2001 From: SukkaW Date: Tue, 24 Feb 2026 00:21:56 +0800 Subject: [PATCH 14/24] test: extract util --- test/_utils.ts | 27 +++++++++++++++++++++++++++ test/http-proxy.test.ts | 21 +-------------------- test/http2-proxy.test.ts | 3 ++- test/https-proxy.test.ts | 25 +------------------------ test/middleware/web-incoming.test.ts | 11 +---------- 5 files changed, 32 insertions(+), 55 deletions(-) create mode 100644 test/_utils.ts diff --git a/test/_utils.ts b/test/_utils.ts new file mode 100644 index 0000000..74aeb73 --- /dev/null +++ b/test/_utils.ts @@ -0,0 +1,27 @@ +import http from "node:http"; +import https from "node:https"; +import net from "node:net"; +import type { AddressInfo } from "node:net"; +import * as httpProxy from "../src/index.ts"; + +export function listenOn(server: http.Server | https.Server | net.Server): Promise { + return new Promise((resolve, reject) => { + server.once("error", reject); + server.listen(0, "127.0.0.1", () => { + resolve((server.address() as AddressInfo).port); + }); + }); +} + +export function proxyListen( + proxy: ReturnType, +): Promise { + return new Promise((resolve, reject) => { + proxy.listen(0, "127.0.0.1"); + const server = (proxy as any)._server as net.Server; + server.once("error", reject); + server.once("listening", () => { + resolve((server.address() as AddressInfo).port); + }); + }); +} diff --git a/test/http-proxy.test.ts b/test/http-proxy.test.ts index d5b8c77..7c6a79d 100644 --- a/test/http-proxy.test.ts +++ b/test/http-proxy.test.ts @@ -7,29 +7,10 @@ import * as io from "socket.io"; import SSE from "sse"; import ioClient from "socket.io-client"; import type { AddressInfo } from "node:net"; +import { listenOn, proxyListen } from "./_utils.ts"; // Source: https://github.com/http-party/node-http-proxy/blob/master/test/lib-http-proxy-test.js -function listenOn(server: http.Server | net.Server): Promise { - return new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", () => { - resolve((server.address() as AddressInfo).port); - }); - }); -} - -function proxyListen(proxy: ReturnType): Promise { - return new Promise((resolve, reject) => { - proxy.listen(0, "127.0.0.1"); - const server = (proxy as any)._server as net.Server; - server.once("error", reject); - server.once("listening", () => { - resolve((server.address() as AddressInfo).port); - }); - }); -} - describe("http-proxy", () => { describe("#createProxyServer", () => { it.skip("should throw without options", () => { diff --git a/test/http2-proxy.test.ts b/test/http2-proxy.test.ts index 1259bfd..024fc63 100644 --- a/test/http2-proxy.test.ts +++ b/test/http2-proxy.test.ts @@ -6,7 +6,8 @@ import * as fs from "node:fs"; import { describe, it, expect, afterAll, beforeAll } from "vitest"; import { Agent, fetch } from "undici"; -import { listenOn, proxyListen } from "./https-proxy.test.ts"; +import { listenOn, proxyListen } from "./_utils.ts"; + import { inspect } from "node:util"; const http1Agent = new Agent({ diff --git a/test/https-proxy.test.ts b/test/https-proxy.test.ts index 873b867..d9e6c4d 100644 --- a/test/https-proxy.test.ts +++ b/test/https-proxy.test.ts @@ -2,35 +2,12 @@ import { describe, it, expect } from "vitest"; import * as httpProxy from "../src/index.ts"; import http from "node:http"; import https from "node:https"; -import net from "node:net"; import path from "node:path"; import fs from "node:fs"; -import type { AddressInfo } from "node:net"; +import { listenOn, proxyListen } from "./_utils.ts"; // Source: https://github.com/http-party/node-http-proxy/blob/master/test/lib-https-proxy-test.js -export function listenOn(server: http.Server | https.Server | net.Server): Promise { - return new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", () => { - resolve((server.address() as AddressInfo).port); - }); - }); -} - -export function proxyListen( - proxy: ReturnType, -): Promise { - return new Promise((resolve, reject) => { - proxy.listen(0, "127.0.0.1"); - const server = (proxy as any)._server as net.Server; - server.once("error", reject); - server.once("listening", () => { - resolve((server.address() as AddressInfo).port); - }); - }); -} - describe("lib/http-proxy.js", () => { describe("HTTPS #createProxyServer", () => { describe("HTTPS to HTTP", () => { diff --git a/test/middleware/web-incoming.test.ts b/test/middleware/web-incoming.test.ts index 5407992..2260ce3 100644 --- a/test/middleware/web-incoming.test.ts +++ b/test/middleware/web-incoming.test.ts @@ -4,25 +4,16 @@ import * as webPasses from "../../src/middleware/web-incoming.ts"; import * as httpProxy from "../../src/index.ts"; import concat from "concat-stream"; import http from "node:http"; -import type { AddressInfo } from "node:net"; import { stubIncomingMessage, stubServerResponse, stubMiddlewareOptions, stubProxyServer, } from "../_stubs.ts"; +import { listenOn } from "../_utils.ts"; // Source: https://github.com/http-party/node-http-proxy/blob/master/test/lib-http-proxy-passes-web-incoming-test.js -function listenOn(server: http.Server): Promise { - return new Promise((resolve, reject) => { - server.once("error", reject); - server.listen(0, "127.0.0.1", () => { - resolve((server.address() as AddressInfo).port); - }); - }); -} - describe("middleware:web-incoming", () => { describe("#deleteLength", () => { it("should change `content-length` for DELETE requests", () => { From 50c9d9e25ee44a04888becb5a7751ca4db55b891 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Tue, 24 Feb 2026 00:23:50 +0800 Subject: [PATCH 15/24] fix: ws listen upgrade --- src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.ts b/src/server.ts index bb2f20a..e8d8283 100644 --- a/src/server.ts +++ b/src/server.ts @@ -89,7 +89,7 @@ export class ProxyServer< if (this.options.ws) { this._server.on("upgrade", (req, socket, head) => { - this.ws(req, socket, head).catch(() => {}); + this.ws(req, socket, this.options, head).catch(() => {}); }); } From 91b63fbc6c9fe3598b472385c249618dd42c3e77 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Mon, 2 Mar 2026 04:54:07 +0800 Subject: [PATCH 16/24] test: fix coverage --- src/_utils.ts | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/_utils.ts b/src/_utils.ts index 72e505b..05e3672 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -224,23 +224,37 @@ export function getPort(req: httpNative.IncomingMessage | Http2ServerRequest): s export function hasEncryptedConnection( req: httpNative.IncomingMessage | Http2ServerRequest, ): boolean { - // req.connection.pair probably does not exist anymore - if ("connection" in req) { - if ("encrypted" in req.connection) { - return req.connection.encrypted; - } - if ("pair" in req.connection) { - return !!req.connection.pair; - } - } // Since Node.js v16 we now have req.socket if ("socket" in req) { + /* v8 ignore start */ + + // encrypted is only present in TLS sockets, not plain net sockets if ("encrypted" in req.socket) { return req.socket.encrypted; } + + // "pair" is deprecated and is not typed by @types/node, but it actually hasn't been removed yet + // we can still fall back to "pair" for backward compatibility, but normally not reachable if ("pair" in req.socket) { return !!req.socket.pair; } + /* v8 ignore stop */ + } + + if ("connection" in req) { + /* v8 ignore start */ + + // encrypted is only present in TLS sockets, not plain net sockets + if ("encrypted" in req.connection) { + return req.connection.encrypted; + } + + // "pair" is deprecated and is not typed by @types/node, but it actually hasn't been removed yet + // we can still fall back to "pair" for backward compatibility, but normally not reachable + if ("pair" in req.connection) { + return !!req.connection.pair; + } + /* v8 ignore stop */ } return false; From c280335fd44e37df99caeedbb86e7c3b9d30f6cb Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:15:32 +0100 Subject: [PATCH 17/24] fix(proxy): restore proxyRes error/close handlers The HTTP/2 PR accidentally removed proxyRes close and error handlers from web-incoming stream middleware, breaking upstream abort propagation to downstream clients. Restores the handlers and the SSE close test. --- src/middleware/web-incoming.ts | 14 ++++++++ test/http-proxy.test.ts | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/src/middleware/web-incoming.ts b/src/middleware/web-incoming.ts index 6b38539..f02a1bc 100644 --- a/src/middleware/web-incoming.ts +++ b/src/middleware/web-incoming.ts @@ -271,6 +271,20 @@ export const stream = defineProxyMiddleware((req, res, options, server, head, ca res.on("close", function () { proxyRes.destroy(); }); + proxyRes.on("close", function () { + if (!proxyRes.complete && !res.destroyed) { + res.destroy(); + } + }); + proxyRes.on("error", function (err) { + if (!res.destroyed) { + res.destroy(err); + } + + if (server.listenerCount("error") > 0) { + server.emit("error", err, req, res, currentUrl); + } + }); proxyRes.on("end", function () { if (server) { server.emit("end", req, res, proxyRes); diff --git a/test/http-proxy.test.ts b/test/http-proxy.test.ts index 7c6a79d..b2b91af 100644 --- a/test/http-proxy.test.ts +++ b/test/http-proxy.test.ts @@ -98,6 +98,72 @@ describe("http-proxy", () => { await promise; }); + it("should close downstream SSE stream when upstream aborts", async () => { + const source = http.createServer((_, res) => { + res.writeHead(200, { + "content-type": "text/event-stream", + "cache-control": "no-cache", + connection: "keep-alive", + }); + res.write(":ok\n\n"); + + setTimeout(() => { + res.socket?.destroy(); + }, 20); + }); + const sourcePort = await listenOn(source); + + const proxy = httpProxy.createProxyServer({ + target: "http://127.0.0.1:" + sourcePort, + }); + const proxyPort = await proxyListen(proxy); + + const { promise, resolve } = Promise.withResolvers(); + let gotFirstChunk = false; + let requestError: Error | undefined; + + const finish = () => { + source.close(); + proxy.close(resolve); + }; + + const timeout = setTimeout(() => { + requestError = new Error("Timed out waiting for downstream SSE close"); + finish(); + }, 1000); + + http + .request( + { + hostname: "127.0.0.1", + port: proxyPort, + method: "GET", + }, + (res) => { + res.on("data", (chunk) => { + if (chunk.toString("utf8").includes(":ok")) { + gotFirstChunk = true; + } + }); + + res.once("close", () => { + clearTimeout(timeout); + finish(); + }); + }, + ) + .on("error", (error) => { + clearTimeout(timeout); + requestError = error; + finish(); + }) + .end(); + + await promise; + expect(requestError).toBeUndefined(); + expect(gotFirstChunk).toBe(true); + }); + it("should make the request on pipe and finish it", async () => { const source = http.createServer(); const sourcePort = await listenOn(source); From 4718a420a50392bb394fcebec01402d0caa6cfd8 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:15:49 +0100 Subject: [PATCH 18/24] fix(server): validate ssl option when http2 is enabled Throws a clear error instead of letting http2.createSecureServer fail with a cryptic message when ssl is not provided. --- src/server.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server.ts b/src/server.ts index e8d8283..95ecc25 100644 --- a/src/server.ts +++ b/src/server.ts @@ -80,6 +80,9 @@ export class ProxyServer< }; if (this.options.http2) { + if (!this.options.ssl) { + throw new Error("HTTP/2 requires ssl option"); + } this._server = http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, closure); } else if (this.options.ssl) { this._server = https.createServer(this.options.ssl, closure); From 3efe1595d5185aa04d0d4a44334c9918150fef91 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:18:18 +0100 Subject: [PATCH 19/24] chore: sync devDependencies with main The contributor branch was based on older versions. Restores versions from main and adds undici as new dev dependency. --- package.json | 18 +- pnpm-lock.yaml | 707 +++++++++++++++++++++++++------------------------ 2 files changed, 364 insertions(+), 361 deletions(-) diff --git a/package.json b/package.json index 696e1a8..7d895db 100644 --- a/package.json +++ b/package.json @@ -33,29 +33,29 @@ "@types/async": "^3.2.25", "@types/concat-stream": "^2.0.3", "@types/express": "^5.0.6", - "@types/node": "^25.2.3", + "@types/node": "^25.5.0", "@types/semver": "^7.7.1", "@types/sse": "^0.0.0", "@types/ws": "^8.18.1", - "@typescript/native-preview": "^7.0.0-dev.20260218.1", - "@vitest/coverage-v8": "^4.0.18", + "@typescript/native-preview": "^7.0.0-dev.20260325.1", + "@vitest/coverage-v8": "^4.1.1", "async": "^3.2.6", "changelogen": "^0.6.2", "concat-stream": "^2.0.0", "eslint-config-unjs": "^0.6.2", "expect.js": "^0.3.1", "ofetch": "^1.5.1", - "oxfmt": "^0.33.0", - "oxlint": "^1.48.0", + "oxfmt": "^0.42.0", + "oxlint": "^1.57.0", "semver": "^7.7.4", "socket.io": "^4.8.3", "socket.io-client": "^4.8.3", "sse": "^0.0.8", - "typescript": "^5.9.3", + "typescript": "^6.0.2", "unbuild": "^3.6.1", "undici": "^7.21.0", - "vitest": "^4.0.18", - "ws": "^8.19.0" + "vitest": "^4.1.1", + "ws": "^8.20.0" }, - "packageManager": "pnpm@10.30.0" + "packageManager": "pnpm@10.33.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6050230..7c6d8b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^5.0.6 version: 5.0.6 '@types/node': - specifier: ^25.2.3 - version: 25.2.3 + specifier: ^25.5.0 + version: 25.5.0 '@types/semver': specifier: ^7.7.1 version: 7.7.1 @@ -30,11 +30,11 @@ importers: specifier: ^8.18.1 version: 8.18.1 '@typescript/native-preview': - specifier: ^7.0.0-dev.20260218.1 - version: 7.0.0-dev.20260218.1 + specifier: ^7.0.0-dev.20260325.1 + version: 7.0.0-dev.20260325.1 '@vitest/coverage-v8': - specifier: ^4.0.18 - version: 4.0.18(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1)) + specifier: ^4.1.1 + version: 4.1.1(vitest@4.1.1(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1))) async: specifier: ^3.2.6 version: 3.2.6 @@ -46,7 +46,7 @@ importers: version: 2.0.0 eslint-config-unjs: specifier: ^0.6.2 - version: 0.6.2(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) + version: 0.6.2(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) expect.js: specifier: ^0.3.1 version: 0.3.1 @@ -54,11 +54,11 @@ importers: specifier: ^1.5.1 version: 1.5.1 oxfmt: - specifier: ^0.33.0 - version: 0.33.0 + specifier: ^0.42.0 + version: 0.42.0 oxlint: - specifier: ^1.48.0 - version: 1.48.0 + specifier: ^1.57.0 + version: 1.57.0 semver: specifier: ^7.7.4 version: 7.7.4 @@ -72,20 +72,20 @@ importers: specifier: ^0.0.8 version: 0.0.8 typescript: - specifier: ^5.9.3 - version: 5.9.3 + specifier: ^6.0.2 + version: 6.0.2 unbuild: specifier: ^3.6.1 - version: 3.6.1(typescript@5.9.3) + version: 3.6.1(typescript@6.0.2) undici: specifier: ^7.21.0 version: 7.21.0 vitest: - specifier: ^4.0.18 - version: 4.0.18(@types/node@25.2.3)(jiti@2.6.1) + specifier: ^4.1.1 + version: 4.1.1(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)) ws: - specifier: ^8.19.0 - version: 8.19.0 + specifier: ^8.20.0 + version: 8.20.0 packages: @@ -502,246 +502,246 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@oxfmt/binding-android-arm-eabi@0.33.0': - resolution: {integrity: sha512-ML6qRW8/HiBANteqfyFAR1Zu0VrJu+6o4gkPLsssq74hQ7wDMkufBYJXI16PGSERxEYNwKxO5fesCuMssgTv9w==} + '@oxfmt/binding-android-arm-eabi@0.42.0': + resolution: {integrity: sha512-dsqPTYsozeokRjlrt/b4E7Pj0z3eS3Eg74TWQuuKbjY4VttBmA88rB7d50Xrd+TZ986qdXCNeZRPEzZHAe+jow==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.33.0': - resolution: {integrity: sha512-WimmcyrGpTOntj7F7CO9RMssncOKYall93nBnzJbI2ZZDhVRuCkvFwTpwz80cZqwYm5udXRXfF40ZXcCxjp9jg==} + '@oxfmt/binding-android-arm64@0.42.0': + resolution: {integrity: sha512-t+aAjHxcr5eOBphFHdg1ouQU9qmZZoRxnX7UOJSaTwSoKsb6TYezNKO0YbWytGXCECObRqNcUxPoPr0KaraAIg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.33.0': - resolution: {integrity: sha512-PorspsX9O5ISstVaq34OK4esN0LVcuU4DVg+XuSqJsfJ//gn6z6WH2Tt7s0rTQaqEcp76g7+QdWQOmnJDZsEVg==} + '@oxfmt/binding-darwin-arm64@0.42.0': + resolution: {integrity: sha512-ulpSEYMKg61C5bRMZinFHrKJYRoKGVbvMEXA5zM1puX3O9T6Q4XXDbft20yrDijpYWeuG59z3Nabt+npeTsM1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.33.0': - resolution: {integrity: sha512-8278bqQtOcHRPhhzcqwN9KIideut+cftBjF8d2TOsSQrlsJSFx41wCCJ38mFmH9NOmU1M+x9jpeobHnbRP1okw==} + '@oxfmt/binding-darwin-x64@0.42.0': + resolution: {integrity: sha512-ttxLKhQYPdFiM8I/Ri37cvqChE4Xa562nNOsZFcv1CKTVLeEozXjKuYClNvxkXmNlcF55nzM80P+CQkdFBu+uQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.33.0': - resolution: {integrity: sha512-BiqYVwWFHLf5dkfg0aCKsXa9rpi//vH1+xePCpd7Ulz9yp9pJKP4DWgS5g+OW8MaqOtt7iyAszhxtk/j1nDKHQ==} + '@oxfmt/binding-freebsd-x64@0.42.0': + resolution: {integrity: sha512-Og7QS3yI3tdIKYZ58SXik0rADxIk2jmd+/YvuHRyKULWpG4V2fR5V4hvKm624Mc0cQET35waPXiCQWvjQEjwYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.33.0': - resolution: {integrity: sha512-oAVmmurXx0OKbNOVv71oK92LsF1LwYWpnhDnX0VaAy/NLsCKf4B7Zo7lxkJh80nfhU20TibcdwYfoHVaqlStPQ==} + '@oxfmt/binding-linux-arm-gnueabihf@0.42.0': + resolution: {integrity: sha512-jwLOw/3CW4H6Vxcry4/buQHk7zm9Ne2YsidzTL1kpiMe4qqrRCwev3dkyWe2YkFmP+iZCQ7zku4KwjcLRoh8ew==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.33.0': - resolution: {integrity: sha512-YB6S8CiRol59oRxnuclJiWoV6l+l8ru/NsuQNYjXZnnPXfSTXKtMLWHCnL/figpCFYA1E7JyjrBbar1qxe2aZg==} + '@oxfmt/binding-linux-arm-musleabihf@0.42.0': + resolution: {integrity: sha512-XwXu2vkMtiq2h7tfvN+WA/9/5/1IoGAVCFPiiQUvcAuG3efR97KNcRGM8BetmbYouFotQ2bDal3yyjUx6IPsTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.33.0': - resolution: {integrity: sha512-hrYy+FpWoB6N24E9oGRimhVkqlls9yeqcRmQakEPUHoAbij6rYxsHHYIp3+FHRiQZFAOUxWKn/CCQoy/Mv3Dgw==} + '@oxfmt/binding-linux-arm64-gnu@0.42.0': + resolution: {integrity: sha512-ea7s/XUJoT7ENAtUQDudFe3nkSM3e3Qpz4nJFRdzO2wbgXEcjnchKLEsV3+t4ev3r8nWxIYr9NRjPWtnyIFJVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.33.0': - resolution: {integrity: sha512-O1YIzymGRdWj9cG5iVTjkP7zk9/hSaVN8ZEbqMnWZjLC1phXlv54cUvANGGXndgJp2JS4W9XENn7eo5I4jZueg==} + '@oxfmt/binding-linux-arm64-musl@0.42.0': + resolution: {integrity: sha512-+JA0YMlSdDqmacygGi2REp57c3fN+tzARD8nwsukx9pkCHK+6DkbAA9ojS4lNKsiBjIW8WWa0pBrBWhdZEqfuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.33.0': - resolution: {integrity: sha512-2lrkNe+B0w1tCgQTaozfUNQCYMbqKKCGcnTDATmWCZzO77W2sh+3n04r1lk9Q1CK3bI+C3fPwhFPUR2X2BvlyQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.42.0': + resolution: {integrity: sha512-VfnET0j4Y5mdfCzh5gBt0NK28lgn5DKx+8WgSMLYYeSooHhohdbzwAStLki9pNuGy51y4I7IoW8bqwAaCMiJQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.33.0': - resolution: {integrity: sha512-8DSG1q0M6097vowHAkEyHnKed75/BWr1IBtgCJfytnWQg+Jn1X4DryhfjqonKZOZiv74oFQl5J8TCbdDuXXdtQ==} + '@oxfmt/binding-linux-riscv64-gnu@0.42.0': + resolution: {integrity: sha512-gVlCbmBkB0fxBWbhBj9rcxezPydsQHf4MFKeHoTSPicOQ+8oGeTQgQ8EeesSybWeiFPVRx3bgdt4IJnH6nOjAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.33.0': - resolution: {integrity: sha512-eWaxnpPz7+p0QGUnw7GGviVBDOXabr6Cd0w7S/vnWTqQo9z1VroT7XXFnJEZ3dBwxMB9lphyuuYi/GLTCxqxlg==} + '@oxfmt/binding-linux-riscv64-musl@0.42.0': + resolution: {integrity: sha512-zN5OfstL0avgt/IgvRu0zjQzVh/EPkcLzs33E9LMAzpqlLWiPWeMDZyMGFlSRGOdDjuNmlZBCgj0pFnK5u32TQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.33.0': - resolution: {integrity: sha512-+mH8cQTqq+Tu2CdoB2/Wmk9CqotXResi+gPvXpb+AAUt/LiwpicTQqSolMheQKogkDTYHPuUiSN23QYmy7IXNQ==} + '@oxfmt/binding-linux-s390x-gnu@0.42.0': + resolution: {integrity: sha512-9X6+H2L0qMc2sCAgO9HS03bkGLMKvOFjmEdchaFlany3vNZOjnVui//D8k/xZAtQv2vaCs1reD5KAgPoIU4msA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.33.0': - resolution: {integrity: sha512-fjyslAYAPE2+B6Ckrs5LuDQ6lB1re5MumPnzefAXsen3JGwiRilra6XdjUmszTNoExJKbewoxxd6bcLSTpkAJQ==} + '@oxfmt/binding-linux-x64-gnu@0.42.0': + resolution: {integrity: sha512-BajxJ6KQvMMdpXGPWhBGyjb2Jvx4uec0w+wi6TJZ6Tv7+MzPwe0pO8g5h1U0jyFgoaF7mDl6yKPW3ykWcbUJRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.33.0': - resolution: {integrity: sha512-ve/jGBlTt35Jl/I0A0SfCQX3wKnadzPDdyOFEwe2ZgHHIT9uhqhAv1PaVXTenSBpauICEWYH8mWy+ittzlVE/A==} + '@oxfmt/binding-linux-x64-musl@0.42.0': + resolution: {integrity: sha512-0wV284I6vc5f0AqAhgAbHU2935B4bVpncPoe5n/WzVZY/KnHgqxC8iSFGeSyLWEgstFboIcWkOPck7tqbdHkzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.33.0': - resolution: {integrity: sha512-lsWRgY9e+uPvwXnuDiJkmJ2Zs3XwwaQkaALJ3/SXU9kjZP0Qh8/tGW8Tk/Z6WL32sDxx+aOK5HuU7qFY9dHJhg==} + '@oxfmt/binding-openharmony-arm64@0.42.0': + resolution: {integrity: sha512-p4BG6HpGnhfgHk1rzZfyR6zcWkE7iLrWxyehHfXUy4Qa5j3e0roglFOdP/Nj5cJJ58MA3isQ5dlfkW2nNEpolw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.33.0': - resolution: {integrity: sha512-w8AQHyGDRZutxtQ7IURdBEddwFrtHQiG6+yIFpNJ4HiMyYEqeAWzwBQBfwSAxtSNh6Y9qqbbc1OM2mHN6AB3Uw==} + '@oxfmt/binding-win32-arm64-msvc@0.42.0': + resolution: {integrity: sha512-mn//WV60A+IetORDxYieYGAoQso4KnVRRjORDewMcod4irlRe0OSC7YPhhwaexYNPQz/GCFk+v9iUcZ2W22yxQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.33.0': - resolution: {integrity: sha512-j2X4iumKVwDzQtUx3JBDkaydx6eLuncgUZPl2ybZ8llxJMFbZIniws70FzUQePMfMtzLozIm7vo4bjkvQFsOzw==} + '@oxfmt/binding-win32-ia32-msvc@0.42.0': + resolution: {integrity: sha512-3gWltUrvuz4LPJXWivoAxZ28Of2O4N7OGuM5/X3ubPXCEV8hmgECLZzjz7UYvSDUS3grfdccQwmjynm+51EFpw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.33.0': - resolution: {integrity: sha512-lsBQxbepASwOBUh3chcKAjU+jVAQhLElbPYiagIq26cU8vA9Bttj6t20bMvCQCw31m440IRlNhrK7NpnUI8mzA==} + '@oxfmt/binding-win32-x64-msvc@0.42.0': + resolution: {integrity: sha512-Wg4TMAfQRL9J9AZevJ/ZNy3uyyDztDYQtGr4P8UyyzIhLhFrdSmz1J/9JT+rv0fiCDLaFOBQnj3f3K3+a5PzDQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.48.0': - resolution: {integrity: sha512-1Pz/stJvveO9ZO7ll4ZoEY3f6j2FiUgBLBcCRCiW6ylId9L9UKs+gn3X28m3eTnoiFCkhKwmJJ+VO6vwsu7Qtg==} + '@oxlint/binding-android-arm-eabi@1.57.0': + resolution: {integrity: sha512-C7EiyfAJG4B70496eV543nKiq5cH0o/xIh/ufbjQz3SIvHhlDDsyn+mRFh+aW8KskTyUpyH2LGWL8p2oN6bl1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.48.0': - resolution: {integrity: sha512-Zc42RWGE8huo6Ht0lXKjd0NH2lWNmimQHUmD0JFcvShLOuwN+RSEE/kRakc2/0LIgOUuU/R7PaDMCOdQlPgNUQ==} + '@oxlint/binding-android-arm64@1.57.0': + resolution: {integrity: sha512-9i80AresjZ/FZf5xK8tKFbhQnijD4s1eOZw6/FHUwD59HEZbVLRc2C88ADYJfLZrF5XofWDiRX/Ja9KefCLy7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.48.0': - resolution: {integrity: sha512-jgZs563/4vaG5jH2RSt2TSh8A2jwsFdmhLXrElMdm3Mmto0HPf85FgInLSNi9HcwzQFvkYV8JofcoUg2GH1HTA==} + '@oxlint/binding-darwin-arm64@1.57.0': + resolution: {integrity: sha512-0eUfhRz5L2yKa9I8k3qpyl37XK3oBS5BvrgdVIx599WZK63P8sMbg+0s4IuxmIiZuBK68Ek+Z+gcKgeYf0otsg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.48.0': - resolution: {integrity: sha512-kvo87BujEUjCJREuWDC4aPh1WoXCRFFWE4C7uF6wuoMw2f6N2hypA/cHHcYn9DdL8R2RrgUZPefC8JExyeIMKA==} + '@oxlint/binding-darwin-x64@1.57.0': + resolution: {integrity: sha512-UvrSuzBaYOue+QMAcuDITe0k/Vhj6KZGjfnI6x+NkxBTke/VoM7ZisaxgNY0LWuBkTnd1OmeQfEQdQ48fRjkQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.48.0': - resolution: {integrity: sha512-eyzzPaHQKn0RIM+ueDfgfJF2RU//Wp4oaKs2JVoVYcM5HjbCL36+O0S3wO5Xe1NWpcZIG3cEHc/SuOCDRqZDSg==} + '@oxlint/binding-freebsd-x64@1.57.0': + resolution: {integrity: sha512-wtQq0dCoiw4bUwlsNVDJJ3pxJA218fOezpgtLKrbQqUtQJcM9yP8z+I9fu14aHg0uyAxIY+99toL6uBa2r7nxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.48.0': - resolution: {integrity: sha512-p3kSloztK7GRO7FyO3u38UCjZxQTl92VaLDsMQAq0eGoiNmeeEF1KPeE4+Fr+LSkQhF8WvJKSuls6TwOlurdPA==} + '@oxlint/binding-linux-arm-gnueabihf@1.57.0': + resolution: {integrity: sha512-qxFWl2BBBFcT4djKa+OtMdnLgoHEJXpqjyGwz8OhW35ImoCwR5qtAGqApNYce5260FQqoAHW8S8eZTjiX67Tsg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.48.0': - resolution: {integrity: sha512-uWM+wiTqLW/V0ZmY/eyTWs8ykhIkzU+K2tz/8m35YepYEzohiUGRbnkpAFXj2ioXpQL+GUe5vmM3SLH6ozlfFw==} + '@oxlint/binding-linux-arm-musleabihf@1.57.0': + resolution: {integrity: sha512-SQoIsBU7J0bDW15/f0/RvxHfY3Y0+eB/caKBQtNFbuerTiA6JCYx9P1MrrFTwY2dTm/lMgTSgskvCEYk2AtG/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.48.0': - resolution: {integrity: sha512-OhQNPjs/OICaYqxYJjKKMaIY7p3nJ9IirXcFoHKD+CQE1BZFCeUUAknMzUeLclDCfudH9Vb/UgjFm8+ZM5puAg==} + '@oxlint/binding-linux-arm64-gnu@1.57.0': + resolution: {integrity: sha512-jqxYd1W6WMeozsCmqe9Rzbu3SRrGTyGDAipRlRggetyYbUksJqJKvUNTQtZR/KFoJPb+grnSm5SHhdWrywv3RQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-arm64-musl@1.48.0': - resolution: {integrity: sha512-adu5txuwGvQ4C4fjYHJD+vnY+OCwCixBzn7J3KF3iWlVHBBImcosSv+Ye+fbMMJui4HGjifNXzonjKm9pXmOiw==} + '@oxlint/binding-linux-arm64-musl@1.57.0': + resolution: {integrity: sha512-i66WyEPVEvq9bxRUCJ/MP5EBfnTDN3nhwEdFZFTO5MmLLvzngfWEG3NSdXQzTT3vk5B9i6C2XSIYBh+aG6uqyg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxlint/binding-linux-ppc64-gnu@1.48.0': - resolution: {integrity: sha512-inlQQRUnHCny/7b7wA6NjEoJSSZPNea4qnDhWyeqBYWx8ukf2kzNDSiamfhOw6bfAYPm/PVlkVRYaNXQbkLeTQ==} + '@oxlint/binding-linux-ppc64-gnu@1.57.0': + resolution: {integrity: sha512-oMZDCwz4NobclZU3pH+V1/upVlJZiZvne4jQP+zhJwt+lmio4XXr4qG47CehvrW1Lx2YZiIHuxM2D4YpkG3KVA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-gnu@1.48.0': - resolution: {integrity: sha512-YiJx6sW6bYebQDZRVWLKm/Drswx/hcjIgbLIhULSn0rRcBKc7d9V6mkqPjKDbhcxJgQD5Zi0yVccJiOdF40AWA==} + '@oxlint/binding-linux-riscv64-gnu@1.57.0': + resolution: {integrity: sha512-uoBnjJ3MMEBbfnWC1jSFr7/nSCkcQYa72NYoNtLl1imshDnWSolYCjzb8LVCwYCCfLJXD+0gBLD7fyC14c0+0g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-riscv64-musl@1.48.0': - resolution: {integrity: sha512-zwSqxMgmb2ITamNfDv9Q9EKBc/4ZhCBP9gkg2hhcgR6sEVGPUDl1AKPC89CBKMxkmPUi3685C38EvqtZn5OtHw==} + '@oxlint/binding-linux-riscv64-musl@1.57.0': + resolution: {integrity: sha512-BdrwD7haPZ8a9KrZhKJRSj6jwCor+Z8tHFZ3PT89Y3Jq5v3LfMfEePeAmD0LOTWpiTmzSzdmyw9ijneapiVHKQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxlint/binding-linux-s390x-gnu@1.48.0': - resolution: {integrity: sha512-c/+2oUWAOsQB5JTem0rW8ODlZllF6pAtGSGXoLSvPTonKI1vAwaKhD9Qw1X36jRbcI3Etkpu/9z/RRjMba8vFQ==} + '@oxlint/binding-linux-s390x-gnu@1.57.0': + resolution: {integrity: sha512-BNs+7ZNsRstVg2tpNxAXfMX/Iv5oZh204dVyb8Z37+/gCh+yZqNTlg6YwCLIMPSk5wLWIGOaQjT0GUOahKYImw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-gnu@1.48.0': - resolution: {integrity: sha512-PhauDqeFW5DGed6QxCY5lXZYKSlcBdCXJnH03ZNU6QmDZ0BFM/zSy1oPT2MNb1Afx1G6yOOVk8ErjWsQ7c59ng==} + '@oxlint/binding-linux-x64-gnu@1.57.0': + resolution: {integrity: sha512-AghS18w+XcENcAX0+BQGLiqjpqpaxKJa4cWWP0OWNLacs27vHBxu7TYkv9LUSGe5w8lOJHeMxcYfZNOAPqw2bg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxlint/binding-linux-x64-musl@1.48.0': - resolution: {integrity: sha512-6d7LIFFZGiavbHndhf1cK9kG9qmy2Dmr37sV9Ep7j3H+ciFdKSuOzdLh85mEUYMih+b+esMDlF5DU0WQRZPQjw==} + '@oxlint/binding-linux-x64-musl@1.57.0': + resolution: {integrity: sha512-E/FV3GB8phu/Rpkhz5T96hAiJlGzn91qX5yj5gU754P5cmVGXY1Jw/VSjDSlZBCY3VHjsVLdzgdkJaomEmcNOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxlint/binding-openharmony-arm64@1.48.0': - resolution: {integrity: sha512-r+0KK9lK6vFp3tXAgDMOW32o12dxvKS3B9La1uYMGdWAMoSeu2RzG34KmzSpXu6MyLDl4aSVyZLFM8KGdEjwaw==} + '@oxlint/binding-openharmony-arm64@1.57.0': + resolution: {integrity: sha512-xvZ2yZt0nUVfU14iuGv3V25jpr9pov5N0Wr28RXnHFxHCRxNDMtYPHV61gGLhN9IlXM96gI4pyYpLSJC5ClLCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.48.0': - resolution: {integrity: sha512-Nkw/MocyT3HSp0OJsKPXrcbxZqSPMTYnLLfsqsoiFKoL1ppVNL65MFa7vuTxJehPlBkjy+95gUgacZtuNMECrg==} + '@oxlint/binding-win32-arm64-msvc@1.57.0': + resolution: {integrity: sha512-Z4D8Pd0AyHBKeazhdIXeUUy5sIS3Mo0veOlzlDECg6PhRRKgEsBJCCV1n+keUZtQ04OP+i7+itS3kOykUyNhDg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.48.0': - resolution: {integrity: sha512-reO1SpefvRmeZSP+WeyWkQd1ArxxDD1MyKgMUKuB8lNuUoxk9QEohYtKnsfsxJuFwMT0JTr7p9wZjouA85GzGQ==} + '@oxlint/binding-win32-ia32-msvc@1.57.0': + resolution: {integrity: sha512-StOZ9nFMVKvevicbQfql6Pouu9pgbeQnu60Fvhz2S6yfMaii+wnueLnqQ5I1JPgNF0Syew4voBlAaHD13wH6tw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.48.0': - resolution: {integrity: sha512-T6zwhfcsrorqAybkOglZdPkTLlEwipbtdO1qjE+flbawvwOMsISoyiuaa7vM7zEyfq1hmDvMq1ndvkYFioranA==} + '@oxlint/binding-win32-x64-msvc@1.57.0': + resolution: {integrity: sha512-6PuxhYgth8TuW0+ABPOIkGdBYw+qYGxgIdXPHSVpiCDm+hqTTWCmC739St1Xni0DJBt8HnSHTG67i1y6gr8qrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -992,8 +992,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@25.2.3': - resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} + '@types/node@25.5.0': + resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -1081,82 +1081,82 @@ packages: resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-ybxez4ClJU12TUvX/IxGPIQfS26+Zia7kbB1L4RH+G8yzYg90RPt4njfJkU2WxP70Hp59zS2copPkaBz5gUJkQ==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-TN51zclpW+D9Qe55Do1ATeZaZ77E6H5JX5cG86xFTKhXaFaW35ANagS86t6d5xnf0quemXM6EP06so2WLSYCqw==} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-n9Ki8WTW82w6PlBTlrAQAjEUQB2V7C2oXrkN5U7ElwUH4FOostSFzZHuAdnPMbdzMx76P0pEw9FteYrLDA4m9g==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-iRzGkGdJmTGJHk8jI7PSjHjbDGrrw5oImTUfACevJFpB+dA5Hn/bsYlJQ5MR9KmDAJYoRHY1HQp6Dm30zXZw3A==} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-Osus82LSlwi1l3LoxLWKDuxh5E8JyWwkseBjr2n+TMaTuDPcRSzT8Jr4ywIp3NJpCUUV/LzR84i64jA6g8iVIw==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-qY10cp4PurJBD0TT7e4JwMUh2cGySLI+F7r5wZkkARSU/5aXAsWOImnVtshuzyv+MBfhcq8KHB1XMb62Kjrruw==} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-WRPMvTztPatQ91UzYWSp82NT45JmjMgo/pVgZjXYEWdF2rwS4ejzR6DnHq30jXhEPnMah1bTeOzSWFF2kvXUmg==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-MSumEH3jrfCXAtrkgm8DF4IeNiKAoJBpnyGS4WdjIQkqeI6c2wEGRXWJixOJRj3Lp7/CDx5Wo+ySFyjNdC4Uyg==} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-jcDhKCvhWQyMbra4MiqSgyUoSdM9mAiSkIdc80qScpk03aZOU+BZEmHz51S+fEn+8KRWuMuIHXM3sG3oX/EJZA==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-p93R+o9pV3IuypB3ydWXJSbzUgdHG3KD+5uFQZyo2A/QR9xnRPgTOhFnHXj9ml/RQvGHbmmAdFe/Xe2GiwnsSQ==} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-VmWvJ+TEuTPmZrhWe+buvvUvHbMyiD4ZLgxYPdYcJ3kRQlk2mD5lOq63ZISx1pDB8kYz5/R5xYKy/8gSIU5MgQ==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-OgoAfFryES4XS08PNXEJL54z4VbxY7VDwLb5z+TnMl5TMqYprk7cZZ+hQtq7XzwgailQyI162CQ81e+vtPuXqQ==} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-9zfUrKV3xBog2tpIR9NZOags+QJZSj7v9Ek7KdSkVu978IJqF9RX7oa2xftX+eiHySfV5ZQ8r2fdhdbYBk+kMw==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-BuzbtCqAYR/CmWDzaEw3/s80HLHXCIu+eSepRygjiLdd8CiNbIIAwCo2teQ1C5fjsWQ+Iu8iAJItOLpxWWTCzg==} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260218.1': - resolution: {integrity: sha512-hbGRXBk7abFvOQJk/7mc8K9q1kPkiyziyUsS8r8Hc1sLxrDFUbGgsW9p8qg67Xe1K6NUv/9UU2cdeIitUDexIQ==} + '@typescript/native-preview@7.0.0-dev.20260325.1': + resolution: {integrity: sha512-42I1oVqz2EOkE1vCrzazV3r+zVREq+le4m7Vr4OEz9taH2rhR02yxq+tNygKV3IOUOPLOXkX/soKcgrF3drDHA==} hasBin: true - '@vitest/coverage-v8@4.0.18': - resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==} + '@vitest/coverage-v8@4.1.1': + resolution: {integrity: sha512-nZ4RWwGCoGOQRMmU/Q9wlUY540RVRxJZ9lxFsFfy0QV7Zmo5VVBhB6Sl9Xa0KIp2iIs3zWfPlo9LcY1iqbpzCw==} peerDependencies: - '@vitest/browser': 4.0.18 - vitest: 4.0.18 + '@vitest/browser': 4.1.1 + vitest: 4.1.1 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@vitest/expect@4.1.1': + resolution: {integrity: sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==} - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + '@vitest/mocker@4.1.1': + resolution: {integrity: sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@4.0.18': - resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==} + '@vitest/pretty-format@4.1.1': + resolution: {integrity: sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==} - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@vitest/runner@4.1.1': + resolution: {integrity: sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==} - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/snapshot@4.1.1': + resolution: {integrity: sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==} - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/spy@4.1.1': + resolution: {integrity: sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==} - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@vitest/utils@4.1.1': + resolution: {integrity: sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==} accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -1179,8 +1179,8 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-v8-to-istanbul@0.3.11: - resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -1309,6 +1309,9 @@ packages: convert-gitmoji@0.1.5: resolution: {integrity: sha512-4wqOafJdk2tqZC++cjcbGcaJ13BZ3kwldf06PTiAQRAB76Z1KJwZNL1SaRZMi2w1FM9RYTgZ6QErS8NUl/GBmQ==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} @@ -1451,8 +1454,8 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} @@ -2014,17 +2017,17 @@ packages: resolution: {integrity: sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg==} engines: {node: '>=0.4.0'} - oxfmt@0.33.0: - resolution: {integrity: sha512-ogxBXA9R4BFeo8F1HeMIIxHr5kGnQwKTYZ5k131AEGOq1zLxInNhvYSpyRQ+xIXVMYfCN7yZHKff/lb5lp4auQ==} + oxfmt@0.42.0: + resolution: {integrity: sha512-QhejGErLSMReNuZ6vxgFHDyGoPbjTRNi6uGHjy0cvIjOQFqD6xmr/T+3L41ixR3NIgzcNiJ6ylQKpvShTgDfqg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint@1.48.0: - resolution: {integrity: sha512-m5vyVBgPtPhVCJc3xI//8je9lRc8bYuYB4R/1PH3VPGOjA4vjVhkHtyJukdEjYEjwrw4Qf1eIf+pP9xvfhfMow==} + oxlint@1.57.0: + resolution: {integrity: sha512-DGFsuBX5MFZX9yiDdtKjTrYPq45CZ8Fft6qCltJITYZxfwYjVdGf/6wycGYTACloauwIPxUnYhBVeZbHvleGhw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.12.2' + oxlint-tsgolint: '>=0.15.0' peerDependenciesMeta: oxlint-tsgolint: optional: true @@ -2357,6 +2360,9 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -2422,8 +2428,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + typescript@6.0.2: + resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} engines: {node: '>=14.17'} hasBin: true @@ -2439,8 +2445,8 @@ packages: typescript: optional: true - undici-types@7.16.0: - resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} undici@7.21.0: resolution: {integrity: sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==} @@ -2518,20 +2524,21 @@ packages: yaml: optional: true - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + vitest@4.1.1: + resolution: {integrity: sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.18 - '@vitest/browser-preview': 4.0.18 - '@vitest/browser-webdriverio': 4.0.18 - '@vitest/ui': 4.0.18 + '@vitest/browser-playwright': 4.1.1 + '@vitest/browser-preview': 4.1.1 + '@vitest/browser-webdriverio': 4.1.1 + '@vitest/ui': 4.1.1 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -2578,8 +2585,8 @@ packages: utf-8-validate: optional: true - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -2862,118 +2869,118 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@oxfmt/binding-android-arm-eabi@0.33.0': + '@oxfmt/binding-android-arm-eabi@0.42.0': optional: true - '@oxfmt/binding-android-arm64@0.33.0': + '@oxfmt/binding-android-arm64@0.42.0': optional: true - '@oxfmt/binding-darwin-arm64@0.33.0': + '@oxfmt/binding-darwin-arm64@0.42.0': optional: true - '@oxfmt/binding-darwin-x64@0.33.0': + '@oxfmt/binding-darwin-x64@0.42.0': optional: true - '@oxfmt/binding-freebsd-x64@0.33.0': + '@oxfmt/binding-freebsd-x64@0.42.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.33.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.42.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.33.0': + '@oxfmt/binding-linux-arm-musleabihf@0.42.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.33.0': + '@oxfmt/binding-linux-arm64-gnu@0.42.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.33.0': + '@oxfmt/binding-linux-arm64-musl@0.42.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.33.0': + '@oxfmt/binding-linux-ppc64-gnu@0.42.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.33.0': + '@oxfmt/binding-linux-riscv64-gnu@0.42.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.33.0': + '@oxfmt/binding-linux-riscv64-musl@0.42.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.33.0': + '@oxfmt/binding-linux-s390x-gnu@0.42.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.33.0': + '@oxfmt/binding-linux-x64-gnu@0.42.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.33.0': + '@oxfmt/binding-linux-x64-musl@0.42.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.33.0': + '@oxfmt/binding-openharmony-arm64@0.42.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.33.0': + '@oxfmt/binding-win32-arm64-msvc@0.42.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.33.0': + '@oxfmt/binding-win32-ia32-msvc@0.42.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.33.0': + '@oxfmt/binding-win32-x64-msvc@0.42.0': optional: true - '@oxlint/binding-android-arm-eabi@1.48.0': + '@oxlint/binding-android-arm-eabi@1.57.0': optional: true - '@oxlint/binding-android-arm64@1.48.0': + '@oxlint/binding-android-arm64@1.57.0': optional: true - '@oxlint/binding-darwin-arm64@1.48.0': + '@oxlint/binding-darwin-arm64@1.57.0': optional: true - '@oxlint/binding-darwin-x64@1.48.0': + '@oxlint/binding-darwin-x64@1.57.0': optional: true - '@oxlint/binding-freebsd-x64@1.48.0': + '@oxlint/binding-freebsd-x64@1.57.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.48.0': + '@oxlint/binding-linux-arm-gnueabihf@1.57.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.48.0': + '@oxlint/binding-linux-arm-musleabihf@1.57.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.48.0': + '@oxlint/binding-linux-arm64-gnu@1.57.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.48.0': + '@oxlint/binding-linux-arm64-musl@1.57.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.48.0': + '@oxlint/binding-linux-ppc64-gnu@1.57.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.48.0': + '@oxlint/binding-linux-riscv64-gnu@1.57.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.48.0': + '@oxlint/binding-linux-riscv64-musl@1.57.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.48.0': + '@oxlint/binding-linux-s390x-gnu@1.57.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.48.0': + '@oxlint/binding-linux-x64-gnu@1.57.0': optional: true - '@oxlint/binding-linux-x64-musl@1.48.0': + '@oxlint/binding-linux-x64-musl@1.57.0': optional: true - '@oxlint/binding-openharmony-arm64@1.48.0': + '@oxlint/binding-openharmony-arm64@1.57.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.48.0': + '@oxlint/binding-win32-arm64-msvc@1.57.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.48.0': + '@oxlint/binding-win32-ia32-msvc@1.57.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.48.0': + '@oxlint/binding-win32-x64-msvc@1.57.0': optional: true '@rollup/plugin-alias@5.1.1(rollup@4.57.1)': @@ -3107,7 +3114,7 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/chai@5.2.3': dependencies: @@ -3116,15 +3123,15 @@ snapshots: '@types/concat-stream@2.0.3': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/connect@3.4.38': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/cors@2.8.19': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/debug@4.1.12': dependencies: @@ -3138,7 +3145,7 @@ snapshots: '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -3159,9 +3166,9 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@25.2.3': + '@types/node@25.5.0': dependencies: - undici-types: 7.16.0 + undici-types: 7.18.2 '@types/qs@6.14.0': {} @@ -3173,57 +3180,57 @@ snapshots: '@types/send@1.2.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/sse@0.0.0': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 '@types/unist@3.0.3': {} '@types/ws@8.18.1': dependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 - '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/scope-manager': 8.55.0 - '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) '@typescript-eslint/visitor-keys': 8.55.0 eslint: 10.0.0(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.4.0(typescript@6.0.2) + typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.2) '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 eslint: 10.0.0(jiti@2.6.1) - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.55.0(typescript@6.0.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@6.0.2) '@typescript-eslint/types': 8.55.0 debug: 4.4.3 - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -3232,47 +3239,47 @@ snapshots: '@typescript-eslint/types': 8.55.0 '@typescript-eslint/visitor-keys': 8.55.0 - '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.55.0(typescript@6.0.2)': dependencies: - typescript: 5.9.3 + typescript: 6.0.2 - '@typescript-eslint/type-utils@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.2) + '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) debug: 4.4.3 eslint: 10.0.0(jiti@2.6.1) - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.4.0(typescript@6.0.2) + typescript: 6.0.2 transitivePeerDependencies: - supports-color '@typescript-eslint/types@8.55.0': {} - '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.55.0(typescript@6.0.2)': dependencies: - '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/project-service': 8.55.0(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@6.0.2) '@typescript-eslint/types': 8.55.0 '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 minimatch: 9.0.5 semver: 7.7.4 tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.4.0(typescript@6.0.2) + typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@10.0.0(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/types': 8.55.0 - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.2) eslint: 10.0.0(jiti@2.6.1) - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -3281,88 +3288,90 @@ snapshots: '@typescript-eslint/types': 8.55.0 eslint-visitor-keys: 4.2.1 - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260218.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260325.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260218.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260325.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260218.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260325.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260218.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260325.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260218.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260325.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260218.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260325.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260218.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260325.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260218.1': + '@typescript/native-preview@7.0.0-dev.20260325.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260218.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260218.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260218.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260218.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260218.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260218.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260218.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260325.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260325.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260325.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260325.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260325.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260325.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260325.1 - '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1))': + '@vitest/coverage-v8@4.1.1(vitest@4.1.1(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.18 - ast-v8-to-istanbul: 0.3.11 + '@vitest/utils': 4.1.1 + ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-reports: 3.2.0 magicast: 0.5.2 obug: 2.1.1 - std-env: 3.10.0 + std-env: 4.0.0 tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@25.2.3)(jiti@2.6.1) + vitest: 4.1.1(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)) - '@vitest/expect@4.0.18': + '@vitest/expect@4.1.1': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 + '@vitest/spy': 4.1.1 + '@vitest/utils': 4.1.1 chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1))': + '@vitest/mocker@4.1.1(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1))': dependencies: - '@vitest/spy': 4.0.18 + '@vitest/spy': 4.1.1 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1) - '@vitest/pretty-format@4.0.18': + '@vitest/pretty-format@4.1.1': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.18': + '@vitest/runner@4.1.1': dependencies: - '@vitest/utils': 4.0.18 + '@vitest/utils': 4.1.1 pathe: 2.0.3 - '@vitest/snapshot@4.0.18': + '@vitest/snapshot@4.1.1': dependencies: - '@vitest/pretty-format': 4.0.18 + '@vitest/pretty-format': 4.1.1 + '@vitest/utils': 4.1.1 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.18': {} + '@vitest/spy@4.1.1': {} - '@vitest/utils@4.0.18': + '@vitest/utils@4.1.1': dependencies: - '@vitest/pretty-format': 4.0.18 + '@vitest/pretty-format': 4.1.1 + convert-source-map: 2.0.0 tinyrainbow: 3.0.3 accepts@1.3.8: @@ -3385,7 +3394,7 @@ snapshots: assertion-error@2.0.1: {} - ast-v8-to-istanbul@0.3.11: + ast-v8-to-istanbul@1.0.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 @@ -3527,6 +3536,8 @@ snapshots: convert-gitmoji@0.1.5: {} + convert-source-map@2.0.0: {} + cookie@0.7.2: {} core-js-compat@3.48.0: @@ -3688,7 +3699,7 @@ snapshots: engine.io@6.6.5: dependencies: '@types/cors': 2.8.19 - '@types/node': 25.2.3 + '@types/node': 25.5.0 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -3703,7 +3714,7 @@ snapshots: entities@4.5.0: {} - es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} esbuild@0.25.12: optionalDependencies: @@ -3771,15 +3782,15 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-unjs@0.6.2(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): + eslint-config-unjs@0.6.2(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2): dependencies: '@eslint/js': 9.39.2 '@eslint/markdown': 7.5.1 eslint: 10.0.0(jiti@2.6.1) eslint-plugin-unicorn: 62.0.0(eslint@10.0.0(jiti@2.6.1)) globals: 17.3.0 - typescript: 5.9.3 - typescript-eslint: 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) + typescript: 6.0.2 + typescript-eslint: 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) transitivePeerDependencies: - supports-color @@ -4405,7 +4416,7 @@ snapshots: dependencies: brace-expansion: 2.0.2 - mkdist@2.4.1(typescript@5.9.3): + mkdist@2.4.1(typescript@6.0.2): dependencies: autoprefixer: 10.4.24(postcss@8.5.6) citty: 0.1.6 @@ -4421,7 +4432,7 @@ snapshots: semver: 7.7.4 tinyglobby: 0.2.15 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.2 mlly@1.8.0: dependencies: @@ -4484,51 +4495,51 @@ snapshots: options@0.0.6: {} - oxfmt@0.33.0: + oxfmt@0.42.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.33.0 - '@oxfmt/binding-android-arm64': 0.33.0 - '@oxfmt/binding-darwin-arm64': 0.33.0 - '@oxfmt/binding-darwin-x64': 0.33.0 - '@oxfmt/binding-freebsd-x64': 0.33.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.33.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.33.0 - '@oxfmt/binding-linux-arm64-gnu': 0.33.0 - '@oxfmt/binding-linux-arm64-musl': 0.33.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.33.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.33.0 - '@oxfmt/binding-linux-riscv64-musl': 0.33.0 - '@oxfmt/binding-linux-s390x-gnu': 0.33.0 - '@oxfmt/binding-linux-x64-gnu': 0.33.0 - '@oxfmt/binding-linux-x64-musl': 0.33.0 - '@oxfmt/binding-openharmony-arm64': 0.33.0 - '@oxfmt/binding-win32-arm64-msvc': 0.33.0 - '@oxfmt/binding-win32-ia32-msvc': 0.33.0 - '@oxfmt/binding-win32-x64-msvc': 0.33.0 - - oxlint@1.48.0: + '@oxfmt/binding-android-arm-eabi': 0.42.0 + '@oxfmt/binding-android-arm64': 0.42.0 + '@oxfmt/binding-darwin-arm64': 0.42.0 + '@oxfmt/binding-darwin-x64': 0.42.0 + '@oxfmt/binding-freebsd-x64': 0.42.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.42.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.42.0 + '@oxfmt/binding-linux-arm64-gnu': 0.42.0 + '@oxfmt/binding-linux-arm64-musl': 0.42.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.42.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.42.0 + '@oxfmt/binding-linux-riscv64-musl': 0.42.0 + '@oxfmt/binding-linux-s390x-gnu': 0.42.0 + '@oxfmt/binding-linux-x64-gnu': 0.42.0 + '@oxfmt/binding-linux-x64-musl': 0.42.0 + '@oxfmt/binding-openharmony-arm64': 0.42.0 + '@oxfmt/binding-win32-arm64-msvc': 0.42.0 + '@oxfmt/binding-win32-ia32-msvc': 0.42.0 + '@oxfmt/binding-win32-x64-msvc': 0.42.0 + + oxlint@1.57.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.48.0 - '@oxlint/binding-android-arm64': 1.48.0 - '@oxlint/binding-darwin-arm64': 1.48.0 - '@oxlint/binding-darwin-x64': 1.48.0 - '@oxlint/binding-freebsd-x64': 1.48.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.48.0 - '@oxlint/binding-linux-arm-musleabihf': 1.48.0 - '@oxlint/binding-linux-arm64-gnu': 1.48.0 - '@oxlint/binding-linux-arm64-musl': 1.48.0 - '@oxlint/binding-linux-ppc64-gnu': 1.48.0 - '@oxlint/binding-linux-riscv64-gnu': 1.48.0 - '@oxlint/binding-linux-riscv64-musl': 1.48.0 - '@oxlint/binding-linux-s390x-gnu': 1.48.0 - '@oxlint/binding-linux-x64-gnu': 1.48.0 - '@oxlint/binding-linux-x64-musl': 1.48.0 - '@oxlint/binding-openharmony-arm64': 1.48.0 - '@oxlint/binding-win32-arm64-msvc': 1.48.0 - '@oxlint/binding-win32-ia32-msvc': 1.48.0 - '@oxlint/binding-win32-x64-msvc': 1.48.0 + '@oxlint/binding-android-arm-eabi': 1.57.0 + '@oxlint/binding-android-arm64': 1.57.0 + '@oxlint/binding-darwin-arm64': 1.57.0 + '@oxlint/binding-darwin-x64': 1.57.0 + '@oxlint/binding-freebsd-x64': 1.57.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.57.0 + '@oxlint/binding-linux-arm-musleabihf': 1.57.0 + '@oxlint/binding-linux-arm64-gnu': 1.57.0 + '@oxlint/binding-linux-arm64-musl': 1.57.0 + '@oxlint/binding-linux-ppc64-gnu': 1.57.0 + '@oxlint/binding-linux-riscv64-gnu': 1.57.0 + '@oxlint/binding-linux-riscv64-musl': 1.57.0 + '@oxlint/binding-linux-s390x-gnu': 1.57.0 + '@oxlint/binding-linux-x64-gnu': 1.57.0 + '@oxlint/binding-linux-x64-musl': 1.57.0 + '@oxlint/binding-openharmony-arm64': 1.57.0 + '@oxlint/binding-win32-arm64-msvc': 1.57.0 + '@oxlint/binding-win32-ia32-msvc': 1.57.0 + '@oxlint/binding-win32-x64-msvc': 1.57.0 p-limit@3.1.0: dependencies: @@ -4764,11 +4775,11 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rollup-plugin-dts@6.3.0(rollup@4.57.1)(typescript@5.9.3): + rollup-plugin-dts@6.3.0(rollup@4.57.1)(typescript@6.0.2): dependencies: magic-string: 0.30.21 rollup: 4.57.1 - typescript: 5.9.3 + typescript: 6.0.2 optionalDependencies: '@babel/code-frame': 7.29.0 @@ -4872,6 +4883,8 @@ snapshots: std-env@3.10.0: {} + std-env@4.0.0: {} + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -4913,9 +4926,9 @@ snapshots: tinyrainbow@3.0.3: {} - ts-api-utils@2.4.0(typescript@5.9.3): + ts-api-utils@2.4.0(typescript@6.0.2): dependencies: - typescript: 5.9.3 + typescript: 6.0.2 type-check@0.4.0: dependencies: @@ -4923,22 +4936,22 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2): dependencies: - '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/parser': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@6.0.2) + '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@6.0.2) eslint: 10.0.0(jiti@2.6.1) - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - supports-color - typescript@5.9.3: {} + typescript@6.0.2: {} ufo@1.6.3: {} - unbuild@3.6.1(typescript@5.9.3): + unbuild@3.6.1(typescript@6.0.2): dependencies: '@rollup/plugin-alias': 5.1.1(rollup@4.57.1) '@rollup/plugin-commonjs': 28.0.9(rollup@4.57.1) @@ -4954,25 +4967,25 @@ snapshots: hookable: 5.5.3 jiti: 2.6.1 magic-string: 0.30.21 - mkdist: 2.4.1(typescript@5.9.3) + mkdist: 2.4.1(typescript@6.0.2) mlly: 1.8.0 pathe: 2.0.3 pkg-types: 2.3.0 pretty-bytes: 7.1.0 rollup: 4.57.1 - rollup-plugin-dts: 6.3.0(rollup@4.57.1)(typescript@5.9.3) + rollup-plugin-dts: 6.3.0(rollup@4.57.1)(typescript@6.0.2) scule: 1.3.0 tinyglobby: 0.2.15 untyped: 2.0.0 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.2 transitivePeerDependencies: - sass - vue - vue-sfc-transformer - vue-tsc - undici-types@7.16.0: {} + undici-types@7.18.2: {} undici@7.21.0: {} @@ -5017,7 +5030,7 @@ snapshots: vary@1.1.2: {} - vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1): + vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1): dependencies: esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.3) @@ -5026,46 +5039,36 @@ snapshots: rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 fsevents: 2.3.3 jiti: 2.6.1 - vitest@4.0.18(@types/node@25.2.3)(jiti@2.6.1): + vitest@4.1.1(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)): dependencies: - '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)) - '@vitest/pretty-format': 4.0.18 - '@vitest/runner': 4.0.18 - '@vitest/snapshot': 4.0.18 - '@vitest/spy': 4.0.18 - '@vitest/utils': 4.0.18 - es-module-lexer: 1.7.0 + '@vitest/expect': 4.1.1 + '@vitest/mocker': 4.1.1(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)) + '@vitest/pretty-format': 4.1.1 + '@vitest/runner': 4.1.1 + '@vitest/snapshot': 4.1.1 + '@vitest/spy': 4.1.1 + '@vitest/utils': 4.1.1 + es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.10.0 + std-env: 4.0.0 tinybench: 2.9.0 tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 25.5.0 transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml which@2.0.2: dependencies: @@ -5080,7 +5083,7 @@ snapshots: ws@8.18.3: {} - ws@8.19.0: {} + ws@8.20.0: {} wsl-utils@0.1.0: dependencies: From e01a2feceee22f3e7742d0e108427c1f4b42e3f2 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:18:30 +0100 Subject: [PATCH 20/24] fix(outgoing): update removeChunked comment for http/2 The comment still said "HTTP 1.0" but the logic now covers both HTTP/1.0 and HTTP/2 (anything that isn't HTTP/1.1). --- src/middleware/web-outgoing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index b7aae38..0a22555 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -4,7 +4,7 @@ import { type ProxyOutgoingMiddleware, defineProxyOutgoingMiddleware } from "./_ const redirectRegex = /^201|30([1278])$/; /** - * If is a HTTP 1.0 request, remove chunk headers + * Remove chunked transfer-encoding for HTTP/1.0 and HTTP/2 requests */ export const removeChunked = defineProxyOutgoingMiddleware((req, res, proxyRes) => { // HTTP/1.0 and HTTP/2 do not have transfer-encoding: chunked From aeb299f30e9bde4a1fccecaecf9c0872f5eab8ea Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:19:37 +0100 Subject: [PATCH 21/24] refactor(utils): simplify hasEncryptedConnection All supported Node.js versions (16+) have req.socket. The deprecated req.connection and pair fallbacks were unreachable dead code. --- src/_utils.ts | 36 ++--------------------------- test/middleware/ws-incoming.test.ts | 5 ++-- 2 files changed, 4 insertions(+), 37 deletions(-) diff --git a/src/_utils.ts b/src/_utils.ts index 05e3672..8af169f 100644 --- a/src/_utils.ts +++ b/src/_utils.ts @@ -224,40 +224,8 @@ export function getPort(req: httpNative.IncomingMessage | Http2ServerRequest): s export function hasEncryptedConnection( req: httpNative.IncomingMessage | Http2ServerRequest, ): boolean { - // Since Node.js v16 we now have req.socket - if ("socket" in req) { - /* v8 ignore start */ - - // encrypted is only present in TLS sockets, not plain net sockets - if ("encrypted" in req.socket) { - return req.socket.encrypted; - } - - // "pair" is deprecated and is not typed by @types/node, but it actually hasn't been removed yet - // we can still fall back to "pair" for backward compatibility, but normally not reachable - if ("pair" in req.socket) { - return !!req.socket.pair; - } - /* v8 ignore stop */ - } - - if ("connection" in req) { - /* v8 ignore start */ - - // encrypted is only present in TLS sockets, not plain net sockets - if ("encrypted" in req.connection) { - return req.connection.encrypted; - } - - // "pair" is deprecated and is not typed by @types/node, but it actually hasn't been removed yet - // we can still fall back to "pair" for backward compatibility, but normally not reachable - if ("pair" in req.connection) { - return !!req.connection.pair; - } - /* v8 ignore stop */ - } - - return false; + const socket = req.socket; + return !!socket && "encrypted" in socket && socket.encrypted; } /** diff --git a/test/middleware/ws-incoming.test.ts b/test/middleware/ws-incoming.test.ts index 9827e32..ae05b65 100644 --- a/test/middleware/ws-incoming.test.ts +++ b/test/middleware/ws-incoming.test.ts @@ -119,10 +119,9 @@ describe("middleware:ws-incoming", () => { socket: { remoteAddress: "192.168.1.3", remotePort: "8181", + encrypted: true, }, - connection: { - pair: true, - }, + connection: {}, headers: { host: "192.168.1.3:8181", }, From c4d76733ccc8e0ba92346591cce6cefee1f65a60 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:19:55 +0100 Subject: [PATCH 22/24] refactor(server): remove as any casts in listen() Use proper generic type assertions (as Req/Res) instead of as any for the listener callback closure. --- src/server.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/server.ts b/src/server.ts index 95ecc25..de59d08 100644 --- a/src/server.ts +++ b/src/server.ts @@ -68,15 +68,11 @@ export class ProxyServer< * @param hostname - The hostname to listen on */ listen(port: number, hostname?: string) { - interface ListenerCallback { - ( - req: http.IncomingMessage | http2.Http2ServerRequest, - res: http.ServerResponse | http2.Http2ServerResponse, - ): Promise; - } - - const closure: ListenerCallback = (req, res) => { - return this.web(req as any, res as any); + const closure = ( + req: http.IncomingMessage | http2.Http2ServerRequest, + res: http.ServerResponse | http2.Http2ServerResponse, + ) => { + return this.web(req as Req, res as Res); }; if (this.options.http2) { From dd69045cf9b838fb0f357731899e9b240d1f2f5a Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:25:19 +0100 Subject: [PATCH 23/24] test(http2): remove unnecessary async from describe callbacks Setup logic is already in beforeAll, async on describe is redundant. --- test/http2-proxy.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/http2-proxy.test.ts b/test/http2-proxy.test.ts index 024fc63..69d3121 100644 --- a/test/http2-proxy.test.ts +++ b/test/http2-proxy.test.ts @@ -26,7 +26,7 @@ const http2Agent = new Agent({ }); describe("http/2 listener", () => { - describe("http2 -> http", async () => { + describe("http2 -> http", () => { let source: http.Server; let sourcePort: number; let proxy: httpProxy.ProxyServer; @@ -91,7 +91,7 @@ describe("http/2 listener", () => { }); }); - describe("http2 -> https", async () => { + describe("http2 -> https", () => { let source: https.Server; let sourcePort: number; let proxy: httpProxy.ProxyServer; From 2ed0dfd9d6f4478512a234603b26dd1d7b04ce30 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:30:33 +0100 Subject: [PATCH 24/24] fix(outgoing): use explicit version check in `removeChunked` Use `req.httpVersion === "1.0" || req.httpVersionMajor >= 2` instead of `!== "1.1"` for clarity. Also restore `https.Server` in `_server` type union. --- src/middleware/web-outgoing.ts | 2 +- src/server.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index 0a22555..a347215 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -8,7 +8,7 @@ const redirectRegex = /^201|30([1278])$/; */ export const removeChunked = defineProxyOutgoingMiddleware((req, res, proxyRes) => { // HTTP/1.0 and HTTP/2 do not have transfer-encoding: chunked - if (req.httpVersion !== "1.1") { + if (req.httpVersion === "1.0" || req.httpVersionMajor >= 2) { delete proxyRes.headers["transfer-encoding"]; } }); diff --git a/src/server.ts b/src/server.ts index de59d08..8c13afd 100644 --- a/src/server.ts +++ b/src/server.ts @@ -37,7 +37,7 @@ export class ProxyServer< Res extends http.ServerResponse | http2.Http2ServerResponse = http.ServerResponse, > extends EventEmitter> { // we use http2.Http2Server to handle HTTP/1.1 HTTPS as well (with allowHTTP1 enabled) - private _server?: http.Server | http2.Http2SecureServer; + private _server?: http.Server | https.Server | http2.Http2SecureServer; _webPasses: ProxyMiddleware[] = [...webIncomingMiddleware]; _wsPasses: ProxyMiddleware[] = [...websocketIncomingMiddleware];