@newtalaria/browser 0.1.0 → 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 -10
- package/dist/client.d.ts +52 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +608 -76
- 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 +57 -5
- 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 +11 -1
- 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,9 +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;
|
|
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;
|
|
167
138
|
var SegmentBuffer = class {
|
|
168
139
|
constructor() {
|
|
169
140
|
this.events = [];
|
|
@@ -179,8 +150,31 @@ var SegmentBuffer = class {
|
|
|
179
150
|
this.events.push(event);
|
|
180
151
|
this.pendingBytes += estimateJsonBytes(event);
|
|
181
152
|
}
|
|
182
|
-
|
|
183
|
-
|
|
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) {
|
|
184
178
|
const cutoff = now - RING_BUFFER_MS;
|
|
185
179
|
let remove = 0;
|
|
186
180
|
while (remove < this.events.length && this.events[remove].timestamp < cutoff) {
|
|
@@ -190,22 +184,55 @@ var SegmentBuffer = class {
|
|
|
190
184
|
this.events.splice(0, remove);
|
|
191
185
|
this.recomputeBytes();
|
|
192
186
|
}
|
|
187
|
+
while (this.events.length > 1 && this.pendingBytes > maxBytes) {
|
|
188
|
+
this.events.shift();
|
|
189
|
+
this.recomputeBytes();
|
|
190
|
+
}
|
|
193
191
|
}
|
|
194
192
|
shouldFlushBySize() {
|
|
195
193
|
return this.pendingBytes >= SEGMENT_SIZE_BYTES;
|
|
196
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
|
+
}
|
|
197
220
|
takeAll() {
|
|
198
221
|
const batch = this.events;
|
|
199
222
|
this.events = [];
|
|
200
223
|
this.pendingBytes = 0;
|
|
201
224
|
return batch;
|
|
202
225
|
}
|
|
203
|
-
/** Re-queue events at the front (failed upload retry). */
|
|
226
|
+
/** Re-queue events at the front (failed upload retry / bisect remainder). */
|
|
204
227
|
prepend(events) {
|
|
205
228
|
if (events.length === 0) return;
|
|
206
229
|
this.events = events.concat(this.events);
|
|
207
230
|
this.recomputeBytes();
|
|
208
231
|
}
|
|
232
|
+
clear() {
|
|
233
|
+
this.events = [];
|
|
234
|
+
this.pendingBytes = 0;
|
|
235
|
+
}
|
|
209
236
|
peekTimes() {
|
|
210
237
|
if (this.events.length === 0) return null;
|
|
211
238
|
const first = this.events[0];
|
|
@@ -220,6 +247,114 @@ var SegmentBuffer = class {
|
|
|
220
247
|
}
|
|
221
248
|
};
|
|
222
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
|
+
|
|
223
358
|
// ../../node_modules/rrweb/dist/rrweb.js
|
|
224
359
|
var __defProp = Object.defineProperty;
|
|
225
360
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -12975,13 +13110,27 @@ function redactUrl(raw) {
|
|
|
12975
13110
|
}
|
|
12976
13111
|
}
|
|
12977
13112
|
function defaultBlockSelector(extra) {
|
|
12978
|
-
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
|
+
];
|
|
12979
13120
|
if (extra?.trim()) parts.push(extra.trim());
|
|
12980
13121
|
return parts.join(", ");
|
|
12981
13122
|
}
|
|
12982
13123
|
|
|
12983
13124
|
// src/replay/recorder.ts
|
|
12984
13125
|
function startRecorder(options) {
|
|
13126
|
+
if (typeof document === "undefined") {
|
|
13127
|
+
return {
|
|
13128
|
+
stop: () => {
|
|
13129
|
+
},
|
|
13130
|
+
takeFullSnapshot: () => {
|
|
13131
|
+
}
|
|
13132
|
+
};
|
|
13133
|
+
}
|
|
12985
13134
|
const stopFn = record({
|
|
12986
13135
|
emit(event) {
|
|
12987
13136
|
options.onEvent(event);
|
|
@@ -12992,11 +13141,25 @@ function startRecorder(options) {
|
|
|
12992
13141
|
},
|
|
12993
13142
|
blockSelector: defaultBlockSelector(options.blockSelector),
|
|
12994
13143
|
recordCanvas: false,
|
|
12995
|
-
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
|
+
}
|
|
12996
13153
|
});
|
|
12997
13154
|
return {
|
|
12998
13155
|
stop: () => {
|
|
12999
13156
|
stopFn?.();
|
|
13157
|
+
},
|
|
13158
|
+
takeFullSnapshot: () => {
|
|
13159
|
+
try {
|
|
13160
|
+
record.takeFullSnapshot(true);
|
|
13161
|
+
} catch {
|
|
13162
|
+
}
|
|
13000
13163
|
}
|
|
13001
13164
|
};
|
|
13002
13165
|
}
|
|
@@ -13129,6 +13292,7 @@ function resolveOptions(options) {
|
|
|
13129
13292
|
release: options.release,
|
|
13130
13293
|
replaysSessionSampleRate: clamp01(options.replaysSessionSampleRate ?? 0),
|
|
13131
13294
|
replaysOnErrorSampleRate: clamp01(options.replaysOnErrorSampleRate ?? 1),
|
|
13295
|
+
replaysErrorAfterMs: normalizeErrorAfterMs(options.replaysErrorAfterMs),
|
|
13132
13296
|
maskAllInputs: options.maskAllInputs ?? true,
|
|
13133
13297
|
blockSelector: options.blockSelector ?? "",
|
|
13134
13298
|
userId: options.userId,
|
|
@@ -13139,12 +13303,26 @@ function clamp01(n2) {
|
|
|
13139
13303
|
if (Number.isNaN(n2)) return 0;
|
|
13140
13304
|
return Math.min(1, Math.max(0, n2));
|
|
13141
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
|
+
}
|
|
13142
13312
|
function levelToEventType(level) {
|
|
13143
13313
|
if (level === "fatal" || level === "error") return "error";
|
|
13144
13314
|
if (level === "warning") return "warning";
|
|
13145
13315
|
if (level === "debug") return "debug";
|
|
13146
13316
|
return "info";
|
|
13147
13317
|
}
|
|
13318
|
+
function isTerminalReplayLimitError(error) {
|
|
13319
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
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");
|
|
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
|
+
}
|
|
13148
13326
|
var TalariaClient = class {
|
|
13149
13327
|
constructor() {
|
|
13150
13328
|
this.options = null;
|
|
@@ -13153,14 +13331,28 @@ var TalariaClient = class {
|
|
|
13153
13331
|
this.sessionId = null;
|
|
13154
13332
|
this.recorder = null;
|
|
13155
13333
|
this.buffer = new SegmentBuffer();
|
|
13334
|
+
/** Continuous upload for the whole page session (session sample hit). */
|
|
13335
|
+
this.sessionSampled = false;
|
|
13156
13336
|
this.uploadEnabled = false;
|
|
13157
13337
|
this.startedOnServer = false;
|
|
13158
13338
|
this.finishedOnServer = false;
|
|
13159
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;
|
|
13160
13345
|
this.flushTimer = null;
|
|
13346
|
+
this.errorClipTimer = null;
|
|
13347
|
+
this.maxDurationTimer = null;
|
|
13161
13348
|
this.uploadChain = Promise.resolve();
|
|
13162
13349
|
this.closed = false;
|
|
13163
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;
|
|
13164
13356
|
}
|
|
13165
13357
|
init(options) {
|
|
13166
13358
|
if (this.options) {
|
|
@@ -13178,9 +13370,14 @@ var TalariaClient = class {
|
|
|
13178
13370
|
this.finishedOnServer = false;
|
|
13179
13371
|
this.startedOnServer = false;
|
|
13180
13372
|
this.segmentIndex = 0;
|
|
13373
|
+
this.uploadedCompressedBytes = 0;
|
|
13374
|
+
this.uploadStartedAtMs = null;
|
|
13375
|
+
this.errorClipDeadlineMs = null;
|
|
13376
|
+
this.linkableReplayId = null;
|
|
13181
13377
|
this.buffer = new SegmentBuffer();
|
|
13182
|
-
|
|
13183
|
-
this.
|
|
13378
|
+
this.uploadChain = Promise.resolve();
|
|
13379
|
+
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
13380
|
+
this.uploadEnabled = this.sessionSampled;
|
|
13184
13381
|
this.recorder = startRecorder({
|
|
13185
13382
|
maskAllInputs: this.options.maskAllInputs,
|
|
13186
13383
|
blockSelector: this.options.blockSelector,
|
|
@@ -13205,13 +13402,15 @@ var TalariaClient = class {
|
|
|
13205
13402
|
});
|
|
13206
13403
|
}
|
|
13207
13404
|
if (this.uploadEnabled) {
|
|
13405
|
+
this.markUploadStarted();
|
|
13208
13406
|
void this.enqueueUpload(async () => {
|
|
13209
13407
|
await this.ensureStarted({ keepalive: false });
|
|
13210
13408
|
});
|
|
13211
13409
|
}
|
|
13212
13410
|
}
|
|
13213
13411
|
getReplayId() {
|
|
13214
|
-
|
|
13412
|
+
if (this.uploadEnabled && this.replayId) return this.replayId;
|
|
13413
|
+
return this.linkableReplayId;
|
|
13215
13414
|
}
|
|
13216
13415
|
async captureException(error, context) {
|
|
13217
13416
|
const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
|
|
@@ -13239,16 +13438,39 @@ var TalariaClient = class {
|
|
|
13239
13438
|
return;
|
|
13240
13439
|
}
|
|
13241
13440
|
await this.enqueueUpload(async () => {
|
|
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
|
+
}
|
|
13242
13450
|
await this.ensureStarted({ keepalive });
|
|
13243
13451
|
await this.uploadPendingSegments({ keepalive });
|
|
13244
|
-
if (opts?.finish) {
|
|
13245
|
-
await this.finishOnServer({
|
|
13452
|
+
if (opts?.finish && this.uploadEnabled) {
|
|
13453
|
+
await this.finishOnServer({
|
|
13454
|
+
keepalive,
|
|
13455
|
+
reason: opts.reason ?? "pagehide"
|
|
13456
|
+
});
|
|
13457
|
+
if (!this.sessionSampled) {
|
|
13458
|
+
this.resetToBufferMode();
|
|
13459
|
+
} else {
|
|
13460
|
+
this.uploadEnabled = false;
|
|
13461
|
+
this.clearMaxDurationTimer();
|
|
13462
|
+
}
|
|
13246
13463
|
}
|
|
13247
13464
|
});
|
|
13248
13465
|
}
|
|
13466
|
+
/**
|
|
13467
|
+
* Stop recording, flush while still open, then fully reset so `init()` can
|
|
13468
|
+
* run again (React Strict Mode remount).
|
|
13469
|
+
*/
|
|
13249
13470
|
async close() {
|
|
13250
|
-
if (this.closed) return;
|
|
13251
|
-
this.
|
|
13471
|
+
if (this.closed && !this.options) return;
|
|
13472
|
+
this.clearErrorClipTimer();
|
|
13473
|
+
this.clearMaxDurationTimer();
|
|
13252
13474
|
if (this.flushTimer) {
|
|
13253
13475
|
clearInterval(this.flushTimer);
|
|
13254
13476
|
this.flushTimer = null;
|
|
@@ -13261,17 +13483,45 @@ var TalariaClient = class {
|
|
|
13261
13483
|
} catch {
|
|
13262
13484
|
}
|
|
13263
13485
|
}
|
|
13264
|
-
if (this.uploadEnabled) {
|
|
13265
|
-
|
|
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
|
+
}
|
|
13266
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;
|
|
13267
13509
|
}
|
|
13268
13510
|
onRrwebEvent(event) {
|
|
13269
|
-
if (this.closed) return;
|
|
13511
|
+
if (this.closed || !this.options) return;
|
|
13270
13512
|
this.buffer.push(event);
|
|
13271
13513
|
if (!this.uploadEnabled) {
|
|
13272
13514
|
this.buffer.trimRing();
|
|
13273
13515
|
return;
|
|
13274
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
|
+
}
|
|
13275
13525
|
if (this.buffer.shouldFlushBySize()) {
|
|
13276
13526
|
void this.flush({ reason: "size" });
|
|
13277
13527
|
}
|
|
@@ -13281,13 +13531,19 @@ var TalariaClient = class {
|
|
|
13281
13531
|
throw new Error("@newtalaria/browser: call Talaria.init() first");
|
|
13282
13532
|
}
|
|
13283
13533
|
const isErrorLike = args.level === "error" || args.level === "fatal";
|
|
13284
|
-
if (isErrorLike && !this.
|
|
13285
|
-
if (
|
|
13286
|
-
this.
|
|
13534
|
+
if (isErrorLike && !this.sessionSampled) {
|
|
13535
|
+
if (!this.uploadEnabled) {
|
|
13536
|
+
if (Math.random() < this.options.replaysOnErrorSampleRate) {
|
|
13537
|
+
this.uploadEnabled = true;
|
|
13538
|
+
this.checkoutFullSnapshot();
|
|
13539
|
+
this.markUploadStarted();
|
|
13540
|
+
this.scheduleErrorClipEnd();
|
|
13541
|
+
}
|
|
13542
|
+
} else {
|
|
13543
|
+
this.scheduleErrorClipEnd();
|
|
13287
13544
|
}
|
|
13288
13545
|
}
|
|
13289
|
-
|
|
13290
|
-
if (attachReplay) {
|
|
13546
|
+
if (this.uploadEnabled) {
|
|
13291
13547
|
try {
|
|
13292
13548
|
await this.flush({ reason: "capture", keepalive: args.keepalive });
|
|
13293
13549
|
} catch (error) {
|
|
@@ -13297,7 +13553,7 @@ var TalariaClient = class {
|
|
|
13297
13553
|
}
|
|
13298
13554
|
const tags = { ...args.context?.tags ?? {} };
|
|
13299
13555
|
const extra = args.context?.extra;
|
|
13300
|
-
const replayId =
|
|
13556
|
+
const replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
|
|
13301
13557
|
try {
|
|
13302
13558
|
await ingestEvent(this.transport, {
|
|
13303
13559
|
message: args.message,
|
|
@@ -13316,11 +13572,169 @@ var TalariaClient = class {
|
|
|
13316
13572
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13317
13573
|
keepalive: args.keepalive
|
|
13318
13574
|
});
|
|
13575
|
+
if (replayId && replayId === this.linkableReplayId) {
|
|
13576
|
+
this.linkableReplayId = null;
|
|
13577
|
+
}
|
|
13319
13578
|
} catch (error) {
|
|
13320
13579
|
console.warn("@newtalaria/browser: event ingest failed", error);
|
|
13321
13580
|
throw error;
|
|
13322
13581
|
}
|
|
13323
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
|
+
*/
|
|
13651
|
+
scheduleErrorClipEnd() {
|
|
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());
|
|
13660
|
+
this.clearErrorClipTimer();
|
|
13661
|
+
this.errorClipTimer = setTimeout(() => {
|
|
13662
|
+
this.errorClipTimer = null;
|
|
13663
|
+
void this.endErrorClip({ reason: "error_clip" });
|
|
13664
|
+
}, remaining);
|
|
13665
|
+
}
|
|
13666
|
+
clearErrorClipTimer() {
|
|
13667
|
+
if (this.errorClipTimer) {
|
|
13668
|
+
clearTimeout(this.errorClipTimer);
|
|
13669
|
+
this.errorClipTimer = null;
|
|
13670
|
+
}
|
|
13671
|
+
}
|
|
13672
|
+
/** Flush trailing post-error window, finish replay, return to ring buffer. */
|
|
13673
|
+
async endErrorClip(opts) {
|
|
13674
|
+
if (this.closed || this.sessionSampled || !this.uploadEnabled || !this.options || !this.transport) {
|
|
13675
|
+
return;
|
|
13676
|
+
}
|
|
13677
|
+
await this.enqueueUpload(async () => {
|
|
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"
|
|
13688
|
+
});
|
|
13689
|
+
this.resetToBufferMode();
|
|
13690
|
+
}
|
|
13691
|
+
/** After an error clip (or terminal limit), buffer locally until the next error. */
|
|
13692
|
+
resetToBufferMode() {
|
|
13693
|
+
this.clearErrorClipTimer();
|
|
13694
|
+
this.clearMaxDurationTimer();
|
|
13695
|
+
this.uploadEnabled = false;
|
|
13696
|
+
this.startedOnServer = false;
|
|
13697
|
+
this.finishedOnServer = false;
|
|
13698
|
+
this.segmentIndex = 0;
|
|
13699
|
+
this.uploadedCompressedBytes = 0;
|
|
13700
|
+
this.uploadStartedAtMs = null;
|
|
13701
|
+
this.errorClipDeadlineMs = null;
|
|
13702
|
+
this.replayId = createId();
|
|
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
|
+
}
|
|
13724
|
+
}
|
|
13725
|
+
stopUploadingAfterLimit() {
|
|
13726
|
+
this.clearErrorClipTimer();
|
|
13727
|
+
this.clearMaxDurationTimer();
|
|
13728
|
+
this.uploadEnabled = false;
|
|
13729
|
+
if (this.sessionSampled) {
|
|
13730
|
+
if (this.flushTimer) {
|
|
13731
|
+
clearInterval(this.flushTimer);
|
|
13732
|
+
this.flushTimer = null;
|
|
13733
|
+
}
|
|
13734
|
+
} else {
|
|
13735
|
+
this.resetToBufferMode();
|
|
13736
|
+
}
|
|
13737
|
+
}
|
|
13324
13738
|
installGlobalHandlers() {
|
|
13325
13739
|
if (typeof window === "undefined") return;
|
|
13326
13740
|
const onError = (event) => {
|
|
@@ -13361,13 +13775,61 @@ var TalariaClient = class {
|
|
|
13361
13775
|
keepalive: opts.keepalive
|
|
13362
13776
|
});
|
|
13363
13777
|
this.startedOnServer = true;
|
|
13778
|
+
this.markUploadStarted();
|
|
13364
13779
|
}
|
|
13365
13780
|
async uploadPendingSegments(opts) {
|
|
13366
13781
|
if (!this.options || !this.transport || !this.replayId) return;
|
|
13367
13782
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13783
|
+
this.prepareBufferForNewReplay();
|
|
13368
13784
|
while (this.buffer.length > 0) {
|
|
13369
|
-
|
|
13370
|
-
|
|
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
|
+
}
|
|
13371
13833
|
const startedAt = new Date(events[0].timestamp);
|
|
13372
13834
|
const endedAt = new Date(events[events.length - 1].timestamp);
|
|
13373
13835
|
const index2 = this.segmentIndex;
|
|
@@ -13378,10 +13840,37 @@ var TalariaClient = class {
|
|
|
13378
13840
|
events,
|
|
13379
13841
|
startedAt,
|
|
13380
13842
|
endedAt,
|
|
13843
|
+
gzip,
|
|
13381
13844
|
keepalive: opts.keepalive
|
|
13382
13845
|
});
|
|
13383
13846
|
this.segmentIndex = index2 + 1;
|
|
13847
|
+
this.uploadedCompressedBytes += gzip.length;
|
|
13384
13848
|
} catch (error) {
|
|
13849
|
+
if (isTerminalReplayLimitError(error)) {
|
|
13850
|
+
console.warn(
|
|
13851
|
+
"@newtalaria/browser: replay upload stopped (server limit)",
|
|
13852
|
+
error
|
|
13853
|
+
);
|
|
13854
|
+
await this.finishOnServer({
|
|
13855
|
+
keepalive: opts.keepalive,
|
|
13856
|
+
reason: "limit"
|
|
13857
|
+
});
|
|
13858
|
+
this.stopUploadingAfterLimit();
|
|
13859
|
+
break;
|
|
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
|
+
}
|
|
13385
13874
|
this.buffer.prepend(events);
|
|
13386
13875
|
console.warn("@newtalaria/browser: replays/ingestSegment failed", error);
|
|
13387
13876
|
break;
|
|
@@ -13389,9 +13878,44 @@ var TalariaClient = class {
|
|
|
13389
13878
|
if (opts.keepalive) break;
|
|
13390
13879
|
}
|
|
13391
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
|
+
}
|
|
13392
13913
|
async finishOnServer(opts) {
|
|
13393
13914
|
if (!this.transport || !this.replayId) return;
|
|
13394
13915
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13916
|
+
if (this.segmentIndex > 0) {
|
|
13917
|
+
this.linkableReplayId = this.replayId;
|
|
13918
|
+
}
|
|
13395
13919
|
try {
|
|
13396
13920
|
await finishReplay(this.transport, {
|
|
13397
13921
|
replayId: this.replayId,
|
|
@@ -13429,9 +13953,17 @@ var Talaria = {
|
|
|
13429
13953
|
};
|
|
13430
13954
|
var index_default = Talaria;
|
|
13431
13955
|
export {
|
|
13956
|
+
MAX_COMPRESSED_SEGMENT_BYTES,
|
|
13957
|
+
MAX_ERROR_CLIP_COMPRESSED_BYTES,
|
|
13958
|
+
MAX_SEGMENTS_ERROR_CLIP,
|
|
13959
|
+
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
13432
13960
|
Talaria,
|
|
13433
13961
|
TalariaClient,
|
|
13434
|
-
|
|
13962
|
+
computeErrorClipDeadlineMs,
|
|
13963
|
+
index_default as default,
|
|
13964
|
+
fitCompressedPrefix,
|
|
13965
|
+
isErrorClipBudgetExhausted,
|
|
13966
|
+
planOversizedRetry
|
|
13435
13967
|
};
|
|
13436
13968
|
/*! Bundled license information:
|
|
13437
13969
|
|