@newtalaria/browser 0.1.6 → 0.1.7
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 +14 -2
- 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 +174 -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 +172 -16
- package/dist/talaria.browser.iife.js.map +3 -3
- 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"}
|
|
@@ -123,9 +123,9 @@
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
// 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 -
|
|
126
|
+
var SEGMENT_SIZE_BYTES = 8e5;
|
|
127
|
+
var MAX_COMPRESSED_SEGMENT_BYTES = 1024 * 1024;
|
|
128
|
+
var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES - 64 * 1024;
|
|
129
129
|
var MAX_SEGMENTS_PER_REPLAY = 200;
|
|
130
130
|
var MAX_SEGMENTS_ERROR_CLIP = 12;
|
|
131
131
|
var MAX_ERROR_CLIP_COMPRESSED_BYTES = 2 * 1024 * 1024;
|
|
@@ -357,6 +357,33 @@
|
|
|
357
357
|
return null;
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
// src/replay/capture_outcome.ts
|
|
361
|
+
var REPLAY_CAPTURE_TAG = "replay.capture";
|
|
362
|
+
var REPLAY_CAPTURE_REASON_TAG = "replay.capture_reason";
|
|
363
|
+
function applyReplayCaptureTags(tags, outcome) {
|
|
364
|
+
if (!outcome) return tags;
|
|
365
|
+
const next = { ...tags };
|
|
366
|
+
next[REPLAY_CAPTURE_TAG] = outcome.status;
|
|
367
|
+
if (outcome.reason) {
|
|
368
|
+
next[REPLAY_CAPTURE_REASON_TAG] = outcome.reason;
|
|
369
|
+
}
|
|
370
|
+
return next;
|
|
371
|
+
}
|
|
372
|
+
function mergeReplayCaptureExtra(extra, outcome) {
|
|
373
|
+
if (!outcome || outcome.status === "ok") {
|
|
374
|
+
return extra;
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
...extra ?? {},
|
|
378
|
+
replayCapture: {
|
|
379
|
+
attempted: outcome.status === "failed",
|
|
380
|
+
status: outcome.status,
|
|
381
|
+
reason: outcome.reason,
|
|
382
|
+
...outcome.details ?? {}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
360
387
|
// ../../node_modules/rrweb/dist/rrweb.js
|
|
361
388
|
var __defProp = Object.defineProperty;
|
|
362
389
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -13401,6 +13428,8 @@
|
|
|
13401
13428
|
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
13402
13429
|
*/
|
|
13403
13430
|
this.linkableReplayId = null;
|
|
13431
|
+
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
13432
|
+
this.lastReplayCaptureFailure = null;
|
|
13404
13433
|
}
|
|
13405
13434
|
init(options) {
|
|
13406
13435
|
if (this.options) {
|
|
@@ -13422,6 +13451,7 @@
|
|
|
13422
13451
|
this.uploadStartedAtMs = null;
|
|
13423
13452
|
this.errorClipDeadlineMs = null;
|
|
13424
13453
|
this.linkableReplayId = null;
|
|
13454
|
+
this.lastReplayCaptureFailure = null;
|
|
13425
13455
|
this.buffer = new SegmentBuffer();
|
|
13426
13456
|
this.uploadChain = Promise.resolve();
|
|
13427
13457
|
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
@@ -13583,6 +13613,7 @@
|
|
|
13583
13613
|
this.uploadChain = Promise.resolve();
|
|
13584
13614
|
this.closed = false;
|
|
13585
13615
|
this.linkableReplayId = null;
|
|
13616
|
+
this.lastReplayCaptureFailure = null;
|
|
13586
13617
|
}
|
|
13587
13618
|
onRrwebEvent(event) {
|
|
13588
13619
|
if (this.closed || !this.options) return;
|
|
@@ -13608,15 +13639,22 @@
|
|
|
13608
13639
|
throw new Error("@newtalaria/browser: call Talaria.init() first");
|
|
13609
13640
|
}
|
|
13610
13641
|
const isErrorLike = args.level === "error" || args.level === "fatal";
|
|
13642
|
+
let errorClipOutcome = null;
|
|
13643
|
+
let attemptedErrorClip = false;
|
|
13611
13644
|
if (isErrorLike && !this.sessionSampled) {
|
|
13612
13645
|
if (!this.uploadEnabled) {
|
|
13613
13646
|
if (Math.random() < this.options.replaysOnErrorSampleRate) {
|
|
13647
|
+
attemptedErrorClip = true;
|
|
13648
|
+
this.lastReplayCaptureFailure = null;
|
|
13614
13649
|
this.uploadEnabled = true;
|
|
13615
13650
|
this.checkoutFullSnapshot();
|
|
13616
13651
|
this.markUploadStarted();
|
|
13617
13652
|
this.scheduleErrorClipEnd();
|
|
13653
|
+
} else {
|
|
13654
|
+
errorClipOutcome = { status: "skipped", reason: "not_sampled" };
|
|
13618
13655
|
}
|
|
13619
13656
|
} else {
|
|
13657
|
+
attemptedErrorClip = true;
|
|
13620
13658
|
this.scheduleErrorClipEnd();
|
|
13621
13659
|
}
|
|
13622
13660
|
}
|
|
@@ -13625,15 +13663,49 @@
|
|
|
13625
13663
|
await this.flush({ reason: "capture", keepalive: args.keepalive });
|
|
13626
13664
|
} catch (error) {
|
|
13627
13665
|
console.warn("@newtalaria/browser: replay flush failed", error);
|
|
13628
|
-
|
|
13666
|
+
this.lastReplayCaptureFailure = {
|
|
13667
|
+
reason: "upload_failed",
|
|
13668
|
+
details: {
|
|
13669
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13670
|
+
}
|
|
13671
|
+
};
|
|
13672
|
+
if (!this.sessionSampled && this.segmentIndex === 0) {
|
|
13673
|
+
await this.abortUnusableClip("upload_failed", {
|
|
13674
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13675
|
+
});
|
|
13676
|
+
}
|
|
13629
13677
|
}
|
|
13630
13678
|
}
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
}
|
|
13635
|
-
|
|
13636
|
-
|
|
13679
|
+
let replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
|
|
13680
|
+
if (this.lastReplayCaptureFailure && this.segmentIndex === 0) {
|
|
13681
|
+
replayId = null;
|
|
13682
|
+
}
|
|
13683
|
+
if (isErrorLike && !this.sessionSampled) {
|
|
13684
|
+
if (replayId) {
|
|
13685
|
+
errorClipOutcome = { status: "ok" };
|
|
13686
|
+
this.lastReplayCaptureFailure = null;
|
|
13687
|
+
} else if (errorClipOutcome?.status === "skipped") {
|
|
13688
|
+
} else if (this.lastReplayCaptureFailure) {
|
|
13689
|
+
errorClipOutcome = {
|
|
13690
|
+
status: "failed",
|
|
13691
|
+
reason: this.lastReplayCaptureFailure.reason,
|
|
13692
|
+
details: this.lastReplayCaptureFailure.details
|
|
13693
|
+
};
|
|
13694
|
+
} else if (attemptedErrorClip) {
|
|
13695
|
+
errorClipOutcome = { status: "failed", reason: "buffer_empty" };
|
|
13696
|
+
}
|
|
13697
|
+
}
|
|
13698
|
+
const tags = applyReplayCaptureTags(
|
|
13699
|
+
{
|
|
13700
|
+
...this.options.tags ?? {},
|
|
13701
|
+
...args.context?.tags ?? {}
|
|
13702
|
+
},
|
|
13703
|
+
errorClipOutcome
|
|
13704
|
+
);
|
|
13705
|
+
const extra = mergeReplayCaptureExtra(
|
|
13706
|
+
args.context?.extra,
|
|
13707
|
+
errorClipOutcome
|
|
13708
|
+
);
|
|
13637
13709
|
try {
|
|
13638
13710
|
await ingestEvent(this.transport, {
|
|
13639
13711
|
message: args.message,
|
|
@@ -13645,7 +13717,7 @@
|
|
|
13645
13717
|
release: this.options.release,
|
|
13646
13718
|
userId: args.context?.userId ?? this.options.userId,
|
|
13647
13719
|
sessionId: this.sessionId ?? void 0,
|
|
13648
|
-
replayId,
|
|
13720
|
+
replayId: replayId ?? void 0,
|
|
13649
13721
|
url: typeof location !== "undefined" ? location.href : void 0,
|
|
13650
13722
|
tags: Object.keys(tags).length ? tags : void 0,
|
|
13651
13723
|
extraJson: extra ? JSON.stringify(extra) : void 0,
|
|
@@ -13790,9 +13862,10 @@
|
|
|
13790
13862
|
/**
|
|
13791
13863
|
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
13792
13864
|
* produce a blank rrweb player.
|
|
13865
|
+
* @returns false when no FullSnapshot is available.
|
|
13793
13866
|
*/
|
|
13794
13867
|
prepareBufferForNewReplay() {
|
|
13795
|
-
if (this.segmentIndex !== 0) return;
|
|
13868
|
+
if (this.segmentIndex !== 0) return true;
|
|
13796
13869
|
if (!this.buffer.hasFullSnapshot()) {
|
|
13797
13870
|
this.checkoutFullSnapshot();
|
|
13798
13871
|
}
|
|
@@ -13800,6 +13873,40 @@
|
|
|
13800
13873
|
console.warn(
|
|
13801
13874
|
"@newtalaria/browser: replay has no FullSnapshot; player may show a blank screen"
|
|
13802
13875
|
);
|
|
13876
|
+
return false;
|
|
13877
|
+
}
|
|
13878
|
+
return true;
|
|
13879
|
+
}
|
|
13880
|
+
/**
|
|
13881
|
+
* Stop an error clip that cannot paint (missing/oversized FullSnapshot).
|
|
13882
|
+
* Does not set linkableReplayId — the error event should not open a blank player.
|
|
13883
|
+
*/
|
|
13884
|
+
async abortUnusableClip(reason, details) {
|
|
13885
|
+
this.lastReplayCaptureFailure = { reason, details };
|
|
13886
|
+
console.warn("@newtalaria/browser: aborting unusable replay clip", {
|
|
13887
|
+
reason,
|
|
13888
|
+
...details
|
|
13889
|
+
});
|
|
13890
|
+
this.buffer.clear();
|
|
13891
|
+
this.linkableReplayId = null;
|
|
13892
|
+
if (this.startedOnServer && !this.finishedOnServer && this.transport && this.replayId) {
|
|
13893
|
+
try {
|
|
13894
|
+
await finishReplay(this.transport, {
|
|
13895
|
+
replayId: this.replayId,
|
|
13896
|
+
reason: `capture_failed:${reason}`,
|
|
13897
|
+
keepalive: false
|
|
13898
|
+
});
|
|
13899
|
+
this.finishedOnServer = true;
|
|
13900
|
+
} catch (error) {
|
|
13901
|
+
console.warn("@newtalaria/browser: replays/finish failed after abort", error);
|
|
13902
|
+
}
|
|
13903
|
+
}
|
|
13904
|
+
if (!this.sessionSampled) {
|
|
13905
|
+
this.resetToBufferMode();
|
|
13906
|
+
} else {
|
|
13907
|
+
this.uploadEnabled = false;
|
|
13908
|
+
this.clearErrorClipTimer();
|
|
13909
|
+
this.clearMaxDurationTimer();
|
|
13803
13910
|
}
|
|
13804
13911
|
}
|
|
13805
13912
|
stopUploadingAfterLimit() {
|
|
@@ -13860,7 +13967,10 @@
|
|
|
13860
13967
|
async uploadPendingSegments(opts) {
|
|
13861
13968
|
if (!this.options || !this.transport || !this.replayId) return;
|
|
13862
13969
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13863
|
-
this.prepareBufferForNewReplay()
|
|
13970
|
+
if (!this.prepareBufferForNewReplay()) {
|
|
13971
|
+
await this.abortUnusableClip("no_full_snapshot");
|
|
13972
|
+
return;
|
|
13973
|
+
}
|
|
13864
13974
|
while (this.buffer.length > 0) {
|
|
13865
13975
|
if (this.isPastMaxDuration()) {
|
|
13866
13976
|
break;
|
|
@@ -13900,7 +14010,17 @@
|
|
|
13900
14010
|
}
|
|
13901
14011
|
const fitted = await this.takeFittedSegment();
|
|
13902
14012
|
if (!fitted) break;
|
|
14013
|
+
if (fitted.abortReason) {
|
|
14014
|
+
await this.abortUnusableClip(fitted.abortReason, fitted.abortDetails);
|
|
14015
|
+
return;
|
|
14016
|
+
}
|
|
13903
14017
|
const { events, gzip } = fitted;
|
|
14018
|
+
if (this.segmentIndex === 0 && !events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14019
|
+
await this.abortUnusableClip("no_full_snapshot", {
|
|
14020
|
+
segmentEventTypes: events.map((e) => e.type)
|
|
14021
|
+
});
|
|
14022
|
+
return;
|
|
14023
|
+
}
|
|
13904
14024
|
if (this.isErrorClipMode() && this.uploadedCompressedBytes + gzip.length > MAX_ERROR_CLIP_COMPRESSED_BYTES && this.segmentIndex > 0) {
|
|
13905
14025
|
this.buffer.clear();
|
|
13906
14026
|
await this.finishOnServer({
|
|
@@ -13925,6 +14045,9 @@
|
|
|
13925
14045
|
});
|
|
13926
14046
|
this.segmentIndex = index2 + 1;
|
|
13927
14047
|
this.uploadedCompressedBytes += gzip.length;
|
|
14048
|
+
if (index2 === 0) {
|
|
14049
|
+
this.lastReplayCaptureFailure = null;
|
|
14050
|
+
}
|
|
13928
14051
|
} catch (error) {
|
|
13929
14052
|
if (isTerminalReplayLimitError(error)) {
|
|
13930
14053
|
console.warn(
|
|
@@ -13941,10 +14064,17 @@
|
|
|
13941
14064
|
if (isOversizedSegmentError(error)) {
|
|
13942
14065
|
const plan = planOversizedRetry(events);
|
|
13943
14066
|
if (plan.action === "drop") {
|
|
14067
|
+
const droppedType = events[0]?.type;
|
|
13944
14068
|
console.warn(
|
|
13945
14069
|
"@newtalaria/browser: dropping rrweb event rejected as oversized",
|
|
13946
|
-
{ type:
|
|
14070
|
+
{ type: droppedType }
|
|
13947
14071
|
);
|
|
14072
|
+
if (this.segmentIndex === 0 && droppedType === RRWEB_FULL_SNAPSHOT) {
|
|
14073
|
+
await this.abortUnusableClip("oversized_full_snapshot", {
|
|
14074
|
+
source: "server_reject"
|
|
14075
|
+
});
|
|
14076
|
+
return;
|
|
14077
|
+
}
|
|
13948
14078
|
continue;
|
|
13949
14079
|
}
|
|
13950
14080
|
this.buffer.prepend(plan.right);
|
|
@@ -13953,6 +14083,11 @@
|
|
|
13953
14083
|
}
|
|
13954
14084
|
this.buffer.prepend(events);
|
|
13955
14085
|
console.warn("@newtalaria/browser: replays/ingestSegment failed", error);
|
|
14086
|
+
if (this.segmentIndex === 0) {
|
|
14087
|
+
await this.abortUnusableClip("upload_failed", {
|
|
14088
|
+
message: error instanceof Error ? error.message : String(error)
|
|
14089
|
+
});
|
|
14090
|
+
}
|
|
13956
14091
|
break;
|
|
13957
14092
|
}
|
|
13958
14093
|
if (opts.keepalive) break;
|
|
@@ -13967,7 +14102,8 @@
|
|
|
13967
14102
|
}
|
|
13968
14103
|
/**
|
|
13969
14104
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
13970
|
-
* Drops a single event that can never fit
|
|
14105
|
+
* Drops a single event that can never fit — except FullSnapshot on segment 0
|
|
14106
|
+
* (abort the clip instead of uploading a blank orphan stream).
|
|
13971
14107
|
*/
|
|
13972
14108
|
async takeFittedSegment() {
|
|
13973
14109
|
const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES);
|
|
@@ -13979,12 +14115,32 @@
|
|
|
13979
14115
|
MAX_COMPRESSED_SEGMENT_BYTES
|
|
13980
14116
|
);
|
|
13981
14117
|
if (!fitted) {
|
|
14118
|
+
const dropped = chunk[0];
|
|
13982
14119
|
console.warn(
|
|
13983
14120
|
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
13984
|
-
{ type:
|
|
14121
|
+
{ type: dropped?.type }
|
|
13985
14122
|
);
|
|
14123
|
+
if (this.segmentIndex === 0 && dropped?.type === RRWEB_FULL_SNAPSHOT) {
|
|
14124
|
+
return {
|
|
14125
|
+
events: [],
|
|
14126
|
+
gzip: new Uint8Array(),
|
|
14127
|
+
abortReason: "oversized_full_snapshot",
|
|
14128
|
+
abortDetails: { source: "fit_compressed_prefix" }
|
|
14129
|
+
};
|
|
14130
|
+
}
|
|
14131
|
+
if (chunk.length > 1) {
|
|
14132
|
+
this.buffer.prepend(chunk.slice(1));
|
|
14133
|
+
}
|
|
13986
14134
|
return this.takeFittedSegment();
|
|
13987
14135
|
}
|
|
14136
|
+
if (this.segmentIndex === 0 && chunk.some((e) => e.type === RRWEB_FULL_SNAPSHOT) && !fitted.events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14137
|
+
return {
|
|
14138
|
+
events: [],
|
|
14139
|
+
gzip: new Uint8Array(),
|
|
14140
|
+
abortReason: "oversized_full_snapshot",
|
|
14141
|
+
abortDetails: { source: "fit_dropped_full_snapshot" }
|
|
14142
|
+
};
|
|
14143
|
+
}
|
|
13988
14144
|
if (fitted.remainder.length > 0) {
|
|
13989
14145
|
this.buffer.prepend(fitted.remainder);
|
|
13990
14146
|
}
|