@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 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 (≤480KiB gzip pack target / 512KiB server cap) + trailing window, hard caps **12 segments** or **2MiB** compressed, attach `replayId` only if segments landed, `finish`, return to buffer mode |
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 single rrweb event | Dropped with a console warning (cannot fit under segment cap) |
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;
@@ -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;AAoCpB,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;AA4EpC,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,SAAS,CAAmC;IACpD,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,MAAM,CAAuB;IACrC,yEAAyE;IACzE,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,uBAAuB,CAAK;IACpC,0EAA0E;IAC1E,OAAO,CAAC,iBAAiB,CAAuB;IAChD,4EAA4E;IAC5E,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,UAAU,CAA+C;IACjE,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,gBAAgB,CAA8C;IACtE,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAkB;IACnC;;;OAGG;IACH,OAAO,CAAC,gBAAgB,CAAuB;IAE/C,IAAI,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IA6FvC,WAAW,IAAI,MAAM,GAAG,IAAI;IAKtB,gBAAgB,CACpB,KAAK,EAAE,OAAO,EACd,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC;IAcV,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,aAAsB,EAC7B,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC;IASV,KAAK,CAAC,IAAI,CAAC,EAAE;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0CjB;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmD5B,OAAO,CAAC,YAAY;YAwBN,OAAO;IA+ErB,OAAO,CAAC,iBAAiB;IAkBzB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,uBAAuB;IAiB/B,OAAO,CAAC,qBAAqB;YAOf,qBAAqB;IAsBnC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAmB5B,OAAO,CAAC,mBAAmB;IAO3B,8EAA8E;YAChE,YAAY;YAgBZ,eAAe;IAW7B,oFAAoF;IACpF,OAAO,CAAC,iBAAiB;IAiBzB,+EAA+E;IAC/E,OAAO,CAAC,oBAAoB;IAI5B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;IAYjC,OAAO,CAAC,uBAAuB;IAc/B,OAAO,CAAC,qBAAqB;IA6B7B,OAAO,CAAC,aAAa;YAKP,aAAa;YAkBb,qBAAqB;IA8HnC,OAAO,CAAC,kBAAkB;IAQ1B;;;OAGG;YACW,iBAAiB;YA2BjB,cAAc;CAuB7B"}
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
@@ -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;AAEpB,eAAe,OAAO,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 = 4e5;
125
- var MAX_COMPRESSED_SEGMENT_BYTES = 512 * 1024;
126
- var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES - 32 * 1024;
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
- throw error;
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
- const tags = {
13630
- ...this.options.tags ?? {},
13631
- ...args.context?.tags ?? {}
13632
- };
13633
- const extra = args.context?.extra;
13634
- const replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
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: events[0]?.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: chunk[0]?.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,