@newtalaria/browser 0.1.1 → 0.1.5
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 +88 -11
- package/dist/client.d.ts +42 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/iife.d.ts +2 -0
- package/dist/iife.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +534 -80
- 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 +4 -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/talaria.browser.iife.js +13986 -0
- package/dist/talaria.browser.iife.js.map +7 -0
- 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 +20 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -3
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: options.inlineStylesheet,
|
|
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,9 +13292,12 @@ 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,
|
|
13297
|
+
inlineStylesheet: options.inlineStylesheet ?? false,
|
|
13134
13298
|
blockSelector: options.blockSelector ?? "",
|
|
13135
13299
|
userId: options.userId,
|
|
13300
|
+
tags: options.tags,
|
|
13136
13301
|
disableDefaultIntegrations: options.disableDefaultIntegrations ?? false
|
|
13137
13302
|
};
|
|
13138
13303
|
}
|
|
@@ -13140,6 +13305,12 @@ function clamp01(n2) {
|
|
|
13140
13305
|
if (Number.isNaN(n2)) return 0;
|
|
13141
13306
|
return Math.min(1, Math.max(0, n2));
|
|
13142
13307
|
}
|
|
13308
|
+
function normalizeErrorAfterMs(n2) {
|
|
13309
|
+
if (n2 === void 0 || Number.isNaN(n2) || n2 < 0) {
|
|
13310
|
+
return ERROR_REPLAY_AFTER_MS;
|
|
13311
|
+
}
|
|
13312
|
+
return Math.floor(n2);
|
|
13313
|
+
}
|
|
13143
13314
|
function levelToEventType(level) {
|
|
13144
13315
|
if (level === "fatal" || level === "error") return "error";
|
|
13145
13316
|
if (level === "warning") return "warning";
|
|
@@ -13150,6 +13321,10 @@ function isTerminalReplayLimitError(error) {
|
|
|
13150
13321
|
const msg = error instanceof Error ? error.message : String(error);
|
|
13151
13322
|
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
13323
|
}
|
|
13324
|
+
function isOversizedSegmentError(error) {
|
|
13325
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
13326
|
+
return msg.includes("segment exceeds max compressed size") || msg.includes("segment exceeds max uncompressed size") || msg.includes("HTTP 400") && msg.includes("ApiValidationException");
|
|
13327
|
+
}
|
|
13153
13328
|
var TalariaClient = class {
|
|
13154
13329
|
constructor() {
|
|
13155
13330
|
this.options = null;
|
|
@@ -13164,11 +13339,22 @@ var TalariaClient = class {
|
|
|
13164
13339
|
this.startedOnServer = false;
|
|
13165
13340
|
this.finishedOnServer = false;
|
|
13166
13341
|
this.segmentIndex = 0;
|
|
13342
|
+
this.uploadedCompressedBytes = 0;
|
|
13343
|
+
/** Wall clock when continuous upload began (session or error promote). */
|
|
13344
|
+
this.uploadStartedAtMs = null;
|
|
13345
|
+
/** Absolute deadline for error-clip mode (not extended by later errors). */
|
|
13346
|
+
this.errorClipDeadlineMs = null;
|
|
13167
13347
|
this.flushTimer = null;
|
|
13168
13348
|
this.errorClipTimer = null;
|
|
13349
|
+
this.maxDurationTimer = null;
|
|
13169
13350
|
this.uploadChain = Promise.resolve();
|
|
13170
13351
|
this.closed = false;
|
|
13171
13352
|
this.teardowns = [];
|
|
13353
|
+
/**
|
|
13354
|
+
* Replay id that just finished with segments — kept briefly so capture can
|
|
13355
|
+
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
13356
|
+
*/
|
|
13357
|
+
this.linkableReplayId = null;
|
|
13172
13358
|
}
|
|
13173
13359
|
init(options) {
|
|
13174
13360
|
if (this.options) {
|
|
@@ -13186,11 +13372,17 @@ var TalariaClient = class {
|
|
|
13186
13372
|
this.finishedOnServer = false;
|
|
13187
13373
|
this.startedOnServer = false;
|
|
13188
13374
|
this.segmentIndex = 0;
|
|
13375
|
+
this.uploadedCompressedBytes = 0;
|
|
13376
|
+
this.uploadStartedAtMs = null;
|
|
13377
|
+
this.errorClipDeadlineMs = null;
|
|
13378
|
+
this.linkableReplayId = null;
|
|
13189
13379
|
this.buffer = new SegmentBuffer();
|
|
13380
|
+
this.uploadChain = Promise.resolve();
|
|
13190
13381
|
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
13191
13382
|
this.uploadEnabled = this.sessionSampled;
|
|
13192
13383
|
this.recorder = startRecorder({
|
|
13193
13384
|
maskAllInputs: this.options.maskAllInputs,
|
|
13385
|
+
inlineStylesheet: this.options.inlineStylesheet,
|
|
13194
13386
|
blockSelector: this.options.blockSelector,
|
|
13195
13387
|
onEvent: (event) => this.onRrwebEvent(event)
|
|
13196
13388
|
});
|
|
@@ -13213,13 +13405,15 @@ var TalariaClient = class {
|
|
|
13213
13405
|
});
|
|
13214
13406
|
}
|
|
13215
13407
|
if (this.uploadEnabled) {
|
|
13408
|
+
this.markUploadStarted();
|
|
13216
13409
|
void this.enqueueUpload(async () => {
|
|
13217
13410
|
await this.ensureStarted({ keepalive: false });
|
|
13218
13411
|
});
|
|
13219
13412
|
}
|
|
13220
13413
|
}
|
|
13221
13414
|
getReplayId() {
|
|
13222
|
-
|
|
13415
|
+
if (this.uploadEnabled && this.replayId) return this.replayId;
|
|
13416
|
+
return this.linkableReplayId;
|
|
13223
13417
|
}
|
|
13224
13418
|
async captureException(error, context) {
|
|
13225
13419
|
const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
|
|
@@ -13248,6 +13442,14 @@ var TalariaClient = class {
|
|
|
13248
13442
|
}
|
|
13249
13443
|
await this.enqueueUpload(async () => {
|
|
13250
13444
|
if (!this.uploadEnabled || this.closed) return;
|
|
13445
|
+
if (this.isPastErrorClipDeadline() && !opts?.finish) {
|
|
13446
|
+
await this.runEndErrorClip({ reason: "error_clip_deadline" });
|
|
13447
|
+
return;
|
|
13448
|
+
}
|
|
13449
|
+
if (this.isPastMaxDuration() && !opts?.finish) {
|
|
13450
|
+
await this.runStopForMaxDuration({ keepalive });
|
|
13451
|
+
return;
|
|
13452
|
+
}
|
|
13251
13453
|
await this.ensureStarted({ keepalive });
|
|
13252
13454
|
await this.uploadPendingSegments({ keepalive });
|
|
13253
13455
|
if (opts?.finish && this.uploadEnabled) {
|
|
@@ -13259,14 +13461,19 @@ var TalariaClient = class {
|
|
|
13259
13461
|
this.resetToBufferMode();
|
|
13260
13462
|
} else {
|
|
13261
13463
|
this.uploadEnabled = false;
|
|
13464
|
+
this.clearMaxDurationTimer();
|
|
13262
13465
|
}
|
|
13263
13466
|
}
|
|
13264
13467
|
});
|
|
13265
13468
|
}
|
|
13469
|
+
/**
|
|
13470
|
+
* Stop recording, flush while still open, then fully reset so `init()` can
|
|
13471
|
+
* run again (React Strict Mode remount).
|
|
13472
|
+
*/
|
|
13266
13473
|
async close() {
|
|
13267
|
-
if (this.closed) return;
|
|
13268
|
-
this.closed = true;
|
|
13474
|
+
if (this.closed && !this.options) return;
|
|
13269
13475
|
this.clearErrorClipTimer();
|
|
13476
|
+
this.clearMaxDurationTimer();
|
|
13270
13477
|
if (this.flushTimer) {
|
|
13271
13478
|
clearInterval(this.flushTimer);
|
|
13272
13479
|
this.flushTimer = null;
|
|
@@ -13279,17 +13486,45 @@ var TalariaClient = class {
|
|
|
13279
13486
|
} catch {
|
|
13280
13487
|
}
|
|
13281
13488
|
}
|
|
13282
|
-
if (this.uploadEnabled) {
|
|
13283
|
-
|
|
13489
|
+
if (this.uploadEnabled && this.options && this.transport) {
|
|
13490
|
+
try {
|
|
13491
|
+
await this.flush({ reason: "close", finish: true });
|
|
13492
|
+
} catch (error) {
|
|
13493
|
+
console.warn("@newtalaria/browser: close flush failed", error);
|
|
13494
|
+
}
|
|
13284
13495
|
}
|
|
13496
|
+
this.options = null;
|
|
13497
|
+
this.transport = null;
|
|
13498
|
+
this.replayId = null;
|
|
13499
|
+
this.sessionId = null;
|
|
13500
|
+
this.sessionSampled = false;
|
|
13501
|
+
this.uploadEnabled = false;
|
|
13502
|
+
this.startedOnServer = false;
|
|
13503
|
+
this.finishedOnServer = false;
|
|
13504
|
+
this.segmentIndex = 0;
|
|
13505
|
+
this.uploadedCompressedBytes = 0;
|
|
13506
|
+
this.uploadStartedAtMs = null;
|
|
13507
|
+
this.errorClipDeadlineMs = null;
|
|
13508
|
+
this.buffer.clear();
|
|
13509
|
+
this.uploadChain = Promise.resolve();
|
|
13510
|
+
this.closed = false;
|
|
13511
|
+
this.linkableReplayId = null;
|
|
13285
13512
|
}
|
|
13286
13513
|
onRrwebEvent(event) {
|
|
13287
|
-
if (this.closed) return;
|
|
13514
|
+
if (this.closed || !this.options) return;
|
|
13288
13515
|
this.buffer.push(event);
|
|
13289
13516
|
if (!this.uploadEnabled) {
|
|
13290
13517
|
this.buffer.trimRing();
|
|
13291
13518
|
return;
|
|
13292
13519
|
}
|
|
13520
|
+
if (this.isPastErrorClipDeadline()) {
|
|
13521
|
+
void this.flush({ reason: "error_clip_deadline" });
|
|
13522
|
+
return;
|
|
13523
|
+
}
|
|
13524
|
+
if (this.isPastMaxDuration()) {
|
|
13525
|
+
void this.flush({ reason: "max_duration" });
|
|
13526
|
+
return;
|
|
13527
|
+
}
|
|
13293
13528
|
if (this.buffer.shouldFlushBySize()) {
|
|
13294
13529
|
void this.flush({ reason: "size" });
|
|
13295
13530
|
}
|
|
@@ -13303,14 +13538,15 @@ var TalariaClient = class {
|
|
|
13303
13538
|
if (!this.uploadEnabled) {
|
|
13304
13539
|
if (Math.random() < this.options.replaysOnErrorSampleRate) {
|
|
13305
13540
|
this.uploadEnabled = true;
|
|
13541
|
+
this.checkoutFullSnapshot();
|
|
13542
|
+
this.markUploadStarted();
|
|
13306
13543
|
this.scheduleErrorClipEnd();
|
|
13307
13544
|
}
|
|
13308
13545
|
} else {
|
|
13309
13546
|
this.scheduleErrorClipEnd();
|
|
13310
13547
|
}
|
|
13311
13548
|
}
|
|
13312
|
-
|
|
13313
|
-
if (attachReplay) {
|
|
13549
|
+
if (this.uploadEnabled) {
|
|
13314
13550
|
try {
|
|
13315
13551
|
await this.flush({ reason: "capture", keepalive: args.keepalive });
|
|
13316
13552
|
} catch (error) {
|
|
@@ -13318,9 +13554,12 @@ var TalariaClient = class {
|
|
|
13318
13554
|
throw error;
|
|
13319
13555
|
}
|
|
13320
13556
|
}
|
|
13321
|
-
const tags = {
|
|
13557
|
+
const tags = {
|
|
13558
|
+
...this.options.tags ?? {},
|
|
13559
|
+
...args.context?.tags ?? {}
|
|
13560
|
+
};
|
|
13322
13561
|
const extra = args.context?.extra;
|
|
13323
|
-
const replayId =
|
|
13562
|
+
const replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
|
|
13324
13563
|
try {
|
|
13325
13564
|
await ingestEvent(this.transport, {
|
|
13326
13565
|
message: args.message,
|
|
@@ -13339,18 +13578,96 @@ var TalariaClient = class {
|
|
|
13339
13578
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13340
13579
|
keepalive: args.keepalive
|
|
13341
13580
|
});
|
|
13581
|
+
if (replayId && replayId === this.linkableReplayId) {
|
|
13582
|
+
this.linkableReplayId = null;
|
|
13583
|
+
}
|
|
13342
13584
|
} catch (error) {
|
|
13343
13585
|
console.warn("@newtalaria/browser: event ingest failed", error);
|
|
13344
13586
|
throw error;
|
|
13345
13587
|
}
|
|
13346
13588
|
}
|
|
13589
|
+
markUploadStarted() {
|
|
13590
|
+
if (this.uploadStartedAtMs == null) {
|
|
13591
|
+
this.uploadStartedAtMs = Date.now();
|
|
13592
|
+
}
|
|
13593
|
+
if (!this.sessionSampled && this.options && this.options.replaysErrorAfterMs > 0 && this.errorClipDeadlineMs == null) {
|
|
13594
|
+
this.errorClipDeadlineMs = computeErrorClipDeadlineMs(
|
|
13595
|
+
this.uploadStartedAtMs,
|
|
13596
|
+
this.options.replaysErrorAfterMs
|
|
13597
|
+
);
|
|
13598
|
+
}
|
|
13599
|
+
this.scheduleMaxDurationStop();
|
|
13600
|
+
}
|
|
13601
|
+
isPastMaxDuration() {
|
|
13602
|
+
if (this.uploadStartedAtMs == null) return false;
|
|
13603
|
+
return Date.now() - this.uploadStartedAtMs >= MAX_REPLAY_DURATION_MS;
|
|
13604
|
+
}
|
|
13605
|
+
isPastErrorClipDeadline() {
|
|
13606
|
+
if (this.sessionSampled || this.errorClipDeadlineMs == null) return false;
|
|
13607
|
+
return Date.now() >= this.errorClipDeadlineMs;
|
|
13608
|
+
}
|
|
13609
|
+
isErrorClipMode() {
|
|
13610
|
+
return !this.sessionSampled && !!this.options && this.options.replaysErrorAfterMs > 0;
|
|
13611
|
+
}
|
|
13612
|
+
scheduleMaxDurationStop() {
|
|
13613
|
+
if (this.closed || this.uploadStartedAtMs == null) return;
|
|
13614
|
+
this.clearMaxDurationTimer();
|
|
13615
|
+
const remaining = Math.max(
|
|
13616
|
+
0,
|
|
13617
|
+
MAX_REPLAY_DURATION_MS - (Date.now() - this.uploadStartedAtMs)
|
|
13618
|
+
);
|
|
13619
|
+
this.maxDurationTimer = setTimeout(() => {
|
|
13620
|
+
this.maxDurationTimer = null;
|
|
13621
|
+
if (!this.uploadEnabled || this.closed) return;
|
|
13622
|
+
void this.enqueueUpload(async () => {
|
|
13623
|
+
if (!this.uploadEnabled || this.closed) return;
|
|
13624
|
+
await this.runStopForMaxDuration({ keepalive: false });
|
|
13625
|
+
});
|
|
13626
|
+
}, remaining);
|
|
13627
|
+
}
|
|
13628
|
+
clearMaxDurationTimer() {
|
|
13629
|
+
if (this.maxDurationTimer) {
|
|
13630
|
+
clearTimeout(this.maxDurationTimer);
|
|
13631
|
+
this.maxDurationTimer = null;
|
|
13632
|
+
}
|
|
13633
|
+
}
|
|
13634
|
+
async runStopForMaxDuration(opts) {
|
|
13635
|
+
if (!this.uploadEnabled) return;
|
|
13636
|
+
await this.ensureStarted({ keepalive: opts.keepalive });
|
|
13637
|
+
await this.uploadPendingSegments({ keepalive: opts.keepalive });
|
|
13638
|
+
await this.finishOnServer({
|
|
13639
|
+
keepalive: opts.keepalive,
|
|
13640
|
+
reason: "max_duration"
|
|
13641
|
+
});
|
|
13642
|
+
if (this.sessionSampled) {
|
|
13643
|
+
this.uploadEnabled = false;
|
|
13644
|
+
this.clearMaxDurationTimer();
|
|
13645
|
+
if (this.flushTimer) {
|
|
13646
|
+
clearInterval(this.flushTimer);
|
|
13647
|
+
this.flushTimer = null;
|
|
13648
|
+
}
|
|
13649
|
+
} else {
|
|
13650
|
+
this.resetToBufferMode();
|
|
13651
|
+
}
|
|
13652
|
+
}
|
|
13653
|
+
/**
|
|
13654
|
+
* Schedule end of the cheap error clip against a fixed absolute deadline.
|
|
13655
|
+
* Subsequent errors re-arm the timer but never push the wall later.
|
|
13656
|
+
*/
|
|
13347
13657
|
scheduleErrorClipEnd() {
|
|
13348
|
-
if (this.sessionSampled || this.closed) return;
|
|
13658
|
+
if (this.sessionSampled || this.closed || !this.options) return;
|
|
13659
|
+
const afterMs = this.options.replaysErrorAfterMs;
|
|
13660
|
+
if (afterMs <= 0) return;
|
|
13661
|
+
if (this.errorClipDeadlineMs == null) {
|
|
13662
|
+
const started = this.uploadStartedAtMs ?? Date.now();
|
|
13663
|
+
this.errorClipDeadlineMs = computeErrorClipDeadlineMs(started, afterMs);
|
|
13664
|
+
}
|
|
13665
|
+
const remaining = Math.max(0, this.errorClipDeadlineMs - Date.now());
|
|
13349
13666
|
this.clearErrorClipTimer();
|
|
13350
13667
|
this.errorClipTimer = setTimeout(() => {
|
|
13351
13668
|
this.errorClipTimer = null;
|
|
13352
|
-
void this.endErrorClip();
|
|
13353
|
-
},
|
|
13669
|
+
void this.endErrorClip({ reason: "error_clip" });
|
|
13670
|
+
}, remaining);
|
|
13354
13671
|
}
|
|
13355
13672
|
clearErrorClipTimer() {
|
|
13356
13673
|
if (this.errorClipTimer) {
|
|
@@ -13359,30 +13676,61 @@ var TalariaClient = class {
|
|
|
13359
13676
|
}
|
|
13360
13677
|
}
|
|
13361
13678
|
/** Flush trailing post-error window, finish replay, return to ring buffer. */
|
|
13362
|
-
async endErrorClip() {
|
|
13679
|
+
async endErrorClip(opts) {
|
|
13363
13680
|
if (this.closed || this.sessionSampled || !this.uploadEnabled || !this.options || !this.transport) {
|
|
13364
13681
|
return;
|
|
13365
13682
|
}
|
|
13366
13683
|
await this.enqueueUpload(async () => {
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13684
|
+
await this.runEndErrorClip(opts);
|
|
13685
|
+
});
|
|
13686
|
+
}
|
|
13687
|
+
async runEndErrorClip(opts) {
|
|
13688
|
+
if (!this.uploadEnabled || this.sessionSampled) return;
|
|
13689
|
+
await this.ensureStarted({ keepalive: false });
|
|
13690
|
+
await this.uploadPendingSegments({ keepalive: false });
|
|
13691
|
+
await this.finishOnServer({
|
|
13692
|
+
keepalive: false,
|
|
13693
|
+
reason: opts?.reason ?? "error_clip"
|
|
13372
13694
|
});
|
|
13695
|
+
this.resetToBufferMode();
|
|
13373
13696
|
}
|
|
13374
13697
|
/** After an error clip (or terminal limit), buffer locally until the next error. */
|
|
13375
13698
|
resetToBufferMode() {
|
|
13376
13699
|
this.clearErrorClipTimer();
|
|
13700
|
+
this.clearMaxDurationTimer();
|
|
13377
13701
|
this.uploadEnabled = false;
|
|
13378
13702
|
this.startedOnServer = false;
|
|
13379
13703
|
this.finishedOnServer = false;
|
|
13380
13704
|
this.segmentIndex = 0;
|
|
13705
|
+
this.uploadedCompressedBytes = 0;
|
|
13706
|
+
this.uploadStartedAtMs = null;
|
|
13707
|
+
this.errorClipDeadlineMs = null;
|
|
13381
13708
|
this.replayId = createId();
|
|
13382
|
-
this.buffer.
|
|
13709
|
+
this.buffer.clear();
|
|
13710
|
+
this.checkoutFullSnapshot();
|
|
13711
|
+
}
|
|
13712
|
+
/** Emit a checkout FullSnapshot into the ring buffer (sync via rrweb emit). */
|
|
13713
|
+
checkoutFullSnapshot() {
|
|
13714
|
+
this.recorder?.takeFullSnapshot();
|
|
13715
|
+
}
|
|
13716
|
+
/**
|
|
13717
|
+
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
13718
|
+
* produce a blank rrweb player.
|
|
13719
|
+
*/
|
|
13720
|
+
prepareBufferForNewReplay() {
|
|
13721
|
+
if (this.segmentIndex !== 0) return;
|
|
13722
|
+
if (!this.buffer.hasFullSnapshot()) {
|
|
13723
|
+
this.checkoutFullSnapshot();
|
|
13724
|
+
}
|
|
13725
|
+
if (!this.buffer.trimToFullSnapshot()) {
|
|
13726
|
+
console.warn(
|
|
13727
|
+
"@newtalaria/browser: replay has no FullSnapshot; player may show a blank screen"
|
|
13728
|
+
);
|
|
13729
|
+
}
|
|
13383
13730
|
}
|
|
13384
13731
|
stopUploadingAfterLimit() {
|
|
13385
13732
|
this.clearErrorClipTimer();
|
|
13733
|
+
this.clearMaxDurationTimer();
|
|
13386
13734
|
this.uploadEnabled = false;
|
|
13387
13735
|
if (this.sessionSampled) {
|
|
13388
13736
|
if (this.flushTimer) {
|
|
@@ -13433,13 +13781,61 @@ var TalariaClient = class {
|
|
|
13433
13781
|
keepalive: opts.keepalive
|
|
13434
13782
|
});
|
|
13435
13783
|
this.startedOnServer = true;
|
|
13784
|
+
this.markUploadStarted();
|
|
13436
13785
|
}
|
|
13437
13786
|
async uploadPendingSegments(opts) {
|
|
13438
13787
|
if (!this.options || !this.transport || !this.replayId) return;
|
|
13439
13788
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13789
|
+
this.prepareBufferForNewReplay();
|
|
13440
13790
|
while (this.buffer.length > 0) {
|
|
13441
|
-
|
|
13442
|
-
|
|
13791
|
+
if (this.isPastMaxDuration()) {
|
|
13792
|
+
break;
|
|
13793
|
+
}
|
|
13794
|
+
if (this.isPastErrorClipDeadline()) {
|
|
13795
|
+
break;
|
|
13796
|
+
}
|
|
13797
|
+
if (this.segmentIndex >= this.maxSegmentsAllowed()) {
|
|
13798
|
+
console.warn(
|
|
13799
|
+
"@newtalaria/browser: replay upload stopped (max segments)",
|
|
13800
|
+
{ segmentIndex: this.segmentIndex }
|
|
13801
|
+
);
|
|
13802
|
+
await this.finishOnServer({
|
|
13803
|
+
keepalive: opts.keepalive,
|
|
13804
|
+
reason: "max_segments"
|
|
13805
|
+
});
|
|
13806
|
+
this.stopUploadingAfterLimit();
|
|
13807
|
+
break;
|
|
13808
|
+
}
|
|
13809
|
+
if (this.isErrorClipMode() && isErrorClipBudgetExhausted({
|
|
13810
|
+
segmentIndex: this.segmentIndex,
|
|
13811
|
+
uploadedCompressedBytes: this.uploadedCompressedBytes
|
|
13812
|
+
})) {
|
|
13813
|
+
console.warn(
|
|
13814
|
+
"@newtalaria/browser: replay upload stopped (error clip budget)",
|
|
13815
|
+
{
|
|
13816
|
+
segmentIndex: this.segmentIndex,
|
|
13817
|
+
uploadedCompressedBytes: this.uploadedCompressedBytes
|
|
13818
|
+
}
|
|
13819
|
+
);
|
|
13820
|
+
await this.finishOnServer({
|
|
13821
|
+
keepalive: opts.keepalive,
|
|
13822
|
+
reason: "error_clip_budget"
|
|
13823
|
+
});
|
|
13824
|
+
this.stopUploadingAfterLimit();
|
|
13825
|
+
break;
|
|
13826
|
+
}
|
|
13827
|
+
const fitted = await this.takeFittedSegment();
|
|
13828
|
+
if (!fitted) break;
|
|
13829
|
+
const { events, gzip } = fitted;
|
|
13830
|
+
if (this.isErrorClipMode() && this.uploadedCompressedBytes + gzip.length > MAX_ERROR_CLIP_COMPRESSED_BYTES && this.segmentIndex > 0) {
|
|
13831
|
+
this.buffer.clear();
|
|
13832
|
+
await this.finishOnServer({
|
|
13833
|
+
keepalive: opts.keepalive,
|
|
13834
|
+
reason: "error_clip_budget"
|
|
13835
|
+
});
|
|
13836
|
+
this.stopUploadingAfterLimit();
|
|
13837
|
+
break;
|
|
13838
|
+
}
|
|
13443
13839
|
const startedAt = new Date(events[0].timestamp);
|
|
13444
13840
|
const endedAt = new Date(events[events.length - 1].timestamp);
|
|
13445
13841
|
const index2 = this.segmentIndex;
|
|
@@ -13450,9 +13846,11 @@ var TalariaClient = class {
|
|
|
13450
13846
|
events,
|
|
13451
13847
|
startedAt,
|
|
13452
13848
|
endedAt,
|
|
13849
|
+
gzip,
|
|
13453
13850
|
keepalive: opts.keepalive
|
|
13454
13851
|
});
|
|
13455
13852
|
this.segmentIndex = index2 + 1;
|
|
13853
|
+
this.uploadedCompressedBytes += gzip.length;
|
|
13456
13854
|
} catch (error) {
|
|
13457
13855
|
if (isTerminalReplayLimitError(error)) {
|
|
13458
13856
|
console.warn(
|
|
@@ -13466,6 +13864,19 @@ var TalariaClient = class {
|
|
|
13466
13864
|
this.stopUploadingAfterLimit();
|
|
13467
13865
|
break;
|
|
13468
13866
|
}
|
|
13867
|
+
if (isOversizedSegmentError(error)) {
|
|
13868
|
+
const plan = planOversizedRetry(events);
|
|
13869
|
+
if (plan.action === "drop") {
|
|
13870
|
+
console.warn(
|
|
13871
|
+
"@newtalaria/browser: dropping rrweb event rejected as oversized",
|
|
13872
|
+
{ type: events[0]?.type }
|
|
13873
|
+
);
|
|
13874
|
+
continue;
|
|
13875
|
+
}
|
|
13876
|
+
this.buffer.prepend(plan.right);
|
|
13877
|
+
this.buffer.prepend(plan.left);
|
|
13878
|
+
continue;
|
|
13879
|
+
}
|
|
13469
13880
|
this.buffer.prepend(events);
|
|
13470
13881
|
console.warn("@newtalaria/browser: replays/ingestSegment failed", error);
|
|
13471
13882
|
break;
|
|
@@ -13473,9 +13884,44 @@ var TalariaClient = class {
|
|
|
13473
13884
|
if (opts.keepalive) break;
|
|
13474
13885
|
}
|
|
13475
13886
|
}
|
|
13887
|
+
maxSegmentsAllowed() {
|
|
13888
|
+
if (this.sessionSampled) return MAX_SEGMENTS_PER_REPLAY;
|
|
13889
|
+
if (this.options && this.options.replaysErrorAfterMs <= 0) {
|
|
13890
|
+
return MAX_SEGMENTS_PER_REPLAY;
|
|
13891
|
+
}
|
|
13892
|
+
return MAX_SEGMENTS_ERROR_CLIP;
|
|
13893
|
+
}
|
|
13894
|
+
/**
|
|
13895
|
+
* Pull the largest event prefix that gzips under the target compressed size.
|
|
13896
|
+
* Drops a single event that can never fit.
|
|
13897
|
+
*/
|
|
13898
|
+
async takeFittedSegment() {
|
|
13899
|
+
const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES);
|
|
13900
|
+
if (chunk.length === 0) return null;
|
|
13901
|
+
const fitted = await fitCompressedPrefix(
|
|
13902
|
+
chunk,
|
|
13903
|
+
compressReplayEvents,
|
|
13904
|
+
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
13905
|
+
MAX_COMPRESSED_SEGMENT_BYTES
|
|
13906
|
+
);
|
|
13907
|
+
if (!fitted) {
|
|
13908
|
+
console.warn(
|
|
13909
|
+
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
13910
|
+
{ type: chunk[0]?.type }
|
|
13911
|
+
);
|
|
13912
|
+
return this.takeFittedSegment();
|
|
13913
|
+
}
|
|
13914
|
+
if (fitted.remainder.length > 0) {
|
|
13915
|
+
this.buffer.prepend(fitted.remainder);
|
|
13916
|
+
}
|
|
13917
|
+
return { events: fitted.events, gzip: fitted.gzip };
|
|
13918
|
+
}
|
|
13476
13919
|
async finishOnServer(opts) {
|
|
13477
13920
|
if (!this.transport || !this.replayId) return;
|
|
13478
13921
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13922
|
+
if (this.segmentIndex > 0) {
|
|
13923
|
+
this.linkableReplayId = this.replayId;
|
|
13924
|
+
}
|
|
13479
13925
|
try {
|
|
13480
13926
|
await finishReplay(this.transport, {
|
|
13481
13927
|
replayId: this.replayId,
|
|
@@ -13513,9 +13959,17 @@ var Talaria = {
|
|
|
13513
13959
|
};
|
|
13514
13960
|
var index_default = Talaria;
|
|
13515
13961
|
export {
|
|
13962
|
+
MAX_COMPRESSED_SEGMENT_BYTES,
|
|
13963
|
+
MAX_ERROR_CLIP_COMPRESSED_BYTES,
|
|
13964
|
+
MAX_SEGMENTS_ERROR_CLIP,
|
|
13965
|
+
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
13516
13966
|
Talaria,
|
|
13517
13967
|
TalariaClient,
|
|
13518
|
-
|
|
13968
|
+
computeErrorClipDeadlineMs,
|
|
13969
|
+
index_default as default,
|
|
13970
|
+
fitCompressedPrefix,
|
|
13971
|
+
isErrorClipBudgetExhausted,
|
|
13972
|
+
planOversizedRetry
|
|
13519
13973
|
};
|
|
13520
13974
|
/*! Bundled license information:
|
|
13521
13975
|
|