From 73d9a1e2d2f84906bf01952f1dca8adab576b7bf Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 12 Apr 2024 23:27:26 +0200 Subject: [PATCH] fix: correctly set `Dispatcher` prototype for `ProxyAgent` (#451) In an attempt to bundle only a subset of Undici code, we forgot to take some side-effect into account. --- package.json | 3 +++ sources/httpUtils.ts | 21 +++++++++++++----- tests/_registryServer.mjs | 45 ++++++++++++++++++++++++++++++++++++--- tests/main.test.ts | 2 +- yarn.lock | 29 +++++++++---------------- 5 files changed, 72 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 057bfa1a..34629243 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,9 @@ "v8-compile-cache": "^2.3.0", "which": "^4.0.0" }, + "resolutions": { + "undici-types": "6.x" + }, "scripts": { "build": "rm -rf dist shims && run build:bundle && ts-node ./mkshims.ts", "build:bundle": "esbuild ./sources/_lib.ts --bundle --platform=node --target=node18.17.0 --external:corepack --outfile='./dist/lib/corepack.cjs' --resolve-extensions='.ts,.mjs,.js'", diff --git a/sources/httpUtils.ts b/sources/httpUtils.ts index e995d1e9..5d5a667b 100644 --- a/sources/httpUtils.ts +++ b/sources/httpUtils.ts @@ -92,6 +92,8 @@ export async function fetchUrlStream(input: string | URL, init?: RequestInit) { return stream; } +let ProxyAgent: typeof import('undici').ProxyAgent; + async function getProxyAgent(input: string | URL) { const {getProxyForUrl} = await import(`proxy-from-env`); @@ -100,11 +102,20 @@ async function getProxyAgent(input: string | URL) { if (!proxy) return undefined; - // Doing a deep import here since undici isn't tree-shakeable - const {default: ProxyAgent} = (await import( - // @ts-expect-error No types for this specific file - `undici/lib/proxy-agent.js` - )) as { default: typeof import('undici').ProxyAgent }; + if (ProxyAgent == null) { + // Doing a deep import here since undici isn't tree-shakeable + const [api, Dispatcher, _ProxyAgent] = await Promise.all([ + // @ts-expect-error internal module is untyped + import(`undici/lib/api/index.js`), + // @ts-expect-error internal module is untyped + import(`undici/lib/dispatcher/dispatcher.js`), + // @ts-expect-error internal module is untyped + import(`undici/lib/dispatcher/proxy-agent.js`), + ]); + + Object.assign(Dispatcher.default.prototype, api.default); + ProxyAgent = _ProxyAgent.default; + } return new ProxyAgent(proxy); } diff --git a/tests/_registryServer.mjs b/tests/_registryServer.mjs index eb3f1afd..7f78f70c 100644 --- a/tests/_registryServer.mjs +++ b/tests/_registryServer.mjs @@ -1,6 +1,7 @@ import {createHash} from 'node:crypto'; import {once} from 'node:events'; import {createServer} from 'node:http'; +import {connect} from 'node:net'; import {gzipSync} from 'node:zlib'; function createSimpleTarArchive(fileName, fileContent, mode = 0o644) { @@ -110,12 +111,47 @@ const server = createServer((req, res) => { res.writeHead(500).end(`Internal Error`); throw new Error(`unsupported request`, {cause: {url: req.url, packageName}}); } -}).listen(0, `localhost`); +}); + +if (process.env.AUTH_TYPE === `PROXY`) { + const proxy = createServer((req, res) => { + res.writeHead(200, {[`Content-Type`]: `text/plain`}); + res.end(`okay`); + }); + proxy.on(`connect`, (req, clientSocket, head) => { + if (req.url !== `example.com:80`) { + // Reject all requests except those to `example.com` + clientSocket.end(`HTTP/1.1 404 Not Found\r\n\r\n`); + return; + } + const {address, port} = server.address(); + const serverSocket = connect(port, address, () => { + clientSocket.write(`HTTP/1.1 200 Connection Established\r\n` + + `Proxy-agent: Node.js-Proxy\r\n` + + `\r\n`); + serverSocket.write(head); + serverSocket.pipe(clientSocket); + clientSocket.pipe(serverSocket); + }); + }); + proxy.listen(0, `localhost`); + await once(proxy, `listening`); + const {address, port} = proxy.address(); + process.env.ALL_PROXY = `http://${address.includes(`:`) ? `[${address}]` : address}:${port}`; + + proxy.unref(); +} +server.listen(0, `localhost`); await once(server, `listening`); const {address, port} = server.address(); switch (process.env.AUTH_TYPE) { + case `PROXY`: + // The proxy set up above will redirect all requests to our custom registry, + process.env.COREPACK_NPM_REGISTRY = `http://user:pass@example.com`; + break; + case `COREPACK_NPM_REGISTRY`: process.env.COREPACK_NPM_REGISTRY = `http://user:pass@${address.includes(`:`) ? `[${address}]` : address}:${port}`; break; @@ -137,8 +173,11 @@ switch (process.env.AUTH_TYPE) { if (process.env.NOCK_ENV === `replay`) { const originalFetch = globalThis.fetch; globalThis.fetch = function fetch(i) { - if (!`${i}`.startsWith(`http://${address.includes(`:`) ? `[${address}]` : address}:${port}`)) - throw new Error; + if (!`${i}`.startsWith( + process.env.AUTH_TYPE === `PROXY` ? + `http://example.com` : + `http://${address.includes(`:`) ? `[${address}]` : address}:${port}`)) + throw new Error(`Unexpected request to ${i}`); return Reflect.apply(originalFetch, this, arguments); }; diff --git a/tests/main.test.ts b/tests/main.test.ts index 6d84e3fb..ba01f151 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -805,7 +805,7 @@ it(`should download yarn berry from custom registry`, async () => { }); }); -for (const authType of [`COREPACK_NPM_REGISTRY`, `COREPACK_NPM_TOKEN`, `COREPACK_NPM_PASSWORD`]) { +for (const authType of [`COREPACK_NPM_REGISTRY`, `COREPACK_NPM_TOKEN`, `COREPACK_NPM_PASSWORD`, `PROXY`]) { describe(`custom registry with auth ${authType}`, () => { beforeEach(() => { process.env.AUTH_TYPE = authType; // See `_registryServer.mjs` diff --git a/yarn.lock b/yarn.lock index 52596e86..8d39c265 100644 --- a/yarn.lock +++ b/yarn.lock @@ -730,13 +730,6 @@ __metadata: languageName: node linkType: hard -"@fastify/busboy@npm:^2.0.0": - version: 2.1.0 - resolution: "@fastify/busboy@npm:2.1.0" - checksum: 10c0/7bb641080aac7cf01d88749ad331af10ba9ec3713ec07cabbe833908c75df21bd56249bb6173bdec07f5a41896b21e3689316f86684c06635da45f91ff4565a2 - languageName: node - linkType: hard - "@humanwhocodes/config-array@npm:^0.11.13": version: 0.11.13 resolution: "@humanwhocodes/config-array@npm:0.11.13" @@ -1303,11 +1296,11 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:^20.4.6": - version: 20.10.5 - resolution: "@types/node@npm:20.10.5" + version: 20.12.6 + resolution: "@types/node@npm:20.12.6" dependencies: undici-types: "npm:~5.26.4" - checksum: 10c0/be30609aae0bfe492097815f166ccc07f465220cb604647fa4e5ec05a1d16c012a41b82b5f11ecfe2485cbb479d4d20384b95b809ca0bcff6d94d5bbafa645bb + checksum: 10c0/48ce732162cd6c02656aa5f996f0e695b57fdeb1ae762fbaa966afac2dcdcf52cb56be5ce1efb4babf8f97c2de545889aebc7f43c2e86f033487245c41fa1e6b languageName: node linkType: hard @@ -5991,19 +5984,17 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~5.26.4": - version: 5.26.5 - resolution: "undici-types@npm:5.26.5" - checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 +"undici-types@npm:6.x": + version: 6.12.0 + resolution: "undici-types@npm:6.12.0" + checksum: 10c0/439ad3a384b4b392ff7fbd98b50a29ea4ca0ca1899f29a967a043abb45f0e67a786b058f59a327b3007079cff35ed5337d20603f06b6bb6a5b27539ebecd9792 languageName: node linkType: hard "undici@npm:^6.6.1": - version: 6.6.2 - resolution: "undici@npm:6.6.2" - dependencies: - "@fastify/busboy": "npm:^2.0.0" - checksum: 10c0/c8c8a436059b13603f67ed4d917b4ba6d9ef282ac55c932c4790ee1a1c8cad1369da3c11b6e0b9df5a95ed1849cb98fa2f2310f6d0f9331dd359286c912497d2 + version: 6.12.0 + resolution: "undici@npm:6.12.0" + checksum: 10c0/5bbfd261ea20c8ed6bb3a22703acdae80cb40b1de0595a1c4df46f6602db78d7a387174c292c5640e339422acb1bfd1d2e0987ec086c057ccc24806edfd4688b languageName: node linkType: hard