@newtalaria/browser 0.1.17 → 0.1.18
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 +77 -15
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/index.js +134 -15
- package/dist/index.js.map +2 -2
- package/dist/replay/hooks.d.ts +45 -2
- package/dist/replay/hooks.d.ts.map +1 -1
- package/dist/replay/privacy.d.ts +1 -1
- package/dist/replay/privacy.d.ts.map +1 -1
- package/dist/sdk_meta.d.ts +1 -1
- package/dist/talaria.browser.iife.js +134 -15
- package/dist/talaria.browser.iife.js.map +2 -2
- package/dist/types.d.ts +13 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/network_error.d.ts +5 -0
- package/dist/utils/network_error.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Browser SDK for [Talaria](https://github.com/) — error capture and **session replay** (rrweb event streams, not video).
|
|
4
4
|
|
|
5
|
+
Network calls (`fetch` / XHR) are instrumented as replay breadcrumbs; **only first-party (same-origin) or allowlisted origins** promote failed requests to error events — see [Failed HTTP / network requests](#failed-http--network-requests--events).
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -187,15 +189,24 @@ Set `inlineStylesheet: true` so same-origin stylesheet rules are embedded while
|
|
|
187
189
|
|
|
188
190
|
### Failed HTTP / network requests → events
|
|
189
191
|
|
|
190
|
-
|
|
192
|
+
All instrumented `fetch` / XHR calls are recorded as replay breadcrumbs (`talaria-network`). **Error events are promoted only for first-party (same-origin) or explicitly allowlisted origins.**
|
|
191
193
|
|
|
192
|
-
|
|
194
|
+
| Request | HTTP 5xx | Transport failure / timeout | Abort |
|
|
195
|
+
| --- | --- | --- | --- |
|
|
196
|
+
| Same-origin | Error event | Error event | Breadcrumb only |
|
|
197
|
+
| Allowlisted third-party (`networkErrorOrigins`) | Error event | Error event | Breadcrumb only |
|
|
198
|
+
| Other third-party (analytics, ads, widgets, …) | Breadcrumb only | Breadcrumb only | Breadcrumb only |
|
|
193
199
|
|
|
194
|
-
|
|
200
|
+
This avoids Issue spam from Google Analytics, ad pixels, privacy blockers, and similar third-party noise—without a hardcoded domain denylist. Add origins you intentionally depend on (payments, CMS APIs, widgets):
|
|
195
201
|
|
|
196
|
-
|
|
202
|
+
```ts
|
|
203
|
+
networkErrorOrigins: ['https://api.stripe.com', 'https://widget.yonder.example'],
|
|
204
|
+
// Escape hatch (not recommended): networkErrorOrigins: ['*']
|
|
205
|
+
```
|
|
197
206
|
|
|
198
|
-
|
|
207
|
+
**URL privacy:** network telemetry stores `origin + pathname` only (plus `hostname` / `pathname`). Query strings and fragments are stripped by default. Set `captureRequestQueryParameters: true` to keep query params after redaction of secrets **and** common tracking keys (`token`, `api_key`, `gclid`, `fbclid`, `_ga`, `cid`, `sid`, …). Bodies and auth headers are never captured.
|
|
208
|
+
|
|
209
|
+
Both **`fetch`** and **`XMLHttpRequest`** are instrumented (`network.transport`: `fetch` | `xhr`). Failure kinds are only claimed when detectable:
|
|
199
210
|
|
|
200
211
|
| `failure.kind` | Meaning |
|
|
201
212
|
| --- | --- |
|
|
@@ -204,16 +215,24 @@ Both **`fetch`** and **`XMLHttpRequest`** are instrumented (`http.transport`: `f
|
|
|
204
215
|
| `timeout` | `TimeoutError` or XHR `timeout` event |
|
|
205
216
|
| `abort` | `AbortError` / XHR abort — recorded in breadcrumbs, **not** promoted as events |
|
|
206
217
|
|
|
207
|
-
We **do not** classify status 0 as `cors` — browsers do not expose that reliably.
|
|
218
|
+
We **do not** classify status 0 as `cors` — browsers do not expose that reliably. Tags include `network.party` (`first_party` \| `third_party`).
|
|
208
219
|
|
|
209
|
-
Promoted events use structured `extra
|
|
220
|
+
Promoted events use structured `extra` (server groups on method + host + path + kind + status):
|
|
210
221
|
|
|
211
222
|
```json
|
|
212
223
|
{
|
|
213
|
-
"http": {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
"http": {
|
|
225
|
+
"method": "POST",
|
|
226
|
+
"url": "https://app.example.com/api/orders",
|
|
227
|
+
"origin": "https://app.example.com",
|
|
228
|
+
"hostname": "app.example.com",
|
|
229
|
+
"pathname": "/api/orders",
|
|
230
|
+
"transport": "fetch"
|
|
231
|
+
},
|
|
232
|
+
"failure": { "kind": "network", "name": "TypeError", "message": "Failed to fetch" },
|
|
233
|
+
"network": { "party": "first_party", "durationMs": 842, "aborted": false, "ok": false },
|
|
234
|
+
"exception_class": "NetworkError",
|
|
235
|
+
"status_code": null
|
|
217
236
|
}
|
|
218
237
|
```
|
|
219
238
|
|
|
@@ -224,10 +243,12 @@ Talaria.init({
|
|
|
224
243
|
dsn: 'https://api.example.com',
|
|
225
244
|
apiKey: 'tal_live_…',
|
|
226
245
|
environment: 'production',
|
|
227
|
-
// Default true — HTTP status promotion
|
|
246
|
+
// Default true — HTTP status promotion (first-party / allowlisted only)
|
|
228
247
|
captureFailedRequests: true,
|
|
229
|
-
// Default true — transport failures (
|
|
248
|
+
// Default true — transport failures (first-party / allowlisted only)
|
|
230
249
|
captureNetworkErrors: true,
|
|
250
|
+
// Extra origins to treat like first-party for promotion
|
|
251
|
+
networkErrorOrigins: ['https://api.stripe.com'],
|
|
231
252
|
// Default false — do not store ?query on network URLs
|
|
232
253
|
captureRequestQueryParameters: false,
|
|
233
254
|
// Default [[500, 599]]. CMS admin often wants 4xx too:
|
|
@@ -237,7 +258,24 @@ Talaria.init({
|
|
|
237
258
|
});
|
|
238
259
|
```
|
|
239
260
|
|
|
240
|
-
`AbortError` is never promoted and is ignored by the global `unhandledrejection` handler.
|
|
261
|
+
`AbortError` is never promoted and is ignored by the global `unhandledrejection` handler. Bare `TypeError: Failed to fetch` / `TimeoutError` rejections are correlated with recent network breadcrumbs: promoted failures and third-party noise are suppressed; first-party failures with promotion off keep the exception and merge request context.
|
|
262
|
+
|
|
263
|
+
#### Network event tags (low cardinality)
|
|
264
|
+
|
|
265
|
+
| Tag | Values | Notes |
|
|
266
|
+
| --- | --- | --- |
|
|
267
|
+
| `http.method` | `GET`, `POST`, … | |
|
|
268
|
+
| `http.status_code` | e.g. `500` | Present on HTTP promotions only |
|
|
269
|
+
| `network.failure_kind` | `http` \| `network` \| `timeout` | |
|
|
270
|
+
| `network.transport` | `fetch` \| `xhr` | |
|
|
271
|
+
| `network.party` | `first_party` \| `third_party` | Same-origin vs cross-origin (allowlist does not change this tag) |
|
|
272
|
+
| `network.error_name` | e.g. `TypeError`, `TimeoutError` | When available |
|
|
273
|
+
|
|
274
|
+
Hostname, pathname, full sanitized URL, and duration live in `extra` — not tags — to avoid high-cardinality facets.
|
|
275
|
+
|
|
276
|
+
#### Issue grouping (server)
|
|
277
|
+
|
|
278
|
+
Promoted network events set `extra.exception_class` (`HttpError` \| `NetworkError` \| `TimeoutError`) and `extra.status_code`. The server fingerprints them on **method + hostname + pathname + failure kind + status** (not the human message), so query strings and volatile error text do not fragment Issues. See [`planning/fingerprints.md`](../../../planning/fingerprints.md) §6.4.
|
|
241
279
|
|
|
242
280
|
### Event timestamps
|
|
243
281
|
|
|
@@ -248,11 +286,35 @@ Talaria.init({
|
|
|
248
286
|
|
|
249
287
|
If replay upload runs before event ingest, `extra.sdk.queuedMs` records how long capture waited (ms). A large `createdAt - timestamp` gap is usually flush/queue delay or clock skew — not a wrong failure classification.
|
|
250
288
|
|
|
289
|
+
## Init options reference
|
|
290
|
+
|
|
291
|
+
| Option | Default | Description |
|
|
292
|
+
| --- | --- | --- |
|
|
293
|
+
| `dsn` / `baseUrl` | *(required)* | Serverpod host, e.g. `https://api.example.com` |
|
|
294
|
+
| `apiKey` | *(required)* | Project API key (`tal_live_…`) |
|
|
295
|
+
| `environment` | *(required)* | `production` \| `staging` \| `development` (aliases accepted) |
|
|
296
|
+
| `release` | — | Optional release string on every event |
|
|
297
|
+
| `userId` | — | Optional app user id |
|
|
298
|
+
| `tags` | — | Tags merged into every event |
|
|
299
|
+
| `replaysSessionSampleRate` | `0` | Fraction of sessions that upload continuously |
|
|
300
|
+
| `replaysOnErrorSampleRate` | `1` | Fraction of errors that promote the ring buffer |
|
|
301
|
+
| `replaysErrorAfterMs` | `15000` | Post-error upload window; `0` = continue to 5 min cap |
|
|
302
|
+
| `maskAllInputs` | `true` | rrweb input masking |
|
|
303
|
+
| `inlineStylesheet` | `false` | Embed same-origin CSS (CMS/admin) |
|
|
304
|
+
| `blockSelector` | — | Extra CSS selectors blocked from the DOM snapshot |
|
|
305
|
+
| `disableDefaultIntegrations` | `false` | Skip `window.onerror` / `unhandledrejection` |
|
|
306
|
+
| `captureFailedRequests` | `true` | Promote HTTP status failures (**first-party / allowlisted only**) |
|
|
307
|
+
| `captureNetworkErrors` | `true` | Promote transport/timeout failures (**first-party / allowlisted only**) |
|
|
308
|
+
| `networkErrorOrigins` | `[]` | Extra origins eligible for promotion; same-origin always eligible; `['*']` = all (not recommended) |
|
|
309
|
+
| `failedRequestStatusCodes` | `[[500, 599]]` | Status codes/ranges to promote when origin is eligible |
|
|
310
|
+
| `failedRequestIgnoreUrls` | `[]` | URL substrings never promoted (Talaria `/events` + `/replays` always ignored) |
|
|
311
|
+
| `captureRequestQueryParameters` | `false` | Keep `?query` on network URLs after redaction (`includeNetworkUrlQuery` alias) |
|
|
312
|
+
|
|
251
313
|
## Public API
|
|
252
314
|
|
|
253
315
|
| API | Description |
|
|
254
316
|
| --- | --- |
|
|
255
|
-
| `Talaria.init(options)` | Configure + start recording |
|
|
317
|
+
| `Talaria.init(options)` | Configure + start recording + install network/console hooks |
|
|
256
318
|
| `Talaria.captureException(error)` | Ingest error (+ replay link when sampled) |
|
|
257
319
|
| `Talaria.captureMessage(message, level?)` | Ingest message |
|
|
258
320
|
| `Talaria.getReplayId()` | Active upload replay id, or `null` |
|
package/dist/client.d.ts
CHANGED
|
@@ -107,7 +107,7 @@ export declare class TalariaClient {
|
|
|
107
107
|
private pruneRecentNetworkFailures;
|
|
108
108
|
private rememberNetworkFailure;
|
|
109
109
|
/**
|
|
110
|
-
* Find a recent transport failure for a bare fetch TypeError.
|
|
110
|
+
* Find a recent transport failure for a bare fetch TypeError / TimeoutError.
|
|
111
111
|
* Consumes the entry so a later duplicate path cannot reuse it.
|
|
112
112
|
*/
|
|
113
113
|
private consumeCorrelatedNetworkFailure;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -272,7 +272,7 @@ async function collectBrowserContext() {
|
|
|
272
272
|
|
|
273
273
|
// src/sdk_meta.ts
|
|
274
274
|
var SDK_NAME = "@newtalaria/browser";
|
|
275
|
-
var SDK_VERSION = "0.1.
|
|
275
|
+
var SDK_VERSION = "0.1.18";
|
|
276
276
|
|
|
277
277
|
// src/transport/serverpod.ts
|
|
278
278
|
var ServerpodTransport = class {
|
|
@@ -13482,18 +13482,22 @@ var { freezePage } = record;
|
|
|
13482
13482
|
var { takeFullSnapshot } = record;
|
|
13483
13483
|
|
|
13484
13484
|
// src/replay/privacy.ts
|
|
13485
|
-
var
|
|
13485
|
+
var SENSITIVE_QUERY_KEY = /^(?:token|secret|password|auth|api[_-]?key|access[_-]?token|gclid|fbclid|gcl_au|msclkid|_ga(?:_.*)?|cid|sid)$/i;
|
|
13486
|
+
var SENSITIVE_QUERY_LEGACY = /(?:^|[?&])(token|secret|password|auth|api[_-]?key|access[_-]?token|gclid|fbclid|gcl_au|msclkid|_ga(?:_[^=&#]*)?|cid|sid)=([^&#]*)/gi;
|
|
13487
|
+
function isSensitiveQueryKey(key) {
|
|
13488
|
+
return SENSITIVE_QUERY_KEY.test(key);
|
|
13489
|
+
}
|
|
13486
13490
|
function redactUrl(raw) {
|
|
13487
13491
|
try {
|
|
13488
13492
|
const url = new URL(raw, typeof location !== "undefined" ? location.href : void 0);
|
|
13489
13493
|
for (const key of [...url.searchParams.keys()]) {
|
|
13490
|
-
if (
|
|
13494
|
+
if (isSensitiveQueryKey(key)) {
|
|
13491
13495
|
url.searchParams.set(key, "[Filtered]");
|
|
13492
13496
|
}
|
|
13493
13497
|
}
|
|
13494
13498
|
return url.toString();
|
|
13495
13499
|
} catch {
|
|
13496
|
-
return raw.replace(
|
|
13500
|
+
return raw.replace(SENSITIVE_QUERY_LEGACY, (_m, name) => `${name}=[Filtered]`);
|
|
13497
13501
|
}
|
|
13498
13502
|
}
|
|
13499
13503
|
function sanitizeNetworkUrl(raw, opts) {
|
|
@@ -13590,6 +13594,9 @@ function startRecorder(options) {
|
|
|
13590
13594
|
function isAbortError(error) {
|
|
13591
13595
|
return error instanceof Error && error.name === "AbortError";
|
|
13592
13596
|
}
|
|
13597
|
+
function isTimeoutError(error) {
|
|
13598
|
+
return error instanceof Error && error.name === "TimeoutError";
|
|
13599
|
+
}
|
|
13593
13600
|
function isLikelyNetworkFetchError(error) {
|
|
13594
13601
|
if (!(error instanceof Error)) return false;
|
|
13595
13602
|
if (error.name === "AbortError" || error.name === "TimeoutError") return false;
|
|
@@ -13597,6 +13604,9 @@ function isLikelyNetworkFetchError(error) {
|
|
|
13597
13604
|
const msg = error.message.toLowerCase().trim();
|
|
13598
13605
|
return msg === "failed to fetch" || msg === "load failed" || msg === "networkerror when attempting to fetch resource." || msg.startsWith("networkerror") || msg.includes("failed to fetch");
|
|
13599
13606
|
}
|
|
13607
|
+
function isCorrelatableTransportError(error) {
|
|
13608
|
+
return isLikelyNetworkFetchError(error) || isTimeoutError(error);
|
|
13609
|
+
}
|
|
13600
13610
|
function describeUnknownError(error) {
|
|
13601
13611
|
if (error instanceof Error) {
|
|
13602
13612
|
return {
|
|
@@ -13744,6 +13754,56 @@ function urlMatchesIgnoreList(url, ignoreUrls) {
|
|
|
13744
13754
|
}
|
|
13745
13755
|
return false;
|
|
13746
13756
|
}
|
|
13757
|
+
function normalizeNetworkOrigin(raw) {
|
|
13758
|
+
const trimmed = (raw || "").trim();
|
|
13759
|
+
if (!trimmed || trimmed === "*") return trimmed;
|
|
13760
|
+
try {
|
|
13761
|
+
return new URL(trimmed).origin;
|
|
13762
|
+
} catch {
|
|
13763
|
+
try {
|
|
13764
|
+
return new URL(`https://${trimmed}`).origin;
|
|
13765
|
+
} catch {
|
|
13766
|
+
return trimmed.replace(/\/+$/, "");
|
|
13767
|
+
}
|
|
13768
|
+
}
|
|
13769
|
+
}
|
|
13770
|
+
function resolvePageOrigin(pageOrigin) {
|
|
13771
|
+
if (pageOrigin) {
|
|
13772
|
+
try {
|
|
13773
|
+
return new URL(pageOrigin).origin;
|
|
13774
|
+
} catch {
|
|
13775
|
+
return pageOrigin.replace(/\/+$/, "");
|
|
13776
|
+
}
|
|
13777
|
+
}
|
|
13778
|
+
if (typeof location !== "undefined" && location.origin) {
|
|
13779
|
+
return location.origin;
|
|
13780
|
+
}
|
|
13781
|
+
return "";
|
|
13782
|
+
}
|
|
13783
|
+
function resolveRequestOrigin(rawUrl, pageOrigin) {
|
|
13784
|
+
const base = pageOrigin || (typeof location !== "undefined" ? location.href : void 0);
|
|
13785
|
+
try {
|
|
13786
|
+
return new URL(rawUrl, base).origin;
|
|
13787
|
+
} catch {
|
|
13788
|
+
return "";
|
|
13789
|
+
}
|
|
13790
|
+
}
|
|
13791
|
+
function isAllowedNetworkOrigin(requestOrigin, opts) {
|
|
13792
|
+
if (!requestOrigin) return false;
|
|
13793
|
+
const allow = opts.networkErrorOrigins.map(normalizeNetworkOrigin);
|
|
13794
|
+
if (allow.includes("*")) return true;
|
|
13795
|
+
const page = resolvePageOrigin(opts.pageOrigin);
|
|
13796
|
+
if (page && requestOrigin === page) return true;
|
|
13797
|
+
return allow.includes(requestOrigin);
|
|
13798
|
+
}
|
|
13799
|
+
function classifyNetworkParty(requestOrigin, pageOrigin) {
|
|
13800
|
+
const page = resolvePageOrigin(pageOrigin);
|
|
13801
|
+
if (page && requestOrigin && requestOrigin === page) return "first_party";
|
|
13802
|
+
return "third_party";
|
|
13803
|
+
}
|
|
13804
|
+
function isHttpOkStatus(status) {
|
|
13805
|
+
return status >= 200 && status < 300;
|
|
13806
|
+
}
|
|
13747
13807
|
function shouldPromoteFailedRequest(meta, opts) {
|
|
13748
13808
|
if (!opts.captureFailedRequests) return false;
|
|
13749
13809
|
if (typeof meta.status !== "number" || Number.isNaN(meta.status)) return false;
|
|
@@ -13754,6 +13814,13 @@ function shouldPromoteFailedRequest(meta, opts) {
|
|
|
13754
13814
|
opts.talariaBaseUrl
|
|
13755
13815
|
);
|
|
13756
13816
|
if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
|
|
13817
|
+
const requestOrigin = meta.origin || resolveRequestOrigin(meta.url || "", opts.pageOrigin);
|
|
13818
|
+
if (!isAllowedNetworkOrigin(requestOrigin, {
|
|
13819
|
+
networkErrorOrigins: opts.networkErrorOrigins ?? [],
|
|
13820
|
+
pageOrigin: opts.pageOrigin
|
|
13821
|
+
})) {
|
|
13822
|
+
return false;
|
|
13823
|
+
}
|
|
13757
13824
|
return true;
|
|
13758
13825
|
}
|
|
13759
13826
|
function shouldPromoteNetworkError(meta, opts) {
|
|
@@ -13767,6 +13834,13 @@ function shouldPromoteNetworkError(meta, opts) {
|
|
|
13767
13834
|
opts.talariaBaseUrl
|
|
13768
13835
|
);
|
|
13769
13836
|
if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
|
|
13837
|
+
const requestOrigin = meta.origin || resolveRequestOrigin(meta.url || "", opts.pageOrigin);
|
|
13838
|
+
if (!isAllowedNetworkOrigin(requestOrigin, {
|
|
13839
|
+
networkErrorOrigins: opts.networkErrorOrigins ?? [],
|
|
13840
|
+
pageOrigin: opts.pageOrigin
|
|
13841
|
+
})) {
|
|
13842
|
+
return false;
|
|
13843
|
+
}
|
|
13770
13844
|
return true;
|
|
13771
13845
|
}
|
|
13772
13846
|
function enrichNetworkMeta(meta) {
|
|
@@ -13777,7 +13851,8 @@ function enrichNetworkMeta(meta) {
|
|
|
13777
13851
|
failureKind: meta.ok === false ? "http" : meta.failureKind
|
|
13778
13852
|
};
|
|
13779
13853
|
}
|
|
13780
|
-
|
|
13854
|
+
const hasTransportSignal = !!meta.errorName || !!meta.errorMessage || !!meta.aborted || meta.failureKind === "abort" || meta.failureKind === "timeout" || meta.failureKind === "network";
|
|
13855
|
+
if (hasTransportSignal) {
|
|
13781
13856
|
const failureKind = meta.failureKind === "abort" || meta.failureKind === "timeout" || meta.failureKind === "network" ? meta.failureKind : classifyTransportFailure({
|
|
13782
13857
|
aborted: meta.aborted,
|
|
13783
13858
|
errorName: meta.errorName
|
|
@@ -13790,10 +13865,13 @@ function enrichNetworkMeta(meta) {
|
|
|
13790
13865
|
}
|
|
13791
13866
|
return meta;
|
|
13792
13867
|
}
|
|
13793
|
-
function networkUrlParts(rawUrl, includeQuery) {
|
|
13794
|
-
const
|
|
13868
|
+
function networkUrlParts(rawUrl, includeQuery, pageOrigin) {
|
|
13869
|
+
const baseHref = pageOrigin || (typeof location !== "undefined" ? location.href : void 0);
|
|
13870
|
+
const parts = sanitizeNetworkUrl(rawUrl, { includeQuery, baseHref });
|
|
13871
|
+
const origin = resolveRequestOrigin(rawUrl, pageOrigin);
|
|
13795
13872
|
return {
|
|
13796
13873
|
url: parts.url,
|
|
13874
|
+
origin: origin || void 0,
|
|
13797
13875
|
hostname: parts.hostname,
|
|
13798
13876
|
pathname: parts.pathname,
|
|
13799
13877
|
...parts.search ? { search: parts.search } : {}
|
|
@@ -13809,14 +13887,22 @@ function installNetworkHook(options = {}) {
|
|
|
13809
13887
|
captureNetworkErrors: options.captureNetworkErrors ?? true,
|
|
13810
13888
|
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
13811
13889
|
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
|
|
13890
|
+
networkErrorOrigins: options.networkErrorOrigins ?? [],
|
|
13891
|
+
pageOrigin: options.pageOrigin,
|
|
13812
13892
|
talariaBaseUrl: options.talariaBaseUrl
|
|
13813
13893
|
};
|
|
13814
13894
|
const includeQuery = options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false;
|
|
13815
13895
|
const handleMeta = (meta) => {
|
|
13816
13896
|
const { rawUrl, ...rest } = meta;
|
|
13897
|
+
const parts = networkUrlParts(rawUrl, includeQuery, matchOpts.pageOrigin);
|
|
13898
|
+
const party = classifyNetworkParty(
|
|
13899
|
+
parts.origin || "",
|
|
13900
|
+
matchOpts.pageOrigin
|
|
13901
|
+
);
|
|
13817
13902
|
const enriched = enrichNetworkMeta({
|
|
13818
13903
|
...rest,
|
|
13819
|
-
...
|
|
13904
|
+
...parts,
|
|
13905
|
+
party
|
|
13820
13906
|
});
|
|
13821
13907
|
try {
|
|
13822
13908
|
emitNetwork(enriched);
|
|
@@ -13836,6 +13922,7 @@ function installNetworkHook(options = {}) {
|
|
|
13836
13922
|
const rawUrl = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
|
|
13837
13923
|
try {
|
|
13838
13924
|
const response = await originalFetch(input2, init);
|
|
13925
|
+
const opaque = response.type === "opaque" || response.type === "opaqueredirect";
|
|
13839
13926
|
handleMeta({
|
|
13840
13927
|
method,
|
|
13841
13928
|
rawUrl,
|
|
@@ -13843,7 +13930,8 @@ function installNetworkHook(options = {}) {
|
|
|
13843
13930
|
transport: "fetch",
|
|
13844
13931
|
status: response.status,
|
|
13845
13932
|
durationMs: Date.now() - started,
|
|
13846
|
-
|
|
13933
|
+
// Opaque responses resolve successfully with status 0 — not a failure.
|
|
13934
|
+
ok: opaque ? true : response.ok
|
|
13847
13935
|
});
|
|
13848
13936
|
return response;
|
|
13849
13937
|
} catch (error) {
|
|
@@ -13905,7 +13993,7 @@ function installNetworkHook(options = {}) {
|
|
|
13905
13993
|
transport: "xhr",
|
|
13906
13994
|
status,
|
|
13907
13995
|
durationMs: Date.now() - (this.__talariaStarted ?? Date.now()),
|
|
13908
|
-
ok: status
|
|
13996
|
+
ok: isHttpOkStatus(status),
|
|
13909
13997
|
...transportFail ? {
|
|
13910
13998
|
failureKind,
|
|
13911
13999
|
errorName,
|
|
@@ -13914,7 +14002,7 @@ function installNetworkHook(options = {}) {
|
|
|
13914
14002
|
} : {}
|
|
13915
14003
|
});
|
|
13916
14004
|
};
|
|
13917
|
-
this.addEventListener("loadend", onDone);
|
|
14005
|
+
this.addEventListener("loadend", onDone, { once: true });
|
|
13918
14006
|
return originalSend.call(this, body);
|
|
13919
14007
|
};
|
|
13920
14008
|
}
|
|
@@ -13926,10 +14014,16 @@ function installNetworkHook(options = {}) {
|
|
|
13926
14014
|
}
|
|
13927
14015
|
|
|
13928
14016
|
// src/client.ts
|
|
14017
|
+
function networkExceptionClass(failure) {
|
|
14018
|
+
if (failure.failureKind === "http") return "HttpError";
|
|
14019
|
+
if (failure.failureKind === "timeout") return "TimeoutError";
|
|
14020
|
+
return "NetworkError";
|
|
14021
|
+
}
|
|
13929
14022
|
function networkFailureTags(failure) {
|
|
13930
14023
|
return {
|
|
13931
14024
|
"http.method": failure.method || "GET",
|
|
13932
14025
|
"network.failure_kind": failure.failureKind ?? "network",
|
|
14026
|
+
...failure.party ? { "network.party": failure.party } : {},
|
|
13933
14027
|
...failure.transport ? { "network.transport": failure.transport } : {},
|
|
13934
14028
|
...failure.errorName ? { "network.error_name": failure.errorName } : {}
|
|
13935
14029
|
};
|
|
@@ -13939,6 +14033,7 @@ function networkFailureExtra(failure) {
|
|
|
13939
14033
|
method: failure.method || "GET",
|
|
13940
14034
|
url: failure.url || "(unknown url)"
|
|
13941
14035
|
};
|
|
14036
|
+
if (failure.origin) http.origin = failure.origin;
|
|
13942
14037
|
if (failure.hostname) http.hostname = failure.hostname;
|
|
13943
14038
|
if (failure.pathname) http.pathname = failure.pathname;
|
|
13944
14039
|
if (failure.search) http.search = failure.search;
|
|
@@ -13951,9 +14046,19 @@ function networkFailureExtra(failure) {
|
|
|
13951
14046
|
};
|
|
13952
14047
|
if (failure.errorName) fail.name = failure.errorName;
|
|
13953
14048
|
if (failure.errorMessage) fail.message = failure.errorMessage;
|
|
14049
|
+
const statusCode = typeof failure.status === "number" && failure.status > 0 ? failure.status : null;
|
|
13954
14050
|
return {
|
|
13955
14051
|
http,
|
|
13956
14052
|
failure: fail,
|
|
14053
|
+
network: {
|
|
14054
|
+
party: failure.party ?? "third_party",
|
|
14055
|
+
durationMs: failure.durationMs,
|
|
14056
|
+
aborted: failure.aborted ?? false,
|
|
14057
|
+
ok: failure.ok ?? false
|
|
14058
|
+
},
|
|
14059
|
+
// Top-level for server fingerprint compatibility.
|
|
14060
|
+
status_code: statusCode,
|
|
14061
|
+
exception_class: networkExceptionClass(failure),
|
|
13957
14062
|
durationMs: failure.durationMs,
|
|
13958
14063
|
aborted: failure.aborted ?? false,
|
|
13959
14064
|
ok: failure.ok ?? false
|
|
@@ -13999,6 +14104,7 @@ function resolveOptions(options) {
|
|
|
13999
14104
|
disableDefaultIntegrations: options.disableDefaultIntegrations ?? false,
|
|
14000
14105
|
captureFailedRequests: options.captureFailedRequests ?? true,
|
|
14001
14106
|
captureNetworkErrors: options.captureNetworkErrors ?? true,
|
|
14107
|
+
networkErrorOrigins: options.networkErrorOrigins ?? [],
|
|
14002
14108
|
includeNetworkUrlQuery: options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false,
|
|
14003
14109
|
failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
|
|
14004
14110
|
failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? []
|
|
@@ -14128,6 +14234,8 @@ var TalariaClient = class {
|
|
|
14128
14234
|
installNetworkHook({
|
|
14129
14235
|
captureFailedRequests: this.options.captureFailedRequests,
|
|
14130
14236
|
captureNetworkErrors: this.options.captureNetworkErrors,
|
|
14237
|
+
networkErrorOrigins: this.options.networkErrorOrigins,
|
|
14238
|
+
pageOrigin: typeof location !== "undefined" ? location.origin : void 0,
|
|
14131
14239
|
includeNetworkUrlQuery: this.options.includeNetworkUrlQuery,
|
|
14132
14240
|
failedRequestStatusCodes: this.options.failedRequestStatusCodes,
|
|
14133
14241
|
failedRequestIgnoreUrls: this.options.failedRequestIgnoreUrls,
|
|
@@ -14221,6 +14329,7 @@ var TalariaClient = class {
|
|
|
14221
14329
|
}
|
|
14222
14330
|
const correlated = this.consumeCorrelatedNetworkFailure(err);
|
|
14223
14331
|
if (correlated?.promoted) return;
|
|
14332
|
+
if (correlated && correlated.party === "third_party") return;
|
|
14224
14333
|
const mergedContext = correlated ? mergeNetworkFailureContext(context, correlated) : context;
|
|
14225
14334
|
this.capturing = true;
|
|
14226
14335
|
try {
|
|
@@ -14703,6 +14812,13 @@ var TalariaClient = class {
|
|
|
14703
14812
|
);
|
|
14704
14813
|
}
|
|
14705
14814
|
rememberNetworkFailure(meta, opts) {
|
|
14815
|
+
if (this.options) {
|
|
14816
|
+
const ignore = buildFailedRequestIgnoreUrls(
|
|
14817
|
+
this.options.failedRequestIgnoreUrls,
|
|
14818
|
+
this.options.baseUrl
|
|
14819
|
+
);
|
|
14820
|
+
if (urlMatchesIgnoreList(meta.url || "", ignore)) return;
|
|
14821
|
+
}
|
|
14706
14822
|
const now = Date.now();
|
|
14707
14823
|
this.pruneRecentNetworkFailures(now);
|
|
14708
14824
|
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
@@ -14713,6 +14829,8 @@ var TalariaClient = class {
|
|
|
14713
14829
|
existing.errorName = meta.errorName ?? existing.errorName;
|
|
14714
14830
|
existing.aborted = meta.aborted ?? existing.aborted;
|
|
14715
14831
|
existing.failureKind = meta.failureKind ?? existing.failureKind;
|
|
14832
|
+
existing.party = meta.party ?? existing.party;
|
|
14833
|
+
existing.origin = meta.origin ?? existing.origin;
|
|
14716
14834
|
return;
|
|
14717
14835
|
}
|
|
14718
14836
|
}
|
|
@@ -14723,11 +14841,12 @@ var TalariaClient = class {
|
|
|
14723
14841
|
});
|
|
14724
14842
|
}
|
|
14725
14843
|
/**
|
|
14726
|
-
* Find a recent transport failure for a bare fetch TypeError.
|
|
14844
|
+
* Find a recent transport failure for a bare fetch TypeError / TimeoutError.
|
|
14727
14845
|
* Consumes the entry so a later duplicate path cannot reuse it.
|
|
14728
14846
|
*/
|
|
14729
14847
|
consumeCorrelatedNetworkFailure(error) {
|
|
14730
|
-
if (!
|
|
14848
|
+
if (!isCorrelatableTransportError(error)) return null;
|
|
14849
|
+
const wantKind = isTimeoutError(error) ? "timeout" : "network";
|
|
14731
14850
|
const now = Date.now();
|
|
14732
14851
|
this.pruneRecentNetworkFailures(now);
|
|
14733
14852
|
const takeAt = (index2) => {
|
|
@@ -14736,14 +14855,14 @@ var TalariaClient = class {
|
|
|
14736
14855
|
};
|
|
14737
14856
|
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14738
14857
|
const failure = this.recentNetworkFailures[i];
|
|
14739
|
-
if (failure.aborted || failure.failureKind !==
|
|
14858
|
+
if (failure.aborted || failure.failureKind !== wantKind) continue;
|
|
14740
14859
|
if (failure.errorMessage && failure.errorMessage === error.message) {
|
|
14741
14860
|
return takeAt(i);
|
|
14742
14861
|
}
|
|
14743
14862
|
}
|
|
14744
14863
|
for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
|
|
14745
14864
|
const failure = this.recentNetworkFailures[i];
|
|
14746
|
-
if (failure.aborted || failure.failureKind !==
|
|
14865
|
+
if (failure.aborted || failure.failureKind !== wantKind) continue;
|
|
14747
14866
|
return takeAt(i);
|
|
14748
14867
|
}
|
|
14749
14868
|
return null;
|