@newtalaria/browser 0.1.9 → 0.1.10

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.
@@ -23,11 +23,19 @@ export declare function planOversizedRetry(events: RrwebEvent[]): {
23
23
  left: RrwebEvent[];
24
24
  right: RrwebEvent[];
25
25
  };
26
+ /** Leading Meta+FullSnapshot (2) or FullSnapshot alone (1); else 0. */
27
+ export declare function paintBaseEventCount(events: RrwebEvent[]): number;
26
28
  /**
27
29
  * Binary-search the largest prefix that gzips under the pack target.
28
30
  * Remainder is returned for re-queue; null means every event was dropped.
31
+ *
32
+ * When `atomicMinCount` is set (segment-0 paint base), never return a prefix
33
+ * shorter than that count unless the atomic unit itself exceeds `hardCap`
34
+ * (then null — caller should abort as oversized, not ship Meta-only).
29
35
  */
30
- export declare function fitCompressedPrefix(events: RrwebEvent[], compress: CompressEvents, targetBytes?: number, hardCap?: number): Promise<{
36
+ export declare function fitCompressedPrefix(events: RrwebEvent[], compress: CompressEvents, targetBytes?: number, hardCap?: number, opts?: {
37
+ atomicMinCount?: number;
38
+ }): Promise<{
31
39
  events: RrwebEvent[];
32
40
  gzip: Uint8Array;
33
41
  remainder: RrwebEvent[];
@@ -1 +1 @@
1
- {"version":3,"file":"fit_segment.d.ts","sourceRoot":"","sources":["../../src/replay/fit_segment.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3E;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,iBAAiB,EAAE,MAAM,EACzB,OAAO,EAAE,MAAM,EACf,OAAO,SAA4B,GAClC,MAAM,CAER;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,EAAE,MAAM,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAOV;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAAE,GACnB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAUpF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,EAAE,EACpB,QAAQ,EAAE,cAAc,EACxB,WAAW,SAAkC,EAC7C,OAAO,SAA+B,GACrC,OAAO,CAAC;IACT,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,UAAU,EAAE,CAAC;CACzB,GAAG,IAAI,CAAC,CAkCR"}
1
+ {"version":3,"file":"fit_segment.d.ts","sourceRoot":"","sources":["../../src/replay/fit_segment.ts"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3E;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,iBAAiB,EAAE,MAAM,EACzB,OAAO,EAAE,MAAM,EACf,OAAO,SAA4B,GAClC,MAAM,CAER;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,YAAY,EAAE,MAAM,CAAC;IACrB,uBAAuB,EAAE,MAAM,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAOV;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAAE,GACnB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,UAAU,EAAE,CAAA;CAAE,CAUpF;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,CAYhE;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,UAAU,EAAE,EACpB,QAAQ,EAAE,cAAc,EACxB,WAAW,SAAkC,EAC7C,OAAO,SAA+B,EACtC,IAAI,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACjC,OAAO,CAAC;IACT,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,UAAU,EAAE,CAAC;CACzB,GAAG,IAAI,CAAC,CA2ER"}
@@ -8,7 +8,8 @@ export type PaintBaseSizeDetails = {
8
8
  fullSnapshotEstimatedBytes?: number;
9
9
  /** Actual gzip size of FullSnapshot alone, when measured. */
10
10
  fullSnapshotCompressedBytes?: number;
11
- maxUncompressedSegmentBytes: number;
11
+ /** Soft estimated take window (not the server uncompressed hard cap). */
12
+ softTakeEstimatedBytes: number;
12
13
  targetCompressedSegmentBytes: number;
13
14
  maxCompressedSegmentBytes: number;
14
15
  } & Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"paint_base_size.d.ts","sourceRoot":"","sources":["../../src/replay/paint_base_size.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAE7B,gEAAgE;AAChE,MAAM,MAAM,oBAAoB,GAAG;IACjC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,6DAA6D;IAC7D,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,2BAA2B,EAAE,MAAM,CAAC;IACpC,4BAA4B,EAAE,MAAM,CAAC;IACrC,yBAAyB,EAAE,MAAM,CAAC;CACnC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAAE,EACpB,IAAI,CAAC,EAAE;IAAE,2BAA2B,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,oBAAoB,CAsBtB"}
1
+ {"version":3,"file":"paint_base_size.d.ts","sourceRoot":"","sources":["../../src/replay/paint_base_size.ts"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAE7B,gEAAgE;AAChE,MAAM,MAAM,oBAAoB,GAAG;IACjC,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,0DAA0D;IAC1D,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,6DAA6D;IAC7D,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,yEAAyE;IACzE,sBAAsB,EAAE,MAAM,CAAC;IAC/B,4BAA4B,EAAE,MAAM,CAAC;IACrC,yBAAyB,EAAE,MAAM,CAAC;CACnC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE5B,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAAE,EACpB,IAAI,CAAC,EAAE;IAAE,2BAA2B,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9C,oBAAoB,CAsBtB"}
@@ -5,8 +5,9 @@ export interface RrwebEvent {
5
5
  timestamp: number;
6
6
  }
7
7
  /**
8
- * Soft uncompressed take size before gzip fitting.
9
- * Keep in sync with ReplayLimits.maxUncompressedSegmentBytes intent.
8
+ * Soft estimated-JSON take size before gzip fitting.
9
+ * Not a hard server limit — packing uses MAX_COMPRESSED_SEGMENT_BYTES.
10
+ * Paint base (Meta+FullSnapshot) is taken atomically and may exceed this.
10
11
  */
11
12
  export declare const SEGMENT_SIZE_BYTES = 800000;
12
13
  /**
@@ -66,12 +67,23 @@ export declare class SegmentBuffer {
66
67
  */
67
68
  trimRing(now?: number, maxBytes?: number): void;
68
69
  shouldFlushBySize(): boolean;
70
+ /**
71
+ * How many leading events form the rrweb paint base (Meta+FullSnapshot or
72
+ * FullSnapshot alone). Returns 0 if the buffer does not start with a paint base.
73
+ */
74
+ paintBaseLength(): number;
69
75
  /**
70
76
  * Take a prefix of events whose estimated JSON size is ~maxBytes.
71
77
  * Always returns at least one event when the buffer is non-empty (caller
72
78
  * must handle a single oversized event).
79
+ *
80
+ * When `keepPaintBaseTogether` is true (segment 0), Meta immediately followed
81
+ * by FullSnapshot is taken as one unit even if estimated size exceeds
82
+ * maxBytes — gzip fitting / hard caps decide whether it can ship.
73
83
  */
74
- takeByEstimatedBytes(maxBytes: number): RrwebEvent[];
84
+ takeByEstimatedBytes(maxBytes: number, opts?: {
85
+ keepPaintBaseTogether?: boolean;
86
+ }): RrwebEvent[];
75
87
  takeAll(): RrwebEvent[];
76
88
  /** Re-queue events at the front (failed upload retry / bisect remainder). */
77
89
  prepend(events: RrwebEvent[]): void;
@@ -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,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,4DAA4D;IAC5D,gBAAgB,IAAI,UAAU,GAAG,SAAS;IAI1C;;;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"}
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;;;;GAIG;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,4DAA4D;IAC5D,gBAAgB,IAAI,UAAU,GAAG,SAAS;IAI1C;;;OAGG;IACH,kBAAkB,IAAI,OAAO;IAc7B;;;OAGG;IACH,QAAQ,CAAC,GAAG,SAAa,EAAE,QAAQ,SAAwB,GAAG,IAAI;IAiBlE,iBAAiB,IAAI,OAAO;IAI5B;;;OAGG;IACH,eAAe,IAAI,MAAM;IAczB;;;;;;;;OAQG;IACH,oBAAoB,CAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;QAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;KAAE,GACzC,UAAU,EAAE;IAqCf,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"}
@@ -206,15 +206,44 @@
206
206
  shouldFlushBySize() {
207
207
  return this.pendingBytes >= SEGMENT_SIZE_BYTES;
208
208
  }
209
+ /**
210
+ * How many leading events form the rrweb paint base (Meta+FullSnapshot or
211
+ * FullSnapshot alone). Returns 0 if the buffer does not start with a paint base.
212
+ */
213
+ paintBaseLength() {
214
+ if (this.events.length === 0) return 0;
215
+ if (this.events[0].type === RRWEB_META && this.events[1]?.type === RRWEB_FULL_SNAPSHOT) {
216
+ return 2;
217
+ }
218
+ if (this.events[0].type === RRWEB_FULL_SNAPSHOT) {
219
+ return 1;
220
+ }
221
+ return 0;
222
+ }
209
223
  /**
210
224
  * Take a prefix of events whose estimated JSON size is ~maxBytes.
211
225
  * Always returns at least one event when the buffer is non-empty (caller
212
226
  * must handle a single oversized event).
227
+ *
228
+ * When `keepPaintBaseTogether` is true (segment 0), Meta immediately followed
229
+ * by FullSnapshot is taken as one unit even if estimated size exceeds
230
+ * maxBytes — gzip fitting / hard caps decide whether it can ship.
213
231
  */
214
- takeByEstimatedBytes(maxBytes) {
232
+ takeByEstimatedBytes(maxBytes, opts) {
215
233
  if (this.events.length === 0) return [];
216
234
  const batch = [];
217
235
  let batchBytes = 0;
236
+ if (opts?.keepPaintBaseTogether) {
237
+ const paintLen = this.paintBaseLength();
238
+ if (paintLen > 0) {
239
+ for (let i = 0; i < paintLen; i++) {
240
+ const ev = this.events.shift();
241
+ batch.push(ev);
242
+ batchBytes += estimateJsonBytes(ev);
243
+ }
244
+ this.recomputeBytes();
245
+ }
246
+ }
218
247
  while (this.events.length > 0) {
219
248
  const next = this.events[0];
220
249
  const nextBytes = estimateJsonBytes(next);
@@ -339,8 +368,53 @@
339
368
  right: events.slice(mid)
340
369
  };
341
370
  }
342
- async function fitCompressedPrefix(events, compress, targetBytes = TARGET_COMPRESSED_SEGMENT_BYTES, hardCap = MAX_COMPRESSED_SEGMENT_BYTES) {
371
+ function paintBaseEventCount(events) {
372
+ if (events.length === 0) return 0;
373
+ if (events[0].type === RRWEB_META && events[1]?.type === RRWEB_FULL_SNAPSHOT) {
374
+ return 2;
375
+ }
376
+ if (events[0].type === RRWEB_FULL_SNAPSHOT) {
377
+ return 1;
378
+ }
379
+ return 0;
380
+ }
381
+ async function fitCompressedPrefix(events, compress, targetBytes = TARGET_COMPRESSED_SEGMENT_BYTES, hardCap = MAX_COMPRESSED_SEGMENT_BYTES, opts) {
343
382
  if (events.length === 0) return null;
383
+ const atomicMin = opts?.atomicMinCount ?? 0;
384
+ if (atomicMin > 0) {
385
+ if (events.length < atomicMin) return null;
386
+ const paint = events.slice(0, atomicMin);
387
+ const paintGzip = await compress(paint);
388
+ if (paintGzip.length > hardCap) {
389
+ return null;
390
+ }
391
+ let lo = atomicMin;
392
+ let hi = events.length;
393
+ let best = {
394
+ events: paint,
395
+ gzip: paintGzip
396
+ };
397
+ while (lo <= hi) {
398
+ const mid = Math.floor((lo + hi) / 2);
399
+ if (mid < atomicMin) {
400
+ lo = atomicMin;
401
+ continue;
402
+ }
403
+ const prefix = events.slice(0, mid);
404
+ const gzip = await compress(prefix);
405
+ if (gzip.length <= targetBytes && gzip.length <= hardCap) {
406
+ best = { events: prefix, gzip };
407
+ lo = mid + 1;
408
+ } else {
409
+ hi = mid - 1;
410
+ }
411
+ }
412
+ return {
413
+ events: best.events,
414
+ gzip: best.gzip,
415
+ remainder: events.slice(best.events.length)
416
+ };
417
+ }
344
418
  let remaining = events;
345
419
  while (remaining.length > 0) {
346
420
  let lo = 1;
@@ -402,7 +476,7 @@
402
476
  const full = events.find((e) => e.type === RRWEB_FULL_SNAPSHOT);
403
477
  const details = {
404
478
  segmentEventTypes: events.map((e) => e.type),
405
- maxUncompressedSegmentBytes: SEGMENT_SIZE_BYTES,
479
+ softTakeEstimatedBytes: SEGMENT_SIZE_BYTES,
406
480
  targetCompressedSegmentBytes: TARGET_COMPRESSED_SEGMENT_BYTES,
407
481
  maxCompressedSegmentBytes: MAX_COMPRESSED_SEGMENT_BYTES
408
482
  };
@@ -14055,20 +14129,9 @@
14055
14129
  }
14056
14130
  const { events, gzip } = fitted;
14057
14131
  if (this.segmentIndex === 0 && !events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
14058
- const leftoverFs = this.buffer.peekFullSnapshot();
14059
- let compressedBytes;
14060
- if (leftoverFs) {
14061
- try {
14062
- compressedBytes = (await compressReplayEvents([leftoverFs])).length;
14063
- } catch {
14064
- }
14065
- }
14066
14132
  await this.abortUnusableClip(
14067
14133
  "no_full_snapshot",
14068
- paintBaseSizeDetails(
14069
- leftoverFs ? [...events, leftoverFs] : events,
14070
- { fullSnapshotCompressedBytes: compressedBytes }
14071
- )
14134
+ paintBaseSizeDetails(events)
14072
14135
  );
14073
14136
  return;
14074
14137
  }
@@ -14113,6 +14176,14 @@
14113
14176
  break;
14114
14177
  }
14115
14178
  if (isOversizedSegmentError(error)) {
14179
+ if (this.segmentIndex === 0 && paintBaseEventCount(events) > 0) {
14180
+ await this.abortUnusableClip("oversized_full_snapshot", {
14181
+ source: "server_reject_paint_base",
14182
+ ...paintBaseSizeDetails(events),
14183
+ rejectedCompressedBytes: gzip.length
14184
+ });
14185
+ return;
14186
+ }
14116
14187
  const plan = planOversizedRetry(events);
14117
14188
  if (plan.action === "drop") {
14118
14189
  const droppedType = events[0]?.type;
@@ -14120,7 +14191,7 @@
14120
14191
  "@newtalaria/browser: dropping rrweb event rejected as oversized",
14121
14192
  { type: droppedType }
14122
14193
  );
14123
- if (this.segmentIndex === 0 && droppedType === RRWEB_FULL_SNAPSHOT) {
14194
+ if (droppedType === RRWEB_FULL_SNAPSHOT) {
14124
14195
  await this.abortUnusableClip(
14125
14196
  "oversized_full_snapshot",
14126
14197
  {
@@ -14158,68 +14229,60 @@
14158
14229
  }
14159
14230
  /**
14160
14231
  * Pull the largest event prefix that gzips under the target compressed size.
14161
- * Drops a single event that can never fit — except FullSnapshot on segment 0
14162
- * (abort the clip instead of uploading a blank orphan stream).
14232
+ *
14233
+ * Segment 0 keeps Meta+FullSnapshot (paint base) atomic through take + fit so
14234
+ * a soft estimated-byte window cannot orphan the FullSnapshot.
14163
14235
  */
14164
14236
  async takeFittedSegment() {
14165
- const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES);
14237
+ const isPaintBaseSegment = this.segmentIndex === 0;
14238
+ const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES, {
14239
+ keepPaintBaseTogether: isPaintBaseSegment
14240
+ });
14166
14241
  if (chunk.length === 0) return null;
14242
+ const atomicMin = isPaintBaseSegment ? paintBaseEventCount(chunk) : 0;
14167
14243
  const fitted = await fitCompressedPrefix(
14168
14244
  chunk,
14169
14245
  compressReplayEvents,
14170
14246
  TARGET_COMPRESSED_SEGMENT_BYTES,
14171
- MAX_COMPRESSED_SEGMENT_BYTES
14247
+ MAX_COMPRESSED_SEGMENT_BYTES,
14248
+ atomicMin > 0 ? { atomicMinCount: atomicMin } : void 0
14172
14249
  );
14173
14250
  if (!fitted) {
14174
- const dropped = chunk[0];
14175
- console.warn(
14176
- "@newtalaria/browser: dropping rrweb event that exceeds max segment size",
14177
- { type: dropped?.type }
14178
- );
14179
- if (this.segmentIndex === 0 && dropped?.type === RRWEB_FULL_SNAPSHOT) {
14251
+ if (isPaintBaseSegment && atomicMin > 0) {
14252
+ const paint = chunk.slice(0, atomicMin);
14180
14253
  let compressedBytes;
14181
- try {
14182
- compressedBytes = (await compressReplayEvents([dropped])).length;
14183
- } catch {
14254
+ const full = paint.find((e) => e.type === RRWEB_FULL_SNAPSHOT);
14255
+ if (full) {
14256
+ try {
14257
+ compressedBytes = (await compressReplayEvents([full])).length;
14258
+ } catch {
14259
+ }
14260
+ }
14261
+ if (chunk.length > atomicMin) {
14262
+ this.buffer.prepend(chunk.slice(atomicMin));
14184
14263
  }
14185
14264
  return {
14186
14265
  events: [],
14187
14266
  gzip: new Uint8Array(),
14188
14267
  abortReason: "oversized_full_snapshot",
14189
14268
  abortDetails: {
14190
- source: "fit_compressed_prefix",
14191
- ...paintBaseSizeDetails(chunk, {
14269
+ source: "paint_base_exceeds_hard_cap",
14270
+ ...paintBaseSizeDetails(paint, {
14192
14271
  fullSnapshotCompressedBytes: compressedBytes
14193
14272
  })
14194
14273
  }
14195
14274
  };
14196
14275
  }
14276
+ const dropped = chunk[0];
14277
+ console.warn(
14278
+ "@newtalaria/browser: dropping rrweb event that exceeds max segment size",
14279
+ { type: dropped?.type }
14280
+ );
14197
14281
  if (chunk.length > 1) {
14198
14282
  this.buffer.prepend(chunk.slice(1));
14199
14283
  }
14200
14284
  return this.takeFittedSegment();
14201
14285
  }
14202
- if (this.segmentIndex === 0 && chunk.some((e) => e.type === RRWEB_FULL_SNAPSHOT) && !fitted.events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
14203
- const full = chunk.find((e) => e.type === RRWEB_FULL_SNAPSHOT);
14204
- let compressedBytes;
14205
- if (full) {
14206
- try {
14207
- compressedBytes = (await compressReplayEvents([full])).length;
14208
- } catch {
14209
- }
14210
- }
14211
- return {
14212
- events: [],
14213
- gzip: new Uint8Array(),
14214
- abortReason: "oversized_full_snapshot",
14215
- abortDetails: {
14216
- source: "fit_dropped_full_snapshot",
14217
- ...paintBaseSizeDetails(chunk, {
14218
- fullSnapshotCompressedBytes: compressedBytes
14219
- })
14220
- }
14221
- };
14222
- }
14223
14286
  if (fitted.remainder.length > 0) {
14224
14287
  this.buffer.prepend(fitted.remainder);
14225
14288
  }