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