diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index ef959a4c..dd924717 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -73,18 +73,23 @@ jobs: run: pnpm run test:unit e2e: - name: E2E tests (Node.js 24) + name: E2E tests (Node.js ${{ matrix.node-version }}) if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + node-version: [20, 24] + steps: - name: Checkout repository uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 - - name: Use Node.js 24 + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 with: - node-version: 24 + node-version: ${{ matrix.node-version }} - name: Install pnpm and dependencies uses: apify/actions/pnpm-install@v1.1.2 diff --git a/package.json b/package.json index 4c707002..2b21b1e8 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "typescript": "^6.0.3", "typescript-eslint": "^8.20.0", "underscore": "^1.13.1", - "undici": "^7.16.0", + "undici": "^8.10.0", "vitest": "^4.1.11", "ws": "^8.2.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0eddcd65..aff14214 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,8 +94,8 @@ importers: specifier: ^1.13.1 version: 1.13.8 undici: - specifier: ^7.16.0 - version: 7.29.0 + specifier: ^8.10.0 + version: 8.10.0 vitest: specifier: ^4.1.11 version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(vite@8.2.2(@types/node@26.4.0)(esbuild@0.28.2)(tsx@4.23.12)) @@ -2125,9 +2125,9 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} - engines: {node: '>=20.18.1'} + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} + engines: {node: '>=22.19.0'} unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -4368,7 +4368,7 @@ snapshots: undici-types@8.3.0: {} - undici@7.29.0: {} + undici@8.10.0: {} unpipe@1.0.0: {} diff --git a/test/utils/http_client.ts b/test/utils/http_client.ts index 17149424..d5528ce3 100644 --- a/test/utils/http_client.ts +++ b/test/utils/http_client.ts @@ -1,4 +1,5 @@ import { Buffer } from 'node:buffer'; +import net from 'node:net'; import type { Readable } from 'node:stream'; import util from 'node:util'; import zlib from 'node:zlib'; @@ -80,7 +81,18 @@ export const createDispatcher = (opts: ConnectionOpts): Dispatcher => { if (new URL(opts.url).protocol === 'http:') { return new Client(proxy.origin, { connect: tls, connectTimeout }).compose(forwardToProxy(token)); } - return new ProxyAgent({ uri: proxy.origin, token, connectTimeout, requestTls: tls, proxyTls: tls }); + + // `undici`'s WebSocket support rewrites ws(s): to http(s): before dispatching, so a `ws:` + // target looks like plain HTTP to `ProxyAgent` here. Since undici 8, `ProxyAgent` only + // tunnels plain-HTTP requests through CONNECT when `proxyTunnel` is set - otherwise it + // forwards them (no CONNECT), which breaks the Upgrade handshake through this proxy. + const proxyTunnel = true; + // undici passes the proxy's literal hostname as the TLS `servername` for the proxy + // connection; Node's TLS layer rejects an IP address there. Certificate validation is + // already disabled above when needed, so any non-IP placeholder is fine. + const proxyTls = net.isIP(proxy.hostname) ? { ...tls, servername: 'localhost-test' } : tls; + + return new ProxyAgent({ uri: proxy.origin, token, connectTimeout, requestTls: tls, proxyTls, proxyTunnel }); }; /** Splits credentials out of the URL into a Basic header. */