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