@neta-art/cohub 1.9.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.
- package/README.md +6 -105
- package/dist/chunks/environment.js +33 -0
- package/dist/chunks/http.d.ts +1615 -0
- package/dist/chunks/http.js +1919 -0
- package/dist/chunks/websocket.d.ts +266 -0
- package/dist/chunks/websocket.js +655 -0
- package/dist/debugger.d.ts +198 -0
- package/dist/debugger.js +1120 -0
- package/dist/http.d.ts +3 -32
- package/dist/http.js +2 -48
- package/dist/index.d.ts +35 -14
- package/dist/index.js +105 -8
- package/dist/websocket.d.ts +2 -141
- package/dist/websocket.js +2 -628
- package/package.json +11 -7
- package/dist/apis/channels.d.ts +0 -13
- package/dist/apis/channels.js +0 -24
- package/dist/apis/cron-jobs.d.ts +0 -18
- package/dist/apis/cron-jobs.js +0 -25
- package/dist/apis/explore.d.ts +0 -9
- package/dist/apis/explore.js +0 -9
- package/dist/apis/generations.d.ts +0 -7
- package/dist/apis/generations.js +0 -13
- package/dist/apis/invitations.d.ts +0 -20
- package/dist/apis/invitations.js +0 -36
- package/dist/apis/models.d.ts +0 -10
- package/dist/apis/models.js +0 -13
- package/dist/apis/prompts.d.ts +0 -9
- package/dist/apis/prompts.js +0 -16
- package/dist/apis/search.d.ts +0 -10
- package/dist/apis/search.js +0 -14
- package/dist/apis/session-access.d.ts +0 -13
- package/dist/apis/session-access.js +0 -19
- package/dist/apis/spaces.d.ts +0 -371
- package/dist/apis/spaces.js +0 -766
- package/dist/apis/tasks.d.ts +0 -13
- package/dist/apis/tasks.js +0 -18
- package/dist/apis/user.d.ts +0 -27
- package/dist/apis/user.js +0 -71
- package/dist/client.d.ts +0 -33
- package/dist/client.js +0 -103
- package/dist/environment.d.ts +0 -22
- package/dist/environment.js +0 -37
- package/dist/realtime.d.ts +0 -2
- package/dist/realtime.js +0 -8
- package/dist/session-generation-stream.d.ts +0 -114
- package/dist/session-generation-stream.js +0 -514
- package/dist/session-patch-reducer.d.ts +0 -61
- package/dist/session-patch-reducer.js +0 -432
- package/dist/transport.d.ts +0 -40
- package/dist/transport.js +0 -78
- package/dist/types.d.ts +0 -535
- package/dist/types.js +0 -1
package/dist/debugger.js
ADDED
|
@@ -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 };
|