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