@newtalaria/browser 0.1.12 → 0.1.15
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 +17 -6
- package/dist/client.d.ts +27 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.js +442 -41
- package/dist/index.js.map +3 -3
- package/dist/replay/hooks.d.ts +34 -2
- package/dist/replay/hooks.d.ts.map +1 -1
- package/dist/replay/recorder.d.ts +5 -0
- package/dist/replay/recorder.d.ts.map +1 -1
- package/dist/replay/segment_buffer.d.ts +5 -0
- package/dist/replay/segment_buffer.d.ts.map +1 -1
- package/dist/sdk_meta.d.ts +1 -1
- package/dist/talaria.browser.iife.js +442 -41
- package/dist/talaria.browser.iife.js.map +3 -3
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/environment.d.ts +7 -0
- package/dist/utils/environment.d.ts.map +1 -0
- package/dist/utils/network_error.d.ts +13 -0
- package/dist/utils/network_error.d.ts.map +1 -0
- package/dist/utils/sdk_internal_noise.d.ts +6 -0
- package/dist/utils/sdk_internal_noise.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15,6 +15,43 @@ 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/environment.ts
|
|
19
|
+
var WIRE = /* @__PURE__ */ new Set([
|
|
20
|
+
"production",
|
|
21
|
+
"staging",
|
|
22
|
+
"development"
|
|
23
|
+
]);
|
|
24
|
+
function normalizeEnvironment(raw) {
|
|
25
|
+
const normalized = raw.trim().toLowerCase();
|
|
26
|
+
if (!normalized) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
"@newtalaria/browser: init requires `environment` (production | staging | development)"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
switch (normalized) {
|
|
32
|
+
case "prod":
|
|
33
|
+
case "production":
|
|
34
|
+
case "live":
|
|
35
|
+
return "production";
|
|
36
|
+
case "stage":
|
|
37
|
+
case "staging":
|
|
38
|
+
case "uat":
|
|
39
|
+
case "test":
|
|
40
|
+
return "staging";
|
|
41
|
+
case "dev":
|
|
42
|
+
case "development":
|
|
43
|
+
case "local":
|
|
44
|
+
return "development";
|
|
45
|
+
default:
|
|
46
|
+
if (WIRE.has(normalized)) {
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
`@newtalaria/browser: invalid environment '${raw}'. Expected production, staging, or development.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
18
55
|
// src/utils/browser_extension_noise.ts
|
|
19
56
|
var EXTENSION_URL = /(?:chrome|moz|safari|safari-web|ms-browser)-extension:\/\//i;
|
|
20
57
|
var WEBKIT_MASKED = /webkit-masked-url:\/\/hidden\//i;
|
|
@@ -38,6 +75,28 @@ function isOpaqueCrossOriginScriptError(event) {
|
|
|
38
75
|
return true;
|
|
39
76
|
}
|
|
40
77
|
|
|
78
|
+
// src/utils/sdk_internal_noise.ts
|
|
79
|
+
var SDK_STACK_FRAME = /@newtalaria\/browser|\/npm\/@newtalaria\/browser(?:@[\w.-]+)?\//i;
|
|
80
|
+
function isSdkInternalNoise(opts) {
|
|
81
|
+
const stack = opts.stack ?? "";
|
|
82
|
+
if (!stack) return false;
|
|
83
|
+
const frameLines = stack.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at "));
|
|
84
|
+
if (frameLines.length === 0) return false;
|
|
85
|
+
let sdkFrames = 0;
|
|
86
|
+
let otherUrlFrames = 0;
|
|
87
|
+
for (const line of frameLines) {
|
|
88
|
+
const urlMatch = line.match(/\((https?:\/\/[^)]+)\)|(https?:\/\/\S+)/i);
|
|
89
|
+
const url = urlMatch?.[1] ?? urlMatch?.[2];
|
|
90
|
+
if (!url) continue;
|
|
91
|
+
if (SDK_STACK_FRAME.test(url)) {
|
|
92
|
+
sdkFrames += 1;
|
|
93
|
+
} else {
|
|
94
|
+
otherUrlFrames += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return sdkFrames > 0 && otherUrlFrames === 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
41
100
|
// src/utils/browser_context.ts
|
|
42
101
|
function parseBrowserContext(ua = typeof navigator !== "undefined" ? navigator.userAgent : "", language = typeof navigator !== "undefined" ? navigator.language : "") {
|
|
43
102
|
const userAgent = ua || "";
|
|
@@ -167,7 +226,7 @@ async function collectBrowserContext() {
|
|
|
167
226
|
|
|
168
227
|
// src/sdk_meta.ts
|
|
169
228
|
var SDK_NAME = "@newtalaria/browser";
|
|
170
|
-
var SDK_VERSION = "0.1.
|
|
229
|
+
var SDK_VERSION = "0.1.15";
|
|
171
230
|
|
|
172
231
|
// src/transport/serverpod.ts
|
|
173
232
|
var ServerpodTransport = class {
|
|
@@ -284,6 +343,7 @@ var MAX_ERROR_CLIP_COMPRESSED_BYTES = 2 * 1024 * 1024;
|
|
|
284
343
|
var ERROR_CLIP_FLUSH_SLACK_MS = 5e3;
|
|
285
344
|
var SEGMENT_FLUSH_MS = 5e3;
|
|
286
345
|
var RING_BUFFER_MS = 6e4;
|
|
346
|
+
var RING_BUFFER_CHECKOUT_MS = RING_BUFFER_MS;
|
|
287
347
|
var RING_BUFFER_MAX_BYTES = 15e5;
|
|
288
348
|
var ERROR_REPLAY_AFTER_MS = 15e3;
|
|
289
349
|
var MAX_REPLAY_DURATION_MS = 5 * 60 * 1e3;
|
|
@@ -13426,6 +13486,7 @@ function startRecorder(options) {
|
|
|
13426
13486
|
inlineStylesheet: options.inlineStylesheet,
|
|
13427
13487
|
// Shrink DOM snapshots on heavy sites (docs / marketing).
|
|
13428
13488
|
slimDOMOptions: "all",
|
|
13489
|
+
...options.checkoutEveryNms != null && options.checkoutEveryNms > 0 ? { checkoutEveryNms: options.checkoutEveryNms } : {},
|
|
13429
13490
|
sampling: {
|
|
13430
13491
|
mousemove: 150,
|
|
13431
13492
|
scroll: 200,
|
|
@@ -13445,21 +13506,93 @@ function startRecorder(options) {
|
|
|
13445
13506
|
};
|
|
13446
13507
|
}
|
|
13447
13508
|
|
|
13509
|
+
// src/utils/network_error.ts
|
|
13510
|
+
function isAbortError(error) {
|
|
13511
|
+
return error instanceof Error && error.name === "AbortError";
|
|
13512
|
+
}
|
|
13513
|
+
function isLikelyNetworkFetchError(error) {
|
|
13514
|
+
if (!(error instanceof Error)) return false;
|
|
13515
|
+
if (error.name === "AbortError") return false;
|
|
13516
|
+
if (error.name === "NetworkError") return true;
|
|
13517
|
+
const msg = error.message.toLowerCase().trim();
|
|
13518
|
+
return msg === "failed to fetch" || msg === "load failed" || msg === "networkerror when attempting to fetch resource." || msg.startsWith("networkerror") || msg.includes("failed to fetch");
|
|
13519
|
+
}
|
|
13520
|
+
function describeUnknownError(error) {
|
|
13521
|
+
if (error instanceof Error) {
|
|
13522
|
+
return {
|
|
13523
|
+
errorName: error.name || "Error",
|
|
13524
|
+
errorMessage: (error.message || "").slice(0, 500),
|
|
13525
|
+
aborted: error.name === "AbortError"
|
|
13526
|
+
};
|
|
13527
|
+
}
|
|
13528
|
+
return {
|
|
13529
|
+
errorName: "Error",
|
|
13530
|
+
errorMessage: String(error).slice(0, 500),
|
|
13531
|
+
aborted: false
|
|
13532
|
+
};
|
|
13533
|
+
}
|
|
13534
|
+
|
|
13448
13535
|
// src/replay/hooks.ts
|
|
13536
|
+
function installVisibilityResumeHook(onResume) {
|
|
13537
|
+
if (typeof document === "undefined") {
|
|
13538
|
+
return () => {
|
|
13539
|
+
};
|
|
13540
|
+
}
|
|
13541
|
+
const onVisibility = () => {
|
|
13542
|
+
if (document.visibilityState === "visible") {
|
|
13543
|
+
onResume();
|
|
13544
|
+
}
|
|
13545
|
+
};
|
|
13546
|
+
const onPageShow = (event) => {
|
|
13547
|
+
if (event.persisted) {
|
|
13548
|
+
onResume();
|
|
13549
|
+
}
|
|
13550
|
+
};
|
|
13551
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
13552
|
+
if (typeof window !== "undefined") {
|
|
13553
|
+
window.addEventListener("pageshow", onPageShow);
|
|
13554
|
+
}
|
|
13555
|
+
return () => {
|
|
13556
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
13557
|
+
if (typeof window !== "undefined") {
|
|
13558
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
13559
|
+
}
|
|
13560
|
+
};
|
|
13561
|
+
}
|
|
13562
|
+
function serializeConsoleArg(arg) {
|
|
13563
|
+
if (arg instanceof Error) {
|
|
13564
|
+
return { name: arg.name, message: arg.message, stack: arg.stack };
|
|
13565
|
+
}
|
|
13566
|
+
return arg;
|
|
13567
|
+
}
|
|
13568
|
+
function formatConsoleArg(arg) {
|
|
13569
|
+
if (arg == null) return String(arg);
|
|
13570
|
+
if (typeof arg === "string") return arg;
|
|
13571
|
+
if (typeof arg === "number" || typeof arg === "boolean") return String(arg);
|
|
13572
|
+
if (arg instanceof Error) {
|
|
13573
|
+
return arg.stack || `${arg.name}: ${arg.message}`;
|
|
13574
|
+
}
|
|
13575
|
+
if (typeof arg === "object" && arg !== null && "name" in arg && "message" in arg) {
|
|
13576
|
+
const err = arg;
|
|
13577
|
+
if (typeof err.stack === "string" && err.stack) return err.stack;
|
|
13578
|
+
return `${String(err.name)}: ${String(err.message)}`;
|
|
13579
|
+
}
|
|
13580
|
+
try {
|
|
13581
|
+
return JSON.stringify(arg);
|
|
13582
|
+
} catch {
|
|
13583
|
+
return String(arg);
|
|
13584
|
+
}
|
|
13585
|
+
}
|
|
13449
13586
|
function safeSerialize(args) {
|
|
13450
13587
|
try {
|
|
13451
|
-
return JSON.stringify(
|
|
13452
|
-
args.map((a) => {
|
|
13453
|
-
if (a instanceof Error) {
|
|
13454
|
-
return { name: a.name, message: a.message, stack: a.stack };
|
|
13455
|
-
}
|
|
13456
|
-
return a;
|
|
13457
|
-
})
|
|
13458
|
-
);
|
|
13588
|
+
return JSON.stringify(args.map(serializeConsoleArg));
|
|
13459
13589
|
} catch {
|
|
13460
13590
|
return "[unserializable]";
|
|
13461
13591
|
}
|
|
13462
13592
|
}
|
|
13593
|
+
function consoleMessage(args) {
|
|
13594
|
+
return args.map(formatConsoleArg).join(" ").slice(0, 4e3);
|
|
13595
|
+
}
|
|
13463
13596
|
function installConsoleHook() {
|
|
13464
13597
|
const levels = ["log", "info", "warn", "error", "debug"];
|
|
13465
13598
|
const originals = {};
|
|
@@ -13468,9 +13601,12 @@ function installConsoleHook() {
|
|
|
13468
13601
|
originals[level] = original;
|
|
13469
13602
|
console[level] = (...args) => {
|
|
13470
13603
|
try {
|
|
13604
|
+
const serializedArgs = safeSerialize(args).slice(0, 4e3);
|
|
13471
13605
|
record.addCustomEvent("talaria-console", {
|
|
13472
13606
|
level,
|
|
13473
|
-
args
|
|
13607
|
+
// `message` is what the replay sidebar displays; `args` kept for tooling.
|
|
13608
|
+
message: consoleMessage(args),
|
|
13609
|
+
args: serializedArgs,
|
|
13474
13610
|
timestamp: Date.now()
|
|
13475
13611
|
});
|
|
13476
13612
|
} catch {
|
|
@@ -13506,23 +13642,61 @@ function statusMatches(status, codes) {
|
|
|
13506
13642
|
}
|
|
13507
13643
|
return false;
|
|
13508
13644
|
}
|
|
13509
|
-
function
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
if (!statusMatches(meta.status, opts.failedRequestStatusCodes)) return false;
|
|
13513
|
-
const url = meta.url || "";
|
|
13514
|
-
const ignore = [...opts.failedRequestIgnoreUrls];
|
|
13515
|
-
const base = (opts.talariaBaseUrl ?? "").replace(/\/+$/, "");
|
|
13645
|
+
function buildFailedRequestIgnoreUrls(failedRequestIgnoreUrls, talariaBaseUrl) {
|
|
13646
|
+
const ignore = [...failedRequestIgnoreUrls];
|
|
13647
|
+
const base = (talariaBaseUrl ?? "").replace(/\/+$/, "");
|
|
13516
13648
|
if (base) {
|
|
13517
13649
|
ignore.push(`${base}/events/`, `${base}/replays/`);
|
|
13518
13650
|
}
|
|
13519
13651
|
ignore.push("/events/ingest", "/events/ingestBatch", "/replays/");
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13652
|
+
return ignore;
|
|
13653
|
+
}
|
|
13654
|
+
function urlMatchesIgnoreList(url, ignoreUrls) {
|
|
13655
|
+
const lower = (url || "").toLowerCase();
|
|
13656
|
+
for (const part of ignoreUrls) {
|
|
13657
|
+
if (part && lower.includes(part.toLowerCase())) return true;
|
|
13523
13658
|
}
|
|
13659
|
+
return false;
|
|
13660
|
+
}
|
|
13661
|
+
function shouldPromoteFailedRequest(meta, opts) {
|
|
13662
|
+
if (!opts.captureFailedRequests) return false;
|
|
13663
|
+
if (typeof meta.status !== "number" || Number.isNaN(meta.status)) return false;
|
|
13664
|
+
if (meta.status === 0) return false;
|
|
13665
|
+
if (!statusMatches(meta.status, opts.failedRequestStatusCodes)) return false;
|
|
13666
|
+
const ignore = buildFailedRequestIgnoreUrls(
|
|
13667
|
+
opts.failedRequestIgnoreUrls,
|
|
13668
|
+
opts.talariaBaseUrl
|
|
13669
|
+
);
|
|
13670
|
+
if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
|
|
13671
|
+
return true;
|
|
13672
|
+
}
|
|
13673
|
+
function shouldPromoteNetworkError(meta, opts) {
|
|
13674
|
+
if (!opts.captureNetworkErrors) return false;
|
|
13675
|
+
if (meta.aborted) return false;
|
|
13676
|
+
if (meta.failureKind !== "network") return false;
|
|
13677
|
+
const ignore = buildFailedRequestIgnoreUrls(
|
|
13678
|
+
opts.failedRequestIgnoreUrls,
|
|
13679
|
+
opts.talariaBaseUrl
|
|
13680
|
+
);
|
|
13681
|
+
if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
|
|
13524
13682
|
return true;
|
|
13525
13683
|
}
|
|
13684
|
+
function enrichNetworkMeta(meta) {
|
|
13685
|
+
const hasHttpStatus = typeof meta.status === "number" && !Number.isNaN(meta.status) && meta.status > 0;
|
|
13686
|
+
if (hasHttpStatus) {
|
|
13687
|
+
return {
|
|
13688
|
+
...meta,
|
|
13689
|
+
failureKind: meta.ok === false ? "http" : meta.failureKind
|
|
13690
|
+
};
|
|
13691
|
+
}
|
|
13692
|
+
if (meta.ok === false || meta.errorName || meta.errorMessage) {
|
|
13693
|
+
return {
|
|
13694
|
+
...meta,
|
|
13695
|
+
failureKind: "network"
|
|
13696
|
+
};
|
|
13697
|
+
}
|
|
13698
|
+
return meta;
|
|
13699
|
+
}
|
|
13526
13700
|
function installNetworkHook(options = {}) {
|
|
13527
13701
|
const originalFetch = typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
13528
13702
|
const XHR = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest : null;
|
|
@@ -13530,15 +13704,22 @@ function installNetworkHook(options = {}) {
|
|
|
13530
13704
|
const originalSend = XHR?.prototype.send;
|
|
13531
13705
|
const matchOpts = {
|
|
13532
13706
|
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
13707
|
+
captureNetworkErrors: options.captureNetworkErrors ?? true,
|
|
13533
13708
|
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
13534
13709
|
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
|
|
13535
13710
|
talariaBaseUrl: options.talariaBaseUrl
|
|
13536
13711
|
};
|
|
13537
13712
|
const handleMeta = (meta) => {
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
options.
|
|
13713
|
+
const enriched = enrichNetworkMeta(meta);
|
|
13714
|
+
try {
|
|
13715
|
+
emitNetwork(enriched);
|
|
13716
|
+
options.onNetwork?.(enriched);
|
|
13717
|
+
if (shouldPromoteFailedRequest(enriched, matchOpts)) {
|
|
13718
|
+
options.onFailedRequest?.(enriched);
|
|
13719
|
+
} else if (shouldPromoteNetworkError(enriched, matchOpts)) {
|
|
13720
|
+
options.onNetworkError?.(enriched);
|
|
13721
|
+
}
|
|
13722
|
+
} catch {
|
|
13542
13723
|
}
|
|
13543
13724
|
};
|
|
13544
13725
|
if (originalFetch) {
|
|
@@ -13557,11 +13738,16 @@ function installNetworkHook(options = {}) {
|
|
|
13557
13738
|
});
|
|
13558
13739
|
return response;
|
|
13559
13740
|
} catch (error) {
|
|
13741
|
+
const described = describeUnknownError(error);
|
|
13560
13742
|
handleMeta({
|
|
13561
13743
|
method,
|
|
13562
13744
|
url,
|
|
13563
13745
|
durationMs: Date.now() - started,
|
|
13564
|
-
ok: false
|
|
13746
|
+
ok: false,
|
|
13747
|
+
errorName: described.errorName,
|
|
13748
|
+
errorMessage: described.errorMessage,
|
|
13749
|
+
aborted: described.aborted,
|
|
13750
|
+
failureKind: "network"
|
|
13565
13751
|
});
|
|
13566
13752
|
throw error;
|
|
13567
13753
|
}
|
|
@@ -13576,12 +13762,19 @@ function installNetworkHook(options = {}) {
|
|
|
13576
13762
|
XHR.prototype.send = function(body) {
|
|
13577
13763
|
this.__talariaStarted = Date.now();
|
|
13578
13764
|
const onDone = () => {
|
|
13765
|
+
const status = this.status;
|
|
13766
|
+
const networkFail = status === 0;
|
|
13579
13767
|
handleMeta({
|
|
13580
13768
|
method: this.__talariaMethod ?? "GET",
|
|
13581
13769
|
url: this.__talariaUrl ?? "",
|
|
13582
|
-
status
|
|
13770
|
+
status,
|
|
13583
13771
|
durationMs: Date.now() - (this.__talariaStarted ?? Date.now()),
|
|
13584
|
-
ok:
|
|
13772
|
+
ok: status >= 200 && status < 400,
|
|
13773
|
+
...networkFail ? {
|
|
13774
|
+
failureKind: "network",
|
|
13775
|
+
errorName: "NetworkError",
|
|
13776
|
+
errorMessage: "XMLHttpRequest failed (status 0)"
|
|
13777
|
+
} : {}
|
|
13585
13778
|
});
|
|
13586
13779
|
};
|
|
13587
13780
|
this.addEventListener("loadend", onDone);
|
|
@@ -13596,6 +13789,31 @@ function installNetworkHook(options = {}) {
|
|
|
13596
13789
|
}
|
|
13597
13790
|
|
|
13598
13791
|
// src/client.ts
|
|
13792
|
+
function mergeNetworkFailureContext(context, failure) {
|
|
13793
|
+
const url = redactUrl(failure.url || "(unknown url)");
|
|
13794
|
+
return {
|
|
13795
|
+
...context,
|
|
13796
|
+
tags: {
|
|
13797
|
+
...context?.tags ?? {},
|
|
13798
|
+
"http.method": failure.method || "GET",
|
|
13799
|
+
"network.failure_kind": "network",
|
|
13800
|
+
...failure.errorName ? { "network.error_name": failure.errorName } : {}
|
|
13801
|
+
},
|
|
13802
|
+
extra: {
|
|
13803
|
+
...context?.extra ?? {},
|
|
13804
|
+
network: {
|
|
13805
|
+
method: failure.method,
|
|
13806
|
+
url,
|
|
13807
|
+
durationMs: failure.durationMs,
|
|
13808
|
+
ok: false,
|
|
13809
|
+
failureKind: "network",
|
|
13810
|
+
errorName: failure.errorName,
|
|
13811
|
+
errorMessage: failure.errorMessage,
|
|
13812
|
+
aborted: failure.aborted ?? false
|
|
13813
|
+
}
|
|
13814
|
+
}
|
|
13815
|
+
};
|
|
13816
|
+
}
|
|
13599
13817
|
function resolveOptions(options) {
|
|
13600
13818
|
const baseUrl = (options.baseUrl ?? options.dsn ?? "").trim();
|
|
13601
13819
|
if (!baseUrl) {
|
|
@@ -13610,7 +13828,7 @@ function resolveOptions(options) {
|
|
|
13610
13828
|
return {
|
|
13611
13829
|
baseUrl,
|
|
13612
13830
|
apiKey: options.apiKey.trim(),
|
|
13613
|
-
environment: String(options.environment),
|
|
13831
|
+
environment: normalizeEnvironment(String(options.environment)),
|
|
13614
13832
|
release: options.release,
|
|
13615
13833
|
replaysSessionSampleRate: clamp01(options.replaysSessionSampleRate ?? 0),
|
|
13616
13834
|
replaysOnErrorSampleRate: clamp01(options.replaysOnErrorSampleRate ?? 1),
|
|
@@ -13622,10 +13840,12 @@ function resolveOptions(options) {
|
|
|
13622
13840
|
tags: options.tags,
|
|
13623
13841
|
disableDefaultIntegrations: options.disableDefaultIntegrations ?? false,
|
|
13624
13842
|
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
13843
|
+
captureNetworkErrors: options.captureNetworkErrors ?? true,
|
|
13625
13844
|
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
13626
13845
|
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? []
|
|
13627
13846
|
};
|
|
13628
13847
|
}
|
|
13848
|
+
var RECENT_NETWORK_FAILURE_MS = 5e3;
|
|
13629
13849
|
function clamp01(n2) {
|
|
13630
13850
|
if (Number.isNaN(n2)) return 0;
|
|
13631
13851
|
return Math.min(1, Math.max(0, n2));
|
|
@@ -13650,6 +13870,16 @@ function isOversizedSegmentError(error) {
|
|
|
13650
13870
|
const msg = error instanceof Error ? error.message : String(error);
|
|
13651
13871
|
return msg.includes("segment exceeds max compressed size") || msg.includes("segment exceeds max uncompressed size") || msg.includes("HTTP 400") && msg.includes("ApiValidationException");
|
|
13652
13872
|
}
|
|
13873
|
+
function isPermanentIngestError(error) {
|
|
13874
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
13875
|
+
const match = /Talaria events\/ingest(?:Batch)? failed: HTTP (\d{3})/.exec(
|
|
13876
|
+
msg
|
|
13877
|
+
);
|
|
13878
|
+
if (!match) return false;
|
|
13879
|
+
const status = Number(match[1]);
|
|
13880
|
+
if (status < 400 || status >= 500) return false;
|
|
13881
|
+
return status !== 408 && status !== 429;
|
|
13882
|
+
}
|
|
13653
13883
|
var TalariaClient = class {
|
|
13654
13884
|
constructor() {
|
|
13655
13885
|
this.options = null;
|
|
@@ -13684,6 +13914,15 @@ var TalariaClient = class {
|
|
|
13684
13914
|
this.lastReplayCaptureFailure = null;
|
|
13685
13915
|
/** Cached once per init for event tags/extra. */
|
|
13686
13916
|
this.browserContext = null;
|
|
13917
|
+
/** Prevent ingest/replay failures from being re-captured via unhandledrejection. */
|
|
13918
|
+
this.capturing = false;
|
|
13919
|
+
/**
|
|
13920
|
+
* Set after a permanent events/ingest 4xx (bad env/auth/wire). Further
|
|
13921
|
+
* captures no-op so we don't spin on retries or burn error-clip replay quota.
|
|
13922
|
+
*/
|
|
13923
|
+
this.ingestDisabled = false;
|
|
13924
|
+
/** Recent fetch/XHR transport failures for correlation + dedupe. */
|
|
13925
|
+
this.recentNetworkFailures = [];
|
|
13687
13926
|
}
|
|
13688
13927
|
init(options) {
|
|
13689
13928
|
if (this.options) {
|
|
@@ -13698,6 +13937,7 @@ var TalariaClient = class {
|
|
|
13698
13937
|
this.sessionId = createId();
|
|
13699
13938
|
this.replayId = createId();
|
|
13700
13939
|
this.closed = false;
|
|
13940
|
+
this.ingestDisabled = false;
|
|
13701
13941
|
this.finishedOnServer = false;
|
|
13702
13942
|
this.startedOnServer = false;
|
|
13703
13943
|
this.segmentIndex = 0;
|
|
@@ -13706,6 +13946,7 @@ var TalariaClient = class {
|
|
|
13706
13946
|
this.errorClipDeadlineMs = null;
|
|
13707
13947
|
this.linkableReplayId = null;
|
|
13708
13948
|
this.lastReplayCaptureFailure = null;
|
|
13949
|
+
this.recentNetworkFailures = [];
|
|
13709
13950
|
this.browserContext = parseBrowserContext();
|
|
13710
13951
|
void collectBrowserContext().then((ctx) => {
|
|
13711
13952
|
if (this.options) this.browserContext = ctx;
|
|
@@ -13718,36 +13959,74 @@ var TalariaClient = class {
|
|
|
13718
13959
|
maskAllInputs: this.options.maskAllInputs,
|
|
13719
13960
|
inlineStylesheet: this.options.inlineStylesheet,
|
|
13720
13961
|
blockSelector: this.options.blockSelector,
|
|
13962
|
+
// Buffer/error path: keep a FullSnapshot inside the ~60s ring. Session
|
|
13963
|
+
// sample uploads continuously so periodic checkouts are skipped (size).
|
|
13964
|
+
checkoutEveryNms: this.sessionSampled ? void 0 : RING_BUFFER_CHECKOUT_MS,
|
|
13721
13965
|
onEvent: (event) => this.onRrwebEvent(event)
|
|
13722
13966
|
});
|
|
13723
13967
|
this.teardowns.push(
|
|
13724
13968
|
installConsoleHook(),
|
|
13725
13969
|
installNetworkHook({
|
|
13726
13970
|
captureFailedRequests: this.options.captureFailedRequests,
|
|
13971
|
+
captureNetworkErrors: this.options.captureNetworkErrors,
|
|
13727
13972
|
failedRequestStatusCodes: this.options.failedRequestStatusCodes,
|
|
13728
13973
|
failedRequestIgnoreUrls: this.options.failedRequestIgnoreUrls,
|
|
13729
13974
|
talariaBaseUrl: this.options.baseUrl,
|
|
13975
|
+
onNetwork: (meta) => {
|
|
13976
|
+
if (meta.failureKind === "network" && !meta.aborted) {
|
|
13977
|
+
this.rememberNetworkFailure(meta, { promoted: false });
|
|
13978
|
+
}
|
|
13979
|
+
},
|
|
13730
13980
|
onFailedRequest: (meta) => {
|
|
13731
13981
|
const status = meta.status;
|
|
13732
13982
|
const method = meta.method || "GET";
|
|
13733
|
-
const url = meta.url || "(unknown url)";
|
|
13983
|
+
const url = redactUrl(meta.url || "(unknown url)");
|
|
13734
13984
|
void this.captureMessage(
|
|
13735
13985
|
`HTTP ${status}: ${method} ${url}`,
|
|
13736
13986
|
status >= 500 ? "error" : "warning",
|
|
13737
13987
|
{
|
|
13738
13988
|
tags: {
|
|
13739
13989
|
"http.status_code": String(status),
|
|
13740
|
-
"http.method": method
|
|
13990
|
+
"http.method": method,
|
|
13991
|
+
"network.failure_kind": "http"
|
|
13741
13992
|
},
|
|
13742
13993
|
extra: {
|
|
13743
13994
|
url,
|
|
13744
13995
|
durationMs: meta.durationMs,
|
|
13745
|
-
ok: meta.ok
|
|
13996
|
+
ok: meta.ok,
|
|
13997
|
+
failureKind: "http"
|
|
13998
|
+
}
|
|
13999
|
+
}
|
|
14000
|
+
);
|
|
14001
|
+
},
|
|
14002
|
+
onNetworkError: (meta) => {
|
|
14003
|
+
this.rememberNetworkFailure(meta, { promoted: true });
|
|
14004
|
+
const method = meta.method || "GET";
|
|
14005
|
+
const url = redactUrl(meta.url || "(unknown url)");
|
|
14006
|
+
const errLabel = meta.errorName && meta.errorMessage ? `${meta.errorName}: ${meta.errorMessage}` : meta.errorMessage || meta.errorName || "Failed to fetch";
|
|
14007
|
+
void this.captureMessage(
|
|
14008
|
+
`Network error: ${method} ${url} \u2014 ${errLabel}`,
|
|
14009
|
+
"error",
|
|
14010
|
+
{
|
|
14011
|
+
tags: {
|
|
14012
|
+
"http.method": method,
|
|
14013
|
+
"network.failure_kind": "network",
|
|
14014
|
+
...meta.errorName ? { "network.error_name": meta.errorName } : {}
|
|
14015
|
+
},
|
|
14016
|
+
extra: {
|
|
14017
|
+
url,
|
|
14018
|
+
durationMs: meta.durationMs,
|
|
14019
|
+
ok: false,
|
|
14020
|
+
failureKind: "network",
|
|
14021
|
+
errorName: meta.errorName,
|
|
14022
|
+
errorMessage: meta.errorMessage,
|
|
14023
|
+
aborted: false
|
|
13746
14024
|
}
|
|
13747
14025
|
}
|
|
13748
14026
|
);
|
|
13749
14027
|
}
|
|
13750
|
-
})
|
|
14028
|
+
}),
|
|
14029
|
+
installVisibilityResumeHook(() => this.onForegroundResume())
|
|
13751
14030
|
);
|
|
13752
14031
|
if (!this.options.disableDefaultIntegrations) {
|
|
13753
14032
|
this.installGlobalHandlers();
|
|
@@ -13778,24 +14057,38 @@ var TalariaClient = class {
|
|
|
13778
14057
|
return this.linkableReplayId;
|
|
13779
14058
|
}
|
|
13780
14059
|
async captureException(error, context) {
|
|
14060
|
+
if (this.capturing || this.ingestDisabled) return;
|
|
13781
14061
|
const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
|
|
14062
|
+
if (isAbortError(err)) return;
|
|
13782
14063
|
const filename = typeof context?.extra?.filename === "string" ? context.extra.filename : void 0;
|
|
13783
14064
|
if (isBrowserExtensionNoise({
|
|
13784
14065
|
message: err.message,
|
|
13785
14066
|
stack: err.stack,
|
|
13786
14067
|
filename
|
|
14068
|
+
}) || isSdkInternalNoise({
|
|
14069
|
+
message: err.message,
|
|
14070
|
+
stack: err.stack
|
|
13787
14071
|
})) {
|
|
13788
14072
|
return;
|
|
13789
14073
|
}
|
|
13790
|
-
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
13795
|
-
|
|
13796
|
-
|
|
14074
|
+
const correlated = this.consumeCorrelatedNetworkFailure(err);
|
|
14075
|
+
if (correlated?.promoted) return;
|
|
14076
|
+
const mergedContext = correlated ? mergeNetworkFailureContext(context, correlated) : context;
|
|
14077
|
+
this.capturing = true;
|
|
14078
|
+
try {
|
|
14079
|
+
await this.capture({
|
|
14080
|
+
message: err.message || err.name || "Error",
|
|
14081
|
+
level: "error",
|
|
14082
|
+
title: err.name || "Error",
|
|
14083
|
+
stackTrace: err.stack,
|
|
14084
|
+
context: mergedContext
|
|
14085
|
+
});
|
|
14086
|
+
} finally {
|
|
14087
|
+
this.capturing = false;
|
|
14088
|
+
}
|
|
13797
14089
|
}
|
|
13798
14090
|
async captureMessage(message, level = "info", context) {
|
|
14091
|
+
if (this.ingestDisabled) return;
|
|
13799
14092
|
await this.capture({
|
|
13800
14093
|
message,
|
|
13801
14094
|
level,
|
|
@@ -13881,6 +14174,7 @@ var TalariaClient = class {
|
|
|
13881
14174
|
this.linkableReplayId = null;
|
|
13882
14175
|
this.lastReplayCaptureFailure = null;
|
|
13883
14176
|
this.browserContext = null;
|
|
14177
|
+
this.capturing = false;
|
|
13884
14178
|
}
|
|
13885
14179
|
onRrwebEvent(event) {
|
|
13886
14180
|
if (this.closed || !this.options) return;
|
|
@@ -13905,6 +14199,7 @@ var TalariaClient = class {
|
|
|
13905
14199
|
if (!this.options || !this.transport) {
|
|
13906
14200
|
throw new Error("@newtalaria/browser: call Talaria.init() first");
|
|
13907
14201
|
}
|
|
14202
|
+
if (this.ingestDisabled) return;
|
|
13908
14203
|
const isErrorLike = args.level === "error" || args.level === "fatal";
|
|
13909
14204
|
let errorClipOutcome = null;
|
|
13910
14205
|
let attemptedErrorClip = false;
|
|
@@ -14011,7 +14306,36 @@ var TalariaClient = class {
|
|
|
14011
14306
|
}
|
|
14012
14307
|
} catch (error) {
|
|
14013
14308
|
console.warn("@newtalaria/browser: event ingest failed", error);
|
|
14014
|
-
|
|
14309
|
+
if (isPermanentIngestError(error)) {
|
|
14310
|
+
this.disableIngestAfterPermanentError(error);
|
|
14311
|
+
}
|
|
14312
|
+
}
|
|
14313
|
+
}
|
|
14314
|
+
/**
|
|
14315
|
+
* Stop further event capture (and error-clip uploads) after a permanent
|
|
14316
|
+
* events/ingest 4xx so misconfig cannot spin forever.
|
|
14317
|
+
*/
|
|
14318
|
+
disableIngestAfterPermanentError(error) {
|
|
14319
|
+
if (this.ingestDisabled) return;
|
|
14320
|
+
this.ingestDisabled = true;
|
|
14321
|
+
console.warn(
|
|
14322
|
+
"@newtalaria/browser: event ingest disabled after permanent client error",
|
|
14323
|
+
error
|
|
14324
|
+
);
|
|
14325
|
+
if (!this.sessionSampled && this.uploadEnabled) {
|
|
14326
|
+
this.clearErrorClipTimer();
|
|
14327
|
+
void this.enqueueUpload(async () => {
|
|
14328
|
+
if (this.startedOnServer && !this.finishedOnServer) {
|
|
14329
|
+
await this.finishOnServer({
|
|
14330
|
+
keepalive: false,
|
|
14331
|
+
reason: "ingest_disabled"
|
|
14332
|
+
});
|
|
14333
|
+
}
|
|
14334
|
+
this.resetToBufferMode();
|
|
14335
|
+
});
|
|
14336
|
+
} else if (!this.sessionSampled) {
|
|
14337
|
+
this.clearErrorClipTimer();
|
|
14338
|
+
this.uploadEnabled = false;
|
|
14015
14339
|
}
|
|
14016
14340
|
}
|
|
14017
14341
|
markUploadStarted() {
|
|
@@ -14141,6 +14465,18 @@ var TalariaClient = class {
|
|
|
14141
14465
|
checkoutFullSnapshot() {
|
|
14142
14466
|
this.recorder?.takeFullSnapshot();
|
|
14143
14467
|
}
|
|
14468
|
+
/**
|
|
14469
|
+
* Tab focus / bfcache restore: background timers are throttled, so the
|
|
14470
|
+
* buffer-mode checkout interval may not have fired. Refresh the paint base
|
|
14471
|
+
* so an error clip still has ~60s of usable lead-up after the user returns.
|
|
14472
|
+
*/
|
|
14473
|
+
onForegroundResume() {
|
|
14474
|
+
if (this.closed || !this.options || !this.recorder) return;
|
|
14475
|
+
if (!this.uploadEnabled) {
|
|
14476
|
+
this.buffer.trimRing();
|
|
14477
|
+
}
|
|
14478
|
+
this.checkoutFullSnapshot();
|
|
14479
|
+
}
|
|
14144
14480
|
/**
|
|
14145
14481
|
* Ensure segment 0 begins at Meta+FullSnapshot. Orphan increments alone
|
|
14146
14482
|
* produce a blank rrweb player.
|
|
@@ -14204,6 +14540,57 @@ var TalariaClient = class {
|
|
|
14204
14540
|
this.resetToBufferMode();
|
|
14205
14541
|
}
|
|
14206
14542
|
}
|
|
14543
|
+
pruneRecentNetworkFailures(now = Date.now()) {
|
|
14544
|
+
this.recentNetworkFailures = this.recentNetworkFailures.filter(
|
|
14545
|
+
(f) => now - f.at <= RECENT_NETWORK_FAILURE_MS
|
|
14546
|
+
);
|
|
14547
|
+
}
|
|
14548
|
+
rememberNetworkFailure(meta, opts) {
|
|
14549
|
+
const now = Date.now();
|
|
14550
|
+
this.pruneRecentNetworkFailures(now);
|
|
14551
|
+
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14552
|
+
const existing = this.recentNetworkFailures[i];
|
|
14553
|
+
if (existing.method === meta.method && existing.url === meta.url && existing.errorMessage === meta.errorMessage && now - existing.at < 1e3) {
|
|
14554
|
+
existing.promoted = existing.promoted || opts.promoted;
|
|
14555
|
+
existing.durationMs = meta.durationMs ?? existing.durationMs;
|
|
14556
|
+
existing.errorName = meta.errorName ?? existing.errorName;
|
|
14557
|
+
existing.aborted = meta.aborted ?? existing.aborted;
|
|
14558
|
+
existing.failureKind = meta.failureKind ?? existing.failureKind;
|
|
14559
|
+
return;
|
|
14560
|
+
}
|
|
14561
|
+
}
|
|
14562
|
+
this.recentNetworkFailures.push({
|
|
14563
|
+
...meta,
|
|
14564
|
+
at: now,
|
|
14565
|
+
promoted: opts.promoted
|
|
14566
|
+
});
|
|
14567
|
+
}
|
|
14568
|
+
/**
|
|
14569
|
+
* Find a recent transport failure for a bare fetch TypeError.
|
|
14570
|
+
* Consumes the entry so a later duplicate path cannot reuse it.
|
|
14571
|
+
*/
|
|
14572
|
+
consumeCorrelatedNetworkFailure(error) {
|
|
14573
|
+
if (!isLikelyNetworkFetchError(error)) return null;
|
|
14574
|
+
const now = Date.now();
|
|
14575
|
+
this.pruneRecentNetworkFailures(now);
|
|
14576
|
+
const takeAt = (index2) => {
|
|
14577
|
+
const [failure] = this.recentNetworkFailures.splice(index2, 1);
|
|
14578
|
+
return failure;
|
|
14579
|
+
};
|
|
14580
|
+
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14581
|
+
const failure = this.recentNetworkFailures[i];
|
|
14582
|
+
if (failure.aborted || failure.failureKind !== "network") continue;
|
|
14583
|
+
if (failure.errorMessage && failure.errorMessage === error.message) {
|
|
14584
|
+
return takeAt(i);
|
|
14585
|
+
}
|
|
14586
|
+
}
|
|
14587
|
+
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14588
|
+
const failure = this.recentNetworkFailures[i];
|
|
14589
|
+
if (failure.aborted || failure.failureKind !== "network") continue;
|
|
14590
|
+
return takeAt(i);
|
|
14591
|
+
}
|
|
14592
|
+
return null;
|
|
14593
|
+
}
|
|
14207
14594
|
installGlobalHandlers() {
|
|
14208
14595
|
if (typeof window === "undefined") return;
|
|
14209
14596
|
const onError = (event) => {
|
|
@@ -14225,7 +14612,21 @@ var TalariaClient = class {
|
|
|
14225
14612
|
});
|
|
14226
14613
|
};
|
|
14227
14614
|
const onRejection = (event) => {
|
|
14228
|
-
|
|
14615
|
+
const reason = event.reason;
|
|
14616
|
+
if (isAbortError(reason)) return;
|
|
14617
|
+
const err = reason instanceof Error ? reason : new Error(
|
|
14618
|
+
typeof reason === "string" ? reason : "unhandledrejection"
|
|
14619
|
+
);
|
|
14620
|
+
if (isBrowserExtensionNoise({
|
|
14621
|
+
message: err.message,
|
|
14622
|
+
stack: err.stack
|
|
14623
|
+
}) || isSdkInternalNoise({
|
|
14624
|
+
message: err.message,
|
|
14625
|
+
stack: err.stack
|
|
14626
|
+
})) {
|
|
14627
|
+
return;
|
|
14628
|
+
}
|
|
14629
|
+
void this.captureException(reason);
|
|
14229
14630
|
};
|
|
14230
14631
|
window.addEventListener("error", onError);
|
|
14231
14632
|
window.addEventListener("unhandledrejection", onRejection);
|