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