portprism 2.0.4 → 2.0.5
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/index.js +557 -107
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
#!/usr/bin/env node
|
|
3
1
|
import { createRequire as _createRequire } from 'module';
|
|
4
2
|
import { fileURLToPath as _fileURLToPath } from 'url';
|
|
5
3
|
const require = _createRequire(import.meta.url);
|
|
@@ -6191,7 +6189,7 @@ var require_websocket = __commonJS({
|
|
|
6191
6189
|
"use strict";
|
|
6192
6190
|
var EventEmitter5 = __require("events");
|
|
6193
6191
|
var https2 = __require("https");
|
|
6194
|
-
var
|
|
6192
|
+
var http4 = __require("http");
|
|
6195
6193
|
var net = __require("net");
|
|
6196
6194
|
var tls = __require("tls");
|
|
6197
6195
|
var { randomBytes: randomBytes2, createHash: createHash3 } = __require("crypto");
|
|
@@ -6655,7 +6653,7 @@ var require_websocket = __commonJS({
|
|
|
6655
6653
|
const isSecure = parsedUrl.protocol === "wss:" || parsedUrl.protocol === "https:";
|
|
6656
6654
|
const defaultPort = isSecure ? 443 : 80;
|
|
6657
6655
|
const key = randomBytes2(16).toString("base64");
|
|
6658
|
-
const get = isSecure ? https2.get :
|
|
6656
|
+
const get = isSecure ? https2.get : http4.get;
|
|
6659
6657
|
let perMessageDeflate;
|
|
6660
6658
|
opts.createConnection = isSecure ? tlsConnect : netConnect;
|
|
6661
6659
|
opts.defaultPort = opts.defaultPort || defaultPort;
|
|
@@ -7072,7 +7070,7 @@ var require_websocket_server = __commonJS({
|
|
|
7072
7070
|
"../../node_modules/ws/lib/websocket-server.js"(exports, module) {
|
|
7073
7071
|
"use strict";
|
|
7074
7072
|
var EventEmitter5 = __require("events");
|
|
7075
|
-
var
|
|
7073
|
+
var http4 = __require("http");
|
|
7076
7074
|
var https2 = __require("https");
|
|
7077
7075
|
var net = __require("net");
|
|
7078
7076
|
var tls = __require("tls");
|
|
@@ -7131,8 +7129,8 @@ var require_websocket_server = __commonJS({
|
|
|
7131
7129
|
);
|
|
7132
7130
|
}
|
|
7133
7131
|
if (options.port != null) {
|
|
7134
|
-
this._server =
|
|
7135
|
-
const body =
|
|
7132
|
+
this._server = http4.createServer((req, res) => {
|
|
7133
|
+
const body = http4.STATUS_CODES[426];
|
|
7136
7134
|
res.writeHead(426, {
|
|
7137
7135
|
"Content-Length": body.length,
|
|
7138
7136
|
"Content-Type": "text/plain"
|
|
@@ -7355,7 +7353,7 @@ var require_websocket_server = __commonJS({
|
|
|
7355
7353
|
}
|
|
7356
7354
|
function abortHandshake(socket, code, message, headers) {
|
|
7357
7355
|
if (socket.writable) {
|
|
7358
|
-
message = message ||
|
|
7356
|
+
message = message || http4.STATUS_CODES[code];
|
|
7359
7357
|
headers = {
|
|
7360
7358
|
Connection: "close",
|
|
7361
7359
|
"Content-Type": "text/html",
|
|
@@ -7363,7 +7361,7 @@ var require_websocket_server = __commonJS({
|
|
|
7363
7361
|
...headers
|
|
7364
7362
|
};
|
|
7365
7363
|
socket.write(
|
|
7366
|
-
`HTTP/1.1 ${code} ${
|
|
7364
|
+
`HTTP/1.1 ${code} ${http4.STATUS_CODES[code]}\r
|
|
7367
7365
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
7368
7366
|
);
|
|
7369
7367
|
}
|
|
@@ -51550,7 +51548,7 @@ var require_websocket2 = __commonJS({
|
|
|
51550
51548
|
"use strict";
|
|
51551
51549
|
var EventEmitter5 = __require("events");
|
|
51552
51550
|
var https2 = __require("https");
|
|
51553
|
-
var
|
|
51551
|
+
var http4 = __require("http");
|
|
51554
51552
|
var net = __require("net");
|
|
51555
51553
|
var tls = __require("tls");
|
|
51556
51554
|
var { randomBytes: randomBytes2, createHash: createHash3 } = __require("crypto");
|
|
@@ -52084,7 +52082,7 @@ var require_websocket2 = __commonJS({
|
|
|
52084
52082
|
}
|
|
52085
52083
|
const defaultPort = isSecure ? 443 : 80;
|
|
52086
52084
|
const key = randomBytes2(16).toString("base64");
|
|
52087
|
-
const request3 = isSecure ? https2.request :
|
|
52085
|
+
const request3 = isSecure ? https2.request : http4.request;
|
|
52088
52086
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
52089
52087
|
let perMessageDeflate;
|
|
52090
52088
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -52578,7 +52576,7 @@ var require_websocket_server2 = __commonJS({
|
|
|
52578
52576
|
"../../node_modules/puppeteer-core/node_modules/ws/lib/websocket-server.js"(exports, module) {
|
|
52579
52577
|
"use strict";
|
|
52580
52578
|
var EventEmitter5 = __require("events");
|
|
52581
|
-
var
|
|
52579
|
+
var http4 = __require("http");
|
|
52582
52580
|
var { Duplex } = __require("stream");
|
|
52583
52581
|
var { createHash: createHash3 } = __require("crypto");
|
|
52584
52582
|
var extension2 = require_extension2();
|
|
@@ -52653,8 +52651,8 @@ var require_websocket_server2 = __commonJS({
|
|
|
52653
52651
|
);
|
|
52654
52652
|
}
|
|
52655
52653
|
if (options.port != null) {
|
|
52656
|
-
this._server =
|
|
52657
|
-
const body =
|
|
52654
|
+
this._server = http4.createServer((req, res) => {
|
|
52655
|
+
const body = http4.STATUS_CODES[426];
|
|
52658
52656
|
res.writeHead(426, {
|
|
52659
52657
|
"Content-Length": body.length,
|
|
52660
52658
|
"Content-Type": "text/plain"
|
|
@@ -52941,7 +52939,7 @@ var require_websocket_server2 = __commonJS({
|
|
|
52941
52939
|
this.destroy();
|
|
52942
52940
|
}
|
|
52943
52941
|
function abortHandshake(socket, code, message, headers) {
|
|
52944
|
-
message = message ||
|
|
52942
|
+
message = message || http4.STATUS_CODES[code];
|
|
52945
52943
|
headers = {
|
|
52946
52944
|
Connection: "close",
|
|
52947
52945
|
"Content-Type": "text/html",
|
|
@@ -52950,7 +52948,7 @@ var require_websocket_server2 = __commonJS({
|
|
|
52950
52948
|
};
|
|
52951
52949
|
socket.once("finish", socket.destroy);
|
|
52952
52950
|
socket.end(
|
|
52953
|
-
`HTTP/1.1 ${code} ${
|
|
52951
|
+
`HTTP/1.1 ${code} ${http4.STATUS_CODES[code]}\r
|
|
52954
52952
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
52955
52953
|
);
|
|
52956
52954
|
}
|
|
@@ -72212,7 +72210,13 @@ var import_commander = __toESM(require_commander(), 1);
|
|
|
72212
72210
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
72213
72211
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
72214
72212
|
import { stdin as input2, stdout as output2 } from "node:process";
|
|
72215
|
-
import {
|
|
72213
|
+
import {
|
|
72214
|
+
readFileSync as readFileSync6,
|
|
72215
|
+
writeFileSync as writeFileSync2,
|
|
72216
|
+
existsSync as existsSync4,
|
|
72217
|
+
mkdirSync as mkdirSync2,
|
|
72218
|
+
renameSync
|
|
72219
|
+
} from "node:fs";
|
|
72216
72220
|
import { join as join4 } from "node:path";
|
|
72217
72221
|
import { homedir as homedir2 } from "node:os";
|
|
72218
72222
|
|
|
@@ -75984,8 +75988,10 @@ var Agent = class extends EventEmitter4 {
|
|
|
75984
75988
|
console.error(import_chalk2.default.red(`
|
|
75985
75989
|
\u2716 ${msg.message}`) + "\n");
|
|
75986
75990
|
} else {
|
|
75987
|
-
console.error(
|
|
75988
|
-
|
|
75991
|
+
console.error(
|
|
75992
|
+
import_chalk2.default.red(`
|
|
75993
|
+
Relay error [${msg.code}]: ${msg.message}`)
|
|
75994
|
+
);
|
|
75989
75995
|
}
|
|
75990
75996
|
}
|
|
75991
75997
|
if (terminal2) {
|
|
@@ -75998,6 +76004,16 @@ var Agent = class extends EventEmitter4 {
|
|
|
75998
76004
|
_forwardRequest(msg) {
|
|
75999
76005
|
const { requestId, method, path: path15, headers, body } = msg;
|
|
76000
76006
|
const reqBody = body ? Buffer.from(body, "base64") : void 0;
|
|
76007
|
+
const startedAt = Date.now();
|
|
76008
|
+
this.emit("traffic", {
|
|
76009
|
+
phase: "request",
|
|
76010
|
+
id: requestId,
|
|
76011
|
+
ts: startedAt,
|
|
76012
|
+
method,
|
|
76013
|
+
path: path15,
|
|
76014
|
+
headers,
|
|
76015
|
+
body
|
|
76016
|
+
});
|
|
76001
76017
|
const options = {
|
|
76002
76018
|
hostname: this.options.host,
|
|
76003
76019
|
port: this.port,
|
|
@@ -76020,6 +76036,14 @@ var Agent = class extends EventEmitter4 {
|
|
|
76020
76036
|
if (this.ws?.readyState === import_ws2.default.OPEN) {
|
|
76021
76037
|
this.ws.send(JSON.stringify(outMsg));
|
|
76022
76038
|
}
|
|
76039
|
+
this.emit("traffic", {
|
|
76040
|
+
phase: "response",
|
|
76041
|
+
id: requestId,
|
|
76042
|
+
status: statusCode,
|
|
76043
|
+
headers: respHeaders,
|
|
76044
|
+
body: respBody.toString("base64"),
|
|
76045
|
+
durationMs: Date.now() - startedAt
|
|
76046
|
+
});
|
|
76023
76047
|
};
|
|
76024
76048
|
const req = http2.request(options, (res) => {
|
|
76025
76049
|
const chunks = [];
|
|
@@ -76039,7 +76063,10 @@ var Agent = class extends EventEmitter4 {
|
|
|
76039
76063
|
502,
|
|
76040
76064
|
{ "content-type": "application/json" },
|
|
76041
76065
|
Buffer.from(
|
|
76042
|
-
JSON.stringify({
|
|
76066
|
+
JSON.stringify({
|
|
76067
|
+
error: "Local server unreachable",
|
|
76068
|
+
detail: err.message
|
|
76069
|
+
})
|
|
76043
76070
|
)
|
|
76044
76071
|
);
|
|
76045
76072
|
});
|
|
@@ -76053,11 +76080,13 @@ var Agent = class extends EventEmitter4 {
|
|
|
76053
76080
|
try {
|
|
76054
76081
|
localWs = new import_ws2.default(url);
|
|
76055
76082
|
} catch (err) {
|
|
76056
|
-
this.ws?.send(
|
|
76057
|
-
|
|
76058
|
-
|
|
76059
|
-
|
|
76060
|
-
|
|
76083
|
+
this.ws?.send(
|
|
76084
|
+
JSON.stringify({
|
|
76085
|
+
type: "ws-error",
|
|
76086
|
+
wsId,
|
|
76087
|
+
message: err instanceof Error ? err.message : String(err)
|
|
76088
|
+
})
|
|
76089
|
+
);
|
|
76061
76090
|
return;
|
|
76062
76091
|
}
|
|
76063
76092
|
localWs.on("open", () => {
|
|
@@ -76066,32 +76095,38 @@ var Agent = class extends EventEmitter4 {
|
|
|
76066
76095
|
localWs.on("message", (data, isBinary) => {
|
|
76067
76096
|
if (this.ws?.readyState !== import_ws2.default.OPEN) return;
|
|
76068
76097
|
const binary = typeof isBinary === "boolean" ? isBinary : Buffer.isBuffer(data);
|
|
76069
|
-
this.ws.send(
|
|
76070
|
-
|
|
76071
|
-
|
|
76072
|
-
|
|
76073
|
-
|
|
76074
|
-
|
|
76098
|
+
this.ws.send(
|
|
76099
|
+
JSON.stringify({
|
|
76100
|
+
type: "ws-message",
|
|
76101
|
+
wsId,
|
|
76102
|
+
data: binary ? Buffer.from(data).toString("base64") : data.toString(),
|
|
76103
|
+
binary
|
|
76104
|
+
})
|
|
76105
|
+
);
|
|
76075
76106
|
});
|
|
76076
76107
|
localWs.on("close", (code, reason) => {
|
|
76077
76108
|
this.wsConnections.delete(wsId);
|
|
76078
76109
|
if (this.ws?.readyState === import_ws2.default.OPEN) {
|
|
76079
|
-
this.ws.send(
|
|
76080
|
-
|
|
76081
|
-
|
|
76082
|
-
|
|
76083
|
-
|
|
76084
|
-
|
|
76110
|
+
this.ws.send(
|
|
76111
|
+
JSON.stringify({
|
|
76112
|
+
type: "ws-close",
|
|
76113
|
+
wsId,
|
|
76114
|
+
code,
|
|
76115
|
+
reason: reason.toString()
|
|
76116
|
+
})
|
|
76117
|
+
);
|
|
76085
76118
|
}
|
|
76086
76119
|
});
|
|
76087
76120
|
localWs.on("error", (err) => {
|
|
76088
76121
|
this.wsConnections.delete(wsId);
|
|
76089
76122
|
if (this.ws?.readyState === import_ws2.default.OPEN) {
|
|
76090
|
-
this.ws.send(
|
|
76091
|
-
|
|
76092
|
-
|
|
76093
|
-
|
|
76094
|
-
|
|
76123
|
+
this.ws.send(
|
|
76124
|
+
JSON.stringify({
|
|
76125
|
+
type: "ws-error",
|
|
76126
|
+
wsId,
|
|
76127
|
+
message: err.message
|
|
76128
|
+
})
|
|
76129
|
+
);
|
|
76095
76130
|
}
|
|
76096
76131
|
});
|
|
76097
76132
|
}
|
|
@@ -76154,7 +76189,9 @@ var Agent = class extends EventEmitter4 {
|
|
|
76154
76189
|
try {
|
|
76155
76190
|
puppeteer2 = await Promise.resolve().then(() => (init_puppeteer_core(), puppeteer_core_exports));
|
|
76156
76191
|
} catch {
|
|
76157
|
-
this._statusLog(
|
|
76192
|
+
this._statusLog(
|
|
76193
|
+
import_chalk2.default.dim(" Screenshot skipped \u2014 puppeteer-core not available.")
|
|
76194
|
+
);
|
|
76158
76195
|
return;
|
|
76159
76196
|
}
|
|
76160
76197
|
let browser = null;
|
|
@@ -76187,7 +76224,9 @@ var Agent = class extends EventEmitter4 {
|
|
|
76187
76224
|
if (res.ok) {
|
|
76188
76225
|
this._statusLog(import_chalk2.default.dim(" Screenshot captured."));
|
|
76189
76226
|
} else {
|
|
76190
|
-
this._statusLog(
|
|
76227
|
+
this._statusLog(
|
|
76228
|
+
import_chalk2.default.dim(` Screenshot upload failed (HTTP ${res.status}).`)
|
|
76229
|
+
);
|
|
76191
76230
|
}
|
|
76192
76231
|
} catch (err) {
|
|
76193
76232
|
this._statusLog(
|
|
@@ -76405,6 +76444,305 @@ function printQr(text, opts = {}) {
|
|
|
76405
76444
|
process.stdout.write("\n" + lines.join("\n") + "\n");
|
|
76406
76445
|
}
|
|
76407
76446
|
|
|
76447
|
+
// src/inspector.ts
|
|
76448
|
+
import http3 from "node:http";
|
|
76449
|
+
var MAX_RECORDS = 200;
|
|
76450
|
+
var MAX_BODY_BYTES = 256 * 1024;
|
|
76451
|
+
var TEXT_CONTENT_RE = /(json|text\/|xml|x-www-form-urlencoded|javascript|html|csv|graphql)/i;
|
|
76452
|
+
function splitPath(rawPath) {
|
|
76453
|
+
const qIdx = rawPath.indexOf("?");
|
|
76454
|
+
if (qIdx === -1) return { path: rawPath, query: null };
|
|
76455
|
+
const path15 = rawPath.slice(0, qIdx);
|
|
76456
|
+
const query = {};
|
|
76457
|
+
const params = new URLSearchParams(rawPath.slice(qIdx + 1));
|
|
76458
|
+
for (const [k, v2] of params) query[k] = v2;
|
|
76459
|
+
return { path: path15, query: Object.keys(query).length ? query : null };
|
|
76460
|
+
}
|
|
76461
|
+
function decodeBody(base64, headers) {
|
|
76462
|
+
if (!base64) return { text: null, binary: false, size: 0, truncated: false };
|
|
76463
|
+
const buf = Buffer.from(base64, "base64");
|
|
76464
|
+
const size = buf.length;
|
|
76465
|
+
const truncated = size > MAX_BODY_BYTES;
|
|
76466
|
+
const slice = truncated ? buf.subarray(0, MAX_BODY_BYTES) : buf;
|
|
76467
|
+
const contentType = (headers?.["content-type"] ?? "").toLowerCase();
|
|
76468
|
+
const looksText = contentType ? TEXT_CONTENT_RE.test(contentType) : !slice.includes(0);
|
|
76469
|
+
if (looksText) {
|
|
76470
|
+
return { text: slice.toString("utf8"), binary: false, size, truncated };
|
|
76471
|
+
}
|
|
76472
|
+
return { text: slice.toString("base64"), binary: true, size, truncated };
|
|
76473
|
+
}
|
|
76474
|
+
var Inspector = class {
|
|
76475
|
+
records = [];
|
|
76476
|
+
byId = /* @__PURE__ */ new Map();
|
|
76477
|
+
sseClients = /* @__PURE__ */ new Set();
|
|
76478
|
+
server = null;
|
|
76479
|
+
boundPort = null;
|
|
76480
|
+
/** Ingest one traffic event from the agent, merging by request id. */
|
|
76481
|
+
record(evt) {
|
|
76482
|
+
if (evt.phase === "request") {
|
|
76483
|
+
const { path: path15, query } = splitPath(evt.path ?? "");
|
|
76484
|
+
const rec2 = {
|
|
76485
|
+
id: evt.id,
|
|
76486
|
+
ts: evt.ts ?? Date.now(),
|
|
76487
|
+
method: evt.method ?? "GET",
|
|
76488
|
+
path: path15,
|
|
76489
|
+
query,
|
|
76490
|
+
reqHeaders: evt.headers ?? {},
|
|
76491
|
+
reqBody: decodeBody(evt.body, evt.headers),
|
|
76492
|
+
pending: true
|
|
76493
|
+
};
|
|
76494
|
+
this.byId.set(rec2.id, rec2);
|
|
76495
|
+
this.records.push(rec2);
|
|
76496
|
+
while (this.records.length > MAX_RECORDS) {
|
|
76497
|
+
const dropped = this.records.shift();
|
|
76498
|
+
if (dropped) this.byId.delete(dropped.id);
|
|
76499
|
+
}
|
|
76500
|
+
this.broadcast(rec2);
|
|
76501
|
+
return;
|
|
76502
|
+
}
|
|
76503
|
+
const rec = this.byId.get(evt.id);
|
|
76504
|
+
if (!rec) return;
|
|
76505
|
+
rec.status = evt.status;
|
|
76506
|
+
rec.resHeaders = evt.headers ?? {};
|
|
76507
|
+
rec.resBody = decodeBody(evt.body, evt.headers);
|
|
76508
|
+
rec.durationMs = evt.durationMs;
|
|
76509
|
+
rec.pending = false;
|
|
76510
|
+
this.broadcast(rec);
|
|
76511
|
+
}
|
|
76512
|
+
/** Compact summary used by the list view. */
|
|
76513
|
+
summary(r) {
|
|
76514
|
+
return {
|
|
76515
|
+
id: r.id,
|
|
76516
|
+
ts: r.ts,
|
|
76517
|
+
method: r.method,
|
|
76518
|
+
path: r.path,
|
|
76519
|
+
status: r.status ?? null,
|
|
76520
|
+
durationMs: r.durationMs ?? null,
|
|
76521
|
+
pending: r.pending,
|
|
76522
|
+
reqSize: r.reqBody.size,
|
|
76523
|
+
resSize: r.resBody?.size ?? 0,
|
|
76524
|
+
resContentType: r.resHeaders?.["content-type"] ?? null
|
|
76525
|
+
};
|
|
76526
|
+
}
|
|
76527
|
+
broadcast(rec) {
|
|
76528
|
+
if (this.sseClients.size === 0) return;
|
|
76529
|
+
const line = `data: ${JSON.stringify(this.summary(rec))}
|
|
76530
|
+
|
|
76531
|
+
`;
|
|
76532
|
+
for (const res of this.sseClients) {
|
|
76533
|
+
try {
|
|
76534
|
+
res.write(line);
|
|
76535
|
+
} catch {
|
|
76536
|
+
}
|
|
76537
|
+
}
|
|
76538
|
+
}
|
|
76539
|
+
/**
|
|
76540
|
+
* Start the inspector HTTP server. Resolves with the bound URL, or `null` if the
|
|
76541
|
+
* port is already taken (the tunnel must never fail because :4040 is busy).
|
|
76542
|
+
*/
|
|
76543
|
+
start(port) {
|
|
76544
|
+
return new Promise((resolve6) => {
|
|
76545
|
+
const server = http3.createServer((req, res) => this.handle(req, res));
|
|
76546
|
+
server.once("error", (err) => {
|
|
76547
|
+
if (err.code === "EADDRINUSE") {
|
|
76548
|
+
resolve6(null);
|
|
76549
|
+
} else {
|
|
76550
|
+
resolve6(null);
|
|
76551
|
+
}
|
|
76552
|
+
});
|
|
76553
|
+
server.listen(port, "127.0.0.1", () => {
|
|
76554
|
+
this.server = server;
|
|
76555
|
+
this.boundPort = server.address().port;
|
|
76556
|
+
resolve6(`http://localhost:${this.boundPort}`);
|
|
76557
|
+
});
|
|
76558
|
+
});
|
|
76559
|
+
}
|
|
76560
|
+
stop() {
|
|
76561
|
+
for (const res of this.sseClients) {
|
|
76562
|
+
try {
|
|
76563
|
+
res.end();
|
|
76564
|
+
} catch {
|
|
76565
|
+
}
|
|
76566
|
+
}
|
|
76567
|
+
this.sseClients.clear();
|
|
76568
|
+
this.server?.close();
|
|
76569
|
+
this.server = null;
|
|
76570
|
+
}
|
|
76571
|
+
handle(req, res) {
|
|
76572
|
+
const url = req.url ?? "/";
|
|
76573
|
+
if (url === "/" || url === "/index.html") {
|
|
76574
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
76575
|
+
res.end(INDEX_HTML);
|
|
76576
|
+
return;
|
|
76577
|
+
}
|
|
76578
|
+
if (url === "/__pp/requests") {
|
|
76579
|
+
const body = JSON.stringify(
|
|
76580
|
+
this.records.map((r) => this.summary(r)).reverse()
|
|
76581
|
+
);
|
|
76582
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
76583
|
+
res.end(body);
|
|
76584
|
+
return;
|
|
76585
|
+
}
|
|
76586
|
+
if (url.startsWith("/__pp/requests/")) {
|
|
76587
|
+
const id = decodeURIComponent(url.slice("/__pp/requests/".length));
|
|
76588
|
+
const rec = this.byId.get(id);
|
|
76589
|
+
if (!rec) {
|
|
76590
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
76591
|
+
res.end(JSON.stringify({ error: "not found" }));
|
|
76592
|
+
return;
|
|
76593
|
+
}
|
|
76594
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
76595
|
+
res.end(JSON.stringify(rec));
|
|
76596
|
+
return;
|
|
76597
|
+
}
|
|
76598
|
+
if (url === "/__pp/stream") {
|
|
76599
|
+
res.writeHead(200, {
|
|
76600
|
+
"content-type": "text/event-stream",
|
|
76601
|
+
"cache-control": "no-cache",
|
|
76602
|
+
connection: "keep-alive"
|
|
76603
|
+
});
|
|
76604
|
+
res.write("retry: 3000\n\n");
|
|
76605
|
+
this.sseClients.add(res);
|
|
76606
|
+
req.on("close", () => this.sseClients.delete(res));
|
|
76607
|
+
return;
|
|
76608
|
+
}
|
|
76609
|
+
res.writeHead(404, { "content-type": "text/plain" });
|
|
76610
|
+
res.end("Not found");
|
|
76611
|
+
}
|
|
76612
|
+
};
|
|
76613
|
+
var INDEX_HTML = `<!doctype html>
|
|
76614
|
+
<html lang="en">
|
|
76615
|
+
<head>
|
|
76616
|
+
<meta charset="utf-8">
|
|
76617
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
76618
|
+
<title>PortPrism Inspector</title>
|
|
76619
|
+
<style>
|
|
76620
|
+
:root { color-scheme: dark; }
|
|
76621
|
+
* { box-sizing: border-box; }
|
|
76622
|
+
body { margin:0; font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
76623
|
+
background:#0b0d12; color:#e6e8ee; }
|
|
76624
|
+
header { display:flex; align-items:center; gap:10px; padding:12px 16px;
|
|
76625
|
+
border-bottom:1px solid #1c2030; background:#0e1017; position:sticky; top:0; }
|
|
76626
|
+
header .logo { font-weight:700; background:linear-gradient(90deg,#6366f1,#8b5cf6,#34d399);
|
|
76627
|
+
-webkit-background-clip:text; background-clip:text; color:transparent; }
|
|
76628
|
+
header .dim { color:#7b8194; font-size:12px; }
|
|
76629
|
+
header .live { margin-left:auto; display:flex; align-items:center; gap:6px; color:#7b8194; }
|
|
76630
|
+
header .dot { width:8px; height:8px; border-radius:50%; background:#34d399; box-shadow:0 0 8px #34d399; }
|
|
76631
|
+
.layout { display:grid; grid-template-columns: 1fr; }
|
|
76632
|
+
table { width:100%; border-collapse:collapse; }
|
|
76633
|
+
th,td { text-align:left; padding:7px 12px; border-bottom:1px solid #151824; white-space:nowrap; }
|
|
76634
|
+
th { color:#7b8194; font-weight:600; font-size:11px; text-transform:uppercase; letter-spacing:.05em; }
|
|
76635
|
+
tr.row { cursor:pointer; }
|
|
76636
|
+
tr.row:hover { background:#12151f; }
|
|
76637
|
+
.method { font-weight:700; }
|
|
76638
|
+
.m-GET{color:#34d399} .m-POST{color:#60a5fa} .m-PUT{color:#fbbf24}
|
|
76639
|
+
.m-DELETE{color:#f87171} .m-PATCH{color:#c084fc}
|
|
76640
|
+
.st-2{color:#34d399} .st-3{color:#60a5fa} .st-4{color:#fbbf24} .st-5{color:#f87171}
|
|
76641
|
+
.pending{color:#7b8194}
|
|
76642
|
+
.path { color:#e6e8ee; max-width:48vw; overflow:hidden; text-overflow:ellipsis; }
|
|
76643
|
+
.empty { padding:48px 16px; text-align:center; color:#7b8194; }
|
|
76644
|
+
dialog { width:min(860px,92vw); max-height:86vh; overflow:auto; background:#0e1017;
|
|
76645
|
+
color:#e6e8ee; border:1px solid #262b3d; border-radius:12px; padding:0; }
|
|
76646
|
+
dialog::backdrop { background:rgba(0,0,0,.6); }
|
|
76647
|
+
.d-head { display:flex; align-items:center; gap:10px; padding:14px 18px; border-bottom:1px solid #1c2030;
|
|
76648
|
+
position:sticky; top:0; background:#0e1017; }
|
|
76649
|
+
.d-head .x { margin-left:auto; cursor:pointer; color:#7b8194; background:none; border:none; font-size:18px; }
|
|
76650
|
+
.d-body { padding:14px 18px; }
|
|
76651
|
+
h4 { margin:18px 0 6px; color:#7b8194; font-size:11px; text-transform:uppercase; letter-spacing:.05em; }
|
|
76652
|
+
h4:first-child { margin-top:0; }
|
|
76653
|
+
pre { margin:0; padding:10px 12px; background:#0b0d12; border:1px solid #151824; border-radius:8px;
|
|
76654
|
+
overflow:auto; white-space:pre-wrap; word-break:break-word; }
|
|
76655
|
+
.kv { display:grid; grid-template-columns:200px 1fr; gap:2px 12px; }
|
|
76656
|
+
.kv .k { color:#7b8194; } .kv .v { word-break:break-word; }
|
|
76657
|
+
.tag { font-size:11px; color:#7b8194; }
|
|
76658
|
+
</style>
|
|
76659
|
+
</head>
|
|
76660
|
+
<body>
|
|
76661
|
+
<header>
|
|
76662
|
+
<span class="logo">PortPrism</span><span class="dim">Request Inspector</span>
|
|
76663
|
+
<span class="live"><span class="dot"></span> live</span>
|
|
76664
|
+
</header>
|
|
76665
|
+
<div class="layout">
|
|
76666
|
+
<table>
|
|
76667
|
+
<thead><tr><th>Time</th><th>Method</th><th>Status</th><th>Path</th><th>Dur</th><th>Size</th></tr></thead>
|
|
76668
|
+
<tbody id="rows"></tbody>
|
|
76669
|
+
</table>
|
|
76670
|
+
<div class="empty" id="empty">Waiting for requests\u2026 send traffic through your tunnel.</div>
|
|
76671
|
+
</div>
|
|
76672
|
+
<dialog id="dlg"><div class="d-head"><b id="d-title"></b><button class="x" id="d-close">\u2715</button></div><div class="d-body" id="d-body"></div></dialog>
|
|
76673
|
+
<script>
|
|
76674
|
+
(function(){
|
|
76675
|
+
var rows = document.getElementById("rows");
|
|
76676
|
+
var empty = document.getElementById("empty");
|
|
76677
|
+
var dlg = document.getElementById("dlg");
|
|
76678
|
+
var seen = {};
|
|
76679
|
+
|
|
76680
|
+
function esc(s){ return String(s).replace(/[&<>"]/g, function(c){
|
|
76681
|
+
return {"&":"&","<":"<",">":">","\\"":"""}[c]; }); }
|
|
76682
|
+
function mclass(m){ return "method m-" + esc(m); }
|
|
76683
|
+
function sclass(s){ return s ? "st-" + String(s)[0] : "pending"; }
|
|
76684
|
+
function fmtTime(ts){ var d = new Date(ts); return d.toLocaleTimeString(); }
|
|
76685
|
+
function fmtSize(n){ if(!n) return "\u2014"; if(n<1024) return n+" B"; return (n/1024).toFixed(1)+" KB"; }
|
|
76686
|
+
function fmtDur(d){ return d==null ? "\u2026" : d+" ms"; }
|
|
76687
|
+
|
|
76688
|
+
function upsert(s){
|
|
76689
|
+
empty.style.display = "none";
|
|
76690
|
+
var id = s.id;
|
|
76691
|
+
var tr = document.getElementById("r-"+id);
|
|
76692
|
+
var html = "<td>"+fmtTime(s.ts)+"</td>"
|
|
76693
|
+
+ "<td class='"+mclass(s.method)+"'>"+esc(s.method)+"</td>"
|
|
76694
|
+
+ "<td class='"+sclass(s.status)+"'>"+(s.status||"\u2026")+"</td>"
|
|
76695
|
+
+ "<td class='path'>"+esc(s.path)+"</td>"
|
|
76696
|
+
+ "<td class='tag'>"+fmtDur(s.durationMs)+"</td>"
|
|
76697
|
+
+ "<td class='tag'>"+fmtSize(s.resSize||s.reqSize)+"</td>";
|
|
76698
|
+
if(tr){ tr.innerHTML = html; }
|
|
76699
|
+
else {
|
|
76700
|
+
tr = document.createElement("tr");
|
|
76701
|
+
tr.className = "row"; tr.id = "r-"+id; tr.innerHTML = html;
|
|
76702
|
+
tr.onclick = function(){ openDetail(id); };
|
|
76703
|
+
rows.insertBefore(tr, rows.firstChild);
|
|
76704
|
+
}
|
|
76705
|
+
}
|
|
76706
|
+
|
|
76707
|
+
function section(title, obj){
|
|
76708
|
+
if(!obj || !Object.keys(obj).length) return "";
|
|
76709
|
+
var out = "<h4>"+esc(title)+"</h4><div class='kv'>";
|
|
76710
|
+
Object.keys(obj).forEach(function(k){
|
|
76711
|
+
out += "<div class='k'>"+esc(k)+"</div><div class='v'>"+esc(obj[k])+"</div>";
|
|
76712
|
+
});
|
|
76713
|
+
return out + "</div>";
|
|
76714
|
+
}
|
|
76715
|
+
function bodySection(title, b){
|
|
76716
|
+
if(!b || !b.text) return "";
|
|
76717
|
+
var note = (b.binary ? " (binary, base64)" : "") + (b.truncated ? " (truncated)" : "");
|
|
76718
|
+
return "<h4>"+esc(title)+note+"</h4><pre>"+esc(b.text)+"</pre>";
|
|
76719
|
+
}
|
|
76720
|
+
|
|
76721
|
+
function openDetail(id){
|
|
76722
|
+
fetch("/__pp/requests/"+encodeURIComponent(id)).then(function(r){return r.json();}).then(function(d){
|
|
76723
|
+
document.getElementById("d-title").textContent = d.method+" "+d.path
|
|
76724
|
+
+ (d.status ? " \u2192 "+d.status : "");
|
|
76725
|
+
var html = section("Query", d.query)
|
|
76726
|
+
+ section("Request headers", d.reqHeaders)
|
|
76727
|
+
+ bodySection("Request body", d.reqBody)
|
|
76728
|
+
+ section("Response headers", d.resHeaders)
|
|
76729
|
+
+ bodySection("Response body", d.resBody);
|
|
76730
|
+
document.getElementById("d-body").innerHTML = html || "<p class='tag'>No details.</p>";
|
|
76731
|
+
dlg.showModal();
|
|
76732
|
+
});
|
|
76733
|
+
}
|
|
76734
|
+
document.getElementById("d-close").onclick = function(){ dlg.close(); };
|
|
76735
|
+
|
|
76736
|
+
fetch("/__pp/requests").then(function(r){return r.json();}).then(function(list){
|
|
76737
|
+
list.reverse().forEach(upsert);
|
|
76738
|
+
});
|
|
76739
|
+
var es = new EventSource("/__pp/stream");
|
|
76740
|
+
es.onmessage = function(e){ try { upsert(JSON.parse(e.data)); } catch(_){} };
|
|
76741
|
+
})();
|
|
76742
|
+
</script>
|
|
76743
|
+
</body>
|
|
76744
|
+
</html>`;
|
|
76745
|
+
|
|
76408
76746
|
// src/index.ts
|
|
76409
76747
|
function emitJson(event, data = {}) {
|
|
76410
76748
|
process.stdout.write(
|
|
@@ -76459,20 +76797,28 @@ async function ensurePrivacyConsent(nonInteractive = false) {
|
|
|
76459
76797
|
console.log();
|
|
76460
76798
|
if (isUpdate) {
|
|
76461
76799
|
console.log(
|
|
76462
|
-
import_chalk4.default.yellow(
|
|
76800
|
+
import_chalk4.default.yellow(
|
|
76801
|
+
" \u2139 The PortPrism Privacy Policy has been updated (v" + POLICY_VERSION + ")."
|
|
76802
|
+
)
|
|
76463
76803
|
);
|
|
76464
76804
|
} else {
|
|
76465
|
-
console.log(
|
|
76466
|
-
import_chalk4.default.bold(" PortPrism Privacy Policy")
|
|
76467
|
-
);
|
|
76805
|
+
console.log(import_chalk4.default.bold(" PortPrism Privacy Policy"));
|
|
76468
76806
|
}
|
|
76469
76807
|
console.log(
|
|
76470
|
-
import_chalk4.default.dim(
|
|
76471
|
-
`
|
|
76808
|
+
import_chalk4.default.dim(
|
|
76809
|
+
` Before creating a tunnel, please review the Privacy Policy:
|
|
76810
|
+
`
|
|
76811
|
+
) + import_chalk4.default.cyan(` ${POLICY_URL}
|
|
76472
76812
|
`)
|
|
76473
76813
|
);
|
|
76474
76814
|
console.log(
|
|
76475
|
-
import_chalk4.default.dim(
|
|
76815
|
+
import_chalk4.default.dim(
|
|
76816
|
+
" By continuing you agree that PortPrism may process your data as described,\n"
|
|
76817
|
+
) + import_chalk4.default.dim(
|
|
76818
|
+
" including the use of a device fingerprint for free-plan quota management.\n"
|
|
76819
|
+
) + import_chalk4.default.dim(
|
|
76820
|
+
" We comply with GDPR, ISO 27001 guidelines, and PCI DSS (via Stripe).\n"
|
|
76821
|
+
)
|
|
76476
76822
|
);
|
|
76477
76823
|
const rl = createInterface2({ input: input2, output: output2 });
|
|
76478
76824
|
let answer = "";
|
|
@@ -76525,7 +76871,9 @@ function parseTarget(raw) {
|
|
|
76525
76871
|
host = value.slice(0, idx).trim();
|
|
76526
76872
|
portStr = value.slice(idx + 1).trim();
|
|
76527
76873
|
if (!host) {
|
|
76528
|
-
console.error(
|
|
76874
|
+
console.error(
|
|
76875
|
+
import_chalk4.default.red(` Invalid target: "${raw}" \u2014 missing host before ":"`)
|
|
76876
|
+
);
|
|
76529
76877
|
process.exit(1);
|
|
76530
76878
|
}
|
|
76531
76879
|
}
|
|
@@ -76580,7 +76928,9 @@ program.command("login").description("Log in to PortPrism with a magic link").ac
|
|
|
76580
76928
|
verifySpinner.stop();
|
|
76581
76929
|
if (!res.ok) {
|
|
76582
76930
|
const body = await res.json().catch(() => ({}));
|
|
76583
|
-
console.error(
|
|
76931
|
+
console.error(
|
|
76932
|
+
import_chalk4.default.red(` ${body.error ?? "Invalid or expired code."}`)
|
|
76933
|
+
);
|
|
76584
76934
|
process.exit(1);
|
|
76585
76935
|
}
|
|
76586
76936
|
const data = await res.json();
|
|
@@ -76630,25 +76980,33 @@ program.command("referral").description("Show your referral link and bonus-time
|
|
|
76630
76980
|
let data;
|
|
76631
76981
|
try {
|
|
76632
76982
|
const res = await fetch(`${base}/auth/me`, {
|
|
76633
|
-
headers: {
|
|
76983
|
+
headers: { Authorization: `Bearer ${auth.token}` }
|
|
76634
76984
|
});
|
|
76635
76985
|
spinner.stop();
|
|
76636
76986
|
if (!res.ok) {
|
|
76637
76987
|
const body = await res.json().catch(() => ({}));
|
|
76638
|
-
console.error(
|
|
76988
|
+
console.error(
|
|
76989
|
+
import_chalk4.default.red(` ${body.error ?? "Failed to load referral info."}`)
|
|
76990
|
+
);
|
|
76639
76991
|
process.exit(1);
|
|
76640
76992
|
}
|
|
76641
76993
|
data = await res.json();
|
|
76642
76994
|
} catch (err) {
|
|
76643
76995
|
spinner.stop();
|
|
76644
76996
|
console.error(
|
|
76645
|
-
import_chalk4.default.red(
|
|
76997
|
+
import_chalk4.default.red(
|
|
76998
|
+
` Network error: ${err instanceof Error ? err.message : String(err)}`
|
|
76999
|
+
)
|
|
76646
77000
|
);
|
|
76647
77001
|
process.exit(1);
|
|
76648
77002
|
}
|
|
76649
77003
|
const { referralCode, referralBonusMinutes, referralCount } = data.user;
|
|
76650
77004
|
if (!referralCode) {
|
|
76651
|
-
console.log(
|
|
77005
|
+
console.log(
|
|
77006
|
+
import_chalk4.default.yellow(
|
|
77007
|
+
" Referral code not yet generated \u2014 try again after your next login."
|
|
77008
|
+
)
|
|
77009
|
+
);
|
|
76652
77010
|
return;
|
|
76653
77011
|
}
|
|
76654
77012
|
const link = `https://portprism.io/?ref=${referralCode}`;
|
|
@@ -76660,11 +77018,17 @@ program.command("referral").description("Show your referral link and bonus-time
|
|
|
76660
77018
|
console.log(import_chalk4.default.white.bold(" Your referral link:"));
|
|
76661
77019
|
console.log(import_chalk4.default.cyan(` ${link}`));
|
|
76662
77020
|
console.log();
|
|
76663
|
-
console.log(
|
|
77021
|
+
console.log(
|
|
77022
|
+
import_chalk4.default.dim(
|
|
77023
|
+
` You've referred ${import_chalk4.default.white(String(referralCount))} ${people}.`
|
|
77024
|
+
)
|
|
77025
|
+
);
|
|
76664
77026
|
console.log(import_chalk4.default.dim(` Bonus time remaining: ${import_chalk4.default.white(bonus)}.`));
|
|
76665
77027
|
console.log();
|
|
76666
77028
|
console.log(
|
|
76667
|
-
import_chalk4.default.dim(
|
|
77029
|
+
import_chalk4.default.dim(
|
|
77030
|
+
" Each person who signs up via your link earns you both 30 extra minutes per tunnel."
|
|
77031
|
+
)
|
|
76668
77032
|
);
|
|
76669
77033
|
});
|
|
76670
77034
|
program.command("upgrade").description("Upgrade to PortPrism Pro").action(async () => {
|
|
@@ -76681,21 +77045,27 @@ program.command("upgrade").description("Upgrade to PortPrism Pro").action(async
|
|
|
76681
77045
|
const res = await fetch(`${base}/billing/checkout`, {
|
|
76682
77046
|
method: "POST",
|
|
76683
77047
|
headers: {
|
|
76684
|
-
|
|
77048
|
+
Authorization: `Bearer ${auth.token}`,
|
|
76685
77049
|
"Content-Type": "application/json"
|
|
76686
77050
|
}
|
|
76687
77051
|
});
|
|
76688
77052
|
if (!res.ok) {
|
|
76689
77053
|
spinner.stop();
|
|
76690
77054
|
const body = await res.json().catch(() => ({}));
|
|
76691
|
-
console.error(
|
|
77055
|
+
console.error(
|
|
77056
|
+
import_chalk4.default.red(` ${body.error ?? "Could not start checkout."}`)
|
|
77057
|
+
);
|
|
76692
77058
|
process.exit(1);
|
|
76693
77059
|
}
|
|
76694
77060
|
const data = await res.json();
|
|
76695
77061
|
checkoutUrl = data.url;
|
|
76696
77062
|
} catch (err) {
|
|
76697
77063
|
spinner.stop();
|
|
76698
|
-
console.error(
|
|
77064
|
+
console.error(
|
|
77065
|
+
import_chalk4.default.red(
|
|
77066
|
+
` Network error: ${err instanceof Error ? err.message : String(err)}`
|
|
77067
|
+
)
|
|
77068
|
+
);
|
|
76699
77069
|
process.exit(1);
|
|
76700
77070
|
}
|
|
76701
77071
|
spinner.stop();
|
|
@@ -76708,7 +77078,9 @@ program.command("config").description("Open ~/.portprism/config.json in your $ED
|
|
|
76708
77078
|
console.log(import_chalk4.default.dim(` Opening ${file} in ${editor}\u2026`));
|
|
76709
77079
|
const result = spawnSync4(editor, [file], { stdio: "inherit" });
|
|
76710
77080
|
if (result.error) {
|
|
76711
|
-
console.error(
|
|
77081
|
+
console.error(
|
|
77082
|
+
import_chalk4.default.red(` Could not open editor: ${result.error.message}`)
|
|
77083
|
+
);
|
|
76712
77084
|
process.exit(1);
|
|
76713
77085
|
}
|
|
76714
77086
|
});
|
|
@@ -76716,18 +77088,32 @@ program.argument(
|
|
|
76716
77088
|
"<target>",
|
|
76717
77089
|
"Local port or host:port to tunnel (e.g. 3000 or my-app.local:8080)",
|
|
76718
77090
|
parseTarget
|
|
76719
|
-
).option("--name <string>", "App name shown in the viewer").option("--desc <string>", "One-line description").option("--password <string>", "Protect the share link with a password").option("--relay <url>", "Override relay WebSocket URL").option("--no-open", "Don't auto-open the viewer URL in the browser").option("--no-screenshot", "Skip automatic screenshot capture").option("--qr", "Print the share URL as a QR code").option("--qr-invert", "Force-invert the QR colours (use on light terminals)").option(
|
|
77091
|
+
).option("--name <string>", "App name shown in the viewer").option("--desc <string>", "One-line description").option("--password <string>", "Protect the share link with a password").option("--relay <url>", "Override relay WebSocket URL").option("--no-open", "Don't auto-open the viewer URL in the browser").option("--no-screenshot", "Skip automatic screenshot capture").option("--qr", "Print the share URL as a QR code").option("--qr-invert", "Force-invert the QR colours (use on light terminals)").option(
|
|
77092
|
+
"--json",
|
|
77093
|
+
"Emit machine-readable NDJSON events instead of the formatted box"
|
|
77094
|
+
).option(
|
|
77095
|
+
"--inspect",
|
|
77096
|
+
"Serve a local request inspector (headers/params/bodies) for debugging"
|
|
77097
|
+
).option(
|
|
77098
|
+
"--inspect-port <port>",
|
|
77099
|
+
"Port for the request inspector (default 4040)",
|
|
77100
|
+
"4040"
|
|
77101
|
+
).action(async (target, opts) => {
|
|
76720
77102
|
const json = Boolean(opts.json);
|
|
76721
77103
|
const { host, port } = target;
|
|
76722
77104
|
await ensurePrivacyConsent(json);
|
|
76723
77105
|
const cfg = readConfig();
|
|
76724
77106
|
const auth = readAuth();
|
|
76725
|
-
const relay = normaliseRelay(
|
|
77107
|
+
const relay = normaliseRelay(
|
|
77108
|
+
opts.relay ?? cfg.relay
|
|
77109
|
+
);
|
|
76726
77110
|
const name = opts.name ?? cfg.defaultName;
|
|
76727
77111
|
const desc = opts.desc ?? cfg.defaultDesc;
|
|
76728
77112
|
const jwtToken = auth?.token;
|
|
76729
77113
|
if (auth && !json) {
|
|
76730
|
-
console.log(
|
|
77114
|
+
console.log(
|
|
77115
|
+
import_chalk4.default.dim(` Tunneling as ${auth.user.email} (${auth.user.plan} plan)`)
|
|
77116
|
+
);
|
|
76731
77117
|
}
|
|
76732
77118
|
const agent = new Agent(port, {
|
|
76733
77119
|
name,
|
|
@@ -76741,8 +77127,38 @@ program.argument(
|
|
|
76741
77127
|
json
|
|
76742
77128
|
});
|
|
76743
77129
|
const shareUrl = `${VIEWER_BASE}/v/${agent.token}`;
|
|
77130
|
+
let inspector = null;
|
|
77131
|
+
let inspectorUrl = null;
|
|
77132
|
+
if (opts.inspect) {
|
|
77133
|
+
const inspectPort = Number.parseInt(String(opts.inspectPort ?? "4040"), 10) || 4040;
|
|
77134
|
+
inspector = new Inspector();
|
|
77135
|
+
inspectorUrl = await inspector.start(inspectPort);
|
|
77136
|
+
if (inspectorUrl) {
|
|
77137
|
+
agent.on("traffic", (evt) => inspector.record(evt));
|
|
77138
|
+
if (json) emitJson("inspector", { url: inspectorUrl });
|
|
77139
|
+
} else {
|
|
77140
|
+
inspector = null;
|
|
77141
|
+
if (json)
|
|
77142
|
+
emitJson("inspector-error", {
|
|
77143
|
+
code: "PORT_IN_USE",
|
|
77144
|
+
port: inspectPort
|
|
77145
|
+
});
|
|
77146
|
+
else
|
|
77147
|
+
console.error(
|
|
77148
|
+
import_chalk4.default.yellow(
|
|
77149
|
+
` \u26A0 Inspector port ${inspectPort} is in use \u2014 continuing without --inspect.`
|
|
77150
|
+
)
|
|
77151
|
+
);
|
|
77152
|
+
}
|
|
77153
|
+
}
|
|
76744
77154
|
if (json) {
|
|
76745
|
-
emitJson("connecting", {
|
|
77155
|
+
emitJson("connecting", {
|
|
77156
|
+
token: agent.token,
|
|
77157
|
+
localHost: host,
|
|
77158
|
+
localPort: port,
|
|
77159
|
+
relay,
|
|
77160
|
+
shareUrl
|
|
77161
|
+
});
|
|
76746
77162
|
}
|
|
76747
77163
|
const spinner = json ? null : ora(`Connecting to relay (${host}:${port})\u2026`).start();
|
|
76748
77164
|
let currentStatus = "connecting";
|
|
@@ -76752,7 +77168,16 @@ program.argument(
|
|
|
76752
77168
|
let boxVisible = false;
|
|
76753
77169
|
let refreshTimer = null;
|
|
76754
77170
|
function boxOpts() {
|
|
76755
|
-
return {
|
|
77171
|
+
return {
|
|
77172
|
+
shareUrl,
|
|
77173
|
+
appName: name,
|
|
77174
|
+
localHost: host,
|
|
77175
|
+
localPort: port,
|
|
77176
|
+
expiresAt,
|
|
77177
|
+
status: currentStatus,
|
|
77178
|
+
rtt: currentRtt,
|
|
77179
|
+
reconnectInfo
|
|
77180
|
+
};
|
|
76756
77181
|
}
|
|
76757
77182
|
function startRefresh() {
|
|
76758
77183
|
if (refreshTimer) return;
|
|
@@ -76764,7 +77189,12 @@ program.argument(
|
|
|
76764
77189
|
currentStatus = "connected";
|
|
76765
77190
|
reconnectInfo = void 0;
|
|
76766
77191
|
if (json) {
|
|
76767
|
-
emitJson("connected", {
|
|
77192
|
+
emitJson("connected", {
|
|
77193
|
+
token: agent.token,
|
|
77194
|
+
localHost: host,
|
|
77195
|
+
localPort: port,
|
|
77196
|
+
shareUrl
|
|
77197
|
+
});
|
|
76768
77198
|
return;
|
|
76769
77199
|
}
|
|
76770
77200
|
spinner?.stop();
|
|
@@ -76772,6 +77202,9 @@ program.argument(
|
|
|
76772
77202
|
if (opts.qr) {
|
|
76773
77203
|
printQr(shareUrl, { invert: Boolean(opts.qrInvert) });
|
|
76774
77204
|
}
|
|
77205
|
+
if (inspectorUrl) {
|
|
77206
|
+
console.log(import_chalk4.default.dim(" \u{1F50E} Inspector ") + import_chalk4.default.cyan(inspectorUrl));
|
|
77207
|
+
}
|
|
76775
77208
|
printBox(boxOpts());
|
|
76776
77209
|
startRefresh();
|
|
76777
77210
|
if (opts.open !== false) {
|
|
@@ -76779,31 +77212,37 @@ program.argument(
|
|
|
76779
77212
|
});
|
|
76780
77213
|
}
|
|
76781
77214
|
});
|
|
76782
|
-
agent.on(
|
|
76783
|
-
|
|
76784
|
-
|
|
76785
|
-
|
|
76786
|
-
|
|
76787
|
-
|
|
76788
|
-
|
|
76789
|
-
|
|
76790
|
-
|
|
76791
|
-
|
|
76792
|
-
|
|
76793
|
-
|
|
77215
|
+
agent.on(
|
|
77216
|
+
"ready",
|
|
77217
|
+
(info2) => {
|
|
77218
|
+
expiresAt = info2.expiresAt;
|
|
77219
|
+
if (json) {
|
|
77220
|
+
emitJson("ready", {
|
|
77221
|
+
token: agent.token,
|
|
77222
|
+
localHost: host,
|
|
77223
|
+
localPort: port,
|
|
77224
|
+
shareUrl,
|
|
77225
|
+
expiresAt: info2.expiresAt,
|
|
77226
|
+
plan: info2.plan
|
|
77227
|
+
});
|
|
77228
|
+
return;
|
|
77229
|
+
}
|
|
77230
|
+
if (boxVisible) updateBox(boxOpts());
|
|
76794
77231
|
}
|
|
76795
|
-
|
|
76796
|
-
|
|
76797
|
-
|
|
76798
|
-
|
|
76799
|
-
|
|
76800
|
-
|
|
76801
|
-
|
|
76802
|
-
|
|
76803
|
-
|
|
77232
|
+
);
|
|
77233
|
+
agent.on(
|
|
77234
|
+
"reconnecting",
|
|
77235
|
+
(delayMs, attempt, maxAttempts) => {
|
|
77236
|
+
currentStatus = "reconnecting";
|
|
77237
|
+
currentRtt = void 0;
|
|
77238
|
+
reconnectInfo = { delayMs, attempt, maxAttempts };
|
|
77239
|
+
if (json) {
|
|
77240
|
+
emitJson("reconnecting", { delayMs, attempt, maxAttempts });
|
|
77241
|
+
return;
|
|
77242
|
+
}
|
|
77243
|
+
if (boxVisible) updateBox(boxOpts());
|
|
76804
77244
|
}
|
|
76805
|
-
|
|
76806
|
-
});
|
|
77245
|
+
);
|
|
76807
77246
|
let sawFirstConnected = false;
|
|
76808
77247
|
agent.on("connected", () => {
|
|
76809
77248
|
if (!sawFirstConnected) {
|
|
@@ -76829,29 +77268,39 @@ program.argument(
|
|
|
76829
77268
|
agent.on("relay-error", (err) => {
|
|
76830
77269
|
if (json) emitJson("error", { code: err.code, message: err.message });
|
|
76831
77270
|
});
|
|
76832
|
-
agent.on(
|
|
76833
|
-
|
|
76834
|
-
|
|
76835
|
-
|
|
76836
|
-
|
|
76837
|
-
|
|
77271
|
+
agent.on(
|
|
77272
|
+
"feedback",
|
|
77273
|
+
(fb) => {
|
|
77274
|
+
const comment = sanitizeUntrusted(fb.comment);
|
|
77275
|
+
const path15 = fb.path ? sanitizeUntrusted(fb.path, 120) : "";
|
|
77276
|
+
if (json) {
|
|
77277
|
+
emitJson("feedback", {
|
|
77278
|
+
rating: fb.rating,
|
|
77279
|
+
comment,
|
|
77280
|
+
path: path15 || void 0
|
|
77281
|
+
});
|
|
77282
|
+
return;
|
|
77283
|
+
}
|
|
77284
|
+
const ratingMark = fb.rating === "up" ? import_chalk4.default.green(" \u{1F44D}") : fb.rating === "down" ? import_chalk4.default.red(" \u{1F44E}") : "";
|
|
77285
|
+
const header = import_chalk4.default.magenta.bold(" \u{1F4AC} Viewer feedback") + ratingMark + (path15 ? import_chalk4.default.dim(` \xB7 ${path15}`) : "");
|
|
77286
|
+
const body = comment ? "\n" + import_chalk4.default.white(" " + comment) : "";
|
|
77287
|
+
const block = header + body;
|
|
77288
|
+
if (boxVisible) printAboveBox(block, boxOpts());
|
|
77289
|
+
else console.log(block);
|
|
76838
77290
|
}
|
|
76839
|
-
|
|
76840
|
-
const header = import_chalk4.default.magenta.bold(" \u{1F4AC} Viewer feedback") + ratingMark + (path15 ? import_chalk4.default.dim(` \xB7 ${path15}`) : "");
|
|
76841
|
-
const body = comment ? "\n" + import_chalk4.default.white(" " + comment) : "";
|
|
76842
|
-
const block = header + body;
|
|
76843
|
-
if (boxVisible) printAboveBox(block, boxOpts());
|
|
76844
|
-
else console.log(block);
|
|
76845
|
-
});
|
|
77291
|
+
);
|
|
76846
77292
|
agent.on("terminated", (code) => {
|
|
76847
77293
|
spinner?.stop();
|
|
76848
77294
|
if (refreshTimer) clearInterval(refreshTimer);
|
|
77295
|
+
inspector?.stop();
|
|
76849
77296
|
if (json) {
|
|
76850
77297
|
emitJson("closed", { token: agent.token, code });
|
|
76851
77298
|
} else {
|
|
76852
77299
|
console.error(
|
|
76853
|
-
import_chalk4.default.red(
|
|
76854
|
-
|
|
77300
|
+
import_chalk4.default.red(
|
|
77301
|
+
`
|
|
77302
|
+
Connection closed by relay (code ${code}); not reconnecting.`
|
|
77303
|
+
)
|
|
76855
77304
|
);
|
|
76856
77305
|
}
|
|
76857
77306
|
process.exit(1);
|
|
@@ -76859,6 +77308,7 @@ program.argument(
|
|
|
76859
77308
|
process.on("SIGINT", () => {
|
|
76860
77309
|
spinner?.stop();
|
|
76861
77310
|
if (refreshTimer) clearInterval(refreshTimer);
|
|
77311
|
+
inspector?.stop();
|
|
76862
77312
|
agent.close();
|
|
76863
77313
|
if (json) {
|
|
76864
77314
|
emitJson("closed", { token: agent.token });
|