api-tracer-kit 1.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 (47) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +466 -0
  4. package/cli/bin/api-tracer.mjs +266 -0
  5. package/cli/config.mjs +224 -0
  6. package/cli/import.mjs +231 -0
  7. package/cli/index.mjs +10 -0
  8. package/cli/presets.mjs +212 -0
  9. package/cli/report.mjs +346 -0
  10. package/cli/scan.mjs +142 -0
  11. package/cli/server.mjs +1576 -0
  12. package/cli/shape.mjs +90 -0
  13. package/cli/test.mjs +342 -0
  14. package/cli/web/app.css +1424 -0
  15. package/cli/web/app.js +2260 -0
  16. package/cli/web/favicon.svg +5 -0
  17. package/cli/web/index.html +159 -0
  18. package/cli/web/logo.svg +7 -0
  19. package/dist/axios.cjs +856 -0
  20. package/dist/axios.cjs.map +1 -0
  21. package/dist/axios.d.cts +27 -0
  22. package/dist/axios.d.ts +27 -0
  23. package/dist/axios.js +853 -0
  24. package/dist/axios.js.map +1 -0
  25. package/dist/index.cjs +872 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +74 -0
  28. package/dist/index.d.ts +74 -0
  29. package/dist/index.js +857 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/react.cjs +896 -0
  32. package/dist/react.cjs.map +1 -0
  33. package/dist/react.d.cts +22 -0
  34. package/dist/react.d.ts +22 -0
  35. package/dist/react.js +893 -0
  36. package/dist/react.js.map +1 -0
  37. package/dist/tracer-BUWdU2lG.d.ts +76 -0
  38. package/dist/tracer-DG2YUqK0.d.cts +76 -0
  39. package/dist/types-Bl2-K6_g.d.cts +111 -0
  40. package/dist/types-Bl2-K6_g.d.ts +111 -0
  41. package/dist/ui.cjs +1162 -0
  42. package/dist/ui.cjs.map +1 -0
  43. package/dist/ui.d.cts +16 -0
  44. package/dist/ui.d.ts +16 -0
  45. package/dist/ui.js +1157 -0
  46. package/dist/ui.js.map +1 -0
  47. package/package.json +92 -0
package/dist/ui.cjs ADDED
@@ -0,0 +1,1162 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var react = require('react');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+
8
+ // src/ui/index.tsx
9
+
10
+ // src/storage/memory.ts
11
+ var MemoryTraceStorage = class {
12
+ constructor(max = 500) {
13
+ this.max = max;
14
+ this.order = [];
15
+ this.byId = /* @__PURE__ */ new Map();
16
+ }
17
+ add(trace) {
18
+ if (!this.byId.has(trace.id)) this.order.push(trace.id);
19
+ this.byId.set(trace.id, trace);
20
+ while (this.order.length > this.max) {
21
+ const oldest = this.order.shift();
22
+ if (oldest !== void 0) this.byId.delete(oldest);
23
+ }
24
+ }
25
+ update(id, patch) {
26
+ const current = this.byId.get(id);
27
+ if (!current) return void 0;
28
+ const next = { ...current, ...patch };
29
+ this.byId.set(id, next);
30
+ return next;
31
+ }
32
+ get(id) {
33
+ return this.byId.get(id);
34
+ }
35
+ getAll() {
36
+ return this.order.map((id) => this.byId.get(id)).filter((t) => Boolean(t));
37
+ }
38
+ remove(id) {
39
+ this.byId.delete(id);
40
+ this.order = this.order.filter((x) => x !== id);
41
+ }
42
+ clear() {
43
+ this.order = [];
44
+ this.byId.clear();
45
+ }
46
+ };
47
+
48
+ // src/core/redact.ts
49
+ var REDACTED = "<redacted>";
50
+ var DEFAULT_REDACT_HEADERS = [
51
+ "authorization",
52
+ "cookie",
53
+ "set-cookie",
54
+ "proxy-authorization",
55
+ "x-api-key",
56
+ "api-key",
57
+ "auth_token",
58
+ "access_token",
59
+ "secret_token",
60
+ "x-auth-token",
61
+ "x-csrf-token"
62
+ ];
63
+ var DEFAULT_REDACT_FIELDS = /(token|password|passwd|secret|api[-_]?key|authorization|credential|otp|ssn)/i;
64
+ function redactValue(value, fields, seen = /* @__PURE__ */ new WeakSet()) {
65
+ if (Array.isArray(value)) return value.map((v) => redactValue(v, fields, seen));
66
+ if (value && typeof value === "object") {
67
+ if (seen.has(value)) return "[circular]";
68
+ const proto = Object.getPrototypeOf(value);
69
+ if (proto !== Object.prototype && proto !== null) return value;
70
+ seen.add(value);
71
+ return Object.fromEntries(
72
+ Object.entries(value).map(([k, v]) => [
73
+ k,
74
+ fields.test(k) ? REDACTED : redactValue(v, fields, seen)
75
+ ])
76
+ );
77
+ }
78
+ return value;
79
+ }
80
+ function redactHeaders(headers, names) {
81
+ const drop = new Set(names.map((n) => n.toLowerCase()));
82
+ return Object.fromEntries(
83
+ Object.entries(headers).map(([k, v]) => [k, drop.has(k.toLowerCase()) ? REDACTED : v])
84
+ );
85
+ }
86
+
87
+ // src/core/url.ts
88
+ function parseQuery(search) {
89
+ const out = {};
90
+ const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
91
+ for (const [rawKey, value] of params) {
92
+ const asArray = /^(.+)\[\]$/.exec(rawKey);
93
+ const asNested = /^([^[]+)\[([^\]]+)\]$/.exec(rawKey);
94
+ if (asArray) {
95
+ const key = asArray[1];
96
+ (out[key] ?? (out[key] = [])).push(value);
97
+ } else if (asNested) {
98
+ const key = asNested[1];
99
+ (out[key] ?? (out[key] = {}))[asNested[2]] = value;
100
+ } else {
101
+ out[rawKey] = value;
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+ function absoluteUrl(url, base) {
107
+ try {
108
+ const origin = base ?? (typeof location !== "undefined" ? location.href : void 0);
109
+ return origin ? new URL(url, origin).toString() : new URL(url).toString();
110
+ } catch {
111
+ return url;
112
+ }
113
+ }
114
+ function pathOf(url) {
115
+ try {
116
+ const u = new URL(url);
117
+ return `${u.origin}${u.pathname}`;
118
+ } catch {
119
+ return url.split("?")[0] ?? url;
120
+ }
121
+ }
122
+ function searchOf(url) {
123
+ try {
124
+ return new URL(url).search;
125
+ } catch {
126
+ const at = url.indexOf("?");
127
+ return at === -1 ? "" : url.slice(at);
128
+ }
129
+ }
130
+ function appendParams(url, params, prefix = "") {
131
+ for (const [rawKey, v] of Object.entries(params)) {
132
+ const key = prefix ? `${prefix}[${rawKey}]` : rawKey;
133
+ if (v === void 0 || v === null || v === "") continue;
134
+ if (Array.isArray(v)) {
135
+ for (const item of v) {
136
+ if (item !== null && typeof item === "object") appendParams(url, item, `${key}[]`);
137
+ else url.searchParams.append(`${key}[]`, String(item));
138
+ }
139
+ } else if (typeof v === "object") {
140
+ appendParams(url, v, key);
141
+ } else {
142
+ url.searchParams.append(key, String(v));
143
+ }
144
+ }
145
+ }
146
+ function matches(url, patterns) {
147
+ return patterns.some((p) => typeof p === "string" ? url.includes(p) : p.test(url));
148
+ }
149
+
150
+ // src/core/envelope.ts
151
+ var DEFAULT_ENVELOPE = {
152
+ codeFields: ["status", "code"],
153
+ okField: "success",
154
+ failFrom: 400
155
+ };
156
+ function readEnvelope(bodyText, options) {
157
+ if (options === false || !bodyText) return {};
158
+ const cfg = { ...DEFAULT_ENVELOPE, ...options ?? {} };
159
+ let parsed;
160
+ try {
161
+ parsed = JSON.parse(bodyText);
162
+ } catch {
163
+ return {};
164
+ }
165
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
166
+ const body = parsed;
167
+ let code;
168
+ for (const field of cfg.codeFields) {
169
+ if (typeof body[field] === "number") {
170
+ code = body[field];
171
+ break;
172
+ }
173
+ }
174
+ const flag = body[cfg.okField];
175
+ if (code === void 0 && typeof flag !== "boolean") return {};
176
+ return {
177
+ envelopeCode: code,
178
+ envelopeOk: !(code !== void 0 && code >= cfg.failFrom) && flag !== false
179
+ };
180
+ }
181
+
182
+ // src/core/env.ts
183
+ var hasFetch = () => typeof globalThis.fetch === "function";
184
+ var hasXhr = () => typeof globalThis.XMLHttpRequest === "function";
185
+ var hasFormData = () => typeof globalThis.FormData !== "undefined";
186
+ var hasBlob = () => typeof globalThis.Blob !== "undefined";
187
+ var now = () => typeof globalThis.performance?.now === "function" ? globalThis.performance.now() : Date.now();
188
+ var counter = 0;
189
+ function traceId() {
190
+ const c = globalThis.crypto;
191
+ if (typeof c?.randomUUID === "function") {
192
+ try {
193
+ return c.randomUUID();
194
+ } catch {
195
+ }
196
+ }
197
+ counter += 1;
198
+ return `t-${Date.now().toString(36)}-${counter.toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
199
+ }
200
+
201
+ // src/core/global.ts
202
+ var REGISTRY = /* @__PURE__ */ Symbol.for("api-tracer-kit.registry");
203
+ var registry = () => {
204
+ const host = globalThis;
205
+ return host[REGISTRY] ?? (host[REGISTRY] = {});
206
+ };
207
+ function shared(key, create) {
208
+ const store = registry();
209
+ if (!(key in store)) store[key] = create();
210
+ return store[key];
211
+ }
212
+ function sharedCounter(key) {
213
+ return shared(key, () => ({ value: 0 }));
214
+ }
215
+
216
+ // src/core/body.ts
217
+ function fromFormData(fd) {
218
+ const out = {};
219
+ for (const [key, value] of fd.entries()) {
220
+ const isFile = typeof File !== "undefined" && value instanceof File ? true : hasBlob() && value instanceof Blob;
221
+ const item = isFile ? `<file: ${value.name || "unnamed"}, ${value.size} bytes>` : value;
222
+ if (key in out) out[key] = [].concat(out[key], item);
223
+ else out[key] = item;
224
+ }
225
+ return out;
226
+ }
227
+ var parseQueryString = (text) => Object.fromEntries(new URLSearchParams(text).entries());
228
+ function readRequestBody(value, maxBytes) {
229
+ if (value === void 0 || value === null) return { bodyType: "none" };
230
+ if (hasFormData() && value instanceof FormData) {
231
+ return { bodyType: "formdata", body: fromFormData(value) };
232
+ }
233
+ if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) {
234
+ return { bodyType: "urlencoded", body: Object.fromEntries(value.entries()) };
235
+ }
236
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
237
+ return { bodyType: "binary", omitted: "stream" };
238
+ }
239
+ if (hasBlob() && value instanceof Blob) {
240
+ return { bodyType: "binary", body: `<blob: ${value.size} bytes, ${value.type || "unknown"}>`, omitted: "binary" };
241
+ }
242
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
243
+ return { bodyType: "binary", body: `<binary: ${value.byteLength} bytes>`, omitted: "binary" };
244
+ }
245
+ if (typeof value === "string") {
246
+ if (value.length > maxBytes) return { bodyType: "text", omitted: "too-large" };
247
+ const trimmed = value.trim();
248
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
249
+ try {
250
+ return { bodyType: "json", body: JSON.parse(value) };
251
+ } catch {
252
+ return { bodyType: "text", body: value };
253
+ }
254
+ }
255
+ if (/^[^=&\s]+=[^&]*(&|$)/.test(trimmed)) {
256
+ return { bodyType: "urlencoded", body: parseQueryString(trimmed) };
257
+ }
258
+ return { bodyType: "text", body: value };
259
+ }
260
+ if (typeof value === "object") {
261
+ try {
262
+ const text = JSON.stringify(value);
263
+ if (text && text.length > maxBytes) return { bodyType: "json", omitted: "too-large" };
264
+ return { bodyType: "json", body: value };
265
+ } catch {
266
+ return { bodyType: "json", omitted: "unreadable" };
267
+ }
268
+ }
269
+ return { bodyType: "text", body: String(value) };
270
+ }
271
+ var TEXTUAL = /^(application\/(json|.*\+json|xml|x-www-form-urlencoded|javascript)|text\/)/i;
272
+ var STREAMING = /^text\/event-stream/i;
273
+ async function readResponseBody(response, maxBytes) {
274
+ const type = response.headers.get("content-type") ?? "";
275
+ if (STREAMING.test(type)) return { omitted: "stream" };
276
+ if (type && !TEXTUAL.test(type)) {
277
+ const declared2 = Number(response.headers.get("content-length"));
278
+ return { omitted: "binary", size: Number.isFinite(declared2) ? declared2 : void 0 };
279
+ }
280
+ const declared = Number(response.headers.get("content-length"));
281
+ if (Number.isFinite(declared) && declared > maxBytes) {
282
+ return { omitted: "too-large", size: declared };
283
+ }
284
+ let clone;
285
+ try {
286
+ clone = response.clone();
287
+ } catch {
288
+ return { omitted: "unreadable" };
289
+ }
290
+ try {
291
+ const text = await clone.text();
292
+ if (text.length > maxBytes) return { omitted: "too-large", size: text.length };
293
+ return { body: text, size: text.length };
294
+ } catch {
295
+ return { omitted: "unreadable" };
296
+ }
297
+ }
298
+ function headersToObject(input) {
299
+ if (!input) return {};
300
+ const out = {};
301
+ if (typeof Headers !== "undefined" && input instanceof Headers) {
302
+ input.forEach((value, key) => {
303
+ out[key] = value;
304
+ });
305
+ return out;
306
+ }
307
+ if (Array.isArray(input)) {
308
+ for (const pair of input) if (pair?.[0]) out[pair[0]] = String(pair[1]);
309
+ return out;
310
+ }
311
+ if (typeof input === "object") {
312
+ for (const [k, v] of Object.entries(input)) {
313
+ if (v === void 0 || v === null || typeof v === "object") continue;
314
+ out[k] = String(v);
315
+ }
316
+ }
317
+ return out;
318
+ }
319
+
320
+ // src/adapters/fetch.ts
321
+ var PATCHED = /* @__PURE__ */ Symbol.for("api-tracer-kit.fetch");
322
+ function describe(input, init, maxBytes) {
323
+ const isRequest = typeof Request !== "undefined" && input instanceof Request;
324
+ const raw = isRequest ? input.url : String(input);
325
+ const url = absoluteUrl(raw);
326
+ const method = (init?.method ?? (isRequest ? input.method : "GET")).toUpperCase();
327
+ const headers = headersToObject(init?.headers ?? (isRequest ? input.headers : void 0));
328
+ const body = init?.body !== void 0 ? readRequestBody(init.body, maxBytes) : isRequest && input.body ? { bodyType: "binary", omitted: "stream" } : { bodyType: "none" };
329
+ return {
330
+ url,
331
+ path: pathOf(url),
332
+ method,
333
+ headers,
334
+ params: parseQuery(searchOf(url)),
335
+ body: "body" in body ? body.body : void 0,
336
+ bodyType: body.bodyType,
337
+ bodyOmitted: body.omitted
338
+ };
339
+ }
340
+ function installFetch(tracer) {
341
+ const original = globalThis.fetch;
342
+ if (original[PATCHED]) return () => {
343
+ };
344
+ const patched = function fetch(input, init) {
345
+ let trace;
346
+ try {
347
+ const request = describe(input, init, tracer.options.maxBodyBytes);
348
+ if (tracer.shouldTrace(request.url)) trace = tracer.start("fetch", request);
349
+ } catch {
350
+ }
351
+ const call = original.call(this ?? globalThis, input, init);
352
+ if (!trace) return call;
353
+ const id = trace.id;
354
+ return call.then(
355
+ (response) => {
356
+ void (async () => {
357
+ const read = await readResponseBody(response, tracer.options.maxBodyBytes);
358
+ tracer.complete(id, {
359
+ status: response.ok ? "success" : "error",
360
+ response: {
361
+ status: response.status,
362
+ statusText: response.statusText,
363
+ headers: headersToObject(response.headers),
364
+ body: read.body,
365
+ bodyOmitted: read.omitted,
366
+ size: read.size
367
+ }
368
+ });
369
+ })().catch(() => {
370
+ tracer.complete(id, { status: response.ok ? "success" : "error" });
371
+ });
372
+ return response;
373
+ },
374
+ (error) => {
375
+ const name = error?.name;
376
+ tracer.complete(id, {
377
+ // an aborted request is not a broken endpoint
378
+ status: name === "AbortError" ? "cancelled" : "network-error",
379
+ error: {
380
+ message: String(error?.message ?? error),
381
+ name,
382
+ stack: error?.stack
383
+ }
384
+ });
385
+ throw error;
386
+ }
387
+ );
388
+ };
389
+ patched[PATCHED] = original;
390
+ globalThis.fetch = patched;
391
+ return () => {
392
+ if (globalThis.fetch === patched) globalThis.fetch = original;
393
+ };
394
+ }
395
+
396
+ // src/adapters/suppress.ts
397
+ var depth = () => sharedCounter("xhr-suppression");
398
+ var suppressXhr = (fn) => {
399
+ const d = depth();
400
+ d.value += 1;
401
+ try {
402
+ return fn();
403
+ } finally {
404
+ d.value -= 1;
405
+ }
406
+ };
407
+ var xhrSuppressed = () => depth().value > 0;
408
+
409
+ // src/adapters/xhr.ts
410
+ var STATE = /* @__PURE__ */ Symbol.for("api-tracer-kit.xhr.state");
411
+ var PATCHED2 = /* @__PURE__ */ Symbol.for("api-tracer-kit.xhr");
412
+ function parseResponseHeaders(raw) {
413
+ const out = {};
414
+ for (const line of raw.split(/\r?\n/)) {
415
+ const at = line.indexOf(":");
416
+ if (at === -1) continue;
417
+ out[line.slice(0, at).trim().toLowerCase()] = line.slice(at + 1).trim();
418
+ }
419
+ return out;
420
+ }
421
+ function readBody(xhr, maxBytes) {
422
+ if (xhr.responseType && xhr.responseType !== "text" && xhr.responseType !== "json") {
423
+ return { omitted: "binary" };
424
+ }
425
+ try {
426
+ const text = xhr.responseType === "json" ? JSON.stringify(xhr.response) : xhr.responseText;
427
+ if (typeof text !== "string") return { omitted: "unreadable" };
428
+ if (text.length > maxBytes) return { omitted: "too-large", size: text.length };
429
+ return { body: text, size: text.length };
430
+ } catch {
431
+ return { omitted: "unreadable" };
432
+ }
433
+ }
434
+ function installXhr(tracer) {
435
+ const proto = globalThis.XMLHttpRequest.prototype;
436
+ if (proto[PATCHED2]) return () => {
437
+ };
438
+ const open = proto.open;
439
+ const send = proto.send;
440
+ const setRequestHeader = proto.setRequestHeader;
441
+ proto.open = function(method, url, ...rest) {
442
+ try {
443
+ this[STATE] = { method: String(method).toUpperCase(), url: absoluteUrl(String(url)), headers: {} };
444
+ } catch {
445
+ }
446
+ return open.call(this, method, url, ...rest);
447
+ };
448
+ proto.setRequestHeader = function(name, value) {
449
+ const state = this[STATE];
450
+ if (state) state.headers[name] = value;
451
+ return setRequestHeader.call(this, name, value);
452
+ };
453
+ proto.send = function(body) {
454
+ const state = this[STATE];
455
+ try {
456
+ if (state && !xhrSuppressed() && tracer.shouldTrace(state.url)) {
457
+ const read = readRequestBody(body ?? void 0, tracer.options.maxBodyBytes);
458
+ const trace = tracer.start("xhr", {
459
+ url: state.url,
460
+ path: pathOf(state.url),
461
+ method: state.method,
462
+ headers: state.headers,
463
+ params: parseQuery(searchOf(state.url)),
464
+ body: read.body,
465
+ bodyType: read.bodyType,
466
+ bodyOmitted: read.omitted
467
+ });
468
+ state.traceId = trace.id;
469
+ const id = trace.id;
470
+ this.addEventListener("load", () => {
471
+ const read2 = readBody(this, tracer.options.maxBodyBytes);
472
+ tracer.complete(id, {
473
+ status: this.status >= 200 && this.status < 300 ? "success" : "error",
474
+ response: {
475
+ status: this.status,
476
+ statusText: this.statusText,
477
+ headers: parseResponseHeaders(this.getAllResponseHeaders?.() ?? ""),
478
+ ...read2
479
+ }
480
+ });
481
+ });
482
+ this.addEventListener("error", () => {
483
+ tracer.complete(id, { status: "network-error", error: { message: "network error" } });
484
+ });
485
+ this.addEventListener("timeout", () => {
486
+ tracer.complete(id, { status: "network-error", error: { message: "timeout", name: "TimeoutError" } });
487
+ });
488
+ this.addEventListener("abort", () => {
489
+ tracer.complete(id, { status: "cancelled", error: { message: "aborted", name: "AbortError" } });
490
+ });
491
+ }
492
+ } catch {
493
+ }
494
+ return send.call(this, body ?? null);
495
+ };
496
+ proto[PATCHED2] = true;
497
+ return () => {
498
+ proto.open = open;
499
+ proto.send = send;
500
+ proto.setRequestHeader = setRequestHeader;
501
+ delete proto[PATCHED2];
502
+ };
503
+ }
504
+
505
+ // src/adapters/axios.ts
506
+ var ATTACHED = /* @__PURE__ */ Symbol.for("api-tracer-kit.axios");
507
+ function fullUrl(config) {
508
+ const base = config.baseURL ?? "";
509
+ const path = config.url ?? "";
510
+ const joined = /^https?:\/\//i.test(path) ? path : `${base.replace(/\/+$/, "")}${path.startsWith("/") || !base ? "" : "/"}${path}`;
511
+ const absolute = absoluteUrl(joined);
512
+ if (!config.params || !Object.keys(config.params).length) return absolute;
513
+ try {
514
+ const url = new URL(absolute);
515
+ appendParams(url, config.params);
516
+ return url.toString();
517
+ } catch {
518
+ return absolute;
519
+ }
520
+ }
521
+ function responseBody(data, maxBytes) {
522
+ if (data === void 0 || data === null) return {};
523
+ if (typeof data === "string") {
524
+ return data.length > maxBytes ? { bodyOmitted: "too-large", size: data.length } : { body: data, size: data.length };
525
+ }
526
+ if (typeof Blob !== "undefined" && data instanceof Blob) return { bodyOmitted: "binary", size: data.size };
527
+ if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return { bodyOmitted: "binary", size: data.byteLength };
528
+ try {
529
+ const text = JSON.stringify(data);
530
+ if (typeof text !== "string") return { bodyOmitted: "unreadable" };
531
+ return text.length > maxBytes ? { bodyOmitted: "too-large", size: text.length } : { body: text, size: text.length };
532
+ } catch {
533
+ return { bodyOmitted: "unreadable" };
534
+ }
535
+ }
536
+ var ours = /* @__PURE__ */ new WeakMap();
537
+ var wrapped = [];
538
+ function wrapInstance(instance, tracer) {
539
+ const marked = instance;
540
+ if (!instance || marked[ATTACHED]) return instance;
541
+ marked[ATTACHED] = true;
542
+ const inherited = instance.defaults?.adapter;
543
+ if (typeof inherited === "function" && ours.has(inherited)) {
544
+ wrapped.push({ instance, current: ours.get(inherited) });
545
+ return instance;
546
+ }
547
+ const defaults = instance.defaults ?? (instance.defaults = {});
548
+ let inner = defaults.adapter;
549
+ const adapter = (config) => {
550
+ const run = () => suppressXhr(
551
+ () => typeof inner === "function" ? inner(config) : (
552
+ // fall back to whatever axios would have used, by passing it through
553
+ instance.request({
554
+ ...config,
555
+ adapter: inner
556
+ })
557
+ )
558
+ );
559
+ let id;
560
+ try {
561
+ const url = fullUrl(config);
562
+ if (tracer.shouldTrace(url)) {
563
+ const read = readRequestBody(config.data, tracer.options.maxBodyBytes);
564
+ id = tracer.start("axios", {
565
+ url,
566
+ path: pathOf(url),
567
+ method: (config.method ?? "get").toUpperCase(),
568
+ headers: headersToObject(config.headers),
569
+ params: config.params ? { ...config.params } : parseQuery(searchOf(url)),
570
+ body: read.body,
571
+ bodyType: read.bodyType,
572
+ bodyOmitted: read.omitted
573
+ }).id;
574
+ }
575
+ } catch {
576
+ }
577
+ const promise = run();
578
+ if (!id) return promise;
579
+ const traceId2 = id;
580
+ return promise.then(
581
+ (response) => {
582
+ tracer.complete(traceId2, {
583
+ status: response.status >= 200 && response.status < 300 ? "success" : "error",
584
+ response: {
585
+ status: response.status,
586
+ statusText: response.statusText,
587
+ headers: headersToObject(response.headers),
588
+ ...responseBody(response.data, tracer.options.maxBodyBytes)
589
+ }
590
+ });
591
+ return response;
592
+ },
593
+ (error) => {
594
+ const err = error;
595
+ if (err?.response) {
596
+ tracer.complete(traceId2, {
597
+ status: "error",
598
+ response: {
599
+ status: err.response.status,
600
+ statusText: err.response.statusText,
601
+ headers: headersToObject(err.response.headers),
602
+ ...responseBody(err.response.data, tracer.options.maxBodyBytes)
603
+ },
604
+ error: { message: String(err.message ?? error), name: err.name, stack: err.stack }
605
+ });
606
+ } else {
607
+ const cancelled = err?.code === "ERR_CANCELED" || err?.name === "CanceledError" || err?.name === "AbortError";
608
+ tracer.complete(traceId2, {
609
+ status: cancelled ? "cancelled" : "network-error",
610
+ error: { message: String(err?.message ?? error), name: err?.name, stack: err?.stack }
611
+ });
612
+ }
613
+ throw error;
614
+ }
615
+ );
616
+ };
617
+ wrapped.push({ instance, current: () => inner });
618
+ ours.set(adapter, () => inner);
619
+ Object.defineProperty(defaults, "adapter", {
620
+ configurable: true,
621
+ enumerable: true,
622
+ get: () => adapter,
623
+ set: (next) => {
624
+ inner = next;
625
+ }
626
+ });
627
+ return instance;
628
+ }
629
+ function installAxios(tracer, axios) {
630
+ const instance = axios;
631
+ if (!instance) return () => {
632
+ };
633
+ wrapInstance(instance, tracer);
634
+ const create = instance.create?.bind(instance);
635
+ if (create) {
636
+ instance.create = (config) => wrapInstance(create(config), tracer);
637
+ }
638
+ return () => {
639
+ if (create) instance.create = create;
640
+ for (const entry of wrapped.splice(0)) {
641
+ const marked = entry.instance;
642
+ delete marked[ATTACHED];
643
+ if (!entry.instance.defaults) continue;
644
+ Object.defineProperty(entry.instance.defaults, "adapter", {
645
+ configurable: true,
646
+ enumerable: true,
647
+ writable: true,
648
+ value: entry.current()
649
+ });
650
+ }
651
+ };
652
+ }
653
+
654
+ // src/transport/report.ts
655
+ function toRecordPayload(trace) {
656
+ return {
657
+ method: trace.request.method,
658
+ url: trace.request.url,
659
+ params: trace.request.params,
660
+ data: trace.request.body ?? {},
661
+ bodyType: trace.request.bodyType === "none" ? "json" : trace.request.bodyType,
662
+ status: trace.response?.status ?? null,
663
+ ms: trace.timing.duration,
664
+ body: trace.response?.body,
665
+ error: trace.error?.message
666
+ };
667
+ }
668
+ function createReporter(baseUrl) {
669
+ const endpoint = `${baseUrl.replace(/\/+$/, "")}/api/record`;
670
+ let failures = 0;
671
+ return (trace) => {
672
+ if (!hasFetch() || trace.status === "pending" || failures > 5) return;
673
+ try {
674
+ void globalThis.fetch(endpoint, {
675
+ method: "POST",
676
+ headers: { "Content-Type": "application/json" },
677
+ body: JSON.stringify(toRecordPayload(trace)),
678
+ // survives the page being closed mid-flight
679
+ keepalive: true,
680
+ mode: "cors"
681
+ }).then(
682
+ () => {
683
+ failures = 0;
684
+ },
685
+ () => {
686
+ failures += 1;
687
+ }
688
+ );
689
+ } catch {
690
+ failures += 1;
691
+ }
692
+ };
693
+ }
694
+
695
+ // src/core/tracer.ts
696
+ var DEFAULT_REPORT_URL = "http://localhost:4400";
697
+ var ApiTracer = class {
698
+ constructor() {
699
+ this.storage = new MemoryTraceStorage();
700
+ this.listeners = /* @__PURE__ */ new Set();
701
+ this.uninstallers = [];
702
+ this.started = false;
703
+ this.options = {
704
+ include: [],
705
+ exclude: [],
706
+ redactHeaderNames: DEFAULT_REDACT_HEADERS,
707
+ redactFields: DEFAULT_REDACT_FIELDS,
708
+ maxBodyBytes: 2e5,
709
+ maxTraces: 500,
710
+ envelope: void 0,
711
+ debug: false
712
+ };
713
+ }
714
+ /**
715
+ * Installs the interceptors. Calling it twice is a no-op rather than a second
716
+ * set of hooks — a double-init in React StrictMode or a hot reload must not
717
+ * double-count every request.
718
+ */
719
+ init(options = {}) {
720
+ if (this.started) {
721
+ if (options.onTrace) this.subscribe(options.onTrace);
722
+ return this;
723
+ }
724
+ this.options = {
725
+ include: options.include ?? [],
726
+ exclude: options.exclude ?? [],
727
+ redactHeaderNames: options.redactHeaders ?? DEFAULT_REDACT_HEADERS,
728
+ redactFields: options.redactFields ?? DEFAULT_REDACT_FIELDS,
729
+ maxBodyBytes: options.maxBodyBytes ?? 2e5,
730
+ maxTraces: options.maxTraces ?? 500,
731
+ envelope: options.envelope,
732
+ debug: options.debug ?? false
733
+ };
734
+ this.storage = options.storage ?? new MemoryTraceStorage(this.options.maxTraces);
735
+ if (options.onTrace) this.subscribe(options.onTrace);
736
+ if (options.reportTo) {
737
+ const url = options.reportTo === true ? DEFAULT_REPORT_URL : options.reportTo;
738
+ this.reporter = createReporter(url);
739
+ this.options.exclude = [...this.options.exclude, `${url.replace(/\/+$/, "")}/api/record`];
740
+ }
741
+ const wanted = options.transports ?? ["fetch", "xhr", "axios"];
742
+ if (wanted.includes("fetch") && hasFetch()) this.uninstallers.push(installFetch(this));
743
+ if (wanted.includes("xhr") && hasXhr()) this.uninstallers.push(installXhr(this));
744
+ this.started = true;
745
+ if (this.options.debug) {
746
+ console.info("[api-tracer] tracing", wanted.join(", "));
747
+ }
748
+ return this;
749
+ }
750
+ /**
751
+ * Traces an axios instance and everything it creates. Needed because an axios
752
+ * instance is an object the tracer has no way to reach on its own — there is
753
+ * no global to patch. One call covers the default export and every instance
754
+ * made from it afterwards.
755
+ */
756
+ useAxios(axios) {
757
+ if (!this.started) this.init();
758
+ this.uninstallers.push(installAxios(this, axios));
759
+ return this;
760
+ }
761
+ /** puts every patched global back, so the app behaves exactly as before */
762
+ destroy() {
763
+ for (const off of this.uninstallers.splice(0)) {
764
+ try {
765
+ off();
766
+ } catch {
767
+ }
768
+ }
769
+ this.listeners.clear();
770
+ this.reporter = void 0;
771
+ this.started = false;
772
+ }
773
+ get isActive() {
774
+ return this.started;
775
+ }
776
+ /* ------------------------------------------------------------ read side */
777
+ getTraces() {
778
+ return this.storage.getAll();
779
+ }
780
+ getTrace(id) {
781
+ return this.storage.get(id);
782
+ }
783
+ clear() {
784
+ this.storage.clear();
785
+ }
786
+ remove(id) {
787
+ this.storage.remove(id);
788
+ }
789
+ /** returns the unsubscribe function, so a component can clean up after itself */
790
+ subscribe(listener) {
791
+ this.listeners.add(listener);
792
+ return () => {
793
+ this.listeners.delete(listener);
794
+ };
795
+ }
796
+ /* ----------------------------------------------------------- write side */
797
+ /** whether this URL is one we were asked to watch */
798
+ shouldTrace(url) {
799
+ if (this.options.exclude.length && matches(url, this.options.exclude)) return false;
800
+ if (this.options.include.length) return matches(url, this.options.include);
801
+ return true;
802
+ }
803
+ /** opens a trace; the adapter completes it later with the same id */
804
+ start(transport, request) {
805
+ const trace = {
806
+ id: traceId(),
807
+ transport,
808
+ status: "pending",
809
+ request: {
810
+ ...request,
811
+ headers: redactHeaders(request.headers, this.options.redactHeaderNames),
812
+ params: redactValue(request.params, this.options.redactFields),
813
+ body: request.body === void 0 ? void 0 : redactValue(request.body, this.options.redactFields)
814
+ },
815
+ timing: { startedAt: now() }
816
+ };
817
+ this.storage.add(trace);
818
+ return trace;
819
+ }
820
+ complete(id, outcome) {
821
+ const current = this.storage.get(id);
822
+ if (!current) return;
823
+ const completedAt = now();
824
+ const response = outcome.response ? {
825
+ ...outcome.response,
826
+ headers: redactHeaders(outcome.response.headers, this.options.redactHeaderNames)
827
+ } : void 0;
828
+ const envelope = response ? readEnvelope(response.body, this.options.envelope) : {};
829
+ const next = this.storage.update(id, {
830
+ status: outcome.status,
831
+ response,
832
+ error: outcome.error,
833
+ timing: {
834
+ startedAt: current.timing.startedAt,
835
+ completedAt,
836
+ duration: Math.round(completedAt - current.timing.startedAt)
837
+ },
838
+ ...envelope
839
+ });
840
+ if (next) this.emit(next);
841
+ }
842
+ emit(trace) {
843
+ for (const listener of this.listeners) {
844
+ try {
845
+ listener(trace);
846
+ } catch (e) {
847
+ if (this.options.debug) console.warn("[api-tracer] listener threw", e);
848
+ }
849
+ }
850
+ this.reporter?.(trace);
851
+ }
852
+ };
853
+ var apiTracer = shared("tracer", () => new ApiTracer());
854
+
855
+ // src/react.ts
856
+ function useApiTraces() {
857
+ const [traces, setTraces] = react.useState(() => apiTracer.getTraces());
858
+ const frame = react.useRef(null);
859
+ react.useEffect(() => {
860
+ const flush = () => {
861
+ frame.current = null;
862
+ setTraces(apiTracer.getTraces());
863
+ };
864
+ const schedule = () => {
865
+ if (frame.current !== null) return;
866
+ frame.current = typeof requestAnimationFrame === "function" ? requestAnimationFrame(flush) : setTimeout(flush, 16);
867
+ };
868
+ const off = apiTracer.subscribe(schedule);
869
+ schedule();
870
+ return () => {
871
+ off();
872
+ if (frame.current !== null && typeof cancelAnimationFrame === "function") {
873
+ cancelAnimationFrame(frame.current);
874
+ }
875
+ frame.current = null;
876
+ };
877
+ }, []);
878
+ const clear = react.useCallback(() => {
879
+ apiTracer.clear();
880
+ setTraces([]);
881
+ }, []);
882
+ const remove = react.useCallback((id) => {
883
+ apiTracer.remove(id);
884
+ setTraces(apiTracer.getTraces());
885
+ }, []);
886
+ return { traces, clear, remove };
887
+ }
888
+ var COLOURS = {
889
+ pending: "#9aa4b2",
890
+ success: "#2f9e44",
891
+ error: "#e03131",
892
+ "network-error": "#e8590c",
893
+ cancelled: "#868e96"
894
+ };
895
+ var S = {
896
+ panel: {
897
+ position: "fixed",
898
+ zIndex: 2147483e3,
899
+ width: "min(720px, calc(100vw - 32px))",
900
+ maxHeight: "min(70vh, 640px)",
901
+ display: "flex",
902
+ flexDirection: "column",
903
+ background: "#14171c",
904
+ color: "#e7ebf0",
905
+ border: "1px solid #2b313a",
906
+ borderRadius: 10,
907
+ boxShadow: "0 10px 40px rgba(0,0,0,.45)",
908
+ font: "12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace",
909
+ overflow: "hidden"
910
+ },
911
+ bar: {
912
+ display: "flex",
913
+ gap: 8,
914
+ alignItems: "center",
915
+ padding: "8px 10px",
916
+ borderBottom: "1px solid #2b313a",
917
+ background: "#191d24",
918
+ flexWrap: "wrap"
919
+ },
920
+ input: {
921
+ flex: "1 1 140px",
922
+ minWidth: 100,
923
+ background: "#0f1216",
924
+ border: "1px solid #2b313a",
925
+ borderRadius: 6,
926
+ color: "#e7ebf0",
927
+ padding: "4px 8px",
928
+ font: "inherit"
929
+ },
930
+ select: {
931
+ background: "#0f1216",
932
+ border: "1px solid #2b313a",
933
+ borderRadius: 6,
934
+ color: "#e7ebf0",
935
+ padding: "4px 6px",
936
+ font: "inherit"
937
+ },
938
+ button: {
939
+ background: "#232932",
940
+ border: "1px solid #2b313a",
941
+ borderRadius: 6,
942
+ color: "#e7ebf0",
943
+ padding: "4px 10px",
944
+ cursor: "pointer",
945
+ font: "inherit"
946
+ },
947
+ row: {
948
+ display: "grid",
949
+ gridTemplateColumns: "58px 1fr 52px 62px",
950
+ gap: 8,
951
+ alignItems: "center",
952
+ padding: "5px 10px",
953
+ borderBottom: "1px solid #20252c",
954
+ cursor: "pointer",
955
+ textAlign: "left",
956
+ width: "100%",
957
+ background: "transparent",
958
+ color: "inherit",
959
+ border: 0,
960
+ borderBottomWidth: 1,
961
+ borderBottomStyle: "solid",
962
+ font: "inherit"
963
+ },
964
+ key: { color: "#7f8896" },
965
+ pre: {
966
+ margin: 0,
967
+ padding: 8,
968
+ background: "#0f1216",
969
+ borderRadius: 6,
970
+ maxHeight: 220,
971
+ overflow: "auto",
972
+ whiteSpace: "pre-wrap",
973
+ wordBreak: "break-word"
974
+ }
975
+ };
976
+ var corner = (position) => ({
977
+ "bottom-right": { bottom: 16, right: 16 },
978
+ "bottom-left": { bottom: 16, left: 16 },
979
+ "top-right": { top: 16, right: 16 },
980
+ "top-left": { top: 16, left: 16 }
981
+ })[position];
982
+ var pretty = (value) => {
983
+ if (value === void 0) return "";
984
+ if (typeof value === "string") {
985
+ try {
986
+ return JSON.stringify(JSON.parse(value), null, 2);
987
+ } catch {
988
+ return value;
989
+ }
990
+ }
991
+ try {
992
+ return JSON.stringify(value, null, 2);
993
+ } catch {
994
+ return String(value);
995
+ }
996
+ };
997
+ var shortPath = (trace) => {
998
+ try {
999
+ const url = new URL(trace.request.url);
1000
+ return `${url.pathname}${url.search}`;
1001
+ } catch {
1002
+ return trace.request.url;
1003
+ }
1004
+ };
1005
+ function Section({ title, children }) {
1006
+ if (children === null || children === void 0 || children === "") return null;
1007
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 10 }, children: [
1008
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...S.key, marginBottom: 4 }, children: title }),
1009
+ children
1010
+ ] });
1011
+ }
1012
+ function Detail({ trace }) {
1013
+ const headers = (h) => Object.keys(h).length ? /* @__PURE__ */ jsxRuntime.jsx("pre", { style: S.pre, children: Object.entries(h).map(([k, v]) => `${k}: ${v}`).join("\n") }) : null;
1014
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: 10, borderBottom: "1px solid #20252c", background: "#11141a" }, children: [
1015
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginBottom: 8, wordBreak: "break-all" }, children: [
1016
+ /* @__PURE__ */ jsxRuntime.jsx("b", { children: trace.request.method }),
1017
+ " ",
1018
+ trace.request.url
1019
+ ] }),
1020
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...S.key, marginBottom: 10 }, children: [
1021
+ trace.transport,
1022
+ " \xB7 ",
1023
+ trace.status,
1024
+ trace.response ? ` \xB7 HTTP ${trace.response.status}` : "",
1025
+ trace.envelopeCode !== void 0 ? ` \xB7 body code ${trace.envelopeCode}` : "",
1026
+ trace.timing.duration !== void 0 ? ` \xB7 ${trace.timing.duration}ms` : "",
1027
+ trace.response?.size !== void 0 ? ` \xB7 ${trace.response.size} bytes` : ""
1028
+ ] }),
1029
+ trace.error ? /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "error", children: /* @__PURE__ */ jsxRuntime.jsxs("pre", { style: { ...S.pre, color: COLOURS["network-error"] }, children: [
1030
+ trace.error.name ? `${trace.error.name}: ` : "",
1031
+ trace.error.message
1032
+ ] }) }) : null,
1033
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "query params", children: Object.keys(trace.request.params).length ? /* @__PURE__ */ jsxRuntime.jsx("pre", { style: S.pre, children: pretty(trace.request.params) }) : null }),
1034
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "request headers", children: headers(trace.request.headers) }),
1035
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: `request body (${trace.request.bodyType})`, children: trace.request.bodyOmitted ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.key, children: [
1036
+ "omitted: ",
1037
+ trace.request.bodyOmitted
1038
+ ] }) : trace.request.body !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("pre", { style: S.pre, children: pretty(trace.request.body) }) : null }),
1039
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "response headers", children: trace.response ? headers(trace.response.headers) : null }),
1040
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "response body", children: trace.response?.bodyOmitted ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.key, children: [
1041
+ "omitted: ",
1042
+ trace.response.bodyOmitted
1043
+ ] }) : trace.response?.body !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("pre", { style: S.pre, children: pretty(trace.response.body) }) : null })
1044
+ ] });
1045
+ }
1046
+ function ApiTracerPanel({
1047
+ collapsed = true,
1048
+ position = "bottom-right",
1049
+ enabled = true,
1050
+ style
1051
+ }) {
1052
+ const { traces, clear } = useApiTraces();
1053
+ const [open, setOpen] = react.useState(!collapsed);
1054
+ const [query, setQuery] = react.useState("");
1055
+ const [method, setMethod] = react.useState("");
1056
+ const [status, setStatus] = react.useState("");
1057
+ const [sort, setSort] = react.useState("time");
1058
+ const [expanded, setExpanded] = react.useState(null);
1059
+ const shown = react.useMemo(() => {
1060
+ const q = query.trim().toLowerCase();
1061
+ const rows = traces.filter((t) => {
1062
+ if (method && t.request.method !== method) return false;
1063
+ if (status === "failed" && t.status === "success") return false;
1064
+ if (status === "ok" && t.status !== "success") return false;
1065
+ if (status && status !== "failed" && status !== "ok" && t.status !== status) return false;
1066
+ if (!q) return true;
1067
+ return `${t.request.method} ${t.request.url} ${t.response?.status ?? ""}`.toLowerCase().includes(q);
1068
+ });
1069
+ const by = {
1070
+ time: (a, b) => b.timing.startedAt - a.timing.startedAt,
1071
+ duration: (a, b) => (b.timing.duration ?? 0) - (a.timing.duration ?? 0),
1072
+ status: (a, b) => (a.response?.status ?? 0) - (b.response?.status ?? 0),
1073
+ url: (a, b) => a.request.url.localeCompare(b.request.url)
1074
+ };
1075
+ return [...rows].sort(by[sort]);
1076
+ }, [traces, query, method, status, sort]);
1077
+ if (!enabled) return null;
1078
+ const failed = traces.filter((t) => t.status !== "success" && t.status !== "pending").length;
1079
+ if (!open) {
1080
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1081
+ "button",
1082
+ {
1083
+ type: "button",
1084
+ onClick: () => setOpen(true),
1085
+ style: { ...S.button, ...S.panel, ...corner(position), width: "auto", padding: "6px 12px", display: "block" },
1086
+ "aria-label": "Open the API tracer",
1087
+ children: [
1088
+ "API ",
1089
+ traces.length,
1090
+ failed ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { color: COLOURS.error }, children: [
1091
+ " \xB7 ",
1092
+ failed,
1093
+ " failed"
1094
+ ] }) : null
1095
+ ]
1096
+ }
1097
+ );
1098
+ }
1099
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...S.panel, ...corner(position), ...style }, children: [
1100
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: S.bar, children: [
1101
+ /* @__PURE__ */ jsxRuntime.jsx("b", { style: { marginRight: 4 }, children: "API tracer" }),
1102
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: S.key, children: [
1103
+ shown.length,
1104
+ "/",
1105
+ traces.length
1106
+ ] }),
1107
+ /* @__PURE__ */ jsxRuntime.jsx(
1108
+ "input",
1109
+ {
1110
+ style: S.input,
1111
+ placeholder: "search url, method, status",
1112
+ value: query,
1113
+ onChange: (e) => setQuery(e.target.value)
1114
+ }
1115
+ ),
1116
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { style: S.select, value: method, onChange: (e) => setMethod(e.target.value), "aria-label": "method", children: [
1117
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "any method" }),
1118
+ ["GET", "POST", "PUT", "PATCH", "DELETE"].map((m) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: m, children: m }, m))
1119
+ ] }),
1120
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { style: S.select, value: status, onChange: (e) => setStatus(e.target.value), "aria-label": "status", children: [
1121
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "any status" }),
1122
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "ok", children: "passing" }),
1123
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "failed", children: "failing" }),
1124
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "pending", children: "in flight" }),
1125
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "cancelled", children: "cancelled" }),
1126
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "network-error", children: "network error" })
1127
+ ] }),
1128
+ /* @__PURE__ */ jsxRuntime.jsxs("select", { style: S.select, value: sort, onChange: (e) => setSort(e.target.value), "aria-label": "sort", children: [
1129
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "time", children: "newest" }),
1130
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "duration", children: "slowest" }),
1131
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "status", children: "status" }),
1132
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "url", children: "url" })
1133
+ ] }),
1134
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: S.button, onClick: clear, children: "clear" }),
1135
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", style: S.button, onClick: () => setOpen(false), children: "\xD7" })
1136
+ ] }),
1137
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "auto" }, children: shown.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: 16, ...S.key }, children: "No calls captured yet." }) : shown.map((t) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1138
+ /* @__PURE__ */ jsxRuntime.jsxs(
1139
+ "button",
1140
+ {
1141
+ type: "button",
1142
+ style: S.row,
1143
+ onClick: () => setExpanded(expanded === t.id ? null : t.id),
1144
+ "aria-expanded": expanded === t.id,
1145
+ children: [
1146
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: COLOURS[t.status] }, children: t.request.method }),
1147
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: shortPath(t) }),
1148
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: COLOURS[t.status], textAlign: "right" }, children: t.response?.status ?? (t.status === "pending" ? "\u2026" : "\u2014") }),
1149
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...S.key, textAlign: "right" }, children: t.timing.duration !== void 0 ? `${t.timing.duration}ms` : "" })
1150
+ ]
1151
+ }
1152
+ ),
1153
+ expanded === t.id ? /* @__PURE__ */ jsxRuntime.jsx(Detail, { trace: t }) : null
1154
+ ] }, t.id)) })
1155
+ ] });
1156
+ }
1157
+ var ui_default = ApiTracerPanel;
1158
+
1159
+ exports.ApiTracerPanel = ApiTracerPanel;
1160
+ exports.default = ui_default;
1161
+ //# sourceMappingURL=ui.cjs.map
1162
+ //# sourceMappingURL=ui.cjs.map