@neta-art/cohub 1.10.0 → 1.10.2

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