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