@fedify/debugger 2.0.0-dev.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/mod.js ADDED
@@ -0,0 +1,945 @@
1
+
2
+ import { Temporal } from "@js-temporal/polyfill";
3
+
4
+ import { MemoryKvStore } from "@fedify/fedify/federation";
5
+ import { FedifySpanExporter } from "@fedify/fedify/otel";
6
+ import { configureSync, getConfig } from "@logtape/logtape";
7
+ import { context, propagation, trace } from "@opentelemetry/api";
8
+ import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
9
+ import { W3CTraceContextPropagator } from "@opentelemetry/core";
10
+ import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
11
+ import { AsyncLocalStorage } from "node:async_hooks";
12
+ import { Hono } from "hono";
13
+ import { getCookie } from "hono/cookie";
14
+ import { timingSafeEqual } from "node:crypto";
15
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
16
+
17
+ //#region src/log-store.ts
18
+ /**
19
+ * Persistent storage for log records grouped by trace ID, backed by a
20
+ * {@link KvStore}. When the same `KvStore` is shared across web and worker
21
+ * processes the dashboard can display logs produced by background tasks.
22
+ */
23
+ var LogStore = class {
24
+ #kv;
25
+ #keyPrefix;
26
+ /** Chain of pending write promises for flush(). */
27
+ #pending = Promise.resolve();
28
+ constructor(kv, keyPrefix = [
29
+ "fedify",
30
+ "debugger",
31
+ "logs"
32
+ ]) {
33
+ this.#kv = kv;
34
+ this.#keyPrefix = keyPrefix;
35
+ }
36
+ /**
37
+ * Enqueue a log record for writing. The write happens asynchronously;
38
+ * call {@link flush} to wait for all pending writes to complete.
39
+ *
40
+ * Keys use a timestamp + random suffix so that entries sort
41
+ * chronologically and never collide, even across multiple processes
42
+ * sharing the same {@link KvStore}.
43
+ */
44
+ add(traceId, record) {
45
+ const key = [
46
+ ...this.#keyPrefix,
47
+ traceId,
48
+ `${Date.now().toString(36).padStart(10, "0")}-${Math.random().toString(36).slice(2)}`
49
+ ];
50
+ this.#pending = this.#pending.then(() => this.#kv.set(key, record)).catch(() => {});
51
+ }
52
+ /** Wait for all pending writes to complete. */
53
+ flush() {
54
+ return this.#pending;
55
+ }
56
+ async get(traceId) {
57
+ const prefix = [...this.#keyPrefix, traceId];
58
+ const logs = [];
59
+ for await (const entry of this.#kv.list(prefix)) logs.push(entry.value);
60
+ return logs;
61
+ }
62
+ };
63
+ /**
64
+ * Converts a {@link LogRecord} into a plain serializable object suitable
65
+ * for storage in a {@link KvStore}.
66
+ */
67
+ function serializeLogRecord(record) {
68
+ const messageParts = [];
69
+ for (const part of record.message) if (typeof part === "string") messageParts.push(part);
70
+ else if (part == null) messageParts.push("");
71
+ else messageParts.push(String(part));
72
+ const { traceId: _t, spanId: _s,...properties } = record.properties;
73
+ return {
74
+ category: record.category,
75
+ level: record.level,
76
+ message: messageParts.join(""),
77
+ timestamp: record.timestamp,
78
+ properties
79
+ };
80
+ }
81
+ /**
82
+ * Creates a LogTape {@link Sink} that writes log records into the given
83
+ * {@link LogStore}, grouped by their `traceId` property. Records without
84
+ * a `traceId` are silently discarded.
85
+ */
86
+ function createLogSink(store) {
87
+ return (record) => {
88
+ const traceId = record.properties.traceId;
89
+ if (typeof traceId !== "string" || traceId.length === 0) return;
90
+ store.add(traceId, serializeLogRecord(record));
91
+ };
92
+ }
93
+
94
+ //#endregion
95
+ //#region src/auth.ts
96
+ const SESSION_COOKIE_NAME = "__fedify_debug_session";
97
+ const SESSION_TOKEN = "authenticated";
98
+ async function generateHmacKey() {
99
+ return await crypto.subtle.generateKey({
100
+ name: "HMAC",
101
+ hash: "SHA-256"
102
+ }, false, ["sign", "verify"]);
103
+ }
104
+ function toHex(buffer) {
105
+ return [...new Uint8Array(buffer)].map((b) => b.toString(16).padStart(2, "0")).join("");
106
+ }
107
+ function fromHex(hex) {
108
+ const bytes = new Uint8Array(hex.length / 2);
109
+ for (let i = 0; i < hex.length; i += 2) bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
110
+ return bytes.buffer;
111
+ }
112
+ async function signSession(key) {
113
+ const encoder = new TextEncoder();
114
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(SESSION_TOKEN));
115
+ return toHex(signature);
116
+ }
117
+ async function verifySession(key, signature) {
118
+ try {
119
+ const encoder = new TextEncoder();
120
+ return await crypto.subtle.verify("HMAC", key, fromHex(signature), encoder.encode(SESSION_TOKEN));
121
+ } catch {
122
+ return false;
123
+ }
124
+ }
125
+ /**
126
+ * Constant-time string comparison to prevent timing attacks on credential
127
+ * checks. Uses {@link timingSafeEqual} from `node:crypto` under the hood.
128
+ */
129
+ function constantTimeEqual(a, b) {
130
+ const encoder = new TextEncoder();
131
+ const bufA = encoder.encode(a);
132
+ const bufB = encoder.encode(b);
133
+ if (bufA.byteLength !== bufB.byteLength) {
134
+ timingSafeEqual(bufA, new Uint8Array(bufA.byteLength));
135
+ return false;
136
+ }
137
+ return timingSafeEqual(bufA, bufB);
138
+ }
139
+ async function checkAuth(auth, formData) {
140
+ if (auth.type === "password") {
141
+ if ("authenticate" in auth) return await auth.authenticate(formData.password);
142
+ return constantTimeEqual(formData.password, auth.password);
143
+ }
144
+ if (auth.type === "usernamePassword") {
145
+ if ("authenticate" in auth) return await auth.authenticate(formData.username ?? "", formData.password);
146
+ const usernameMatch = constantTimeEqual(formData.username ?? "", auth.username);
147
+ const passwordMatch = constantTimeEqual(formData.password, auth.password);
148
+ return usernameMatch && passwordMatch;
149
+ }
150
+ return false;
151
+ }
152
+
153
+ //#endregion
154
+ //#region src/views/logo.tsx
155
+ /**
156
+ * Inline SVG of the Fedify logo (mascot bird with fediverse connection nodes).
157
+ */
158
+ const FedifyLogo = ({ size = 24 }) => {
159
+ return /* @__PURE__ */ jsxs("svg", {
160
+ width: size,
161
+ height: size,
162
+ viewBox: "0 0 112 112",
163
+ role: "img",
164
+ "aria-label": "Fedify logo",
165
+ children: [
166
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", {
167
+ clipPathUnits: "userSpaceOnUse",
168
+ id: "fedify-logo-clip",
169
+ children: /* @__PURE__ */ jsx("ellipse", {
170
+ style: "fill: #000; stroke: #000; stroke-width: 3.02635; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-opacity: 1; paint-order: normal",
171
+ cx: "55.92646",
172
+ cy: "56.073448",
173
+ transform: "rotate(-0.07519647)",
174
+ rx: "54.486828",
175
+ ry: "54.486824"
176
+ })
177
+ }) }),
178
+ /* @__PURE__ */ jsx("ellipse", {
179
+ style: "fill: #ffffff; stroke: none; stroke-width: 3.02635; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-opacity: 1; paint-order: normal",
180
+ cx: "55.92646",
181
+ cy: "56.073448",
182
+ transform: "rotate(-0.07519647)",
183
+ rx: "54.486828",
184
+ ry: "54.486824"
185
+ }),
186
+ /* @__PURE__ */ jsxs("g", {
187
+ "clip-path": "url(#fedify-logo-clip)",
188
+ children: [/* @__PURE__ */ jsxs("g", { children: [
189
+ /* @__PURE__ */ jsx("path", {
190
+ d: "M 77.4624,78.9593 C 78.2802,68.3428 73.7143,58.8833 71.3291,55.4806 L 87.6847,48.335 c 4.9066,1.6333 6.474,17.3537 6.6444,25.0098 0,0 -3.5778,0.5104 -5.6222,2.0416 -2.085,1.5616 -5.6222,5.1041 -11.2445,3.5729 z",
191
+ fill: "#ffffff",
192
+ stroke: "#84b5d9",
193
+ "stroke-width": "3",
194
+ "stroke-linecap": "round"
195
+ }),
196
+ /* @__PURE__ */ jsx("path", {
197
+ d: "M 7.06239,52.159 C -5.55748,54.1782 -12.682,66.0659 -17.661,73.2769 c -0.8584,13.3918 -0.6181,41.1021 7.211,44.8111 7.82906,3.709 26.9553,1.545 35.5398,0 v 4.121 c 1.3736,0.515 5.0477,1.648 8.7562,2.06 3.7085,0.412 6.696,-1.202 7.7261,-2.06 v -9.787 c 0.5151,-0.343 2.9874,-1.957 8.7562,-5.666 7.211,-4.635 11.3315,-16.482 9.7863,-24.7229 -1.1589,-6.181 3.6055,-18.5427 6.1809,-26.7838 9.7863,2.0601 22.148,-1.0301 23.1781,-14.9369 C 90.1205,31.5801 80.7174,19.9868 63.2051,25.3752 45.6927,30.7636 48.268,52.159 41.5721,59.37 35.3913,53.1891 23.5446,49.5219 7.06239,52.159 Z",
198
+ fill: "#bae6fd",
199
+ stroke: "#0c4a6e",
200
+ "stroke-width": "3",
201
+ "stroke-linecap": "round"
202
+ }),
203
+ /* @__PURE__ */ jsx("path", {
204
+ d: "M 66.2955,55.2493 C 64.5786,54.7342 60.9387,53.6011 60.1146,53.189",
205
+ stroke: "#0284c7",
206
+ "stroke-opacity": "0.37",
207
+ "stroke-width": "3",
208
+ "stroke-linecap": "round",
209
+ style: "opacity: 1; fill: none; stroke-width: 3; stroke-linejoin: miter; stroke-dasharray: none; paint-order: normal"
210
+ }),
211
+ /* @__PURE__ */ jsx("path", {
212
+ d: "m 41.5721,59.3698 c -0.6868,0.8585 -2.6784,2.7814 -5.1507,3.6055",
213
+ stroke: "#0284c7",
214
+ "stroke-opacity": "0.37",
215
+ "stroke-width": "3",
216
+ "stroke-linecap": "round",
217
+ style: "fill: none"
218
+ }),
219
+ /* @__PURE__ */ jsx("circle", {
220
+ cx: "68.870796",
221
+ cy: "42.8876",
222
+ r: "2.0602801",
223
+ fill: "#000000"
224
+ })
225
+ ] }), /* @__PURE__ */ jsxs("g", {
226
+ transform: "matrix(0.08160718,0,0,0.08160718,76.994732,53.205469)",
227
+ style: "display: inline",
228
+ children: [
229
+ /* @__PURE__ */ jsx("path", {
230
+ style: "fill: #a730b8; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
231
+ d: "m 181.13086,275.13672 a 68.892408,68.892408 0 0 1 -29.46484,29.32812 l 161.75781,162.38868 38.99805,-19.76368 z m 213.36328,214.1875 -38.99805,19.76367 81.96289,82.2832 a 68.892409,68.892409 0 0 1 29.47071,-29.33203 z",
232
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
233
+ }),
234
+ /* @__PURE__ */ jsx("path", {
235
+ style: "fill: #5496be; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
236
+ d: "m 581.64648,339.39062 -91.57617,46.41016 6.75196,43.18945 103.61523,-52.51367 A 68.892409,68.892409 0 0 1 581.64648,339.39062 Z M 436.9082,412.74219 220.38281,522.47656 a 68.892408,68.892408 0 0 1 18.79492,37.08985 L 443.66016,455.93359 Z",
237
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
238
+ }),
239
+ /* @__PURE__ */ jsx("path", {
240
+ style: "fill: #ce3d1a; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
241
+ d: "M 367.27539,142.4375 262.79492,346.4082 293.64258,377.375 404.26562,161.41797 A 68.892408,68.892408 0 0 1 367.27539,142.4375 Z m -131.6543,257.02148 -52.92187,103.31446 a 68.892409,68.892409 0 0 1 36.98633,18.97851 l 46.78125,-91.32812 z",
242
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
243
+ }),
244
+ /* @__PURE__ */ jsx("path", {
245
+ style: "fill: #d0188f; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
246
+ d: "m 150.76758,304.91797 a 68.892408,68.892408 0 0 1 -34.41602,7.19531 68.892408,68.892408 0 0 1 -6.65039,-0.69531 l 30.90235,197.66211 a 68.892409,68.892409 0 0 1 34.41601,-7.19531 68.892409,68.892409 0 0 1 6.64649,0.69531 z",
247
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
248
+ }),
249
+ /* @__PURE__ */ jsx("path", {
250
+ style: "fill: #5b36e9; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
251
+ d: "m 239.3418,560.54492 a 68.892408,68.892408 0 0 1 0.7207,13.87696 68.892408,68.892408 0 0 1 -7.26758,27.17968 l 197.62891,31.71289 a 68.892409,68.892409 0 0 1 -0.72266,-13.8789 68.892409,68.892409 0 0 1 7.26953,-27.17774 z",
252
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
253
+ }),
254
+ /* @__PURE__ */ jsx("path", {
255
+ style: "fill: #30b873; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
256
+ d: "m 601.13281,377.19922 -91.21875,178.08203 a 68.892408,68.892408 0 0 1 36.99414,18.98242 L 638.125,396.18359 a 68.892409,68.892409 0 0 1 -36.99219,-18.98437 z",
257
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
258
+ }),
259
+ /* @__PURE__ */ jsx("path", {
260
+ style: "fill: #ebe305; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
261
+ d: "m 476.72266,125.33008 a 68.892408,68.892408 0 0 1 -29.47071,29.33203 l 141.26563,141.81055 a 68.892409,68.892409 0 0 1 29.46875,-29.33204 z",
262
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
263
+ }),
264
+ /* @__PURE__ */ jsx("path", {
265
+ style: "fill: #f47601; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
266
+ d: "m 347.78711,104.63086 -178.57617,90.49805 a 68.892409,68.892409 0 0 1 18.79297,37.08593 l 178.57421,-90.50195 a 68.892408,68.892408 0 0 1 -18.79101,-37.08203 z",
267
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
268
+ }),
269
+ /* @__PURE__ */ jsx("path", {
270
+ style: "fill: #57c115; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
271
+ d: "m 446.92578,154.82617 a 68.892408,68.892408 0 0 1 -34.98242,7.48242 68.892408,68.892408 0 0 1 -6.0293,-0.63281 l 15.81836,101.29102 43.16211,6.92578 z m -16,167.02735 37.40039,239.48242 a 68.892409,68.892409 0 0 1 33.91406,-6.94336 68.892409,68.892409 0 0 1 7.20704,0.79101 L 474.08984,328.77734 Z",
272
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
273
+ }),
274
+ /* @__PURE__ */ jsx("path", {
275
+ style: "fill: #dbb210; fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 41.5748",
276
+ d: "m 188.13086,232.97461 a 68.892408,68.892408 0 0 1 0.75781,14.0957 68.892408,68.892408 0 0 1 -7.16015,26.98242 l 101.36914,16.28125 19.92382,-38.9082 z m 173.73633,27.90039 -19.92578,38.91211 239.51367,38.4668 a 68.892409,68.892409 0 0 1 -0.69531,-13.71875 68.892409,68.892409 0 0 1 7.34961,-27.32422 z",
277
+ transform: "matrix(0.26458333,0,0,0.26458333,-6.6789703,32.495842)"
278
+ }),
279
+ /* @__PURE__ */ jsx("circle", {
280
+ style: "fill: #ffca00; fill-opacity: 0.995968; stroke: none; stroke-width: 0.264583; stroke-opacity: 0.960784",
281
+ cx: "106.26596",
282
+ cy: "51.535553",
283
+ r: "16.570711",
284
+ transform: "rotate(3.1178174)"
285
+ }),
286
+ /* @__PURE__ */ jsx("circle", {
287
+ style: "fill: #64ff00; fill-opacity: 0.995968; stroke: none; stroke-width: 0.264583; stroke-opacity: 0.960784",
288
+ cx: "171.42836",
289
+ cy: "110.19328",
290
+ r: "16.570711",
291
+ transform: "rotate(3.1178174)"
292
+ }),
293
+ /* @__PURE__ */ jsx("circle", {
294
+ style: "fill: #00a3ff; fill-opacity: 0.995968; stroke: none; stroke-width: 0.264583; stroke-opacity: 0.960784",
295
+ cx: "135.76379",
296
+ cy: "190.27704",
297
+ r: "16.570711",
298
+ transform: "rotate(3.1178174)"
299
+ }),
300
+ /* @__PURE__ */ jsx("circle", {
301
+ style: "fill: #9500ff; fill-opacity: 0.995968; stroke: none; stroke-width: 0.264583; stroke-opacity: 0.960784",
302
+ cx: "48.559471",
303
+ cy: "181.1138",
304
+ r: "16.570711",
305
+ transform: "rotate(3.1178174)"
306
+ }),
307
+ /* @__PURE__ */ jsx("circle", {
308
+ style: "fill: #ff0000; fill-opacity: 0.995968; stroke: none; stroke-width: 0.264583; stroke-opacity: 0.960784",
309
+ cx: "30.328812",
310
+ cy: "95.366837",
311
+ r: "16.570711",
312
+ transform: "rotate(3.1178174)"
313
+ })
314
+ ]
315
+ })]
316
+ }),
317
+ /* @__PURE__ */ jsx("circle", {
318
+ style: "opacity: 1; fill: none; stroke: #84b5d9; stroke-width: 4.91342; stroke-linejoin: miter; stroke-dasharray: none; stroke-dashoffset: 0; stroke-opacity: 1; paint-order: normal",
319
+ cx: "55.926456",
320
+ cy: "56.073448",
321
+ transform: "rotate(-0.07519625)",
322
+ r: "53.543289"
323
+ })
324
+ ]
325
+ });
326
+ };
327
+
328
+ //#endregion
329
+ //#region src/views/layout.tsx
330
+ /**
331
+ * Root HTML layout for the debug dashboard.
332
+ */
333
+ const Layout = ({ title, pathPrefix, children }) => {
334
+ return /* @__PURE__ */ jsxs("html", { children: [/* @__PURE__ */ jsxs("head", { children: [
335
+ /* @__PURE__ */ jsx("meta", { charset: "utf-8" }),
336
+ /* @__PURE__ */ jsx("meta", {
337
+ name: "viewport",
338
+ content: "width=device-width, initial-scale=1"
339
+ }),
340
+ /* @__PURE__ */ jsxs("title", { children: [title != null ? `${title} — ` : "", "Fedify Debug Dashboard"] }),
341
+ /* @__PURE__ */ jsx("style", { children: `
342
+ body {
343
+ font-family: system-ui, -apple-system, sans-serif;
344
+ max-width: 960px;
345
+ margin: 0 auto;
346
+ padding: 1rem;
347
+ color: #333;
348
+ }
349
+ header { border-bottom: 1px solid #ddd; padding-bottom: 0.5rem; margin-bottom: 1rem; }
350
+ header h1 { margin: 0; font-size: 1.25rem; }
351
+ header h1 a { color: inherit; text-decoration: none; display: inline-flex; align-items: center; gap: 0.5rem; }
352
+ table { width: 100%; border-collapse: collapse; }
353
+ th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
354
+ th { font-weight: 600; font-size: 0.875rem; color: #666; }
355
+ a { color: #0969da; }
356
+ code { background: #f0f0f0; padding: 0.15em 0.3em; border-radius: 3px; font-size: 0.875em; }
357
+ .badge { display: inline-block; background: #e0e0e0; color: #333; padding: 0.15em 0.5em; border-radius: 3px; font-size: 0.75rem; }
358
+ .badge-inbound { background: #ddf4ff; color: #0969da; }
359
+ .badge-outbound { background: #fff8c5; color: #9a6700; }
360
+ .detail-section { margin-bottom: 1.5rem; }
361
+ .detail-section h2 { font-size: 1rem; margin-bottom: 0.5rem; border-bottom: 1px solid #eee; padding-bottom: 0.25rem; }
362
+ pre { background: #f6f8fa; padding: 1rem; overflow-x: auto; border-radius: 6px; font-size: 0.8125rem; }
363
+ .empty { color: #888; font-style: italic; }
364
+ nav a { margin-right: 0.5rem; }
365
+ .log-table td { font-size: 0.8125rem; vertical-align: top; }
366
+ .log-table time { font-family: monospace; white-space: nowrap; }
367
+ .badge-debug { background: #e8e8e8; color: #666; }
368
+ .badge-info { background: #ddf4ff; color: #0969da; }
369
+ .badge-warning { background: #fff8c5; color: #9a6700; }
370
+ .badge-error { background: #ffebe9; color: #cf222e; }
371
+ .badge-fatal { background: #cf222e; color: #fff; }
372
+ .log-error td { background: #fff5f5; }
373
+ .log-fatal td { background: #ffebe9; }
374
+
375
+ @media (prefers-color-scheme: dark) {
376
+ body { background: #0d1117; color: #e6edf3; }
377
+ header { border-bottom-color: #30363d; }
378
+ th { color: #9198a1; }
379
+ th, td { border-bottom-color: #21262d; }
380
+ a { color: #58a6ff; }
381
+ code { background: #161b22; }
382
+ .badge { background: #30363d; color: #e6edf3; }
383
+ .badge-inbound { background: #122d42; color: #58a6ff; }
384
+ .badge-outbound { background: #2e2a1f; color: #d29922; }
385
+ .detail-section h2 { border-bottom-color: #21262d; }
386
+ pre { background: #161b22; }
387
+ .empty { color: #9198a1; }
388
+ .badge-debug { background: #21262d; color: #9198a1; }
389
+ .badge-info { background: #122d42; color: #58a6ff; }
390
+ .badge-warning { background: #2e2a1f; color: #d29922; }
391
+ .badge-error { background: #3d1f20; color: #f85149; }
392
+ .badge-fatal { background: #da3633; color: #fff; }
393
+ .log-error td { background: #2d1215; }
394
+ .log-fatal td { background: #3d1f20; }
395
+ }
396
+ ` })
397
+ ] }), /* @__PURE__ */ jsxs("body", { children: [/* @__PURE__ */ jsx("header", { children: /* @__PURE__ */ jsx("h1", { children: /* @__PURE__ */ jsxs("a", {
398
+ href: pathPrefix + "/",
399
+ children: [/* @__PURE__ */ jsx(FedifyLogo, { size: 24 }), "Fedify Debug Dashboard"]
400
+ }) }) }), /* @__PURE__ */ jsx("main", { children })] })] });
401
+ };
402
+
403
+ //#endregion
404
+ //#region src/views/login.tsx
405
+ /**
406
+ * Login page for the debug dashboard.
407
+ */
408
+ const LoginPage = ({ pathPrefix, showUsername, error }) => {
409
+ return /* @__PURE__ */ jsxs(Layout, {
410
+ title: "Login",
411
+ pathPrefix,
412
+ children: [
413
+ /* @__PURE__ */ jsx("h2", { children: "Login Required" }),
414
+ /* @__PURE__ */ jsx("p", {
415
+ class: "login-description",
416
+ children: "The debug dashboard requires authentication to access."
417
+ }),
418
+ error && /* @__PURE__ */ jsx("p", {
419
+ class: "login-error",
420
+ children: error
421
+ }),
422
+ /* @__PURE__ */ jsxs("form", {
423
+ method: "post",
424
+ action: pathPrefix + "/login",
425
+ class: "login-form",
426
+ children: [
427
+ showUsername && /* @__PURE__ */ jsxs("div", {
428
+ class: "login-field",
429
+ children: [/* @__PURE__ */ jsx("label", {
430
+ for: "username",
431
+ children: "Username"
432
+ }), /* @__PURE__ */ jsx("input", {
433
+ type: "text",
434
+ id: "username",
435
+ name: "username",
436
+ required: true,
437
+ autocomplete: "username"
438
+ })]
439
+ }),
440
+ /* @__PURE__ */ jsxs("div", {
441
+ class: "login-field",
442
+ children: [/* @__PURE__ */ jsx("label", {
443
+ for: "password",
444
+ children: "Password"
445
+ }), /* @__PURE__ */ jsx("input", {
446
+ type: "password",
447
+ id: "password",
448
+ name: "password",
449
+ required: true,
450
+ autocomplete: "current-password"
451
+ })]
452
+ }),
453
+ /* @__PURE__ */ jsx("button", {
454
+ type: "submit",
455
+ children: "Log in"
456
+ })
457
+ ]
458
+ }),
459
+ /* @__PURE__ */ jsx("style", { children: `
460
+ .login-form {
461
+ max-width: 320px;
462
+ }
463
+ .login-field {
464
+ margin-bottom: 0.75rem;
465
+ }
466
+ .login-field label {
467
+ display: block;
468
+ margin-bottom: 0.25rem;
469
+ font-weight: 600;
470
+ font-size: 0.875rem;
471
+ }
472
+ .login-field input {
473
+ width: 100%;
474
+ padding: 0.5rem;
475
+ border: 1px solid #ccc;
476
+ border-radius: 4px;
477
+ font-size: 0.875rem;
478
+ box-sizing: border-box;
479
+ }
480
+ .login-form button {
481
+ padding: 0.5rem 1.5rem;
482
+ background: #0969da;
483
+ color: #fff;
484
+ border: none;
485
+ border-radius: 4px;
486
+ font-size: 0.875rem;
487
+ cursor: pointer;
488
+ }
489
+ .login-form button:hover {
490
+ background: #0860c5;
491
+ }
492
+ .login-error {
493
+ color: #d1242f;
494
+ background: #ffebe9;
495
+ padding: 0.5rem 0.75rem;
496
+ border-radius: 4px;
497
+ font-size: 0.875rem;
498
+ }
499
+ .login-description {
500
+ color: #666;
501
+ font-size: 0.875rem;
502
+ }
503
+
504
+ @media (prefers-color-scheme: dark) {
505
+ .login-field input {
506
+ background: #0d1117;
507
+ color: #e6edf3;
508
+ border-color: #30363d;
509
+ }
510
+ .login-form button {
511
+ background: #1f6feb;
512
+ }
513
+ .login-form button:hover {
514
+ background: #388bfd;
515
+ }
516
+ .login-error {
517
+ color: #f85149;
518
+ background: #3d1f20;
519
+ }
520
+ .login-description {
521
+ color: #9198a1;
522
+ }
523
+ }
524
+ ` })
525
+ ]
526
+ });
527
+ };
528
+
529
+ //#endregion
530
+ //#region src/views/trace-detail.tsx
531
+ /**
532
+ * Safely formats a timestamp (milliseconds since epoch) as an ISO string.
533
+ * Returns `"(invalid)"` if the timestamp is not a finite number or produces
534
+ * an invalid `Date`.
535
+ */
536
+ function safeISOString(timestamp) {
537
+ if (!Number.isFinite(timestamp)) return "(invalid)";
538
+ try {
539
+ return new Date(timestamp).toISOString();
540
+ } catch {
541
+ return "(invalid)";
542
+ }
543
+ }
544
+ /**
545
+ * The trace detail page of the debug dashboard.
546
+ */
547
+ const TraceDetailPage = ({ traceId, activities, logs, pathPrefix }) => {
548
+ return /* @__PURE__ */ jsxs(Layout, {
549
+ pathPrefix,
550
+ title: `Trace ${traceId.slice(0, 8)}`,
551
+ children: [
552
+ /* @__PURE__ */ jsx("nav", { children: /* @__PURE__ */ jsx("a", {
553
+ href: `${pathPrefix}/`,
554
+ children: "← Back to traces"
555
+ }) }),
556
+ /* @__PURE__ */ jsxs("h2", { children: ["Trace ", /* @__PURE__ */ jsx("code", { children: traceId.slice(0, 8) })] }),
557
+ /* @__PURE__ */ jsxs("p", { children: [
558
+ "Full ID: ",
559
+ /* @__PURE__ */ jsx("code", { children: traceId }),
560
+ " —",
561
+ " ",
562
+ /* @__PURE__ */ jsx("strong", { children: activities.length }),
563
+ " ",
564
+ "activit",
565
+ activities.length !== 1 ? "ies" : "y",
566
+ ",",
567
+ " ",
568
+ /* @__PURE__ */ jsx("strong", { children: logs.length }),
569
+ " log record",
570
+ logs.length !== 1 ? "s" : ""
571
+ ] }),
572
+ activities.length === 0 ? /* @__PURE__ */ jsx("p", {
573
+ class: "empty",
574
+ children: "No activities found for this trace."
575
+ }) : activities.map((activity) => /* @__PURE__ */ jsxs("div", {
576
+ class: "detail-section",
577
+ children: [
578
+ /* @__PURE__ */ jsxs("h2", { children: [
579
+ /* @__PURE__ */ jsx("span", {
580
+ class: `badge ${activity.direction === "inbound" ? "badge-inbound" : "badge-outbound"}`,
581
+ children: activity.direction
582
+ }),
583
+ " ",
584
+ activity.activityType
585
+ ] }),
586
+ /* @__PURE__ */ jsx("table", { children: /* @__PURE__ */ jsxs("tbody", { children: [
587
+ /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Span ID" }), /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: activity.spanId }) })] }),
588
+ activity.parentSpanId != null && /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Parent Span" }), /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: activity.parentSpanId }) })] }),
589
+ activity.activityId != null && /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Activity ID" }), /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: activity.activityId }) })] }),
590
+ activity.actorId != null && /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Actor" }), /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: activity.actorId }) })] }),
591
+ /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Timestamp" }), /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("time", {
592
+ datetime: activity.timestamp,
593
+ children: activity.timestamp
594
+ }) })] }),
595
+ activity.direction === "outbound" && activity.inboxUrl != null && /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Inbox URL" }), /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: activity.inboxUrl }) })] }),
596
+ activity.direction === "inbound" && /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Verified" }), /* @__PURE__ */ jsx("td", { children: activity.verified ? "Yes" : "No" })] }),
597
+ activity.signatureDetails != null && /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", { children: "Signature Details" }), /* @__PURE__ */ jsxs("td", { children: [
598
+ "HTTP Signatures:",
599
+ " ",
600
+ activity.signatureDetails.httpSignaturesVerified ? "verified" : "not verified",
601
+ activity.signatureDetails.httpSignaturesKeyId != null && /* @__PURE__ */ jsxs("span", { children: [
602
+ "\xA0(key:\xA0",
603
+ /* @__PURE__ */ jsx("code", { children: activity.signatureDetails.httpSignaturesKeyId }),
604
+ ")"
605
+ ] }),
606
+ /* @__PURE__ */ jsx("br", {}),
607
+ "LD Signatures:",
608
+ " ",
609
+ activity.signatureDetails.ldSignaturesVerified ? "verified" : "not verified"
610
+ ] })] })
611
+ ] }) }),
612
+ /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", { children: "Activity JSON" }), /* @__PURE__ */ jsx("pre", { children: formatJson(activity.activityJson) })] })
613
+ ]
614
+ }, activity.spanId)),
615
+ /* @__PURE__ */ jsx("h2", { children: "Logs" }),
616
+ logs.length === 0 ? /* @__PURE__ */ jsx("p", {
617
+ class: "empty",
618
+ children: "No logs captured for this trace."
619
+ }) : /* @__PURE__ */ jsxs("table", {
620
+ class: "log-table",
621
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
622
+ /* @__PURE__ */ jsx("th", { children: "Time" }),
623
+ /* @__PURE__ */ jsx("th", { children: "Level" }),
624
+ /* @__PURE__ */ jsx("th", { children: "Category" }),
625
+ /* @__PURE__ */ jsx("th", { children: "Message" })
626
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: logs.map((log, i) => /* @__PURE__ */ jsxs("tr", {
627
+ class: `log-${log.level}`,
628
+ children: [
629
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("time", {
630
+ datetime: safeISOString(log.timestamp),
631
+ children: (() => {
632
+ const iso = safeISOString(log.timestamp);
633
+ return iso === "(invalid)" ? iso : iso.slice(11, 23);
634
+ })()
635
+ }) }),
636
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", {
637
+ class: `badge badge-${log.level}`,
638
+ children: log.level
639
+ }) }),
640
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("code", { children: log.category.join(".") }) }),
641
+ /* @__PURE__ */ jsxs("td", { children: [log.message, Object.keys(log.properties).length > 0 && /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", { children: "Properties" }), /* @__PURE__ */ jsx("pre", { children: JSON.stringify(log.properties, null, 2) })] })] })
642
+ ]
643
+ }, i)) })]
644
+ })
645
+ ]
646
+ });
647
+ };
648
+ function formatJson(json) {
649
+ try {
650
+ return JSON.stringify(JSON.parse(json), null, 2);
651
+ } catch {
652
+ return json;
653
+ }
654
+ }
655
+
656
+ //#endregion
657
+ //#region src/views/traces-list.tsx
658
+ /**
659
+ * The traces list page of the debug dashboard.
660
+ */
661
+ const TracesListPage = ({ traces, pathPrefix }) => {
662
+ return /* @__PURE__ */ jsxs(Layout, {
663
+ pathPrefix,
664
+ children: [
665
+ /* @__PURE__ */ jsxs("p", { children: [
666
+ "Showing ",
667
+ /* @__PURE__ */ jsx("strong", { children: traces.length }),
668
+ " ",
669
+ "trace",
670
+ traces.length !== 1 ? "s" : "",
671
+ "."
672
+ ] }),
673
+ traces.length === 0 ? /* @__PURE__ */ jsx("p", {
674
+ class: "empty",
675
+ children: "No traces captured yet."
676
+ }) : /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
677
+ /* @__PURE__ */ jsx("th", { children: "Trace ID" }),
678
+ /* @__PURE__ */ jsx("th", { children: "Activity Types" }),
679
+ /* @__PURE__ */ jsx("th", { children: "Activities" }),
680
+ /* @__PURE__ */ jsx("th", { children: "Timestamp" })
681
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: traces.map((trace$1) => /* @__PURE__ */ jsxs("tr", { children: [
682
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("a", {
683
+ href: `${pathPrefix}/traces/${trace$1.traceId}`,
684
+ children: /* @__PURE__ */ jsx("code", { children: trace$1.traceId.slice(0, 8) })
685
+ }) }),
686
+ /* @__PURE__ */ jsxs("td", { children: [trace$1.activityTypes.map((t) => /* @__PURE__ */ jsx("span", {
687
+ class: "badge",
688
+ children: t
689
+ }, t)), trace$1.activityTypes.length === 0 && /* @__PURE__ */ jsx("span", {
690
+ class: "empty",
691
+ children: "none"
692
+ })] }),
693
+ /* @__PURE__ */ jsx("td", { children: trace$1.activityCount }),
694
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("time", {
695
+ datetime: trace$1.timestamp,
696
+ children: trace$1.timestamp
697
+ }) })
698
+ ] }, trace$1.traceId)) })] }),
699
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: `
700
+ (function() {
701
+ var interval = setInterval(function() {
702
+ fetch(${JSON.stringify(pathPrefix).replace(/</g, "\\u003c")} + "/api/traces")
703
+ .then(function(r) { return r.json(); })
704
+ .then(function(data) {
705
+ var countEl = document.querySelector("strong");
706
+ if (countEl) {
707
+ var current = parseInt(countEl.textContent, 10);
708
+ if (data.length !== current) {
709
+ location.reload();
710
+ }
711
+ }
712
+ })
713
+ .catch(function() {});
714
+ }, 3000);
715
+ window.addEventListener("beforeunload", function() {
716
+ clearInterval(interval);
717
+ });
718
+ })();
719
+ ` } })
720
+ ]
721
+ });
722
+ };
723
+
724
+ //#endregion
725
+ //#region src/routes.tsx
726
+ function createDebugApp(pathPrefix, exporter, logStore, auth) {
727
+ const app = new Hono({ strict: false }).basePath(pathPrefix);
728
+ let hmacKeyPromise;
729
+ if (auth != null && auth.type !== "request") hmacKeyPromise = generateHmacKey();
730
+ if (auth != null) if (auth.type === "request") app.use("*", async (c, next) => {
731
+ const allowed = await auth.authenticate(c.req.raw);
732
+ if (!allowed) return c.text("Forbidden", 403);
733
+ await next();
734
+ });
735
+ else {
736
+ const showUsername = auth.type === "usernamePassword";
737
+ app.post("/login", async (c) => {
738
+ const body = await c.req.parseBody();
739
+ const password = typeof body.password === "string" ? body.password : "";
740
+ const username = typeof body.username === "string" ? body.username : void 0;
741
+ const ok = await checkAuth(auth, {
742
+ username,
743
+ password
744
+ });
745
+ if (!ok) return c.html(/* @__PURE__ */ jsx(LoginPage, {
746
+ pathPrefix,
747
+ showUsername,
748
+ error: "Invalid credentials."
749
+ }), 401);
750
+ const key = await hmacKeyPromise;
751
+ const sig = await signSession(key);
752
+ const secure = new URL(c.req.url).protocol === "https:";
753
+ return new Response(null, {
754
+ status: 303,
755
+ headers: {
756
+ "Location": pathPrefix + "/",
757
+ "Set-Cookie": `${SESSION_COOKIE_NAME}=${sig}; Path=${pathPrefix}; HttpOnly; SameSite=Strict${secure ? "; Secure" : ""}`
758
+ }
759
+ });
760
+ });
761
+ app.get("/logout", (c) => {
762
+ const secure = new URL(c.req.url).protocol === "https:";
763
+ return new Response(null, {
764
+ status: 303,
765
+ headers: {
766
+ "Location": pathPrefix + "/",
767
+ "Set-Cookie": `${SESSION_COOKIE_NAME}=; Path=${pathPrefix}; HttpOnly; SameSite=Strict${secure ? "; Secure" : ""}; Max-Age=0`
768
+ }
769
+ });
770
+ });
771
+ app.use("*", async (c, next) => {
772
+ const path = new URL(c.req.url).pathname;
773
+ const loginPath = pathPrefix + "/login";
774
+ const logoutPath = pathPrefix + "/logout";
775
+ if (path === loginPath || path === logoutPath) {
776
+ await next();
777
+ return;
778
+ }
779
+ const sessionValue = getCookie(c, SESSION_COOKIE_NAME);
780
+ if (sessionValue) {
781
+ const key = await hmacKeyPromise;
782
+ const valid = await verifySession(key, sessionValue);
783
+ if (valid) {
784
+ await next();
785
+ return;
786
+ }
787
+ }
788
+ return c.html(/* @__PURE__ */ jsx(LoginPage, {
789
+ pathPrefix,
790
+ showUsername
791
+ }), 401);
792
+ });
793
+ }
794
+ app.get("/api/traces", async (c) => {
795
+ const traces = await exporter.getRecentTraces();
796
+ return c.json(traces);
797
+ });
798
+ app.get("/api/logs/:traceId", async (c) => {
799
+ const traceId = c.req.param("traceId");
800
+ await logStore.flush();
801
+ const logs = await logStore.get(traceId);
802
+ return c.json(logs);
803
+ });
804
+ app.get("/traces/:traceId", async (c) => {
805
+ const traceId = c.req.param("traceId");
806
+ await logStore.flush();
807
+ const activities = await exporter.getActivitiesByTraceId(traceId);
808
+ const logs = await logStore.get(traceId);
809
+ return c.html(/* @__PURE__ */ jsx(TraceDetailPage, {
810
+ traceId,
811
+ activities,
812
+ logs,
813
+ pathPrefix
814
+ }));
815
+ });
816
+ app.get("/", async (c) => {
817
+ const traces = await exporter.getRecentTraces();
818
+ return c.html(/* @__PURE__ */ jsx(TracesListPage, {
819
+ traces,
820
+ pathPrefix
821
+ }));
822
+ });
823
+ return app;
824
+ }
825
+
826
+ //#endregion
827
+ //#region src/mod.tsx
828
+ /**
829
+ * Cached auto-setup state so that repeated calls to
830
+ * `createFederationDebugger()` without an explicit exporter reuse the same
831
+ * global OpenTelemetry tracer provider and exporter instead of registering
832
+ * duplicate providers and LogTape sinks.
833
+ */
834
+ let _autoSetup;
835
+ /**
836
+ * Resets the internal auto-setup state. This is intended **only for tests**
837
+ * that need to exercise the auto-setup code path more than once within the
838
+ * same process.
839
+ *
840
+ * @internal
841
+ */
842
+ function resetAutoSetup() {
843
+ _autoSetup = void 0;
844
+ }
845
+ /**
846
+ * Validates and normalizes the path prefix for the debug dashboard.
847
+ *
848
+ * The path must start with `/`, must not end with `/` (unless it is exactly
849
+ * `"/"`), and must not contain control characters, semicolons, or commas
850
+ * (which are unsafe in HTTP headers like `Set-Cookie` and `Location`).
851
+ *
852
+ * @param path The path prefix to validate.
853
+ * @returns The normalized path prefix (trailing slash stripped).
854
+ * @throws {TypeError} If the path is invalid.
855
+ */
856
+ function validatePathPrefix(path) {
857
+ if (path === "" || !path.startsWith("/")) throw new TypeError(`Invalid debug dashboard path: ${JSON.stringify(path)}. The path must start with '/'.`);
858
+ if (/[\x00-\x1f\x7f;,]/.test(path)) throw new TypeError(`Invalid debug dashboard path: ${JSON.stringify(path)}. The path must not contain control characters, semicolons, or commas.`);
859
+ if (path.length > 1 && path.endsWith("/")) return path.slice(0, -1);
860
+ return path;
861
+ }
862
+ function createFederationDebugger(federation, options) {
863
+ const pathPrefix = validatePathPrefix(options?.path ?? "/__debug__");
864
+ let exporter;
865
+ let logKv;
866
+ if (options != null && "exporter" in options) {
867
+ exporter = options.exporter;
868
+ logKv = options.kv;
869
+ } else if (_autoSetup != null) {
870
+ exporter = _autoSetup.exporter;
871
+ logKv = _autoSetup.kv;
872
+ } else {
873
+ const kv = new MemoryKvStore();
874
+ logKv = kv;
875
+ exporter = new FedifySpanExporter(kv);
876
+ const tracerProvider = new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] });
877
+ trace.setGlobalTracerProvider(tracerProvider);
878
+ const contextManager = new AsyncLocalStorageContextManager();
879
+ context.setGlobalContextManager(contextManager);
880
+ propagation.setGlobalPropagator(new W3CTraceContextPropagator());
881
+ _autoSetup = {
882
+ exporter,
883
+ kv
884
+ };
885
+ }
886
+ const logStore = new LogStore(logKv);
887
+ const sink = createLogSink(logStore);
888
+ if (options == null || !("exporter" in options)) {
889
+ const existingConfig = getConfig();
890
+ if (existingConfig != null) {
891
+ const sinks = {
892
+ ...existingConfig.sinks,
893
+ __fedify_debugger__: sink
894
+ };
895
+ const loggers = existingConfig.loggers.map((l) => ({
896
+ ...l,
897
+ sinks: Array.isArray(l.category) && l.category.length < 1 ? [...l.sinks ?? [], "__fedify_debugger__"] : l.sinks
898
+ }));
899
+ if (loggers.every((l) => typeof l.category === "string" || Array.isArray(l.category) && l.category.length > 0)) loggers.push({
900
+ category: [],
901
+ sinks: ["__fedify_debugger__"]
902
+ });
903
+ configureSync({
904
+ ...existingConfig,
905
+ contextLocalStorage: existingConfig.contextLocalStorage ?? new AsyncLocalStorage(),
906
+ reset: true,
907
+ sinks,
908
+ loggers
909
+ });
910
+ } else configureSync({
911
+ sinks: { __fedify_debugger__: sink },
912
+ loggers: [{
913
+ category: [],
914
+ sinks: ["__fedify_debugger__"]
915
+ }],
916
+ contextLocalStorage: new AsyncLocalStorage()
917
+ });
918
+ }
919
+ const auth = options?.auth;
920
+ const app = createDebugApp(pathPrefix, exporter, logStore, auth);
921
+ const debugFetch = async (request, fetchOptions) => {
922
+ const url = new URL(request.url);
923
+ if (url.pathname === pathPrefix || url.pathname.startsWith(pathPrefix + "/")) return await app.fetch(request);
924
+ return await federation.fetch(request, fetchOptions);
925
+ };
926
+ const overrides = {
927
+ fetch: debugFetch,
928
+ sink
929
+ };
930
+ return new Proxy(federation, {
931
+ get(target, prop, receiver) {
932
+ if (prop in overrides) return overrides[prop];
933
+ const value = Reflect.get(target, prop, receiver);
934
+ if (typeof value === "function") return value.bind(target);
935
+ return value;
936
+ },
937
+ has(target, prop) {
938
+ if (prop in overrides) return true;
939
+ return Reflect.has(target, prop);
940
+ }
941
+ });
942
+ }
943
+
944
+ //#endregion
945
+ export { createFederationDebugger, resetAutoSetup };