@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
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ await Talaria.flush();
|
|
|
53
53
|
await Talaria.close();
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
`Talaria.init` also installs `window.onerror` / `unhandledrejection` handlers unless you pass `disableDefaultIntegrations: true`.
|
|
56
|
+
`Talaria.init` also installs `window.onerror` / `unhandledrejection` handlers unless you pass `disableDefaultIntegrations: true`. Opaque cross-origin `"Script error."` events (no usable `Error` object) are ignored by default.
|
|
57
57
|
|
|
58
58
|
Optional init `tags` are merged into every captured event (per-call tags win on key conflict).
|
|
59
59
|
|
|
@@ -96,12 +96,24 @@ You pay for **uploaded + retained** bytes, not for local buffering. Prefer error
|
|
|
96
96
|
| --- | --- |
|
|
97
97
|
| Session sample hit | `replays/start` immediately; segments upload every ~5s or ~100KB until unload or **5 min** max |
|
|
98
98
|
| Session sample miss | Record into a ~60s ring buffer; nothing uploaded until an error sample hits |
|
|
99
|
-
| Error sample + `replaysErrorAfterMs > 0` | Upload buffer (
|
|
99
|
+
| Error sample + `replaysErrorAfterMs > 0` | Upload buffer (≤~960KiB gzip pack target / **1MiB** server cap) + trailing window, hard caps **12 segments** or **2MiB** compressed, attach `replayId` only if segments landed, `finish`, return to buffer mode |
|
|
100
100
|
| Error sample + `replaysErrorAfterMs = 0` | Upload buffer then continue like session mode until **5 min** / unload / size caps |
|
|
101
101
|
| Server limit (replay segments / total size / duration) | Stop uploading that replay; no retries |
|
|
102
|
-
| Oversized
|
|
102
|
+
| Oversized FullSnapshot on segment 0 | **Abort** the clip (no blank orphan upload); error event gets `replay.capture=failed` + reason |
|
|
103
|
+
| Oversized single non-FS rrweb event | Dropped with a console warning (cannot fit under segment cap) |
|
|
103
104
|
| `pagehide` / `close` | Flush pending segments with `fetch` `keepalive`, then `replays/finish`; `close` fully resets so `init()` works again (React Strict Mode) |
|
|
104
105
|
|
|
106
|
+
### Replay capture outcome tags
|
|
107
|
+
|
|
108
|
+
When an **error/fatal** event attempts an on-error clip (`replaysSessionSampleRate` miss), the SDK may attach:
|
|
109
|
+
|
|
110
|
+
| Tag | Values |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| `replay.capture` | `ok` \| `failed` \| `skipped` |
|
|
113
|
+
| `replay.capture_reason` | `oversized_full_snapshot` \| `no_full_snapshot` \| `upload_failed` \| `not_sampled` \| `buffer_empty` |
|
|
114
|
+
|
|
115
|
+
Failed captures do **not** set `replayId` (avoids linking a blank player). Details are also under `extra.replayCapture`.
|
|
116
|
+
|
|
105
117
|
## Serverpod URL pattern
|
|
106
118
|
|
|
107
119
|
Talaria uses **Serverpod RPC**, not REST resource URLs:
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CaptureContext, SeverityLevel, TalariaInitOptions } from './types.js';
|
|
2
2
|
export { computeErrorClipDeadlineMs, fitCompressedPrefix, isErrorClipBudgetExhausted, planOversizedRetry, } from './replay/fit_segment.js';
|
|
3
3
|
export { MAX_SEGMENTS_ERROR_CLIP, MAX_ERROR_CLIP_COMPRESSED_BYTES, TARGET_COMPRESSED_SEGMENT_BYTES, MAX_COMPRESSED_SEGMENT_BYTES, } from './replay/segment_buffer.js';
|
|
4
|
+
export { REPLAY_CAPTURE_TAG, REPLAY_CAPTURE_REASON_TAG, applyReplayCaptureTags, mergeReplayCaptureExtra, } from './replay/capture_outcome.js';
|
|
5
|
+
export type { ReplayCaptureOutcome, ReplayCaptureReason, ReplayCaptureStatus, } from './replay/capture_outcome.js';
|
|
4
6
|
export declare class TalariaClient {
|
|
5
7
|
private options;
|
|
6
8
|
private transport;
|
|
@@ -30,6 +32,8 @@ export declare class TalariaClient {
|
|
|
30
32
|
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
31
33
|
*/
|
|
32
34
|
private linkableReplayId;
|
|
35
|
+
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
36
|
+
private lastReplayCaptureFailure;
|
|
33
37
|
init(options: TalariaInitOptions): void;
|
|
34
38
|
getReplayId(): string | null;
|
|
35
39
|
captureException(error: unknown, context?: CaptureContext): Promise<void>;
|
|
@@ -69,8 +73,14 @@ export declare class TalariaClient {
|
|
|
69
73
|
/**
|
|
70
74
|
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
71
75
|
* produce a blank rrweb player.
|
|
76
|
+
* @returns false when no FullSnapshot is available.
|
|
72
77
|
*/
|
|
73
78
|
private prepareBufferForNewReplay;
|
|
79
|
+
/**
|
|
80
|
+
* Stop an error clip that cannot paint (missing/oversized FullSnapshot).
|
|
81
|
+
* Does not set linkableReplayId — the error event should not open a blank player.
|
|
82
|
+
*/
|
|
83
|
+
private abortUnusableClip;
|
|
74
84
|
private stopUploadingAfterLimit;
|
|
75
85
|
private installGlobalHandlers;
|
|
76
86
|
private enqueueUpload;
|
|
@@ -79,7 +89,8 @@ export declare class TalariaClient {
|
|
|
79
89
|
private maxSegmentsAllowed;
|
|
80
90
|
/**
|
|
81
91
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
82
|
-
* Drops a single event that can never fit
|
|
92
|
+
* Drops a single event that can never fit — except FullSnapshot on segment 0
|
|
93
|
+
* (abort the clip instead of uploading a blank orphan stream).
|
|
83
94
|
*/
|
|
84
95
|
private takeFittedSegment;
|
|
85
96
|
private finishOnServer;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -33,5 +33,7 @@ export { TalariaClient } from './client.js';
|
|
|
33
33
|
export { computeErrorClipDeadlineMs, fitCompressedPrefix, isErrorClipBudgetExhausted, planOversizedRetry, } from './replay/fit_segment.js';
|
|
34
34
|
export { MAX_SEGMENTS_ERROR_CLIP, MAX_ERROR_CLIP_COMPRESSED_BYTES, TARGET_COMPRESSED_SEGMENT_BYTES, MAX_COMPRESSED_SEGMENT_BYTES, } from './replay/segment_buffer.js';
|
|
35
35
|
export type { CaptureContext, Environment, FailedRequestStatusCode, SeverityLevel, TalariaInitOptions, } from './types.js';
|
|
36
|
+
export { REPLAY_CAPTURE_TAG, REPLAY_CAPTURE_REASON_TAG, } from './replay/capture_outcome.js';
|
|
37
|
+
export type { ReplayCaptureOutcome, ReplayCaptureReason, ReplayCaptureStatus, } from './replay/capture_outcome.js';
|
|
36
38
|
export default Talaria;
|
|
37
39
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,OAAO;kBACJ,kBAAkB,GAAG,IAAI;4BAIf,OAAO,YAAY,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;4BAK9D,MAAM,UACP,aAAa,YACX,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC;mBAID,MAAM,GAAG,IAAI;aAInB,OAAO,CAAC,IAAI,CAAC;aAIb,OAAO,CAAC,IAAI,CAAC;CAGvB,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,cAAc,EACd,WAAW,EACX,uBAAuB,EACvB,aAAa,EACb,kBAAkB,GACnB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,OAAO;kBACJ,kBAAkB,GAAG,IAAI;4BAIf,OAAO,YAAY,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;4BAK9D,MAAM,UACP,aAAa,YACX,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC;mBAID,MAAM,GAAG,IAAI;aAInB,OAAO,CAAC,IAAI,CAAC;aAIb,OAAO,CAAC,IAAI,CAAC;CAGvB,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,cAAc,EACd,WAAW,EACX,uBAAuB,EACvB,aAAa,EACb,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,6BAA6B,CAAC;AAErC,eAAe,OAAO,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,14 @@ function createId() {
|
|
|
15
15
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// src/utils/opaque_script_error.ts
|
|
19
|
+
function isOpaqueCrossOriginScriptError(event) {
|
|
20
|
+
const msg = (event.message ?? "").trim();
|
|
21
|
+
if (!/^script error\.?$/i.test(msg)) return false;
|
|
22
|
+
if (event.error instanceof Error) return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
// src/transport/serverpod.ts
|
|
19
27
|
var ServerpodTransport = class {
|
|
20
28
|
constructor(options) {
|
|
@@ -121,9 +129,9 @@ function estimateJsonBytes(value) {
|
|
|
121
129
|
}
|
|
122
130
|
|
|
123
131
|
// src/replay/segment_buffer.ts
|
|
124
|
-
var SEGMENT_SIZE_BYTES =
|
|
125
|
-
var MAX_COMPRESSED_SEGMENT_BYTES =
|
|
126
|
-
var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES -
|
|
132
|
+
var SEGMENT_SIZE_BYTES = 8e5;
|
|
133
|
+
var MAX_COMPRESSED_SEGMENT_BYTES = 1024 * 1024;
|
|
134
|
+
var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES - 64 * 1024;
|
|
127
135
|
var MAX_SEGMENTS_PER_REPLAY = 200;
|
|
128
136
|
var MAX_SEGMENTS_ERROR_CLIP = 12;
|
|
129
137
|
var MAX_ERROR_CLIP_COMPRESSED_BYTES = 2 * 1024 * 1024;
|
|
@@ -355,6 +363,33 @@ async function fitCompressedPrefix(events, compress, targetBytes = TARGET_COMPRE
|
|
|
355
363
|
return null;
|
|
356
364
|
}
|
|
357
365
|
|
|
366
|
+
// src/replay/capture_outcome.ts
|
|
367
|
+
var REPLAY_CAPTURE_TAG = "replay.capture";
|
|
368
|
+
var REPLAY_CAPTURE_REASON_TAG = "replay.capture_reason";
|
|
369
|
+
function applyReplayCaptureTags(tags, outcome) {
|
|
370
|
+
if (!outcome) return tags;
|
|
371
|
+
const next = { ...tags };
|
|
372
|
+
next[REPLAY_CAPTURE_TAG] = outcome.status;
|
|
373
|
+
if (outcome.reason) {
|
|
374
|
+
next[REPLAY_CAPTURE_REASON_TAG] = outcome.reason;
|
|
375
|
+
}
|
|
376
|
+
return next;
|
|
377
|
+
}
|
|
378
|
+
function mergeReplayCaptureExtra(extra, outcome) {
|
|
379
|
+
if (!outcome || outcome.status === "ok") {
|
|
380
|
+
return extra;
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
...extra ?? {},
|
|
384
|
+
replayCapture: {
|
|
385
|
+
attempted: outcome.status === "failed",
|
|
386
|
+
status: outcome.status,
|
|
387
|
+
reason: outcome.reason,
|
|
388
|
+
...outcome.details ?? {}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
358
393
|
// ../../node_modules/rrweb/dist/rrweb.js
|
|
359
394
|
var __defProp = Object.defineProperty;
|
|
360
395
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -13399,6 +13434,8 @@ var TalariaClient = class {
|
|
|
13399
13434
|
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
13400
13435
|
*/
|
|
13401
13436
|
this.linkableReplayId = null;
|
|
13437
|
+
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
13438
|
+
this.lastReplayCaptureFailure = null;
|
|
13402
13439
|
}
|
|
13403
13440
|
init(options) {
|
|
13404
13441
|
if (this.options) {
|
|
@@ -13420,6 +13457,7 @@ var TalariaClient = class {
|
|
|
13420
13457
|
this.uploadStartedAtMs = null;
|
|
13421
13458
|
this.errorClipDeadlineMs = null;
|
|
13422
13459
|
this.linkableReplayId = null;
|
|
13460
|
+
this.lastReplayCaptureFailure = null;
|
|
13423
13461
|
this.buffer = new SegmentBuffer();
|
|
13424
13462
|
this.uploadChain = Promise.resolve();
|
|
13425
13463
|
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
@@ -13581,6 +13619,7 @@ var TalariaClient = class {
|
|
|
13581
13619
|
this.uploadChain = Promise.resolve();
|
|
13582
13620
|
this.closed = false;
|
|
13583
13621
|
this.linkableReplayId = null;
|
|
13622
|
+
this.lastReplayCaptureFailure = null;
|
|
13584
13623
|
}
|
|
13585
13624
|
onRrwebEvent(event) {
|
|
13586
13625
|
if (this.closed || !this.options) return;
|
|
@@ -13606,15 +13645,22 @@ var TalariaClient = class {
|
|
|
13606
13645
|
throw new Error("@newtalaria/browser: call Talaria.init() first");
|
|
13607
13646
|
}
|
|
13608
13647
|
const isErrorLike = args.level === "error" || args.level === "fatal";
|
|
13648
|
+
let errorClipOutcome = null;
|
|
13649
|
+
let attemptedErrorClip = false;
|
|
13609
13650
|
if (isErrorLike && !this.sessionSampled) {
|
|
13610
13651
|
if (!this.uploadEnabled) {
|
|
13611
13652
|
if (Math.random() < this.options.replaysOnErrorSampleRate) {
|
|
13653
|
+
attemptedErrorClip = true;
|
|
13654
|
+
this.lastReplayCaptureFailure = null;
|
|
13612
13655
|
this.uploadEnabled = true;
|
|
13613
13656
|
this.checkoutFullSnapshot();
|
|
13614
13657
|
this.markUploadStarted();
|
|
13615
13658
|
this.scheduleErrorClipEnd();
|
|
13659
|
+
} else {
|
|
13660
|
+
errorClipOutcome = { status: "skipped", reason: "not_sampled" };
|
|
13616
13661
|
}
|
|
13617
13662
|
} else {
|
|
13663
|
+
attemptedErrorClip = true;
|
|
13618
13664
|
this.scheduleErrorClipEnd();
|
|
13619
13665
|
}
|
|
13620
13666
|
}
|
|
@@ -13623,15 +13669,49 @@ var TalariaClient = class {
|
|
|
13623
13669
|
await this.flush({ reason: "capture", keepalive: args.keepalive });
|
|
13624
13670
|
} catch (error) {
|
|
13625
13671
|
console.warn("@newtalaria/browser: replay flush failed", error);
|
|
13626
|
-
|
|
13672
|
+
this.lastReplayCaptureFailure = {
|
|
13673
|
+
reason: "upload_failed",
|
|
13674
|
+
details: {
|
|
13675
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13676
|
+
}
|
|
13677
|
+
};
|
|
13678
|
+
if (!this.sessionSampled && this.segmentIndex === 0) {
|
|
13679
|
+
await this.abortUnusableClip("upload_failed", {
|
|
13680
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13681
|
+
});
|
|
13682
|
+
}
|
|
13627
13683
|
}
|
|
13628
13684
|
}
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
}
|
|
13633
|
-
|
|
13634
|
-
|
|
13685
|
+
let replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
|
|
13686
|
+
if (this.lastReplayCaptureFailure && this.segmentIndex === 0) {
|
|
13687
|
+
replayId = null;
|
|
13688
|
+
}
|
|
13689
|
+
if (isErrorLike && !this.sessionSampled) {
|
|
13690
|
+
if (replayId) {
|
|
13691
|
+
errorClipOutcome = { status: "ok" };
|
|
13692
|
+
this.lastReplayCaptureFailure = null;
|
|
13693
|
+
} else if (errorClipOutcome?.status === "skipped") {
|
|
13694
|
+
} else if (this.lastReplayCaptureFailure) {
|
|
13695
|
+
errorClipOutcome = {
|
|
13696
|
+
status: "failed",
|
|
13697
|
+
reason: this.lastReplayCaptureFailure.reason,
|
|
13698
|
+
details: this.lastReplayCaptureFailure.details
|
|
13699
|
+
};
|
|
13700
|
+
} else if (attemptedErrorClip) {
|
|
13701
|
+
errorClipOutcome = { status: "failed", reason: "buffer_empty" };
|
|
13702
|
+
}
|
|
13703
|
+
}
|
|
13704
|
+
const tags = applyReplayCaptureTags(
|
|
13705
|
+
{
|
|
13706
|
+
...this.options.tags ?? {},
|
|
13707
|
+
...args.context?.tags ?? {}
|
|
13708
|
+
},
|
|
13709
|
+
errorClipOutcome
|
|
13710
|
+
);
|
|
13711
|
+
const extra = mergeReplayCaptureExtra(
|
|
13712
|
+
args.context?.extra,
|
|
13713
|
+
errorClipOutcome
|
|
13714
|
+
);
|
|
13635
13715
|
try {
|
|
13636
13716
|
await ingestEvent(this.transport, {
|
|
13637
13717
|
message: args.message,
|
|
@@ -13643,7 +13723,7 @@ var TalariaClient = class {
|
|
|
13643
13723
|
release: this.options.release,
|
|
13644
13724
|
userId: args.context?.userId ?? this.options.userId,
|
|
13645
13725
|
sessionId: this.sessionId ?? void 0,
|
|
13646
|
-
replayId,
|
|
13726
|
+
replayId: replayId ?? void 0,
|
|
13647
13727
|
url: typeof location !== "undefined" ? location.href : void 0,
|
|
13648
13728
|
tags: Object.keys(tags).length ? tags : void 0,
|
|
13649
13729
|
extraJson: extra ? JSON.stringify(extra) : void 0,
|
|
@@ -13788,9 +13868,10 @@ var TalariaClient = class {
|
|
|
13788
13868
|
/**
|
|
13789
13869
|
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
13790
13870
|
* produce a blank rrweb player.
|
|
13871
|
+
* @returns false when no FullSnapshot is available.
|
|
13791
13872
|
*/
|
|
13792
13873
|
prepareBufferForNewReplay() {
|
|
13793
|
-
if (this.segmentIndex !== 0) return;
|
|
13874
|
+
if (this.segmentIndex !== 0) return true;
|
|
13794
13875
|
if (!this.buffer.hasFullSnapshot()) {
|
|
13795
13876
|
this.checkoutFullSnapshot();
|
|
13796
13877
|
}
|
|
@@ -13798,6 +13879,40 @@ var TalariaClient = class {
|
|
|
13798
13879
|
console.warn(
|
|
13799
13880
|
"@newtalaria/browser: replay has no FullSnapshot; player may show a blank screen"
|
|
13800
13881
|
);
|
|
13882
|
+
return false;
|
|
13883
|
+
}
|
|
13884
|
+
return true;
|
|
13885
|
+
}
|
|
13886
|
+
/**
|
|
13887
|
+
* Stop an error clip that cannot paint (missing/oversized FullSnapshot).
|
|
13888
|
+
* Does not set linkableReplayId — the error event should not open a blank player.
|
|
13889
|
+
*/
|
|
13890
|
+
async abortUnusableClip(reason, details) {
|
|
13891
|
+
this.lastReplayCaptureFailure = { reason, details };
|
|
13892
|
+
console.warn("@newtalaria/browser: aborting unusable replay clip", {
|
|
13893
|
+
reason,
|
|
13894
|
+
...details
|
|
13895
|
+
});
|
|
13896
|
+
this.buffer.clear();
|
|
13897
|
+
this.linkableReplayId = null;
|
|
13898
|
+
if (this.startedOnServer && !this.finishedOnServer && this.transport && this.replayId) {
|
|
13899
|
+
try {
|
|
13900
|
+
await finishReplay(this.transport, {
|
|
13901
|
+
replayId: this.replayId,
|
|
13902
|
+
reason: `capture_failed:${reason}`,
|
|
13903
|
+
keepalive: false
|
|
13904
|
+
});
|
|
13905
|
+
this.finishedOnServer = true;
|
|
13906
|
+
} catch (error) {
|
|
13907
|
+
console.warn("@newtalaria/browser: replays/finish failed after abort", error);
|
|
13908
|
+
}
|
|
13909
|
+
}
|
|
13910
|
+
if (!this.sessionSampled) {
|
|
13911
|
+
this.resetToBufferMode();
|
|
13912
|
+
} else {
|
|
13913
|
+
this.uploadEnabled = false;
|
|
13914
|
+
this.clearErrorClipTimer();
|
|
13915
|
+
this.clearMaxDurationTimer();
|
|
13801
13916
|
}
|
|
13802
13917
|
}
|
|
13803
13918
|
stopUploadingAfterLimit() {
|
|
@@ -13816,6 +13931,7 @@ var TalariaClient = class {
|
|
|
13816
13931
|
installGlobalHandlers() {
|
|
13817
13932
|
if (typeof window === "undefined") return;
|
|
13818
13933
|
const onError = (event) => {
|
|
13934
|
+
if (isOpaqueCrossOriginScriptError(event)) return;
|
|
13819
13935
|
const error = event.error instanceof Error ? event.error : new Error(event.message || "window.onerror");
|
|
13820
13936
|
void this.captureException(error, {
|
|
13821
13937
|
extra: {
|
|
@@ -13858,7 +13974,10 @@ var TalariaClient = class {
|
|
|
13858
13974
|
async uploadPendingSegments(opts) {
|
|
13859
13975
|
if (!this.options || !this.transport || !this.replayId) return;
|
|
13860
13976
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13861
|
-
this.prepareBufferForNewReplay()
|
|
13977
|
+
if (!this.prepareBufferForNewReplay()) {
|
|
13978
|
+
await this.abortUnusableClip("no_full_snapshot");
|
|
13979
|
+
return;
|
|
13980
|
+
}
|
|
13862
13981
|
while (this.buffer.length > 0) {
|
|
13863
13982
|
if (this.isPastMaxDuration()) {
|
|
13864
13983
|
break;
|
|
@@ -13898,7 +14017,17 @@ var TalariaClient = class {
|
|
|
13898
14017
|
}
|
|
13899
14018
|
const fitted = await this.takeFittedSegment();
|
|
13900
14019
|
if (!fitted) break;
|
|
14020
|
+
if (fitted.abortReason) {
|
|
14021
|
+
await this.abortUnusableClip(fitted.abortReason, fitted.abortDetails);
|
|
14022
|
+
return;
|
|
14023
|
+
}
|
|
13901
14024
|
const { events, gzip } = fitted;
|
|
14025
|
+
if (this.segmentIndex === 0 && !events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14026
|
+
await this.abortUnusableClip("no_full_snapshot", {
|
|
14027
|
+
segmentEventTypes: events.map((e) => e.type)
|
|
14028
|
+
});
|
|
14029
|
+
return;
|
|
14030
|
+
}
|
|
13902
14031
|
if (this.isErrorClipMode() && this.uploadedCompressedBytes + gzip.length > MAX_ERROR_CLIP_COMPRESSED_BYTES && this.segmentIndex > 0) {
|
|
13903
14032
|
this.buffer.clear();
|
|
13904
14033
|
await this.finishOnServer({
|
|
@@ -13923,6 +14052,9 @@ var TalariaClient = class {
|
|
|
13923
14052
|
});
|
|
13924
14053
|
this.segmentIndex = index2 + 1;
|
|
13925
14054
|
this.uploadedCompressedBytes += gzip.length;
|
|
14055
|
+
if (index2 === 0) {
|
|
14056
|
+
this.lastReplayCaptureFailure = null;
|
|
14057
|
+
}
|
|
13926
14058
|
} catch (error) {
|
|
13927
14059
|
if (isTerminalReplayLimitError(error)) {
|
|
13928
14060
|
console.warn(
|
|
@@ -13939,10 +14071,17 @@ var TalariaClient = class {
|
|
|
13939
14071
|
if (isOversizedSegmentError(error)) {
|
|
13940
14072
|
const plan = planOversizedRetry(events);
|
|
13941
14073
|
if (plan.action === "drop") {
|
|
14074
|
+
const droppedType = events[0]?.type;
|
|
13942
14075
|
console.warn(
|
|
13943
14076
|
"@newtalaria/browser: dropping rrweb event rejected as oversized",
|
|
13944
|
-
{ type:
|
|
14077
|
+
{ type: droppedType }
|
|
13945
14078
|
);
|
|
14079
|
+
if (this.segmentIndex === 0 && droppedType === RRWEB_FULL_SNAPSHOT) {
|
|
14080
|
+
await this.abortUnusableClip("oversized_full_snapshot", {
|
|
14081
|
+
source: "server_reject"
|
|
14082
|
+
});
|
|
14083
|
+
return;
|
|
14084
|
+
}
|
|
13946
14085
|
continue;
|
|
13947
14086
|
}
|
|
13948
14087
|
this.buffer.prepend(plan.right);
|
|
@@ -13951,6 +14090,11 @@ var TalariaClient = class {
|
|
|
13951
14090
|
}
|
|
13952
14091
|
this.buffer.prepend(events);
|
|
13953
14092
|
console.warn("@newtalaria/browser: replays/ingestSegment failed", error);
|
|
14093
|
+
if (this.segmentIndex === 0) {
|
|
14094
|
+
await this.abortUnusableClip("upload_failed", {
|
|
14095
|
+
message: error instanceof Error ? error.message : String(error)
|
|
14096
|
+
});
|
|
14097
|
+
}
|
|
13954
14098
|
break;
|
|
13955
14099
|
}
|
|
13956
14100
|
if (opts.keepalive) break;
|
|
@@ -13965,7 +14109,8 @@ var TalariaClient = class {
|
|
|
13965
14109
|
}
|
|
13966
14110
|
/**
|
|
13967
14111
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
13968
|
-
* Drops a single event that can never fit
|
|
14112
|
+
* Drops a single event that can never fit — except FullSnapshot on segment 0
|
|
14113
|
+
* (abort the clip instead of uploading a blank orphan stream).
|
|
13969
14114
|
*/
|
|
13970
14115
|
async takeFittedSegment() {
|
|
13971
14116
|
const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES);
|
|
@@ -13977,12 +14122,32 @@ var TalariaClient = class {
|
|
|
13977
14122
|
MAX_COMPRESSED_SEGMENT_BYTES
|
|
13978
14123
|
);
|
|
13979
14124
|
if (!fitted) {
|
|
14125
|
+
const dropped = chunk[0];
|
|
13980
14126
|
console.warn(
|
|
13981
14127
|
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
13982
|
-
{ type:
|
|
14128
|
+
{ type: dropped?.type }
|
|
13983
14129
|
);
|
|
14130
|
+
if (this.segmentIndex === 0 && dropped?.type === RRWEB_FULL_SNAPSHOT) {
|
|
14131
|
+
return {
|
|
14132
|
+
events: [],
|
|
14133
|
+
gzip: new Uint8Array(),
|
|
14134
|
+
abortReason: "oversized_full_snapshot",
|
|
14135
|
+
abortDetails: { source: "fit_compressed_prefix" }
|
|
14136
|
+
};
|
|
14137
|
+
}
|
|
14138
|
+
if (chunk.length > 1) {
|
|
14139
|
+
this.buffer.prepend(chunk.slice(1));
|
|
14140
|
+
}
|
|
13984
14141
|
return this.takeFittedSegment();
|
|
13985
14142
|
}
|
|
14143
|
+
if (this.segmentIndex === 0 && chunk.some((e) => e.type === RRWEB_FULL_SNAPSHOT) && !fitted.events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14144
|
+
return {
|
|
14145
|
+
events: [],
|
|
14146
|
+
gzip: new Uint8Array(),
|
|
14147
|
+
abortReason: "oversized_full_snapshot",
|
|
14148
|
+
abortDetails: { source: "fit_dropped_full_snapshot" }
|
|
14149
|
+
};
|
|
14150
|
+
}
|
|
13986
14151
|
if (fitted.remainder.length > 0) {
|
|
13987
14152
|
this.buffer.prepend(fitted.remainder);
|
|
13988
14153
|
}
|
|
@@ -14034,6 +14199,8 @@ export {
|
|
|
14034
14199
|
MAX_COMPRESSED_SEGMENT_BYTES,
|
|
14035
14200
|
MAX_ERROR_CLIP_COMPRESSED_BYTES,
|
|
14036
14201
|
MAX_SEGMENTS_ERROR_CLIP,
|
|
14202
|
+
REPLAY_CAPTURE_REASON_TAG,
|
|
14203
|
+
REPLAY_CAPTURE_TAG,
|
|
14037
14204
|
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
14038
14205
|
Talaria,
|
|
14039
14206
|
TalariaClient,
|