Skip to content
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

Support global registration like global-agent (#314) #347

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/global-proxy-agent/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(The MIT License)

Copyright (c) 2013 Nathan Rajlich <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
59 changes: 59 additions & 0 deletions packages/global-proxy-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
global-proxy-agent
===========
### Registers provided agent as global `HTTP`/`HTTPS` proxy

This module provides an `enableGlobalProxyAgent` method to replace `http.globalAgent`/`https.globalAgent` with provided proxy agent.

Example
-------

```ts
import * as https from 'https';
import { ProxyAgent } from 'proxy-agent';
import { enableGlobalProxyAgent } from 'global-proxy-agent';

// You may use any proxy agent implementation (see https://github.com/TooTallNate/proxy-agents)
const agent = new ProxyAgent();

enableGlobalProxyAgent(agent)

// The rest works just like any other normal HTTP request
https.get('https://jsonip.com', (res) => {
console.log(res.statusCode, res.headers);
res.pipe(process.stdout);
});
```

API
---

### enableGlobalProxyAgent(proxyAgent: http.Agent | https.Agent, options?: GlobalProxyAgentOptions)

```ts
interface GlobalProxyAgentOptions {
forceGlobalAgent?: boolean;
http?: boolean; // true - skips http module
https?: boolean; // true - skips https module
}
```

Replaces global proxy agent and returns callback to disable global proxy.

Example

```ts
import * as https from 'https';
import { ProxyAgent } from 'proxy-agent';
import { enableGlobalProxyAgent } from 'global-proxy-agent';

const agent = new ProxyAgent();

const disableGlobalProxyAgent = enableGlobalProxyAgent(agent, { https: false })

https.get('https://jsonip.com', (res) => {
console.log(res.statusCode, res.headers);
res.pipe(process.stdout);
});

setTimeout(() => disableGlobalProxyAgent(), 5000)
```
5 changes: 5 additions & 0 deletions packages/global-proxy-agent/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @type {import('@ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
56 changes: 56 additions & 0 deletions packages/global-proxy-agent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "global-proxy-agent",
"version": "0.0.1",
"description": "Registers provided agent as global HTTP/HTTPS proxy",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "tsc",
"test": "jest --env node --verbose --bail",
"lint": "eslint . --ext .ts",
"pack": "node ../../scripts/pack.mjs"
},
"engines": {
"node": ">= 14"
},
"repository": {
"type": "git",
"url": "https://github.com/TooTallNate/proxy-agents.git",
"directory": "packages/global-proxy-agent"
},
"keywords": [
"http",
"https",
"socks",
"agent",
"proxy",
"global"
],
"author": "Nathan Rajlich <[email protected]> (http://n8.io/)",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",
"semver": "^7.6.3"
},
"devDependencies": {
"@types/agent-base": "^4.2.0",
"@types/debug": "^4.1.7",
"@types/jest": "^29.5.1",
"@types/node": "^14.18.45",
"@types/proxy-from-env": "^1.0.1",
"@types/semver": "^7.3.13",
"agent-base": "^7.1.1",
"async-listen": "^3.0.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.5",
"jest": "^29.5.0",
"proxy": "workspace:*",
"ts-jest": "^29.1.0",
"tsconfig": "workspace:*",
"typescript": "^5.0.4"
}
}
184 changes: 184 additions & 0 deletions packages/global-proxy-agent/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import http from 'http';
import https from 'https';
import semver from 'semver';
import createDebug from 'debug';

type AgentType = http.Agent | https.Agent;

const debug = createDebug('global-proxy-agent');

export function bindHttpMethod(
originalMethod: (...args: unknown[]) => unknown,
agent: AgentType,
forceGlobalAgent: boolean
) {
return (...args: unknown[]) => {
let url;
let options: Record<string, unknown>;
let callback;

if (typeof args[0] === 'string' || args[0] instanceof URL) {
url = args[0];

if (typeof args[1] === 'function') {
options = {};
callback = args[1];
} else {
options = {
...(args[1] as object),
};
callback = args[2];
}
} else {
options = {
...(args[0] as object),
};
callback = args[1];
}

if (forceGlobalAgent) {
options.agent = agent;
} else {
if (!options.agent) {
options.agent = agent;
}

if (
options.agent === http.globalAgent ||
options.agent === https.globalAgent
) {
options.agent = agent;
}
}

if (url) {
return originalMethod(url, options, callback);
} else {
return originalMethod(options, callback);
}
};
}

/**
* @property forceGlobalAgent Forces to use `global-proxy-agent` HTTP(S) agent even when request was explicitly constructed with another agent. (Default: `true`)
* @property http Defines that http module should use proxy. (Default: `true`)
* @property https Defines that https module should use proxy. (Default: `true`)
*/
export interface GlobalProxyAgentOptions {
forceGlobalAgent?: boolean;
http?: boolean;
https?: boolean;
}

const httpGet = http.get;
const httpRequest = http.request;
const httpsGet = https.get;
const httpsRequest = https.request;
const httpGlobalAgent = http.globalAgent;
const httpsGlobalAgent = https.globalAgent;

/**
* enableGlobalProxyAgent replaces http.globalAgent/https.globalAgent with provided proxyAgent
* @param proxyAgent
* @param options
* @see https://github.com/gajus/global-agent
* @return disableGlobalProxyAgent - Disables globalProxyAgent
*/
export function enableGlobalProxyAgent(
proxyAgent: AgentType,
options?: GlobalProxyAgentOptions
): () => void {
const forceGlobalAgent = options?.forceGlobalAgent ?? true;
const skipHttp = options?.http === false;
const skipHttps = options?.https === false;

// Overriding globalAgent was added in v11.7.
// @see https://nodejs.org/uk/blog/release/v11.7.0/
if (semver.gte(process.version, 'v11.7.0')) {
if (!skipHttp) {
debug('replace http.globalAgent');
// @see https://github.com/facebook/flow/issues/7670
http.globalAgent = proxyAgent;
}

if (!skipHttps) {
debug('replace https.globalAgent');
// @ts-expect-error Node.js version compatibility
https.globalAgent = proxyAgent;
}
}

// The reason this logic is used in addition to overriding http(s).globalAgent
// is because there is no guarantee that we set http(s).globalAgent variable
// before an instance of http(s).Agent has been already constructed by someone,
// e.g. Stripe SDK creates instances of http(s).Agent at the top-level.
// @see https://github.com/gajus/global-agent/pull/13
//
// We still want to override http(s).globalAgent when possible to enable logic
// in `bindHttpMethod`.
if (semver.gte(process.version, 'v10.0.0')) {
if (!skipHttp) {
debug('bindHttpMethod', 'http.get', forceGlobalAgent);
// @ts-expect-error seems like we are using wrong type for httpAgent
http.get = bindHttpMethod(httpGet, proxyAgent, forceGlobalAgent);

debug('bindHttpMethod', 'http.request', forceGlobalAgent);
// @ts-expect-error seems like we are using wrong type for httpAgent
http.request = bindHttpMethod(
// @ts-expect-error seems like we are using wrong type for httpAgent
httpRequest,
proxyAgent,
forceGlobalAgent
);
}

if (!skipHttps) {
debug('bindHttpMethod', 'https.get', forceGlobalAgent);
// @ts-expect-error seems like we are using wrong type for httpsAgent
https.get = bindHttpMethod(httpsGet, proxyAgent, forceGlobalAgent);

debug('bindHttpMethod', 'https.request', forceGlobalAgent);
// @ts-expect-error seems like we are using wrong type for httpsAgent
https.request = bindHttpMethod(
// @ts-expect-error seems like we are using wrong type for httpsAgent
httpsRequest,
proxyAgent,
forceGlobalAgent
);
}
} else {
// eslint-disable-next-line no-console
console.warn(
'attempt to initialize global proxy-agent in unsupported Node.js version was ignored'
);
}

return () => {
if (semver.gte(process.version, 'v11.7.0')) {
if (!skipHttp) {
debug('restore http.globalAgent');
http.globalAgent = httpGlobalAgent;
}
if (!skipHttps) {
debug('restore https.globalAgent');
https.globalAgent = httpsGlobalAgent;
}
}
if (semver.gte(process.version, 'v10.0.0')) {
if (!skipHttp) {
debug('restore http.get', forceGlobalAgent);
http.get = httpGet;

debug('restore http.request', forceGlobalAgent);
http.request = httpRequest;
}
if (!skipHttps) {
debug('restore https.get', forceGlobalAgent);
https.get = httpsGet;

debug('restore https.request', forceGlobalAgent);
https.request = httpsRequest;
}
}
};
}
15 changes: 15 additions & 0 deletions packages/global-proxy-agent/test/ssl-cert-snakeoil.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCzURxIqzer0ACAbX/lHdsn4Gd9PLKrf7EeDYfIdV0HZKPD8WDr
bBx2/fBu0OW2sjnzv/SVZbJ0DAuPE/p0+eT0qb2qC10iz9iTD7ribd7gxhirVb8y
b3fBjXsxc8V8p4Ny1LcvNSqCjwUbJqdRogfoJeTiqPM58z5sNzuv5iq7iwIDAQAB
AoGAPMQy4olrP0UotlzlJ36bowLP70ffgHCwU+/f4NWs5fF78c3du0oSx1w820Dd
Z7E0JF8bgnlJJTxjumPZz0RUCugrEHBKJmzEz3cxF5E3+7NvteZcjKn9D67RrM5x
1/uSZ9cqKE9cYvY4fSuHx18diyZ4axR/wB1Pea2utjjDM+ECQQDb9ZbmmaWMiRpQ
5Up+loxP7BZNPsEVsm+DVJmEFbaFgGfncWBqSIqnPNjMwTwj0OigTwCAEGPkfRVW
T0pbYWCxAkEA0LK7SCTwzyDmhASUalk0x+3uCAA6ryFdwJf/wd8TRAvVOmkTEldX
uJ7ldLvfrONYO3v56uKTU/SoNdZYzKtO+wJAX2KM4ctXYy5BXztPpr2acz4qHa1N
Bh+vBAC34fOYhyQ76r3b1btHhWZ5jbFuZwm9F2erC94Ps5IaoqcX07DSwQJAPKGw
h2U0EPkd/3zVIZCJJQya+vgWFIs9EZcXVtvYXQyTBkVApTN66MhBIYjzkub5205J
bVQmOV37AKklY1DhwQJAA1wos0cYxro02edzatxd0DIR2r4qqOqLkw6BhYHhq6HJ
ZvIcQkHqdSXzdETFc01I1znDGGIrJHcnvKWgBPoEUg==
-----END RSA PRIVATE KEY-----
12 changes: 12 additions & 0 deletions packages/global-proxy-agent/test/ssl-cert-snakeoil.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB1TCCAT4CCQDV5mPlzm9+izANBgkqhkiG9w0BAQUFADAvMS0wKwYDVQQDEyQ3
NTI3YmQ3Ny1hYjNlLTQ3NGItYWNlNy1lZWQ2MDUzOTMxZTcwHhcNMTUwNzA2MjI0
NTA3WhcNMjUwNzAzMjI0NTA3WjAvMS0wKwYDVQQDEyQ3NTI3YmQ3Ny1hYjNlLTQ3
NGItYWNlNy1lZWQ2MDUzOTMxZTcwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
ALNRHEirN6vQAIBtf+Ud2yfgZ308sqt/sR4Nh8h1XQdko8PxYOtsHHb98G7Q5bay
OfO/9JVlsnQMC48T+nT55PSpvaoLXSLP2JMPuuJt3uDGGKtVvzJvd8GNezFzxXyn
g3LUty81KoKPBRsmp1GiB+gl5OKo8znzPmw3O6/mKruLAgMBAAEwDQYJKoZIhvcN
AQEFBQADgYEACzoHUF8UV2Z6541Q2wKEA0UFUzmUjf/E1XwBO+1P15ZZ64uw34B4
1RwMPtAo9RY/PmICTWtNxWGxkzwb2JtDWtnxVER/lF8k2XcXPE76fxTHJF/BKk9J
QU8OTD1dd9gHCBviQB9TqntRZ5X7axjtuWjb2umY+owBYzAHZkp1HKI=
-----END CERTIFICATE-----
Loading