@newtalaria/browser 0.1.1 → 0.1.4
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 +58 -11
- package/dist/client.d.ts +42 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +527 -79
- package/dist/index.js.map +3 -3
- package/dist/replay/fit_segment.d.ts +35 -0
- package/dist/replay/fit_segment.d.ts.map +1 -0
- package/dist/replay/privacy.d.ts.map +1 -1
- package/dist/replay/recorder.d.ts +2 -0
- package/dist/replay/recorder.d.ts.map +1 -1
- package/dist/replay/segment_buffer.d.ts +56 -6
- package/dist/replay/segment_buffer.d.ts.map +1 -1
- package/dist/transport/replays.d.ts +3 -0
- package/dist/transport/replays.d.ts.map +1 -1
- package/dist/transport/serverpod.d.ts.map +1 -1
- package/dist/types.d.ts +9 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -38,8 +38,18 @@ var ServerpodTransport = class {
|
|
|
38
38
|
});
|
|
39
39
|
if (!response.ok) {
|
|
40
40
|
const text = await response.text().catch(() => "");
|
|
41
|
+
let detail = text.slice(0, 400);
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(text);
|
|
44
|
+
const className = parsed.className ?? parsed.exception;
|
|
45
|
+
const message = parsed.message;
|
|
46
|
+
if (className || message) {
|
|
47
|
+
detail = [className, message].filter(Boolean).join(": ");
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
41
51
|
throw new Error(
|
|
42
|
-
`Talaria ${endpoint}/${method} failed: HTTP ${response.status}${
|
|
52
|
+
`Talaria ${endpoint}/${method} failed: HTTP ${response.status}${detail ? ` \u2014 ${detail}` : ""}`
|
|
43
53
|
);
|
|
44
54
|
}
|
|
45
55
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -101,56 +111,6 @@ function toServerpodByteData(bytes) {
|
|
|
101
111
|
return `decode('${b64}', 'base64')`;
|
|
102
112
|
}
|
|
103
113
|
|
|
104
|
-
// src/transport/replays.ts
|
|
105
|
-
async function startReplay(transport, params) {
|
|
106
|
-
const input2 = {
|
|
107
|
-
__className__: "StartReplayInput",
|
|
108
|
-
replayId: params.replayId,
|
|
109
|
-
environment: params.environment
|
|
110
|
-
};
|
|
111
|
-
if (params.sessionId) input2.sessionId = params.sessionId;
|
|
112
|
-
if (params.url) input2.url = params.url;
|
|
113
|
-
if (params.userId) input2.userId = params.userId;
|
|
114
|
-
return transport.call(
|
|
115
|
-
"replays",
|
|
116
|
-
"start",
|
|
117
|
-
{ input: input2 },
|
|
118
|
-
{ keepalive: params.keepalive }
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
async function ingestReplaySegment(transport, params) {
|
|
122
|
-
const json = JSON.stringify(params.events);
|
|
123
|
-
const compressed = await gzipBytes(new TextEncoder().encode(json));
|
|
124
|
-
const input2 = {
|
|
125
|
-
__className__: "IngestReplaySegmentInput",
|
|
126
|
-
replayId: params.replayId,
|
|
127
|
-
segmentIndex: params.segmentIndex,
|
|
128
|
-
gzipBytes: toServerpodByteData(compressed),
|
|
129
|
-
eventCount: params.events.length,
|
|
130
|
-
startedAt: params.startedAt.toISOString(),
|
|
131
|
-
endedAt: params.endedAt.toISOString()
|
|
132
|
-
};
|
|
133
|
-
return transport.call(
|
|
134
|
-
"replays",
|
|
135
|
-
"ingestSegment",
|
|
136
|
-
{ input: input2 },
|
|
137
|
-
{ keepalive: params.keepalive }
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
async function finishReplay(transport, params) {
|
|
141
|
-
const input2 = {
|
|
142
|
-
__className__: "FinishReplayInput",
|
|
143
|
-
replayId: params.replayId
|
|
144
|
-
};
|
|
145
|
-
if (params.reason) input2.reason = params.reason;
|
|
146
|
-
return transport.call(
|
|
147
|
-
"replays",
|
|
148
|
-
"finish",
|
|
149
|
-
{ input: input2 },
|
|
150
|
-
{ keepalive: params.keepalive }
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
114
|
// src/utils/estimate.ts
|
|
155
115
|
function estimateJsonBytes(value) {
|
|
156
116
|
try {
|
|
@@ -161,10 +121,20 @@ function estimateJsonBytes(value) {
|
|
|
161
121
|
}
|
|
162
122
|
|
|
163
123
|
// src/replay/segment_buffer.ts
|
|
164
|
-
var SEGMENT_SIZE_BYTES =
|
|
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;
|
|
127
|
+
var MAX_SEGMENTS_PER_REPLAY = 200;
|
|
128
|
+
var MAX_SEGMENTS_ERROR_CLIP = 12;
|
|
129
|
+
var MAX_ERROR_CLIP_COMPRESSED_BYTES = 2 * 1024 * 1024;
|
|
130
|
+
var ERROR_CLIP_FLUSH_SLACK_MS = 5e3;
|
|
165
131
|
var SEGMENT_FLUSH_MS = 5e3;
|
|
166
132
|
var RING_BUFFER_MS = 6e4;
|
|
133
|
+
var RING_BUFFER_MAX_BYTES = 15e5;
|
|
167
134
|
var ERROR_REPLAY_AFTER_MS = 15e3;
|
|
135
|
+
var MAX_REPLAY_DURATION_MS = 5 * 60 * 1e3;
|
|
136
|
+
var RRWEB_FULL_SNAPSHOT = 2;
|
|
137
|
+
var RRWEB_META = 4;
|
|
168
138
|
var SegmentBuffer = class {
|
|
169
139
|
constructor() {
|
|
170
140
|
this.events = [];
|
|
@@ -180,8 +150,31 @@ var SegmentBuffer = class {
|
|
|
180
150
|
this.events.push(event);
|
|
181
151
|
this.pendingBytes += estimateJsonBytes(event);
|
|
182
152
|
}
|
|
183
|
-
|
|
184
|
-
|
|
153
|
+
hasFullSnapshot() {
|
|
154
|
+
return this.events.some((e) => e.type === RRWEB_FULL_SNAPSHOT);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Drop leading events until Meta+FullSnapshot (or FullSnapshot alone).
|
|
158
|
+
* Returns false if no FullSnapshot is present (replay would paint blank).
|
|
159
|
+
*/
|
|
160
|
+
trimToFullSnapshot() {
|
|
161
|
+
const fullIdx = this.events.findIndex((e) => e.type === RRWEB_FULL_SNAPSHOT);
|
|
162
|
+
if (fullIdx < 0) return false;
|
|
163
|
+
let start = fullIdx;
|
|
164
|
+
if (fullIdx > 0 && this.events[fullIdx - 1].type === RRWEB_META) {
|
|
165
|
+
start = fullIdx - 1;
|
|
166
|
+
}
|
|
167
|
+
if (start > 0) {
|
|
168
|
+
this.events.splice(0, start);
|
|
169
|
+
this.recomputeBytes();
|
|
170
|
+
}
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Drop events older than the ring window and/or over the byte cap
|
|
175
|
+
* (buffer-only mode). Prefer keeping the newest events.
|
|
176
|
+
*/
|
|
177
|
+
trimRing(now = Date.now(), maxBytes = RING_BUFFER_MAX_BYTES) {
|
|
185
178
|
const cutoff = now - RING_BUFFER_MS;
|
|
186
179
|
let remove = 0;
|
|
187
180
|
while (remove < this.events.length && this.events[remove].timestamp < cutoff) {
|
|
@@ -191,22 +184,55 @@ var SegmentBuffer = class {
|
|
|
191
184
|
this.events.splice(0, remove);
|
|
192
185
|
this.recomputeBytes();
|
|
193
186
|
}
|
|
187
|
+
while (this.events.length > 1 && this.pendingBytes > maxBytes) {
|
|
188
|
+
this.events.shift();
|
|
189
|
+
this.recomputeBytes();
|
|
190
|
+
}
|
|
194
191
|
}
|
|
195
192
|
shouldFlushBySize() {
|
|
196
193
|
return this.pendingBytes >= SEGMENT_SIZE_BYTES;
|
|
197
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Take a prefix of events whose estimated JSON size is ~maxBytes.
|
|
197
|
+
* Always returns at least one event when the buffer is non-empty (caller
|
|
198
|
+
* must handle a single oversized event).
|
|
199
|
+
*/
|
|
200
|
+
takeByEstimatedBytes(maxBytes) {
|
|
201
|
+
if (this.events.length === 0) return [];
|
|
202
|
+
const batch = [];
|
|
203
|
+
let batchBytes = 0;
|
|
204
|
+
while (this.events.length > 0) {
|
|
205
|
+
const next = this.events[0];
|
|
206
|
+
const nextBytes = estimateJsonBytes(next);
|
|
207
|
+
if (batch.length > 0 && batchBytes + nextBytes > maxBytes) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
this.events.shift();
|
|
211
|
+
batch.push(next);
|
|
212
|
+
batchBytes += nextBytes;
|
|
213
|
+
if (batch.length === 1 && nextBytes >= maxBytes) {
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this.recomputeBytes();
|
|
218
|
+
return batch;
|
|
219
|
+
}
|
|
198
220
|
takeAll() {
|
|
199
221
|
const batch = this.events;
|
|
200
222
|
this.events = [];
|
|
201
223
|
this.pendingBytes = 0;
|
|
202
224
|
return batch;
|
|
203
225
|
}
|
|
204
|
-
/** Re-queue events at the front (failed upload retry). */
|
|
226
|
+
/** Re-queue events at the front (failed upload retry / bisect remainder). */
|
|
205
227
|
prepend(events) {
|
|
206
228
|
if (events.length === 0) return;
|
|
207
229
|
this.events = events.concat(this.events);
|
|
208
230
|
this.recomputeBytes();
|
|
209
231
|
}
|
|
232
|
+
clear() {
|
|
233
|
+
this.events = [];
|
|
234
|
+
this.pendingBytes = 0;
|
|
235
|
+
}
|
|
210
236
|
peekTimes() {
|
|
211
237
|
if (this.events.length === 0) return null;
|
|
212
238
|
const first = this.events[0];
|
|
@@ -221,6 +247,114 @@ var SegmentBuffer = class {
|
|
|
221
247
|
}
|
|
222
248
|
};
|
|
223
249
|
|
|
250
|
+
// src/transport/replays.ts
|
|
251
|
+
async function compressReplayEvents(events) {
|
|
252
|
+
const json = JSON.stringify(events);
|
|
253
|
+
return gzipBytes(new TextEncoder().encode(json));
|
|
254
|
+
}
|
|
255
|
+
async function startReplay(transport, params) {
|
|
256
|
+
const input2 = {
|
|
257
|
+
__className__: "StartReplayInput",
|
|
258
|
+
replayId: params.replayId,
|
|
259
|
+
environment: params.environment
|
|
260
|
+
};
|
|
261
|
+
if (params.sessionId) input2.sessionId = params.sessionId;
|
|
262
|
+
if (params.url) input2.url = params.url;
|
|
263
|
+
if (params.userId) input2.userId = params.userId;
|
|
264
|
+
return transport.call(
|
|
265
|
+
"replays",
|
|
266
|
+
"start",
|
|
267
|
+
{ input: input2 },
|
|
268
|
+
{ keepalive: params.keepalive }
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
async function ingestReplaySegment(transport, params) {
|
|
272
|
+
const compressed = params.gzip ?? await compressReplayEvents(params.events);
|
|
273
|
+
if (compressed.length > MAX_COMPRESSED_SEGMENT_BYTES) {
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Talaria replays/ingestSegment failed: HTTP 400 \u2014 segment exceeds max compressed size (${compressed.length} > ${MAX_COMPRESSED_SEGMENT_BYTES})`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
const input2 = {
|
|
279
|
+
__className__: "IngestReplaySegmentInput",
|
|
280
|
+
replayId: params.replayId,
|
|
281
|
+
segmentIndex: params.segmentIndex,
|
|
282
|
+
gzipBytes: toServerpodByteData(compressed),
|
|
283
|
+
eventCount: params.events.length,
|
|
284
|
+
startedAt: params.startedAt.toISOString(),
|
|
285
|
+
endedAt: params.endedAt.toISOString()
|
|
286
|
+
};
|
|
287
|
+
return transport.call(
|
|
288
|
+
"replays",
|
|
289
|
+
"ingestSegment",
|
|
290
|
+
{ input: input2 },
|
|
291
|
+
{ keepalive: params.keepalive }
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
async function finishReplay(transport, params) {
|
|
295
|
+
const input2 = {
|
|
296
|
+
__className__: "FinishReplayInput",
|
|
297
|
+
replayId: params.replayId
|
|
298
|
+
};
|
|
299
|
+
if (params.reason) input2.reason = params.reason;
|
|
300
|
+
return transport.call(
|
|
301
|
+
"replays",
|
|
302
|
+
"finish",
|
|
303
|
+
{ input: input2 },
|
|
304
|
+
{ keepalive: params.keepalive }
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/replay/fit_segment.ts
|
|
309
|
+
function computeErrorClipDeadlineMs(uploadStartedAtMs, afterMs, slackMs = ERROR_CLIP_FLUSH_SLACK_MS) {
|
|
310
|
+
return uploadStartedAtMs + afterMs + slackMs;
|
|
311
|
+
}
|
|
312
|
+
function isErrorClipBudgetExhausted(opts) {
|
|
313
|
+
const maxSegments = opts.maxSegments ?? MAX_SEGMENTS_ERROR_CLIP;
|
|
314
|
+
const maxBytes = opts.maxBytes ?? MAX_ERROR_CLIP_COMPRESSED_BYTES;
|
|
315
|
+
return opts.segmentIndex >= maxSegments || opts.uploadedCompressedBytes >= maxBytes;
|
|
316
|
+
}
|
|
317
|
+
function planOversizedRetry(events) {
|
|
318
|
+
if (events.length <= 1) {
|
|
319
|
+
return { action: "drop" };
|
|
320
|
+
}
|
|
321
|
+
const mid = Math.ceil(events.length / 2);
|
|
322
|
+
return {
|
|
323
|
+
action: "bisect",
|
|
324
|
+
left: events.slice(0, mid),
|
|
325
|
+
right: events.slice(mid)
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
async function fitCompressedPrefix(events, compress, targetBytes = TARGET_COMPRESSED_SEGMENT_BYTES, hardCap = MAX_COMPRESSED_SEGMENT_BYTES) {
|
|
329
|
+
if (events.length === 0) return null;
|
|
330
|
+
let remaining = events;
|
|
331
|
+
while (remaining.length > 0) {
|
|
332
|
+
let lo = 1;
|
|
333
|
+
let hi = remaining.length;
|
|
334
|
+
let best = null;
|
|
335
|
+
while (lo <= hi) {
|
|
336
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
337
|
+
const prefix = remaining.slice(0, mid);
|
|
338
|
+
const gzip = await compress(prefix);
|
|
339
|
+
if (gzip.length <= targetBytes && gzip.length <= hardCap) {
|
|
340
|
+
best = { events: prefix, gzip };
|
|
341
|
+
lo = mid + 1;
|
|
342
|
+
} else {
|
|
343
|
+
hi = mid - 1;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (best) {
|
|
347
|
+
return {
|
|
348
|
+
events: best.events,
|
|
349
|
+
gzip: best.gzip,
|
|
350
|
+
remainder: remaining.slice(best.events.length)
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
remaining = remaining.slice(1);
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
224
358
|
// ../../node_modules/rrweb/dist/rrweb.js
|
|
225
359
|
var __defProp = Object.defineProperty;
|
|
226
360
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -12976,13 +13110,27 @@ function redactUrl(raw) {
|
|
|
12976
13110
|
}
|
|
12977
13111
|
}
|
|
12978
13112
|
function defaultBlockSelector(extra) {
|
|
12979
|
-
const parts = [
|
|
13113
|
+
const parts = [
|
|
13114
|
+
"[data-talaria-mask]",
|
|
13115
|
+
".talaria-block",
|
|
13116
|
+
// Heavy docs surfaces (syntax-highlighted code) blow up FullSnapshots.
|
|
13117
|
+
"pre",
|
|
13118
|
+
"code"
|
|
13119
|
+
];
|
|
12980
13120
|
if (extra?.trim()) parts.push(extra.trim());
|
|
12981
13121
|
return parts.join(", ");
|
|
12982
13122
|
}
|
|
12983
13123
|
|
|
12984
13124
|
// src/replay/recorder.ts
|
|
12985
13125
|
function startRecorder(options) {
|
|
13126
|
+
if (typeof document === "undefined") {
|
|
13127
|
+
return {
|
|
13128
|
+
stop: () => {
|
|
13129
|
+
},
|
|
13130
|
+
takeFullSnapshot: () => {
|
|
13131
|
+
}
|
|
13132
|
+
};
|
|
13133
|
+
}
|
|
12986
13134
|
const stopFn = record({
|
|
12987
13135
|
emit(event) {
|
|
12988
13136
|
options.onEvent(event);
|
|
@@ -12993,11 +13141,25 @@ function startRecorder(options) {
|
|
|
12993
13141
|
},
|
|
12994
13142
|
blockSelector: defaultBlockSelector(options.blockSelector),
|
|
12995
13143
|
recordCanvas: false,
|
|
12996
|
-
collectFonts: false
|
|
13144
|
+
collectFonts: false,
|
|
13145
|
+
inlineStylesheet: false,
|
|
13146
|
+
// Shrink DOM snapshots on heavy sites (docs / marketing).
|
|
13147
|
+
slimDOMOptions: "all",
|
|
13148
|
+
sampling: {
|
|
13149
|
+
mousemove: 150,
|
|
13150
|
+
scroll: 200,
|
|
13151
|
+
input: "last"
|
|
13152
|
+
}
|
|
12997
13153
|
});
|
|
12998
13154
|
return {
|
|
12999
13155
|
stop: () => {
|
|
13000
13156
|
stopFn?.();
|
|
13157
|
+
},
|
|
13158
|
+
takeFullSnapshot: () => {
|
|
13159
|
+
try {
|
|
13160
|
+
record.takeFullSnapshot(true);
|
|
13161
|
+
} catch {
|
|
13162
|
+
}
|
|
13001
13163
|
}
|
|
13002
13164
|
};
|
|
13003
13165
|
}
|
|
@@ -13130,6 +13292,7 @@ function resolveOptions(options) {
|
|
|
13130
13292
|
release: options.release,
|
|
13131
13293
|
replaysSessionSampleRate: clamp01(options.replaysSessionSampleRate ?? 0),
|
|
13132
13294
|
replaysOnErrorSampleRate: clamp01(options.replaysOnErrorSampleRate ?? 1),
|
|
13295
|
+
replaysErrorAfterMs: normalizeErrorAfterMs(options.replaysErrorAfterMs),
|
|
13133
13296
|
maskAllInputs: options.maskAllInputs ?? true,
|
|
13134
13297
|
blockSelector: options.blockSelector ?? "",
|
|
13135
13298
|
userId: options.userId,
|
|
@@ -13140,6 +13303,12 @@ function clamp01(n2) {
|
|
|
13140
13303
|
if (Number.isNaN(n2)) return 0;
|
|
13141
13304
|
return Math.min(1, Math.max(0, n2));
|
|
13142
13305
|
}
|
|
13306
|
+
function normalizeErrorAfterMs(n2) {
|
|
13307
|
+
if (n2 === void 0 || Number.isNaN(n2) || n2 < 0) {
|
|
13308
|
+
return ERROR_REPLAY_AFTER_MS;
|
|
13309
|
+
}
|
|
13310
|
+
return Math.floor(n2);
|
|
13311
|
+
}
|
|
13143
13312
|
function levelToEventType(level) {
|
|
13144
13313
|
if (level === "fatal" || level === "error") return "error";
|
|
13145
13314
|
if (level === "warning") return "warning";
|
|
@@ -13150,6 +13319,10 @@ function isTerminalReplayLimitError(error) {
|
|
|
13150
13319
|
const msg = error instanceof Error ? error.message : String(error);
|
|
13151
13320
|
return msg.includes("replay exceeds max segment count") || msg.includes("replay exceeds max total size") || msg.includes("replay exceeds max duration") || msg.includes("Replay has expired");
|
|
13152
13321
|
}
|
|
13322
|
+
function isOversizedSegmentError(error) {
|
|
13323
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
13324
|
+
return msg.includes("segment exceeds max compressed size") || msg.includes("segment exceeds max uncompressed size") || msg.includes("HTTP 400") && msg.includes("ApiValidationException");
|
|
13325
|
+
}
|
|
13153
13326
|
var TalariaClient = class {
|
|
13154
13327
|
constructor() {
|
|
13155
13328
|
this.options = null;
|
|
@@ -13164,11 +13337,22 @@ var TalariaClient = class {
|
|
|
13164
13337
|
this.startedOnServer = false;
|
|
13165
13338
|
this.finishedOnServer = false;
|
|
13166
13339
|
this.segmentIndex = 0;
|
|
13340
|
+
this.uploadedCompressedBytes = 0;
|
|
13341
|
+
/** Wall clock when continuous upload began (session or error promote). */
|
|
13342
|
+
this.uploadStartedAtMs = null;
|
|
13343
|
+
/** Absolute deadline for error-clip mode (not extended by later errors). */
|
|
13344
|
+
this.errorClipDeadlineMs = null;
|
|
13167
13345
|
this.flushTimer = null;
|
|
13168
13346
|
this.errorClipTimer = null;
|
|
13347
|
+
this.maxDurationTimer = null;
|
|
13169
13348
|
this.uploadChain = Promise.resolve();
|
|
13170
13349
|
this.closed = false;
|
|
13171
13350
|
this.teardowns = [];
|
|
13351
|
+
/**
|
|
13352
|
+
* Replay id that just finished with segments — kept briefly so capture can
|
|
13353
|
+
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
13354
|
+
*/
|
|
13355
|
+
this.linkableReplayId = null;
|
|
13172
13356
|
}
|
|
13173
13357
|
init(options) {
|
|
13174
13358
|
if (this.options) {
|
|
@@ -13186,7 +13370,12 @@ var TalariaClient = class {
|
|
|
13186
13370
|
this.finishedOnServer = false;
|
|
13187
13371
|
this.startedOnServer = false;
|
|
13188
13372
|
this.segmentIndex = 0;
|
|
13373
|
+
this.uploadedCompressedBytes = 0;
|
|
13374
|
+
this.uploadStartedAtMs = null;
|
|
13375
|
+
this.errorClipDeadlineMs = null;
|
|
13376
|
+
this.linkableReplayId = null;
|
|
13189
13377
|
this.buffer = new SegmentBuffer();
|
|
13378
|
+
this.uploadChain = Promise.resolve();
|
|
13190
13379
|
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
13191
13380
|
this.uploadEnabled = this.sessionSampled;
|
|
13192
13381
|
this.recorder = startRecorder({
|
|
@@ -13213,13 +13402,15 @@ var TalariaClient = class {
|
|
|
13213
13402
|
});
|
|
13214
13403
|
}
|
|
13215
13404
|
if (this.uploadEnabled) {
|
|
13405
|
+
this.markUploadStarted();
|
|
13216
13406
|
void this.enqueueUpload(async () => {
|
|
13217
13407
|
await this.ensureStarted({ keepalive: false });
|
|
13218
13408
|
});
|
|
13219
13409
|
}
|
|
13220
13410
|
}
|
|
13221
13411
|
getReplayId() {
|
|
13222
|
-
|
|
13412
|
+
if (this.uploadEnabled && this.replayId) return this.replayId;
|
|
13413
|
+
return this.linkableReplayId;
|
|
13223
13414
|
}
|
|
13224
13415
|
async captureException(error, context) {
|
|
13225
13416
|
const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
|
|
@@ -13248,6 +13439,14 @@ var TalariaClient = class {
|
|
|
13248
13439
|
}
|
|
13249
13440
|
await this.enqueueUpload(async () => {
|
|
13250
13441
|
if (!this.uploadEnabled || this.closed) return;
|
|
13442
|
+
if (this.isPastErrorClipDeadline() && !opts?.finish) {
|
|
13443
|
+
await this.runEndErrorClip({ reason: "error_clip_deadline" });
|
|
13444
|
+
return;
|
|
13445
|
+
}
|
|
13446
|
+
if (this.isPastMaxDuration() && !opts?.finish) {
|
|
13447
|
+
await this.runStopForMaxDuration({ keepalive });
|
|
13448
|
+
return;
|
|
13449
|
+
}
|
|
13251
13450
|
await this.ensureStarted({ keepalive });
|
|
13252
13451
|
await this.uploadPendingSegments({ keepalive });
|
|
13253
13452
|
if (opts?.finish && this.uploadEnabled) {
|
|
@@ -13259,14 +13458,19 @@ var TalariaClient = class {
|
|
|
13259
13458
|
this.resetToBufferMode();
|
|
13260
13459
|
} else {
|
|
13261
13460
|
this.uploadEnabled = false;
|
|
13461
|
+
this.clearMaxDurationTimer();
|
|
13262
13462
|
}
|
|
13263
13463
|
}
|
|
13264
13464
|
});
|
|
13265
13465
|
}
|
|
13466
|
+
/**
|
|
13467
|
+
* Stop recording, flush while still open, then fully reset so `init()` can
|
|
13468
|
+
* run again (React Strict Mode remount).
|
|
13469
|
+
*/
|
|
13266
13470
|
async close() {
|
|
13267
|
-
if (this.closed) return;
|
|
13268
|
-
this.closed = true;
|
|
13471
|
+
if (this.closed && !this.options) return;
|
|
13269
13472
|
this.clearErrorClipTimer();
|
|
13473
|
+
this.clearMaxDurationTimer();
|
|
13270
13474
|
if (this.flushTimer) {
|
|
13271
13475
|
clearInterval(this.flushTimer);
|
|
13272
13476
|
this.flushTimer = null;
|
|
@@ -13279,17 +13483,45 @@ var TalariaClient = class {
|
|
|
13279
13483
|
} catch {
|
|
13280
13484
|
}
|
|
13281
13485
|
}
|
|
13282
|
-
if (this.uploadEnabled) {
|
|
13283
|
-
|
|
13486
|
+
if (this.uploadEnabled && this.options && this.transport) {
|
|
13487
|
+
try {
|
|
13488
|
+
await this.flush({ reason: "close", finish: true });
|
|
13489
|
+
} catch (error) {
|
|
13490
|
+
console.warn("@newtalaria/browser: close flush failed", error);
|
|
13491
|
+
}
|
|
13284
13492
|
}
|
|
13493
|
+
this.options = null;
|
|
13494
|
+
this.transport = null;
|
|
13495
|
+
this.replayId = null;
|
|
13496
|
+
this.sessionId = null;
|
|
13497
|
+
this.sessionSampled = false;
|
|
13498
|
+
this.uploadEnabled = false;
|
|
13499
|
+
this.startedOnServer = false;
|
|
13500
|
+
this.finishedOnServer = false;
|
|
13501
|
+
this.segmentIndex = 0;
|
|
13502
|
+
this.uploadedCompressedBytes = 0;
|
|
13503
|
+
this.uploadStartedAtMs = null;
|
|
13504
|
+
this.errorClipDeadlineMs = null;
|
|
13505
|
+
this.buffer.clear();
|
|
13506
|
+
this.uploadChain = Promise.resolve();
|
|
13507
|
+
this.closed = false;
|
|
13508
|
+
this.linkableReplayId = null;
|
|
13285
13509
|
}
|
|
13286
13510
|
onRrwebEvent(event) {
|
|
13287
|
-
if (this.closed) return;
|
|
13511
|
+
if (this.closed || !this.options) return;
|
|
13288
13512
|
this.buffer.push(event);
|
|
13289
13513
|
if (!this.uploadEnabled) {
|
|
13290
13514
|
this.buffer.trimRing();
|
|
13291
13515
|
return;
|
|
13292
13516
|
}
|
|
13517
|
+
if (this.isPastErrorClipDeadline()) {
|
|
13518
|
+
void this.flush({ reason: "error_clip_deadline" });
|
|
13519
|
+
return;
|
|
13520
|
+
}
|
|
13521
|
+
if (this.isPastMaxDuration()) {
|
|
13522
|
+
void this.flush({ reason: "max_duration" });
|
|
13523
|
+
return;
|
|
13524
|
+
}
|
|
13293
13525
|
if (this.buffer.shouldFlushBySize()) {
|
|
13294
13526
|
void this.flush({ reason: "size" });
|
|
13295
13527
|
}
|
|
@@ -13303,14 +13535,15 @@ var TalariaClient = class {
|
|
|
13303
13535
|
if (!this.uploadEnabled) {
|
|
13304
13536
|
if (Math.random() < this.options.replaysOnErrorSampleRate) {
|
|
13305
13537
|
this.uploadEnabled = true;
|
|
13538
|
+
this.checkoutFullSnapshot();
|
|
13539
|
+
this.markUploadStarted();
|
|
13306
13540
|
this.scheduleErrorClipEnd();
|
|
13307
13541
|
}
|
|
13308
13542
|
} else {
|
|
13309
13543
|
this.scheduleErrorClipEnd();
|
|
13310
13544
|
}
|
|
13311
13545
|
}
|
|
13312
|
-
|
|
13313
|
-
if (attachReplay) {
|
|
13546
|
+
if (this.uploadEnabled) {
|
|
13314
13547
|
try {
|
|
13315
13548
|
await this.flush({ reason: "capture", keepalive: args.keepalive });
|
|
13316
13549
|
} catch (error) {
|
|
@@ -13320,7 +13553,7 @@ var TalariaClient = class {
|
|
|
13320
13553
|
}
|
|
13321
13554
|
const tags = { ...args.context?.tags ?? {} };
|
|
13322
13555
|
const extra = args.context?.extra;
|
|
13323
|
-
const replayId =
|
|
13556
|
+
const replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
|
|
13324
13557
|
try {
|
|
13325
13558
|
await ingestEvent(this.transport, {
|
|
13326
13559
|
message: args.message,
|
|
@@ -13339,18 +13572,96 @@ var TalariaClient = class {
|
|
|
13339
13572
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13340
13573
|
keepalive: args.keepalive
|
|
13341
13574
|
});
|
|
13575
|
+
if (replayId && replayId === this.linkableReplayId) {
|
|
13576
|
+
this.linkableReplayId = null;
|
|
13577
|
+
}
|
|
13342
13578
|
} catch (error) {
|
|
13343
13579
|
console.warn("@newtalaria/browser: event ingest failed", error);
|
|
13344
13580
|
throw error;
|
|
13345
13581
|
}
|
|
13346
13582
|
}
|
|
13583
|
+
markUploadStarted() {
|
|
13584
|
+
if (this.uploadStartedAtMs == null) {
|
|
13585
|
+
this.uploadStartedAtMs = Date.now();
|
|
13586
|
+
}
|
|
13587
|
+
if (!this.sessionSampled && this.options && this.options.replaysErrorAfterMs > 0 && this.errorClipDeadlineMs == null) {
|
|
13588
|
+
this.errorClipDeadlineMs = computeErrorClipDeadlineMs(
|
|
13589
|
+
this.uploadStartedAtMs,
|
|
13590
|
+
this.options.replaysErrorAfterMs
|
|
13591
|
+
);
|
|
13592
|
+
}
|
|
13593
|
+
this.scheduleMaxDurationStop();
|
|
13594
|
+
}
|
|
13595
|
+
isPastMaxDuration() {
|
|
13596
|
+
if (this.uploadStartedAtMs == null) return false;
|
|
13597
|
+
return Date.now() - this.uploadStartedAtMs >= MAX_REPLAY_DURATION_MS;
|
|
13598
|
+
}
|
|
13599
|
+
isPastErrorClipDeadline() {
|
|
13600
|
+
if (this.sessionSampled || this.errorClipDeadlineMs == null) return false;
|
|
13601
|
+
return Date.now() >= this.errorClipDeadlineMs;
|
|
13602
|
+
}
|
|
13603
|
+
isErrorClipMode() {
|
|
13604
|
+
return !this.sessionSampled && !!this.options && this.options.replaysErrorAfterMs > 0;
|
|
13605
|
+
}
|
|
13606
|
+
scheduleMaxDurationStop() {
|
|
13607
|
+
if (this.closed || this.uploadStartedAtMs == null) return;
|
|
13608
|
+
this.clearMaxDurationTimer();
|
|
13609
|
+
const remaining = Math.max(
|
|
13610
|
+
0,
|
|
13611
|
+
MAX_REPLAY_DURATION_MS - (Date.now() - this.uploadStartedAtMs)
|
|
13612
|
+
);
|
|
13613
|
+
this.maxDurationTimer = setTimeout(() => {
|
|
13614
|
+
this.maxDurationTimer = null;
|
|
13615
|
+
if (!this.uploadEnabled || this.closed) return;
|
|
13616
|
+
void this.enqueueUpload(async () => {
|
|
13617
|
+
if (!this.uploadEnabled || this.closed) return;
|
|
13618
|
+
await this.runStopForMaxDuration({ keepalive: false });
|
|
13619
|
+
});
|
|
13620
|
+
}, remaining);
|
|
13621
|
+
}
|
|
13622
|
+
clearMaxDurationTimer() {
|
|
13623
|
+
if (this.maxDurationTimer) {
|
|
13624
|
+
clearTimeout(this.maxDurationTimer);
|
|
13625
|
+
this.maxDurationTimer = null;
|
|
13626
|
+
}
|
|
13627
|
+
}
|
|
13628
|
+
async runStopForMaxDuration(opts) {
|
|
13629
|
+
if (!this.uploadEnabled) return;
|
|
13630
|
+
await this.ensureStarted({ keepalive: opts.keepalive });
|
|
13631
|
+
await this.uploadPendingSegments({ keepalive: opts.keepalive });
|
|
13632
|
+
await this.finishOnServer({
|
|
13633
|
+
keepalive: opts.keepalive,
|
|
13634
|
+
reason: "max_duration"
|
|
13635
|
+
});
|
|
13636
|
+
if (this.sessionSampled) {
|
|
13637
|
+
this.uploadEnabled = false;
|
|
13638
|
+
this.clearMaxDurationTimer();
|
|
13639
|
+
if (this.flushTimer) {
|
|
13640
|
+
clearInterval(this.flushTimer);
|
|
13641
|
+
this.flushTimer = null;
|
|
13642
|
+
}
|
|
13643
|
+
} else {
|
|
13644
|
+
this.resetToBufferMode();
|
|
13645
|
+
}
|
|
13646
|
+
}
|
|
13647
|
+
/**
|
|
13648
|
+
* Schedule end of the cheap error clip against a fixed absolute deadline.
|
|
13649
|
+
* Subsequent errors re-arm the timer but never push the wall later.
|
|
13650
|
+
*/
|
|
13347
13651
|
scheduleErrorClipEnd() {
|
|
13348
|
-
if (this.sessionSampled || this.closed) return;
|
|
13652
|
+
if (this.sessionSampled || this.closed || !this.options) return;
|
|
13653
|
+
const afterMs = this.options.replaysErrorAfterMs;
|
|
13654
|
+
if (afterMs <= 0) return;
|
|
13655
|
+
if (this.errorClipDeadlineMs == null) {
|
|
13656
|
+
const started = this.uploadStartedAtMs ?? Date.now();
|
|
13657
|
+
this.errorClipDeadlineMs = computeErrorClipDeadlineMs(started, afterMs);
|
|
13658
|
+
}
|
|
13659
|
+
const remaining = Math.max(0, this.errorClipDeadlineMs - Date.now());
|
|
13349
13660
|
this.clearErrorClipTimer();
|
|
13350
13661
|
this.errorClipTimer = setTimeout(() => {
|
|
13351
13662
|
this.errorClipTimer = null;
|
|
13352
|
-
void this.endErrorClip();
|
|
13353
|
-
},
|
|
13663
|
+
void this.endErrorClip({ reason: "error_clip" });
|
|
13664
|
+
}, remaining);
|
|
13354
13665
|
}
|
|
13355
13666
|
clearErrorClipTimer() {
|
|
13356
13667
|
if (this.errorClipTimer) {
|
|
@@ -13359,30 +13670,61 @@ var TalariaClient = class {
|
|
|
13359
13670
|
}
|
|
13360
13671
|
}
|
|
13361
13672
|
/** Flush trailing post-error window, finish replay, return to ring buffer. */
|
|
13362
|
-
async endErrorClip() {
|
|
13673
|
+
async endErrorClip(opts) {
|
|
13363
13674
|
if (this.closed || this.sessionSampled || !this.uploadEnabled || !this.options || !this.transport) {
|
|
13364
13675
|
return;
|
|
13365
13676
|
}
|
|
13366
13677
|
await this.enqueueUpload(async () => {
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13678
|
+
await this.runEndErrorClip(opts);
|
|
13679
|
+
});
|
|
13680
|
+
}
|
|
13681
|
+
async runEndErrorClip(opts) {
|
|
13682
|
+
if (!this.uploadEnabled || this.sessionSampled) return;
|
|
13683
|
+
await this.ensureStarted({ keepalive: false });
|
|
13684
|
+
await this.uploadPendingSegments({ keepalive: false });
|
|
13685
|
+
await this.finishOnServer({
|
|
13686
|
+
keepalive: false,
|
|
13687
|
+
reason: opts?.reason ?? "error_clip"
|
|
13372
13688
|
});
|
|
13689
|
+
this.resetToBufferMode();
|
|
13373
13690
|
}
|
|
13374
13691
|
/** After an error clip (or terminal limit), buffer locally until the next error. */
|
|
13375
13692
|
resetToBufferMode() {
|
|
13376
13693
|
this.clearErrorClipTimer();
|
|
13694
|
+
this.clearMaxDurationTimer();
|
|
13377
13695
|
this.uploadEnabled = false;
|
|
13378
13696
|
this.startedOnServer = false;
|
|
13379
13697
|
this.finishedOnServer = false;
|
|
13380
13698
|
this.segmentIndex = 0;
|
|
13699
|
+
this.uploadedCompressedBytes = 0;
|
|
13700
|
+
this.uploadStartedAtMs = null;
|
|
13701
|
+
this.errorClipDeadlineMs = null;
|
|
13381
13702
|
this.replayId = createId();
|
|
13382
|
-
this.buffer.
|
|
13703
|
+
this.buffer.clear();
|
|
13704
|
+
this.checkoutFullSnapshot();
|
|
13705
|
+
}
|
|
13706
|
+
/** Emit a checkout FullSnapshot into the ring buffer (sync via rrweb emit). */
|
|
13707
|
+
checkoutFullSnapshot() {
|
|
13708
|
+
this.recorder?.takeFullSnapshot();
|
|
13709
|
+
}
|
|
13710
|
+
/**
|
|
13711
|
+
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
13712
|
+
* produce a blank rrweb player.
|
|
13713
|
+
*/
|
|
13714
|
+
prepareBufferForNewReplay() {
|
|
13715
|
+
if (this.segmentIndex !== 0) return;
|
|
13716
|
+
if (!this.buffer.hasFullSnapshot()) {
|
|
13717
|
+
this.checkoutFullSnapshot();
|
|
13718
|
+
}
|
|
13719
|
+
if (!this.buffer.trimToFullSnapshot()) {
|
|
13720
|
+
console.warn(
|
|
13721
|
+
"@newtalaria/browser: replay has no FullSnapshot; player may show a blank screen"
|
|
13722
|
+
);
|
|
13723
|
+
}
|
|
13383
13724
|
}
|
|
13384
13725
|
stopUploadingAfterLimit() {
|
|
13385
13726
|
this.clearErrorClipTimer();
|
|
13727
|
+
this.clearMaxDurationTimer();
|
|
13386
13728
|
this.uploadEnabled = false;
|
|
13387
13729
|
if (this.sessionSampled) {
|
|
13388
13730
|
if (this.flushTimer) {
|
|
@@ -13433,13 +13775,61 @@ var TalariaClient = class {
|
|
|
13433
13775
|
keepalive: opts.keepalive
|
|
13434
13776
|
});
|
|
13435
13777
|
this.startedOnServer = true;
|
|
13778
|
+
this.markUploadStarted();
|
|
13436
13779
|
}
|
|
13437
13780
|
async uploadPendingSegments(opts) {
|
|
13438
13781
|
if (!this.options || !this.transport || !this.replayId) return;
|
|
13439
13782
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13783
|
+
this.prepareBufferForNewReplay();
|
|
13440
13784
|
while (this.buffer.length > 0) {
|
|
13441
|
-
|
|
13442
|
-
|
|
13785
|
+
if (this.isPastMaxDuration()) {
|
|
13786
|
+
break;
|
|
13787
|
+
}
|
|
13788
|
+
if (this.isPastErrorClipDeadline()) {
|
|
13789
|
+
break;
|
|
13790
|
+
}
|
|
13791
|
+
if (this.segmentIndex >= this.maxSegmentsAllowed()) {
|
|
13792
|
+
console.warn(
|
|
13793
|
+
"@newtalaria/browser: replay upload stopped (max segments)",
|
|
13794
|
+
{ segmentIndex: this.segmentIndex }
|
|
13795
|
+
);
|
|
13796
|
+
await this.finishOnServer({
|
|
13797
|
+
keepalive: opts.keepalive,
|
|
13798
|
+
reason: "max_segments"
|
|
13799
|
+
});
|
|
13800
|
+
this.stopUploadingAfterLimit();
|
|
13801
|
+
break;
|
|
13802
|
+
}
|
|
13803
|
+
if (this.isErrorClipMode() && isErrorClipBudgetExhausted({
|
|
13804
|
+
segmentIndex: this.segmentIndex,
|
|
13805
|
+
uploadedCompressedBytes: this.uploadedCompressedBytes
|
|
13806
|
+
})) {
|
|
13807
|
+
console.warn(
|
|
13808
|
+
"@newtalaria/browser: replay upload stopped (error clip budget)",
|
|
13809
|
+
{
|
|
13810
|
+
segmentIndex: this.segmentIndex,
|
|
13811
|
+
uploadedCompressedBytes: this.uploadedCompressedBytes
|
|
13812
|
+
}
|
|
13813
|
+
);
|
|
13814
|
+
await this.finishOnServer({
|
|
13815
|
+
keepalive: opts.keepalive,
|
|
13816
|
+
reason: "error_clip_budget"
|
|
13817
|
+
});
|
|
13818
|
+
this.stopUploadingAfterLimit();
|
|
13819
|
+
break;
|
|
13820
|
+
}
|
|
13821
|
+
const fitted = await this.takeFittedSegment();
|
|
13822
|
+
if (!fitted) break;
|
|
13823
|
+
const { events, gzip } = fitted;
|
|
13824
|
+
if (this.isErrorClipMode() && this.uploadedCompressedBytes + gzip.length > MAX_ERROR_CLIP_COMPRESSED_BYTES && this.segmentIndex > 0) {
|
|
13825
|
+
this.buffer.clear();
|
|
13826
|
+
await this.finishOnServer({
|
|
13827
|
+
keepalive: opts.keepalive,
|
|
13828
|
+
reason: "error_clip_budget"
|
|
13829
|
+
});
|
|
13830
|
+
this.stopUploadingAfterLimit();
|
|
13831
|
+
break;
|
|
13832
|
+
}
|
|
13443
13833
|
const startedAt = new Date(events[0].timestamp);
|
|
13444
13834
|
const endedAt = new Date(events[events.length - 1].timestamp);
|
|
13445
13835
|
const index2 = this.segmentIndex;
|
|
@@ -13450,9 +13840,11 @@ var TalariaClient = class {
|
|
|
13450
13840
|
events,
|
|
13451
13841
|
startedAt,
|
|
13452
13842
|
endedAt,
|
|
13843
|
+
gzip,
|
|
13453
13844
|
keepalive: opts.keepalive
|
|
13454
13845
|
});
|
|
13455
13846
|
this.segmentIndex = index2 + 1;
|
|
13847
|
+
this.uploadedCompressedBytes += gzip.length;
|
|
13456
13848
|
} catch (error) {
|
|
13457
13849
|
if (isTerminalReplayLimitError(error)) {
|
|
13458
13850
|
console.warn(
|
|
@@ -13466,6 +13858,19 @@ var TalariaClient = class {
|
|
|
13466
13858
|
this.stopUploadingAfterLimit();
|
|
13467
13859
|
break;
|
|
13468
13860
|
}
|
|
13861
|
+
if (isOversizedSegmentError(error)) {
|
|
13862
|
+
const plan = planOversizedRetry(events);
|
|
13863
|
+
if (plan.action === "drop") {
|
|
13864
|
+
console.warn(
|
|
13865
|
+
"@newtalaria/browser: dropping rrweb event rejected as oversized",
|
|
13866
|
+
{ type: events[0]?.type }
|
|
13867
|
+
);
|
|
13868
|
+
continue;
|
|
13869
|
+
}
|
|
13870
|
+
this.buffer.prepend(plan.right);
|
|
13871
|
+
this.buffer.prepend(plan.left);
|
|
13872
|
+
continue;
|
|
13873
|
+
}
|
|
13469
13874
|
this.buffer.prepend(events);
|
|
13470
13875
|
console.warn("@newtalaria/browser: replays/ingestSegment failed", error);
|
|
13471
13876
|
break;
|
|
@@ -13473,9 +13878,44 @@ var TalariaClient = class {
|
|
|
13473
13878
|
if (opts.keepalive) break;
|
|
13474
13879
|
}
|
|
13475
13880
|
}
|
|
13881
|
+
maxSegmentsAllowed() {
|
|
13882
|
+
if (this.sessionSampled) return MAX_SEGMENTS_PER_REPLAY;
|
|
13883
|
+
if (this.options && this.options.replaysErrorAfterMs <= 0) {
|
|
13884
|
+
return MAX_SEGMENTS_PER_REPLAY;
|
|
13885
|
+
}
|
|
13886
|
+
return MAX_SEGMENTS_ERROR_CLIP;
|
|
13887
|
+
}
|
|
13888
|
+
/**
|
|
13889
|
+
* Pull the largest event prefix that gzips under the target compressed size.
|
|
13890
|
+
* Drops a single event that can never fit.
|
|
13891
|
+
*/
|
|
13892
|
+
async takeFittedSegment() {
|
|
13893
|
+
const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES);
|
|
13894
|
+
if (chunk.length === 0) return null;
|
|
13895
|
+
const fitted = await fitCompressedPrefix(
|
|
13896
|
+
chunk,
|
|
13897
|
+
compressReplayEvents,
|
|
13898
|
+
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
13899
|
+
MAX_COMPRESSED_SEGMENT_BYTES
|
|
13900
|
+
);
|
|
13901
|
+
if (!fitted) {
|
|
13902
|
+
console.warn(
|
|
13903
|
+
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
13904
|
+
{ type: chunk[0]?.type }
|
|
13905
|
+
);
|
|
13906
|
+
return this.takeFittedSegment();
|
|
13907
|
+
}
|
|
13908
|
+
if (fitted.remainder.length > 0) {
|
|
13909
|
+
this.buffer.prepend(fitted.remainder);
|
|
13910
|
+
}
|
|
13911
|
+
return { events: fitted.events, gzip: fitted.gzip };
|
|
13912
|
+
}
|
|
13476
13913
|
async finishOnServer(opts) {
|
|
13477
13914
|
if (!this.transport || !this.replayId) return;
|
|
13478
13915
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13916
|
+
if (this.segmentIndex > 0) {
|
|
13917
|
+
this.linkableReplayId = this.replayId;
|
|
13918
|
+
}
|
|
13479
13919
|
try {
|
|
13480
13920
|
await finishReplay(this.transport, {
|
|
13481
13921
|
replayId: this.replayId,
|
|
@@ -13513,9 +13953,17 @@ var Talaria = {
|
|
|
13513
13953
|
};
|
|
13514
13954
|
var index_default = Talaria;
|
|
13515
13955
|
export {
|
|
13956
|
+
MAX_COMPRESSED_SEGMENT_BYTES,
|
|
13957
|
+
MAX_ERROR_CLIP_COMPRESSED_BYTES,
|
|
13958
|
+
MAX_SEGMENTS_ERROR_CLIP,
|
|
13959
|
+
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
13516
13960
|
Talaria,
|
|
13517
13961
|
TalariaClient,
|
|
13518
|
-
|
|
13962
|
+
computeErrorClipDeadlineMs,
|
|
13963
|
+
index_default as default,
|
|
13964
|
+
fitCompressedPrefix,
|
|
13965
|
+
isErrorClipBudgetExhausted,
|
|
13966
|
+
planOversizedRetry
|
|
13519
13967
|
};
|
|
13520
13968
|
/*! Bundled license information:
|
|
13521
13969
|
|