@neta-art/cohub 1.10.0 → 1.10.1

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.
@@ -0,0 +1,198 @@
1
+ //#region src/debugger.d.ts
2
+ type CohubConsoleLevel = "debug" | "error" | "info" | "log" | "trace" | "warn";
3
+ type CohubNetworkKind = "eventsource" | "fetch" | "resource" | "websocket" | "xhr";
4
+ type CohubNetworkPhase = "abort" | "close" | "error" | "line" | "message" | "open" | "request" | "response" | "timeout";
5
+ interface CohubDebuggerOptions {
6
+ enabled?: boolean;
7
+ maxConsoleEntries?: number;
8
+ maxNetworkEntries?: number;
9
+ maxPayloadBytes?: number;
10
+ maxLineBytes?: number;
11
+ captureHeaders?: boolean;
12
+ captureRequestBody?: boolean;
13
+ captureResponseBody?: boolean;
14
+ redactHeaders?: string[];
15
+ }
16
+ interface CohubDebuggerHandle {
17
+ exportLog: () => CohubDebugLogPackage;
18
+ exportHar: () => CohubDebugHar;
19
+ clear: () => void;
20
+ stop: () => void;
21
+ }
22
+ interface CohubDebugLogPackage {
23
+ version: 1;
24
+ exportedAt: string;
25
+ startedAt: string;
26
+ userAgent?: string;
27
+ url?: string;
28
+ options: Required<CohubDebuggerOptions>;
29
+ console: CohubConsoleEntry[];
30
+ network: CohubNetworkEntry[];
31
+ dropped: {
32
+ console: number;
33
+ network: number;
34
+ };
35
+ }
36
+ interface CohubConsoleEntry {
37
+ id: number;
38
+ at: string;
39
+ elapsedMs: number;
40
+ level: CohubConsoleLevel;
41
+ args: SerializedValue[];
42
+ stack?: string;
43
+ }
44
+ interface CohubNetworkEntry {
45
+ id: number;
46
+ connectionId: string;
47
+ at: string;
48
+ elapsedMs: number;
49
+ kind: CohubNetworkKind;
50
+ phase: CohubNetworkPhase;
51
+ method?: string;
52
+ url: string;
53
+ status?: number;
54
+ statusText?: string;
55
+ durationMs?: number;
56
+ requestHeaders?: Record<string, string>;
57
+ responseHeaders?: Record<string, string>;
58
+ direction?: "incoming" | "outgoing";
59
+ source?: "instrumentation" | "performance";
60
+ initiatorType?: string;
61
+ lineNumber?: number;
62
+ sizeBytes?: number;
63
+ truncated?: boolean;
64
+ eventName?: string;
65
+ payload?: SerializedValue | string;
66
+ error?: string;
67
+ close?: {
68
+ code: number;
69
+ reason: string;
70
+ wasClean: boolean;
71
+ };
72
+ }
73
+ interface CohubDebugHar {
74
+ log: {
75
+ version: "1.2";
76
+ creator: {
77
+ name: "@neta-art/cohub/debugger";
78
+ version: string;
79
+ };
80
+ browser?: {
81
+ name: string;
82
+ version: string;
83
+ };
84
+ pages: HarPage[];
85
+ entries: HarEntry[];
86
+ _cohub?: {
87
+ exportedAt: string;
88
+ startedAt: string;
89
+ dropped: {
90
+ console: number;
91
+ network: number;
92
+ };
93
+ };
94
+ };
95
+ }
96
+ interface HarPage {
97
+ startedDateTime: string;
98
+ id: string;
99
+ title: string;
100
+ pageTimings: {
101
+ onContentLoad: number;
102
+ onLoad: number;
103
+ };
104
+ }
105
+ interface HarEntry {
106
+ pageref: string;
107
+ startedDateTime: string;
108
+ time: number;
109
+ request: HarRequest;
110
+ response: HarResponse;
111
+ cache: Record<string, never>;
112
+ timings: HarTimings;
113
+ serverIPAddress: string;
114
+ connection: string;
115
+ _resourceType: string;
116
+ _cohubConnectionId: string;
117
+ _cohubKind: CohubNetworkKind;
118
+ _cohubSource?: "instrumentation" | "performance";
119
+ _cohubEntries: CohubNetworkEntry[];
120
+ _cohubMessages?: HarCohubMessage[];
121
+ _webSocketMessages?: HarWebSocketMessage[];
122
+ }
123
+ interface HarRequest {
124
+ method: string;
125
+ url: string;
126
+ httpVersion: string;
127
+ cookies: HarCookie[];
128
+ headers: HarNameValuePair[];
129
+ queryString: HarNameValuePair[];
130
+ headersSize: number;
131
+ bodySize: number;
132
+ postData?: HarPostData;
133
+ }
134
+ interface HarResponse {
135
+ status: number;
136
+ statusText: string;
137
+ httpVersion: string;
138
+ cookies: HarCookie[];
139
+ headers: HarNameValuePair[];
140
+ content: HarContent;
141
+ redirectURL: string;
142
+ headersSize: number;
143
+ bodySize: number;
144
+ }
145
+ interface HarNameValuePair {
146
+ name: string;
147
+ value: string;
148
+ }
149
+ interface HarCookie extends HarNameValuePair {
150
+ path?: string;
151
+ domain?: string;
152
+ expires?: string | null;
153
+ httpOnly?: boolean;
154
+ secure?: boolean;
155
+ }
156
+ interface HarPostData {
157
+ mimeType: string;
158
+ text: string;
159
+ params: HarNameValuePair[];
160
+ }
161
+ interface HarContent {
162
+ size: number;
163
+ mimeType: string;
164
+ text?: string;
165
+ encoding?: string;
166
+ }
167
+ interface HarTimings {
168
+ blocked: number;
169
+ dns: number;
170
+ connect: number;
171
+ send: number;
172
+ wait: number;
173
+ receive: number;
174
+ ssl: number;
175
+ }
176
+ interface HarCohubMessage {
177
+ time: string;
178
+ direction?: "incoming" | "outgoing";
179
+ lineNumber?: number;
180
+ eventName?: string;
181
+ data: string;
182
+ }
183
+ interface HarWebSocketMessage {
184
+ type: "receive" | "send";
185
+ time: number;
186
+ opcode: number;
187
+ data: string;
188
+ }
189
+ type SerializedValue = null | boolean | number | string | SerializedValue[] | {
190
+ [key: string]: SerializedValue;
191
+ };
192
+ declare function startCohubDebugger(options?: CohubDebuggerOptions): CohubDebuggerHandle;
193
+ declare function exportCohubDebugLog(): CohubDebugLogPackage;
194
+ declare function exportCohubDebugHar(): CohubDebugHar;
195
+ declare function clearCohubDebugLog(): void;
196
+ declare function stopCohubDebugger(): void;
197
+ //#endregion
198
+ export { CohubConsoleEntry, CohubConsoleLevel, CohubDebugHar, CohubDebugLogPackage, CohubDebuggerHandle, CohubDebuggerOptions, CohubNetworkEntry, CohubNetworkKind, CohubNetworkPhase, HarCohubMessage, HarContent, HarCookie, HarEntry, HarNameValuePair, HarPage, HarPostData, HarRequest, HarResponse, HarTimings, HarWebSocketMessage, SerializedValue, clearCohubDebugLog, exportCohubDebugHar, exportCohubDebugLog, startCohubDebugger, stopCohubDebugger };
@@ -0,0 +1,1120 @@
1
+ //#region src/debugger.ts
2
+ const DEFAULT_OPTIONS = {
3
+ enabled: true,
4
+ maxConsoleEntries: 1e3,
5
+ maxNetworkEntries: 5e3,
6
+ maxPayloadBytes: 64 * 1024,
7
+ maxLineBytes: 16 * 1024,
8
+ captureHeaders: true,
9
+ captureRequestBody: true,
10
+ captureResponseBody: true,
11
+ redactHeaders: [
12
+ "authorization",
13
+ "cookie",
14
+ "set-cookie",
15
+ "x-api-key"
16
+ ]
17
+ };
18
+ const consoleLevels = [
19
+ "debug",
20
+ "error",
21
+ "info",
22
+ "log",
23
+ "trace",
24
+ "warn"
25
+ ];
26
+ const globalKey = "__cohubDebuggerState__";
27
+ const textEncoder = new TextEncoder();
28
+ function startCohubDebugger(options = {}) {
29
+ const state = getOrCreateState(options);
30
+ state.options = normalizeOptions({
31
+ ...state.options,
32
+ ...options
33
+ });
34
+ if (state.installed || !state.options.enabled) return createHandle(state);
35
+ installConsoleCollector(state);
36
+ installFetchCollector(state);
37
+ installXhrCollector(state);
38
+ installEventSourceCollector(state);
39
+ installWebSocketCollector(state);
40
+ installPerformanceCollector(state);
41
+ state.installed = true;
42
+ return createHandle(state);
43
+ }
44
+ function exportCohubDebugLog() {
45
+ return createLogPackage(getOrCreateState());
46
+ }
47
+ function exportCohubDebugHar() {
48
+ return createHarPackage(getOrCreateState());
49
+ }
50
+ function clearCohubDebugLog() {
51
+ const state = getOrCreateState();
52
+ state.consoleBuffer.clear();
53
+ state.networkBuffer.clear();
54
+ }
55
+ function stopCohubDebugger() {
56
+ const state = getExistingState();
57
+ if (!state?.installed) return;
58
+ for (const level of consoleLevels) {
59
+ const original = state.originals.console[level];
60
+ if (original) console[level] = original;
61
+ }
62
+ if (state.originals.fetch) globalThis.fetch = state.originals.fetch;
63
+ if (state.originals.XMLHttpRequest) globalThis.XMLHttpRequest = state.originals.XMLHttpRequest;
64
+ if (state.originals.xhrOpen && state.originals.xhrSend && state.originals.xhrSetRequestHeader && globalThis.XMLHttpRequest) {
65
+ XMLHttpRequest.prototype.open = state.originals.xhrOpen;
66
+ XMLHttpRequest.prototype.send = state.originals.xhrSend;
67
+ XMLHttpRequest.prototype.setRequestHeader = state.originals.xhrSetRequestHeader;
68
+ }
69
+ if (state.originals.EventSource) globalThis.EventSource = state.originals.EventSource;
70
+ if (state.originals.WebSocket) globalThis.WebSocket = state.originals.WebSocket;
71
+ state.originals.performanceObserver?.disconnect();
72
+ state.originals.performanceObserver = void 0;
73
+ state.installed = false;
74
+ }
75
+ function installConsoleCollector(state) {
76
+ for (const level of consoleLevels) {
77
+ const original = console[level]?.bind(console);
78
+ if (!original) continue;
79
+ state.originals.console[level] = original;
80
+ console[level] = ((...args) => {
81
+ appendConsole(state, level, args);
82
+ original(...args);
83
+ });
84
+ }
85
+ }
86
+ function installFetchCollector(state) {
87
+ if (typeof globalThis.fetch !== "function") return;
88
+ const originalFetch = globalThis.fetch.bind(globalThis);
89
+ state.originals.fetch = globalThis.fetch;
90
+ globalThis.fetch = (async (input, init) => {
91
+ const connectionId = nextConnectionId(state, "fetch");
92
+ const startedAtMs = Date.now();
93
+ const requestInfo = normalizeFetchRequest(input, init, state.options);
94
+ appendNetwork(state, {
95
+ connectionId,
96
+ kind: "fetch",
97
+ phase: "request",
98
+ method: requestInfo.method,
99
+ url: requestInfo.url,
100
+ requestHeaders: requestInfo.headers,
101
+ payload: requestInfo.body,
102
+ sizeBytes: requestInfo.bodySizeBytes,
103
+ truncated: requestInfo.bodyTruncated
104
+ });
105
+ try {
106
+ const response = await originalFetch(input, init);
107
+ const durationMs = Date.now() - startedAtMs;
108
+ const responseHeaders = state.options.captureHeaders ? headersToRecord(response.headers, state.options) : void 0;
109
+ appendNetwork(state, {
110
+ connectionId,
111
+ kind: "fetch",
112
+ phase: "response",
113
+ method: requestInfo.method,
114
+ url: response.url || requestInfo.url,
115
+ status: response.status,
116
+ statusText: response.statusText,
117
+ durationMs,
118
+ responseHeaders
119
+ });
120
+ collectFetchResponseBody(state, connectionId, requestInfo.method, response.url || requestInfo.url, response);
121
+ return response;
122
+ } catch (error) {
123
+ appendNetwork(state, {
124
+ connectionId,
125
+ kind: "fetch",
126
+ phase: "error",
127
+ method: requestInfo.method,
128
+ url: requestInfo.url,
129
+ durationMs: Date.now() - startedAtMs,
130
+ error: errorToString(error)
131
+ });
132
+ throw error;
133
+ }
134
+ });
135
+ }
136
+ function installXhrCollector(state) {
137
+ if (typeof globalThis.XMLHttpRequest !== "function") return;
138
+ const metadata = /* @__PURE__ */ new WeakMap();
139
+ const originalOpen = XMLHttpRequest.prototype.open;
140
+ const originalSend = XMLHttpRequest.prototype.send;
141
+ const originalSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
142
+ state.originals.XMLHttpRequest = globalThis.XMLHttpRequest;
143
+ state.originals.xhrOpen = originalOpen;
144
+ state.originals.xhrSend = originalSend;
145
+ state.originals.xhrSetRequestHeader = originalSetRequestHeader;
146
+ XMLHttpRequest.prototype.open = function open(method, url, async, username, password) {
147
+ metadata.set(this, {
148
+ id: nextConnectionId(state, "xhr"),
149
+ method: method.toUpperCase(),
150
+ url: String(url),
151
+ requestHeaders: {},
152
+ responseLineCount: 0,
153
+ lastResponseLength: 0,
154
+ pendingResponseLine: ""
155
+ });
156
+ return originalOpen.call(this, method, url, async ?? true, username, password);
157
+ };
158
+ XMLHttpRequest.prototype.setRequestHeader = function setRequestHeader(name, value) {
159
+ const meta = metadata.get(this);
160
+ if (meta) meta.requestHeaders[name] = value;
161
+ return originalSetRequestHeader.call(this, name, value);
162
+ };
163
+ XMLHttpRequest.prototype.send = function send(body) {
164
+ const meta = metadata.get(this);
165
+ if (meta) {
166
+ meta.startedAtMs = Date.now();
167
+ const bodyPreview = state.options.captureRequestBody ? serializeBodyPreview(body, state.options) : void 0;
168
+ appendNetwork(state, {
169
+ connectionId: meta.id,
170
+ kind: "xhr",
171
+ phase: "request",
172
+ method: meta.method,
173
+ url: meta.url,
174
+ requestHeaders: state.options.captureHeaders ? redactHeaderRecord(meta.requestHeaders, state.options) : void 0,
175
+ payload: bodyPreview?.payload,
176
+ sizeBytes: bodyPreview?.sizeBytes,
177
+ truncated: bodyPreview?.truncated
178
+ });
179
+ this.addEventListener("progress", () => {
180
+ collectXhrResponseLines(state, this, meta);
181
+ });
182
+ this.addEventListener("readystatechange", () => {
183
+ collectXhrResponseLines(state, this, meta);
184
+ });
185
+ this.addEventListener("loadend", () => {
186
+ collectXhrResponseLines(state, this, meta, true);
187
+ appendNetwork(state, {
188
+ connectionId: meta.id,
189
+ kind: "xhr",
190
+ phase: "response",
191
+ method: meta.method,
192
+ url: meta.url,
193
+ status: this.status,
194
+ statusText: this.statusText,
195
+ durationMs: meta.startedAtMs ? Date.now() - meta.startedAtMs : void 0,
196
+ responseHeaders: state.options.captureHeaders ? parseRawHeaders(this.getAllResponseHeaders(), state.options) : void 0,
197
+ payload: getXhrResponsePreview(this, state.options)
198
+ });
199
+ });
200
+ this.addEventListener("error", () => {
201
+ appendNetwork(state, {
202
+ connectionId: meta.id,
203
+ kind: "xhr",
204
+ phase: "error",
205
+ method: meta.method,
206
+ url: meta.url,
207
+ durationMs: meta.startedAtMs ? Date.now() - meta.startedAtMs : void 0
208
+ });
209
+ });
210
+ this.addEventListener("abort", () => {
211
+ appendNetwork(state, {
212
+ connectionId: meta.id,
213
+ kind: "xhr",
214
+ phase: "abort",
215
+ method: meta.method,
216
+ url: meta.url,
217
+ durationMs: meta.startedAtMs ? Date.now() - meta.startedAtMs : void 0
218
+ });
219
+ });
220
+ this.addEventListener("timeout", () => {
221
+ appendNetwork(state, {
222
+ connectionId: meta.id,
223
+ kind: "xhr",
224
+ phase: "timeout",
225
+ method: meta.method,
226
+ url: meta.url,
227
+ durationMs: meta.startedAtMs ? Date.now() - meta.startedAtMs : void 0
228
+ });
229
+ });
230
+ }
231
+ return originalSend.call(this, body ?? null);
232
+ };
233
+ }
234
+ function installEventSourceCollector(state) {
235
+ if (typeof globalThis.EventSource !== "function") return;
236
+ const OriginalEventSource = globalThis.EventSource;
237
+ state.originals.EventSource = OriginalEventSource;
238
+ const InstrumentedEventSource = function EventSource(url, eventSourceInitDict) {
239
+ const connectionId = nextConnectionId(state, "eventsource");
240
+ const source = new OriginalEventSource(url, eventSourceInitDict);
241
+ const sourceUrl = String(url);
242
+ let lineNumber = 0;
243
+ const listenerRecords = [];
244
+ const appendEventSourceMessage = (eventName, event) => {
245
+ appendLines(state, {
246
+ connectionId,
247
+ kind: "eventsource",
248
+ method: "GET",
249
+ url: sourceUrl,
250
+ direction: "incoming",
251
+ eventName,
252
+ text: event.data,
253
+ nextLineNumber: () => {
254
+ lineNumber += 1;
255
+ return lineNumber;
256
+ }
257
+ });
258
+ };
259
+ appendNetwork(state, {
260
+ connectionId,
261
+ kind: "eventsource",
262
+ phase: "request",
263
+ method: "GET",
264
+ url: sourceUrl,
265
+ payload: eventSourceInitDict ? serializeValue(eventSourceInitDict, state.options) : void 0
266
+ });
267
+ source.addEventListener("open", () => {
268
+ appendNetwork(state, {
269
+ connectionId,
270
+ kind: "eventsource",
271
+ phase: "open",
272
+ method: "GET",
273
+ url: sourceUrl
274
+ });
275
+ });
276
+ source.addEventListener("message", (event) => {
277
+ appendEventSourceMessage("message", event);
278
+ });
279
+ source.addEventListener("error", () => {
280
+ appendNetwork(state, {
281
+ connectionId,
282
+ kind: "eventsource",
283
+ phase: "error",
284
+ method: "GET",
285
+ url: sourceUrl
286
+ });
287
+ });
288
+ const originalAddEventListener = source.addEventListener.bind(source);
289
+ const originalRemoveEventListener = source.removeEventListener.bind(source);
290
+ source.addEventListener = ((type, listener, options) => {
291
+ if (!listener) return;
292
+ if (type === "open" || type === "message" || type === "error") return originalAddEventListener(type, listener, options);
293
+ const wrappedListener = (event) => {
294
+ if (event instanceof MessageEvent) appendEventSourceMessage(type, event);
295
+ if (typeof listener === "function") return listener.call(source, event);
296
+ return listener.handleEvent(event);
297
+ };
298
+ listenerRecords.push({
299
+ type,
300
+ listener,
301
+ options,
302
+ wrappedListener
303
+ });
304
+ return originalAddEventListener(type, wrappedListener, options);
305
+ });
306
+ source.removeEventListener = ((type, listener, options) => {
307
+ if (!listener) return;
308
+ const recordIndex = listenerRecords.findIndex((record) => record.type === type && record.listener === listener);
309
+ return originalRemoveEventListener(type, (recordIndex >= 0 ? listenerRecords.splice(recordIndex, 1)[0] : void 0)?.wrappedListener ?? listener, options);
310
+ });
311
+ return source;
312
+ };
313
+ InstrumentedEventSource.prototype = OriginalEventSource.prototype;
314
+ Object.defineProperty(InstrumentedEventSource, "CONNECTING", { value: OriginalEventSource.CONNECTING });
315
+ Object.defineProperty(InstrumentedEventSource, "OPEN", { value: OriginalEventSource.OPEN });
316
+ Object.defineProperty(InstrumentedEventSource, "CLOSED", { value: OriginalEventSource.CLOSED });
317
+ globalThis.EventSource = InstrumentedEventSource;
318
+ }
319
+ function installWebSocketCollector(state) {
320
+ if (typeof globalThis.WebSocket !== "function") return;
321
+ const OriginalWebSocket = globalThis.WebSocket;
322
+ state.originals.WebSocket = OriginalWebSocket;
323
+ const InstrumentedWebSocket = function WebSocket(url, protocols) {
324
+ const connectionId = nextConnectionId(state, "websocket");
325
+ const socket = protocols === void 0 ? new OriginalWebSocket(url) : new OriginalWebSocket(url, protocols);
326
+ const socketUrl = String(url);
327
+ let lineNumber = 0;
328
+ let userOnOpen = null;
329
+ let userOnMessage = null;
330
+ let userOnError = null;
331
+ let userOnClose = null;
332
+ appendNetwork(state, {
333
+ connectionId,
334
+ kind: "websocket",
335
+ phase: "request",
336
+ method: "GET",
337
+ url: socketUrl,
338
+ payload: protocols ? serializeValue({ protocols }, state.options) : void 0
339
+ });
340
+ socket.addEventListener("open", () => {
341
+ appendNetwork(state, {
342
+ connectionId,
343
+ kind: "websocket",
344
+ phase: "open",
345
+ method: "GET",
346
+ url: socketUrl
347
+ });
348
+ });
349
+ socket.addEventListener("message", (event) => {
350
+ appendWebSocketPayload(state, {
351
+ connectionId,
352
+ url: socketUrl,
353
+ direction: "incoming",
354
+ data: event.data,
355
+ nextLineNumber: () => {
356
+ lineNumber += 1;
357
+ return lineNumber;
358
+ }
359
+ });
360
+ });
361
+ socket.addEventListener("error", () => {
362
+ appendNetwork(state, {
363
+ connectionId,
364
+ kind: "websocket",
365
+ phase: "error",
366
+ method: "GET",
367
+ url: socketUrl
368
+ });
369
+ });
370
+ socket.addEventListener("close", (event) => {
371
+ appendNetwork(state, {
372
+ connectionId,
373
+ kind: "websocket",
374
+ phase: "close",
375
+ method: "GET",
376
+ url: socketUrl,
377
+ close: {
378
+ code: event.code,
379
+ reason: event.reason,
380
+ wasClean: event.wasClean
381
+ }
382
+ });
383
+ });
384
+ Object.defineProperties(socket, {
385
+ onopen: {
386
+ configurable: true,
387
+ enumerable: true,
388
+ get: () => userOnOpen,
389
+ set: (handler) => {
390
+ userOnOpen = typeof handler === "function" ? handler : null;
391
+ }
392
+ },
393
+ onmessage: {
394
+ configurable: true,
395
+ enumerable: true,
396
+ get: () => userOnMessage,
397
+ set: (handler) => {
398
+ userOnMessage = typeof handler === "function" ? handler : null;
399
+ }
400
+ },
401
+ onerror: {
402
+ configurable: true,
403
+ enumerable: true,
404
+ get: () => userOnError,
405
+ set: (handler) => {
406
+ userOnError = typeof handler === "function" ? handler : null;
407
+ }
408
+ },
409
+ onclose: {
410
+ configurable: true,
411
+ enumerable: true,
412
+ get: () => userOnClose,
413
+ set: (handler) => {
414
+ userOnClose = typeof handler === "function" ? handler : null;
415
+ }
416
+ }
417
+ });
418
+ socket.addEventListener("open", (event) => {
419
+ userOnOpen?.call(socket, event);
420
+ });
421
+ socket.addEventListener("message", (event) => {
422
+ userOnMessage?.call(socket, event);
423
+ });
424
+ socket.addEventListener("error", (event) => {
425
+ userOnError?.call(socket, event);
426
+ });
427
+ socket.addEventListener("close", (event) => {
428
+ userOnClose?.call(socket, event);
429
+ });
430
+ const originalSend = socket.send.bind(socket);
431
+ socket.send = (data) => {
432
+ appendWebSocketPayload(state, {
433
+ connectionId,
434
+ url: socketUrl,
435
+ direction: "outgoing",
436
+ data,
437
+ nextLineNumber: () => {
438
+ lineNumber += 1;
439
+ return lineNumber;
440
+ }
441
+ });
442
+ return originalSend(data);
443
+ };
444
+ return socket;
445
+ };
446
+ InstrumentedWebSocket.prototype = OriginalWebSocket.prototype;
447
+ Object.defineProperty(InstrumentedWebSocket, "CONNECTING", { value: OriginalWebSocket.CONNECTING });
448
+ Object.defineProperty(InstrumentedWebSocket, "OPEN", { value: OriginalWebSocket.OPEN });
449
+ Object.defineProperty(InstrumentedWebSocket, "CLOSING", { value: OriginalWebSocket.CLOSING });
450
+ Object.defineProperty(InstrumentedWebSocket, "CLOSED", { value: OriginalWebSocket.CLOSED });
451
+ globalThis.WebSocket = InstrumentedWebSocket;
452
+ }
453
+ function installPerformanceCollector(state) {
454
+ if (typeof performance === "undefined" || typeof PerformanceObserver === "undefined") return;
455
+ collectPerformanceResources(state, performance.getEntriesByType("resource"));
456
+ const observer = new PerformanceObserver((list) => {
457
+ collectPerformanceResources(state, list.getEntries());
458
+ });
459
+ try {
460
+ observer.observe({
461
+ type: "resource",
462
+ buffered: true
463
+ });
464
+ state.originals.performanceObserver = observer;
465
+ } catch {
466
+ observer.disconnect();
467
+ }
468
+ }
469
+ function collectPerformanceResources(state, entries) {
470
+ for (const entry of entries) {
471
+ if (!(entry instanceof PerformanceResourceTiming)) continue;
472
+ const key = `${entry.name}:${entry.startTime}:${entry.duration}`;
473
+ if (state.performanceResourceKeys.has(key)) continue;
474
+ state.performanceResourceKeys.add(key);
475
+ const initiatorType = entry.initiatorType || "resource";
476
+ const kind = resourceKindFromInitiator(initiatorType, entry.name);
477
+ appendNetwork(state, {
478
+ connectionId: nextConnectionId(state, "resource"),
479
+ kind,
480
+ phase: "response",
481
+ method: "GET",
482
+ url: entry.name,
483
+ status: entry.responseStatus || void 0,
484
+ durationMs: Math.round(entry.duration),
485
+ source: "performance",
486
+ initiatorType,
487
+ sizeBytes: Math.round(entry.encodedBodySize || entry.transferSize || entry.decodedBodySize || 0),
488
+ payload: serializeValue({
489
+ startTimeMs: Math.round(entry.startTime),
490
+ responseEndMs: Math.round(entry.responseEnd),
491
+ transferSize: entry.transferSize,
492
+ encodedBodySize: entry.encodedBodySize,
493
+ decodedBodySize: entry.decodedBodySize
494
+ }, state.options)
495
+ });
496
+ }
497
+ }
498
+ function resourceKindFromInitiator(initiatorType, url) {
499
+ if (initiatorType === "fetch") return "fetch";
500
+ if (initiatorType === "xmlhttprequest") return "xhr";
501
+ if (initiatorType === "eventsource") return "eventsource";
502
+ if (initiatorType === "websocket" || url.startsWith("ws")) return "websocket";
503
+ return "resource";
504
+ }
505
+ function collectFetchResponseBody(state, connectionId, method, url, response) {
506
+ if (!state.options.captureResponseBody || !response.body) return;
507
+ const clone = response.clone();
508
+ if ((clone.headers.get("content-type") ?? "").includes("text/event-stream")) {
509
+ collectReadableStreamLines(state, {
510
+ connectionId,
511
+ kind: "fetch",
512
+ method,
513
+ url,
514
+ response: clone
515
+ });
516
+ return;
517
+ }
518
+ clone.text().then((text) => {
519
+ appendNetwork(state, {
520
+ connectionId,
521
+ kind: "fetch",
522
+ phase: "line",
523
+ method,
524
+ url,
525
+ direction: "incoming",
526
+ lineNumber: 1,
527
+ payload: truncateText(text, state.options.maxPayloadBytes),
528
+ sizeBytes: byteLength(text),
529
+ truncated: byteLength(text) > state.options.maxPayloadBytes
530
+ });
531
+ }, () => {});
532
+ }
533
+ async function collectReadableStreamLines(state, details) {
534
+ const reader = details.response.body?.getReader();
535
+ if (!reader) return;
536
+ const decoder = new TextDecoder();
537
+ let buffered = "";
538
+ let lineNumber = 0;
539
+ try {
540
+ while (true) {
541
+ const { value, done } = await reader.read();
542
+ if (done) break;
543
+ buffered += decoder.decode(value, { stream: true });
544
+ const parts = buffered.split(/\r?\n/);
545
+ buffered = parts.pop() ?? "";
546
+ for (const line of parts) {
547
+ lineNumber += 1;
548
+ appendLine(state, {
549
+ connectionId: details.connectionId,
550
+ kind: details.kind,
551
+ method: details.method,
552
+ url: details.url,
553
+ direction: "incoming",
554
+ lineNumber,
555
+ text: line
556
+ });
557
+ }
558
+ }
559
+ buffered += decoder.decode();
560
+ if (buffered) {
561
+ lineNumber += 1;
562
+ appendLine(state, {
563
+ connectionId: details.connectionId,
564
+ kind: details.kind,
565
+ method: details.method,
566
+ url: details.url,
567
+ direction: "incoming",
568
+ lineNumber,
569
+ text: buffered
570
+ });
571
+ }
572
+ } catch (error) {
573
+ appendNetwork(state, {
574
+ connectionId: details.connectionId,
575
+ kind: details.kind,
576
+ phase: "error",
577
+ method: details.method,
578
+ url: details.url,
579
+ error: errorToString(error)
580
+ });
581
+ } finally {
582
+ reader.releaseLock();
583
+ }
584
+ }
585
+ function collectXhrResponseLines(state, xhr, meta, flush = false) {
586
+ if (!state.options.captureResponseBody) return;
587
+ if (!(xhr.getResponseHeader("content-type") ?? "").includes("text/event-stream")) return;
588
+ if (xhr.responseType && xhr.responseType !== "text") return;
589
+ const text = xhr.responseText;
590
+ if (text.length <= meta.lastResponseLength) return;
591
+ const nextChunk = text.slice(meta.lastResponseLength);
592
+ meta.lastResponseLength = text.length;
593
+ const parts = `${meta.pendingResponseLine}${nextChunk}`.split(/\r?\n/);
594
+ meta.pendingResponseLine = parts.pop() ?? "";
595
+ for (const line of parts) {
596
+ if (!line) continue;
597
+ appendLine(state, {
598
+ connectionId: meta.id,
599
+ kind: "xhr",
600
+ method: meta.method,
601
+ url: meta.url,
602
+ direction: "incoming",
603
+ lineNumber: nextXhrLineNumber(meta),
604
+ text: line
605
+ });
606
+ }
607
+ if (flush && meta.pendingResponseLine) {
608
+ const line = meta.pendingResponseLine;
609
+ meta.pendingResponseLine = "";
610
+ appendLine(state, {
611
+ connectionId: meta.id,
612
+ kind: "xhr",
613
+ method: meta.method,
614
+ url: meta.url,
615
+ direction: "incoming",
616
+ lineNumber: nextXhrLineNumber(meta),
617
+ text: line
618
+ });
619
+ }
620
+ }
621
+ function appendWebSocketPayload(state, details) {
622
+ if (typeof details.data === "string") {
623
+ appendLines(state, {
624
+ connectionId: details.connectionId,
625
+ kind: "websocket",
626
+ method: "GET",
627
+ url: details.url,
628
+ direction: details.direction,
629
+ text: details.data,
630
+ nextLineNumber: details.nextLineNumber
631
+ });
632
+ return;
633
+ }
634
+ const payload = serializeBodyPreview(details.data, state.options) ?? { payload: null };
635
+ appendNetwork(state, {
636
+ connectionId: details.connectionId,
637
+ kind: "websocket",
638
+ phase: "message",
639
+ method: "GET",
640
+ url: details.url,
641
+ direction: details.direction,
642
+ lineNumber: details.nextLineNumber(),
643
+ payload: payload.payload,
644
+ sizeBytes: payload.sizeBytes,
645
+ truncated: payload.truncated
646
+ });
647
+ }
648
+ function appendLines(state, details) {
649
+ for (const line of details.text.split(/\r?\n/)) appendLine(state, {
650
+ ...details,
651
+ lineNumber: details.nextLineNumber(),
652
+ text: line
653
+ });
654
+ }
655
+ function appendLine(state, details) {
656
+ appendNetwork(state, {
657
+ connectionId: details.connectionId,
658
+ kind: details.kind,
659
+ phase: "line",
660
+ method: details.method,
661
+ url: details.url,
662
+ direction: details.direction,
663
+ lineNumber: details.lineNumber,
664
+ eventName: details.eventName,
665
+ payload: truncateText(details.text, state.options.maxLineBytes),
666
+ sizeBytes: byteLength(details.text),
667
+ truncated: byteLength(details.text) > state.options.maxLineBytes
668
+ });
669
+ }
670
+ function nextXhrLineNumber(meta) {
671
+ meta.responseLineCount += 1;
672
+ return meta.responseLineCount;
673
+ }
674
+ function normalizeFetchRequest(input, init, options) {
675
+ const request = typeof Request !== "undefined" && input instanceof Request ? input : void 0;
676
+ const method = (init?.method ?? request?.method ?? "GET").toUpperCase();
677
+ const url = typeof Request !== "undefined" && input instanceof Request ? input.url : typeof URL !== "undefined" && input instanceof URL ? input.href : String(input);
678
+ const headers = options.captureHeaders ? mergeHeaders(request?.headers, init?.headers, options) : void 0;
679
+ const bodyPreview = options.captureRequestBody ? serializeBodyPreview(init?.body, options) : void 0;
680
+ return {
681
+ method,
682
+ url,
683
+ headers,
684
+ body: bodyPreview?.payload,
685
+ bodySizeBytes: bodyPreview?.sizeBytes,
686
+ bodyTruncated: bodyPreview?.truncated
687
+ };
688
+ }
689
+ function getXhrResponsePreview(xhr, options) {
690
+ if (!options.captureResponseBody) return;
691
+ if (xhr.responseType && xhr.responseType !== "text") return serializeValue({
692
+ responseType: xhr.responseType,
693
+ note: "Non-text XHR response body was not captured."
694
+ }, options);
695
+ return truncateText(xhr.responseText, options.maxPayloadBytes);
696
+ }
697
+ function serializeBodyPreview(body, options) {
698
+ if (body == null) return;
699
+ if (typeof body === "string") return {
700
+ payload: truncateText(body, options.maxPayloadBytes),
701
+ sizeBytes: byteLength(body),
702
+ truncated: byteLength(body) > options.maxPayloadBytes
703
+ };
704
+ if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) {
705
+ const text = body.toString();
706
+ return {
707
+ payload: truncateText(text, options.maxPayloadBytes),
708
+ sizeBytes: byteLength(text),
709
+ truncated: byteLength(text) > options.maxPayloadBytes
710
+ };
711
+ }
712
+ if (typeof Blob !== "undefined" && body instanceof Blob) return {
713
+ payload: {
714
+ type: "Blob",
715
+ mimeType: body.type,
716
+ size: body.size
717
+ },
718
+ sizeBytes: body.size,
719
+ truncated: body.size > options.maxPayloadBytes
720
+ };
721
+ if (typeof FormData !== "undefined" && body instanceof FormData) return { payload: serializeFormData(body, options) };
722
+ if (typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer) return {
723
+ payload: {
724
+ type: "ArrayBuffer",
725
+ byteLength: body.byteLength
726
+ },
727
+ sizeBytes: body.byteLength,
728
+ truncated: body.byteLength > options.maxPayloadBytes
729
+ };
730
+ if (ArrayBuffer.isView(body)) return {
731
+ payload: {
732
+ type: body.constructor.name,
733
+ byteLength: body.byteLength
734
+ },
735
+ sizeBytes: body.byteLength,
736
+ truncated: body.byteLength > options.maxPayloadBytes
737
+ };
738
+ return { payload: serializeValue(body, options) };
739
+ }
740
+ function serializeFormData(formData, options) {
741
+ const entries = [];
742
+ for (const [name, value] of formData.entries()) if (typeof value === "string") entries.push({
743
+ name,
744
+ value: truncateText(value, options.maxLineBytes)
745
+ });
746
+ else entries.push({
747
+ name,
748
+ value: serializeFormDataFileValue(value)
749
+ });
750
+ return {
751
+ type: "FormData",
752
+ entries
753
+ };
754
+ }
755
+ function serializeFormDataFileValue(value) {
756
+ return {
757
+ type: typeof File !== "undefined" && value instanceof File ? "File" : "Blob",
758
+ fileName: "name" in value && typeof value.name === "string" ? value.name : null,
759
+ mimeType: value.type,
760
+ size: value.size
761
+ };
762
+ }
763
+ function serializeValue(value, options, seen = /* @__PURE__ */ new WeakSet()) {
764
+ if (value == null) return null;
765
+ if (typeof value === "string") return truncateText(value, options.maxPayloadBytes);
766
+ if (typeof value === "number" || typeof value === "boolean") return value;
767
+ if (typeof value === "bigint") return `${value.toString()}n`;
768
+ if (typeof value === "symbol") return value.toString();
769
+ if (typeof value === "function") return `[Function ${value.name || "anonymous"}]`;
770
+ if (value instanceof Error) return {
771
+ name: value.name,
772
+ message: value.message,
773
+ stack: value.stack ?? null
774
+ };
775
+ if (value instanceof Date) return value.toISOString();
776
+ if (value instanceof RegExp) return value.toString();
777
+ if (typeof Blob !== "undefined" && value instanceof Blob) return {
778
+ type: "Blob",
779
+ mimeType: value.type,
780
+ size: value.size
781
+ };
782
+ if (typeof Element !== "undefined" && value instanceof Element) return {
783
+ type: "Element",
784
+ tagName: value.tagName.toLowerCase(),
785
+ id: value.id || null,
786
+ className: typeof value.className === "string" ? truncateText(value.className, options.maxLineBytes) : null
787
+ };
788
+ if (typeof value !== "object") return String(value);
789
+ if (seen.has(value)) return "[Circular]";
790
+ seen.add(value);
791
+ if (Array.isArray(value)) return value.slice(0, 100).map((item) => serializeValue(item, options, seen));
792
+ const output = {};
793
+ let count = 0;
794
+ for (const key of Object.keys(value)) {
795
+ if (count >= 100) {
796
+ output.__truncatedKeys = "Only the first 100 keys were captured.";
797
+ break;
798
+ }
799
+ count += 1;
800
+ try {
801
+ output[key] = serializeValue(value[key], options, seen);
802
+ } catch (error) {
803
+ output[key] = `[Unserializable: ${errorToString(error)}]`;
804
+ }
805
+ }
806
+ return output;
807
+ }
808
+ function appendConsole(state, level, args) {
809
+ state.consoleBuffer.push({
810
+ id: state.nextConsoleId,
811
+ at: (/* @__PURE__ */ new Date()).toISOString(),
812
+ elapsedMs: Date.now() - state.startedAtMs,
813
+ level,
814
+ args: args.map((arg) => serializeValue(arg, state.options)),
815
+ stack: level === "trace" ? (/* @__PURE__ */ new Error()).stack : void 0
816
+ });
817
+ state.nextConsoleId += 1;
818
+ }
819
+ function appendNetwork(state, entry) {
820
+ state.networkBuffer.push({
821
+ ...entry,
822
+ id: state.nextNetworkId,
823
+ at: (/* @__PURE__ */ new Date()).toISOString(),
824
+ elapsedMs: Date.now() - state.startedAtMs
825
+ });
826
+ state.nextNetworkId += 1;
827
+ }
828
+ function createHandle(state) {
829
+ return {
830
+ exportLog: () => createLogPackage(state),
831
+ exportHar: () => createHarPackage(state),
832
+ clear: () => {
833
+ state.consoleBuffer.clear();
834
+ state.networkBuffer.clear();
835
+ },
836
+ stop: stopCohubDebugger
837
+ };
838
+ }
839
+ function createLogPackage(state) {
840
+ return {
841
+ version: 1,
842
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
843
+ startedAt: state.startedAt,
844
+ userAgent: typeof navigator === "undefined" ? void 0 : navigator.userAgent,
845
+ url: typeof location === "undefined" ? void 0 : location.href,
846
+ options: state.options,
847
+ console: state.consoleBuffer.toArray(),
848
+ network: state.networkBuffer.toArray(),
849
+ dropped: {
850
+ console: state.consoleBuffer.getDropped(),
851
+ network: state.networkBuffer.getDropped()
852
+ }
853
+ };
854
+ }
855
+ function createHarPackage(state) {
856
+ const logPackage = createLogPackage(state);
857
+ const pageId = "page_1";
858
+ const entriesByConnection = /* @__PURE__ */ new Map();
859
+ for (const entry of logPackage.network) {
860
+ const entries = entriesByConnection.get(entry.connectionId) ?? [];
861
+ entries.push(entry);
862
+ entriesByConnection.set(entry.connectionId, entries);
863
+ }
864
+ return { log: {
865
+ version: "1.2",
866
+ creator: {
867
+ name: "@neta-art/cohub/debugger",
868
+ version: "1"
869
+ },
870
+ browser: logPackage.userAgent ? {
871
+ name: "Chrome",
872
+ version: logPackage.userAgent
873
+ } : void 0,
874
+ pages: [{
875
+ startedDateTime: logPackage.startedAt,
876
+ id: pageId,
877
+ title: logPackage.url ?? "Cohub",
878
+ pageTimings: {
879
+ onContentLoad: -1,
880
+ onLoad: -1
881
+ }
882
+ }],
883
+ entries: [...entriesByConnection.values()].map((entries) => createHarEntry(pageId, entries)),
884
+ _cohub: {
885
+ exportedAt: logPackage.exportedAt,
886
+ startedAt: logPackage.startedAt,
887
+ dropped: logPackage.dropped
888
+ }
889
+ } };
890
+ }
891
+ function createHarEntry(pageId, entries) {
892
+ const sortedEntries = [...entries].sort((a, b) => a.id - b.id);
893
+ const firstEntry = sortedEntries[0];
894
+ if (!firstEntry) throw new Error("Cannot create a HAR entry without network entries.");
895
+ const requestEntry = sortedEntries.find((entry) => entry.phase === "request") ?? firstEntry;
896
+ const responseEntry = [...sortedEntries].reverse().find((entry) => entry.phase === "response") ?? [...sortedEntries].reverse().find((entry) => entry.phase === "close") ?? [...sortedEntries].reverse().find((entry) => entry.phase === "error") ?? requestEntry;
897
+ const messageEntries = sortedEntries.filter((entry) => entry.phase === "line" || entry.phase === "message");
898
+ const requestBodyEntry = sortedEntries.find((entry) => entry.phase === "request" && entry.payload !== void 0);
899
+ const responseBodyEntry = [...messageEntries].reverse().find((entry) => entry.direction === "incoming");
900
+ const durationMs = Math.max(responseEntry.durationMs ?? responseEntry.elapsedMs - requestEntry.elapsedMs, 0);
901
+ const responseText = responseEntry.kind === "websocket" ? void 0 : messageEntries.filter((entry) => entry.direction !== "outgoing").map((entry) => payloadToText(entry.payload)).filter(Boolean).join("\n");
902
+ return {
903
+ pageref: pageId,
904
+ startedDateTime: requestEntry.at,
905
+ time: durationMs,
906
+ request: {
907
+ method: requestEntry.method ?? "GET",
908
+ url: requestEntry.url,
909
+ httpVersion: "HTTP/1.1",
910
+ cookies: [],
911
+ headers: headersRecordToHarPairs(requestEntry.requestHeaders),
912
+ queryString: queryStringToHarPairs(requestEntry.url),
913
+ headersSize: -1,
914
+ bodySize: requestBodyEntry?.sizeBytes ?? -1,
915
+ postData: requestBodyEntry?.payload === void 0 ? void 0 : {
916
+ mimeType: guessMimeType(requestEntry.requestHeaders),
917
+ text: payloadToText(requestBodyEntry.payload),
918
+ params: []
919
+ }
920
+ },
921
+ response: {
922
+ status: responseEntry.status ?? (responseEntry.kind === "websocket" ? 101 : 0),
923
+ statusText: responseEntry.statusText ?? responseStatusText(responseEntry),
924
+ httpVersion: "HTTP/1.1",
925
+ cookies: [],
926
+ headers: headersRecordToHarPairs(responseEntry.responseHeaders),
927
+ content: {
928
+ size: responseBodyEntry?.sizeBytes ?? responseEntry.sizeBytes ?? -1,
929
+ mimeType: guessMimeType(responseEntry.responseHeaders),
930
+ text: responseText || void 0
931
+ },
932
+ redirectURL: "",
933
+ headersSize: -1,
934
+ bodySize: responseBodyEntry?.sizeBytes ?? responseEntry.sizeBytes ?? -1
935
+ },
936
+ cache: {},
937
+ timings: {
938
+ blocked: -1,
939
+ dns: -1,
940
+ connect: -1,
941
+ send: 0,
942
+ wait: durationMs,
943
+ receive: 0,
944
+ ssl: -1
945
+ },
946
+ serverIPAddress: "",
947
+ connection: requestEntry.connectionId,
948
+ _resourceType: harResourceType(firstEntry),
949
+ _cohubConnectionId: requestEntry.connectionId,
950
+ _cohubKind: firstEntry.kind,
951
+ _cohubSource: firstEntry.source,
952
+ _cohubEntries: sortedEntries,
953
+ _cohubMessages: createCohubMessages(messageEntries),
954
+ _webSocketMessages: firstEntry.kind === "websocket" ? createWebSocketMessages(messageEntries) : void 0
955
+ };
956
+ }
957
+ function headersRecordToHarPairs(headers) {
958
+ return Object.entries(headers ?? {}).map(([name, value]) => ({
959
+ name,
960
+ value
961
+ }));
962
+ }
963
+ function queryStringToHarPairs(url) {
964
+ try {
965
+ return [...new URL(url, "http://localhost").searchParams.entries()].map(([name, value]) => ({
966
+ name,
967
+ value
968
+ }));
969
+ } catch {
970
+ return [];
971
+ }
972
+ }
973
+ function createCohubMessages(entries) {
974
+ return entries.map((entry) => ({
975
+ time: entry.at,
976
+ direction: entry.direction,
977
+ lineNumber: entry.lineNumber,
978
+ eventName: entry.eventName,
979
+ data: payloadToText(entry.payload)
980
+ }));
981
+ }
982
+ function createWebSocketMessages(entries) {
983
+ return entries.map((entry) => ({
984
+ type: entry.direction === "outgoing" ? "send" : "receive",
985
+ time: Date.parse(entry.at) / 1e3,
986
+ opcode: typeof entry.payload === "string" ? 1 : 2,
987
+ data: payloadToText(entry.payload)
988
+ }));
989
+ }
990
+ function responseStatusText(entry) {
991
+ if (entry.close) return `WebSocket closed ${entry.close.code}`;
992
+ if (entry.error) return entry.error;
993
+ return entry.status ? String(entry.status) : "";
994
+ }
995
+ function harResourceType(entry) {
996
+ if (entry.kind === "websocket") return "websocket";
997
+ if (entry.kind === "fetch") return "fetch";
998
+ if (entry.kind === "xhr") return "xhr";
999
+ if (entry.kind === "eventsource") return "eventsource";
1000
+ return entry.initiatorType ?? "other";
1001
+ }
1002
+ function guessMimeType(headers) {
1003
+ return Object.entries(headers ?? {}).find(([name]) => name.toLowerCase() === "content-type")?.[1] ?? "text/plain";
1004
+ }
1005
+ function payloadToText(payload) {
1006
+ if (payload === void 0) return "";
1007
+ if (typeof payload === "string") return payload;
1008
+ return JSON.stringify(payload);
1009
+ }
1010
+ function createRingBuffer(capacity) {
1011
+ const items = [];
1012
+ let dropped = 0;
1013
+ return {
1014
+ push: (entry) => {
1015
+ if (items.length >= capacity) {
1016
+ items.shift();
1017
+ dropped += 1;
1018
+ }
1019
+ items.push(entry);
1020
+ },
1021
+ clear: () => {
1022
+ items.length = 0;
1023
+ dropped = 0;
1024
+ },
1025
+ toArray: () => [...items],
1026
+ getDropped: () => dropped
1027
+ };
1028
+ }
1029
+ function getOrCreateState(options = {}) {
1030
+ const globalRecord = globalThis;
1031
+ if (globalRecord[globalKey]) return globalRecord[globalKey];
1032
+ const normalizedOptions = normalizeOptions(options);
1033
+ const startedAtMs = Date.now();
1034
+ const state = {
1035
+ options: normalizedOptions,
1036
+ startedAtMs,
1037
+ startedAt: new Date(startedAtMs).toISOString(),
1038
+ consoleBuffer: createRingBuffer(normalizedOptions.maxConsoleEntries),
1039
+ networkBuffer: createRingBuffer(normalizedOptions.maxNetworkEntries),
1040
+ performanceResourceKeys: /* @__PURE__ */ new Set(),
1041
+ nextConsoleId: 1,
1042
+ nextNetworkId: 1,
1043
+ nextConnectionId: 1,
1044
+ originals: { console: {} },
1045
+ installed: false
1046
+ };
1047
+ globalRecord[globalKey] = state;
1048
+ return state;
1049
+ }
1050
+ function getExistingState() {
1051
+ return globalThis[globalKey];
1052
+ }
1053
+ function normalizeOptions(options) {
1054
+ return {
1055
+ ...DEFAULT_OPTIONS,
1056
+ ...options,
1057
+ redactHeaders: options.redactHeaders ?? DEFAULT_OPTIONS.redactHeaders
1058
+ };
1059
+ }
1060
+ function nextConnectionId(state, kind) {
1061
+ const id = `${kind}-${state.nextConnectionId}`;
1062
+ state.nextConnectionId += 1;
1063
+ return id;
1064
+ }
1065
+ function headersToRecord(headers, options) {
1066
+ const record = {};
1067
+ headers.forEach((value, key) => {
1068
+ record[key] = shouldRedactHeader(key, options) ? "[redacted]" : value;
1069
+ });
1070
+ return record;
1071
+ }
1072
+ function mergeHeaders(base, override, options) {
1073
+ const merged = new Headers(base);
1074
+ if (override) new Headers(override).forEach((value, key) => {
1075
+ merged.set(key, value);
1076
+ });
1077
+ return headersToRecord(merged, options);
1078
+ }
1079
+ function redactHeaderRecord(headers, options) {
1080
+ const redacted = {};
1081
+ for (const [key, value] of Object.entries(headers)) redacted[key] = shouldRedactHeader(key, options) ? "[redacted]" : value;
1082
+ return redacted;
1083
+ }
1084
+ function parseRawHeaders(rawHeaders, options) {
1085
+ const record = {};
1086
+ for (const line of rawHeaders.trim().split(/\r?\n/)) {
1087
+ if (!line) continue;
1088
+ const separatorIndex = line.indexOf(":");
1089
+ if (separatorIndex < 0) continue;
1090
+ const key = line.slice(0, separatorIndex).trim();
1091
+ const value = line.slice(separatorIndex + 1).trim();
1092
+ record[key] = shouldRedactHeader(key, options) ? "[redacted]" : value;
1093
+ }
1094
+ return record;
1095
+ }
1096
+ function shouldRedactHeader(key, options) {
1097
+ const normalizedKey = key.toLowerCase();
1098
+ return options.redactHeaders.some((header) => header.toLowerCase() === normalizedKey);
1099
+ }
1100
+ function truncateText(text, maxBytes) {
1101
+ if (byteLength(text) <= maxBytes) return text;
1102
+ let result = "";
1103
+ let size = 0;
1104
+ for (const char of text) {
1105
+ const charSize = byteLength(char);
1106
+ if (size + charSize > maxBytes) break;
1107
+ result += char;
1108
+ size += charSize;
1109
+ }
1110
+ return `${result}\n[truncated at ${maxBytes} bytes]`;
1111
+ }
1112
+ function byteLength(text) {
1113
+ return textEncoder.encode(text).byteLength;
1114
+ }
1115
+ function errorToString(error) {
1116
+ if (error instanceof Error) return `${error.name}: ${error.message}`;
1117
+ return String(error);
1118
+ }
1119
+ //#endregion
1120
+ export { clearCohubDebugLog, exportCohubDebugHar, exportCohubDebugLog, startCohubDebugger, stopCohubDebugger };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub",
3
- "version": "1.10.0",
3
+ "version": "1.10.1",
4
4
  "description": "Cohub SDK for spaces, sessions, checkpoints, and realtime agent collaboration.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -36,6 +36,10 @@
36
36
  "./websocket": {
37
37
  "types": "./dist/websocket.d.ts",
38
38
  "import": "./dist/websocket.js"
39
+ },
40
+ "./debugger": {
41
+ "types": "./dist/debugger.d.ts",
42
+ "import": "./dist/debugger.js"
39
43
  }
40
44
  },
41
45
  "types": "./dist/index.d.ts",