@newtalaria/browser 0.1.6 → 0.1.8
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 +15 -3
- package/dist/client.d.ts +12 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +183 -16
- package/dist/index.js.map +3 -3
- package/dist/replay/capture_outcome.d.ts +18 -0
- package/dist/replay/capture_outcome.d.ts.map +1 -0
- package/dist/replay/segment_buffer.d.ts +2 -2
- package/dist/replay/segment_buffer.d.ts.map +1 -1
- package/dist/talaria.browser.iife.js +181 -16
- package/dist/talaria.browser.iife.js.map +3 -3
- package/dist/utils/opaque_script_error.d.ts +12 -0
- package/dist/utils/opaque_script_error.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Durable tags on error events when an error-clip replay was attempted. */
|
|
2
|
+
export declare const REPLAY_CAPTURE_TAG = "replay.capture";
|
|
3
|
+
export declare const REPLAY_CAPTURE_REASON_TAG = "replay.capture_reason";
|
|
4
|
+
export type ReplayCaptureStatus = 'ok' | 'failed' | 'skipped';
|
|
5
|
+
export type ReplayCaptureReason = 'oversized_full_snapshot' | 'no_full_snapshot' | 'upload_failed' | 'not_sampled' | 'buffer_empty';
|
|
6
|
+
export interface ReplayCaptureFailure {
|
|
7
|
+
reason: ReplayCaptureReason;
|
|
8
|
+
details?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface ReplayCaptureOutcome {
|
|
11
|
+
status: ReplayCaptureStatus;
|
|
12
|
+
reason?: ReplayCaptureReason;
|
|
13
|
+
details?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/** Merge replay capture tags into event tags (error-clip path only). */
|
|
16
|
+
export declare function applyReplayCaptureTags(tags: Record<string, string>, outcome: ReplayCaptureOutcome | null): Record<string, string>;
|
|
17
|
+
export declare function mergeReplayCaptureExtra(extra: Record<string, unknown> | undefined, outcome: ReplayCaptureOutcome | null): Record<string, unknown> | undefined;
|
|
18
|
+
//# sourceMappingURL=capture_outcome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capture_outcome.d.ts","sourceRoot":"","sources":["../../src/replay/capture_outcome.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AACnD,eAAO,MAAM,yBAAyB,0BAA0B,CAAC;AAEjE,MAAM,MAAM,mBAAmB,GAAG,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE9D,MAAM,MAAM,mBAAmB,GAC3B,yBAAyB,GACzB,kBAAkB,GAClB,eAAe,GACf,aAAa,GACb,cAAc,CAAC;AAEnB,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,oBAAoB,GAAG,IAAI,GACnC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,OAAO,EAAE,oBAAoB,GAAG,IAAI,GACnC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAarC"}
|
|
@@ -8,13 +8,13 @@ export interface RrwebEvent {
|
|
|
8
8
|
* Soft uncompressed take size before gzip fitting.
|
|
9
9
|
* Keep in sync with ReplayLimits.maxUncompressedSegmentBytes intent.
|
|
10
10
|
*/
|
|
11
|
-
export declare const SEGMENT_SIZE_BYTES =
|
|
11
|
+
export declare const SEGMENT_SIZE_BYTES = 800000;
|
|
12
12
|
/**
|
|
13
13
|
* Server hard cap — keep in sync with ReplayLimits.maxCompressedSegmentBytes.
|
|
14
14
|
* Restart Serverpod after changing this constant pair.
|
|
15
15
|
*/
|
|
16
16
|
export declare const MAX_COMPRESSED_SEGMENT_BYTES: number;
|
|
17
|
-
/** Pack under hard cap minus margin (never disagree with a live
|
|
17
|
+
/** Pack under hard cap minus margin (never disagree with a live 1MiB server). */
|
|
18
18
|
export declare const TARGET_COMPRESSED_SEGMENT_BYTES: number;
|
|
19
19
|
/**
|
|
20
20
|
* Absolute max segments per replay (session / long error mode).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segment_buffer.d.ts","sourceRoot":"","sources":["../../src/replay/segment_buffer.ts"],"names":[],"mappings":"AAEA,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,SAAU,CAAC;AAC1C;;;GAGG;AACH,eAAO,MAAM,4BAA4B,
|
|
1
|
+
{"version":3,"file":"segment_buffer.d.ts","sourceRoot":"","sources":["../../src/replay/segment_buffer.ts"],"names":[],"mappings":"AAEA,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,SAAU,CAAC;AAC1C;;;GAGG;AACH,eAAO,MAAM,4BAA4B,QAAc,CAAC;AACxD,iFAAiF;AACjF,eAAO,MAAM,+BAA+B,QACF,CAAC;AAC3C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C;;GAEG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,uEAAuE;AACvE,eAAO,MAAM,+BAA+B,QAAkB,CAAC;AAC/D,8DAA8D;AAC9D,eAAO,MAAM,yBAAyB,OAAQ,CAAC;AAC/C,+BAA+B;AAC/B,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,oEAAoE;AACpE,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB,UAAY,CAAC;AAC/C,2DAA2D;AAC3D,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAC5C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,QAAgB,CAAC;AAEpD,mCAAmC;AACnC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,2BAA2B;AAC3B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,YAAY,CAAK;IAEzB,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAK7B,eAAe,IAAI,OAAO;IAI1B;;;OAGG;IACH,kBAAkB,IAAI,OAAO;IAc7B;;;OAGG;IACH,QAAQ,CAAC,GAAG,SAAa,EAAE,QAAQ,SAAwB,GAAG,IAAI;IAiBlE,iBAAiB,IAAI,OAAO;IAI5B;;;;OAIG;IACH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE;IAyBpD,OAAO,IAAI,UAAU,EAAE;IAOvB,6EAA6E;IAC7E,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI;IAMnC,KAAK,IAAI,IAAI;IAKb,SAAS,IAAI;QAAE,SAAS,EAAE,IAAI,CAAC;QAAC,OAAO,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI;IAUtD,OAAO,CAAC,cAAc;CAGvB"}
|
|
@@ -17,6 +17,14 @@
|
|
|
17
17
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// src/utils/opaque_script_error.ts
|
|
21
|
+
function isOpaqueCrossOriginScriptError(event) {
|
|
22
|
+
const msg = (event.message ?? "").trim();
|
|
23
|
+
if (!/^script error\.?$/i.test(msg)) return false;
|
|
24
|
+
if (event.error instanceof Error) return false;
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
20
28
|
// src/transport/serverpod.ts
|
|
21
29
|
var ServerpodTransport = class {
|
|
22
30
|
constructor(options) {
|
|
@@ -123,9 +131,9 @@
|
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
// src/replay/segment_buffer.ts
|
|
126
|
-
var SEGMENT_SIZE_BYTES =
|
|
127
|
-
var MAX_COMPRESSED_SEGMENT_BYTES =
|
|
128
|
-
var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES -
|
|
134
|
+
var SEGMENT_SIZE_BYTES = 8e5;
|
|
135
|
+
var MAX_COMPRESSED_SEGMENT_BYTES = 1024 * 1024;
|
|
136
|
+
var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES - 64 * 1024;
|
|
129
137
|
var MAX_SEGMENTS_PER_REPLAY = 200;
|
|
130
138
|
var MAX_SEGMENTS_ERROR_CLIP = 12;
|
|
131
139
|
var MAX_ERROR_CLIP_COMPRESSED_BYTES = 2 * 1024 * 1024;
|
|
@@ -357,6 +365,33 @@
|
|
|
357
365
|
return null;
|
|
358
366
|
}
|
|
359
367
|
|
|
368
|
+
// src/replay/capture_outcome.ts
|
|
369
|
+
var REPLAY_CAPTURE_TAG = "replay.capture";
|
|
370
|
+
var REPLAY_CAPTURE_REASON_TAG = "replay.capture_reason";
|
|
371
|
+
function applyReplayCaptureTags(tags, outcome) {
|
|
372
|
+
if (!outcome) return tags;
|
|
373
|
+
const next = { ...tags };
|
|
374
|
+
next[REPLAY_CAPTURE_TAG] = outcome.status;
|
|
375
|
+
if (outcome.reason) {
|
|
376
|
+
next[REPLAY_CAPTURE_REASON_TAG] = outcome.reason;
|
|
377
|
+
}
|
|
378
|
+
return next;
|
|
379
|
+
}
|
|
380
|
+
function mergeReplayCaptureExtra(extra, outcome) {
|
|
381
|
+
if (!outcome || outcome.status === "ok") {
|
|
382
|
+
return extra;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
...extra ?? {},
|
|
386
|
+
replayCapture: {
|
|
387
|
+
attempted: outcome.status === "failed",
|
|
388
|
+
status: outcome.status,
|
|
389
|
+
reason: outcome.reason,
|
|
390
|
+
...outcome.details ?? {}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
360
395
|
// ../../node_modules/rrweb/dist/rrweb.js
|
|
361
396
|
var __defProp = Object.defineProperty;
|
|
362
397
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -13401,6 +13436,8 @@
|
|
|
13401
13436
|
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
13402
13437
|
*/
|
|
13403
13438
|
this.linkableReplayId = null;
|
|
13439
|
+
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
13440
|
+
this.lastReplayCaptureFailure = null;
|
|
13404
13441
|
}
|
|
13405
13442
|
init(options) {
|
|
13406
13443
|
if (this.options) {
|
|
@@ -13422,6 +13459,7 @@
|
|
|
13422
13459
|
this.uploadStartedAtMs = null;
|
|
13423
13460
|
this.errorClipDeadlineMs = null;
|
|
13424
13461
|
this.linkableReplayId = null;
|
|
13462
|
+
this.lastReplayCaptureFailure = null;
|
|
13425
13463
|
this.buffer = new SegmentBuffer();
|
|
13426
13464
|
this.uploadChain = Promise.resolve();
|
|
13427
13465
|
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
@@ -13583,6 +13621,7 @@
|
|
|
13583
13621
|
this.uploadChain = Promise.resolve();
|
|
13584
13622
|
this.closed = false;
|
|
13585
13623
|
this.linkableReplayId = null;
|
|
13624
|
+
this.lastReplayCaptureFailure = null;
|
|
13586
13625
|
}
|
|
13587
13626
|
onRrwebEvent(event) {
|
|
13588
13627
|
if (this.closed || !this.options) return;
|
|
@@ -13608,15 +13647,22 @@
|
|
|
13608
13647
|
throw new Error("@newtalaria/browser: call Talaria.init() first");
|
|
13609
13648
|
}
|
|
13610
13649
|
const isErrorLike = args.level === "error" || args.level === "fatal";
|
|
13650
|
+
let errorClipOutcome = null;
|
|
13651
|
+
let attemptedErrorClip = false;
|
|
13611
13652
|
if (isErrorLike && !this.sessionSampled) {
|
|
13612
13653
|
if (!this.uploadEnabled) {
|
|
13613
13654
|
if (Math.random() < this.options.replaysOnErrorSampleRate) {
|
|
13655
|
+
attemptedErrorClip = true;
|
|
13656
|
+
this.lastReplayCaptureFailure = null;
|
|
13614
13657
|
this.uploadEnabled = true;
|
|
13615
13658
|
this.checkoutFullSnapshot();
|
|
13616
13659
|
this.markUploadStarted();
|
|
13617
13660
|
this.scheduleErrorClipEnd();
|
|
13661
|
+
} else {
|
|
13662
|
+
errorClipOutcome = { status: "skipped", reason: "not_sampled" };
|
|
13618
13663
|
}
|
|
13619
13664
|
} else {
|
|
13665
|
+
attemptedErrorClip = true;
|
|
13620
13666
|
this.scheduleErrorClipEnd();
|
|
13621
13667
|
}
|
|
13622
13668
|
}
|
|
@@ -13625,15 +13671,49 @@
|
|
|
13625
13671
|
await this.flush({ reason: "capture", keepalive: args.keepalive });
|
|
13626
13672
|
} catch (error) {
|
|
13627
13673
|
console.warn("@newtalaria/browser: replay flush failed", error);
|
|
13628
|
-
|
|
13674
|
+
this.lastReplayCaptureFailure = {
|
|
13675
|
+
reason: "upload_failed",
|
|
13676
|
+
details: {
|
|
13677
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13678
|
+
}
|
|
13679
|
+
};
|
|
13680
|
+
if (!this.sessionSampled && this.segmentIndex === 0) {
|
|
13681
|
+
await this.abortUnusableClip("upload_failed", {
|
|
13682
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13683
|
+
});
|
|
13684
|
+
}
|
|
13629
13685
|
}
|
|
13630
13686
|
}
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
}
|
|
13635
|
-
|
|
13636
|
-
|
|
13687
|
+
let replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
|
|
13688
|
+
if (this.lastReplayCaptureFailure && this.segmentIndex === 0) {
|
|
13689
|
+
replayId = null;
|
|
13690
|
+
}
|
|
13691
|
+
if (isErrorLike && !this.sessionSampled) {
|
|
13692
|
+
if (replayId) {
|
|
13693
|
+
errorClipOutcome = { status: "ok" };
|
|
13694
|
+
this.lastReplayCaptureFailure = null;
|
|
13695
|
+
} else if (errorClipOutcome?.status === "skipped") {
|
|
13696
|
+
} else if (this.lastReplayCaptureFailure) {
|
|
13697
|
+
errorClipOutcome = {
|
|
13698
|
+
status: "failed",
|
|
13699
|
+
reason: this.lastReplayCaptureFailure.reason,
|
|
13700
|
+
details: this.lastReplayCaptureFailure.details
|
|
13701
|
+
};
|
|
13702
|
+
} else if (attemptedErrorClip) {
|
|
13703
|
+
errorClipOutcome = { status: "failed", reason: "buffer_empty" };
|
|
13704
|
+
}
|
|
13705
|
+
}
|
|
13706
|
+
const tags = applyReplayCaptureTags(
|
|
13707
|
+
{
|
|
13708
|
+
...this.options.tags ?? {},
|
|
13709
|
+
...args.context?.tags ?? {}
|
|
13710
|
+
},
|
|
13711
|
+
errorClipOutcome
|
|
13712
|
+
);
|
|
13713
|
+
const extra = mergeReplayCaptureExtra(
|
|
13714
|
+
args.context?.extra,
|
|
13715
|
+
errorClipOutcome
|
|
13716
|
+
);
|
|
13637
13717
|
try {
|
|
13638
13718
|
await ingestEvent(this.transport, {
|
|
13639
13719
|
message: args.message,
|
|
@@ -13645,7 +13725,7 @@
|
|
|
13645
13725
|
release: this.options.release,
|
|
13646
13726
|
userId: args.context?.userId ?? this.options.userId,
|
|
13647
13727
|
sessionId: this.sessionId ?? void 0,
|
|
13648
|
-
replayId,
|
|
13728
|
+
replayId: replayId ?? void 0,
|
|
13649
13729
|
url: typeof location !== "undefined" ? location.href : void 0,
|
|
13650
13730
|
tags: Object.keys(tags).length ? tags : void 0,
|
|
13651
13731
|
extraJson: extra ? JSON.stringify(extra) : void 0,
|
|
@@ -13790,9 +13870,10 @@
|
|
|
13790
13870
|
/**
|
|
13791
13871
|
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
13792
13872
|
* produce a blank rrweb player.
|
|
13873
|
+
* @returns false when no FullSnapshot is available.
|
|
13793
13874
|
*/
|
|
13794
13875
|
prepareBufferForNewReplay() {
|
|
13795
|
-
if (this.segmentIndex !== 0) return;
|
|
13876
|
+
if (this.segmentIndex !== 0) return true;
|
|
13796
13877
|
if (!this.buffer.hasFullSnapshot()) {
|
|
13797
13878
|
this.checkoutFullSnapshot();
|
|
13798
13879
|
}
|
|
@@ -13800,6 +13881,40 @@
|
|
|
13800
13881
|
console.warn(
|
|
13801
13882
|
"@newtalaria/browser: replay has no FullSnapshot; player may show a blank screen"
|
|
13802
13883
|
);
|
|
13884
|
+
return false;
|
|
13885
|
+
}
|
|
13886
|
+
return true;
|
|
13887
|
+
}
|
|
13888
|
+
/**
|
|
13889
|
+
* Stop an error clip that cannot paint (missing/oversized FullSnapshot).
|
|
13890
|
+
* Does not set linkableReplayId — the error event should not open a blank player.
|
|
13891
|
+
*/
|
|
13892
|
+
async abortUnusableClip(reason, details) {
|
|
13893
|
+
this.lastReplayCaptureFailure = { reason, details };
|
|
13894
|
+
console.warn("@newtalaria/browser: aborting unusable replay clip", {
|
|
13895
|
+
reason,
|
|
13896
|
+
...details
|
|
13897
|
+
});
|
|
13898
|
+
this.buffer.clear();
|
|
13899
|
+
this.linkableReplayId = null;
|
|
13900
|
+
if (this.startedOnServer && !this.finishedOnServer && this.transport && this.replayId) {
|
|
13901
|
+
try {
|
|
13902
|
+
await finishReplay(this.transport, {
|
|
13903
|
+
replayId: this.replayId,
|
|
13904
|
+
reason: `capture_failed:${reason}`,
|
|
13905
|
+
keepalive: false
|
|
13906
|
+
});
|
|
13907
|
+
this.finishedOnServer = true;
|
|
13908
|
+
} catch (error) {
|
|
13909
|
+
console.warn("@newtalaria/browser: replays/finish failed after abort", error);
|
|
13910
|
+
}
|
|
13911
|
+
}
|
|
13912
|
+
if (!this.sessionSampled) {
|
|
13913
|
+
this.resetToBufferMode();
|
|
13914
|
+
} else {
|
|
13915
|
+
this.uploadEnabled = false;
|
|
13916
|
+
this.clearErrorClipTimer();
|
|
13917
|
+
this.clearMaxDurationTimer();
|
|
13803
13918
|
}
|
|
13804
13919
|
}
|
|
13805
13920
|
stopUploadingAfterLimit() {
|
|
@@ -13818,6 +13933,7 @@
|
|
|
13818
13933
|
installGlobalHandlers() {
|
|
13819
13934
|
if (typeof window === "undefined") return;
|
|
13820
13935
|
const onError = (event) => {
|
|
13936
|
+
if (isOpaqueCrossOriginScriptError(event)) return;
|
|
13821
13937
|
const error = event.error instanceof Error ? event.error : new Error(event.message || "window.onerror");
|
|
13822
13938
|
void this.captureException(error, {
|
|
13823
13939
|
extra: {
|
|
@@ -13860,7 +13976,10 @@
|
|
|
13860
13976
|
async uploadPendingSegments(opts) {
|
|
13861
13977
|
if (!this.options || !this.transport || !this.replayId) return;
|
|
13862
13978
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13863
|
-
this.prepareBufferForNewReplay()
|
|
13979
|
+
if (!this.prepareBufferForNewReplay()) {
|
|
13980
|
+
await this.abortUnusableClip("no_full_snapshot");
|
|
13981
|
+
return;
|
|
13982
|
+
}
|
|
13864
13983
|
while (this.buffer.length > 0) {
|
|
13865
13984
|
if (this.isPastMaxDuration()) {
|
|
13866
13985
|
break;
|
|
@@ -13900,7 +14019,17 @@
|
|
|
13900
14019
|
}
|
|
13901
14020
|
const fitted = await this.takeFittedSegment();
|
|
13902
14021
|
if (!fitted) break;
|
|
14022
|
+
if (fitted.abortReason) {
|
|
14023
|
+
await this.abortUnusableClip(fitted.abortReason, fitted.abortDetails);
|
|
14024
|
+
return;
|
|
14025
|
+
}
|
|
13903
14026
|
const { events, gzip } = fitted;
|
|
14027
|
+
if (this.segmentIndex === 0 && !events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14028
|
+
await this.abortUnusableClip("no_full_snapshot", {
|
|
14029
|
+
segmentEventTypes: events.map((e) => e.type)
|
|
14030
|
+
});
|
|
14031
|
+
return;
|
|
14032
|
+
}
|
|
13904
14033
|
if (this.isErrorClipMode() && this.uploadedCompressedBytes + gzip.length > MAX_ERROR_CLIP_COMPRESSED_BYTES && this.segmentIndex > 0) {
|
|
13905
14034
|
this.buffer.clear();
|
|
13906
14035
|
await this.finishOnServer({
|
|
@@ -13925,6 +14054,9 @@
|
|
|
13925
14054
|
});
|
|
13926
14055
|
this.segmentIndex = index2 + 1;
|
|
13927
14056
|
this.uploadedCompressedBytes += gzip.length;
|
|
14057
|
+
if (index2 === 0) {
|
|
14058
|
+
this.lastReplayCaptureFailure = null;
|
|
14059
|
+
}
|
|
13928
14060
|
} catch (error) {
|
|
13929
14061
|
if (isTerminalReplayLimitError(error)) {
|
|
13930
14062
|
console.warn(
|
|
@@ -13941,10 +14073,17 @@
|
|
|
13941
14073
|
if (isOversizedSegmentError(error)) {
|
|
13942
14074
|
const plan = planOversizedRetry(events);
|
|
13943
14075
|
if (plan.action === "drop") {
|
|
14076
|
+
const droppedType = events[0]?.type;
|
|
13944
14077
|
console.warn(
|
|
13945
14078
|
"@newtalaria/browser: dropping rrweb event rejected as oversized",
|
|
13946
|
-
{ type:
|
|
14079
|
+
{ type: droppedType }
|
|
13947
14080
|
);
|
|
14081
|
+
if (this.segmentIndex === 0 && droppedType === RRWEB_FULL_SNAPSHOT) {
|
|
14082
|
+
await this.abortUnusableClip("oversized_full_snapshot", {
|
|
14083
|
+
source: "server_reject"
|
|
14084
|
+
});
|
|
14085
|
+
return;
|
|
14086
|
+
}
|
|
13948
14087
|
continue;
|
|
13949
14088
|
}
|
|
13950
14089
|
this.buffer.prepend(plan.right);
|
|
@@ -13953,6 +14092,11 @@
|
|
|
13953
14092
|
}
|
|
13954
14093
|
this.buffer.prepend(events);
|
|
13955
14094
|
console.warn("@newtalaria/browser: replays/ingestSegment failed", error);
|
|
14095
|
+
if (this.segmentIndex === 0) {
|
|
14096
|
+
await this.abortUnusableClip("upload_failed", {
|
|
14097
|
+
message: error instanceof Error ? error.message : String(error)
|
|
14098
|
+
});
|
|
14099
|
+
}
|
|
13956
14100
|
break;
|
|
13957
14101
|
}
|
|
13958
14102
|
if (opts.keepalive) break;
|
|
@@ -13967,7 +14111,8 @@
|
|
|
13967
14111
|
}
|
|
13968
14112
|
/**
|
|
13969
14113
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
13970
|
-
* Drops a single event that can never fit
|
|
14114
|
+
* Drops a single event that can never fit — except FullSnapshot on segment 0
|
|
14115
|
+
* (abort the clip instead of uploading a blank orphan stream).
|
|
13971
14116
|
*/
|
|
13972
14117
|
async takeFittedSegment() {
|
|
13973
14118
|
const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES);
|
|
@@ -13979,12 +14124,32 @@
|
|
|
13979
14124
|
MAX_COMPRESSED_SEGMENT_BYTES
|
|
13980
14125
|
);
|
|
13981
14126
|
if (!fitted) {
|
|
14127
|
+
const dropped = chunk[0];
|
|
13982
14128
|
console.warn(
|
|
13983
14129
|
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
13984
|
-
{ type:
|
|
14130
|
+
{ type: dropped?.type }
|
|
13985
14131
|
);
|
|
14132
|
+
if (this.segmentIndex === 0 && dropped?.type === RRWEB_FULL_SNAPSHOT) {
|
|
14133
|
+
return {
|
|
14134
|
+
events: [],
|
|
14135
|
+
gzip: new Uint8Array(),
|
|
14136
|
+
abortReason: "oversized_full_snapshot",
|
|
14137
|
+
abortDetails: { source: "fit_compressed_prefix" }
|
|
14138
|
+
};
|
|
14139
|
+
}
|
|
14140
|
+
if (chunk.length > 1) {
|
|
14141
|
+
this.buffer.prepend(chunk.slice(1));
|
|
14142
|
+
}
|
|
13986
14143
|
return this.takeFittedSegment();
|
|
13987
14144
|
}
|
|
14145
|
+
if (this.segmentIndex === 0 && chunk.some((e) => e.type === RRWEB_FULL_SNAPSHOT) && !fitted.events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14146
|
+
return {
|
|
14147
|
+
events: [],
|
|
14148
|
+
gzip: new Uint8Array(),
|
|
14149
|
+
abortReason: "oversized_full_snapshot",
|
|
14150
|
+
abortDetails: { source: "fit_dropped_full_snapshot" }
|
|
14151
|
+
};
|
|
14152
|
+
}
|
|
13988
14153
|
if (fitted.remainder.length > 0) {
|
|
13989
14154
|
this.buffer.prepend(fitted.remainder);
|
|
13990
14155
|
}
|