peep-proxy 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +1 -1
- package/dist/cli.js +31 -2
- package/dist/proxy/index.d.ts +1 -0
- package/dist/proxy/index.js +1 -0
- package/dist/proxy/proxy-server.js +91 -10
- package/dist/proxy/types.d.ts +2 -0
- package/dist/proxy/upstream.d.ts +7 -0
- package/dist/proxy/upstream.js +54 -0
- package/package.json +1 -1
package/dist/app.js
CHANGED
|
@@ -158,5 +158,5 @@ export default function App({ store, port, onQuit }) {
|
|
|
158
158
|
if (quitting) {
|
|
159
159
|
return (_jsxs(Box, { flexDirection: "column", height: rows, paddingTop: 1, paddingLeft: 2, children: [_jsx(Text, { bold: true, children: "Shutting down\u2026" }), quitSteps.map((msg) => (_jsxs(Text, { dimColor: true, children: [" ", msg] }, msg)))] }));
|
|
160
160
|
}
|
|
161
|
-
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [
|
|
161
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { flexDirection: "row", height: available, children: [_jsx(DomainSidebar, { items: visibleItems, selectedIndex: sidebarSelectedIndex, scrollOffset: sidebarScrollOffset, viewportHeight: sidebarViewportHeight, width: SIDEBAR_WIDTH, height: available, isActive: activePanel === "sidebar" }), modalOpen ? (_jsx(Box, { width: contentWidth, height: available, alignItems: "center", justifyContent: "center", children: _jsx(SortModal, { sortConfig: sortConfig, onSelect: selectColumn, onClose: closeModal }) })) : (_jsxs(Box, { flexDirection: "column", width: contentWidth, children: [_jsx(RequestList, { entries: sortedEntries, selectedIndex: selectedIndex, scrollOffset: scrollOffset, viewportHeight: listViewportHeight, sortConfig: sortConfig, columnWidths: columnWidths, width: contentWidth, height: listHeight, isActive: activePanel === "list" }), selectedEntry && detailHeight > 0 && (_jsx(DetailPanel, { entry: selectedEntry, activePanel: activePanel, requestTab: requestTab, responseTab: responseTab, width: contentWidth, height: detailHeight }))] }))] }), _jsx(StatusBar, { port: port, requestCount: sortedEntries.length, selectedIndex: selectedIndex, columns: columns, activePanel: activePanel, notification: notification })] }));
|
|
162
162
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
3
4
|
import * as os from "node:os";
|
|
4
5
|
import * as path from "node:path";
|
|
5
6
|
import { render } from "ink";
|
|
@@ -14,10 +15,14 @@ const cli = meow(`
|
|
|
14
15
|
$ peep
|
|
15
16
|
|
|
16
17
|
Options
|
|
17
|
-
--port
|
|
18
|
+
--port Proxy port (default: 8080)
|
|
19
|
+
--upstream-proxy Upstream proxy URL
|
|
20
|
+
--ca-cert Path to additional CA certificate (PEM)
|
|
18
21
|
|
|
19
22
|
Examples
|
|
20
23
|
$ peep --port=3128
|
|
24
|
+
$ peep --upstream-proxy=http://proxy:8080
|
|
25
|
+
$ peep --ca-cert=/path/to/zscaler-ca.pem
|
|
21
26
|
`, {
|
|
22
27
|
importMeta: import.meta,
|
|
23
28
|
flags: {
|
|
@@ -25,6 +30,12 @@ const cli = meow(`
|
|
|
25
30
|
type: "number",
|
|
26
31
|
default: 8080,
|
|
27
32
|
},
|
|
33
|
+
upstreamProxy: {
|
|
34
|
+
type: "string",
|
|
35
|
+
},
|
|
36
|
+
caCert: {
|
|
37
|
+
type: "string",
|
|
38
|
+
},
|
|
28
39
|
},
|
|
29
40
|
});
|
|
30
41
|
const port = cli.flags.port;
|
|
@@ -61,7 +72,25 @@ if (findNssProfiles().length > 0 && !isNssTrusted()) {
|
|
|
61
72
|
break;
|
|
62
73
|
}
|
|
63
74
|
}
|
|
64
|
-
const
|
|
75
|
+
const upstreamProxy = cli.flags.upstreamProxy
|
|
76
|
+
? new URL(cli.flags.upstreamProxy)
|
|
77
|
+
: undefined;
|
|
78
|
+
const extraCaCerts = [];
|
|
79
|
+
if (cli.flags.caCert) {
|
|
80
|
+
try {
|
|
81
|
+
extraCaCerts.push(readFileSync(cli.flags.caCert, "utf-8"));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
process.stderr.write(`Failed to read CA certificate: ${cli.flags.caCert}\n`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const proxy = new ProxyServer({
|
|
89
|
+
port,
|
|
90
|
+
ca,
|
|
91
|
+
upstreamProxy,
|
|
92
|
+
extraCaCerts: extraCaCerts.length > 0 ? extraCaCerts : undefined,
|
|
93
|
+
});
|
|
65
94
|
const store = new TrafficStore(proxy);
|
|
66
95
|
await proxy.start();
|
|
67
96
|
const proxyService = enableSystemProxy(port);
|
package/dist/proxy/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { ProxyServer } from "./proxy-server.js";
|
|
2
2
|
export { loadOrCreateCA } from "./ca.js";
|
|
3
|
+
export { connectThroughProxy, getUpstreamProxy, shouldBypass, } from "./upstream.js";
|
|
3
4
|
export type { CaConfig, ProxyConfig, ProxyConnectEvent, ProxyErrorEvent, ProxyEventMap, ProxyRequestEvent, ProxyResponseEvent, RequestId, } from "./types.js";
|
package/dist/proxy/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _ProxyServer_instances, _ProxyServer_config, _ProxyServer_server, _ProxyServer_emitter, _ProxyServer_certCache, _ProxyServer_sockets, _ProxyServer_tunnelSockets, _ProxyServer_mitmServers, _ProxyServer_handleRequest, _ProxyServer_handleConnect, _ProxyServer_handleTunnel, _ProxyServer_handleMitm;
|
|
12
|
+
var _ProxyServer_instances, _ProxyServer_config, _ProxyServer_server, _ProxyServer_emitter, _ProxyServer_upstream, _ProxyServer_certCache, _ProxyServer_sockets, _ProxyServer_tunnelSockets, _ProxyServer_mitmServers, _ProxyServer_handleRequest, _ProxyServer_handleConnect, _ProxyServer_handleTunnel, _ProxyServer_handleMitm;
|
|
13
13
|
import { randomUUID } from "node:crypto";
|
|
14
14
|
import * as http from "node:http";
|
|
15
15
|
import * as https from "node:https";
|
|
@@ -17,12 +17,14 @@ import * as net from "node:net";
|
|
|
17
17
|
import * as tls from "node:tls";
|
|
18
18
|
import { EventEmitter } from "node:events";
|
|
19
19
|
import { generateHostCert } from "./ca.js";
|
|
20
|
+
import { connectThroughProxy, getUpstreamProxy, shouldBypass, } from "./upstream.js";
|
|
20
21
|
export class ProxyServer {
|
|
21
22
|
constructor(config) {
|
|
22
23
|
_ProxyServer_instances.add(this);
|
|
23
24
|
_ProxyServer_config.set(this, void 0);
|
|
24
25
|
_ProxyServer_server.set(this, void 0);
|
|
25
26
|
_ProxyServer_emitter.set(this, void 0);
|
|
27
|
+
_ProxyServer_upstream.set(this, void 0);
|
|
26
28
|
_ProxyServer_certCache.set(this, new Map());
|
|
27
29
|
_ProxyServer_sockets.set(this, new Set());
|
|
28
30
|
_ProxyServer_tunnelSockets.set(this, new Set());
|
|
@@ -56,13 +58,23 @@ export class ProxyServer {
|
|
|
56
58
|
timestamp,
|
|
57
59
|
};
|
|
58
60
|
__classPrivateFieldGet(this, _ProxyServer_emitter, "f").emit("request", requestEvent);
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
const upstreamHttp = __classPrivateFieldGet(this, _ProxyServer_upstream, "f").http;
|
|
62
|
+
const useUpstream = upstreamHttp && !shouldBypass(parsed.hostname);
|
|
63
|
+
const upstreamOptions = useUpstream
|
|
64
|
+
? {
|
|
65
|
+
hostname: upstreamHttp.hostname,
|
|
66
|
+
port: Number(upstreamHttp.port) || 80,
|
|
67
|
+
path: clientReq.url,
|
|
68
|
+
method: clientReq.method,
|
|
69
|
+
headers: clientReq.headers,
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
hostname: parsed.hostname,
|
|
73
|
+
port: parsed.port || 80,
|
|
74
|
+
path: parsed.pathname + parsed.search,
|
|
75
|
+
method: clientReq.method,
|
|
76
|
+
headers: clientReq.headers,
|
|
77
|
+
};
|
|
66
78
|
const upstreamReq = http.request(upstreamOptions, (upstreamRes) => {
|
|
67
79
|
const chunks = [];
|
|
68
80
|
upstreamRes.on("data", (chunk) => {
|
|
@@ -103,6 +115,7 @@ export class ProxyServer {
|
|
|
103
115
|
}
|
|
104
116
|
});
|
|
105
117
|
__classPrivateFieldSet(this, _ProxyServer_config, config, "f");
|
|
118
|
+
__classPrivateFieldSet(this, _ProxyServer_upstream, getUpstreamProxy(config.upstreamProxy), "f");
|
|
106
119
|
__classPrivateFieldSet(this, _ProxyServer_emitter, new EventEmitter(), "f");
|
|
107
120
|
__classPrivateFieldSet(this, _ProxyServer_server, http.createServer(__classPrivateFieldGet(this, _ProxyServer_handleRequest, "f")), "f");
|
|
108
121
|
__classPrivateFieldGet(this, _ProxyServer_server, "f").on("connect", __classPrivateFieldGet(this, _ProxyServer_handleConnect, "f"));
|
|
@@ -152,7 +165,7 @@ export class ProxyServer {
|
|
|
152
165
|
__classPrivateFieldGet(this, _ProxyServer_emitter, "f").off(event, listener);
|
|
153
166
|
}
|
|
154
167
|
}
|
|
155
|
-
_ProxyServer_config = new WeakMap(), _ProxyServer_server = new WeakMap(), _ProxyServer_emitter = new WeakMap(), _ProxyServer_certCache = new WeakMap(), _ProxyServer_sockets = new WeakMap(), _ProxyServer_tunnelSockets = new WeakMap(), _ProxyServer_mitmServers = new WeakMap(), _ProxyServer_handleRequest = new WeakMap(), _ProxyServer_handleConnect = new WeakMap(), _ProxyServer_instances = new WeakSet(), _ProxyServer_handleTunnel = function _ProxyServer_handleTunnel(host, port, clientSocket, head) {
|
|
168
|
+
_ProxyServer_config = new WeakMap(), _ProxyServer_server = new WeakMap(), _ProxyServer_emitter = new WeakMap(), _ProxyServer_upstream = new WeakMap(), _ProxyServer_certCache = new WeakMap(), _ProxyServer_sockets = new WeakMap(), _ProxyServer_tunnelSockets = new WeakMap(), _ProxyServer_mitmServers = new WeakMap(), _ProxyServer_handleRequest = new WeakMap(), _ProxyServer_handleConnect = new WeakMap(), _ProxyServer_instances = new WeakSet(), _ProxyServer_handleTunnel = function _ProxyServer_handleTunnel(host, port, clientSocket, head) {
|
|
156
169
|
const id = randomUUID();
|
|
157
170
|
__classPrivateFieldGet(this, _ProxyServer_emitter, "f").emit("connect", {
|
|
158
171
|
id,
|
|
@@ -160,6 +173,39 @@ _ProxyServer_config = new WeakMap(), _ProxyServer_server = new WeakMap(), _Proxy
|
|
|
160
173
|
port,
|
|
161
174
|
timestamp: Date.now(),
|
|
162
175
|
});
|
|
176
|
+
const upstreamHttps = __classPrivateFieldGet(this, _ProxyServer_upstream, "f").https;
|
|
177
|
+
const useUpstream = upstreamHttps && !shouldBypass(host);
|
|
178
|
+
if (useUpstream) {
|
|
179
|
+
connectThroughProxy(upstreamHttps, host, port).then((upstreamSocket) => {
|
|
180
|
+
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
181
|
+
if (head.length > 0) {
|
|
182
|
+
upstreamSocket.write(head);
|
|
183
|
+
}
|
|
184
|
+
upstreamSocket.pipe(clientSocket);
|
|
185
|
+
clientSocket.pipe(upstreamSocket);
|
|
186
|
+
__classPrivateFieldGet(this, _ProxyServer_tunnelSockets, "f").add(upstreamSocket);
|
|
187
|
+
upstreamSocket.once("close", () => __classPrivateFieldGet(this, _ProxyServer_tunnelSockets, "f").delete(upstreamSocket));
|
|
188
|
+
upstreamSocket.on("error", (error) => {
|
|
189
|
+
__classPrivateFieldGet(this, _ProxyServer_emitter, "f").emit("error", {
|
|
190
|
+
id,
|
|
191
|
+
error,
|
|
192
|
+
phase: "connect",
|
|
193
|
+
});
|
|
194
|
+
clientSocket.end();
|
|
195
|
+
});
|
|
196
|
+
clientSocket.on("error", () => {
|
|
197
|
+
upstreamSocket.end();
|
|
198
|
+
});
|
|
199
|
+
}, (error) => {
|
|
200
|
+
__classPrivateFieldGet(this, _ProxyServer_emitter, "f").emit("error", {
|
|
201
|
+
id,
|
|
202
|
+
error: error,
|
|
203
|
+
phase: "connect",
|
|
204
|
+
});
|
|
205
|
+
clientSocket.end();
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
163
209
|
const upstreamSocket = net.connect(port, host, () => {
|
|
164
210
|
clientSocket.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
165
211
|
if (head.length > 0) {
|
|
@@ -202,7 +248,7 @@ _ProxyServer_config = new WeakMap(), _ProxyServer_server = new WeakMap(), _Proxy
|
|
|
202
248
|
clientReq.on("data", (chunk) => {
|
|
203
249
|
reqChunks.push(chunk);
|
|
204
250
|
});
|
|
205
|
-
clientReq.on("end", () => {
|
|
251
|
+
clientReq.on("end", async () => {
|
|
206
252
|
const reqBody = Buffer.concat(reqChunks);
|
|
207
253
|
const requestEvent = {
|
|
208
254
|
id,
|
|
@@ -222,6 +268,41 @@ _ProxyServer_config = new WeakMap(), _ProxyServer_server = new WeakMap(), _Proxy
|
|
|
222
268
|
method: clientReq.method,
|
|
223
269
|
headers: { ...clientReq.headers, host },
|
|
224
270
|
};
|
|
271
|
+
const upstreamHttps = __classPrivateFieldGet(this, _ProxyServer_upstream, "f").https;
|
|
272
|
+
const useUpstream = upstreamHttps && !shouldBypass(host);
|
|
273
|
+
if (useUpstream) {
|
|
274
|
+
try {
|
|
275
|
+
const tunnelSocket = await connectThroughProxy(upstreamHttps, host, port);
|
|
276
|
+
const extraCa = __classPrivateFieldGet(this, _ProxyServer_config, "f").extraCaCerts;
|
|
277
|
+
upstreamOptions.createConnection = () => tls.connect({
|
|
278
|
+
socket: tunnelSocket,
|
|
279
|
+
servername: host,
|
|
280
|
+
...(extraCa?.length && {
|
|
281
|
+
ca: [...tls.rootCertificates, ...extraCa],
|
|
282
|
+
}),
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
__classPrivateFieldGet(this, _ProxyServer_emitter, "f").emit("error", {
|
|
287
|
+
id,
|
|
288
|
+
error: error,
|
|
289
|
+
phase: "connect",
|
|
290
|
+
});
|
|
291
|
+
if (!clientRes.headersSent) {
|
|
292
|
+
clientRes.writeHead(502, {
|
|
293
|
+
"Content-Type": "text/plain",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
clientRes.end("Bad Gateway");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else if (__classPrivateFieldGet(this, _ProxyServer_config, "f").extraCaCerts?.length) {
|
|
301
|
+
upstreamOptions.ca = [
|
|
302
|
+
...tls.rootCertificates,
|
|
303
|
+
...__classPrivateFieldGet(this, _ProxyServer_config, "f").extraCaCerts,
|
|
304
|
+
];
|
|
305
|
+
}
|
|
225
306
|
const upstreamReq = https.request(upstreamOptions, (upstreamRes) => {
|
|
226
307
|
const chunks = [];
|
|
227
308
|
upstreamRes.on("data", (chunk) => {
|
package/dist/proxy/types.d.ts
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type * as net from "node:net";
|
|
2
|
+
export declare function getUpstreamProxy(explicit?: URL): {
|
|
3
|
+
http?: URL;
|
|
4
|
+
https?: URL;
|
|
5
|
+
};
|
|
6
|
+
export declare function shouldBypass(hostname: string): boolean;
|
|
7
|
+
export declare function connectThroughProxy(proxyUrl: URL, host: string, port: number): Promise<net.Socket>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as http from "node:http";
|
|
2
|
+
export function getUpstreamProxy(explicit) {
|
|
3
|
+
if (explicit) {
|
|
4
|
+
return { http: explicit, https: explicit };
|
|
5
|
+
}
|
|
6
|
+
const httpsRaw = process.env["HTTPS_PROXY"] ?? process.env["https_proxy"];
|
|
7
|
+
const httpRaw = process.env["HTTP_PROXY"] ?? process.env["http_proxy"];
|
|
8
|
+
return {
|
|
9
|
+
http: httpRaw ? new URL(httpRaw) : undefined,
|
|
10
|
+
https: httpsRaw ? new URL(httpsRaw) : undefined,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function shouldBypass(hostname) {
|
|
14
|
+
const raw = process.env["NO_PROXY"] ?? process.env["no_proxy"] ?? "";
|
|
15
|
+
if (!raw)
|
|
16
|
+
return false;
|
|
17
|
+
if (raw === "*")
|
|
18
|
+
return true;
|
|
19
|
+
const entries = raw.split(",").map((e) => e.trim().toLowerCase());
|
|
20
|
+
const host = hostname.toLowerCase();
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (!entry)
|
|
23
|
+
continue;
|
|
24
|
+
if (host === entry)
|
|
25
|
+
return true;
|
|
26
|
+
if (entry.startsWith(".") && host.endsWith(entry))
|
|
27
|
+
return true;
|
|
28
|
+
if (host.endsWith(`.${entry}`))
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
export function connectThroughProxy(proxyUrl, host, port) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const req = http.request({
|
|
36
|
+
hostname: proxyUrl.hostname,
|
|
37
|
+
port: Number(proxyUrl.port) || 80,
|
|
38
|
+
method: "CONNECT",
|
|
39
|
+
path: `${host}:${port}`,
|
|
40
|
+
headers: { Host: `${host}:${port}` },
|
|
41
|
+
});
|
|
42
|
+
req.on("connect", (res, socket) => {
|
|
43
|
+
if (res.statusCode === 200) {
|
|
44
|
+
resolve(socket);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
socket.destroy();
|
|
48
|
+
reject(new Error(`Upstream proxy CONNECT failed with status ${res.statusCode}`));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
req.on("error", reject);
|
|
52
|
+
req.end();
|
|
53
|
+
});
|
|
54
|
+
}
|