node-network-devtools
Version:
Inspecting Node.js's Network with Chrome DevTools
621 lines (620 loc) • 17.9 kB
JavaScript
var x = (t) => {
throw TypeError(t);
};
var H = (t, e, s) => e.has(t) || x("Cannot " + s);
var D = (t, e, s) => (H(t, e, "read from private field"), s ? s.call(t) : e.get(t)), C = (t, e, s) => e.has(t) ? x("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(t) : e.set(t, s), v = (t, e, s, r) => (H(t, e, "write to private field"), r ? r.call(t, s) : e.set(t, s), s);
import { Server as J, WebSocket as K } from "ws";
import Y, { apps as Q } from "open";
import { l as y, I as X, c as U, P as z, a as Z, _ as ee, R as L, p as te, f as j, s as se, g as E, S as re } from "./common-BUk9ORDJ.mjs";
import { fileURLToPath as ne, pathToFileURL as P } from "url";
import _ from "iconv-lite";
import N from "node:zlib";
import S from "fs";
import B from "path";
const oe = (t, e) => {
try {
return JSON.parse(t);
} catch {
return e;
}
};
class ie {
constructor() {
this.timestamp = 0, this.startTime = Date.now(), this.listeners = [];
}
getTimestamp() {
return this.updateTimestamp(), this.timestamp;
}
updateTimestamp() {
this.timestamp = (Date.now() - this.startTime) / 1e3;
}
on(e) {
this.listeners.push(e);
}
}
class ae extends ie {
constructor(e) {
super(), this.browser = null;
const { port: s, autoOpenDevtool: r = !0, onConnect: i, onClose: a } = e;
this.port = s, this.server = new J({ port: s });
const { server: c } = this;
c.on("listening", () => {
y(`devtool server is listening on port ${s}`), r && this.open();
}), this.socket = new Promise((p) => {
c.on("connection", (n) => {
i?.(), this.socket.then((o) => {
o[0] = n;
}), y("devtool connected"), n.on("message", (o) => {
const u = JSON.parse(o.toString());
this.listeners.forEach((d) => d(null, u));
}), n.on("close", () => {
y("devtool closed"), a?.();
}), n.on("error", (o) => {
this.listeners.forEach((u) => u(o));
}), p([n]);
});
});
}
async open() {
const e = `devtools://devtools/bundled/inspector.html?ws=localhost:${this.port}`;
try {
if (X) {
y(`In dev mode, open chrome devtool manually: ${e}`);
return;
}
const s = await Y(e, {
app: {
name: Q.chrome,
arguments: [
process.platform !== "darwin" ? `--remote-debugging-port=${U}` : ""
]
},
wait: !0
});
e: if (process.platform !== "darwin") {
const r = await new Promise(
(n) => {
let o = 0, u = setInterval(async () => {
o > 5 && (clearInterval(u), n([]));
try {
o++, n((await fetch(`http://localhost:${U}/json`)).json()), clearInterval(u);
} catch {
}
}, 500);
}
), [i] = r;
if (!i)
break e;
const { id: a, webSocketDebuggerUrl: c } = i, p = new K(c);
p.on("open", () => {
const n = {
id: a,
method: "Page.navigate",
params: {
url: e
}
};
p.send(JSON.stringify(n)), p.close();
});
}
y("opened in chrome or click here to open chrome devtool: ", e), this.browser = s;
return;
} catch {
console.warn(
"Open devtools failed, but don't worry, you can open it in browser(Chrome or Edge) manually: " + e
);
}
}
close() {
this.server.close(), this.browser && this.browser.kill();
}
async send(e) {
const [s] = await this.socket;
return s.send(JSON.stringify(e));
}
}
var I, w;
class ce {
constructor(e) {
C(this, I);
C(this, w);
this.listeners = {}, v(this, I, []), this.options = e;
const { serverPort: s, autoOpenDevtool: r } = e;
this.devtool = new ae({
port: s,
autoOpenDevtool: r,
onConnect: () => {
this.listeners.onConnect?.forEach(
(a) => a({
data: null,
id: "onConnect"
})
);
}
}), this.devtool.on((i, a) => {
if (i) {
y(i);
return;
}
const c = this.listeners[a.method];
c && c.forEach((p) => {
p({
data: a.params,
id: a.id
});
});
}), this.server = this.initServer();
}
loadPlugins(e) {
v(this, I, e), v(this, w, /* @__PURE__ */ new Map()), e.forEach((s) => {
const r = s({
devtool: this.devtool,
core: this,
plugins: D(this, I)
});
D(this, w).set(s.id, r);
});
}
usePlugin(e) {
return D(this, w) ? D(this, w).get(e) : null;
}
on(e, s) {
return this.listeners[e] || (this.listeners[e] = /* @__PURE__ */ new Set()), this.listeners[e].add(s), () => {
this.listeners[e].delete(s);
};
}
close() {
this.server.close(), this.devtool.close();
}
initServer() {
const e = new J({ port: this.options.port || z });
return e.on("connection", (s) => {
s.on("message", (r) => {
const a = JSON.parse(r.toString());
switch (a.type) {
default:
{
const c = this.listeners[a.type];
if (!c)
break;
c.forEach((p) => {
p({
data: a.data
});
});
}
break;
}
});
}), e.on("listening", () => {
process.send && process.send(Z);
}), e;
}
}
I = new WeakMap(), w = new WeakMap();
let k = null;
const ue = (t, e) => {
k = {
devtool: e,
core: t
};
}, pe = () => k = null, q = (t, e) => Object.assign((i) => {
ue(i.core, i.devtool);
const a = e(i);
return pe(), a;
}, { id: t }), g = (t, e) => {
if (!k)
return;
const s = k, { core: r } = s;
return r.on(t, e);
}, de = (t) => {
if (!k)
return;
const { core: e } = k;
return e.on("onConnect", t);
}, le = q("debugger", ({ devtool: t, core: e }) => {
const s = e.usePlugin("network");
g("Debugger.getScriptSource", ({ id: i, data: a }) => {
if (!i)
return;
const { scriptId: c } = a, p = s.resourceService.getScriptSource(c);
t.send({
id: i,
method: "Debugger.getScriptSourceResponse",
result: {
scriptSource: p
}
});
});
const r = s.resourceService.getLocalScriptList();
de(() => {
r.forEach((i) => {
t.send({
method: "Debugger.scriptParsed",
params: i
});
});
});
});
class R {
constructor(e) {
this.headers = { ...e || {} }, Object.keys(this.headers).forEach((s) => {
this.headers[s] = String(this.headers[s]);
});
}
getHeader(e) {
const s = e.toLowerCase(), r = Object.keys(this.headers);
for (const i of r)
if (i.toLowerCase() === s)
return this.headers[i];
}
getData() {
return this.headers;
}
valueOf() {
return this.headers;
}
}
class he {
constructor(e) {
this.req = e;
}
decodeBody() {
const { req: e } = this, r = new R(e.responseHeaders).getHeader("content-type") || "text/plain; charset=utf-8", i = r.match(/charset=([^;]+)/), a = i ? i[1] : "utf-8", c = !/text|json|xml/.test(r);
return {
body: (() => {
if (!(e.responseData === void 0 || e.responseData === null))
return c ? e.responseData.toString("base64") : Buffer.isBuffer(e.responseData) ? _.decode(e.responseData, a) : typeof e.responseData == "object" && "type" in e.responseData && e.responseData.type === "Buffer" && "data" in e.responseData && Array.isArray(e.responseData.data) ? _.decode(Buffer.from(e.responseData.data), a) : e.responseData;
})(),
base64Encoded: c
};
}
}
function me(t) {
switch (t.split(".").pop()?.toLowerCase()) {
case "js":
case "mjs":
case "cjs":
return "JavaScript";
case "wasm":
return "WebAssembly";
default:
return "Unknown";
}
}
class ge {
constructor() {
this.urlToScriptId = /* @__PURE__ */ new Map(), this.scriptIdToUrl = /* @__PURE__ */ new Map();
}
addMapping(e, s) {
this.urlToScriptId.set(e, s), this.scriptIdToUrl.set(s, e);
}
getUrlByScriptId(e) {
return this.scriptIdToUrl.get(e);
}
getScriptIdByUrl(e) {
return this.urlToScriptId.get(e);
}
}
class fe {
constructor() {
this.scriptMap = new ge(), this.scriptIdCounter = 0;
}
getScriptIdByUrl(e) {
return this.scriptMap.getScriptIdByUrl(e);
}
getUrlByScriptId(e) {
return this.scriptMap.getUrlByScriptId(e);
}
getScriptSource(e) {
const s = this.scriptMap.getUrlByScriptId(e);
if (!s)
return console.error(`No file path found for script ID: ${e}`), null;
const r = ne(s);
try {
return S.readFileSync(r, "utf-8");
} catch (i) {
return console.error("Error reading file:", i), null;
}
}
/**
* @description Read the last lines of the file
* @param filePath
* @param stat
* @param totalLines
* @returns string
*/
readLastLine(e, s, r = 1) {
const i = s.size, a = Math.min(1024, i);
let c = i - a, p = Buffer.alloc(a), n = [];
const o = S.openSync(e, "r");
for (; n.length < r && c >= 0; )
S.readSync(o, p, 0, a, c), n = p.toString("utf8").split(`
`).concat(n), c -= a, c < 0 && (c = 0, p = Buffer.alloc(i - c));
return S.closeSync(o), n.slice(-r).join(`
`);
}
traverseDirToMap(e, s = ["node_modules"]) {
const r = [], i = [e];
let a = this.scriptIdCounter;
for (; i.length > 0; ) {
const c = i.pop(), p = S.readdirSync(c);
for (const n of p) {
if (s.includes(n))
continue;
const o = B.join(c, n), u = S.statSync(o);
if (u.isDirectory())
i.push(o);
else {
const d = B.resolve(o), h = P(d), l = `${++a}`;
let m = "";
if (/\.(js|ts)$/.test(d)) {
const b = this.readLastLine(o, u, 2).match(/sourceMappingURL=(.+)$/m)?.[1] ?? "";
m = b ? b.startsWith("data:") ? (
// inline sourcemap
b
) : (
// file path
P(B.join(c, b)).href
) : "";
}
const f = h.href, V = me(f);
r.push({
url: f,
scriptLanguage: V,
embedderName: h.href,
scriptId: l,
sourceMapURL: m,
hasSourceURL: !!m
}), this.scriptMap.addMapping(f, l);
}
}
}
return this.scriptIdCounter += r.length, r;
}
getLocalScriptList() {
const e = this.traverseDirToMap(process.cwd()), s = this.traverseDirToMap(ee);
return [...e, ...s];
}
}
const $ = "517.528", F = "517.529", we = (t) => t.split(";")[0] || "text/plain", Se = q("network", ({ devtool: t, core: e }) => {
const s = {}, r = new fe(), i = (n) => s[n], a = (n) => {
s[n.id] = n;
}, c = (n) => {
n.requestEndTime = n.requestEndTime || Date.now(), t.updateTimestamp();
const u = new R(n.responseHeaders).getHeader("content-type") || "text/plain; charset=utf-8", d = /image/.test(u) ? "Image" : /javascript/.test(u) ? "Script" : /css/.test(u) ? "Stylesheet" : /html/.test(u) ? "Document" : "Other";
t.send({
method: "Network.responseReceived",
params: {
requestId: n.id,
frameId: $,
loaderId: F,
timestamp: t.timestamp,
type: d,
response: {
url: n.url,
status: n.responseStatusCode,
statusText: n.responseStatusCode === 200 ? "OK" : "",
headers: n.responseHeaders,
connectionReused: !1,
encodedDataLength: n.responseInfo.encodedDataLength,
charset: "utf-8",
mimeType: we(u)
}
}
}), t.updateTimestamp(), t.send({
method: "Network.dataReceived",
params: {
requestId: n.id,
timestamp: t.timestamp,
dataLength: n.responseInfo.dataLength,
encodedDataLength: n.responseInfo.encodedDataLength
}
}), t.updateTimestamp(), t.send({
method: "Network.loadingFinished",
params: {
requestId: n.id,
timestamp: t.timestamp,
encodedDataLength: n.responseInfo.encodedDataLength
}
});
}, p = (n, o) => {
const u = [N.gunzip, N.inflate, N.brotliDecompress];
let d = 0;
const h = () => {
if (d >= u.length) {
o(n);
return;
}
const l = u[d];
d += 1, l(n, (m, f) => {
m ? h() : o(f);
});
};
h();
};
return g("Network.getResponseBody", ({ data: n, id: o }) => {
const u = i(n.requestId);
if (!o || !u) {
console.error("request is not found");
return;
}
const d = new he(u).decodeBody();
t.send({
id: o,
result: d
});
}), g("initRequest", ({ data: n }) => {
const o = new L(n);
o.initiator && o.initiator.stack.callFrames.forEach((u) => {
const d = P(u.url), h = r.getScriptIdByUrl(d.href) ?? r.getScriptIdByUrl(u.url);
h && (u.scriptId = h);
}), s[o.id] = o;
}), g("registerRequest", ({ data: n }) => {
const o = new L(n);
s[o.id] = o, o.initiator && o.initiator.stack.callFrames.forEach((h) => {
const l = P(h.url), m = r.getScriptIdByUrl(l.href) ?? r.getScriptIdByUrl(h.url);
m && (h.scriptId = m);
}), t.updateTimestamp();
const u = new R(o.requestHeaders), d = u.getHeader("content-type");
return t.send({
method: "Network.requestWillBeSent",
params: {
requestId: o.id,
frameId: $,
loaderId: F,
request: {
url: o.url,
method: o.method,
headers: u.getData(),
initialPriority: "High",
mixedContentType: "none",
...o.requestData ? {
postData: d?.includes("application/json") ? JSON.stringify(o.requestData) : o.requestData
} : {}
},
timestamp: t.timestamp,
wallTime: o.requestStartTime,
initiator: o.initiator,
type: o.isWebSocket() ? "WebSocket" : "Fetch"
}
});
}), g("endRequest", ({ data: n }) => {
const o = new L(n);
c(o);
}), g("responseData", ({ data: n }) => {
const { id: o, rawData: u, statusCode: d, headers: h } = n, l = i(o), m = Buffer.from(u);
l && (l.responseInfo.encodedDataLength = m.length, p(m, (f) => {
l.responseData = f, l.responseInfo.dataLength = f.length, l.responseStatusCode = d, l.responseHeaders = new R(h).getData(), a(l), c(l);
}));
}), {
getRequest: i,
resourceService: r
};
}), ye = q("websocket", ({ devtool: t, core: e }) => {
const s = e.usePlugin("network");
g(
"Network.webSocketCreated",
async ({ data: { response: r, requestId: i } }) => {
if (!i)
return;
const a = s.getRequest(i);
if (!a)
return;
const c = te(r.rawHeaders);
await t.send({
method: "Network.webSocketCreated",
params: {
url: a.url,
initiator: a.initiator,
requestId: a.id
}
}), await t.send({
method: "Network.webSocketWillSendHandshakeRequest",
params: {
wallTime: Date.now(),
timestamp: t.getTimestamp(),
requestId: a.id,
request: {
headers: a.requestHeaders
}
}
}), await t.send({
method: "Network.webSocketHandshakeResponseReceived",
params: {
requestId: a.id,
response: {
headers: c,
headersText: j(
`HTTP/${r.httpVersion} ${r.statusCode} ${r.statusMessage}\r
`,
c
),
status: r.statusCode,
statusText: r.statusMessage,
requestHeadersText: j(
`${a.method} ${a.url} HTTP/${r.httpVersion}\r
`,
a.requestHeaders
),
requestHeaders: se(a.requestHeaders)
},
timestamp: t.getTimestamp()
}
});
}
), g("Network.webSocketFrameSent", async ({ data: r }) => {
r.requestId && await t.send({
method: "Network.webSocketFrameSent",
params: {
requestId: r.requestId,
response: r.response,
// Network中数据时间戳
timestamp: E()
}
});
}), g("Network.webSocketFrameReceived", async ({ data: r }) => {
r.requestId && await t.send({
method: "Network.webSocketFrameReceived",
params: {
requestId: r.requestId,
response: r.response,
timestamp: E()
}
});
}), g("Network.webSocketClosed", async ({ data: r }) => {
r.requestId && await t.send({
method: "Network.webSocketClosed",
params: {
requestId: r.requestId,
timestamp: E()
}
});
});
}), Ie = q("health", () => {
const t = () => {
process.exit(0);
};
let e = setTimeout(t, 5e3);
g("healthcheck", () => {
clearTimeout(e), e = setTimeout(t, 5e3);
});
}), ke = (t) => {
t.loadPlugins([Se, le, ye, Ie]);
}, M = oe(process.env.NETWORK_OPTIONS || "{}", {}), A = () => {
const t = new ce({
serverPort: M.serverPort || re,
port: M.port || z,
autoOpenDevtool: M.autoOpenDevtool
});
return ke(t), t;
};
let W = A(), O = 0;
const De = 5, Te = 30 * 1e3, G = () => {
setTimeout(
() => {
if (O++, O >= De) {
console.error("Restart limit reached"), T();
return;
}
W.close(), W = A();
},
10 + Math.random() * 100
);
};
setInterval(() => {
O = 0;
}, Te);
const T = () => {
process.exit(0);
};
process.on("exit", T);
process.on("SIGINT", T);
process.on("SIGTERM", T);
process.on("beforeExit", T);
process.on("uncaughtException", (t) => {
console.error("uncaughtException: ", t), G();
});
process.on("unhandledRejection", (t) => {
console.error("unhandledRejection: ", t), G();
});