@newtalaria/browser 0.1.13 → 0.1.16
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 +29 -6
- package/dist/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.js +468 -48
- package/dist/index.js.map +3 -3
- package/dist/replay/hooks.d.ts +40 -2
- package/dist/replay/hooks.d.ts.map +1 -1
- package/dist/replay/privacy.d.ts +18 -1
- package/dist/replay/privacy.d.ts.map +1 -1
- package/dist/sdk_meta.d.ts +1 -1
- package/dist/talaria.browser.iife.js +468 -48
- package/dist/talaria.browser.iife.js.map +3 -3
- package/dist/types.d.ts +14 -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.16";
|
|
171
230
|
|
|
172
231
|
// src/transport/serverpod.ts
|
|
173
232
|
var ServerpodTransport = class {
|
|
@@ -13391,6 +13450,40 @@ function redactUrl(raw) {
|
|
|
13391
13450
|
return raw.replace(SENSITIVE_QUERY, (_m, name) => `${name}=[Filtered]`);
|
|
13392
13451
|
}
|
|
13393
13452
|
}
|
|
13453
|
+
function sanitizeNetworkUrl(raw, opts) {
|
|
13454
|
+
const baseHref = opts?.baseHref ?? (typeof location !== "undefined" ? location.href : void 0);
|
|
13455
|
+
try {
|
|
13456
|
+
const parsed = new URL(raw, baseHref);
|
|
13457
|
+
const hostname = parsed.hostname;
|
|
13458
|
+
const pathname = parsed.pathname || "/";
|
|
13459
|
+
const originPath = `${parsed.origin}${pathname}`;
|
|
13460
|
+
if (!opts?.includeQuery) {
|
|
13461
|
+
return { url: originPath, hostname, pathname };
|
|
13462
|
+
}
|
|
13463
|
+
const redacted = new URL(redactUrl(parsed.toString()));
|
|
13464
|
+
const search = redacted.search || void 0;
|
|
13465
|
+
return {
|
|
13466
|
+
url: search ? `${originPath}${search}` : originPath,
|
|
13467
|
+
hostname,
|
|
13468
|
+
pathname,
|
|
13469
|
+
...search ? { search } : {}
|
|
13470
|
+
};
|
|
13471
|
+
} catch {
|
|
13472
|
+
const stripped = (raw.split(/[?#]/)[0] ?? raw).trim() || raw;
|
|
13473
|
+
if (!opts?.includeQuery) {
|
|
13474
|
+
return { url: stripped, hostname: "", pathname: stripped };
|
|
13475
|
+
}
|
|
13476
|
+
const redacted = redactUrl(raw);
|
|
13477
|
+
const qIndex = redacted.indexOf("?");
|
|
13478
|
+
const search = qIndex >= 0 ? redacted.slice(qIndex) : void 0;
|
|
13479
|
+
return {
|
|
13480
|
+
url: redacted.split("#")[0] || redacted,
|
|
13481
|
+
hostname: "",
|
|
13482
|
+
pathname: stripped,
|
|
13483
|
+
...search ? { search } : {}
|
|
13484
|
+
};
|
|
13485
|
+
}
|
|
13486
|
+
}
|
|
13394
13487
|
function defaultBlockSelector(extra) {
|
|
13395
13488
|
const parts = [
|
|
13396
13489
|
"[data-talaria-mask]",
|
|
@@ -13447,6 +13540,32 @@ function startRecorder(options) {
|
|
|
13447
13540
|
};
|
|
13448
13541
|
}
|
|
13449
13542
|
|
|
13543
|
+
// src/utils/network_error.ts
|
|
13544
|
+
function isAbortError(error) {
|
|
13545
|
+
return error instanceof Error && error.name === "AbortError";
|
|
13546
|
+
}
|
|
13547
|
+
function isLikelyNetworkFetchError(error) {
|
|
13548
|
+
if (!(error instanceof Error)) return false;
|
|
13549
|
+
if (error.name === "AbortError") return false;
|
|
13550
|
+
if (error.name === "NetworkError") return true;
|
|
13551
|
+
const msg = error.message.toLowerCase().trim();
|
|
13552
|
+
return msg === "failed to fetch" || msg === "load failed" || msg === "networkerror when attempting to fetch resource." || msg.startsWith("networkerror") || msg.includes("failed to fetch");
|
|
13553
|
+
}
|
|
13554
|
+
function describeUnknownError(error) {
|
|
13555
|
+
if (error instanceof Error) {
|
|
13556
|
+
return {
|
|
13557
|
+
errorName: error.name || "Error",
|
|
13558
|
+
errorMessage: (error.message || "").slice(0, 500),
|
|
13559
|
+
aborted: error.name === "AbortError"
|
|
13560
|
+
};
|
|
13561
|
+
}
|
|
13562
|
+
return {
|
|
13563
|
+
errorName: "Error",
|
|
13564
|
+
errorMessage: String(error).slice(0, 500),
|
|
13565
|
+
aborted: false
|
|
13566
|
+
};
|
|
13567
|
+
}
|
|
13568
|
+
|
|
13450
13569
|
// src/replay/hooks.ts
|
|
13451
13570
|
function installVisibilityResumeHook(onResume) {
|
|
13452
13571
|
if (typeof document === "undefined") {
|
|
@@ -13474,20 +13593,40 @@ function installVisibilityResumeHook(onResume) {
|
|
|
13474
13593
|
}
|
|
13475
13594
|
};
|
|
13476
13595
|
}
|
|
13596
|
+
function serializeConsoleArg(arg) {
|
|
13597
|
+
if (arg instanceof Error) {
|
|
13598
|
+
return { name: arg.name, message: arg.message, stack: arg.stack };
|
|
13599
|
+
}
|
|
13600
|
+
return arg;
|
|
13601
|
+
}
|
|
13602
|
+
function formatConsoleArg(arg) {
|
|
13603
|
+
if (arg == null) return String(arg);
|
|
13604
|
+
if (typeof arg === "string") return arg;
|
|
13605
|
+
if (typeof arg === "number" || typeof arg === "boolean") return String(arg);
|
|
13606
|
+
if (arg instanceof Error) {
|
|
13607
|
+
return arg.stack || `${arg.name}: ${arg.message}`;
|
|
13608
|
+
}
|
|
13609
|
+
if (typeof arg === "object" && arg !== null && "name" in arg && "message" in arg) {
|
|
13610
|
+
const err = arg;
|
|
13611
|
+
if (typeof err.stack === "string" && err.stack) return err.stack;
|
|
13612
|
+
return `${String(err.name)}: ${String(err.message)}`;
|
|
13613
|
+
}
|
|
13614
|
+
try {
|
|
13615
|
+
return JSON.stringify(arg);
|
|
13616
|
+
} catch {
|
|
13617
|
+
return String(arg);
|
|
13618
|
+
}
|
|
13619
|
+
}
|
|
13477
13620
|
function safeSerialize(args) {
|
|
13478
13621
|
try {
|
|
13479
|
-
return JSON.stringify(
|
|
13480
|
-
args.map((a) => {
|
|
13481
|
-
if (a instanceof Error) {
|
|
13482
|
-
return { name: a.name, message: a.message, stack: a.stack };
|
|
13483
|
-
}
|
|
13484
|
-
return a;
|
|
13485
|
-
})
|
|
13486
|
-
);
|
|
13622
|
+
return JSON.stringify(args.map(serializeConsoleArg));
|
|
13487
13623
|
} catch {
|
|
13488
13624
|
return "[unserializable]";
|
|
13489
13625
|
}
|
|
13490
13626
|
}
|
|
13627
|
+
function consoleMessage(args) {
|
|
13628
|
+
return args.map(formatConsoleArg).join(" ").slice(0, 4e3);
|
|
13629
|
+
}
|
|
13491
13630
|
function installConsoleHook() {
|
|
13492
13631
|
const levels = ["log", "info", "warn", "error", "debug"];
|
|
13493
13632
|
const originals = {};
|
|
@@ -13496,9 +13635,12 @@ function installConsoleHook() {
|
|
|
13496
13635
|
originals[level] = original;
|
|
13497
13636
|
console[level] = (...args) => {
|
|
13498
13637
|
try {
|
|
13638
|
+
const serializedArgs = safeSerialize(args).slice(0, 4e3);
|
|
13499
13639
|
record.addCustomEvent("talaria-console", {
|
|
13500
13640
|
level,
|
|
13501
|
-
args
|
|
13641
|
+
// `message` is what the replay sidebar displays; `args` kept for tooling.
|
|
13642
|
+
message: consoleMessage(args),
|
|
13643
|
+
args: serializedArgs,
|
|
13502
13644
|
timestamp: Date.now()
|
|
13503
13645
|
});
|
|
13504
13646
|
} catch {
|
|
@@ -13517,7 +13659,6 @@ function emitNetwork(meta) {
|
|
|
13517
13659
|
try {
|
|
13518
13660
|
record.addCustomEvent("talaria-network", {
|
|
13519
13661
|
...meta,
|
|
13520
|
-
url: redactUrl(meta.url),
|
|
13521
13662
|
timestamp: Date.now()
|
|
13522
13663
|
});
|
|
13523
13664
|
} catch {
|
|
@@ -13534,23 +13675,70 @@ function statusMatches(status, codes) {
|
|
|
13534
13675
|
}
|
|
13535
13676
|
return false;
|
|
13536
13677
|
}
|
|
13537
|
-
function
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
if (!statusMatches(meta.status, opts.failedRequestStatusCodes)) return false;
|
|
13541
|
-
const url = meta.url || "";
|
|
13542
|
-
const ignore = [...opts.failedRequestIgnoreUrls];
|
|
13543
|
-
const base = (opts.talariaBaseUrl ?? "").replace(/\/+$/, "");
|
|
13678
|
+
function buildFailedRequestIgnoreUrls(failedRequestIgnoreUrls, talariaBaseUrl) {
|
|
13679
|
+
const ignore = [...failedRequestIgnoreUrls];
|
|
13680
|
+
const base = (talariaBaseUrl ?? "").replace(/\/+$/, "");
|
|
13544
13681
|
if (base) {
|
|
13545
13682
|
ignore.push(`${base}/events/`, `${base}/replays/`);
|
|
13546
13683
|
}
|
|
13547
13684
|
ignore.push("/events/ingest", "/events/ingestBatch", "/replays/");
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13685
|
+
return ignore;
|
|
13686
|
+
}
|
|
13687
|
+
function urlMatchesIgnoreList(url, ignoreUrls) {
|
|
13688
|
+
const lower = (url || "").toLowerCase();
|
|
13689
|
+
for (const part of ignoreUrls) {
|
|
13690
|
+
if (part && lower.includes(part.toLowerCase())) return true;
|
|
13551
13691
|
}
|
|
13692
|
+
return false;
|
|
13693
|
+
}
|
|
13694
|
+
function shouldPromoteFailedRequest(meta, opts) {
|
|
13695
|
+
if (!opts.captureFailedRequests) return false;
|
|
13696
|
+
if (typeof meta.status !== "number" || Number.isNaN(meta.status)) return false;
|
|
13697
|
+
if (meta.status === 0) return false;
|
|
13698
|
+
if (!statusMatches(meta.status, opts.failedRequestStatusCodes)) return false;
|
|
13699
|
+
const ignore = buildFailedRequestIgnoreUrls(
|
|
13700
|
+
opts.failedRequestIgnoreUrls,
|
|
13701
|
+
opts.talariaBaseUrl
|
|
13702
|
+
);
|
|
13703
|
+
if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
|
|
13552
13704
|
return true;
|
|
13553
13705
|
}
|
|
13706
|
+
function shouldPromoteNetworkError(meta, opts) {
|
|
13707
|
+
if (!opts.captureNetworkErrors) return false;
|
|
13708
|
+
if (meta.aborted) return false;
|
|
13709
|
+
if (meta.failureKind !== "network") return false;
|
|
13710
|
+
const ignore = buildFailedRequestIgnoreUrls(
|
|
13711
|
+
opts.failedRequestIgnoreUrls,
|
|
13712
|
+
opts.talariaBaseUrl
|
|
13713
|
+
);
|
|
13714
|
+
if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
|
|
13715
|
+
return true;
|
|
13716
|
+
}
|
|
13717
|
+
function enrichNetworkMeta(meta) {
|
|
13718
|
+
const hasHttpStatus = typeof meta.status === "number" && !Number.isNaN(meta.status) && meta.status > 0;
|
|
13719
|
+
if (hasHttpStatus) {
|
|
13720
|
+
return {
|
|
13721
|
+
...meta,
|
|
13722
|
+
failureKind: meta.ok === false ? "http" : meta.failureKind
|
|
13723
|
+
};
|
|
13724
|
+
}
|
|
13725
|
+
if (meta.ok === false || meta.errorName || meta.errorMessage) {
|
|
13726
|
+
return {
|
|
13727
|
+
...meta,
|
|
13728
|
+
failureKind: "network"
|
|
13729
|
+
};
|
|
13730
|
+
}
|
|
13731
|
+
return meta;
|
|
13732
|
+
}
|
|
13733
|
+
function networkUrlParts(rawUrl, includeQuery) {
|
|
13734
|
+
const parts = sanitizeNetworkUrl(rawUrl, { includeQuery });
|
|
13735
|
+
return {
|
|
13736
|
+
url: parts.url,
|
|
13737
|
+
hostname: parts.hostname,
|
|
13738
|
+
pathname: parts.pathname,
|
|
13739
|
+
...parts.search ? { search: parts.search } : {}
|
|
13740
|
+
};
|
|
13741
|
+
}
|
|
13554
13742
|
function installNetworkHook(options = {}) {
|
|
13555
13743
|
const originalFetch = typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
13556
13744
|
const XHR = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest : null;
|
|
@@ -13558,38 +13746,57 @@ function installNetworkHook(options = {}) {
|
|
|
13558
13746
|
const originalSend = XHR?.prototype.send;
|
|
13559
13747
|
const matchOpts = {
|
|
13560
13748
|
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
13749
|
+
captureNetworkErrors: options.captureNetworkErrors ?? true,
|
|
13561
13750
|
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
13562
13751
|
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
|
|
13563
13752
|
talariaBaseUrl: options.talariaBaseUrl
|
|
13564
13753
|
};
|
|
13754
|
+
const includeQuery = options.includeNetworkUrlQuery ?? false;
|
|
13565
13755
|
const handleMeta = (meta) => {
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13756
|
+
const { rawUrl, ...rest } = meta;
|
|
13757
|
+
const enriched = enrichNetworkMeta({
|
|
13758
|
+
...rest,
|
|
13759
|
+
...networkUrlParts(rawUrl, includeQuery)
|
|
13760
|
+
});
|
|
13761
|
+
try {
|
|
13762
|
+
emitNetwork(enriched);
|
|
13763
|
+
options.onNetwork?.(enriched);
|
|
13764
|
+
if (shouldPromoteFailedRequest(enriched, matchOpts)) {
|
|
13765
|
+
options.onFailedRequest?.(enriched);
|
|
13766
|
+
} else if (shouldPromoteNetworkError(enriched, matchOpts)) {
|
|
13767
|
+
options.onNetworkError?.(enriched);
|
|
13768
|
+
}
|
|
13769
|
+
} catch {
|
|
13570
13770
|
}
|
|
13571
13771
|
};
|
|
13572
13772
|
if (originalFetch) {
|
|
13573
13773
|
globalThis.fetch = async (input2, init) => {
|
|
13574
13774
|
const started = Date.now();
|
|
13575
13775
|
const method = (init?.method ?? (input2 instanceof Request ? input2.method : "GET")).toUpperCase();
|
|
13576
|
-
const
|
|
13776
|
+
const rawUrl = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
|
|
13577
13777
|
try {
|
|
13578
13778
|
const response = await originalFetch(input2, init);
|
|
13579
13779
|
handleMeta({
|
|
13580
13780
|
method,
|
|
13581
|
-
|
|
13781
|
+
rawUrl,
|
|
13782
|
+
url: rawUrl,
|
|
13582
13783
|
status: response.status,
|
|
13583
13784
|
durationMs: Date.now() - started,
|
|
13584
13785
|
ok: response.ok
|
|
13585
13786
|
});
|
|
13586
13787
|
return response;
|
|
13587
13788
|
} catch (error) {
|
|
13789
|
+
const described = describeUnknownError(error);
|
|
13588
13790
|
handleMeta({
|
|
13589
13791
|
method,
|
|
13590
|
-
|
|
13792
|
+
rawUrl,
|
|
13793
|
+
url: rawUrl,
|
|
13591
13794
|
durationMs: Date.now() - started,
|
|
13592
|
-
ok: false
|
|
13795
|
+
ok: false,
|
|
13796
|
+
errorName: described.errorName,
|
|
13797
|
+
errorMessage: described.errorMessage,
|
|
13798
|
+
aborted: described.aborted,
|
|
13799
|
+
failureKind: "network"
|
|
13593
13800
|
});
|
|
13594
13801
|
throw error;
|
|
13595
13802
|
}
|
|
@@ -13604,12 +13811,21 @@ function installNetworkHook(options = {}) {
|
|
|
13604
13811
|
XHR.prototype.send = function(body) {
|
|
13605
13812
|
this.__talariaStarted = Date.now();
|
|
13606
13813
|
const onDone = () => {
|
|
13814
|
+
const status = this.status;
|
|
13815
|
+
const networkFail = status === 0;
|
|
13816
|
+
const rawUrl = this.__talariaUrl ?? "";
|
|
13607
13817
|
handleMeta({
|
|
13608
13818
|
method: this.__talariaMethod ?? "GET",
|
|
13609
|
-
|
|
13610
|
-
|
|
13819
|
+
rawUrl,
|
|
13820
|
+
url: rawUrl,
|
|
13821
|
+
status,
|
|
13611
13822
|
durationMs: Date.now() - (this.__talariaStarted ?? Date.now()),
|
|
13612
|
-
ok:
|
|
13823
|
+
ok: status >= 200 && status < 400,
|
|
13824
|
+
...networkFail ? {
|
|
13825
|
+
failureKind: "network",
|
|
13826
|
+
errorName: "NetworkError",
|
|
13827
|
+
errorMessage: "XMLHttpRequest failed (status 0)"
|
|
13828
|
+
} : {}
|
|
13613
13829
|
});
|
|
13614
13830
|
};
|
|
13615
13831
|
this.addEventListener("loadend", onDone);
|
|
@@ -13624,6 +13840,37 @@ function installNetworkHook(options = {}) {
|
|
|
13624
13840
|
}
|
|
13625
13841
|
|
|
13626
13842
|
// src/client.ts
|
|
13843
|
+
function networkFailureExtra(failure) {
|
|
13844
|
+
return {
|
|
13845
|
+
method: failure.method || "GET",
|
|
13846
|
+
url: failure.url || "(unknown url)",
|
|
13847
|
+
...failure.hostname ? { hostname: failure.hostname } : {},
|
|
13848
|
+
...failure.pathname ? { pathname: failure.pathname } : {},
|
|
13849
|
+
...failure.search ? { search: failure.search } : {},
|
|
13850
|
+
...typeof failure.status === "number" ? { status: failure.status } : {},
|
|
13851
|
+
durationMs: failure.durationMs,
|
|
13852
|
+
ok: failure.ok ?? false,
|
|
13853
|
+
failureKind: failure.failureKind ?? "network",
|
|
13854
|
+
aborted: failure.aborted ?? false,
|
|
13855
|
+
...failure.errorName ? { errorName: failure.errorName } : {},
|
|
13856
|
+
...failure.errorMessage ? { errorMessage: failure.errorMessage } : {}
|
|
13857
|
+
};
|
|
13858
|
+
}
|
|
13859
|
+
function mergeNetworkFailureContext(context, failure) {
|
|
13860
|
+
return {
|
|
13861
|
+
...context,
|
|
13862
|
+
tags: {
|
|
13863
|
+
...context?.tags ?? {},
|
|
13864
|
+
"http.method": failure.method || "GET",
|
|
13865
|
+
"network.failure_kind": "network",
|
|
13866
|
+
...failure.errorName ? { "network.error_name": failure.errorName } : {}
|
|
13867
|
+
},
|
|
13868
|
+
extra: {
|
|
13869
|
+
...context?.extra ?? {},
|
|
13870
|
+
network: networkFailureExtra(failure)
|
|
13871
|
+
}
|
|
13872
|
+
};
|
|
13873
|
+
}
|
|
13627
13874
|
function resolveOptions(options) {
|
|
13628
13875
|
const baseUrl = (options.baseUrl ?? options.dsn ?? "").trim();
|
|
13629
13876
|
if (!baseUrl) {
|
|
@@ -13638,7 +13885,7 @@ function resolveOptions(options) {
|
|
|
13638
13885
|
return {
|
|
13639
13886
|
baseUrl,
|
|
13640
13887
|
apiKey: options.apiKey.trim(),
|
|
13641
|
-
environment: String(options.environment),
|
|
13888
|
+
environment: normalizeEnvironment(String(options.environment)),
|
|
13642
13889
|
release: options.release,
|
|
13643
13890
|
replaysSessionSampleRate: clamp01(options.replaysSessionSampleRate ?? 0),
|
|
13644
13891
|
replaysOnErrorSampleRate: clamp01(options.replaysOnErrorSampleRate ?? 1),
|
|
@@ -13650,10 +13897,13 @@ function resolveOptions(options) {
|
|
|
13650
13897
|
tags: options.tags,
|
|
13651
13898
|
disableDefaultIntegrations: options.disableDefaultIntegrations ?? false,
|
|
13652
13899
|
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
13900
|
+
captureNetworkErrors: options.captureNetworkErrors ?? true,
|
|
13901
|
+
includeNetworkUrlQuery: options.includeNetworkUrlQuery ?? false,
|
|
13653
13902
|
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
13654
13903
|
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? []
|
|
13655
13904
|
};
|
|
13656
13905
|
}
|
|
13906
|
+
var RECENT_NETWORK_FAILURE_MS = 5e3;
|
|
13657
13907
|
function clamp01(n2) {
|
|
13658
13908
|
if (Number.isNaN(n2)) return 0;
|
|
13659
13909
|
return Math.min(1, Math.max(0, n2));
|
|
@@ -13678,6 +13928,16 @@ function isOversizedSegmentError(error) {
|
|
|
13678
13928
|
const msg = error instanceof Error ? error.message : String(error);
|
|
13679
13929
|
return msg.includes("segment exceeds max compressed size") || msg.includes("segment exceeds max uncompressed size") || msg.includes("HTTP 400") && msg.includes("ApiValidationException");
|
|
13680
13930
|
}
|
|
13931
|
+
function isPermanentIngestError(error) {
|
|
13932
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
13933
|
+
const match = /Talaria events\/ingest(?:Batch)? failed: HTTP (\d{3})/.exec(
|
|
13934
|
+
msg
|
|
13935
|
+
);
|
|
13936
|
+
if (!match) return false;
|
|
13937
|
+
const status = Number(match[1]);
|
|
13938
|
+
if (status < 400 || status >= 500) return false;
|
|
13939
|
+
return status !== 408 && status !== 429;
|
|
13940
|
+
}
|
|
13681
13941
|
var TalariaClient = class {
|
|
13682
13942
|
constructor() {
|
|
13683
13943
|
this.options = null;
|
|
@@ -13712,6 +13972,15 @@ var TalariaClient = class {
|
|
|
13712
13972
|
this.lastReplayCaptureFailure = null;
|
|
13713
13973
|
/** Cached once per init for event tags/extra. */
|
|
13714
13974
|
this.browserContext = null;
|
|
13975
|
+
/** Prevent ingest/replay failures from being re-captured via unhandledrejection. */
|
|
13976
|
+
this.capturing = false;
|
|
13977
|
+
/**
|
|
13978
|
+
* Set after a permanent events/ingest 4xx (bad env/auth/wire). Further
|
|
13979
|
+
* captures no-op so we don't spin on retries or burn error-clip replay quota.
|
|
13980
|
+
*/
|
|
13981
|
+
this.ingestDisabled = false;
|
|
13982
|
+
/** Recent fetch/XHR transport failures for correlation + dedupe. */
|
|
13983
|
+
this.recentNetworkFailures = [];
|
|
13715
13984
|
}
|
|
13716
13985
|
init(options) {
|
|
13717
13986
|
if (this.options) {
|
|
@@ -13726,6 +13995,7 @@ var TalariaClient = class {
|
|
|
13726
13995
|
this.sessionId = createId();
|
|
13727
13996
|
this.replayId = createId();
|
|
13728
13997
|
this.closed = false;
|
|
13998
|
+
this.ingestDisabled = false;
|
|
13729
13999
|
this.finishedOnServer = false;
|
|
13730
14000
|
this.startedOnServer = false;
|
|
13731
14001
|
this.segmentIndex = 0;
|
|
@@ -13734,6 +14004,7 @@ var TalariaClient = class {
|
|
|
13734
14004
|
this.errorClipDeadlineMs = null;
|
|
13735
14005
|
this.linkableReplayId = null;
|
|
13736
14006
|
this.lastReplayCaptureFailure = null;
|
|
14007
|
+
this.recentNetworkFailures = [];
|
|
13737
14008
|
this.browserContext = parseBrowserContext();
|
|
13738
14009
|
void collectBrowserContext().then((ctx) => {
|
|
13739
14010
|
if (this.options) this.browserContext = ctx;
|
|
@@ -13755,9 +14026,16 @@ var TalariaClient = class {
|
|
|
13755
14026
|
installConsoleHook(),
|
|
13756
14027
|
installNetworkHook({
|
|
13757
14028
|
captureFailedRequests: this.options.captureFailedRequests,
|
|
14029
|
+
captureNetworkErrors: this.options.captureNetworkErrors,
|
|
14030
|
+
includeNetworkUrlQuery: this.options.includeNetworkUrlQuery,
|
|
13758
14031
|
failedRequestStatusCodes: this.options.failedRequestStatusCodes,
|
|
13759
14032
|
failedRequestIgnoreUrls: this.options.failedRequestIgnoreUrls,
|
|
13760
14033
|
talariaBaseUrl: this.options.baseUrl,
|
|
14034
|
+
onNetwork: (meta) => {
|
|
14035
|
+
if (meta.failureKind === "network" && !meta.aborted) {
|
|
14036
|
+
this.rememberNetworkFailure(meta, { promoted: false });
|
|
14037
|
+
}
|
|
14038
|
+
},
|
|
13761
14039
|
onFailedRequest: (meta) => {
|
|
13762
14040
|
const status = meta.status;
|
|
13763
14041
|
const method = meta.method || "GET";
|
|
@@ -13768,15 +14046,40 @@ var TalariaClient = class {
|
|
|
13768
14046
|
{
|
|
13769
14047
|
tags: {
|
|
13770
14048
|
"http.status_code": String(status),
|
|
13771
|
-
"http.method": method
|
|
14049
|
+
"http.method": method,
|
|
14050
|
+
"network.failure_kind": "http"
|
|
13772
14051
|
},
|
|
13773
14052
|
extra: {
|
|
13774
|
-
|
|
13775
|
-
|
|
13776
|
-
|
|
14053
|
+
...networkFailureExtra({
|
|
14054
|
+
...meta,
|
|
14055
|
+
failureKind: "http",
|
|
14056
|
+
aborted: false
|
|
14057
|
+
})
|
|
13777
14058
|
}
|
|
13778
14059
|
}
|
|
13779
14060
|
);
|
|
14061
|
+
},
|
|
14062
|
+
onNetworkError: (meta) => {
|
|
14063
|
+
this.rememberNetworkFailure(meta, { promoted: true });
|
|
14064
|
+
const method = meta.method || "GET";
|
|
14065
|
+
const url = meta.url || "(unknown url)";
|
|
14066
|
+
const errLabel = meta.errorName && meta.errorMessage ? `${meta.errorName}: ${meta.errorMessage}` : meta.errorMessage || meta.errorName || "Failed to fetch";
|
|
14067
|
+
void this.captureMessage(
|
|
14068
|
+
`Network error: ${method} ${url} \u2014 ${errLabel}`,
|
|
14069
|
+
"error",
|
|
14070
|
+
{
|
|
14071
|
+
tags: {
|
|
14072
|
+
"http.method": method,
|
|
14073
|
+
"network.failure_kind": "network",
|
|
14074
|
+
...meta.errorName ? { "network.error_name": meta.errorName } : {}
|
|
14075
|
+
},
|
|
14076
|
+
extra: networkFailureExtra({
|
|
14077
|
+
...meta,
|
|
14078
|
+
failureKind: "network",
|
|
14079
|
+
aborted: false
|
|
14080
|
+
})
|
|
14081
|
+
}
|
|
14082
|
+
);
|
|
13780
14083
|
}
|
|
13781
14084
|
}),
|
|
13782
14085
|
installVisibilityResumeHook(() => this.onForegroundResume())
|
|
@@ -13810,24 +14113,38 @@ var TalariaClient = class {
|
|
|
13810
14113
|
return this.linkableReplayId;
|
|
13811
14114
|
}
|
|
13812
14115
|
async captureException(error, context) {
|
|
14116
|
+
if (this.capturing || this.ingestDisabled) return;
|
|
13813
14117
|
const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
|
|
14118
|
+
if (isAbortError(err)) return;
|
|
13814
14119
|
const filename = typeof context?.extra?.filename === "string" ? context.extra.filename : void 0;
|
|
13815
14120
|
if (isBrowserExtensionNoise({
|
|
13816
14121
|
message: err.message,
|
|
13817
14122
|
stack: err.stack,
|
|
13818
14123
|
filename
|
|
14124
|
+
}) || isSdkInternalNoise({
|
|
14125
|
+
message: err.message,
|
|
14126
|
+
stack: err.stack
|
|
13819
14127
|
})) {
|
|
13820
14128
|
return;
|
|
13821
14129
|
}
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
14130
|
+
const correlated = this.consumeCorrelatedNetworkFailure(err);
|
|
14131
|
+
if (correlated?.promoted) return;
|
|
14132
|
+
const mergedContext = correlated ? mergeNetworkFailureContext(context, correlated) : context;
|
|
14133
|
+
this.capturing = true;
|
|
14134
|
+
try {
|
|
14135
|
+
await this.capture({
|
|
14136
|
+
message: err.message || err.name || "Error",
|
|
14137
|
+
level: "error",
|
|
14138
|
+
title: err.name || "Error",
|
|
14139
|
+
stackTrace: err.stack,
|
|
14140
|
+
context: mergedContext
|
|
14141
|
+
});
|
|
14142
|
+
} finally {
|
|
14143
|
+
this.capturing = false;
|
|
14144
|
+
}
|
|
13829
14145
|
}
|
|
13830
14146
|
async captureMessage(message, level = "info", context) {
|
|
14147
|
+
if (this.ingestDisabled) return;
|
|
13831
14148
|
await this.capture({
|
|
13832
14149
|
message,
|
|
13833
14150
|
level,
|
|
@@ -13913,6 +14230,7 @@ var TalariaClient = class {
|
|
|
13913
14230
|
this.linkableReplayId = null;
|
|
13914
14231
|
this.lastReplayCaptureFailure = null;
|
|
13915
14232
|
this.browserContext = null;
|
|
14233
|
+
this.capturing = false;
|
|
13916
14234
|
}
|
|
13917
14235
|
onRrwebEvent(event) {
|
|
13918
14236
|
if (this.closed || !this.options) return;
|
|
@@ -13937,6 +14255,8 @@ var TalariaClient = class {
|
|
|
13937
14255
|
if (!this.options || !this.transport) {
|
|
13938
14256
|
throw new Error("@newtalaria/browser: call Talaria.init() first");
|
|
13939
14257
|
}
|
|
14258
|
+
if (this.ingestDisabled) return;
|
|
14259
|
+
const occurredAt = /* @__PURE__ */ new Date();
|
|
13940
14260
|
const isErrorLike = args.level === "error" || args.level === "fatal";
|
|
13941
14261
|
let errorClipOutcome = null;
|
|
13942
14262
|
let attemptedErrorClip = false;
|
|
@@ -13994,6 +14314,7 @@ var TalariaClient = class {
|
|
|
13994
14314
|
errorClipOutcome = { status: "failed", reason: "buffer_empty" };
|
|
13995
14315
|
}
|
|
13996
14316
|
}
|
|
14317
|
+
const queuedMs = Math.max(0, Date.now() - occurredAt.getTime());
|
|
13997
14318
|
const tags = applyReplayCaptureTags(
|
|
13998
14319
|
{
|
|
13999
14320
|
...this.browserContext ? browserContextTags(this.browserContext) : {},
|
|
@@ -14015,7 +14336,12 @@ var TalariaClient = class {
|
|
|
14015
14336
|
userAgent: this.browserContext.userAgent
|
|
14016
14337
|
}
|
|
14017
14338
|
} : {},
|
|
14018
|
-
sdk: {
|
|
14339
|
+
sdk: {
|
|
14340
|
+
name: SDK_NAME,
|
|
14341
|
+
version: SDK_VERSION,
|
|
14342
|
+
// Time spent in capture() before ingest (mostly replay flush).
|
|
14343
|
+
...queuedMs >= 50 ? { queuedMs } : {}
|
|
14344
|
+
},
|
|
14019
14345
|
...args.context?.extra
|
|
14020
14346
|
},
|
|
14021
14347
|
errorClipOutcome
|
|
@@ -14035,7 +14361,7 @@ var TalariaClient = class {
|
|
|
14035
14361
|
url: typeof location !== "undefined" ? location.href : void 0,
|
|
14036
14362
|
tags: Object.keys(tags).length ? tags : void 0,
|
|
14037
14363
|
extraJson: extra ? JSON.stringify(extra) : void 0,
|
|
14038
|
-
timestamp:
|
|
14364
|
+
timestamp: occurredAt.toISOString(),
|
|
14039
14365
|
keepalive: args.keepalive
|
|
14040
14366
|
});
|
|
14041
14367
|
if (replayId && replayId === this.linkableReplayId) {
|
|
@@ -14043,7 +14369,36 @@ var TalariaClient = class {
|
|
|
14043
14369
|
}
|
|
14044
14370
|
} catch (error) {
|
|
14045
14371
|
console.warn("@newtalaria/browser: event ingest failed", error);
|
|
14046
|
-
|
|
14372
|
+
if (isPermanentIngestError(error)) {
|
|
14373
|
+
this.disableIngestAfterPermanentError(error);
|
|
14374
|
+
}
|
|
14375
|
+
}
|
|
14376
|
+
}
|
|
14377
|
+
/**
|
|
14378
|
+
* Stop further event capture (and error-clip uploads) after a permanent
|
|
14379
|
+
* events/ingest 4xx so misconfig cannot spin forever.
|
|
14380
|
+
*/
|
|
14381
|
+
disableIngestAfterPermanentError(error) {
|
|
14382
|
+
if (this.ingestDisabled) return;
|
|
14383
|
+
this.ingestDisabled = true;
|
|
14384
|
+
console.warn(
|
|
14385
|
+
"@newtalaria/browser: event ingest disabled after permanent client error",
|
|
14386
|
+
error
|
|
14387
|
+
);
|
|
14388
|
+
if (!this.sessionSampled && this.uploadEnabled) {
|
|
14389
|
+
this.clearErrorClipTimer();
|
|
14390
|
+
void this.enqueueUpload(async () => {
|
|
14391
|
+
if (this.startedOnServer && !this.finishedOnServer) {
|
|
14392
|
+
await this.finishOnServer({
|
|
14393
|
+
keepalive: false,
|
|
14394
|
+
reason: "ingest_disabled"
|
|
14395
|
+
});
|
|
14396
|
+
}
|
|
14397
|
+
this.resetToBufferMode();
|
|
14398
|
+
});
|
|
14399
|
+
} else if (!this.sessionSampled) {
|
|
14400
|
+
this.clearErrorClipTimer();
|
|
14401
|
+
this.uploadEnabled = false;
|
|
14047
14402
|
}
|
|
14048
14403
|
}
|
|
14049
14404
|
markUploadStarted() {
|
|
@@ -14248,6 +14603,57 @@ var TalariaClient = class {
|
|
|
14248
14603
|
this.resetToBufferMode();
|
|
14249
14604
|
}
|
|
14250
14605
|
}
|
|
14606
|
+
pruneRecentNetworkFailures(now = Date.now()) {
|
|
14607
|
+
this.recentNetworkFailures = this.recentNetworkFailures.filter(
|
|
14608
|
+
(f) => now - f.at <= RECENT_NETWORK_FAILURE_MS
|
|
14609
|
+
);
|
|
14610
|
+
}
|
|
14611
|
+
rememberNetworkFailure(meta, opts) {
|
|
14612
|
+
const now = Date.now();
|
|
14613
|
+
this.pruneRecentNetworkFailures(now);
|
|
14614
|
+
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14615
|
+
const existing = this.recentNetworkFailures[i];
|
|
14616
|
+
if (existing.method === meta.method && existing.url === meta.url && existing.errorMessage === meta.errorMessage && now - existing.at < 1e3) {
|
|
14617
|
+
existing.promoted = existing.promoted || opts.promoted;
|
|
14618
|
+
existing.durationMs = meta.durationMs ?? existing.durationMs;
|
|
14619
|
+
existing.errorName = meta.errorName ?? existing.errorName;
|
|
14620
|
+
existing.aborted = meta.aborted ?? existing.aborted;
|
|
14621
|
+
existing.failureKind = meta.failureKind ?? existing.failureKind;
|
|
14622
|
+
return;
|
|
14623
|
+
}
|
|
14624
|
+
}
|
|
14625
|
+
this.recentNetworkFailures.push({
|
|
14626
|
+
...meta,
|
|
14627
|
+
at: now,
|
|
14628
|
+
promoted: opts.promoted
|
|
14629
|
+
});
|
|
14630
|
+
}
|
|
14631
|
+
/**
|
|
14632
|
+
* Find a recent transport failure for a bare fetch TypeError.
|
|
14633
|
+
* Consumes the entry so a later duplicate path cannot reuse it.
|
|
14634
|
+
*/
|
|
14635
|
+
consumeCorrelatedNetworkFailure(error) {
|
|
14636
|
+
if (!isLikelyNetworkFetchError(error)) return null;
|
|
14637
|
+
const now = Date.now();
|
|
14638
|
+
this.pruneRecentNetworkFailures(now);
|
|
14639
|
+
const takeAt = (index2) => {
|
|
14640
|
+
const [failure] = this.recentNetworkFailures.splice(index2, 1);
|
|
14641
|
+
return failure;
|
|
14642
|
+
};
|
|
14643
|
+
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14644
|
+
const failure = this.recentNetworkFailures[i];
|
|
14645
|
+
if (failure.aborted || failure.failureKind !== "network") continue;
|
|
14646
|
+
if (failure.errorMessage && failure.errorMessage === error.message) {
|
|
14647
|
+
return takeAt(i);
|
|
14648
|
+
}
|
|
14649
|
+
}
|
|
14650
|
+
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14651
|
+
const failure = this.recentNetworkFailures[i];
|
|
14652
|
+
if (failure.aborted || failure.failureKind !== "network") continue;
|
|
14653
|
+
return takeAt(i);
|
|
14654
|
+
}
|
|
14655
|
+
return null;
|
|
14656
|
+
}
|
|
14251
14657
|
installGlobalHandlers() {
|
|
14252
14658
|
if (typeof window === "undefined") return;
|
|
14253
14659
|
const onError = (event) => {
|
|
@@ -14269,7 +14675,21 @@ var TalariaClient = class {
|
|
|
14269
14675
|
});
|
|
14270
14676
|
};
|
|
14271
14677
|
const onRejection = (event) => {
|
|
14272
|
-
|
|
14678
|
+
const reason = event.reason;
|
|
14679
|
+
if (isAbortError(reason)) return;
|
|
14680
|
+
const err = reason instanceof Error ? reason : new Error(
|
|
14681
|
+
typeof reason === "string" ? reason : "unhandledrejection"
|
|
14682
|
+
);
|
|
14683
|
+
if (isBrowserExtensionNoise({
|
|
14684
|
+
message: err.message,
|
|
14685
|
+
stack: err.stack
|
|
14686
|
+
}) || isSdkInternalNoise({
|
|
14687
|
+
message: err.message,
|
|
14688
|
+
stack: err.stack
|
|
14689
|
+
})) {
|
|
14690
|
+
return;
|
|
14691
|
+
}
|
|
14692
|
+
void this.captureException(reason);
|
|
14273
14693
|
};
|
|
14274
14694
|
window.addEventListener("error", onError);
|
|
14275
14695
|
window.addEventListener("unhandledrejection", onRejection);
|