@newtalaria/browser 0.1.15 → 0.1.17

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.
@@ -1,4 +1,12 @@
1
1
  import type { FailedRequestStatusCode } from '../types.js';
2
+ /** How the request was issued. */
3
+ export type NetworkTransport = 'fetch' | 'xhr';
4
+ /**
5
+ * Failure classification for network telemetry.
6
+ * `cors` is intentionally absent — browsers do not expose CORS reliably
7
+ * (status 0 / Failed to fetch cover many causes).
8
+ */
9
+ export type NetworkFailureKind = 'http' | 'network' | 'abort' | 'timeout';
2
10
  export type Teardown = () => void;
3
11
  /**
4
12
  * Fire when the document becomes visible again (tab focus) or is restored
@@ -8,16 +16,21 @@ export type Teardown = () => void;
8
16
  export declare function installVisibilityResumeHook(onResume: () => void): Teardown;
9
17
  export interface NetworkMeta {
10
18
  method: string;
19
+ /** Sanitized request URL (origin + pathname; query stripped by default). */
11
20
  url: string;
21
+ hostname?: string;
22
+ pathname?: string;
23
+ /** Only when query capture is enabled. */
24
+ search?: string;
12
25
  status?: number;
13
26
  durationMs?: number;
14
27
  ok?: boolean;
28
+ transport?: NetworkTransport;
15
29
  /** Present on transport failures (fetch reject / XHR status 0). */
16
30
  errorName?: string;
17
31
  errorMessage?: string;
18
32
  aborted?: boolean;
19
- /** `http` = completed response with status; `network` = no usable status. */
20
- failureKind?: 'http' | 'network';
33
+ failureKind?: NetworkFailureKind;
21
34
  }
22
35
  export interface NetworkHookOptions {
23
36
  /** Still emit rrweb breadcrumbs for all requests. */
@@ -37,6 +50,13 @@ export interface NetworkHookOptions {
37
50
  captureFailedRequests?: boolean;
38
51
  /** Promote CORS/offline/DNS fetch failures (default `true`). */
39
52
  captureNetworkErrors?: boolean;
53
+ /**
54
+ * Keep URL query strings in network telemetry (after sensitive-key redaction).
55
+ * Default `false`. Prefer `captureRequestQueryParameters` (same meaning).
56
+ */
57
+ includeNetworkUrlQuery?: boolean;
58
+ /** Alias of `includeNetworkUrlQuery` — privacy-preserving default is `false`. */
59
+ captureRequestQueryParameters?: boolean;
40
60
  failedRequestStatusCodes?: FailedRequestStatusCode[];
41
61
  failedRequestIgnoreUrls?: string[];
42
62
  /** SDK base URL — requests under this host + /events|/replays are never promoted. */
@@ -57,8 +77,8 @@ export declare function shouldPromoteFailedRequest(meta: NetworkMeta, opts: {
57
77
  status: number;
58
78
  };
59
79
  /**
60
- * Promote fetch rejects / XHR status-0 failures (CORS, offline, DNS).
61
- * AbortError is never promoted.
80
+ * Promote fetch rejects / XHR status-0 failures (offline, DNS, blocked, etc.).
81
+ * Abort is never promoted; timeout may be promoted as `failureKind: timeout`.
62
82
  */
63
83
  export declare function shouldPromoteNetworkError(meta: NetworkMeta, opts: {
64
84
  captureNetworkErrors: boolean;
@@ -66,6 +86,8 @@ export declare function shouldPromoteNetworkError(meta: NetworkMeta, opts: {
66
86
  talariaBaseUrl?: string;
67
87
  }): boolean;
68
88
  export declare function enrichNetworkMeta(meta: NetworkMeta): NetworkMeta;
89
+ /** Apply privacy sanitization to a raw request URL for network meta. */
90
+ export declare function networkUrlParts(rawUrl: string, includeQuery: boolean): Pick<NetworkMeta, 'url' | 'hostname' | 'pathname' | 'search'>;
69
91
  /** Capture fetch / XHR metadata (no bodies, no auth headers); optionally promote failures. */
70
92
  export declare function installNetworkHook(options?: NetworkHookOptions): Teardown;
71
93
  //# sourceMappingURL=hooks.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/replay/hooks.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAG3D,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,QAAQ,CA4B1E;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACnE;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gEAAgE;IAChE,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,wBAAwB,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACrD,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAwCD,2EAA2E;AAC3E,wBAAgB,kBAAkB,IAAI,QAAQ,CA8B7C;AAcD,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,uBAAuB,EAAE,GAC/B,OAAO,CAUT;AAED,+DAA+D;AAC/D,wBAAgB,4BAA4B,CAC1C,uBAAuB,EAAE,MAAM,EAAE,EACjC,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,EAAE,CASV;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAM/E;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE;IACJ,qBAAqB,EAAE,OAAO,CAAC;IAC/B,wBAAwB,EAAE,uBAAuB,EAAE,CAAC;IACpD,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACA,IAAI,IAAI,WAAW,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAc1C;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE;IACJ,oBAAoB,EAAE,OAAO,CAAC;IAC9B,uBAAuB,EAAE,MAAM,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,GACA,OAAO,CAYT;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAqBhE;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,kBAAuB,GAAG,QAAQ,CAwH7E"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/replay/hooks.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,21 @@
1
- /** Redact sensitive query params from a URL for network breadcrumbs. */
1
+ /** Redact sensitive query params from a URL (keeps other query keys). */
2
2
  export declare function redactUrl(raw: string): string;
3
+ export interface SanitizedNetworkUrl {
4
+ /** Safe URL for telemetry: origin + pathname (no query/hash by default). */
5
+ url: string;
6
+ hostname: string;
7
+ pathname: string;
8
+ /** Present only when query capture is opted in (after sensitive-key redaction). */
9
+ search?: string;
10
+ }
11
+ /**
12
+ * Sanitize a request URL for network breadcrumbs / failure events.
13
+ * Default: drop query string and fragment (GA/ads URLs carry session ids).
14
+ * Opt-in `includeQuery` keeps search after `redactUrl` filtering.
15
+ */
16
+ export declare function sanitizeNetworkUrl(raw: string, opts?: {
17
+ includeQuery?: boolean;
18
+ baseHref?: string;
19
+ }): SanitizedNetworkUrl;
3
20
  export declare function defaultBlockSelector(extra?: string): string;
4
21
  //# sourceMappingURL=privacy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/replay/privacy.ts"],"names":[],"mappings":"AAEA,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY7C;AAED,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAU3D"}
1
+ {"version":3,"file":"privacy.d.ts","sourceRoot":"","sources":["../../src/replay/privacy.ts"],"names":[],"mappings":"AAEA,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY7C;AAED,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,mBAAmB,CAsCrB;AAED,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAU3D"}
@@ -1,4 +1,4 @@
1
1
  /** Bumped with package.json version for event metadata. */
2
2
  export declare const SDK_NAME = "@newtalaria/browser";
3
- export declare const SDK_VERSION = "0.1.15";
3
+ export declare const SDK_VERSION = "0.1.17";
4
4
  //# sourceMappingURL=sdk_meta.d.ts.map
@@ -100,9 +100,46 @@
100
100
  }
101
101
 
102
102
  // src/utils/browser_context.ts
103
+ var BOT_RULES = [
104
+ { name: "Baiduspider", re: /Baiduspider/i },
105
+ { name: "Googlebot", re: /Googlebot/i },
106
+ { name: "Bingbot", re: /bingbot/i },
107
+ { name: "DuckDuckBot", re: /DuckDuckBot/i },
108
+ { name: "YandexBot", re: /Yandex(Bot|Images)/i },
109
+ { name: "Applebot", re: /Applebot/i },
110
+ { name: "facebookexternalhit", re: /facebookexternalhit|Facebot/i },
111
+ { name: "Twitterbot", re: /Twitterbot/i },
112
+ { name: "LinkedInBot", re: /LinkedInBot/i },
113
+ { name: "Slackbot", re: /Slackbot/i },
114
+ { name: "Discordbot", re: /Discordbot/i },
115
+ { name: "Bytespider", re: /Bytespider/i },
116
+ { name: "PetalBot", re: /PetalBot/i },
117
+ { name: "SemrushBot", re: /SemrushBot/i },
118
+ { name: "AhrefsBot", re: /AhrefsBot/i },
119
+ { name: "DotBot", re: /DotBot/i },
120
+ { name: "GPTBot", re: /GPTBot/i },
121
+ { name: "ClaudeBot", re: /ClaudeBot|anthropic-ai/i },
122
+ { name: "Amazonbot", re: /Amazonbot/i },
123
+ { name: "Sogou", re: /Sogou/i },
124
+ // Generic last — still mark as bot without inventing a browser name.
125
+ { name: "bot", re: /\b(?:bot|crawler|spider|slurp)\b/i }
126
+ ];
127
+ function detectBot(ua) {
128
+ if (!ua) return { bot: false };
129
+ for (const rule2 of BOT_RULES) {
130
+ if (rule2.re.test(ua)) {
131
+ return {
132
+ bot: true,
133
+ botName: rule2.name === "bot" ? void 0 : rule2.name
134
+ };
135
+ }
136
+ }
137
+ return { bot: false };
138
+ }
103
139
  function parseBrowserContext(ua = typeof navigator !== "undefined" ? navigator.userAgent : "", language = typeof navigator !== "undefined" ? navigator.language : "") {
104
140
  const userAgent = ua || "";
105
141
  const lang = language || "";
142
+ const botInfo = detectBot(userAgent);
106
143
  let name = "unknown";
107
144
  let version = "";
108
145
  const rules = [
@@ -113,18 +150,20 @@
113
150
  { name: "Chrome", re: /(?:Chrome|CriOS)\/([\d.]+)/ },
114
151
  { name: "Safari", re: /Version\/([\d.]+).*Safari/ }
115
152
  ];
116
- for (const rule2 of rules) {
117
- const m = userAgent.match(rule2.re);
118
- if (m) {
119
- name = rule2.name;
120
- version = m[1] ?? "";
121
- break;
153
+ if (!botInfo.bot) {
154
+ for (const rule2 of rules) {
155
+ const m = userAgent.match(rule2.re);
156
+ if (m) {
157
+ name = rule2.name;
158
+ version = m[1] ?? "";
159
+ break;
160
+ }
161
+ }
162
+ if (name === "unknown" && /iPhone|iPad|iPod/.test(userAgent) && /AppleWebKit/.test(userAgent)) {
163
+ name = "Safari";
164
+ const m = userAgent.match(/OS ([\d_]+)/);
165
+ if (m?.[1]) version = m[1].replace(/_/g, ".");
122
166
  }
123
- }
124
- if (name === "unknown" && /iPhone|iPad|iPod/.test(userAgent) && /AppleWebKit/.test(userAgent)) {
125
- name = "Safari";
126
- const m = userAgent.match(/OS ([\d_]+)/);
127
- if (m?.[1]) version = m[1].replace(/_/g, ".");
128
167
  }
129
168
  let os = "unknown";
130
169
  let osVersion = "";
@@ -170,7 +209,9 @@
170
209
  osVersion,
171
210
  device,
172
211
  language: lang,
173
- userAgent
212
+ userAgent,
213
+ bot: botInfo.bot,
214
+ ...botInfo.botName ? { botName: botInfo.botName } : {}
174
215
  };
175
216
  }
176
217
  function browserContextTags(ctx) {
@@ -179,11 +220,16 @@
179
220
  "browser.version": ctx.version || "unknown",
180
221
  "os.name": ctx.os,
181
222
  "os.version": ctx.osVersion || "unknown",
182
- device: ctx.device
223
+ device: ctx.device,
224
+ ...ctx.bot ? {
225
+ bot: "true",
226
+ ...ctx.botName ? { "bot.name": ctx.botName } : {}
227
+ } : {}
183
228
  };
184
229
  }
185
230
  async function collectBrowserContext() {
186
231
  const base = parseBrowserContext();
232
+ if (base.bot) return base;
187
233
  const nav = typeof navigator !== "undefined" ? navigator : void 0;
188
234
  const uad = nav?.userAgentData;
189
235
  if (!uad) return base;
@@ -228,7 +274,7 @@
228
274
 
229
275
  // src/sdk_meta.ts
230
276
  var SDK_NAME = "@newtalaria/browser";
231
- var SDK_VERSION = "0.1.15";
277
+ var SDK_VERSION = "0.1.17";
232
278
 
233
279
  // src/transport/serverpod.ts
234
280
  var ServerpodTransport = class {
@@ -13452,6 +13498,40 @@
13452
13498
  return raw.replace(SENSITIVE_QUERY, (_m, name) => `${name}=[Filtered]`);
13453
13499
  }
13454
13500
  }
13501
+ function sanitizeNetworkUrl(raw, opts) {
13502
+ const baseHref = opts?.baseHref ?? (typeof location !== "undefined" ? location.href : void 0);
13503
+ try {
13504
+ const parsed = new URL(raw, baseHref);
13505
+ const hostname = parsed.hostname;
13506
+ const pathname = parsed.pathname || "/";
13507
+ const originPath = `${parsed.origin}${pathname}`;
13508
+ if (!opts?.includeQuery) {
13509
+ return { url: originPath, hostname, pathname };
13510
+ }
13511
+ const redacted = new URL(redactUrl(parsed.toString()));
13512
+ const search = redacted.search || void 0;
13513
+ return {
13514
+ url: search ? `${originPath}${search}` : originPath,
13515
+ hostname,
13516
+ pathname,
13517
+ ...search ? { search } : {}
13518
+ };
13519
+ } catch {
13520
+ const stripped = (raw.split(/[?#]/)[0] ?? raw).trim() || raw;
13521
+ if (!opts?.includeQuery) {
13522
+ return { url: stripped, hostname: "", pathname: stripped };
13523
+ }
13524
+ const redacted = redactUrl(raw);
13525
+ const qIndex = redacted.indexOf("?");
13526
+ const search = qIndex >= 0 ? redacted.slice(qIndex) : void 0;
13527
+ return {
13528
+ url: redacted.split("#")[0] || redacted,
13529
+ hostname: "",
13530
+ pathname: stripped,
13531
+ ...search ? { search } : {}
13532
+ };
13533
+ }
13534
+ }
13455
13535
  function defaultBlockSelector(extra) {
13456
13536
  const parts = [
13457
13537
  "[data-talaria-mask]",
@@ -13514,7 +13594,7 @@
13514
13594
  }
13515
13595
  function isLikelyNetworkFetchError(error) {
13516
13596
  if (!(error instanceof Error)) return false;
13517
- if (error.name === "AbortError") return false;
13597
+ if (error.name === "AbortError" || error.name === "TimeoutError") return false;
13518
13598
  if (error.name === "NetworkError") return true;
13519
13599
  const msg = error.message.toLowerCase().trim();
13520
13600
  return msg === "failed to fetch" || msg === "load failed" || msg === "networkerror when attempting to fetch resource." || msg.startsWith("networkerror") || msg.includes("failed to fetch");
@@ -13524,15 +13604,22 @@
13524
13604
  return {
13525
13605
  errorName: error.name || "Error",
13526
13606
  errorMessage: (error.message || "").slice(0, 500),
13527
- aborted: error.name === "AbortError"
13607
+ aborted: error.name === "AbortError",
13608
+ timedOut: error.name === "TimeoutError"
13528
13609
  };
13529
13610
  }
13530
13611
  return {
13531
13612
  errorName: "Error",
13532
13613
  errorMessage: String(error).slice(0, 500),
13533
- aborted: false
13614
+ aborted: false,
13615
+ timedOut: false
13534
13616
  };
13535
13617
  }
13618
+ function classifyTransportFailure(opts) {
13619
+ if (opts.aborted || opts.errorName === "AbortError") return "abort";
13620
+ if (opts.timedOut || opts.errorName === "TimeoutError") return "timeout";
13621
+ return "network";
13622
+ }
13536
13623
 
13537
13624
  // src/replay/hooks.ts
13538
13625
  function installVisibilityResumeHook(onResume) {
@@ -13627,7 +13714,6 @@
13627
13714
  try {
13628
13715
  record.addCustomEvent("talaria-network", {
13629
13716
  ...meta,
13630
- url: redactUrl(meta.url),
13631
13717
  timestamp: Date.now()
13632
13718
  });
13633
13719
  } catch {
@@ -13674,8 +13760,10 @@
13674
13760
  }
13675
13761
  function shouldPromoteNetworkError(meta, opts) {
13676
13762
  if (!opts.captureNetworkErrors) return false;
13677
- if (meta.aborted) return false;
13678
- if (meta.failureKind !== "network") return false;
13763
+ if (meta.aborted || meta.failureKind === "abort") return false;
13764
+ if (meta.failureKind !== "network" && meta.failureKind !== "timeout") {
13765
+ return false;
13766
+ }
13679
13767
  const ignore = buildFailedRequestIgnoreUrls(
13680
13768
  opts.failedRequestIgnoreUrls,
13681
13769
  opts.talariaBaseUrl
@@ -13691,14 +13779,28 @@
13691
13779
  failureKind: meta.ok === false ? "http" : meta.failureKind
13692
13780
  };
13693
13781
  }
13694
- if (meta.ok === false || meta.errorName || meta.errorMessage) {
13782
+ if (meta.ok === false || meta.errorName || meta.errorMessage || meta.aborted) {
13783
+ const failureKind = meta.failureKind === "abort" || meta.failureKind === "timeout" || meta.failureKind === "network" ? meta.failureKind : classifyTransportFailure({
13784
+ aborted: meta.aborted,
13785
+ errorName: meta.errorName
13786
+ });
13695
13787
  return {
13696
13788
  ...meta,
13697
- failureKind: "network"
13789
+ failureKind,
13790
+ aborted: meta.aborted ?? failureKind === "abort"
13698
13791
  };
13699
13792
  }
13700
13793
  return meta;
13701
13794
  }
13795
+ function networkUrlParts(rawUrl, includeQuery) {
13796
+ const parts = sanitizeNetworkUrl(rawUrl, { includeQuery });
13797
+ return {
13798
+ url: parts.url,
13799
+ hostname: parts.hostname,
13800
+ pathname: parts.pathname,
13801
+ ...parts.search ? { search: parts.search } : {}
13802
+ };
13803
+ }
13702
13804
  function installNetworkHook(options = {}) {
13703
13805
  const originalFetch = typeof fetch === "function" ? fetch.bind(globalThis) : null;
13704
13806
  const XHR = typeof XMLHttpRequest !== "undefined" ? XMLHttpRequest : null;
@@ -13711,8 +13813,13 @@
13711
13813
  failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
13712
13814
  talariaBaseUrl: options.talariaBaseUrl
13713
13815
  };
13816
+ const includeQuery = options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false;
13714
13817
  const handleMeta = (meta) => {
13715
- const enriched = enrichNetworkMeta(meta);
13818
+ const { rawUrl, ...rest } = meta;
13819
+ const enriched = enrichNetworkMeta({
13820
+ ...rest,
13821
+ ...networkUrlParts(rawUrl, includeQuery)
13822
+ });
13716
13823
  try {
13717
13824
  emitNetwork(enriched);
13718
13825
  options.onNetwork?.(enriched);
@@ -13728,12 +13835,14 @@
13728
13835
  globalThis.fetch = async (input2, init) => {
13729
13836
  const started = Date.now();
13730
13837
  const method = (init?.method ?? (input2 instanceof Request ? input2.method : "GET")).toUpperCase();
13731
- const url = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
13838
+ const rawUrl = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
13732
13839
  try {
13733
13840
  const response = await originalFetch(input2, init);
13734
13841
  handleMeta({
13735
13842
  method,
13736
- url,
13843
+ rawUrl,
13844
+ url: rawUrl,
13845
+ transport: "fetch",
13737
13846
  status: response.status,
13738
13847
  durationMs: Date.now() - started,
13739
13848
  ok: response.ok
@@ -13741,15 +13850,18 @@
13741
13850
  return response;
13742
13851
  } catch (error) {
13743
13852
  const described = describeUnknownError(error);
13853
+ const failureKind = classifyTransportFailure(described);
13744
13854
  handleMeta({
13745
13855
  method,
13746
- url,
13856
+ rawUrl,
13857
+ url: rawUrl,
13858
+ transport: "fetch",
13747
13859
  durationMs: Date.now() - started,
13748
13860
  ok: false,
13749
13861
  errorName: described.errorName,
13750
13862
  errorMessage: described.errorMessage,
13751
13863
  aborted: described.aborted,
13752
- failureKind: "network"
13864
+ failureKind
13753
13865
  });
13754
13866
  throw error;
13755
13867
  }
@@ -13763,19 +13875,44 @@
13763
13875
  };
13764
13876
  XHR.prototype.send = function(body) {
13765
13877
  this.__talariaStarted = Date.now();
13878
+ this.__talariaTimedOut = false;
13879
+ this.__talariaAborted = false;
13880
+ this.addEventListener(
13881
+ "timeout",
13882
+ () => {
13883
+ this.__talariaTimedOut = true;
13884
+ },
13885
+ { once: true }
13886
+ );
13887
+ this.addEventListener(
13888
+ "abort",
13889
+ () => {
13890
+ this.__talariaAborted = true;
13891
+ },
13892
+ { once: true }
13893
+ );
13766
13894
  const onDone = () => {
13767
13895
  const status = this.status;
13768
- const networkFail = status === 0;
13896
+ const transportFail = status === 0;
13897
+ const rawUrl = this.__talariaUrl ?? "";
13898
+ const aborted = !!this.__talariaAborted;
13899
+ const timedOut = !!this.__talariaTimedOut;
13900
+ const failureKind = transportFail ? classifyTransportFailure({ aborted, timedOut }) : void 0;
13901
+ const errorName = transportFail ? failureKind === "abort" ? "AbortError" : failureKind === "timeout" ? "TimeoutError" : "NetworkError" : void 0;
13902
+ const errorMessage = transportFail ? failureKind === "abort" ? "XMLHttpRequest aborted" : failureKind === "timeout" ? "XMLHttpRequest timed out" : "XMLHttpRequest failed (status 0)" : void 0;
13769
13903
  handleMeta({
13770
13904
  method: this.__talariaMethod ?? "GET",
13771
- url: this.__talariaUrl ?? "",
13905
+ rawUrl,
13906
+ url: rawUrl,
13907
+ transport: "xhr",
13772
13908
  status,
13773
13909
  durationMs: Date.now() - (this.__talariaStarted ?? Date.now()),
13774
13910
  ok: status >= 200 && status < 400,
13775
- ...networkFail ? {
13776
- failureKind: "network",
13777
- errorName: "NetworkError",
13778
- errorMessage: "XMLHttpRequest failed (status 0)"
13911
+ ...transportFail ? {
13912
+ failureKind,
13913
+ errorName,
13914
+ errorMessage,
13915
+ aborted
13779
13916
  } : {}
13780
13917
  });
13781
13918
  };
@@ -13791,28 +13928,49 @@
13791
13928
  }
13792
13929
 
13793
13930
  // src/client.ts
13931
+ function networkFailureTags(failure) {
13932
+ return {
13933
+ "http.method": failure.method || "GET",
13934
+ "network.failure_kind": failure.failureKind ?? "network",
13935
+ ...failure.transport ? { "network.transport": failure.transport } : {},
13936
+ ...failure.errorName ? { "network.error_name": failure.errorName } : {}
13937
+ };
13938
+ }
13939
+ function networkFailureExtra(failure) {
13940
+ const http = {
13941
+ method: failure.method || "GET",
13942
+ url: failure.url || "(unknown url)"
13943
+ };
13944
+ if (failure.hostname) http.hostname = failure.hostname;
13945
+ if (failure.pathname) http.pathname = failure.pathname;
13946
+ if (failure.search) http.search = failure.search;
13947
+ if (typeof failure.status === "number" && failure.status > 0) {
13948
+ http.status = failure.status;
13949
+ }
13950
+ if (failure.transport) http.transport = failure.transport;
13951
+ const fail = {
13952
+ kind: failure.failureKind ?? "network"
13953
+ };
13954
+ if (failure.errorName) fail.name = failure.errorName;
13955
+ if (failure.errorMessage) fail.message = failure.errorMessage;
13956
+ return {
13957
+ http,
13958
+ failure: fail,
13959
+ durationMs: failure.durationMs,
13960
+ aborted: failure.aborted ?? false,
13961
+ ok: failure.ok ?? false
13962
+ };
13963
+ }
13794
13964
  function mergeNetworkFailureContext(context, failure) {
13795
- const url = redactUrl(failure.url || "(unknown url)");
13796
13965
  return {
13797
13966
  ...context,
13798
13967
  tags: {
13799
13968
  ...context?.tags ?? {},
13800
- "http.method": failure.method || "GET",
13801
- "network.failure_kind": "network",
13802
- ...failure.errorName ? { "network.error_name": failure.errorName } : {}
13969
+ ...networkFailureTags(failure)
13803
13970
  },
13804
13971
  extra: {
13805
13972
  ...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
- }
13973
+ ...networkFailureExtra(failure)
13816
13974
  }
13817
13975
  };
13818
13976
  }
@@ -13843,6 +14001,7 @@
13843
14001
  disableDefaultIntegrations: options.disableDefaultIntegrations ?? false,
13844
14002
  captureFailedRequests: options.captureFailedRequests ?? true,
13845
14003
  captureNetworkErrors: options.captureNetworkErrors ?? true,
14004
+ includeNetworkUrlQuery: options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false,
13846
14005
  failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
13847
14006
  failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? []
13848
14007
  };
@@ -13971,59 +14130,48 @@
13971
14130
  installNetworkHook({
13972
14131
  captureFailedRequests: this.options.captureFailedRequests,
13973
14132
  captureNetworkErrors: this.options.captureNetworkErrors,
14133
+ includeNetworkUrlQuery: this.options.includeNetworkUrlQuery,
13974
14134
  failedRequestStatusCodes: this.options.failedRequestStatusCodes,
13975
14135
  failedRequestIgnoreUrls: this.options.failedRequestIgnoreUrls,
13976
14136
  talariaBaseUrl: this.options.baseUrl,
13977
14137
  onNetwork: (meta) => {
13978
- if (meta.failureKind === "network" && !meta.aborted) {
14138
+ if ((meta.failureKind === "network" || meta.failureKind === "timeout") && !meta.aborted) {
13979
14139
  this.rememberNetworkFailure(meta, { promoted: false });
13980
14140
  }
13981
14141
  },
13982
14142
  onFailedRequest: (meta) => {
13983
14143
  const status = meta.status;
13984
14144
  const method = meta.method || "GET";
13985
- const url = redactUrl(meta.url || "(unknown url)");
14145
+ const url = meta.url || "(unknown url)";
14146
+ const enriched = {
14147
+ ...meta,
14148
+ failureKind: "http",
14149
+ aborted: false
14150
+ };
13986
14151
  void this.captureMessage(
13987
14152
  `HTTP ${status}: ${method} ${url}`,
13988
14153
  status >= 500 ? "error" : "warning",
13989
14154
  {
13990
14155
  tags: {
13991
- "http.status_code": String(status),
13992
- "http.method": method,
13993
- "network.failure_kind": "http"
14156
+ ...networkFailureTags(enriched),
14157
+ "http.status_code": String(status)
13994
14158
  },
13995
- extra: {
13996
- url,
13997
- durationMs: meta.durationMs,
13998
- ok: meta.ok,
13999
- failureKind: "http"
14000
- }
14159
+ extra: networkFailureExtra(enriched)
14001
14160
  }
14002
14161
  );
14003
14162
  },
14004
14163
  onNetworkError: (meta) => {
14005
14164
  this.rememberNetworkFailure(meta, { promoted: true });
14006
14165
  const method = meta.method || "GET";
14007
- const url = redactUrl(meta.url || "(unknown url)");
14166
+ const url = meta.url || "(unknown url)";
14008
14167
  const errLabel = meta.errorName && meta.errorMessage ? `${meta.errorName}: ${meta.errorMessage}` : meta.errorMessage || meta.errorName || "Failed to fetch";
14168
+ const prefix = meta.failureKind === "timeout" ? "Timeout error" : "Network error";
14009
14169
  void this.captureMessage(
14010
- `Network error: ${method} ${url} \u2014 ${errLabel}`,
14170
+ `${prefix}: ${method} ${url} \u2014 ${errLabel}`,
14011
14171
  "error",
14012
14172
  {
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
14026
- }
14173
+ tags: networkFailureTags(meta),
14174
+ extra: networkFailureExtra(meta)
14027
14175
  }
14028
14176
  );
14029
14177
  }
@@ -14202,6 +14350,7 @@
14202
14350
  throw new Error("@newtalaria/browser: call Talaria.init() first");
14203
14351
  }
14204
14352
  if (this.ingestDisabled) return;
14353
+ const occurredAt = /* @__PURE__ */ new Date();
14205
14354
  const isErrorLike = args.level === "error" || args.level === "fatal";
14206
14355
  let errorClipOutcome = null;
14207
14356
  let attemptedErrorClip = false;
@@ -14259,6 +14408,7 @@
14259
14408
  errorClipOutcome = { status: "failed", reason: "buffer_empty" };
14260
14409
  }
14261
14410
  }
14411
+ const queuedMs = Math.max(0, Date.now() - occurredAt.getTime());
14262
14412
  const tags = applyReplayCaptureTags(
14263
14413
  {
14264
14414
  ...this.browserContext ? browserContextTags(this.browserContext) : {},
@@ -14277,10 +14427,17 @@
14277
14427
  osVersion: this.browserContext.osVersion,
14278
14428
  device: this.browserContext.device,
14279
14429
  language: this.browserContext.language,
14280
- userAgent: this.browserContext.userAgent
14430
+ userAgent: this.browserContext.userAgent,
14431
+ bot: this.browserContext.bot,
14432
+ ...this.browserContext.botName ? { botName: this.browserContext.botName } : {}
14281
14433
  }
14282
14434
  } : {},
14283
- sdk: { name: SDK_NAME, version: SDK_VERSION },
14435
+ sdk: {
14436
+ name: SDK_NAME,
14437
+ version: SDK_VERSION,
14438
+ // Time spent in capture() before ingest (mostly replay flush).
14439
+ ...queuedMs >= 50 ? { queuedMs } : {}
14440
+ },
14284
14441
  ...args.context?.extra
14285
14442
  },
14286
14443
  errorClipOutcome
@@ -14300,7 +14457,7 @@
14300
14457
  url: typeof location !== "undefined" ? location.href : void 0,
14301
14458
  tags: Object.keys(tags).length ? tags : void 0,
14302
14459
  extraJson: extra ? JSON.stringify(extra) : void 0,
14303
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14460
+ timestamp: occurredAt.toISOString(),
14304
14461
  keepalive: args.keepalive
14305
14462
  });
14306
14463
  if (replayId && replayId === this.linkableReplayId) {