@newtalaria/browser 0.1.5 → 0.1.7
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 +34 -2
- package/dist/client.d.ts +12 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -22
- package/dist/index.js.map +3 -3
- package/dist/replay/capture_outcome.d.ts +18 -0
- package/dist/replay/capture_outcome.d.ts.map +1 -0
- package/dist/replay/hooks.d.ts +35 -2
- package/dist/replay/hooks.d.ts.map +1 -1
- package/dist/replay/segment_buffer.d.ts +2 -2
- package/dist/replay/segment_buffer.d.ts.map +1 -1
- package/dist/talaria.browser.iife.js +250 -22
- package/dist/talaria.browser.iife.js.map +3 -3
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -96,12 +96,24 @@ You pay for **uploaded + retained** bytes, not for local buffering. Prefer error
|
|
|
96
96
|
| --- | --- |
|
|
97
97
|
| Session sample hit | `replays/start` immediately; segments upload every ~5s or ~100KB until unload or **5 min** max |
|
|
98
98
|
| Session sample miss | Record into a ~60s ring buffer; nothing uploaded until an error sample hits |
|
|
99
|
-
| Error sample + `replaysErrorAfterMs > 0` | Upload buffer (
|
|
99
|
+
| 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
100
|
| Error sample + `replaysErrorAfterMs = 0` | Upload buffer then continue like session mode until **5 min** / unload / size caps |
|
|
101
101
|
| Server limit (replay segments / total size / duration) | Stop uploading that replay; no retries |
|
|
102
|
-
| Oversized
|
|
102
|
+
| Oversized FullSnapshot on segment 0 | **Abort** the clip (no blank orphan upload); error event gets `replay.capture=failed` + reason |
|
|
103
|
+
| Oversized single non-FS rrweb event | Dropped with a console warning (cannot fit under segment cap) |
|
|
103
104
|
| `pagehide` / `close` | Flush pending segments with `fetch` `keepalive`, then `replays/finish`; `close` fully resets so `init()` works again (React Strict Mode) |
|
|
104
105
|
|
|
106
|
+
### Replay capture outcome tags
|
|
107
|
+
|
|
108
|
+
When an **error/fatal** event attempts an on-error clip (`replaysSessionSampleRate` miss), the SDK may attach:
|
|
109
|
+
|
|
110
|
+
| Tag | Values |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| `replay.capture` | `ok` \| `failed` \| `skipped` |
|
|
113
|
+
| `replay.capture_reason` | `oversized_full_snapshot` \| `no_full_snapshot` \| `upload_failed` \| `not_sampled` \| `buffer_empty` |
|
|
114
|
+
|
|
115
|
+
Failed captures do **not** set `replayId` (avoids linking a blank player). Details are also under `extra.replayCapture`.
|
|
116
|
+
|
|
105
117
|
## Serverpod URL pattern
|
|
106
118
|
|
|
107
119
|
Talaria uses **Serverpod RPC**, not REST resource URLs:
|
|
@@ -157,6 +169,26 @@ By default `inlineStylesheet` is `false`: linked stylesheets stay as `href`s and
|
|
|
157
169
|
|
|
158
170
|
Set `inlineStylesheet: true` so same-origin stylesheet rules are embedded while the user is logged in. Cross-origin sheets without CORS still cannot be inlined (browser `cssRules` restriction).
|
|
159
171
|
|
|
172
|
+
### Failed HTTP requests → events
|
|
173
|
+
|
|
174
|
+
By default, completed `fetch` / XHR responses with status **500–599** are also sent as Talaria events (message like `HTTP 500: GET /api/...`). Replay still gets `talaria-network` breadcrumbs for all requests.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
Talaria.init({
|
|
178
|
+
dsn: 'https://api.example.com',
|
|
179
|
+
apiKey: 'tal_live_…',
|
|
180
|
+
environment: 'production',
|
|
181
|
+
// Default true
|
|
182
|
+
captureFailedRequests: true,
|
|
183
|
+
// Default [[500, 599]]. CMS admin often wants 4xx too:
|
|
184
|
+
failedRequestStatusCodes: [[400, 599]],
|
|
185
|
+
// Extra URL substrings to skip (Talaria /events and /replays are always skipped)
|
|
186
|
+
failedRequestIgnoreUrls: ['/health'],
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Transport errors (network down, no status) are not promoted as HTTP events; uncaught exceptions from failed fetches still go through normal `error` / `unhandledrejection` handlers when the app rethrows.
|
|
191
|
+
|
|
160
192
|
## Public API
|
|
161
193
|
|
|
162
194
|
| API | Description |
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CaptureContext, SeverityLevel, TalariaInitOptions } from './types.js';
|
|
2
2
|
export { computeErrorClipDeadlineMs, fitCompressedPrefix, isErrorClipBudgetExhausted, 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
|
+
export { REPLAY_CAPTURE_TAG, REPLAY_CAPTURE_REASON_TAG, applyReplayCaptureTags, mergeReplayCaptureExtra, } from './replay/capture_outcome.js';
|
|
5
|
+
export type { ReplayCaptureOutcome, ReplayCaptureReason, ReplayCaptureStatus, } from './replay/capture_outcome.js';
|
|
4
6
|
export declare class TalariaClient {
|
|
5
7
|
private options;
|
|
6
8
|
private transport;
|
|
@@ -30,6 +32,8 @@ export declare class TalariaClient {
|
|
|
30
32
|
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
31
33
|
*/
|
|
32
34
|
private linkableReplayId;
|
|
35
|
+
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
36
|
+
private lastReplayCaptureFailure;
|
|
33
37
|
init(options: TalariaInitOptions): void;
|
|
34
38
|
getReplayId(): string | null;
|
|
35
39
|
captureException(error: unknown, context?: CaptureContext): Promise<void>;
|
|
@@ -69,8 +73,14 @@ export declare class TalariaClient {
|
|
|
69
73
|
/**
|
|
70
74
|
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
71
75
|
* produce a blank rrweb player.
|
|
76
|
+
* @returns false when no FullSnapshot is available.
|
|
72
77
|
*/
|
|
73
78
|
private prepareBufferForNewReplay;
|
|
79
|
+
/**
|
|
80
|
+
* Stop an error clip that cannot paint (missing/oversized FullSnapshot).
|
|
81
|
+
* Does not set linkableReplayId — the error event should not open a blank player.
|
|
82
|
+
*/
|
|
83
|
+
private abortUnusableClip;
|
|
74
84
|
private stopUploadingAfterLimit;
|
|
75
85
|
private installGlobalHandlers;
|
|
76
86
|
private enqueueUpload;
|
|
@@ -79,7 +89,8 @@ export declare class TalariaClient {
|
|
|
79
89
|
private maxSegmentsAllowed;
|
|
80
90
|
/**
|
|
81
91
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
82
|
-
* Drops a single event that can never fit
|
|
92
|
+
* Drops a single event that can never fit — except FullSnapshot on segment 0
|
|
93
|
+
* (abort the clip instead of uploading a blank orphan stream).
|
|
83
94
|
*/
|
|
84
95
|
private takeFittedSegment;
|
|
85
96
|
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.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export declare const Talaria: {
|
|
|
32
32
|
export { TalariaClient } from './client.js';
|
|
33
33
|
export { computeErrorClipDeadlineMs, fitCompressedPrefix, isErrorClipBudgetExhausted, planOversizedRetry, } from './replay/fit_segment.js';
|
|
34
34
|
export { MAX_SEGMENTS_ERROR_CLIP, MAX_ERROR_CLIP_COMPRESSED_BYTES, TARGET_COMPRESSED_SEGMENT_BYTES, MAX_COMPRESSED_SEGMENT_BYTES, } from './replay/segment_buffer.js';
|
|
35
|
-
export type { CaptureContext, Environment, SeverityLevel, TalariaInitOptions, } from './types.js';
|
|
35
|
+
export type { CaptureContext, Environment, FailedRequestStatusCode, SeverityLevel, TalariaInitOptions, } from './types.js';
|
|
36
|
+
export { REPLAY_CAPTURE_TAG, REPLAY_CAPTURE_REASON_TAG, } from './replay/capture_outcome.js';
|
|
37
|
+
export type { ReplayCaptureOutcome, ReplayCaptureReason, ReplayCaptureStatus, } from './replay/capture_outcome.js';
|
|
36
38
|
export default Talaria;
|
|
37
39
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,OAAO;kBACJ,kBAAkB,GAAG,IAAI;4BAIf,OAAO,YAAY,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;4BAK9D,MAAM,UACP,aAAa,YACX,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC;mBAID,MAAM,GAAG,IAAI;aAInB,OAAO,CAAC,IAAI,CAAC;aAIb,OAAO,CAAC,IAAI,CAAC;CAGvB,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,cAAc,EACd,WAAW,EACX,aAAa,EACb,kBAAkB,GACnB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,OAAO;kBACJ,kBAAkB,GAAG,IAAI;4BAIf,OAAO,YAAY,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;4BAK9D,MAAM,UACP,aAAa,YACX,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC;mBAID,MAAM,GAAG,IAAI;aAInB,OAAO,CAAC,IAAI,CAAC;aAIb,OAAO,CAAC,IAAI,CAAC;CAGvB,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,+BAA+B,EAC/B,+BAA+B,EAC/B,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,cAAc,EACd,WAAW,EACX,uBAAuB,EACvB,aAAa,EACb,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,6BAA6B,CAAC;AAErC,eAAe,OAAO,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -121,9 +121,9 @@ function estimateJsonBytes(value) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// src/replay/segment_buffer.ts
|
|
124
|
-
var SEGMENT_SIZE_BYTES =
|
|
125
|
-
var MAX_COMPRESSED_SEGMENT_BYTES =
|
|
126
|
-
var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES -
|
|
124
|
+
var SEGMENT_SIZE_BYTES = 8e5;
|
|
125
|
+
var MAX_COMPRESSED_SEGMENT_BYTES = 1024 * 1024;
|
|
126
|
+
var TARGET_COMPRESSED_SEGMENT_BYTES = MAX_COMPRESSED_SEGMENT_BYTES - 64 * 1024;
|
|
127
127
|
var MAX_SEGMENTS_PER_REPLAY = 200;
|
|
128
128
|
var MAX_SEGMENTS_ERROR_CLIP = 12;
|
|
129
129
|
var MAX_ERROR_CLIP_COMPRESSED_BYTES = 2 * 1024 * 1024;
|
|
@@ -355,6 +355,33 @@ async function fitCompressedPrefix(events, compress, targetBytes = TARGET_COMPRE
|
|
|
355
355
|
return null;
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
+
// src/replay/capture_outcome.ts
|
|
359
|
+
var REPLAY_CAPTURE_TAG = "replay.capture";
|
|
360
|
+
var REPLAY_CAPTURE_REASON_TAG = "replay.capture_reason";
|
|
361
|
+
function applyReplayCaptureTags(tags, outcome) {
|
|
362
|
+
if (!outcome) return tags;
|
|
363
|
+
const next = { ...tags };
|
|
364
|
+
next[REPLAY_CAPTURE_TAG] = outcome.status;
|
|
365
|
+
if (outcome.reason) {
|
|
366
|
+
next[REPLAY_CAPTURE_REASON_TAG] = outcome.reason;
|
|
367
|
+
}
|
|
368
|
+
return next;
|
|
369
|
+
}
|
|
370
|
+
function mergeReplayCaptureExtra(extra, outcome) {
|
|
371
|
+
if (!outcome || outcome.status === "ok") {
|
|
372
|
+
return extra;
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
...extra ?? {},
|
|
376
|
+
replayCapture: {
|
|
377
|
+
attempted: outcome.status === "failed",
|
|
378
|
+
status: outcome.status,
|
|
379
|
+
reason: outcome.reason,
|
|
380
|
+
...outcome.details ?? {}
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
358
385
|
// ../../node_modules/rrweb/dist/rrweb.js
|
|
359
386
|
var __defProp = Object.defineProperty;
|
|
360
387
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -13214,11 +13241,52 @@ function emitNetwork(meta) {
|
|
|
13214
13241
|
} catch {
|
|
13215
13242
|
}
|
|
13216
13243
|
}
|
|
13217
|
-
function
|
|
13244
|
+
function statusMatches(status, codes) {
|
|
13245
|
+
for (const entry of codes) {
|
|
13246
|
+
if (typeof entry === "number") {
|
|
13247
|
+
if (status === entry) return true;
|
|
13248
|
+
} else if (Array.isArray(entry) && entry.length >= 2) {
|
|
13249
|
+
const [min, max] = entry;
|
|
13250
|
+
if (status >= min && status <= max) return true;
|
|
13251
|
+
}
|
|
13252
|
+
}
|
|
13253
|
+
return false;
|
|
13254
|
+
}
|
|
13255
|
+
function shouldPromoteFailedRequest(meta, opts) {
|
|
13256
|
+
if (!opts.captureFailedRequests) return false;
|
|
13257
|
+
if (typeof meta.status !== "number" || Number.isNaN(meta.status)) return false;
|
|
13258
|
+
if (!statusMatches(meta.status, opts.failedRequestStatusCodes)) return false;
|
|
13259
|
+
const url = meta.url || "";
|
|
13260
|
+
const ignore = [...opts.failedRequestIgnoreUrls];
|
|
13261
|
+
const base = (opts.talariaBaseUrl ?? "").replace(/\/+$/, "");
|
|
13262
|
+
if (base) {
|
|
13263
|
+
ignore.push(`${base}/events/`, `${base}/replays/`);
|
|
13264
|
+
}
|
|
13265
|
+
ignore.push("/events/ingest", "/events/ingestBatch", "/replays/");
|
|
13266
|
+
const lower = url.toLowerCase();
|
|
13267
|
+
for (const part of ignore) {
|
|
13268
|
+
if (part && lower.includes(part.toLowerCase())) return false;
|
|
13269
|
+
}
|
|
13270
|
+
return true;
|
|
13271
|
+
}
|
|
13272
|
+
function installNetworkHook(options = {}) {
|
|
13218
13273
|
const originalFetch = typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
13219
13274
|
const XHR = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest : null;
|
|
13220
13275
|
const originalOpen = XHR?.prototype.open;
|
|
13221
13276
|
const originalSend = XHR?.prototype.send;
|
|
13277
|
+
const matchOpts = {
|
|
13278
|
+
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
13279
|
+
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
13280
|
+
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
|
|
13281
|
+
talariaBaseUrl: options.talariaBaseUrl
|
|
13282
|
+
};
|
|
13283
|
+
const handleMeta = (meta) => {
|
|
13284
|
+
emitNetwork(meta);
|
|
13285
|
+
options.onNetwork?.(meta);
|
|
13286
|
+
if (shouldPromoteFailedRequest(meta, matchOpts)) {
|
|
13287
|
+
options.onFailedRequest?.(meta);
|
|
13288
|
+
}
|
|
13289
|
+
};
|
|
13222
13290
|
if (originalFetch) {
|
|
13223
13291
|
globalThis.fetch = async (input2, init) => {
|
|
13224
13292
|
const started = Date.now();
|
|
@@ -13226,7 +13294,7 @@ function installNetworkHook() {
|
|
|
13226
13294
|
const url = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
|
|
13227
13295
|
try {
|
|
13228
13296
|
const response = await originalFetch(input2, init);
|
|
13229
|
-
|
|
13297
|
+
handleMeta({
|
|
13230
13298
|
method,
|
|
13231
13299
|
url,
|
|
13232
13300
|
status: response.status,
|
|
@@ -13235,7 +13303,7 @@ function installNetworkHook() {
|
|
|
13235
13303
|
});
|
|
13236
13304
|
return response;
|
|
13237
13305
|
} catch (error) {
|
|
13238
|
-
|
|
13306
|
+
handleMeta({
|
|
13239
13307
|
method,
|
|
13240
13308
|
url,
|
|
13241
13309
|
durationMs: Date.now() - started,
|
|
@@ -13254,7 +13322,7 @@ function installNetworkHook() {
|
|
|
13254
13322
|
XHR.prototype.send = function(body) {
|
|
13255
13323
|
this.__talariaStarted = Date.now();
|
|
13256
13324
|
const onDone = () => {
|
|
13257
|
-
|
|
13325
|
+
handleMeta({
|
|
13258
13326
|
method: this.__talariaMethod ?? "GET",
|
|
13259
13327
|
url: this.__talariaUrl ?? "",
|
|
13260
13328
|
status: this.status,
|
|
@@ -13298,7 +13366,10 @@ function resolveOptions(options) {
|
|
|
13298
13366
|
blockSelector: options.blockSelector ?? "",
|
|
13299
13367
|
userId: options.userId,
|
|
13300
13368
|
tags: options.tags,
|
|
13301
|
-
disableDefaultIntegrations: options.disableDefaultIntegrations ?? false
|
|
13369
|
+
disableDefaultIntegrations: options.disableDefaultIntegrations ?? false,
|
|
13370
|
+
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
13371
|
+
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
13372
|
+
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? []
|
|
13302
13373
|
};
|
|
13303
13374
|
}
|
|
13304
13375
|
function clamp01(n2) {
|
|
@@ -13355,6 +13426,8 @@ var TalariaClient = class {
|
|
|
13355
13426
|
* attach it to the error event after budget/timer finish resets buffer mode.
|
|
13356
13427
|
*/
|
|
13357
13428
|
this.linkableReplayId = null;
|
|
13429
|
+
/** Last error-clip paint/upload failure (cleared on successful segment 0). */
|
|
13430
|
+
this.lastReplayCaptureFailure = null;
|
|
13358
13431
|
}
|
|
13359
13432
|
init(options) {
|
|
13360
13433
|
if (this.options) {
|
|
@@ -13376,6 +13449,7 @@ var TalariaClient = class {
|
|
|
13376
13449
|
this.uploadStartedAtMs = null;
|
|
13377
13450
|
this.errorClipDeadlineMs = null;
|
|
13378
13451
|
this.linkableReplayId = null;
|
|
13452
|
+
this.lastReplayCaptureFailure = null;
|
|
13379
13453
|
this.buffer = new SegmentBuffer();
|
|
13380
13454
|
this.uploadChain = Promise.resolve();
|
|
13381
13455
|
this.sessionSampled = Math.random() < this.options.replaysSessionSampleRate;
|
|
@@ -13386,7 +13460,35 @@ var TalariaClient = class {
|
|
|
13386
13460
|
blockSelector: this.options.blockSelector,
|
|
13387
13461
|
onEvent: (event) => this.onRrwebEvent(event)
|
|
13388
13462
|
});
|
|
13389
|
-
this.teardowns.push(
|
|
13463
|
+
this.teardowns.push(
|
|
13464
|
+
installConsoleHook(),
|
|
13465
|
+
installNetworkHook({
|
|
13466
|
+
captureFailedRequests: this.options.captureFailedRequests,
|
|
13467
|
+
failedRequestStatusCodes: this.options.failedRequestStatusCodes,
|
|
13468
|
+
failedRequestIgnoreUrls: this.options.failedRequestIgnoreUrls,
|
|
13469
|
+
talariaBaseUrl: this.options.baseUrl,
|
|
13470
|
+
onFailedRequest: (meta) => {
|
|
13471
|
+
const status = meta.status;
|
|
13472
|
+
const method = meta.method || "GET";
|
|
13473
|
+
const url = meta.url || "(unknown url)";
|
|
13474
|
+
void this.captureMessage(
|
|
13475
|
+
`HTTP ${status}: ${method} ${url}`,
|
|
13476
|
+
status >= 500 ? "error" : "warning",
|
|
13477
|
+
{
|
|
13478
|
+
tags: {
|
|
13479
|
+
"http.status_code": String(status),
|
|
13480
|
+
"http.method": method
|
|
13481
|
+
},
|
|
13482
|
+
extra: {
|
|
13483
|
+
url,
|
|
13484
|
+
durationMs: meta.durationMs,
|
|
13485
|
+
ok: meta.ok
|
|
13486
|
+
}
|
|
13487
|
+
}
|
|
13488
|
+
);
|
|
13489
|
+
}
|
|
13490
|
+
})
|
|
13491
|
+
);
|
|
13390
13492
|
if (!this.options.disableDefaultIntegrations) {
|
|
13391
13493
|
this.installGlobalHandlers();
|
|
13392
13494
|
}
|
|
@@ -13509,6 +13611,7 @@ var TalariaClient = class {
|
|
|
13509
13611
|
this.uploadChain = Promise.resolve();
|
|
13510
13612
|
this.closed = false;
|
|
13511
13613
|
this.linkableReplayId = null;
|
|
13614
|
+
this.lastReplayCaptureFailure = null;
|
|
13512
13615
|
}
|
|
13513
13616
|
onRrwebEvent(event) {
|
|
13514
13617
|
if (this.closed || !this.options) return;
|
|
@@ -13534,15 +13637,22 @@ var TalariaClient = class {
|
|
|
13534
13637
|
throw new Error("@newtalaria/browser: call Talaria.init() first");
|
|
13535
13638
|
}
|
|
13536
13639
|
const isErrorLike = args.level === "error" || args.level === "fatal";
|
|
13640
|
+
let errorClipOutcome = null;
|
|
13641
|
+
let attemptedErrorClip = false;
|
|
13537
13642
|
if (isErrorLike && !this.sessionSampled) {
|
|
13538
13643
|
if (!this.uploadEnabled) {
|
|
13539
13644
|
if (Math.random() < this.options.replaysOnErrorSampleRate) {
|
|
13645
|
+
attemptedErrorClip = true;
|
|
13646
|
+
this.lastReplayCaptureFailure = null;
|
|
13540
13647
|
this.uploadEnabled = true;
|
|
13541
13648
|
this.checkoutFullSnapshot();
|
|
13542
13649
|
this.markUploadStarted();
|
|
13543
13650
|
this.scheduleErrorClipEnd();
|
|
13651
|
+
} else {
|
|
13652
|
+
errorClipOutcome = { status: "skipped", reason: "not_sampled" };
|
|
13544
13653
|
}
|
|
13545
13654
|
} else {
|
|
13655
|
+
attemptedErrorClip = true;
|
|
13546
13656
|
this.scheduleErrorClipEnd();
|
|
13547
13657
|
}
|
|
13548
13658
|
}
|
|
@@ -13551,15 +13661,49 @@ var TalariaClient = class {
|
|
|
13551
13661
|
await this.flush({ reason: "capture", keepalive: args.keepalive });
|
|
13552
13662
|
} catch (error) {
|
|
13553
13663
|
console.warn("@newtalaria/browser: replay flush failed", error);
|
|
13554
|
-
|
|
13664
|
+
this.lastReplayCaptureFailure = {
|
|
13665
|
+
reason: "upload_failed",
|
|
13666
|
+
details: {
|
|
13667
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13668
|
+
}
|
|
13669
|
+
};
|
|
13670
|
+
if (!this.sessionSampled && this.segmentIndex === 0) {
|
|
13671
|
+
await this.abortUnusableClip("upload_failed", {
|
|
13672
|
+
message: error instanceof Error ? error.message : String(error)
|
|
13673
|
+
});
|
|
13674
|
+
}
|
|
13555
13675
|
}
|
|
13556
13676
|
}
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
}
|
|
13561
|
-
|
|
13562
|
-
|
|
13677
|
+
let replayId = this.uploadEnabled && this.segmentIndex > 0 ? this.replayId : this.linkableReplayId;
|
|
13678
|
+
if (this.lastReplayCaptureFailure && this.segmentIndex === 0) {
|
|
13679
|
+
replayId = null;
|
|
13680
|
+
}
|
|
13681
|
+
if (isErrorLike && !this.sessionSampled) {
|
|
13682
|
+
if (replayId) {
|
|
13683
|
+
errorClipOutcome = { status: "ok" };
|
|
13684
|
+
this.lastReplayCaptureFailure = null;
|
|
13685
|
+
} else if (errorClipOutcome?.status === "skipped") {
|
|
13686
|
+
} else if (this.lastReplayCaptureFailure) {
|
|
13687
|
+
errorClipOutcome = {
|
|
13688
|
+
status: "failed",
|
|
13689
|
+
reason: this.lastReplayCaptureFailure.reason,
|
|
13690
|
+
details: this.lastReplayCaptureFailure.details
|
|
13691
|
+
};
|
|
13692
|
+
} else if (attemptedErrorClip) {
|
|
13693
|
+
errorClipOutcome = { status: "failed", reason: "buffer_empty" };
|
|
13694
|
+
}
|
|
13695
|
+
}
|
|
13696
|
+
const tags = applyReplayCaptureTags(
|
|
13697
|
+
{
|
|
13698
|
+
...this.options.tags ?? {},
|
|
13699
|
+
...args.context?.tags ?? {}
|
|
13700
|
+
},
|
|
13701
|
+
errorClipOutcome
|
|
13702
|
+
);
|
|
13703
|
+
const extra = mergeReplayCaptureExtra(
|
|
13704
|
+
args.context?.extra,
|
|
13705
|
+
errorClipOutcome
|
|
13706
|
+
);
|
|
13563
13707
|
try {
|
|
13564
13708
|
await ingestEvent(this.transport, {
|
|
13565
13709
|
message: args.message,
|
|
@@ -13571,7 +13715,7 @@ var TalariaClient = class {
|
|
|
13571
13715
|
release: this.options.release,
|
|
13572
13716
|
userId: args.context?.userId ?? this.options.userId,
|
|
13573
13717
|
sessionId: this.sessionId ?? void 0,
|
|
13574
|
-
replayId,
|
|
13718
|
+
replayId: replayId ?? void 0,
|
|
13575
13719
|
url: typeof location !== "undefined" ? location.href : void 0,
|
|
13576
13720
|
tags: Object.keys(tags).length ? tags : void 0,
|
|
13577
13721
|
extraJson: extra ? JSON.stringify(extra) : void 0,
|
|
@@ -13716,9 +13860,10 @@ var TalariaClient = class {
|
|
|
13716
13860
|
/**
|
|
13717
13861
|
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
13718
13862
|
* produce a blank rrweb player.
|
|
13863
|
+
* @returns false when no FullSnapshot is available.
|
|
13719
13864
|
*/
|
|
13720
13865
|
prepareBufferForNewReplay() {
|
|
13721
|
-
if (this.segmentIndex !== 0) return;
|
|
13866
|
+
if (this.segmentIndex !== 0) return true;
|
|
13722
13867
|
if (!this.buffer.hasFullSnapshot()) {
|
|
13723
13868
|
this.checkoutFullSnapshot();
|
|
13724
13869
|
}
|
|
@@ -13726,6 +13871,40 @@ var TalariaClient = class {
|
|
|
13726
13871
|
console.warn(
|
|
13727
13872
|
"@newtalaria/browser: replay has no FullSnapshot; player may show a blank screen"
|
|
13728
13873
|
);
|
|
13874
|
+
return false;
|
|
13875
|
+
}
|
|
13876
|
+
return true;
|
|
13877
|
+
}
|
|
13878
|
+
/**
|
|
13879
|
+
* Stop an error clip that cannot paint (missing/oversized FullSnapshot).
|
|
13880
|
+
* Does not set linkableReplayId — the error event should not open a blank player.
|
|
13881
|
+
*/
|
|
13882
|
+
async abortUnusableClip(reason, details) {
|
|
13883
|
+
this.lastReplayCaptureFailure = { reason, details };
|
|
13884
|
+
console.warn("@newtalaria/browser: aborting unusable replay clip", {
|
|
13885
|
+
reason,
|
|
13886
|
+
...details
|
|
13887
|
+
});
|
|
13888
|
+
this.buffer.clear();
|
|
13889
|
+
this.linkableReplayId = null;
|
|
13890
|
+
if (this.startedOnServer && !this.finishedOnServer && this.transport && this.replayId) {
|
|
13891
|
+
try {
|
|
13892
|
+
await finishReplay(this.transport, {
|
|
13893
|
+
replayId: this.replayId,
|
|
13894
|
+
reason: `capture_failed:${reason}`,
|
|
13895
|
+
keepalive: false
|
|
13896
|
+
});
|
|
13897
|
+
this.finishedOnServer = true;
|
|
13898
|
+
} catch (error) {
|
|
13899
|
+
console.warn("@newtalaria/browser: replays/finish failed after abort", error);
|
|
13900
|
+
}
|
|
13901
|
+
}
|
|
13902
|
+
if (!this.sessionSampled) {
|
|
13903
|
+
this.resetToBufferMode();
|
|
13904
|
+
} else {
|
|
13905
|
+
this.uploadEnabled = false;
|
|
13906
|
+
this.clearErrorClipTimer();
|
|
13907
|
+
this.clearMaxDurationTimer();
|
|
13729
13908
|
}
|
|
13730
13909
|
}
|
|
13731
13910
|
stopUploadingAfterLimit() {
|
|
@@ -13786,7 +13965,10 @@ var TalariaClient = class {
|
|
|
13786
13965
|
async uploadPendingSegments(opts) {
|
|
13787
13966
|
if (!this.options || !this.transport || !this.replayId) return;
|
|
13788
13967
|
if (!this.startedOnServer || this.finishedOnServer) return;
|
|
13789
|
-
this.prepareBufferForNewReplay()
|
|
13968
|
+
if (!this.prepareBufferForNewReplay()) {
|
|
13969
|
+
await this.abortUnusableClip("no_full_snapshot");
|
|
13970
|
+
return;
|
|
13971
|
+
}
|
|
13790
13972
|
while (this.buffer.length > 0) {
|
|
13791
13973
|
if (this.isPastMaxDuration()) {
|
|
13792
13974
|
break;
|
|
@@ -13826,7 +14008,17 @@ var TalariaClient = class {
|
|
|
13826
14008
|
}
|
|
13827
14009
|
const fitted = await this.takeFittedSegment();
|
|
13828
14010
|
if (!fitted) break;
|
|
14011
|
+
if (fitted.abortReason) {
|
|
14012
|
+
await this.abortUnusableClip(fitted.abortReason, fitted.abortDetails);
|
|
14013
|
+
return;
|
|
14014
|
+
}
|
|
13829
14015
|
const { events, gzip } = fitted;
|
|
14016
|
+
if (this.segmentIndex === 0 && !events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14017
|
+
await this.abortUnusableClip("no_full_snapshot", {
|
|
14018
|
+
segmentEventTypes: events.map((e) => e.type)
|
|
14019
|
+
});
|
|
14020
|
+
return;
|
|
14021
|
+
}
|
|
13830
14022
|
if (this.isErrorClipMode() && this.uploadedCompressedBytes + gzip.length > MAX_ERROR_CLIP_COMPRESSED_BYTES && this.segmentIndex > 0) {
|
|
13831
14023
|
this.buffer.clear();
|
|
13832
14024
|
await this.finishOnServer({
|
|
@@ -13851,6 +14043,9 @@ var TalariaClient = class {
|
|
|
13851
14043
|
});
|
|
13852
14044
|
this.segmentIndex = index2 + 1;
|
|
13853
14045
|
this.uploadedCompressedBytes += gzip.length;
|
|
14046
|
+
if (index2 === 0) {
|
|
14047
|
+
this.lastReplayCaptureFailure = null;
|
|
14048
|
+
}
|
|
13854
14049
|
} catch (error) {
|
|
13855
14050
|
if (isTerminalReplayLimitError(error)) {
|
|
13856
14051
|
console.warn(
|
|
@@ -13867,10 +14062,17 @@ var TalariaClient = class {
|
|
|
13867
14062
|
if (isOversizedSegmentError(error)) {
|
|
13868
14063
|
const plan = planOversizedRetry(events);
|
|
13869
14064
|
if (plan.action === "drop") {
|
|
14065
|
+
const droppedType = events[0]?.type;
|
|
13870
14066
|
console.warn(
|
|
13871
14067
|
"@newtalaria/browser: dropping rrweb event rejected as oversized",
|
|
13872
|
-
{ type:
|
|
14068
|
+
{ type: droppedType }
|
|
13873
14069
|
);
|
|
14070
|
+
if (this.segmentIndex === 0 && droppedType === RRWEB_FULL_SNAPSHOT) {
|
|
14071
|
+
await this.abortUnusableClip("oversized_full_snapshot", {
|
|
14072
|
+
source: "server_reject"
|
|
14073
|
+
});
|
|
14074
|
+
return;
|
|
14075
|
+
}
|
|
13874
14076
|
continue;
|
|
13875
14077
|
}
|
|
13876
14078
|
this.buffer.prepend(plan.right);
|
|
@@ -13879,6 +14081,11 @@ var TalariaClient = class {
|
|
|
13879
14081
|
}
|
|
13880
14082
|
this.buffer.prepend(events);
|
|
13881
14083
|
console.warn("@newtalaria/browser: replays/ingestSegment failed", error);
|
|
14084
|
+
if (this.segmentIndex === 0) {
|
|
14085
|
+
await this.abortUnusableClip("upload_failed", {
|
|
14086
|
+
message: error instanceof Error ? error.message : String(error)
|
|
14087
|
+
});
|
|
14088
|
+
}
|
|
13882
14089
|
break;
|
|
13883
14090
|
}
|
|
13884
14091
|
if (opts.keepalive) break;
|
|
@@ -13893,7 +14100,8 @@ var TalariaClient = class {
|
|
|
13893
14100
|
}
|
|
13894
14101
|
/**
|
|
13895
14102
|
* Pull the largest event prefix that gzips under the target compressed size.
|
|
13896
|
-
* Drops a single event that can never fit
|
|
14103
|
+
* Drops a single event that can never fit — except FullSnapshot on segment 0
|
|
14104
|
+
* (abort the clip instead of uploading a blank orphan stream).
|
|
13897
14105
|
*/
|
|
13898
14106
|
async takeFittedSegment() {
|
|
13899
14107
|
const chunk = this.buffer.takeByEstimatedBytes(SEGMENT_SIZE_BYTES);
|
|
@@ -13905,12 +14113,32 @@ var TalariaClient = class {
|
|
|
13905
14113
|
MAX_COMPRESSED_SEGMENT_BYTES
|
|
13906
14114
|
);
|
|
13907
14115
|
if (!fitted) {
|
|
14116
|
+
const dropped = chunk[0];
|
|
13908
14117
|
console.warn(
|
|
13909
14118
|
"@newtalaria/browser: dropping rrweb event that exceeds max segment size",
|
|
13910
|
-
{ type:
|
|
14119
|
+
{ type: dropped?.type }
|
|
13911
14120
|
);
|
|
14121
|
+
if (this.segmentIndex === 0 && dropped?.type === RRWEB_FULL_SNAPSHOT) {
|
|
14122
|
+
return {
|
|
14123
|
+
events: [],
|
|
14124
|
+
gzip: new Uint8Array(),
|
|
14125
|
+
abortReason: "oversized_full_snapshot",
|
|
14126
|
+
abortDetails: { source: "fit_compressed_prefix" }
|
|
14127
|
+
};
|
|
14128
|
+
}
|
|
14129
|
+
if (chunk.length > 1) {
|
|
14130
|
+
this.buffer.prepend(chunk.slice(1));
|
|
14131
|
+
}
|
|
13912
14132
|
return this.takeFittedSegment();
|
|
13913
14133
|
}
|
|
14134
|
+
if (this.segmentIndex === 0 && chunk.some((e) => e.type === RRWEB_FULL_SNAPSHOT) && !fitted.events.some((e) => e.type === RRWEB_FULL_SNAPSHOT)) {
|
|
14135
|
+
return {
|
|
14136
|
+
events: [],
|
|
14137
|
+
gzip: new Uint8Array(),
|
|
14138
|
+
abortReason: "oversized_full_snapshot",
|
|
14139
|
+
abortDetails: { source: "fit_dropped_full_snapshot" }
|
|
14140
|
+
};
|
|
14141
|
+
}
|
|
13914
14142
|
if (fitted.remainder.length > 0) {
|
|
13915
14143
|
this.buffer.prepend(fitted.remainder);
|
|
13916
14144
|
}
|
|
@@ -13962,6 +14190,8 @@ export {
|
|
|
13962
14190
|
MAX_COMPRESSED_SEGMENT_BYTES,
|
|
13963
14191
|
MAX_ERROR_CLIP_COMPRESSED_BYTES,
|
|
13964
14192
|
MAX_SEGMENTS_ERROR_CLIP,
|
|
14193
|
+
REPLAY_CAPTURE_REASON_TAG,
|
|
14194
|
+
REPLAY_CAPTURE_TAG,
|
|
13965
14195
|
TARGET_COMPRESSED_SEGMENT_BYTES,
|
|
13966
14196
|
Talaria,
|
|
13967
14197
|
TalariaClient,
|