-
Notifications
You must be signed in to change notification settings - Fork 169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: correctly set Dispatcher
prototype for ProxyAgent
#451
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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`), | ||
]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we doing all that just to reclaim some bytes? It seems a little dangerous 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 304000 of them but yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added tests, hopefully those ensure we'll catch bugs if this hack breaks again in the future. |
||
|
||
Object.assign(Dispatcher.default.prototype, api.default); | ||
ProxyAgent = _ProxyAgent.default; | ||
} | ||
|
||
return new ProxyAgent(proxy); | ||
} |
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) { | ||
|
@@ -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:[email protected]`; | ||
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); | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these need to be loaded in a particular order?
I would recommend putting them in a separate file and just exposing the ProxyAgent from that file and add a comment on why those files are needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, any order, that's why I'm loading them concurrently.