autotel-devtools 8.1.1 → 9.0.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.
Files changed (63) hide show
  1. package/dist/cli.cjs +108 -1429
  2. package/dist/cli.cjs.map +1 -1
  3. package/dist/cli.d.cts +1 -1
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +109 -1422
  6. package/dist/cli.js.map +1 -1
  7. package/dist/error-aggregator-BvNmgn7E.d.ts +120 -0
  8. package/dist/error-aggregator-nnfbpSR7.d.cts +120 -0
  9. package/dist/exporter-1Y3GmLVS.d.cts +182 -0
  10. package/dist/exporter-CZ5HdD3o.d.ts +182 -0
  11. package/dist/genai/index.cjs +650 -537
  12. package/dist/genai/index.cjs.map +1 -1
  13. package/dist/genai/index.d.cts +164 -157
  14. package/dist/genai/index.d.ts +164 -157
  15. package/dist/genai/index.js +649 -536
  16. package/dist/genai/index.js.map +1 -1
  17. package/dist/http-BkkKa9C_.js +1128 -0
  18. package/dist/http-BkkKa9C_.js.map +1 -0
  19. package/dist/http-Yj6iSrMX.cjs +1275 -0
  20. package/dist/http-Yj6iSrMX.cjs.map +1 -0
  21. package/dist/index.cjs +50 -1728
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +21 -23
  24. package/dist/index.d.ts +21 -23
  25. package/dist/index.js +44 -1716
  26. package/dist/index.js.map +1 -1
  27. package/dist/listen-BBsxO0wm.cjs +125 -0
  28. package/dist/listen-BBsxO0wm.cjs.map +1 -0
  29. package/dist/listen-DfOCquUq.js +120 -0
  30. package/dist/listen-DfOCquUq.js.map +1 -0
  31. package/dist/resource-utils-B4UVvfnH.js +18 -0
  32. package/dist/resource-utils-B4UVvfnH.js.map +1 -0
  33. package/dist/resource-utils-DjHJB6uc.cjs +24 -0
  34. package/dist/resource-utils-DjHJB6uc.cjs.map +1 -0
  35. package/dist/server/exporter.cjs +135 -159
  36. package/dist/server/exporter.cjs.map +1 -1
  37. package/dist/server/exporter.d.cts +2 -4
  38. package/dist/server/exporter.d.ts +2 -4
  39. package/dist/server/exporter.js +134 -158
  40. package/dist/server/exporter.js.map +1 -1
  41. package/dist/server/index.cjs +29 -1660
  42. package/dist/server/index.d.cts +34 -31
  43. package/dist/server/index.d.ts +34 -31
  44. package/dist/server/index.js +5 -1630
  45. package/dist/server/log-exporter.cjs +75 -102
  46. package/dist/server/log-exporter.cjs.map +1 -1
  47. package/dist/server/log-exporter.d.cts +27 -46
  48. package/dist/server/log-exporter.d.ts +27 -46
  49. package/dist/server/log-exporter.js +74 -100
  50. package/dist/server/log-exporter.js.map +1 -1
  51. package/dist/server/remote-exporter.cjs +171 -213
  52. package/dist/server/remote-exporter.cjs.map +1 -1
  53. package/dist/server/remote-exporter.d.cts +62 -82
  54. package/dist/server/remote-exporter.d.ts +62 -82
  55. package/dist/server/remote-exporter.js +170 -212
  56. package/dist/server/remote-exporter.js.map +1 -1
  57. package/package.json +5 -5
  58. package/dist/error-aggregator-D0Uu5r38.d.ts +0 -147
  59. package/dist/error-aggregator-D1Mr221Y.d.cts +0 -147
  60. package/dist/exporter-De6p4iAD.d.cts +0 -182
  61. package/dist/exporter-De6p4iAD.d.ts +0 -182
  62. package/dist/server/index.cjs.map +0 -1
  63. package/dist/server/index.js.map +0 -1
@@ -0,0 +1,1128 @@
1
+ import { t as getResourceName } from "./resource-utils-B4UVvfnH.js";
2
+ import { createServer } from "node:http";
3
+ import { WebSocket, WebSocketServer } from "ws";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import protobuf from "protobufjs";
8
+
9
+ //#region src/server/error-aggregator.ts
10
+ var ErrorAggregator = class {
11
+ errorGroups = /* @__PURE__ */ new Map();
12
+ options;
13
+ constructor(options = {}) {
14
+ this.options = {
15
+ maxGroups: options.maxGroups ?? 100,
16
+ maxAffectedTraces: options.maxAffectedTraces ?? 10,
17
+ maxAffectedSpans: options.maxAffectedSpans ?? 5,
18
+ stackFramesForFingerprint: options.stackFramesForFingerprint ?? 5
19
+ };
20
+ }
21
+ /**
22
+ * Add an error occurrence to the aggregator
23
+ */
24
+ addError(occurrence) {
25
+ const fingerprint = this.generateFingerprint(occurrence);
26
+ const existing = this.errorGroups.get(fingerprint);
27
+ if (existing) {
28
+ existing.count++;
29
+ existing.lastSeen = occurrence.timestamp;
30
+ if (!existing.affectedTraces.includes(occurrence.traceId)) {
31
+ existing.affectedTraces.push(occurrence.traceId);
32
+ if (existing.affectedTraces.length > this.options.maxAffectedTraces) existing.affectedTraces.shift();
33
+ }
34
+ if (!existing.affectedSpans.includes(occurrence.spanName)) {
35
+ existing.affectedSpans.push(occurrence.spanName);
36
+ if (existing.affectedSpans.length > this.options.maxAffectedSpans) existing.affectedSpans.shift();
37
+ }
38
+ return existing;
39
+ }
40
+ const newGroup = {
41
+ fingerprint,
42
+ type: occurrence.error.type,
43
+ message: occurrence.error.message,
44
+ stackTrace: this.normalizeStackTrace(occurrence.error.stackTrace),
45
+ count: 1,
46
+ firstSeen: occurrence.timestamp,
47
+ lastSeen: occurrence.timestamp,
48
+ affectedTraces: [occurrence.traceId],
49
+ affectedSpans: [occurrence.spanName],
50
+ service: occurrence.service,
51
+ attributes: occurrence.attributes
52
+ };
53
+ if (this.errorGroups.size >= this.options.maxGroups) this.evictOldestGroup();
54
+ this.errorGroups.set(fingerprint, newGroup);
55
+ return newGroup;
56
+ }
57
+ /**
58
+ * Extract errors from a trace and add them to the aggregator
59
+ */
60
+ addErrorsFromTrace(trace) {
61
+ const addedGroups = [];
62
+ for (const span of trace.spans) if (span.status.code === "ERROR") {
63
+ const occurrence = this.extractErrorFromSpan(span, trace);
64
+ if (occurrence) {
65
+ const group = this.addError(occurrence);
66
+ addedGroups.push(group);
67
+ }
68
+ }
69
+ return addedGroups;
70
+ }
71
+ /**
72
+ * Extract error occurrence from a span
73
+ */
74
+ extractErrorFromSpan(span, trace) {
75
+ const exceptionEvent = span.events?.find((e) => e.name === "exception");
76
+ const errorType = span.attributes["exception.type"] || span.attributes["error.type"] || exceptionEvent?.attributes?.["exception.type"] || "Error";
77
+ const errorMessage = span.status.message || span.attributes["exception.message"] || span.attributes["error.message"] || "Unknown error";
78
+ const stackTrace = span.attributes["exception.stacktrace"] || span.attributes["exception.stack"] || this.extractStackFromEvents(span);
79
+ return {
80
+ traceId: trace.traceId,
81
+ spanId: span.spanId,
82
+ spanName: span.name,
83
+ service: trace.service,
84
+ timestamp: span.endTime,
85
+ error: {
86
+ type: errorType,
87
+ message: errorMessage,
88
+ stackTrace
89
+ },
90
+ attributes: this.extractRelevantAttributes(span.attributes)
91
+ };
92
+ }
93
+ /**
94
+ * Extract stack trace from span events (exception events)
95
+ */
96
+ extractStackFromEvents(span) {
97
+ if (!span.events) return void 0;
98
+ const exceptionEvent = span.events.find((e) => e.name === "exception");
99
+ if (exceptionEvent?.attributes) return exceptionEvent.attributes["exception.stacktrace"] || exceptionEvent.attributes["exception.stack"];
100
+ }
101
+ /**
102
+ * Extract relevant attributes for error context
103
+ */
104
+ extractRelevantAttributes(attributes) {
105
+ const relevant = {};
106
+ for (const key of [
107
+ "http.method",
108
+ "http.url",
109
+ "http.route",
110
+ "http.status_code",
111
+ "db.system",
112
+ "db.operation",
113
+ "rpc.method",
114
+ "rpc.service",
115
+ "code.function",
116
+ "code.filepath",
117
+ "user.id",
118
+ "operation.name"
119
+ ]) if (key in attributes) relevant[key] = attributes[key];
120
+ return relevant;
121
+ }
122
+ /**
123
+ * Generate a fingerprint for error grouping
124
+ *
125
+ * Uses error type + first N stack frames (normalized)
126
+ */
127
+ generateFingerprint(occurrence) {
128
+ const parts = [occurrence.error.type];
129
+ if (occurrence.error.stackTrace) {
130
+ const frames = this.extractStackFrames(occurrence.error.stackTrace, this.options.stackFramesForFingerprint);
131
+ parts.push(...frames);
132
+ } else parts.push(this.normalizeMessage(occurrence.error.message));
133
+ return this.simpleHash(parts.join("|"));
134
+ }
135
+ /**
136
+ * Extract and normalize stack frames from a stack trace
137
+ */
138
+ extractStackFrames(stackTrace, count) {
139
+ const lines = stackTrace.split("\n");
140
+ const frames = [];
141
+ for (const line of lines) {
142
+ if (frames.length >= count) break;
143
+ const trimmed = line.trim();
144
+ const nodeMatch = trimmed.match(/^at\s+(.+?)\s+\((.+?):(\d+):\d+\)$/);
145
+ if (nodeMatch) {
146
+ frames.push(`${nodeMatch[1]}@${this.normalizeFilePath(nodeMatch[2])}`);
147
+ continue;
148
+ }
149
+ const anonMatch = trimmed.match(/^at\s+(.+?):(\d+):\d+$/);
150
+ if (anonMatch) {
151
+ frames.push(`anonymous@${this.normalizeFilePath(anonMatch[1])}`);
152
+ continue;
153
+ }
154
+ const browserMatch = trimmed.match(/^(.+?)@(.+?):(\d+):\d+$/);
155
+ if (browserMatch) {
156
+ frames.push(`${browserMatch[1]}@${this.normalizeFilePath(browserMatch[2])}`);
157
+ continue;
158
+ }
159
+ }
160
+ return frames;
161
+ }
162
+ /**
163
+ * Normalize file path by removing absolute path prefixes and node_modules paths
164
+ */
165
+ normalizeFilePath(filePath) {
166
+ const nodeModulesMatch = filePath.match(/node_modules\/(@[^/]+\/[^/]+|[^/]+)/);
167
+ if (nodeModulesMatch) return `[npm]/${nodeModulesMatch[1]}`;
168
+ return filePath.replace(/^.*?\/src\//, "src/").replace(/^.*?\/dist\//, "dist/").replace(/^.*?\/lib\//, "lib/").replace(/^file:\/\//, "");
169
+ }
170
+ /**
171
+ * Normalize error message by removing dynamic parts
172
+ */
173
+ normalizeMessage(message) {
174
+ return message.replaceAll(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "[UUID]").replaceAll(/\b[0-9a-f]{16,}\b/gi, "[ID]").replaceAll(/\b\d+\b/g, "[N]").replaceAll(/"[^"]*"/g, "\"[STR]\"").replaceAll(/'[^']*'/g, "'[STR]'").slice(0, 200);
175
+ }
176
+ /**
177
+ * Normalize stack trace for display
178
+ */
179
+ normalizeStackTrace(stackTrace) {
180
+ if (!stackTrace) return void 0;
181
+ return stackTrace.split("\n").slice(0, 10).join("\n");
182
+ }
183
+ /**
184
+ * Simple hash function for fingerprinting
185
+ */
186
+ simpleHash(str) {
187
+ let hash = 0;
188
+ for (let i = 0; i < str.length; i++) {
189
+ const char = str.charCodeAt(i);
190
+ hash = (hash << 5) - hash + char;
191
+ hash = hash & hash;
192
+ }
193
+ return Math.abs(hash).toString(16).padStart(8, "0");
194
+ }
195
+ /**
196
+ * Evict the oldest error group
197
+ */
198
+ evictOldestGroup() {
199
+ let oldest = null;
200
+ for (const [fingerprint, group] of this.errorGroups) if (!oldest || group.lastSeen < oldest.lastSeen) oldest = {
201
+ fingerprint,
202
+ lastSeen: group.lastSeen
203
+ };
204
+ if (oldest) this.errorGroups.delete(oldest.fingerprint);
205
+ }
206
+ /**
207
+ * Get all error groups, sorted by most recent
208
+ */
209
+ getErrorGroups() {
210
+ return [...this.errorGroups.values()].sort((a, b) => b.lastSeen - a.lastSeen);
211
+ }
212
+ /**
213
+ * Get error groups sorted by count (most frequent first)
214
+ */
215
+ getErrorGroupsByFrequency() {
216
+ return [...this.errorGroups.values()].sort((a, b) => b.count - a.count);
217
+ }
218
+ /**
219
+ * Get a specific error group by fingerprint
220
+ */
221
+ getErrorGroup(fingerprint) {
222
+ return this.errorGroups.get(fingerprint);
223
+ }
224
+ /**
225
+ * Get error groups for a specific service
226
+ */
227
+ getErrorGroupsByService(service) {
228
+ return this.getErrorGroups().filter((g) => g.service === service);
229
+ }
230
+ /**
231
+ * Get total error count across all groups
232
+ */
233
+ getTotalErrorCount() {
234
+ let total = 0;
235
+ for (const group of this.errorGroups.values()) total += group.count;
236
+ return total;
237
+ }
238
+ /**
239
+ * Get error statistics
240
+ */
241
+ getStats() {
242
+ const oneHourAgo = Date.now() - 3600 * 1e3;
243
+ let recentErrors = 0;
244
+ const typeCount = /* @__PURE__ */ new Map();
245
+ for (const group of this.errorGroups.values()) {
246
+ if (group.lastSeen > oneHourAgo) recentErrors += group.count;
247
+ typeCount.set(group.type, (typeCount.get(group.type) || 0) + group.count);
248
+ }
249
+ const topErrorTypes = [...typeCount.entries()].map(([type, count]) => ({
250
+ type,
251
+ count
252
+ })).sort((a, b) => b.count - a.count).slice(0, 5);
253
+ return {
254
+ totalGroups: this.errorGroups.size,
255
+ totalErrors: this.getTotalErrorCount(),
256
+ recentErrors,
257
+ topErrorTypes
258
+ };
259
+ }
260
+ /**
261
+ * Clear all error groups
262
+ */
263
+ clear() {
264
+ this.errorGroups.clear();
265
+ }
266
+ /**
267
+ * Clear old error groups (not seen in given time window)
268
+ */
269
+ clearOlderThan(maxAgeMs) {
270
+ const cutoff = Date.now() - maxAgeMs;
271
+ let cleared = 0;
272
+ for (const [fingerprint, group] of this.errorGroups) if (group.lastSeen < cutoff) {
273
+ this.errorGroups.delete(fingerprint);
274
+ cleared++;
275
+ }
276
+ return cleared;
277
+ }
278
+ };
279
+
280
+ //#endregion
281
+ //#region src/server/telemetry-limits.ts
282
+ const defaultLimit = 100;
283
+ function parseLimit(value) {
284
+ if (!value) return void 0;
285
+ const parsed = Number.parseInt(value, 10);
286
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
287
+ }
288
+ function resolveTelemetryLimits(args = {}) {
289
+ const env = args.env ?? process.env;
290
+ const fallback = args.maxHistory ?? defaultLimit;
291
+ return {
292
+ maxTraceCount: args.maxTraceCount ?? parseLimit(env.AUTOTEL_MAX_TRACE_COUNT) ?? fallback,
293
+ maxLogCount: args.maxLogCount ?? parseLimit(env.AUTOTEL_MAX_LOG_COUNT) ?? fallback,
294
+ maxMetricCount: args.maxMetricCount ?? parseLimit(env.AUTOTEL_MAX_METRIC_COUNT) ?? fallback
295
+ };
296
+ }
297
+ function appendWithLimit(items, item, limit) {
298
+ if (limit <= 0) return [];
299
+ const next = [...items, item];
300
+ return next.length > limit ? next.slice(next.length - limit) : next;
301
+ }
302
+ function appendManyWithLimit(items, incoming, limit) {
303
+ if (limit <= 0 || incoming.length === 0) return limit <= 0 ? [] : items;
304
+ const next = [...items, ...incoming];
305
+ return next.length > limit ? next.slice(next.length - limit) : next;
306
+ }
307
+ function applyTelemetryLimits(data, limits) {
308
+ return {
309
+ ...data,
310
+ traces: data.traces.slice(-limits.maxTraceCount),
311
+ logs: data.logs.slice(-limits.maxLogCount),
312
+ metrics: data.metrics.slice(-limits.maxMetricCount)
313
+ };
314
+ }
315
+
316
+ //#endregion
317
+ //#region src/server/origin-guard.ts
318
+ const LOOPBACK_IPV6 = new Set(["::1", "0:0:0:0:0:0:0:1"]);
319
+ /** True for `localhost`, any `127.x.x.x`, and IPv6 loopback. Case-insensitive. */
320
+ function isLoopbackHostname(hostname) {
321
+ const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
322
+ return h === "localhost" || /^127\./.test(h) || LOOPBACK_IPV6.has(h);
323
+ }
324
+ /** Hostname from a `Host` header (`host`, `host:port`, `[::1]:port`). */
325
+ function hostnameFromHostHeader(host) {
326
+ const h = host.trim();
327
+ if (h.startsWith("[")) {
328
+ const end = h.indexOf("]");
329
+ return end > 0 ? h.slice(1, end) : h;
330
+ }
331
+ const colon = h.indexOf(":");
332
+ return colon === -1 ? h : h.slice(0, colon);
333
+ }
334
+ /** True when the `Host` header names a loopback host. */
335
+ function hostHeaderIsLoopback(host) {
336
+ return isLoopbackHostname(hostnameFromHostHeader(host));
337
+ }
338
+ /** True when an `Origin` header names a loopback origin. A malformed or opaque
339
+ * origin (e.g. the literal `null` from a sandboxed iframe) is treated as
340
+ * non-loopback. */
341
+ function originIsLoopback(origin) {
342
+ try {
343
+ return isLoopbackHostname(new URL(origin).hostname);
344
+ } catch {
345
+ return false;
346
+ }
347
+ }
348
+ /**
349
+ * Decide whether a request to a sensitive (read/mutate) endpoint is allowed.
350
+ * - A present, non-loopback `Origin` is always rejected (cross-origin read).
351
+ * - When `loopbackOnly`, a present, non-loopback `Host` is rejected (DNS
352
+ * rebinding). Skipped when the receiver is bound to a non-loopback host.
353
+ */
354
+ function allowSensitiveRequest(headers, loopbackOnly) {
355
+ const { origin, host } = headers;
356
+ if (origin && origin.length > 0 && !originIsLoopback(origin)) return false;
357
+ if (loopbackOnly && host && host.length > 0 && !hostHeaderIsLoopback(host)) return false;
358
+ return true;
359
+ }
360
+
361
+ //#endregion
362
+ //#region src/server/server.ts
363
+ var DevtoolsServer = class {
364
+ wss;
365
+ clients = /* @__PURE__ */ new Set();
366
+ httpServer;
367
+ traces = [];
368
+ logs = [];
369
+ metrics = [];
370
+ errorAggregator = new ErrorAggregator();
371
+ limits;
372
+ verbose;
373
+ _port;
374
+ onData;
375
+ constructor(options = {}) {
376
+ this.limits = resolveTelemetryLimits(options);
377
+ this.verbose = options.verbose ?? false;
378
+ this._port = options.port ?? 4318;
379
+ this.onData = options.onData;
380
+ this.httpServer = options.server ?? createServer();
381
+ const loopbackOnly = options.host == null || hostHeaderIsLoopback(options.host);
382
+ this.wss = new WebSocketServer({
383
+ server: this.httpServer,
384
+ path: options.path ?? "/ws",
385
+ verifyClient: ({ origin, req }) => allowSensitiveRequest({
386
+ origin,
387
+ host: req.headers.host
388
+ }, loopbackOnly)
389
+ });
390
+ this.wss.on("error", (err) => {
391
+ if (this.httpServer.listening) throw err;
392
+ });
393
+ this.wss.on("connection", (ws) => {
394
+ this.clients.add(ws);
395
+ this.log(`Client connected (${this.clients.size} total)`);
396
+ const data = this.getCurrentData();
397
+ if (data.traces.length > 0 || data.logs.length > 0 || data.errors.length > 0) ws.send(JSON.stringify(data));
398
+ ws.on("close", () => {
399
+ this.clients.delete(ws);
400
+ this.log(`Client disconnected (${this.clients.size} total)`);
401
+ });
402
+ });
403
+ if (!options.server) this.httpServer.listen(this._port, () => {
404
+ const addr = this.httpServer.address();
405
+ if (addr && typeof addr === "object") this._port = addr.port;
406
+ this.log(`WebSocket server listening on port ${this._port}`);
407
+ });
408
+ }
409
+ get port() {
410
+ const addr = this.httpServer.address();
411
+ if (addr && typeof addr === "object") return addr.port;
412
+ return this._port;
413
+ }
414
+ get clientCount() {
415
+ return this.clients.size;
416
+ }
417
+ addTrace(trace) {
418
+ const existing = this.traces.find((t) => t.traceId === trace.traceId);
419
+ const merged = existing ?? trace;
420
+ if (existing) {
421
+ const existingSpanIds = new Set(existing.spans.map((s) => s.spanId));
422
+ for (const span of trace.spans) if (!existingSpanIds.has(span.spanId)) existing.spans.push(span);
423
+ existing.startTime = Math.min(existing.startTime, trace.startTime);
424
+ existing.endTime = Math.max(existing.endTime, trace.endTime);
425
+ existing.duration = existing.endTime - existing.startTime;
426
+ if (trace.status === "ERROR") existing.status = "ERROR";
427
+ const root = existing.spans.find((s) => !s.parentSpanId);
428
+ if (root) {
429
+ existing.rootSpan = root;
430
+ const rootService = root.attributes?.["service.name"];
431
+ if (typeof rootService === "string" && rootService.length > 0) existing.service = rootService;
432
+ }
433
+ } else this.traces = appendWithLimit(this.traces, trace, this.limits.maxTraceCount);
434
+ this.errorAggregator.addErrorsFromTrace(trace);
435
+ this.broadcast({
436
+ traces: [merged],
437
+ metrics: [],
438
+ logs: [],
439
+ errors: this.errorAggregator.getErrorGroups()
440
+ });
441
+ }
442
+ addTraces(traces) {
443
+ for (const trace of traces) this.addTrace(trace);
444
+ }
445
+ addLog(log) {
446
+ this.logs = appendWithLimit(this.logs, log, this.limits.maxLogCount);
447
+ this.broadcast({
448
+ traces: [],
449
+ metrics: [],
450
+ logs: [log],
451
+ errors: this.errorAggregator.getErrorGroups()
452
+ });
453
+ }
454
+ addLogs(logs) {
455
+ this.logs = appendManyWithLimit(this.logs, logs, this.limits.maxLogCount);
456
+ this.broadcast({
457
+ traces: [],
458
+ metrics: [],
459
+ logs,
460
+ errors: this.errorAggregator.getErrorGroups()
461
+ });
462
+ }
463
+ addMetric(metric) {
464
+ this.metrics = appendWithLimit(this.metrics, metric, this.limits.maxMetricCount);
465
+ this.broadcast({
466
+ traces: [],
467
+ metrics: [metric],
468
+ logs: [],
469
+ errors: this.errorAggregator.getErrorGroups()
470
+ });
471
+ }
472
+ getCurrentData() {
473
+ return {
474
+ traces: this.traces,
475
+ metrics: this.metrics,
476
+ logs: this.logs,
477
+ errors: this.errorAggregator.getErrorGroups()
478
+ };
479
+ }
480
+ clearData() {
481
+ this.traces = [];
482
+ this.logs = [];
483
+ this.metrics = [];
484
+ this.errorAggregator.clear();
485
+ }
486
+ broadcast(data) {
487
+ const msg = JSON.stringify(data);
488
+ for (const client of this.clients) if (client.readyState === WebSocket.OPEN) client.send(msg);
489
+ if (this.onData) try {
490
+ this.onData(data);
491
+ } catch {}
492
+ }
493
+ log(message) {
494
+ if (this.verbose) console.log(`[autotel-devtools] ${message}`);
495
+ }
496
+ async close() {
497
+ for (const client of this.clients) client.close();
498
+ this.clients.clear();
499
+ this.wss.close();
500
+ await new Promise((resolve) => this.httpServer.close(() => resolve()));
501
+ }
502
+ };
503
+
504
+ //#endregion
505
+ //#region src/server/otlp.ts
506
+ function resolveOtlpValue(v) {
507
+ if (!v) return void 0;
508
+ if (v.stringValue !== void 0) return v.stringValue;
509
+ if (v.boolValue !== void 0) return v.boolValue;
510
+ if (v.intValue !== void 0) return typeof v.intValue === "string" ? Number(v.intValue) : v.intValue;
511
+ if (v.doubleValue !== void 0) return v.doubleValue;
512
+ if (v.bytesValue !== void 0) return v.bytesValue;
513
+ if (v.arrayValue?.values) return v.arrayValue.values.map(resolveOtlpValue);
514
+ if (v.kvlistValue?.values) return flattenAttributes(v.kvlistValue.values);
515
+ }
516
+ function flattenAttributes(attrs) {
517
+ const out = {};
518
+ if (!attrs) return out;
519
+ for (const { key, value } of attrs) out[key] = resolveOtlpValue(value);
520
+ return out;
521
+ }
522
+ function nanoToMs(nano) {
523
+ if (!nano) return 0;
524
+ const ns = BigInt(nano);
525
+ const ms = ns / 1000000n;
526
+ const remNs = ns % 1000000n;
527
+ return Number(ms) + Number(remNs) / 1e6;
528
+ }
529
+ const SPAN_KIND_MAP = {
530
+ 0: "INTERNAL",
531
+ 1: "INTERNAL",
532
+ 2: "SERVER",
533
+ 3: "CLIENT",
534
+ 4: "PRODUCER",
535
+ 5: "CONSUMER",
536
+ SPAN_KIND_INTERNAL: "INTERNAL",
537
+ SPAN_KIND_SERVER: "SERVER",
538
+ SPAN_KIND_CLIENT: "CLIENT",
539
+ SPAN_KIND_PRODUCER: "PRODUCER",
540
+ SPAN_KIND_CONSUMER: "CONSUMER"
541
+ };
542
+ function normalizeHexId(id) {
543
+ if (!id) return "";
544
+ if (/^[A-Za-z0-9+/=]+$/.test(id) && !/^[0-9a-f]+$/i.test(id) && (id.length === 12 || id.length === 24 || id.length === 28 || id.length === 44 || id.length === 48)) try {
545
+ return Buffer.from(id, "base64").toString("hex");
546
+ } catch {}
547
+ return id;
548
+ }
549
+ function parseOtlpTraces(payload) {
550
+ if (!payload || typeof payload !== "object") return [];
551
+ const { resourceSpans } = payload;
552
+ if (!Array.isArray(resourceSpans) || resourceSpans.length === 0) return [];
553
+ const traceMap = /* @__PURE__ */ new Map();
554
+ for (const rs of resourceSpans) {
555
+ const resourceAttrs = flattenAttributes(rs.resource?.attributes);
556
+ const service = String(resourceAttrs["service.name"] || "unknown");
557
+ const scopeSpans = rs.scopeSpans || [];
558
+ for (const ss of scopeSpans) {
559
+ const scope = ss.scope?.name ? {
560
+ name: ss.scope.name,
561
+ version: ss.scope.version || void 0
562
+ } : void 0;
563
+ for (const span of ss.spans || []) {
564
+ const traceId = normalizeHexId(span.traceId);
565
+ if (!traceId) continue;
566
+ const startMs = nanoToMs(span.startTimeUnixNano);
567
+ const endMs = nanoToMs(span.endTimeUnixNano);
568
+ const statusCode = span.status?.code;
569
+ let status = "UNSET";
570
+ if (statusCode === 1 || statusCode === "STATUS_CODE_OK") status = "OK";
571
+ if (statusCode === 2 || statusCode === "STATUS_CODE_ERROR") status = "ERROR";
572
+ const spanData = {
573
+ traceId,
574
+ spanId: normalizeHexId(span.spanId),
575
+ parentSpanId: normalizeHexId(span.parentSpanId) || void 0,
576
+ name: span.name || "unknown",
577
+ kind: SPAN_KIND_MAP[span.kind ?? 0] || "INTERNAL",
578
+ startTime: startMs,
579
+ endTime: endMs,
580
+ duration: endMs - startMs,
581
+ attributes: {
582
+ ...resourceAttrs,
583
+ ...flattenAttributes(span.attributes)
584
+ },
585
+ status: {
586
+ code: status,
587
+ message: span.status?.message
588
+ },
589
+ events: (span.events || []).map((e) => ({
590
+ name: e.name || "",
591
+ timestamp: nanoToMs(e.timeUnixNano),
592
+ attributes: flattenAttributes(e.attributes)
593
+ })),
594
+ links: (span.links || []).map((l) => ({
595
+ traceId: normalizeHexId(l.traceId),
596
+ spanId: normalizeHexId(l.spanId),
597
+ attributes: flattenAttributes(l.attributes)
598
+ })),
599
+ scope
600
+ };
601
+ const existing = traceMap.get(traceId);
602
+ if (existing) existing.spans.push(spanData);
603
+ else traceMap.set(traceId, {
604
+ spans: [spanData],
605
+ service
606
+ });
607
+ }
608
+ }
609
+ }
610
+ const traces = [];
611
+ for (const [traceId, { spans, service }] of traceMap) {
612
+ const sorted = spans.sort((a, b) => a.startTime - b.startTime);
613
+ const rootSpan = sorted.find((s) => !s.parentSpanId) || sorted[0];
614
+ const startTime = Math.min(...sorted.map((s) => s.startTime));
615
+ const endTime = Math.max(...sorted.map((s) => s.endTime));
616
+ const hasError = sorted.some((s) => s.status.code === "ERROR");
617
+ traces.push({
618
+ traceId,
619
+ correlationId: traceId.slice(0, 16),
620
+ rootSpan,
621
+ spans: sorted,
622
+ startTime,
623
+ endTime,
624
+ duration: endTime - startTime,
625
+ status: hasError ? "ERROR" : "OK",
626
+ service
627
+ });
628
+ }
629
+ return traces;
630
+ }
631
+ function parseOtlpLogs(payload) {
632
+ if (!payload || typeof payload !== "object") return [];
633
+ const { resourceLogs } = payload;
634
+ if (!Array.isArray(resourceLogs)) return [];
635
+ const logs = [];
636
+ for (const rl of resourceLogs) {
637
+ const resourceAttrs = flattenAttributes(rl.resource?.attributes);
638
+ for (const sl of rl.scopeLogs || []) for (const rec of sl.logRecords || []) {
639
+ const timestamp = nanoToMs(rec.timeUnixNano || rec.observedTimeUnixNano);
640
+ const traceId = normalizeHexId(rec.traceId) || void 0;
641
+ const spanId = normalizeHexId(rec.spanId) || void 0;
642
+ const body = rec.body ? resolveOtlpValue(rec.body) : "";
643
+ logs.push({
644
+ id: `${traceId || "no-trace"}:${spanId || "no-span"}:${timestamp}:${rec.severityNumber || 0}`,
645
+ traceId,
646
+ spanId,
647
+ resourceName: getResourceName(resourceAttrs),
648
+ severityText: rec.severityText,
649
+ severityNumber: rec.severityNumber,
650
+ body: typeof body === "string" ? body : body,
651
+ timestamp,
652
+ attributes: flattenAttributes(rec.attributes),
653
+ resource: resourceAttrs
654
+ });
655
+ }
656
+ }
657
+ return logs;
658
+ }
659
+ function countOtlpMetrics(payload) {
660
+ if (!payload || typeof payload !== "object") return 0;
661
+ const { resourceMetrics } = payload;
662
+ if (!Array.isArray(resourceMetrics)) return 0;
663
+ let count = 0;
664
+ for (const rm of resourceMetrics) for (const sm of rm.scopeMetrics || []) count += (sm.metrics || []).length;
665
+ return count;
666
+ }
667
+ async function readJsonBody(req) {
668
+ return new Promise((resolve, reject) => {
669
+ const chunks = [];
670
+ req.on("data", (chunk) => chunks.push(chunk));
671
+ req.on("end", () => {
672
+ try {
673
+ resolve(JSON.parse(Buffer.concat(chunks).toString()));
674
+ } catch {
675
+ reject(/* @__PURE__ */ new Error("Invalid JSON"));
676
+ }
677
+ });
678
+ req.on("error", reject);
679
+ });
680
+ }
681
+ async function readRawBody(req) {
682
+ return new Promise((resolve, reject) => {
683
+ const chunks = [];
684
+ req.on("data", (chunk) => chunks.push(chunk));
685
+ req.on("end", () => resolve(Buffer.concat(chunks)));
686
+ req.on("error", reject);
687
+ });
688
+ }
689
+ /**
690
+ * True for OTLP/protobuf bodies. The OpenTelemetry Python/Java/Go SDKs default to
691
+ * `http/protobuf` over OTLP HTTP, sending `application/x-protobuf`; some clients use
692
+ * `application/protobuf`. Anything else (JSON, unset) is treated as OTLP/JSON.
693
+ */
694
+ function isProtobufContentType(contentType) {
695
+ if (!contentType) return false;
696
+ const value = contentType.toLowerCase();
697
+ return value.includes("application/x-protobuf") || value.includes("application/protobuf");
698
+ }
699
+ function sendJson(res, status, data) {
700
+ const body = JSON.stringify(data);
701
+ res.writeHead(status, {
702
+ "Content-Type": "application/json",
703
+ "Content-Length": Buffer.byteLength(body)
704
+ });
705
+ res.end(body);
706
+ }
707
+
708
+ //#endregion
709
+ //#region src/server/otlp-proto.ts
710
+ const COMMON_PROTO = `
711
+ syntax = "proto3";
712
+ package opentelemetry.proto.common.v1;
713
+
714
+ message AnyValue {
715
+ oneof value {
716
+ string string_value = 1;
717
+ bool bool_value = 2;
718
+ int64 int_value = 3;
719
+ double double_value = 4;
720
+ ArrayValue array_value = 5;
721
+ KeyValueList kvlist_value = 6;
722
+ bytes bytes_value = 7;
723
+ }
724
+ }
725
+ message ArrayValue { repeated AnyValue values = 1; }
726
+ message KeyValueList { repeated KeyValue values = 1; }
727
+ message KeyValue {
728
+ string key = 1;
729
+ AnyValue value = 2;
730
+ }
731
+ message InstrumentationScope {
732
+ string name = 1;
733
+ string version = 2;
734
+ repeated KeyValue attributes = 3;
735
+ uint32 dropped_attributes_count = 4;
736
+ }
737
+ `;
738
+ const RESOURCE_PROTO = `
739
+ syntax = "proto3";
740
+ package opentelemetry.proto.resource.v1;
741
+
742
+ message Resource {
743
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 1;
744
+ uint32 dropped_attributes_count = 2;
745
+ }
746
+ `;
747
+ const TRACE_PROTO = `
748
+ syntax = "proto3";
749
+ package opentelemetry.proto.trace.v1;
750
+
751
+ message ResourceSpans {
752
+ opentelemetry.proto.resource.v1.Resource resource = 1;
753
+ repeated ScopeSpans scope_spans = 2;
754
+ string schema_url = 3;
755
+ }
756
+ message ScopeSpans {
757
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
758
+ repeated Span spans = 2;
759
+ string schema_url = 3;
760
+ }
761
+ message Span {
762
+ bytes trace_id = 1;
763
+ bytes span_id = 2;
764
+ string trace_state = 3;
765
+ bytes parent_span_id = 4;
766
+ fixed32 flags = 16;
767
+ string name = 5;
768
+ SpanKind kind = 6;
769
+ fixed64 start_time_unix_nano = 7;
770
+ fixed64 end_time_unix_nano = 8;
771
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 9;
772
+ uint32 dropped_attributes_count = 10;
773
+ repeated Event events = 11;
774
+ uint32 dropped_events_count = 12;
775
+ repeated Link links = 13;
776
+ uint32 dropped_links_count = 14;
777
+ Status status = 15;
778
+
779
+ enum SpanKind {
780
+ SPAN_KIND_UNSPECIFIED = 0;
781
+ SPAN_KIND_INTERNAL = 1;
782
+ SPAN_KIND_SERVER = 2;
783
+ SPAN_KIND_CLIENT = 3;
784
+ SPAN_KIND_PRODUCER = 4;
785
+ SPAN_KIND_CONSUMER = 5;
786
+ }
787
+ message Event {
788
+ fixed64 time_unix_nano = 1;
789
+ string name = 2;
790
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 3;
791
+ uint32 dropped_attributes_count = 4;
792
+ }
793
+ message Link {
794
+ bytes trace_id = 1;
795
+ bytes span_id = 2;
796
+ string trace_state = 3;
797
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 4;
798
+ uint32 dropped_attributes_count = 5;
799
+ fixed32 flags = 6;
800
+ }
801
+ }
802
+ message Status {
803
+ reserved 1;
804
+ string message = 2;
805
+ StatusCode code = 3;
806
+
807
+ enum StatusCode {
808
+ STATUS_CODE_UNSET = 0;
809
+ STATUS_CODE_OK = 1;
810
+ STATUS_CODE_ERROR = 2;
811
+ }
812
+ }
813
+ message ExportTraceServiceRequest {
814
+ repeated ResourceSpans resource_spans = 1;
815
+ }
816
+ `;
817
+ const LOGS_PROTO = `
818
+ syntax = "proto3";
819
+ package opentelemetry.proto.logs.v1;
820
+
821
+ enum SeverityNumber {
822
+ SEVERITY_NUMBER_UNSPECIFIED = 0;
823
+ SEVERITY_NUMBER_TRACE = 1;
824
+ SEVERITY_NUMBER_TRACE2 = 2;
825
+ SEVERITY_NUMBER_TRACE3 = 3;
826
+ SEVERITY_NUMBER_TRACE4 = 4;
827
+ SEVERITY_NUMBER_DEBUG = 5;
828
+ SEVERITY_NUMBER_DEBUG2 = 6;
829
+ SEVERITY_NUMBER_DEBUG3 = 7;
830
+ SEVERITY_NUMBER_DEBUG4 = 8;
831
+ SEVERITY_NUMBER_INFO = 9;
832
+ SEVERITY_NUMBER_INFO2 = 10;
833
+ SEVERITY_NUMBER_INFO3 = 11;
834
+ SEVERITY_NUMBER_INFO4 = 12;
835
+ SEVERITY_NUMBER_WARN = 13;
836
+ SEVERITY_NUMBER_WARN2 = 14;
837
+ SEVERITY_NUMBER_WARN3 = 15;
838
+ SEVERITY_NUMBER_WARN4 = 16;
839
+ SEVERITY_NUMBER_ERROR = 17;
840
+ SEVERITY_NUMBER_ERROR2 = 18;
841
+ SEVERITY_NUMBER_ERROR3 = 19;
842
+ SEVERITY_NUMBER_ERROR4 = 20;
843
+ SEVERITY_NUMBER_FATAL = 21;
844
+ SEVERITY_NUMBER_FATAL2 = 22;
845
+ SEVERITY_NUMBER_FATAL3 = 23;
846
+ SEVERITY_NUMBER_FATAL4 = 24;
847
+ }
848
+ message ResourceLogs {
849
+ opentelemetry.proto.resource.v1.Resource resource = 1;
850
+ repeated ScopeLogs scope_logs = 2;
851
+ string schema_url = 3;
852
+ }
853
+ message ScopeLogs {
854
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
855
+ repeated LogRecord log_records = 2;
856
+ string schema_url = 3;
857
+ }
858
+ message LogRecord {
859
+ reserved 4;
860
+ fixed64 time_unix_nano = 1;
861
+ fixed64 observed_time_unix_nano = 11;
862
+ SeverityNumber severity_number = 2;
863
+ string severity_text = 3;
864
+ opentelemetry.proto.common.v1.AnyValue body = 5;
865
+ repeated opentelemetry.proto.common.v1.KeyValue attributes = 6;
866
+ uint32 dropped_attributes_count = 7;
867
+ fixed32 flags = 8;
868
+ bytes trace_id = 9;
869
+ bytes span_id = 10;
870
+ }
871
+ message ExportLogsServiceRequest {
872
+ repeated ResourceLogs resource_logs = 1;
873
+ }
874
+ `;
875
+ const METRICS_PROTO = `
876
+ syntax = "proto3";
877
+ package opentelemetry.proto.metrics.v1;
878
+
879
+ message ResourceMetrics {
880
+ opentelemetry.proto.resource.v1.Resource resource = 1;
881
+ repeated ScopeMetrics scope_metrics = 2;
882
+ string schema_url = 3;
883
+ }
884
+ message ScopeMetrics {
885
+ opentelemetry.proto.common.v1.InstrumentationScope scope = 1;
886
+ repeated Metric metrics = 2;
887
+ string schema_url = 3;
888
+ }
889
+ message Metric {
890
+ string name = 1;
891
+ string description = 2;
892
+ string unit = 3;
893
+ }
894
+ message ExportMetricsServiceRequest {
895
+ repeated ResourceMetrics resource_metrics = 1;
896
+ }
897
+ `;
898
+ const TO_OBJECT_OPTIONS = {
899
+ longs: String,
900
+ bytes: String,
901
+ defaults: false
902
+ };
903
+ let cachedRoot = null;
904
+ function getRoot() {
905
+ if (cachedRoot) return cachedRoot;
906
+ const root = new protobuf.Root();
907
+ for (const source of [
908
+ COMMON_PROTO,
909
+ RESOURCE_PROTO,
910
+ TRACE_PROTO,
911
+ LOGS_PROTO,
912
+ METRICS_PROTO
913
+ ]) protobuf.parse(source, root, { keepCase: false });
914
+ root.resolveAll();
915
+ cachedRoot = root;
916
+ return root;
917
+ }
918
+ function decodeRequest(typeName, body) {
919
+ const messageType = getRoot().lookupType(typeName);
920
+ const message = messageType.decode(body);
921
+ return messageType.toObject(message, TO_OBJECT_OPTIONS);
922
+ }
923
+ /** Decode an OTLP/protobuf `ExportTraceServiceRequest` into the OTLP/JSON object shape. */
924
+ function decodeOtlpTraceRequest(body) {
925
+ return decodeRequest("opentelemetry.proto.trace.v1.ExportTraceServiceRequest", body);
926
+ }
927
+ /** Decode an OTLP/protobuf `ExportLogsServiceRequest` into the OTLP/JSON object shape. */
928
+ function decodeOtlpLogsRequest(body) {
929
+ return decodeRequest("opentelemetry.proto.logs.v1.ExportLogsServiceRequest", body);
930
+ }
931
+ /** Decode an OTLP/protobuf `ExportMetricsServiceRequest` into the OTLP/JSON object shape. */
932
+ function decodeOtlpMetricsRequest(body) {
933
+ return decodeRequest("opentelemetry.proto.metrics.v1.ExportMetricsServiceRequest", body);
934
+ }
935
+
936
+ //#endregion
937
+ //#region src/server/identity.ts
938
+ /** Value of the `x-autotel-devtools` response header and the /healthz `service` field. */
939
+ const DEVTOOLS_IDENTITY = "autotel-devtools";
940
+ /**
941
+ * Probe `host:port` over HTTP and classify what is listening. Used when our
942
+ * requested port is busy: it lets us tell "a stale autotel-devtools is still
943
+ * up" (benign) apart from "a foreign collector owns this port" — the latter is
944
+ * the silent footgun where apps keep exporting OTLP to the busy port and reach
945
+ * the wrong process, so the devtools UI stays empty and the app sees errors.
946
+ */
947
+ async function probePortHolder(host, port, timeoutMs = 500) {
948
+ const authority = host.includes(":") ? `[${host}]` : host;
949
+ const controller = new AbortController();
950
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
951
+ try {
952
+ const res = await fetch(`http://${authority}:${port}/healthz`, { signal: controller.signal });
953
+ if (res.headers.get("x-autotel-devtools")) return "autotel-devtools";
954
+ try {
955
+ const body = await res.json();
956
+ if (body && body.service === "autotel-devtools") return "autotel-devtools";
957
+ } catch {}
958
+ return "foreign";
959
+ } catch {
960
+ return "none";
961
+ } finally {
962
+ clearTimeout(timer);
963
+ }
964
+ }
965
+
966
+ //#endregion
967
+ //#region src/server/http.ts
968
+ function sendOtlpError(res, req, e) {
969
+ sendJson(res, 400, {
970
+ error: "Invalid OTLP payload",
971
+ message: e instanceof Error ? e.message : String(e),
972
+ contentType: req.headers["content-type"] ?? null
973
+ });
974
+ }
975
+ const PROTOBUF_DECODERS = {
976
+ traces: decodeOtlpTraceRequest,
977
+ logs: decodeOtlpLogsRequest,
978
+ metrics: decodeOtlpMetricsRequest
979
+ };
980
+ async function readOtlpPayload(req, signal) {
981
+ if (isProtobufContentType(req.headers["content-type"])) return PROTOBUF_DECODERS[signal](await readRawBody(req));
982
+ return readJsonBody(req);
983
+ }
984
+ function findPackageRoot() {
985
+ let dir = dirname(fileURLToPath(import.meta.url));
986
+ for (let i = 0; i < 5; i++) {
987
+ if (existsSync(resolve(dir, "package.json"))) return dir;
988
+ dir = dirname(dir);
989
+ }
990
+ return dir;
991
+ }
992
+ const 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\">🛰️</text></svg>";
993
+ const 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>`;
994
+ let cachedVersion = null;
995
+ function getVersion() {
996
+ if (cachedVersion !== null) return cachedVersion;
997
+ let version = "unknown";
998
+ try {
999
+ const pkg = JSON.parse(readFileSync(resolve(findPackageRoot(), "package.json"), "utf8"));
1000
+ if (typeof pkg.version === "string") version = pkg.version;
1001
+ } catch {}
1002
+ cachedVersion = version;
1003
+ return version;
1004
+ }
1005
+ let cachedWidgetJs = null;
1006
+ function getWidgetJs() {
1007
+ if (!cachedWidgetJs) {
1008
+ const pkgRoot = findPackageRoot();
1009
+ const candidates = [resolve(pkgRoot, "dist", "widget.global.js"), resolve(pkgRoot, "widget.global.js")];
1010
+ for (const candidate of candidates) try {
1011
+ cachedWidgetJs = readFileSync(candidate, "utf8");
1012
+ break;
1013
+ } catch {}
1014
+ if (!cachedWidgetJs) cachedWidgetJs = "// widget bundle not found - run pnpm build first";
1015
+ }
1016
+ return cachedWidgetJs;
1017
+ }
1018
+ function attachDevtoolsRoutes(httpServer, devtools, options = {}) {
1019
+ const loopbackOnly = options.loopbackOnly ?? true;
1020
+ httpServer.on("request", async (req, res) => {
1021
+ if (req.headers.upgrade?.toLowerCase() === "websocket") return;
1022
+ res.setHeader("Access-Control-Allow-Origin", "*");
1023
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
1024
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1025
+ res.setHeader("x-autotel-devtools", getVersion());
1026
+ res.setHeader("Access-Control-Expose-Headers", "x-autotel-devtools");
1027
+ if (req.method === "OPTIONS") {
1028
+ res.writeHead(204);
1029
+ res.end();
1030
+ return;
1031
+ }
1032
+ const url = req.url || "/";
1033
+ if (req.method === "GET" && url === "/") {
1034
+ res.writeHead(200, {
1035
+ "Content-Type": "text/html; charset=utf-8",
1036
+ "Content-Length": Buffer.byteLength(FULLPAGE_HTML)
1037
+ });
1038
+ res.end(FULLPAGE_HTML);
1039
+ return;
1040
+ }
1041
+ if (req.method === "GET" && url.startsWith("/widget.js")) {
1042
+ const js = getWidgetJs();
1043
+ res.writeHead(200, {
1044
+ "Content-Type": "application/javascript; charset=utf-8",
1045
+ "Content-Length": Buffer.byteLength(js)
1046
+ });
1047
+ res.end(js);
1048
+ return;
1049
+ }
1050
+ if (req.method === "GET" && (url === "/favicon.svg" || url === "/favicon.ico")) {
1051
+ res.writeHead(200, {
1052
+ "Content-Type": "image/svg+xml; charset=utf-8",
1053
+ "Cache-Control": "public, max-age=86400",
1054
+ "Content-Length": Buffer.byteLength(DEVTOOLS_FAVICON_SVG)
1055
+ });
1056
+ res.end(DEVTOOLS_FAVICON_SVG);
1057
+ return;
1058
+ }
1059
+ if (req.method === "GET" && url === "/healthz") {
1060
+ sendJson(res, 200, {
1061
+ ok: true,
1062
+ service: DEVTOOLS_IDENTITY,
1063
+ version: getVersion(),
1064
+ clients: devtools.clientCount
1065
+ });
1066
+ return;
1067
+ }
1068
+ if (req.method === "GET" && url === "/v1/traces") {
1069
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
1070
+ sendJson(res, 403, { error: "Forbidden" });
1071
+ return;
1072
+ }
1073
+ const data = devtools.getCurrentData();
1074
+ sendJson(res, 200, {
1075
+ traces: data.traces,
1076
+ count: data.traces.length
1077
+ });
1078
+ return;
1079
+ }
1080
+ if (req.method === "DELETE" && url === "/v1/traces") {
1081
+ if (!allowSensitiveRequest(req.headers, loopbackOnly)) {
1082
+ sendJson(res, 403, { error: "Forbidden" });
1083
+ return;
1084
+ }
1085
+ devtools.clearData();
1086
+ sendJson(res, 200, { cleared: true });
1087
+ return;
1088
+ }
1089
+ if (req.method === "POST" && url === "/v1/traces") {
1090
+ try {
1091
+ const traces = parseOtlpTraces(await readOtlpPayload(req, "traces"));
1092
+ devtools.addTraces(traces);
1093
+ sendJson(res, 200, { acceptedTraces: traces.length });
1094
+ } catch (e) {
1095
+ sendOtlpError(res, req, e);
1096
+ }
1097
+ return;
1098
+ }
1099
+ if (req.method === "POST" && url === "/v1/logs") {
1100
+ try {
1101
+ const logs = parseOtlpLogs(await readOtlpPayload(req, "logs"));
1102
+ devtools.addLogs(logs);
1103
+ sendJson(res, 200, { acceptedLogs: logs.length });
1104
+ } catch (e) {
1105
+ sendOtlpError(res, req, e);
1106
+ }
1107
+ return;
1108
+ }
1109
+ if (req.method === "POST" && url === "/v1/metrics") {
1110
+ try {
1111
+ sendJson(res, 200, { acceptedMetrics: countOtlpMetrics(await readOtlpPayload(req, "metrics")) });
1112
+ } catch (e) {
1113
+ sendOtlpError(res, req, e);
1114
+ }
1115
+ return;
1116
+ }
1117
+ sendJson(res, 404, { error: "Not found" });
1118
+ });
1119
+ }
1120
+ function createDevtoolsHttpServer(devtools, _options = {}) {
1121
+ const server = createServer();
1122
+ attachDevtoolsRoutes(server, devtools);
1123
+ return server;
1124
+ }
1125
+
1126
+ //#endregion
1127
+ export { appendWithLimit as _, decodeOtlpLogsRequest as a, ErrorAggregator as b, isProtobufContentType as c, DevtoolsServer as d, allowSensitiveRequest as f, appendManyWithLimit as g, originIsLoopback as h, probePortHolder as i, parseOtlpLogs as l, isLoopbackHostname as m, createDevtoolsHttpServer as n, decodeOtlpMetricsRequest as o, hostHeaderIsLoopback as p, DEVTOOLS_IDENTITY as r, decodeOtlpTraceRequest as s, attachDevtoolsRoutes as t, parseOtlpTraces as u, applyTelemetryLimits as v, resolveTelemetryLimits as y };
1128
+ //# sourceMappingURL=http-BkkKa9C_.js.map