Skip to content

Commit 3454f2f

Browse files
committed
WIP
1 parent 4fee5aa commit 3454f2f

File tree

9 files changed

+59
-8
lines changed

9 files changed

+59
-8
lines changed

packages/devtools-proxy-support/src/agent.spec.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ describe('createAgent', function () {
1414
url: string,
1515
agent: Agent
1616
): Promise<IncomingMessage & { body: string }> => {
17-
const getFn = new URL(url).protocol === 'https:' ? httpsGet : httpGet;
17+
const nodeJSBuiltinGet =
18+
new URL(url).protocol === 'https:' ? httpsGet : httpGet;
1819
const options = {
1920
agent,
2021
ca: setup.tlsOptions.ca,
2122
checkServerIdentity: () => undefined, // allow hostname mismatches
2223
};
2324
agents.push(agent);
2425
const res = await new Promise<IncomingMessage>((resolve, reject) =>
25-
getFn(url, options, resolve).once('error', reject)
26+
nodeJSBuiltinGet(url, options, resolve).once('error', reject)
2627
);
2728
let body = '';
2829
res.setEncoding('utf8');

packages/devtools-proxy-support/src/agent.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { SSHAgent } from './ssh';
1010
import type { ProxyLogEmitter } from './logging';
1111
import type { EventEmitter } from 'events';
1212

13+
// Helper type that represents an https.Agent (= connection factory)
14+
// with some custom properties that TS does not know about and/or
15+
// that we add for our own purposes.
1316
export type AgentWithInitialize = Agent & {
1417
// This is genuinely custom for our usage (to allow establishing an SSH tunnel
1518
// first before starting to push connections through it)
@@ -24,7 +27,7 @@ export type AgentWithInitialize = Agent & {
2427
cb: (err: Error | null, s?: Duplex) => void
2528
): void;
2629

27-
// http.Agent is an EventEmitter
30+
// http.Agent is an EventEmitter, just missing from @types/node
2831
} & Partial<EventEmitter>;
2932

3033
export function createAgent(

packages/devtools-proxy-support/src/fetch.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { DevtoolsProxyOptions } from './proxy-options';
66

77
declare const __webpack_require__: unknown;
88

9+
// The original version of this code was largely taken from
10+
// https://github.com/mongodb-js/mongosh/blob/8e6962432397154941f593c847d8f774bfd49f1c/packages/import-node-fetch/src/index.ts
911
async function importNodeFetch(): Promise<typeof fetch> {
1012
// Node-fetch is an ESM module from 3.x
1113
// Importing ESM modules to CommonJS is possible with a dynamic import.

packages/devtools-proxy-support/src/logging.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// Most mongoLogId() calls here come from code that was
2+
// previously part of the MongoDB Compass monorepo, hence the specific
3+
// values used here; in particular,
4+
// https://github.com/mongodb-js/compass/tree/55a5a608713d7316d158dc66febeb6b114d8b40d/packages/ssh-tunnel/src
5+
16
interface BaseSocks5RequestMetadata {
27
srcAddr: string;
38
srcPort: number;

packages/devtools-proxy-support/src/proxy-options.spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { HTTPServerProxyTestSetup } from '../test/helpers';
1818

1919
describe('proxy options handling', function () {
2020
describe('proxyConfForEnvVars', function () {
21-
it('should return a map of proxies and noProxy', function () {
21+
it('should return a map listing configured proxies and no-proxy hosts', function () {
2222
const env = {
2323
HTTP_PROXY: 'http://proxy.example.com',
2424
HTTPS_PROXY: 'https://proxy.example.com',
@@ -145,7 +145,12 @@ describe('proxy options handling', function () {
145145
).to.deep.equal({});
146146
});
147147
});
148+
148149
context('integration tests', function () {
150+
// These are some integration tests that leverage the fact that
151+
// Electron can be used as a script runner to verify that our generated
152+
// Electron proxyh configuration actually behaves the way it is intended to.
153+
149154
let childProcess: ChildProcess;
150155
let exitPromise: Promise<unknown>;
151156
let server: Server;
@@ -164,6 +169,8 @@ describe('proxy options handling', function () {
164169
setup = new HTTPServerProxyTestSetup();
165170
await setup.listen();
166171

172+
// Use a TCP connection for communication with the electron process;
173+
// see electron-test-server.js for details.
167174
server = createServer();
168175
server.listen(0);
169176
await once(server, 'listening');
@@ -185,7 +192,7 @@ describe('proxy options handling', function () {
185192
}
186193
);
187194
exitPromise = once(childProcess, 'exit').catch(() => {
188-
/* ignore */
195+
// ignore unhandledRejection warning/error
189196
});
190197
await once(childProcess, 'spawn');
191198

@@ -205,6 +212,8 @@ describe('proxy options handling', function () {
205212
};
206213

207214
testResolveProxy = async (proxyOptions, url) => {
215+
// https://www.electronjs.org/docs/latest/api/app#appsetproxyconfig
216+
// https://www.electronjs.org/docs/latest/api/app#appresolveproxyurl
208217
return await runJS(`app.setProxy(${JSON.stringify(
209218
translateToElectronProxyConfig(proxyOptions)
210219
)}).then(_ => {

packages/devtools-proxy-support/src/proxy-options.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ interface ElectronProxyConfig {
3535
proxyBypassRules?: string;
3636
}
3737

38+
// Reads through all environment variables and gathers proxy settings from them
3839
export function proxyConfForEnvVars(env: Record<string, string | undefined>): {
3940
map: Map<string, string>;
4041
noProxy: string;
@@ -52,6 +53,7 @@ export function proxyConfForEnvVars(env: Record<string, string | undefined>): {
5253
return { map, noProxy };
5354
}
5455

56+
// Whether a given URL should be proxied or not, according to `noProxy`
5557
function shouldProxy(noProxy: string, url: URL): boolean {
5658
if (!noProxy) return true;
5759
if (noProxy === '*') return false;
@@ -72,6 +74,8 @@ function shouldProxy(noProxy: string, url: URL): boolean {
7274
return true;
7375
}
7476

77+
// Create a function which returns the proxy URL for a given target URL,
78+
// based on the proxy config passed to it.
7579
export function proxyForUrl(
7680
proxyOptions: DevtoolsProxyOptions
7781
): (url: string) => string {
@@ -109,6 +113,17 @@ export function proxyForUrl(
109113

110114
function validateElectronProxyURL(url: URL | string): string {
111115
url = new URL(url.toString());
116+
// ssh and authenticated proxies are not supported by Electron/Chromium currently.
117+
// (See https://issues.chromium.org/issues/40829748, https://bugs.chromium.org/p/chromium/issues/detail?id=1309413
118+
// and related tickets for history.)
119+
// If we do want to support them at some point, a possible way to achieve this would be
120+
// to use a local in-application Socks5 proxy server which reaches out to the
121+
// actual target proxy (or directly connects, depending on the configuration),
122+
// and then passing the local proxy's information to Electron.
123+
// A core downside with this approach, however, is that because the proxy cannot be
124+
// authenticated, it would be available to any local application, which is problematic
125+
// when running on multi-user machine where the proxy would then be available to
126+
// arbitraty users.
112127
if (url.protocol === 'ssh:') {
113128
throw new Error(
114129
`Using ssh:// proxies for generic browser proxy usage is not supported (translating '${redactUrl(
@@ -150,11 +165,13 @@ function validateElectronProxyURL(url: URL | string): string {
150165
return url.toString().replace(/\/$/, '');
151166
}
152167

168+
// Convert our own `DevtoolsProxyOptions` to the format used by Electron.
153169
export function translateToElectronProxyConfig(
154170
proxyOptions: DevtoolsProxyOptions
155171
): ElectronProxyConfig {
156172
if (proxyOptions.proxy) {
157173
let url = proxyOptions.proxy;
174+
// https://en.wikipedia.org/wiki/Proxy_auto-config
158175
if (new URL(url).protocol.startsWith('pac+')) {
159176
url = url.replace('pac+', '');
160177
return {

packages/devtools-proxy-support/src/socks5.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ function createFakeHttpClientRequest(dstAddr: string, dstPort: number) {
6161
});
6262
}
6363

64+
// The original version of this code was largely taken from
65+
// https://github.com/mongodb-js/compass/tree/55a5a608713d7316d158dc66febeb6b114d8b40d/packages/ssh-tunnel/src
6466
class Socks5Server extends EventEmitter implements Tunnel {
6567
public logger: ProxyLogEmitter = new EventEmitter();
6668
private readonly agent: AgentWithInitialize;

packages/devtools-proxy-support/src/ssh.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import type { ProxyLogEmitter } from './logging';
1313
import { connect as tlsConnect } from 'tls';
1414
import type { Socket } from 'net';
1515

16+
// The original version of this code was largely taken from
17+
// https://github.com/mongodb-js/compass/tree/55a5a608713d7316d158dc66febeb6b114d8b40d/packages/ssh-tunnel/src
1618
export class SSHAgent extends AgentBase implements AgentWithInitialize {
1719
public logger: ProxyLogEmitter;
1820
private readonly proxyOptions: Readonly<DevtoolsProxyOptions>;
@@ -31,7 +33,7 @@ export class SSHAgent extends AgentBase implements AgentWithInitialize {
3133
constructor(options: DevtoolsProxyOptions, logger?: ProxyLogEmitter) {
3234
super();
3335
(this as AgentWithInitialize).on?.('error', () => {
34-
//Errors should not crash the process
36+
// Errors here should not crash the process
3537
});
3638
this.logger = logger ?? new EventEmitter();
3739
this.proxyOptions = options;

packages/devtools-proxy-support/test/electron-test-server.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
const { app } = require('electron');
33
const { once } = require('events');
44
const { connect } = require('net');
5+
6+
// Essentially a REPL that we use for running Electron code.
7+
// Communicates with a parent process via TCP and runs the code
8+
// it receives over that socket. Input and output are NUL-delimited
9+
// chunks of UTF-8 strings.
510
(async function () {
611
try {
712
await app.whenReady();
@@ -14,8 +19,13 @@ const { connect } = require('net');
1419
while (buffer.includes('\0')) {
1520
const readyToExecute = buffer.substring(0, buffer.indexOf('\0'));
1621
buffer = buffer.substring(buffer.indexOf('\0') + 1);
17-
const result = JSON.stringify(await eval(JSON.parse(readyToExecute)));
18-
socket.write(result + '\0');
22+
let result;
23+
try {
24+
result = await eval(JSON.parse(readyToExecute));
25+
} catch (err) {
26+
result = { message: err.message, stack: err.stack, ...err };
27+
}
28+
socket.write(JSON.stringify(result) + '\0');
1929
}
2030
}
2131
process.exit(0);

0 commit comments

Comments
 (0)