autotel-devtools 6.0.1 → 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.
package/dist/cli.js CHANGED
@@ -916,7 +916,38 @@ function decodeOtlpMetricsRequest(body) {
916
916
  return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
917
917
  }
918
918
 
919
+ // src/server/identity.ts
920
+ var DEVTOOLS_IDENTITY = "autotel-devtools";
921
+ async function probePortHolder(host, port, timeoutMs = 500) {
922
+ const authority = host.includes(":") ? `[${host}]` : host;
923
+ const controller = new AbortController();
924
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
925
+ try {
926
+ const res = await fetch(`http://${authority}:${port}/healthz`, {
927
+ signal: controller.signal
928
+ });
929
+ if (res.headers.get("x-autotel-devtools")) return "autotel-devtools";
930
+ try {
931
+ const body = await res.json();
932
+ if (body && body.service === DEVTOOLS_IDENTITY) return "autotel-devtools";
933
+ } catch {
934
+ }
935
+ return "foreign";
936
+ } catch {
937
+ return "none";
938
+ } finally {
939
+ clearTimeout(timer);
940
+ }
941
+ }
942
+
919
943
  // src/server/http.ts
944
+ function sendOtlpError(res, req, e) {
945
+ sendJson(res, 400, {
946
+ error: "Invalid OTLP payload",
947
+ message: e instanceof Error ? e.message : String(e),
948
+ contentType: req.headers["content-type"] ?? null
949
+ });
950
+ }
920
951
  var PROTOBUF_DECODERS = {
921
952
  traces: decodeOtlpTraceRequest,
922
953
  logs: decodeOtlpLogsRequest,
@@ -937,6 +968,18 @@ function findPackageRoot() {
937
968
  return dir;
938
969
  }
939
970
  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>`;
971
+ var cachedVersion = null;
972
+ function getVersion() {
973
+ if (cachedVersion !== null) return cachedVersion;
974
+ let version = "unknown";
975
+ try {
976
+ const pkg = JSON.parse(readFileSync(resolve(findPackageRoot(), "package.json"), "utf8"));
977
+ if (typeof pkg.version === "string") version = pkg.version;
978
+ } catch {
979
+ }
980
+ cachedVersion = version;
981
+ return version;
982
+ }
940
983
  var cachedWidgetJs = null;
941
984
  function getWidgetJs() {
942
985
  if (!cachedWidgetJs) {
@@ -963,6 +1006,8 @@ function attachDevtoolsRoutes(httpServer, devtools) {
963
1006
  res.setHeader("Access-Control-Allow-Origin", "*");
964
1007
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
965
1008
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1009
+ res.setHeader("x-autotel-devtools", getVersion());
1010
+ res.setHeader("Access-Control-Expose-Headers", "x-autotel-devtools");
966
1011
  if (req.method === "OPTIONS") {
967
1012
  res.writeHead(204);
968
1013
  res.end();
@@ -981,7 +1026,12 @@ function attachDevtoolsRoutes(httpServer, devtools) {
981
1026
  return;
982
1027
  }
983
1028
  if (req.method === "GET" && url === "/healthz") {
984
- sendJson(res, 200, { ok: true, clients: devtools.clientCount });
1029
+ sendJson(res, 200, {
1030
+ ok: true,
1031
+ service: DEVTOOLS_IDENTITY,
1032
+ version: getVersion(),
1033
+ clients: devtools.clientCount
1034
+ });
985
1035
  return;
986
1036
  }
987
1037
  if (req.method === "GET" && url === "/v1/traces") {
@@ -1001,7 +1051,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1001
1051
  devtools.addTraces(traces);
1002
1052
  sendJson(res, 200, { acceptedTraces: traces.length });
1003
1053
  } catch (e) {
1004
- sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
1054
+ sendOtlpError(res, req, e);
1005
1055
  }
1006
1056
  return;
1007
1057
  }
@@ -1012,7 +1062,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1012
1062
  devtools.addLogs(logs);
1013
1063
  sendJson(res, 200, { acceptedLogs: logs.length });
1014
1064
  } catch (e) {
1015
- sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
1065
+ sendOtlpError(res, req, e);
1016
1066
  }
1017
1067
  return;
1018
1068
  }
@@ -1022,7 +1072,7 @@ function attachDevtoolsRoutes(httpServer, devtools) {
1022
1072
  const count = countOtlpMetrics(payload);
1023
1073
  sendJson(res, 200, { acceptedMetrics: count });
1024
1074
  } catch (e) {
1025
- sendJson(res, 400, { error: "Invalid OTLP payload", message: e instanceof Error ? e.message : String(e) });
1075
+ sendOtlpError(res, req, e);
1026
1076
  }
1027
1077
  return;
1028
1078
  }
@@ -1239,6 +1289,18 @@ async function main() {
1239
1289
  attachSecondary: (s) => attachDevtoolsRoutes(s, wsServer)
1240
1290
  });
1241
1291
  const { addresses, warnings, port: boundPort } = await listeners.ready;
1292
+ if (boundPort !== options.port) {
1293
+ const holder = await probePortHolder(options.host, options.port);
1294
+ if (holder === "autotel-devtools") {
1295
+ warnings.push(
1296
+ `another autotel-devtools is already running on port ${options.port}; this instance is on ${boundPort}. Use the existing one, or stop it and restart here.`
1297
+ );
1298
+ } else {
1299
+ warnings.push(
1300
+ `port ${options.port} is held by another process that is NOT autotel-devtools. Anything exporting OTLP to :${options.port} is reaching that process, not this devtools. Point your exporter at :${boundPort}, or free :${options.port} and restart.`
1301
+ );
1302
+ }
1303
+ }
1242
1304
  const uiBase = `http://${options.host === "localhost" ? "127.0.0.1" : options.host}:${boundPort}`;
1243
1305
  const title = options.title || "autotel-devtools";
1244
1306
  process.stdout.write(`