@newtalaria/browser 0.1.9 → 0.1.12
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 +4 -2
- package/dist/client.d.ts +6 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/index.js +299 -53
- package/dist/index.js.map +4 -4
- package/dist/replay/fit_segment.d.ts +9 -1
- package/dist/replay/fit_segment.d.ts.map +1 -1
- package/dist/replay/paint_base_size.d.ts +2 -1
- package/dist/replay/paint_base_size.d.ts.map +1 -1
- package/dist/replay/segment_buffer.d.ts +15 -3
- package/dist/replay/segment_buffer.d.ts.map +1 -1
- package/dist/sdk_meta.d.ts +4 -0
- package/dist/sdk_meta.d.ts.map +1 -0
- package/dist/talaria.browser.iife.js +299 -53
- package/dist/talaria.browser.iife.js.map +4 -4
- package/dist/utils/browser_context.d.ts +28 -0
- package/dist/utils/browser_context.d.ts.map +1 -0
- package/dist/utils/browser_extension_noise.d.ts +11 -0
- package/dist/utils/browser_extension_noise.d.ts.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,9 @@ await Talaria.flush();
|
|
|
53
53
|
await Talaria.close();
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
`Talaria.init` also installs `window.onerror` / `unhandledrejection` handlers unless you pass `disableDefaultIntegrations: true`. Opaque cross-origin `"Script error."` events (no usable `Error` object) are ignored by default.
|
|
56
|
+
`Talaria.init` also installs `window.onerror` / `unhandledrejection` handlers unless you pass `disableDefaultIntegrations: true`. Opaque cross-origin `"Script error."` events (no usable `Error` object) are ignored by default. Errors from browser extensions (`chrome-extension://`, `moz-extension://`, etc.) are also ignored.
|
|
57
|
+
|
|
58
|
+
Every ingested event includes browser runtime tags (`browser.name`, `browser.version`, `os.name`, `os.version`, `device`) and `extra.browser` / `extra.sdk` for triage.
|
|
57
59
|
|
|
58
60
|
Optional init `tags` are merged into every captured event (per-call tags win on key conflict).
|
|
59
61
|
|
|
@@ -99,7 +101,7 @@ You pay for **uploaded + retained** bytes, not for local buffering. Prefer error
|
|
|
99
101
|
| Error sample + `replaysErrorAfterMs > 0` | Upload buffer (≤~960KiB gzip pack target / **1MiB** server cap) + trailing window, hard caps **12 segments** or **2MiB** compressed, attach `replayId` only if segments landed, `finish`, return to buffer mode |
|
|
100
102
|
| Error sample + `replaysErrorAfterMs = 0` | Upload buffer then continue like session mode until **5 min** / unload / size caps |
|
|
101
103
|
| Server limit (replay segments / total size / duration) | Stop uploading that replay; no retries |
|
|
102
|
-
| Oversized FullSnapshot on segment 0 | **Abort** the clip (no blank orphan upload); error event gets `replay.capture=failed` + reason |
|
|
104
|
+
| Oversized FullSnapshot on segment 0 | **Abort** the clip (no blank orphan upload); error event gets `replay.capture=failed` + reason. Meta+FullSnapshot are taken/fitted **atomically** so a soft estimated take window cannot orphan the snapshot. |
|
|
103
105
|
| Oversized single non-FS rrweb event | Dropped with a console warning (cannot fit under segment cap) |
|
|
104
106
|
| `pagehide` / `close` | Flush pending segments with `fetch` `keepalive`, then `replays/finish`; `close` fully resets so `init()` works again (React Strict Mode) |
|
|
105
107
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CaptureContext, SeverityLevel, TalariaInitOptions } from './types.js';
|
|
2
|
-
export { computeErrorClipDeadlineMs, fitCompressedPrefix, isErrorClipBudgetExhausted, planOversizedRetry, } from './replay/fit_segment.js';
|
|
2
|
+
export { computeErrorClipDeadlineMs, fitCompressedPrefix, isErrorClipBudgetExhausted, paintBaseEventCount, planOversizedRetry, } from './replay/fit_segment.js';
|
|
3
3
|
export { MAX_SEGMENTS_ERROR_CLIP, MAX_ERROR_CLIP_COMPRESSED_BYTES, TARGET_COMPRESSED_SEGMENT_BYTES, MAX_COMPRESSED_SEGMENT_BYTES, } from './replay/segment_buffer.js';
|
|
4
4
|
export { REPLAY_CAPTURE_TAG, REPLAY_CAPTURE_REASON_TAG, applyReplayCaptureTags, mergeReplayCaptureExtra, } from './replay/capture_outcome.js';
|
|
5
5
|
export type { ReplayCaptureOutcome, ReplayCaptureReason, ReplayCaptureStatus, } from './replay/capture_outcome.js';
|
|
@@ -34,6 +34,8 @@ export declare class TalariaClient {
|
|
|
34
34
|
private linkableReplayId;
|
|
35
35
|
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
36
36
|
private lastReplayCaptureFailure;
|
|
37
|
+
/** Cached once per init for event tags/extra. */
|
|
38
|
+
private browserContext;
|
|
37
39
|
init(options: TalariaInitOptions): void;
|
|
38
40
|
getReplayId(): string | null;
|
|
39
41
|
captureException(error: unknown, context?: CaptureContext): Promise<void>;
|
|
@@ -89,8 +91,9 @@ export declare class TalariaClient {
|
|
|
89
91
|
private maxSegmentsAllowed;
|
|
90
92
|
/**
|
|
91
93
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
+
*
|
|
95
|
+
* Segment 0 keeps Meta+FullSnapshot (paint base) atomic through take + fit so
|
|
96
|
+
* a soft estimated-byte window cannot orphan the FullSnapshot.
|
|
94
97
|
*/
|
|
95
98
|
private takeFittedSegment;
|
|
96
99
|
private finishOnServer;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,21 @@ function createId() {
|
|
|
15
15
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// src/utils/browser_extension_noise.ts
|
|
19
|
+
var EXTENSION_URL = /(?:chrome|moz|safari|safari-web|ms-browser)-extension:\/\//i;
|
|
20
|
+
var WEBKIT_MASKED = /webkit-masked-url:\/\/hidden\//i;
|
|
21
|
+
function isBrowserExtensionNoise(opts) {
|
|
22
|
+
const filename = opts.filename ?? "";
|
|
23
|
+
if (EXTENSION_URL.test(filename) || WEBKIT_MASKED.test(filename)) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
const stack = opts.stack ?? "";
|
|
27
|
+
if (EXTENSION_URL.test(stack) || WEBKIT_MASKED.test(stack)) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
// src/utils/opaque_script_error.ts
|
|
19
34
|
function isOpaqueCrossOriginScriptError(event) {
|
|
20
35
|
const msg = (event.message ?? "").trim();
|
|
@@ -23,6 +38,137 @@ function isOpaqueCrossOriginScriptError(event) {
|
|
|
23
38
|
return true;
|
|
24
39
|
}
|
|
25
40
|
|
|
41
|
+
// src/utils/browser_context.ts
|
|
42
|
+
function parseBrowserContext(ua = typeof navigator !== "undefined" ? navigator.userAgent : "", language = typeof navigator !== "undefined" ? navigator.language : "") {
|
|
43
|
+
const userAgent = ua || "";
|
|
44
|
+
const lang = language || "";
|
|
45
|
+
let name = "unknown";
|
|
46
|
+
let version = "";
|
|
47
|
+
const rules = [
|
|
48
|
+
{ name: "Edge", re: /Edg(?:e|A|iOS)?\/([\d.]+)/ },
|
|
49
|
+
{ name: "Opera", re: /OPR\/([\d.]+)/ },
|
|
50
|
+
{ name: "Samsung Internet", re: /SamsungBrowser\/([\d.]+)/ },
|
|
51
|
+
{ name: "Firefox", re: /Firefox\/([\d.]+)/ },
|
|
52
|
+
{ name: "Chrome", re: /(?:Chrome|CriOS)\/([\d.]+)/ },
|
|
53
|
+
{ name: "Safari", re: /Version\/([\d.]+).*Safari/ }
|
|
54
|
+
];
|
|
55
|
+
for (const rule2 of rules) {
|
|
56
|
+
const m = userAgent.match(rule2.re);
|
|
57
|
+
if (m) {
|
|
58
|
+
name = rule2.name;
|
|
59
|
+
version = m[1] ?? "";
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (name === "unknown" && /iPhone|iPad|iPod/.test(userAgent) && /AppleWebKit/.test(userAgent)) {
|
|
64
|
+
name = "Safari";
|
|
65
|
+
const m = userAgent.match(/OS ([\d_]+)/);
|
|
66
|
+
if (m?.[1]) version = m[1].replace(/_/g, ".");
|
|
67
|
+
}
|
|
68
|
+
let os = "unknown";
|
|
69
|
+
let osVersion = "";
|
|
70
|
+
const win = userAgent.match(/Windows NT ([\d.]+)/);
|
|
71
|
+
if (win) {
|
|
72
|
+
os = "Windows";
|
|
73
|
+
osVersion = win[1] ?? "";
|
|
74
|
+
} else {
|
|
75
|
+
const android = userAgent.match(/Android ([\d.]+)/);
|
|
76
|
+
if (android) {
|
|
77
|
+
os = "Android";
|
|
78
|
+
osVersion = android[1] ?? "";
|
|
79
|
+
} else {
|
|
80
|
+
const ios = userAgent.match(/(?:iPhone|iPad|iPod).*OS ([\d_]+)/);
|
|
81
|
+
if (ios) {
|
|
82
|
+
os = "iOS";
|
|
83
|
+
osVersion = (ios[1] ?? "").replace(/_/g, ".");
|
|
84
|
+
} else {
|
|
85
|
+
const mac = userAgent.match(/Mac OS X ([\d_]+)/);
|
|
86
|
+
if (mac) {
|
|
87
|
+
os = "macOS";
|
|
88
|
+
osVersion = (mac[1] ?? "").replace(/_/g, ".");
|
|
89
|
+
} else if (/CrOS/.test(userAgent)) {
|
|
90
|
+
os = "Chrome OS";
|
|
91
|
+
} else if (/Linux/.test(userAgent)) {
|
|
92
|
+
os = "Linux";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
let device = "desktop";
|
|
98
|
+
if (/iPad|Tablet|Android(?!.*Mobile)/i.test(userAgent)) {
|
|
99
|
+
device = "tablet";
|
|
100
|
+
} else if (/Mobi|iPhone|iPod|Android.*Mobile/i.test(userAgent)) {
|
|
101
|
+
device = "mobile";
|
|
102
|
+
} else if (!userAgent) {
|
|
103
|
+
device = "unknown";
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
name,
|
|
107
|
+
version,
|
|
108
|
+
os,
|
|
109
|
+
osVersion,
|
|
110
|
+
device,
|
|
111
|
+
language: lang,
|
|
112
|
+
userAgent
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function browserContextTags(ctx) {
|
|
116
|
+
return {
|
|
117
|
+
"browser.name": ctx.name,
|
|
118
|
+
"browser.version": ctx.version || "unknown",
|
|
119
|
+
"os.name": ctx.os,
|
|
120
|
+
"os.version": ctx.osVersion || "unknown",
|
|
121
|
+
device: ctx.device
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
async function collectBrowserContext() {
|
|
125
|
+
const base = parseBrowserContext();
|
|
126
|
+
const nav = typeof navigator !== "undefined" ? navigator : void 0;
|
|
127
|
+
const uad = nav?.userAgentData;
|
|
128
|
+
if (!uad) return base;
|
|
129
|
+
if (uad.mobile === true) base.device = "mobile";
|
|
130
|
+
if (uad.platform) {
|
|
131
|
+
const p = uad.platform;
|
|
132
|
+
if (/Windows/i.test(p)) base.os = "Windows";
|
|
133
|
+
else if (/macOS|Mac OS/i.test(p)) base.os = "macOS";
|
|
134
|
+
else if (/Android/i.test(p)) base.os = "Android";
|
|
135
|
+
else if (/iOS|iPhone/i.test(p)) base.os = "iOS";
|
|
136
|
+
else if (/Linux/i.test(p)) base.os = "Linux";
|
|
137
|
+
else if (/Chrome OS|Chromium OS/i.test(p)) base.os = "Chrome OS";
|
|
138
|
+
else base.os = p;
|
|
139
|
+
}
|
|
140
|
+
const brands = uad.brands ?? [];
|
|
141
|
+
const interesting = brands.find(
|
|
142
|
+
(b) => !/Not.?A.?Brand/i.test(b.brand) && !/Chromium/i.test(b.brand)
|
|
143
|
+
);
|
|
144
|
+
if (interesting) {
|
|
145
|
+
base.name = interesting.brand.replace(/^Google /, "");
|
|
146
|
+
base.version = interesting.version;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
if (typeof uad.getHighEntropyValues === "function") {
|
|
150
|
+
const hi = await uad.getHighEntropyValues([
|
|
151
|
+
"platformVersion",
|
|
152
|
+
"fullVersionList"
|
|
153
|
+
]);
|
|
154
|
+
if (hi.platformVersion) base.osVersion = hi.platformVersion;
|
|
155
|
+
const full = hi.fullVersionList?.find(
|
|
156
|
+
(b) => !/Not.?A.?Brand/i.test(b.brand) && !/Chromium/i.test(b.brand)
|
|
157
|
+
);
|
|
158
|
+
if (full) {
|
|
159
|
+
base.name = full.brand.replace(/^Google /, "");
|
|
160
|
+
base.version = full.version;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
return base;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/sdk_meta.ts
|
|
169
|
+
var SDK_NAME = "@newtalaria/browser";
|
|
170
|
+
var SDK_VERSION = "0.1.12";
|
|
171
|
+
|
|
26
172
|
// src/transport/serverpod.ts
|
|
27
173
|
var ServerpodTransport = class {
|
|
28
174
|
constructor(options) {
|
|
@@ -204,15 +350,44 @@ var SegmentBuffer = class {
|
|
|
204
350
|
shouldFlushBySize() {
|
|
205
351
|
return this.pendingBytes >= SEGMENT_SIZE_BYTES;
|
|
206
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* How many leading events form the rrweb paint base (Meta+FullSnapshot or
|
|
355
|
+
* FullSnapshot alone). Returns 0 if the buffer does not start with a paint base.
|
|
356
|
+
*/
|
|
357
|
+
paintBaseLength() {
|
|
358
|
+
if (this.events.length === 0) return 0;
|
|
359
|
+
if (this.events[0].type === RRWEB_META && this.events[1]?.type === RRWEB_FULL_SNAPSHOT) {
|
|
360
|
+
return 2;
|
|
361
|
+
}
|
|
362
|
+
if (this.events[0].type === RRWEB_FULL_SNAPSHOT) {
|
|
363
|
+
return 1;
|
|
364
|
+
}
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
207
367
|
/**
|
|
208
368
|
* Take a prefix of events whose estimated JSON size is ~maxBytes.
|
|
209
369
|
* Always returns at least one event when the buffer is non-empty (caller
|
|
210
370
|
* must handle a single oversized event).
|
|
371
|
+
*
|
|
372
|
+
* When `keepPaintBaseTogether` is true (segment 0), Meta immediately followed
|
|
373
|
+
* by FullSnapshot is taken as one unit even if estimated size exceeds
|
|
374
|
+
* maxBytes — gzip fitting / hard caps decide whether it can ship.
|
|
211
375
|
*/
|
|
212
|
-
takeByEstimatedBytes(maxBytes) {
|
|
376
|
+
takeByEstimatedBytes(maxBytes, opts) {
|
|
213
377
|
if (this.events.length === 0) return [];
|
|
214
378
|
const batch = [];
|
|
215
379
|
let batchBytes = 0;
|
|
380
|
+
if (opts?.keepPaintBaseTogether) {
|
|
381
|
+
const paintLen = this.paintBaseLength();
|
|
382
|
+
if (paintLen > 0) {
|
|
383
|
+
for (let i = 0; i < paintLen; i++) {
|
|
384
|
+
const ev = this.events.shift();
|
|
385
|
+
batch.push(ev);
|
|
386
|
+
batchBytes += estimateJsonBytes(ev);
|
|
387
|
+
}
|
|
388
|
+
this.recomputeBytes();
|
|
389
|
+
}
|
|
390
|
+
}
|
|
216
391
|
while (this.events.length > 0) {
|
|
217
392
|
const next = this.events[0];
|
|
218
393
|
const nextBytes = estimateJsonBytes(next);
|
|
@@ -337,8 +512,53 @@ function planOversizedRetry(events) {
|
|
|
337
512
|
right: events.slice(mid)
|
|
338
513
|
};
|
|
339
514
|
}
|
|
340
|
-
|
|
515
|
+
function paintBaseEventCount(events) {
|
|
516
|
+
if (events.length === 0) return 0;
|
|
517
|
+
if (events[0].type === RRWEB_META && events[1]?.type === RRWEB_FULL_SNAPSHOT) {
|
|
518
|
+
return 2;
|
|
519
|
+
}
|
|
520
|
+
if (events[0].type === RRWEB_FULL_SNAPSHOT) {
|
|
521
|
+
return 1;
|
|
522
|
+
}
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
async function fitCompressedPrefix(events, compress, targetBytes = TARGET_COMPRESSED_SEGMENT_BYTES, hardCap = MAX_COMPRESSED_SEGMENT_BYTES, opts) {
|
|
341
526
|
if (events.length === 0) return null;
|
|
527
|
+
const atomicMin = opts?.atomicMinCount ?? 0;
|
|
528
|
+
if (atomicMin > 0) {
|
|
529
|
+
if (events.length < atomicMin) return null;
|
|
530
|
+
const paint = events.slice(0, atomicMin);
|
|
531
|
+
const paintGzip = await compress(paint);
|
|
532
|
+
if (paintGzip.length > hardCap) {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
let lo = atomicMin;
|
|
536
|
+
let hi = events.length;
|
|
537
|
+
let best = {
|
|
538
|
+
events: paint,
|
|
539
|
+
gzip: paintGzip
|
|
540
|
+
};
|
|
541
|
+
while (lo <= hi) {
|
|
542
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
543
|
+
if (mid < atomicMin) {
|
|
544
|
+
lo = atomicMin;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const prefix = events.slice(0, mid);
|
|
548
|
+
const gzip = await compress(prefix);
|
|
549
|
+
if (gzip.length <= targetBytes && gzip.length <= hardCap) {
|
|
550
|
+
best = { events: prefix, gzip };
|
|
551
|
+
lo = mid + 1;
|
|
552
|
+
} else {
|
|
553
|
+
hi = mid - 1;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return {
|
|
557
|
+
events: best.events,
|
|
558
|
+
gzip: best.gzip,
|
|
559
|
+
remainder: events.slice(best.events.length)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
342
562
|
let remaining = events;
|
|
343
563
|
while (remaining.length > 0) {
|
|
344
564
|
let lo = 1;
|
|
@@ -400,7 +620,7 @@ function paintBaseSizeDetails(events, opts) {
|
|
|
400
620
|
const full = events.find((e) => e.type === RRWEB_FULL_SNAPSHOT);
|
|
401
621
|
const details = {
|
|
402
622
|
segmentEventTypes: events.map((e) => e.type),
|
|
403
|
-
|
|
623
|
+
softTakeEstimatedBytes: SEGMENT_SIZE_BYTES,
|
|
404
624
|
targetCompressedSegmentBytes: TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
405
625
|
maxCompressedSegmentBytes: MAX_COMPRESSED_SEGMENT_BYTES
|
|
406
626
|
};
|
|
@@ -13462,6 +13682,8 @@ var TalariaClient = class {
|
|
|
13462
13682
|
this.linkableReplayId = null;
|
|
13463
13683
|
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
13464
13684
|
this.lastReplayCaptureFailure = null;
|
|
13685
|
+
/** Cached once per init for event tags/extra. */
|
|
13686
|
+
this.browserContext = null;
|
|
13465
13687
|
}
|
|
13466
13688
|
init(options) {
|
|
13467
13689
|
if (this.options) {
|
|
@@ -13484,6 +13706,10 @@ var TalariaClient = class {
|
|
|
13484
13706
|
this.errorClipDeadlineMs = null;
|
|
13485
13707
|
this.linkableReplayId = null;
|
|
13486
13708
|
this.lastReplayCaptureFailure = null;
|
|
13709
|
+
this.browserContext = parseBrowserContext();
|
|
13710
|
+
void collectBrowserContext().then((ctx) => {
|
|
13711
|
+
if (this.options) this.browserContext = ctx;
|
|
13712
|
+
});
|
|
13487
13713
|
this.buffer = new SegmentBuffer();
|
|
13488
13714
|
this.uploadChain = Promise.resolve();
|
|
13489
13715
|
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
@@ -13553,6 +13779,14 @@ var TalariaClient = class {
|
|
|
13553
13779
|
}
|
|
13554
13780
|
async captureException(error, context) {
|
|
13555
13781
|
const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
|
|
13782
|
+
const filename = typeof context?.extra?.filename === "string" ? context.extra.filename : void 0;
|
|
13783
|
+
if (isBrowserExtensionNoise({
|
|
13784
|
+
message: err.message,
|
|
13785
|
+
stack: err.stack,
|
|
13786
|
+
filename
|
|
13787
|
+
})) {
|
|
13788
|
+
return;
|
|
13789
|
+
}
|
|
13556
13790
|
await this.capture({
|
|
13557
13791
|
message: err.message || err.name || "Error",
|
|
13558
13792
|
level: "error",
|
|
@@ -13646,6 +13880,7 @@ var TalariaClient = class {
|
|
|
13646
13880
|
this.closed = false;
|
|
13647
13881
|
this.linkableReplayId = null;
|
|
13648
13882
|
this.lastReplayCaptureFailure = null;
|
|
13883
|
+
this.browserContext = null;
|
|
13649
13884
|
}
|
|
13650
13885
|
onRrwebEvent(event) {
|
|
13651
13886
|
if (this.closed || !this.options) return;
|
|
@@ -13729,13 +13964,28 @@ var TalariaClient = class {
|
|
|
13729
13964
|
}
|
|
13730
13965
|
const tags = applyReplayCaptureTags(
|
|
13731
13966
|
{
|
|
13967
|
+
...this.browserContext ? browserContextTags(this.browserContext) : {},
|
|
13732
13968
|
...this.options.tags ?? {},
|
|
13733
13969
|
...args.context?.tags ?? {}
|
|
13734
13970
|
},
|
|
13735
13971
|
errorClipOutcome
|
|
13736
13972
|
);
|
|
13737
13973
|
const extra = mergeReplayCaptureExtra(
|
|
13738
|
-
|
|
13974
|
+
{
|
|
13975
|
+
...this.browserContext ? {
|
|
13976
|
+
browser: {
|
|
13977
|
+
name: this.browserContext.name,
|
|
13978
|
+
version: this.browserContext.version,
|
|
13979
|
+
os: this.browserContext.os,
|
|
13980
|
+
osVersion: this.browserContext.osVersion,
|
|
13981
|
+
device: this.browserContext.device,
|
|
13982
|
+
language: this.browserContext.language,
|
|
13983
|
+
userAgent: this.browserContext.userAgent
|
|
13984
|
+
}
|
|
13985
|
+
} : {},
|
|
13986
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
13987
|
+
...args.context?.extra
|
|
13988
|
+
},
|
|
13739
13989
|
errorClipOutcome
|
|
13740
13990
|
);
|
|
13741
13991
|
try {
|
|
@@ -13959,6 +14209,13 @@ var TalariaClient = class {
|
|
|
13959
14209
|
const onError = (event) => {
|
|
13960
14210
|
if (isOpaqueCrossOriginScriptError(event)) return;
|
|
13961
14211
|
const error = event.error instanceof Error ? event.error : new Error(event.message || "window.onerror");
|
|
14212
|
+
if (isBrowserExtensionNoise({
|
|
14213
|
+
message: event.message || error.message,
|
|
14214
|
+
stack: error.stack,
|
|
14215
|
+
filename: event.filename
|
|
14216
|
+
})) {
|
|
14217
|
+
return;
|
|
14218
|
+
}
|
|
13962
14219
|
void this.captureException(error, {
|
|
13963
14220
|
extra: {
|
|
13964
14221
|
filename: event.filename,
|
|
@@ -14053,20 +14310,9 @@ var TalariaClient = class {
|
|
|
14053
14310
|
}
|
|
14054
14311
|
const { events, gzip } = fitted;
|
|
14055
14312
|
if (this.segmentIndex === 0 && !events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14056
|
-
const leftoverFs = this.buffer.peekFullSnapshot();
|
|
14057
|
-
let compressedBytes;
|
|
14058
|
-
if (leftoverFs) {
|
|
14059
|
-
try {
|
|
14060
|
-
compressedBytes = (await compressReplayEvents([leftoverFs])).length;
|
|
14061
|
-
} catch {
|
|
14062
|
-
}
|
|
14063
|
-
}
|
|
14064
14313
|
await this.abortUnusableClip(
|
|
14065
14314
|
"no_full_snapshot",
|
|
14066
|
-
paintBaseSizeDetails(
|
|
14067
|
-
leftoverFs ? [...events, leftoverFs] : events,
|
|
14068
|
-
{ fullSnapshotCompressedBytes: compressedBytes }
|
|
14069
|
-
)
|
|
14315
|
+
paintBaseSizeDetails(events)
|
|
14070
14316
|
);
|
|
14071
14317
|
return;
|
|
14072
14318
|
}
|
|
@@ -14111,6 +14357,14 @@ var TalariaClient = class {
|
|
|
14111
14357
|
break;
|
|
14112
14358
|
}
|
|
14113
14359
|
if (isOversizedSegmentError(error)) {
|
|
14360
|
+
if (this.segmentIndex === 0 && paintBaseEventCount(events) > 0) {
|
|
14361
|
+
await this.abortUnusableClip("oversized_full_snapshot", {
|
|
14362
|
+
source: "server_reject_paint_base",
|
|
14363
|
+
...paintBaseSizeDetails(events),
|
|
14364
|
+
rejectedCompressedBytes: gzip.length
|
|
14365
|
+
});
|
|
14366
|
+
return;
|
|
14367
|
+
}
|
|
14114
14368
|
const plan = planOversizedRetry(events);
|
|
14115
14369
|
if (plan.action === "drop") {
|
|
14116
14370
|
const droppedType = events[0]?.type;
|
|
@@ -14118,7 +14372,7 @@ var TalariaClient = class {
|
|
|
14118
14372
|
"@newtalaria/browser: dropping rrweb event rejected as oversized",
|
|
14119
14373
|
{ type: droppedType }
|
|
14120
14374
|
);
|
|
14121
|
-
if (
|
|
14375
|
+
if (droppedType === RRWEB_FULL_SNAPSHOT) {
|
|
14122
14376
|
await this.abortUnusableClip(
|
|
14123
14377
|
"oversized_full_snapshot",
|
|
14124
14378
|
{
|
|
@@ -14156,68 +14410,60 @@ var TalariaClient = class {
|
|
|
14156
14410
|
}
|
|
14157
14411
|
/**
|
|
14158
14412
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
14159
|
-
*
|
|
14160
|
-
*
|
|
14413
|
+
*
|
|
14414
|
+
* Segment 0 keeps Meta+FullSnapshot (paint base) atomic through take + fit so
|
|
14415
|
+
* a soft estimated-byte window cannot orphan the FullSnapshot.
|
|
14161
14416
|
*/
|
|
14162
14417
|
async takeFittedSegment() {
|
|
14163
|
-
const
|
|
14418
|
+
const isPaintBaseSegment = this.segmentIndex === 0;
|
|
14419
|
+
const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES, {
|
|
14420
|
+
keepPaintBaseTogether: isPaintBaseSegment
|
|
14421
|
+
});
|
|
14164
14422
|
if (chunk.length === 0) return null;
|
|
14423
|
+
const atomicMin = isPaintBaseSegment ? paintBaseEventCount(chunk) : 0;
|
|
14165
14424
|
const fitted = await fitCompressedPrefix(
|
|
14166
14425
|
chunk,
|
|
14167
14426
|
compressReplayEvents,
|
|
14168
14427
|
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
14169
|
-
MAX_COMPRESSED_SEGMENT_BYTES
|
|
14428
|
+
MAX_COMPRESSED_SEGMENT_BYTES,
|
|
14429
|
+
atomicMin > 0 ? { atomicMinCount: atomicMin } : void 0
|
|
14170
14430
|
);
|
|
14171
14431
|
if (!fitted) {
|
|
14172
|
-
|
|
14173
|
-
|
|
14174
|
-
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
14175
|
-
{ type: dropped?.type }
|
|
14176
|
-
);
|
|
14177
|
-
if (this.segmentIndex === 0 && dropped?.type === RRWEB_FULL_SNAPSHOT) {
|
|
14432
|
+
if (isPaintBaseSegment && atomicMin > 0) {
|
|
14433
|
+
const paint = chunk.slice(0, atomicMin);
|
|
14178
14434
|
let compressedBytes;
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14435
|
+
const full = paint.find((e) => e.type === RRWEB_FULL_SNAPSHOT);
|
|
14436
|
+
if (full) {
|
|
14437
|
+
try {
|
|
14438
|
+
compressedBytes = (await compressReplayEvents([full])).length;
|
|
14439
|
+
} catch {
|
|
14440
|
+
}
|
|
14441
|
+
}
|
|
14442
|
+
if (chunk.length > atomicMin) {
|
|
14443
|
+
this.buffer.prepend(chunk.slice(atomicMin));
|
|
14182
14444
|
}
|
|
14183
14445
|
return {
|
|
14184
14446
|
events: [],
|
|
14185
14447
|
gzip: new Uint8Array(),
|
|
14186
14448
|
abortReason: "oversized_full_snapshot",
|
|
14187
14449
|
abortDetails: {
|
|
14188
|
-
source: "
|
|
14189
|
-
...paintBaseSizeDetails(
|
|
14450
|
+
source: "paint_base_exceeds_hard_cap",
|
|
14451
|
+
...paintBaseSizeDetails(paint, {
|
|
14190
14452
|
fullSnapshotCompressedBytes: compressedBytes
|
|
14191
14453
|
})
|
|
14192
14454
|
}
|
|
14193
14455
|
};
|
|
14194
14456
|
}
|
|
14457
|
+
const dropped = chunk[0];
|
|
14458
|
+
console.warn(
|
|
14459
|
+
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
14460
|
+
{ type: dropped?.type }
|
|
14461
|
+
);
|
|
14195
14462
|
if (chunk.length > 1) {
|
|
14196
14463
|
this.buffer.prepend(chunk.slice(1));
|
|
14197
14464
|
}
|
|
14198
14465
|
return this.takeFittedSegment();
|
|
14199
14466
|
}
|
|
14200
|
-
if (this.segmentIndex === 0 && chunk.some((e) => e.type === RRWEB_FULL_SNAPSHOT) && !fitted.events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14201
|
-
const full = chunk.find((e) => e.type === RRWEB_FULL_SNAPSHOT);
|
|
14202
|
-
let compressedBytes;
|
|
14203
|
-
if (full) {
|
|
14204
|
-
try {
|
|
14205
|
-
compressedBytes = (await compressReplayEvents([full])).length;
|
|
14206
|
-
} catch {
|
|
14207
|
-
}
|
|
14208
|
-
}
|
|
14209
|
-
return {
|
|
14210
|
-
events: [],
|
|
14211
|
-
gzip: new Uint8Array(),
|
|
14212
|
-
abortReason: "oversized_full_snapshot",
|
|
14213
|
-
abortDetails: {
|
|
14214
|
-
source: "fit_dropped_full_snapshot",
|
|
14215
|
-
...paintBaseSizeDetails(chunk, {
|
|
14216
|
-
fullSnapshotCompressedBytes: compressedBytes
|
|
14217
|
-
})
|
|
14218
|
-
}
|
|
14219
|
-
};
|
|
14220
|
-
}
|
|
14221
14467
|
if (fitted.remainder.length > 0) {
|
|
14222
14468
|
this.buffer.prepend(fitted.remainder);
|
|
14223
14469
|
}
|