@cedarai/session-replay-sdk 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +148 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ function isCompressionSupported() {
|
|
|
24
24
|
|
|
25
25
|
// src/transport.ts
|
|
26
26
|
var BEACON_MAX_SIZE = 64 * 1024;
|
|
27
|
+
var MAX_QUEUE_LENGTH = 1e3;
|
|
27
28
|
var Transport = class {
|
|
28
29
|
config;
|
|
29
30
|
queue = [];
|
|
@@ -65,6 +66,9 @@ var Transport = class {
|
|
|
65
66
|
enqueue(event) {
|
|
66
67
|
if (!this.started) return;
|
|
67
68
|
this.queue.push(event);
|
|
69
|
+
if (this.queue.length > MAX_QUEUE_LENGTH) {
|
|
70
|
+
this.queue.splice(0, this.queue.length - MAX_QUEUE_LENGTH);
|
|
71
|
+
}
|
|
68
72
|
const maxSize = this.config.batchMaxSize ?? 1024 * 512;
|
|
69
73
|
const estimatedSize = this.estimateQueueSize();
|
|
70
74
|
if (estimatedSize >= maxSize) {
|
|
@@ -294,10 +298,37 @@ var ErrorCapture = class {
|
|
|
294
298
|
|
|
295
299
|
// src/network.ts
|
|
296
300
|
var nextId = 0;
|
|
301
|
+
var RESPONSE_BODY_MAX_SIZE = 100 * 1024;
|
|
302
|
+
function extractHeaders(headers) {
|
|
303
|
+
if (!headers) return void 0;
|
|
304
|
+
const result = {};
|
|
305
|
+
if (headers instanceof Headers) {
|
|
306
|
+
headers.forEach((v, k) => {
|
|
307
|
+
result[k] = v;
|
|
308
|
+
});
|
|
309
|
+
} else if (Array.isArray(headers)) {
|
|
310
|
+
for (const [k, v] of headers) {
|
|
311
|
+
result[k] = v;
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
Object.assign(result, headers);
|
|
315
|
+
}
|
|
316
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
317
|
+
}
|
|
318
|
+
function headersToRecord(headers) {
|
|
319
|
+
const result = {};
|
|
320
|
+
headers.forEach((v, k) => {
|
|
321
|
+
result[k] = v;
|
|
322
|
+
});
|
|
323
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
324
|
+
}
|
|
297
325
|
var NetworkCapture = class {
|
|
298
326
|
onEvent;
|
|
299
327
|
serverUrl;
|
|
300
328
|
originalFetch = null;
|
|
329
|
+
originalXHROpen = null;
|
|
330
|
+
originalXHRSend = null;
|
|
331
|
+
originalXHRSetHeader = null;
|
|
301
332
|
started = false;
|
|
302
333
|
constructor(onEvent2, serverUrl) {
|
|
303
334
|
this.onEvent = onEvent2;
|
|
@@ -306,6 +337,31 @@ var NetworkCapture = class {
|
|
|
306
337
|
start() {
|
|
307
338
|
if (this.started) return;
|
|
308
339
|
this.started = true;
|
|
340
|
+
this.interceptFetch();
|
|
341
|
+
this.interceptXHR();
|
|
342
|
+
}
|
|
343
|
+
stop() {
|
|
344
|
+
if (!this.started) return;
|
|
345
|
+
this.started = false;
|
|
346
|
+
if (this.originalFetch) {
|
|
347
|
+
globalThis.fetch = this.originalFetch;
|
|
348
|
+
this.originalFetch = null;
|
|
349
|
+
}
|
|
350
|
+
if (this.originalXHROpen) {
|
|
351
|
+
XMLHttpRequest.prototype.open = this.originalXHROpen;
|
|
352
|
+
this.originalXHROpen = null;
|
|
353
|
+
}
|
|
354
|
+
if (this.originalXHRSend) {
|
|
355
|
+
XMLHttpRequest.prototype.send = this.originalXHRSend;
|
|
356
|
+
this.originalXHRSend = null;
|
|
357
|
+
}
|
|
358
|
+
if (this.originalXHRSetHeader) {
|
|
359
|
+
XMLHttpRequest.prototype.setRequestHeader = this.originalXHRSetHeader;
|
|
360
|
+
this.originalXHRSetHeader = null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// ── Fetch interception ────────────────────────────────────────
|
|
364
|
+
interceptFetch() {
|
|
309
365
|
this.originalFetch = globalThis.fetch;
|
|
310
366
|
globalThis.fetch = async (input, init) => {
|
|
311
367
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
@@ -314,6 +370,7 @@ var NetworkCapture = class {
|
|
|
314
370
|
}
|
|
315
371
|
const method = init?.method?.toUpperCase() ?? "GET";
|
|
316
372
|
const requestBody = init?.body ? String(init.body) : void 0;
|
|
373
|
+
const requestHeaders = extractHeaders(init?.headers);
|
|
317
374
|
const startTime = performance.now();
|
|
318
375
|
const id = String(++nextId);
|
|
319
376
|
let graphqlOperationName;
|
|
@@ -330,10 +387,16 @@ var NetworkCapture = class {
|
|
|
330
387
|
const response = await this.originalFetch(input, init);
|
|
331
388
|
const endTime = performance.now();
|
|
332
389
|
let responseBody;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
390
|
+
const contentLength = parseInt(response.headers.get("content-length") ?? "0", 10);
|
|
391
|
+
if (contentLength === 0 || contentLength <= RESPONSE_BODY_MAX_SIZE) {
|
|
392
|
+
try {
|
|
393
|
+
const cloned = response.clone();
|
|
394
|
+
const text = await cloned.text();
|
|
395
|
+
if (text.length <= RESPONSE_BODY_MAX_SIZE) {
|
|
396
|
+
responseBody = text;
|
|
397
|
+
}
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
337
400
|
}
|
|
338
401
|
this.onEvent({
|
|
339
402
|
type: "network",
|
|
@@ -343,8 +406,10 @@ var NetworkCapture = class {
|
|
|
343
406
|
method,
|
|
344
407
|
url,
|
|
345
408
|
graphqlOperationName,
|
|
409
|
+
requestHeaders,
|
|
346
410
|
requestBody,
|
|
347
411
|
status: response.status,
|
|
412
|
+
responseHeaders: headersToRecord(response.headers),
|
|
348
413
|
responseBody,
|
|
349
414
|
startTime,
|
|
350
415
|
endTime,
|
|
@@ -362,6 +427,7 @@ var NetworkCapture = class {
|
|
|
362
427
|
method,
|
|
363
428
|
url,
|
|
364
429
|
graphqlOperationName,
|
|
430
|
+
requestHeaders,
|
|
365
431
|
requestBody,
|
|
366
432
|
startTime,
|
|
367
433
|
endTime,
|
|
@@ -373,18 +439,87 @@ var NetworkCapture = class {
|
|
|
373
439
|
}
|
|
374
440
|
};
|
|
375
441
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
442
|
+
// ── XMLHttpRequest interception ───────────────────────────────
|
|
443
|
+
interceptXHR() {
|
|
444
|
+
const self = this;
|
|
445
|
+
const xhrData = /* @__PURE__ */ new WeakMap();
|
|
446
|
+
this.originalXHROpen = XMLHttpRequest.prototype.open;
|
|
447
|
+
this.originalXHRSend = XMLHttpRequest.prototype.send;
|
|
448
|
+
this.originalXHRSetHeader = XMLHttpRequest.prototype.setRequestHeader;
|
|
449
|
+
XMLHttpRequest.prototype.open = function(method, url, ...rest) {
|
|
450
|
+
const urlStr = typeof url === "string" ? url : url.href;
|
|
451
|
+
xhrData.set(this, { method: method.toUpperCase(), url: urlStr, headers: {}, startTime: 0 });
|
|
452
|
+
return self.originalXHROpen.apply(this, [method, url, ...rest]);
|
|
453
|
+
};
|
|
454
|
+
XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
|
|
455
|
+
const data = xhrData.get(this);
|
|
456
|
+
if (data) data.headers[name] = value;
|
|
457
|
+
return self.originalXHRSetHeader.apply(this, [name, value]);
|
|
458
|
+
};
|
|
459
|
+
XMLHttpRequest.prototype.send = function(body) {
|
|
460
|
+
const data = xhrData.get(this);
|
|
461
|
+
if (data && data.url.startsWith(self.serverUrl)) {
|
|
462
|
+
return self.originalXHRSend.apply(this, [body]);
|
|
463
|
+
}
|
|
464
|
+
if (data) {
|
|
465
|
+
data.startTime = performance.now();
|
|
466
|
+
}
|
|
467
|
+
const requestBody = body != null ? String(body) : void 0;
|
|
468
|
+
this.addEventListener("loadend", function() {
|
|
469
|
+
if (!data) return;
|
|
470
|
+
const endTime = performance.now();
|
|
471
|
+
const rawHeaders = this.getAllResponseHeaders();
|
|
472
|
+
const responseHeaders = {};
|
|
473
|
+
if (rawHeaders) {
|
|
474
|
+
rawHeaders.trim().split(/[\r\n]+/).forEach((line) => {
|
|
475
|
+
const idx = line.indexOf(": ");
|
|
476
|
+
if (idx > 0) responseHeaders[line.substring(0, idx)] = line.substring(idx + 2);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
let responseBody;
|
|
480
|
+
if (this.responseType === "" || this.responseType === "text") {
|
|
481
|
+
try {
|
|
482
|
+
const text = this.responseText;
|
|
483
|
+
if (text.length <= RESPONSE_BODY_MAX_SIZE) responseBody = text;
|
|
484
|
+
} catch {
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
self.onEvent({
|
|
488
|
+
type: "network",
|
|
489
|
+
timestamp: Date.now(),
|
|
490
|
+
data: {
|
|
491
|
+
id: String(++nextId),
|
|
492
|
+
method: data.method,
|
|
493
|
+
url: data.url,
|
|
494
|
+
requestHeaders: Object.keys(data.headers).length > 0 ? data.headers : void 0,
|
|
495
|
+
requestBody,
|
|
496
|
+
status: this.status,
|
|
497
|
+
responseHeaders: Object.keys(responseHeaders).length > 0 ? responseHeaders : void 0,
|
|
498
|
+
responseBody,
|
|
499
|
+
startTime: data.startTime,
|
|
500
|
+
endTime,
|
|
501
|
+
duration: endTime - data.startTime,
|
|
502
|
+
error: this.status === 0 ? "Network error" : void 0
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
return self.originalXHRSend.apply(this, [body]);
|
|
507
|
+
};
|
|
383
508
|
}
|
|
384
509
|
};
|
|
385
510
|
|
|
386
511
|
// src/recorder.ts
|
|
387
512
|
import { record } from "rrweb";
|
|
513
|
+
var DEFAULT_SAMPLING = {
|
|
514
|
+
mousemove: 50,
|
|
515
|
+
// throttle mousemove to one event per 50ms (default is every frame)
|
|
516
|
+
mouseInteraction: true,
|
|
517
|
+
// keep mouse clicks/interactions
|
|
518
|
+
scroll: 150,
|
|
519
|
+
// throttle scroll to one event per 150ms
|
|
520
|
+
input: "last"
|
|
521
|
+
// only capture the final input value, not every keystroke
|
|
522
|
+
};
|
|
388
523
|
var RecorderCapture = class {
|
|
389
524
|
onEvent;
|
|
390
525
|
config;
|
|
@@ -398,6 +533,7 @@ var RecorderCapture = class {
|
|
|
398
533
|
if (this.started) return;
|
|
399
534
|
this.started = true;
|
|
400
535
|
const { checkoutEveryNms, blockSelector, maskAllInputs, inlineStylesheet, sampling } = this.config;
|
|
536
|
+
const mergedSampling = { ...DEFAULT_SAMPLING, ...sampling };
|
|
401
537
|
const result = record({
|
|
402
538
|
emit: (event) => {
|
|
403
539
|
if (!this.started) return;
|
|
@@ -410,7 +546,7 @@ var RecorderCapture = class {
|
|
|
410
546
|
...blockSelector !== void 0 && { blockSelector },
|
|
411
547
|
...maskAllInputs !== void 0 && { maskAllInputs },
|
|
412
548
|
...inlineStylesheet !== void 0 && { inlineStylesheet },
|
|
413
|
-
|
|
549
|
+
sampling: mergedSampling
|
|
414
550
|
});
|
|
415
551
|
this.stopFn = result ?? null;
|
|
416
552
|
}
|