autotel-devtools 8.0.0 → 8.1.1
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/README.md +37 -0
- package/dist/cli.cjs +76 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +76 -7
- package/dist/cli.js.map +1 -1
- package/dist/{error-aggregator-CAk_pt3Z.d.ts → error-aggregator-D0Uu5r38.d.ts} +1 -1
- package/dist/{error-aggregator-CbLiuot4.d.cts → error-aggregator-D1Mr221Y.d.cts} +1 -1
- package/dist/{exporter-DjLkU621.d.cts → exporter-De6p4iAD.d.cts} +6 -0
- package/dist/{exporter-DjLkU621.d.ts → exporter-De6p4iAD.d.ts} +6 -0
- package/dist/genai/index.cjs +7 -2
- package/dist/genai/index.cjs.map +1 -1
- package/dist/genai/index.d.cts +6 -2
- package/dist/genai/index.d.ts +6 -2
- package/dist/genai/index.js +7 -2
- package/dist/genai/index.js.map +1 -1
- package/dist/index.cjs +76 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +76 -6
- package/dist/index.js.map +1 -1
- package/dist/server/exporter.d.cts +1 -1
- package/dist/server/exporter.d.ts +1 -1
- package/dist/server/index.cjs +76 -4
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +30 -5
- package/dist/server/index.d.ts +30 -5
- package/dist/server/index.js +73 -5
- package/dist/server/index.js.map +1 -1
- package/dist/widget.global.js +10 -9
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -346,6 +346,40 @@ function appendManyWithLimit(items, incoming, limit) {
|
|
|
346
346
|
return next.length > limit ? next.slice(next.length - limit) : next;
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
// src/server/origin-guard.ts
|
|
350
|
+
var LOOPBACK_IPV6 = /* @__PURE__ */ new Set(["::1", "0:0:0:0:0:0:0:1"]);
|
|
351
|
+
function isLoopbackHostname(hostname) {
|
|
352
|
+
const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
353
|
+
return h === "localhost" || /^127\./.test(h) || LOOPBACK_IPV6.has(h);
|
|
354
|
+
}
|
|
355
|
+
function hostnameFromHostHeader(host) {
|
|
356
|
+
const h = host.trim();
|
|
357
|
+
if (h.startsWith("[")) {
|
|
358
|
+
const end = h.indexOf("]");
|
|
359
|
+
return end > 0 ? h.slice(1, end) : h;
|
|
360
|
+
}
|
|
361
|
+
const colon = h.indexOf(":");
|
|
362
|
+
return colon === -1 ? h : h.slice(0, colon);
|
|
363
|
+
}
|
|
364
|
+
function hostHeaderIsLoopback(host) {
|
|
365
|
+
return isLoopbackHostname(hostnameFromHostHeader(host));
|
|
366
|
+
}
|
|
367
|
+
function originIsLoopback(origin) {
|
|
368
|
+
try {
|
|
369
|
+
return isLoopbackHostname(new URL(origin).hostname);
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function allowSensitiveRequest(headers, loopbackOnly) {
|
|
375
|
+
const { origin, host } = headers;
|
|
376
|
+
if (origin && origin.length > 0 && !originIsLoopback(origin)) return false;
|
|
377
|
+
if (loopbackOnly && host && host.length > 0 && !hostHeaderIsLoopback(host)) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
|
|
349
383
|
// src/server/server.ts
|
|
350
384
|
var DevtoolsServer = class {
|
|
351
385
|
wss;
|
|
@@ -365,7 +399,12 @@ var DevtoolsServer = class {
|
|
|
365
399
|
this._port = options.port ?? 4318;
|
|
366
400
|
this.onData = options.onData;
|
|
367
401
|
this.httpServer = options.server ?? createServer();
|
|
368
|
-
|
|
402
|
+
const loopbackOnly = options.host == null || hostHeaderIsLoopback(options.host);
|
|
403
|
+
this.wss = new WebSocketServer({
|
|
404
|
+
server: this.httpServer,
|
|
405
|
+
path: options.path ?? "/ws",
|
|
406
|
+
verifyClient: ({ origin, req }) => allowSensitiveRequest({ origin, host: req.headers.host }, loopbackOnly)
|
|
407
|
+
});
|
|
369
408
|
this.wss.on("error", (err) => {
|
|
370
409
|
if (this.httpServer.listening) throw err;
|
|
371
410
|
});
|
|
@@ -399,6 +438,7 @@ var DevtoolsServer = class {
|
|
|
399
438
|
}
|
|
400
439
|
addTrace(trace) {
|
|
401
440
|
const existing = this.traces.find((t) => t.traceId === trace.traceId);
|
|
441
|
+
const merged = existing ?? trace;
|
|
402
442
|
if (existing) {
|
|
403
443
|
const existingSpanIds = new Set(existing.spans.map((s) => s.spanId));
|
|
404
444
|
for (const span of trace.spans) {
|
|
@@ -410,6 +450,14 @@ var DevtoolsServer = class {
|
|
|
410
450
|
existing.endTime = Math.max(existing.endTime, trace.endTime);
|
|
411
451
|
existing.duration = existing.endTime - existing.startTime;
|
|
412
452
|
if (trace.status === "ERROR") existing.status = "ERROR";
|
|
453
|
+
const root = existing.spans.find((s) => !s.parentSpanId);
|
|
454
|
+
if (root) {
|
|
455
|
+
existing.rootSpan = root;
|
|
456
|
+
const rootService = root.attributes?.["service.name"];
|
|
457
|
+
if (typeof rootService === "string" && rootService.length > 0) {
|
|
458
|
+
existing.service = rootService;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
413
461
|
} else {
|
|
414
462
|
this.traces = appendWithLimit(
|
|
415
463
|
this.traces,
|
|
@@ -418,7 +466,7 @@ var DevtoolsServer = class {
|
|
|
418
466
|
);
|
|
419
467
|
}
|
|
420
468
|
this.errorAggregator.addErrorsFromTrace(trace);
|
|
421
|
-
this.broadcast({ traces: [
|
|
469
|
+
this.broadcast({ traces: [merged], metrics: [], logs: [], errors: this.errorAggregator.getErrorGroups() });
|
|
422
470
|
}
|
|
423
471
|
addTraces(traces) {
|
|
424
472
|
for (const trace of traces) this.addTrace(trace);
|
|
@@ -970,7 +1018,8 @@ function findPackageRoot() {
|
|
|
970
1018
|
}
|
|
971
1019
|
return dir;
|
|
972
1020
|
}
|
|
973
|
-
var
|
|
1021
|
+
var DEVTOOLS_FAVICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#0f172a"/><text x="32" y="41" text-anchor="middle" font-size="32">\u{1F6F0}\uFE0F</text></svg>';
|
|
1022
|
+
var FULLPAGE_HTML = `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>autotel-devtools</title><link rel="icon" href="/favicon.svg" type="image/svg+xml"><style>*{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;width:100%;overflow:hidden}</style></head><body><script src="/widget.js?mode=fullpage"></script></body></html>`;
|
|
974
1023
|
var cachedVersion = null;
|
|
975
1024
|
function getVersion() {
|
|
976
1025
|
if (cachedVersion !== null) return cachedVersion;
|
|
@@ -1004,8 +1053,10 @@ function getWidgetJs() {
|
|
|
1004
1053
|
}
|
|
1005
1054
|
return cachedWidgetJs;
|
|
1006
1055
|
}
|
|
1007
|
-
function attachDevtoolsRoutes(httpServer, devtools) {
|
|
1056
|
+
function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
|
|
1057
|
+
const loopbackOnly = options.loopbackOnly ?? true;
|
|
1008
1058
|
httpServer.on("request", async (req, res) => {
|
|
1059
|
+
if (req.headers.upgrade?.toLowerCase() === "websocket") return;
|
|
1009
1060
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1010
1061
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
1011
1062
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
@@ -1028,6 +1079,15 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1028
1079
|
res.end(js);
|
|
1029
1080
|
return;
|
|
1030
1081
|
}
|
|
1082
|
+
if (req.method === "GET" && (url === "/favicon.svg" || url === "/favicon.ico")) {
|
|
1083
|
+
res.writeHead(200, {
|
|
1084
|
+
"Content-Type": "image/svg+xml; charset=utf-8",
|
|
1085
|
+
"Cache-Control": "public, max-age=86400",
|
|
1086
|
+
"Content-Length": Buffer.byteLength(DEVTOOLS_FAVICON_SVG)
|
|
1087
|
+
});
|
|
1088
|
+
res.end(DEVTOOLS_FAVICON_SVG);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1031
1091
|
if (req.method === "GET" && url === "/healthz") {
|
|
1032
1092
|
sendJson(res, 200, {
|
|
1033
1093
|
ok: true,
|
|
@@ -1038,11 +1098,19 @@ function attachDevtoolsRoutes(httpServer, devtools) {
|
|
|
1038
1098
|
return;
|
|
1039
1099
|
}
|
|
1040
1100
|
if (req.method === "GET" && url === "/v1/traces") {
|
|
1101
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
1102
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1041
1105
|
const data = devtools.getCurrentData();
|
|
1042
1106
|
sendJson(res, 200, { traces: data.traces, count: data.traces.length });
|
|
1043
1107
|
return;
|
|
1044
1108
|
}
|
|
1045
1109
|
if (req.method === "DELETE" && url === "/v1/traces") {
|
|
1110
|
+
if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
|
|
1111
|
+
sendJson(res, 403, { error: "Forbidden" });
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1046
1114
|
devtools.clearData();
|
|
1047
1115
|
sendJson(res, 200, { cleared: true });
|
|
1048
1116
|
return;
|
|
@@ -1283,13 +1351,14 @@ async function main() {
|
|
|
1283
1351
|
process.exit(0);
|
|
1284
1352
|
}
|
|
1285
1353
|
const httpServer = createServer();
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1354
|
+
const loopbackOnly = hostHeaderIsLoopback(options.host);
|
|
1355
|
+
const wsServer = new DevtoolsServer({ server: httpServer, host: options.host, verbose: true });
|
|
1356
|
+
attachDevtoolsRoutes(httpServer, wsServer, { loopbackOnly });
|
|
1288
1357
|
const listeners = listenLoopbackDualStack({
|
|
1289
1358
|
primary: httpServer,
|
|
1290
1359
|
port: options.port,
|
|
1291
1360
|
host: options.host,
|
|
1292
|
-
attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer)
|
|
1361
|
+
attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer, { loopbackOnly })
|
|
1293
1362
|
});
|
|
1294
1363
|
const { addresses, warnings, port: boundPort } = await listeners.ready;
|
|
1295
1364
|
if (boundPort !== options.port) {
|