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