Skip to content

Commit

Permalink
fix: correctly set Dispatcher prototype for ProxyAgent (#451)
Browse files Browse the repository at this point in the history
In an attempt to bundle only a subset of Undici code, we forgot to take
some side-effect into account.
  • Loading branch information
aduh95 committed Apr 12, 2024
1 parent 07b58cc commit 73d9a1e
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 28 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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'",
Expand Down
21 changes: 16 additions & 5 deletions sources/httpUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);

Expand All @@ -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);
}
45 changes: 42 additions & 3 deletions tests/_registryServer.mjs
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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);
};
Expand Down
2 changes: 1 addition & 1 deletion tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
29 changes: 10 additions & 19 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down

0 comments on commit 73d9a1e

Please sign in to comment.