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