autotel-devtools 6.0.0 → 6.1.0

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.
@@ -24,6 +24,22 @@ declare function parseOtlpLogs(payload: unknown): LogData[];
24
24
  */
25
25
  declare function isProtobufContentType(contentType?: string): boolean;
26
26
 
27
+ /** Value of the `x-autotel-devtools` response header and the /healthz `service` field. */
28
+ declare const DEVTOOLS_IDENTITY = "autotel-devtools";
29
+ /** Who is holding a TCP port, as far as we can tell over HTTP:
30
+ * - `autotel-devtools` — another instance of us (benign; the user has two running)
31
+ * - `foreign` — an HTTP server that is NOT us (e.g. an IDE's OTLP collector)
32
+ * - `none` — nothing answered HTTP (refused, timed out, or non-HTTP listener) */
33
+ type PortHolder = 'autotel-devtools' | 'foreign' | 'none';
34
+ /**
35
+ * Probe `host:port` over HTTP and classify what is listening. Used when our
36
+ * requested port is busy: it lets us tell "a stale autotel-devtools is still
37
+ * up" (benign) apart from "a foreign collector owns this port" — the latter is
38
+ * the silent footgun where apps keep exporting OTLP to the busy port and reach
39
+ * the wrong process, so the devtools UI stays empty and the app sees errors.
40
+ */
41
+ declare function probePortHolder(host: string, port: number, timeoutMs?: number): Promise<PortHolder>;
42
+
27
43
  /** Decode an OTLP/protobuf `ExportTraceServiceRequest` into the OTLP/JSON object shape. */
28
44
  declare function decodeOtlpTraceRequest(body: Uint8Array): Record<string, unknown>;
29
45
  /** Decode an OTLP/protobuf `ExportLogsServiceRequest` into the OTLP/JSON object shape. */
@@ -48,4 +64,4 @@ declare function appendWithLimit<T>(items: T[], item: T, limit: number): T[];
48
64
  declare function appendManyWithLimit<T>(items: T[], incoming: T[], limit: number): T[];
49
65
  declare function applyTelemetryLimits(data: DevtoolsData, limits: TelemetryLimits): DevtoolsData;
50
66
 
51
- export { DevtoolsData, DevtoolsServer, type HttpServerOptions, LogData, type TelemetryLimits, TraceData, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, resolveTelemetryLimits };
67
+ export { DEVTOOLS_IDENTITY, DevtoolsData, DevtoolsServer, type HttpServerOptions, LogData, type PortHolder, type TelemetryLimits, TraceData, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, probePortHolder, resolveTelemetryLimits };
@@ -376,6 +376,9 @@ var DevtoolsServer = class {
376
376
  this.onData = options.onData;
377
377
  this.httpServer = options.server ?? createServer();
378
378
  this.wss = new WebSocketServer({ server: this.httpServer, path: options.path ?? "/ws" });
379
+ this.wss.on("error", (err) => {
380
+ if (this.httpServer.listening) throw err;
381
+ });
379
382
  this.wss.on("connection", (ws) => {
380
383
  this.clients.add(ws);
381
384
  this.log(`Client connected (${this.clients.size} total)`);
@@ -1383,7 +1386,38 @@ function decodeOtlpMetricsRequest(body) {
1383
1386
  return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
1384
1387
  }
1385
1388
 
1389
+ // src/server/identity.ts
1390
+ var DEVTOOLS_IDENTITY = "autotel-devtools";
1391
+ async function probePortHolder(host, port, timeoutMs = 500) {
1392
+ const authority = host.includes(":") ? `[${host}]` : host;
1393
+ const controller = new AbortController();
1394
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1395
+ try {
1396
+ const res = await fetch(`http://${authority}:${port}/healthz`, {
1397
+ signal: controller.signal
1398
+ });
1399
+ if (res.headers.get("x-autotel-devtools")) return "autotel-devtools";
1400
+ try {
1401
+ const body = await res.json();
1402
+ if (body && body.service === DEVTOOLS_IDENTITY) return "autotel-devtools";
1403
+ } catch {
1404
+ }
1405
+ return "foreign";
1406
+ } catch {
1407
+ return "none";
1408
+ } finally {
1409
+ clearTimeout(timer);
1410
+ }
1411
+ }
1412
+
1386
1413
  // src/server/http.ts
1414
+ function sendOtlpError(res, req, e) {
1415
+ sendJson(res, 400, {
1416
+ error: "Invalid OTLP payload",
1417
+ message: e instanceof Error ? e.message : String(e),
1418
+ contentType: req.headers["content-type"] ?? null
1419
+ });
1420
+ }
1387
1421
  var PROTOBUF_DECODERS = {
1388
1422
  traces: decodeOtlpTraceRequest,
1389
1423
  logs: decodeOtlpLogsRequest,
@@ -1404,6 +1438,18 @@ function findPackageRoot() {
1404
1438
  return dir;
1405
1439
  }
1406
1440
  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><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>`;
1441
+ var cachedVersion = null;
1442
+ function getVersion() {
1443
+ if (cachedVersion !== null) return cachedVersion;
1444
+ let version = "unknown";
1445
+ try {
1446
+ const pkg = JSON.parse(readFileSync(resolve(findPackageRoot(), "package.json"), "utf8"));
1447
+ if (typeof pkg.version === "string") version = pkg.version;
1448
+ } catch {
1449
+ }
1450
+ cachedVersion = version;
1451
+ return version;
1452
+ }
1407
1453
  var cachedWidgetJs = null;
1408
1454
  function getWidgetJs() {
1409
1455
  if (!cachedWidgetJs) {
@@ -1430,6 +1476,8 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1430
1476
  res.setHeader("Access-Control-Allow-Origin", "*");
1431
1477
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
1432
1478
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1479
+ res.setHeader("x-autotel-devtools", getVersion());
1480
+ res.setHeader("Access-Control-Expose-Headers", "x-autotel-devtools");
1433
1481
  if (req.method === "OPTIONS") {
1434
1482
  res.writeHead(204);
1435
1483
  res.end();
@@ -1448,7 +1496,12 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1448
1496
  return;
1449
1497
  }
1450
1498
  if (req.method === "GET" && url === "/healthz") {
1451
- sendJson(res, 200, { ok: true, clients: devtools.clientCount });
1499
+ sendJson(res, 200, {
1500
+ ok: true,
1501
+ service: DEVTOOLS_IDENTITY,
1502
+ version: getVersion(),
1503
+ clients: devtools.clientCount
1504
+ });
1452
1505
  return;
1453
1506
  }
1454
1507
  if (req.method === "GET" && url === "/v1/traces") {
@@ -1468,7 +1521,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1468
1521
  devtools.addTraces(traces);
1469
1522
  sendJson(res, 200, { acceptedTraces: traces.length });
1470
1523
  } catch (e) {
1471
- sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
1524
+ sendOtlpError(res, req, e);
1472
1525
  }
1473
1526
  return;
1474
1527
  }
@@ -1479,7 +1532,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1479
1532
  devtools.addLogs(logs);
1480
1533
  sendJson(res, 200, { acceptedLogs: logs.length });
1481
1534
  } catch (e) {
1482
- sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
1535
+ sendOtlpError(res, req, e);
1483
1536
  }
1484
1537
  return;
1485
1538
  }
@@ -1489,7 +1542,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1489
1542
  const count = countOtlpMetrics(payload);
1490
1543
  sendJson(res, 200, { acceptedMetrics: count });
1491
1544
  } catch (e) {
1492
- sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
1545
+ sendOtlpError(res, req, e);
1493
1546
  }
1494
1547
  return;
1495
1548
  }
@@ -1502,6 +1555,6 @@ function createDevtoolsHttpServer(devtools, _options = {}) {
1502
1555
  return server;
1503
1556
  }
1504
1557
 
1505
- export { DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, resolveTelemetryLimits };
1558
+ export { DEVTOOLS_IDENTITY, DevtoolsLogExporter, DevtoolsRemoteExporter, DevtoolsServer, DevtoolsSpanExporter, ErrorAggregator, appendManyWithLimit, appendWithLimit, applyTelemetryLimits, attachDevtoolsRoutes, createDevtoolsHttpServer, decodeOtlpLogsRequest, decodeOtlpMetricsRequest, decodeOtlpTraceRequest, isProtobufContentType, parseOtlpLogs, parseOtlpTraces, probePortHolder, resolveTelemetryLimits };
1506
1559
  //# sourceMappingURL=index.js.map
1507
1560
  //# sourceMappingURL=index.js.map