Skip to content

Commit f300a55

Browse files
committed
Inject proxy into Node <10 using global-tunnel
This also defers http module interception until it's first loaded, to avoid breaking other require interception hooks, e.g. anything that's mocking out http entirely.
1 parent 734207d commit f300a55

File tree

4 files changed

+127
-4
lines changed

4 files changed

+127
-4
lines changed

overrides/path/node

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ set -e
44
# Exclude ourselves from PATH within this script, to avoid recursing
55
PATH="${PATH/`dirname $0`:/}"
66

7+
GLOBAL_AGENT_PATH=`dirname $0`/prepend.js
8+
79
# Call node with the given arguments, prefixed with our extra logic
810
if command -v winpty >/dev/null 2>&1; then
9-
winpty node -r "`dirname $0`/node_modules/global-agent/bootstrap" "$@"
11+
winpty node -r "$GLOBAL_AGENT_PATH" "$@"
1012
else
11-
node -r "`dirname $0`/node_modules/global-agent/bootstrap" "$@"
13+
node -r "$GLOBAL_AGENT_PATH" "$@"
1214
fi

overrides/path/prepend.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
--require'd by node before loading any other modules.
3+
This file sets up a global agent for both the http
4+
and https modules, so they pick up the proxy from
5+
the environment.
6+
7+
Tested against Node 6, 8, 10 and 12.
8+
*/
9+
10+
let httpAlreadyIntercepted = false;
11+
12+
function interceptAllHttp() {
13+
if (httpAlreadyIntercepted) return;
14+
httpAlreadyIntercepted = true;
15+
16+
const MAJOR_NODEJS_VERSION = parseInt(process.version.slice(1).split('.')[0], 10);
17+
18+
if (MAJOR_NODEJS_VERSION >= 10) {
19+
// `global-agent` works with Node.js v10 and above.
20+
const globalAgent = require('global-agent');
21+
globalAgent.bootstrap();
22+
} else {
23+
// `global-tunnel-ng` works only with Node.js v10 and below.
24+
const globalTunnel = require('global-tunnel-ng');
25+
globalTunnel.initialize();
26+
}
27+
}
28+
29+
// Intercept calls to require() certain modules, to monkey-patch/wrap them.
30+
31+
// Primarily exists to intercept http/https, but could be used for any
32+
// other modules in future if required.
33+
34+
// For HTTP/HTTPS, we inject a global agent that adds our proxy settings as
35+
// the default for all HTTP requests. We could do this upfront, but instead
36+
// it's deferred until the first require(), in case the user cares about
37+
// require order (e.g. if they're mocking out the 'http' module completely).
38+
39+
const alreadyIntercepted = [];
40+
41+
// Normally false, but can be set to a list of modules to defer interception so
42+
// we can use the required modules as part of the monkey patching process.
43+
// E.g. global-agent requires http & https, so we would otherwise get circular
44+
// requires that can cause problems here.
45+
let delayedInterception = false;
46+
47+
const mod = require('module');
48+
const realLoad = mod._load;
49+
50+
// Given a just-loaded module's name, do whatever is required to set up interception
51+
function fixModule(name) {
52+
if (name === 'http' || name === 'https') {
53+
delayedInterception = [];
54+
55+
interceptAllHttp();
56+
57+
delayedInterception.forEach(function (modDetails) {
58+
fixModule(modDetails.name);
59+
});
60+
}
61+
}
62+
63+
// Our hook into require():
64+
mod._load = function (name) {
65+
const loadedModule = realLoad.apply(this, arguments);
66+
67+
// Should always be set, but check just in case. This also allows users to disable
68+
// interception explicitly, if need be.
69+
if (!process.env.HTTP_TOOLKIT_ACTIVE) return loadedModule;
70+
71+
// Don't mess with modules if we've seen them before
72+
if (alreadyIntercepted.indexOf(name) >= 0) return loadedModule;
73+
else alreadyIntercepted.push(name);
74+
75+
if (delayedInterception !== false) {
76+
delayedInterception.push({ name: name, loadedModule: loadedModule });
77+
} else {
78+
fixModule(name);
79+
}
80+
81+
return loadedModule;
82+
};

package-lock.json

Lines changed: 40 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"command-exists": "^1.2.8",
3737
"env-paths": "^1.0.0",
3838
"global-agent": "^1.12.2",
39+
"global-tunnel-ng": "^2.7.1",
3940
"graphql": "^14.0.2",
4041
"graphql-yoga": "^1.16.7",
4142
"lodash": "^4.17.11",

0 commit comments

Comments
 (0)