@newtalaria/browser 0.1.17 → 0.1.19

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.
@@ -272,9 +272,253 @@
272
272
  return base;
273
273
  }
274
274
 
275
+ // src/utils/tags.ts
276
+ var MAX_TAGS_PER_EVENT = 20;
277
+ var MAX_TAG_KEY_LENGTH = 64;
278
+ var MAX_TAG_VALUE_LENGTH = 128;
279
+ var MAX_TAG_TOTAL_UTF8_BYTES = 2048;
280
+ var KEY_PATTERN = /^[a-z0-9_.-]+$/;
281
+ var HIGH_CARDINALITY_TAG_KEYS = /* @__PURE__ */ new Set([
282
+ "user_id",
283
+ "userid",
284
+ "request_id",
285
+ "requestid",
286
+ "business_id",
287
+ "order_id",
288
+ "email",
289
+ "url",
290
+ "uuid",
291
+ "session_id",
292
+ "trace_id",
293
+ "span_id"
294
+ ]);
295
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
296
+ var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
297
+ var LONG_NUMERIC_RE = /^\d{8,}$/;
298
+ function utf8Bytes(s) {
299
+ if (typeof TextEncoder !== "undefined") {
300
+ return new TextEncoder().encode(s).length;
301
+ }
302
+ return unescape(encodeURIComponent(s)).length;
303
+ }
304
+ function normalizeKey(raw) {
305
+ const key = raw.trim().toLowerCase();
306
+ if (!key || key.length > MAX_TAG_KEY_LENGTH) return null;
307
+ if (!KEY_PATTERN.test(key)) return null;
308
+ return key;
309
+ }
310
+ function normalizeValue(raw) {
311
+ if (raw == null) return null;
312
+ const value = String(raw).trim();
313
+ if (!value) return null;
314
+ return value.length <= MAX_TAG_VALUE_LENGTH ? value : value.slice(0, MAX_TAG_VALUE_LENGTH);
315
+ }
316
+ function normalizeTags(tags) {
317
+ if (!tags) return {};
318
+ const result2 = {};
319
+ let totalBytes = 0;
320
+ for (const [rawKey, rawValue] of Object.entries(tags)) {
321
+ if (Object.keys(result2).length >= MAX_TAGS_PER_EVENT) break;
322
+ const key = normalizeKey(rawKey);
323
+ if (!key) continue;
324
+ const value = normalizeValue(rawValue);
325
+ if (value == null) continue;
326
+ if (result2[key] != null) {
327
+ totalBytes -= utf8Bytes(key) + utf8Bytes(result2[key]);
328
+ }
329
+ const entryBytes = utf8Bytes(key) + utf8Bytes(value);
330
+ if (totalBytes + entryBytes > MAX_TAG_TOTAL_UTF8_BYTES) continue;
331
+ result2[key] = value;
332
+ totalBytes += entryBytes;
333
+ }
334
+ return result2;
335
+ }
336
+ function mergeTags(...parts) {
337
+ const merged = {};
338
+ for (const part of parts) {
339
+ if (!part) continue;
340
+ Object.assign(merged, part);
341
+ }
342
+ return normalizeTags(merged);
343
+ }
344
+ function looksHighCardinalityValue(value) {
345
+ return UUID_RE.test(value) || EMAIL_RE.test(value) || LONG_NUMERIC_RE.test(value);
346
+ }
347
+ function warnSuspiciousTags(tags, environment) {
348
+ if (!environment || environment === "production") return;
349
+ if (typeof console === "undefined" || typeof console.warn !== "function") {
350
+ return;
351
+ }
352
+ for (const [key, value] of Object.entries(tags)) {
353
+ if (HIGH_CARDINALITY_TAG_KEYS.has(key)) {
354
+ console.warn(
355
+ `@newtalaria/browser: Tag "${key}" appears to have high cardinality. Consider moving it to context/extra.`
356
+ );
357
+ continue;
358
+ }
359
+ if (looksHighCardinalityValue(value)) {
360
+ console.warn(
361
+ `@newtalaria/browser: Tag "${key}" value looks high-cardinality. Consider moving it to context/extra.`
362
+ );
363
+ }
364
+ }
365
+ }
366
+
367
+ // src/utils/stacktrace.ts
368
+ var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/;
369
+ var V8_FRAME_BARE = /^\s*at\s+(.+)\s*$/;
370
+ var EXTENSION_URL2 = /(?:chrome|moz|safari|safari-web|ms-browser)-extension:\/\//i;
371
+ function parseStackTrace(stack, inAppOptions) {
372
+ if (!stack || !stack.trim()) return void 0;
373
+ const parsed = [];
374
+ for (const line of stack.split("\n")) {
375
+ const frame = parseStackLine(line, inAppOptions);
376
+ if (frame) parsed.push(frame);
377
+ }
378
+ if (parsed.length === 0) return void 0;
379
+ parsed.reverse();
380
+ return { frames: parsed };
381
+ }
382
+ function parseStackLine(line, inAppOptions) {
383
+ if (!/^\s*at\s+/.test(line)) return null;
384
+ const v8 = V8_FRAME.exec(line);
385
+ if (v8) {
386
+ const functionName = cleanFunction(v8[1]);
387
+ const absPath = stripQuery(v8[2] ?? "");
388
+ const lineno = Number.parseInt(v8[3] ?? "", 10);
389
+ const colno = Number.parseInt(v8[4] ?? "", 10);
390
+ return {
391
+ functionName,
392
+ absPath: absPath || void 0,
393
+ filename: basename(absPath),
394
+ lineno: Number.isFinite(lineno) ? lineno : void 0,
395
+ colno: Number.isFinite(colno) ? colno : void 0,
396
+ inApp: isInAppFrame(absPath, inAppOptions),
397
+ platform: "javascript"
398
+ };
399
+ }
400
+ const bare = V8_FRAME_BARE.exec(line);
401
+ if (bare) {
402
+ const functionName = cleanFunction(bare[1]);
403
+ if (!functionName) return null;
404
+ return {
405
+ functionName,
406
+ inApp: false,
407
+ platform: "javascript"
408
+ };
409
+ }
410
+ return null;
411
+ }
412
+ function applySourceLocation(stacktrace, source, inAppOptions) {
413
+ if (!source) return stacktrace;
414
+ const filename = source.filename?.trim() || void 0;
415
+ const lineno = typeof source.lineno === "number" && source.lineno > 0 ? source.lineno : void 0;
416
+ const colno = typeof source.colno === "number" && source.colno >= 0 ? source.colno : void 0;
417
+ if (!filename && lineno == null && colno == null) return stacktrace;
418
+ if (!stacktrace || stacktrace.frames.length === 0) {
419
+ if (!filename && lineno == null) return stacktrace;
420
+ const absPath = filename;
421
+ return {
422
+ frames: [
423
+ {
424
+ filename: absPath ? basename(absPath) : void 0,
425
+ absPath,
426
+ lineno,
427
+ colno,
428
+ inApp: absPath ? isInAppFrame(absPath, inAppOptions) : true,
429
+ platform: "javascript"
430
+ }
431
+ ]
432
+ };
433
+ }
434
+ const frames = stacktrace.frames.slice();
435
+ const top = { ...frames[frames.length - 1] };
436
+ if (filename) {
437
+ top.absPath = filename;
438
+ top.filename = basename(filename);
439
+ top.inApp = isInAppFrame(filename, inAppOptions);
440
+ }
441
+ if (lineno != null) top.lineno = lineno;
442
+ if (colno != null) top.colno = colno;
443
+ frames[frames.length - 1] = top;
444
+ return { ...stacktrace, frames };
445
+ }
446
+ function isInAppFrame(path, options) {
447
+ const lower = path.toLowerCase();
448
+ if (!lower) return true;
449
+ if (lower.includes("node_modules")) return false;
450
+ if (EXTENSION_URL2.test(path) || lower.includes("webkit-masked-url://")) {
451
+ return false;
452
+ }
453
+ if (lower.includes("@newtalaria/browser")) return false;
454
+ if (matchesUrlList(path, options?.denyUrls)) return false;
455
+ if (matchesUrlList(path, options?.allowUrls)) return true;
456
+ const frameOrigin = tryParseOrigin(path);
457
+ if (frameOrigin != null) {
458
+ const pageOrigin = normalizeOrigin(options?.pageOrigin);
459
+ if (pageOrigin && frameOrigin === pageOrigin) return true;
460
+ const extra = options?.inAppOrigins ?? [];
461
+ for (const origin of extra) {
462
+ if (normalizeOrigin(origin) === frameOrigin) return true;
463
+ }
464
+ return false;
465
+ }
466
+ return true;
467
+ }
468
+ function matchesUrlList(path, patterns) {
469
+ if (!patterns || patterns.length === 0) return false;
470
+ for (const pattern of patterns) {
471
+ if (typeof pattern === "string") {
472
+ if (pattern && path.includes(pattern)) return true;
473
+ } else if (pattern.test(path)) {
474
+ return true;
475
+ }
476
+ }
477
+ return false;
478
+ }
479
+ function tryParseOrigin(path) {
480
+ if (!/^https?:\/\//i.test(path)) return null;
481
+ try {
482
+ return new URL(path).origin;
483
+ } catch {
484
+ return null;
485
+ }
486
+ }
487
+ function normalizeOrigin(origin) {
488
+ if (!origin || !origin.trim()) return null;
489
+ const trimmed = origin.trim();
490
+ try {
491
+ if (/^https?:\/\//i.test(trimmed)) {
492
+ return new URL(trimmed).origin;
493
+ }
494
+ return trimmed;
495
+ } catch {
496
+ return trimmed;
497
+ }
498
+ }
499
+ function resolvePageOrigin(originOrUrl) {
500
+ return normalizeOrigin(originOrUrl) ?? void 0;
501
+ }
502
+ function cleanFunction(raw) {
503
+ if (raw == null) return void 0;
504
+ const value = raw.trim();
505
+ return value.length === 0 ? void 0 : value;
506
+ }
507
+ function stripQuery(path) {
508
+ const q = path.indexOf("?");
509
+ return q >= 0 ? path.slice(0, q) : path;
510
+ }
511
+ function basename(path) {
512
+ if (!path) return void 0;
513
+ const normalized = stripQuery(path);
514
+ const parts = normalized.split(/[/\\]/);
515
+ const last = parts[parts.length - 1];
516
+ return last || normalized;
517
+ }
518
+
275
519
  // src/sdk_meta.ts
276
520
  var SDK_NAME = "@newtalaria/browser";
277
- var SDK_VERSION = "0.1.17";
521
+ var SDK_VERSION = "0.1.18";
278
522
 
279
523
  // src/transport/serverpod.ts
280
524
  var ServerpodTransport = class {
@@ -332,6 +576,9 @@
332
576
  if (params.eventType) input2.eventType = params.eventType;
333
577
  if (params.title) input2.title = params.title;
334
578
  if (params.stackTrace) input2.stackTrace = params.stackTrace;
579
+ if (params.exception) input2.exception = serializeException(params.exception);
580
+ if (params.debugMeta) input2.debugMeta = serializeDebugMeta(params.debugMeta);
581
+ if (params.platform) input2.platform = params.platform;
335
582
  if (params.release) input2.release = params.release;
336
583
  if (params.userId) input2.userId = params.userId;
337
584
  if (params.sessionId) input2.sessionId = params.sessionId;
@@ -348,6 +595,89 @@
348
595
  { keepalive: params.keepalive }
349
596
  );
350
597
  }
598
+ function serializeException(data) {
599
+ return {
600
+ __className__: "ExceptionDataDto",
601
+ values: data.values.map(serializeExceptionValue)
602
+ };
603
+ }
604
+ function serializeExceptionValue(value) {
605
+ const out = {
606
+ __className__: "ExceptionValueDto"
607
+ };
608
+ if (value.type != null) out.type = value.type;
609
+ if (value.value != null) out.value = value.value;
610
+ if (value.module != null) out.module = value.module;
611
+ if (value.threadId != null) out.threadId = value.threadId;
612
+ if (value.code != null) out.code = value.code;
613
+ if (value.mechanism) out.mechanism = serializeMechanism(value.mechanism);
614
+ if (value.stacktrace) out.stacktrace = serializeStackTrace(value.stacktrace);
615
+ return out;
616
+ }
617
+ function serializeMechanism(mechanism) {
618
+ const out = {
619
+ __className__: "ExceptionMechanismDto",
620
+ type: mechanism.type
621
+ };
622
+ if (mechanism.handled != null) out.handled = mechanism.handled;
623
+ if (mechanism.synthetic != null) out.synthetic = mechanism.synthetic;
624
+ return out;
625
+ }
626
+ function serializeStackTrace(stack) {
627
+ const out = {
628
+ __className__: "StackTraceDto",
629
+ frames: stack.frames.map(serializeStackFrame)
630
+ };
631
+ if (stack.registers) out.registers = stack.registers;
632
+ return out;
633
+ }
634
+ function serializeStackFrame(frame) {
635
+ const out = {
636
+ __className__: "StackFrameDto"
637
+ };
638
+ if (frame.filename != null) out.filename = frame.filename;
639
+ if (frame.absPath != null) out.absPath = frame.absPath;
640
+ if (frame.functionName != null) out.functionName = frame.functionName;
641
+ if (frame.rawFunction != null) out.rawFunction = frame.rawFunction;
642
+ if (frame.module != null) out.module = frame.module;
643
+ if (frame.package != null) out.package = frame.package;
644
+ if (frame.platform != null) out.platform = frame.platform;
645
+ if (frame.lineno != null) out.lineno = frame.lineno;
646
+ if (frame.colno != null) out.colno = frame.colno;
647
+ if (frame.inApp != null) out.inApp = frame.inApp;
648
+ if (frame.instructionAddr != null) out.instructionAddr = frame.instructionAddr;
649
+ if (frame.symbolAddr != null) out.symbolAddr = frame.symbolAddr;
650
+ if (frame.imageAddr != null) out.imageAddr = frame.imageAddr;
651
+ if (frame.addrMode != null) out.addrMode = frame.addrMode;
652
+ if (frame.contextLine != null) out.contextLine = frame.contextLine;
653
+ if (frame.preContext != null) out.preContext = frame.preContext;
654
+ if (frame.postContext != null) out.postContext = frame.postContext;
655
+ if (frame.vars != null) out.vars = frame.vars;
656
+ if (frame.stackStart != null) out.stackStart = frame.stackStart;
657
+ return out;
658
+ }
659
+ function serializeDebugMeta(meta) {
660
+ const out = {
661
+ __className__: "DebugMetaDto"
662
+ };
663
+ if (meta.images) {
664
+ out.images = meta.images.map((image) => {
665
+ const img = {
666
+ __className__: "DebugImageDto"
667
+ };
668
+ if (image.type != null) img.type = image.type;
669
+ if (image.imageAddr != null) img.imageAddr = image.imageAddr;
670
+ if (image.imageSize != null) img.imageSize = image.imageSize;
671
+ if (image.debugId != null) img.debugId = image.debugId;
672
+ if (image.debugFile != null) img.debugFile = image.debugFile;
673
+ if (image.codeId != null) img.codeId = image.codeId;
674
+ if (image.codeFile != null) img.codeFile = image.codeFile;
675
+ if (image.arch != null) img.arch = image.arch;
676
+ return img;
677
+ });
678
+ }
679
+ return out;
680
+ }
351
681
 
352
682
  // src/utils/gzip.ts
353
683
  async function gzipBytes(data) {
@@ -13484,18 +13814,22 @@
13484
13814
  var { takeFullSnapshot } = record;
13485
13815
 
13486
13816
  // src/replay/privacy.ts
13487
- var SENSITIVE_QUERY = /(?:^|[?&])(token|secret|password|auth|api[_-]?key|access[_-]?token)=([^&#]*)/gi;
13817
+ var SENSITIVE_QUERY_KEY = /^(?:token|secret|password|auth|api[_-]?key|access[_-]?token|gclid|fbclid|gcl_au|msclkid|_ga(?:_.*)?|cid|sid)$/i;
13818
+ var SENSITIVE_QUERY_LEGACY = /(?:^|[?&])(token|secret|password|auth|api[_-]?key|access[_-]?token|gclid|fbclid|gcl_au|msclkid|_ga(?:_[^=&#]*)?|cid|sid)=([^&#]*)/gi;
13819
+ function isSensitiveQueryKey(key) {
13820
+ return SENSITIVE_QUERY_KEY.test(key);
13821
+ }
13488
13822
  function redactUrl(raw) {
13489
13823
  try {
13490
13824
  const url = new URL(raw, typeof location !== "undefined" ? location.href : void 0);
13491
13825
  for (const key of [...url.searchParams.keys()]) {
13492
- if (/token|secret|password|auth|api[_-]?key|access[_-]?token/i.test(key)) {
13826
+ if (isSensitiveQueryKey(key)) {
13493
13827
  url.searchParams.set(key, "[Filtered]");
13494
13828
  }
13495
13829
  }
13496
13830
  return url.toString();
13497
13831
  } catch {
13498
- return raw.replace(SENSITIVE_QUERY, (_m, name) => `${name}=[Filtered]`);
13832
+ return raw.replace(SENSITIVE_QUERY_LEGACY, (_m, name) => `${name}=[Filtered]`);
13499
13833
  }
13500
13834
  }
13501
13835
  function sanitizeNetworkUrl(raw, opts) {
@@ -13592,6 +13926,9 @@
13592
13926
  function isAbortError(error) {
13593
13927
  return error instanceof Error && error.name === "AbortError";
13594
13928
  }
13929
+ function isTimeoutError(error) {
13930
+ return error instanceof Error && error.name === "TimeoutError";
13931
+ }
13595
13932
  function isLikelyNetworkFetchError(error) {
13596
13933
  if (!(error instanceof Error)) return false;
13597
13934
  if (error.name === "AbortError" || error.name === "TimeoutError") return false;
@@ -13599,6 +13936,9 @@
13599
13936
  const msg = error.message.toLowerCase().trim();
13600
13937
  return msg === "failed to fetch" || msg === "load failed" || msg === "networkerror when attempting to fetch resource." || msg.startsWith("networkerror") || msg.includes("failed to fetch");
13601
13938
  }
13939
+ function isCorrelatableTransportError(error) {
13940
+ return isLikelyNetworkFetchError(error) || isTimeoutError(error);
13941
+ }
13602
13942
  function describeUnknownError(error) {
13603
13943
  if (error instanceof Error) {
13604
13944
  return {
@@ -13746,6 +14086,56 @@
13746
14086
  }
13747
14087
  return false;
13748
14088
  }
14089
+ function normalizeNetworkOrigin(raw) {
14090
+ const trimmed = (raw || "").trim();
14091
+ if (!trimmed || trimmed === "*") return trimmed;
14092
+ try {
14093
+ return new URL(trimmed).origin;
14094
+ } catch {
14095
+ try {
14096
+ return new URL(`https://${trimmed}`).origin;
14097
+ } catch {
14098
+ return trimmed.replace(/\/+$/, "");
14099
+ }
14100
+ }
14101
+ }
14102
+ function resolvePageOrigin2(pageOrigin) {
14103
+ if (pageOrigin) {
14104
+ try {
14105
+ return new URL(pageOrigin).origin;
14106
+ } catch {
14107
+ return pageOrigin.replace(/\/+$/, "");
14108
+ }
14109
+ }
14110
+ if (typeof location !== "undefined" && location.origin) {
14111
+ return location.origin;
14112
+ }
14113
+ return "";
14114
+ }
14115
+ function resolveRequestOrigin(rawUrl, pageOrigin) {
14116
+ const base = pageOrigin || (typeof location !== "undefined" ? location.href : void 0);
14117
+ try {
14118
+ return new URL(rawUrl, base).origin;
14119
+ } catch {
14120
+ return "";
14121
+ }
14122
+ }
14123
+ function isAllowedNetworkOrigin(requestOrigin, opts) {
14124
+ if (!requestOrigin) return false;
14125
+ const allow = opts.networkErrorOrigins.map(normalizeNetworkOrigin);
14126
+ if (allow.includes("*")) return true;
14127
+ const page = resolvePageOrigin2(opts.pageOrigin);
14128
+ if (page && requestOrigin === page) return true;
14129
+ return allow.includes(requestOrigin);
14130
+ }
14131
+ function classifyNetworkParty(requestOrigin, pageOrigin) {
14132
+ const page = resolvePageOrigin2(pageOrigin);
14133
+ if (page && requestOrigin && requestOrigin === page) return "first_party";
14134
+ return "third_party";
14135
+ }
14136
+ function isHttpOkStatus(status) {
14137
+ return status >= 200 && status < 300;
14138
+ }
13749
14139
  function shouldPromoteFailedRequest(meta, opts) {
13750
14140
  if (!opts.captureFailedRequests) return false;
13751
14141
  if (typeof meta.status !== "number" || Number.isNaN(meta.status)) return false;
@@ -13756,6 +14146,13 @@
13756
14146
  opts.talariaBaseUrl
13757
14147
  );
13758
14148
  if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
14149
+ const requestOrigin = meta.origin || resolveRequestOrigin(meta.url || "", opts.pageOrigin);
14150
+ if (!isAllowedNetworkOrigin(requestOrigin, {
14151
+ networkErrorOrigins: opts.networkErrorOrigins ?? [],
14152
+ pageOrigin: opts.pageOrigin
14153
+ })) {
14154
+ return false;
14155
+ }
13759
14156
  return true;
13760
14157
  }
13761
14158
  function shouldPromoteNetworkError(meta, opts) {
@@ -13769,6 +14166,13 @@
13769
14166
  opts.talariaBaseUrl
13770
14167
  );
13771
14168
  if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
14169
+ const requestOrigin = meta.origin || resolveRequestOrigin(meta.url || "", opts.pageOrigin);
14170
+ if (!isAllowedNetworkOrigin(requestOrigin, {
14171
+ networkErrorOrigins: opts.networkErrorOrigins ?? [],
14172
+ pageOrigin: opts.pageOrigin
14173
+ })) {
14174
+ return false;
14175
+ }
13772
14176
  return true;
13773
14177
  }
13774
14178
  function enrichNetworkMeta(meta) {
@@ -13779,7 +14183,8 @@
13779
14183
  failureKind: meta.ok === false ? "http" : meta.failureKind
13780
14184
  };
13781
14185
  }
13782
- if (meta.ok === false || meta.errorName || meta.errorMessage || meta.aborted) {
14186
+ const hasTransportSignal = !!meta.errorName || !!meta.errorMessage || !!meta.aborted || meta.failureKind === "abort" || meta.failureKind === "timeout" || meta.failureKind === "network";
14187
+ if (hasTransportSignal) {
13783
14188
  const failureKind = meta.failureKind === "abort" || meta.failureKind === "timeout" || meta.failureKind === "network" ? meta.failureKind : classifyTransportFailure({
13784
14189
  aborted: meta.aborted,
13785
14190
  errorName: meta.errorName
@@ -13792,10 +14197,13 @@
13792
14197
  }
13793
14198
  return meta;
13794
14199
  }
13795
- function networkUrlParts(rawUrl, includeQuery) {
13796
- const parts = sanitizeNetworkUrl(rawUrl, { includeQuery });
14200
+ function networkUrlParts(rawUrl, includeQuery, pageOrigin) {
14201
+ const baseHref = pageOrigin || (typeof location !== "undefined" ? location.href : void 0);
14202
+ const parts = sanitizeNetworkUrl(rawUrl, { includeQuery, baseHref });
14203
+ const origin = resolveRequestOrigin(rawUrl, pageOrigin);
13797
14204
  return {
13798
14205
  url: parts.url,
14206
+ origin: origin || void 0,
13799
14207
  hostname: parts.hostname,
13800
14208
  pathname: parts.pathname,
13801
14209
  ...parts.search ? { search: parts.search } : {}
@@ -13811,14 +14219,22 @@
13811
14219
  captureNetworkErrors: options.captureNetworkErrors ?? true,
13812
14220
  failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
13813
14221
  failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
14222
+ networkErrorOrigins: options.networkErrorOrigins ?? [],
14223
+ pageOrigin: options.pageOrigin,
13814
14224
  talariaBaseUrl: options.talariaBaseUrl
13815
14225
  };
13816
14226
  const includeQuery = options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false;
13817
14227
  const handleMeta = (meta) => {
13818
14228
  const { rawUrl, ...rest } = meta;
14229
+ const parts = networkUrlParts(rawUrl, includeQuery, matchOpts.pageOrigin);
14230
+ const party = classifyNetworkParty(
14231
+ parts.origin || "",
14232
+ matchOpts.pageOrigin
14233
+ );
13819
14234
  const enriched = enrichNetworkMeta({
13820
14235
  ...rest,
13821
- ...networkUrlParts(rawUrl, includeQuery)
14236
+ ...parts,
14237
+ party
13822
14238
  });
13823
14239
  try {
13824
14240
  emitNetwork(enriched);
@@ -13838,6 +14254,7 @@
13838
14254
  const rawUrl = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
13839
14255
  try {
13840
14256
  const response = await originalFetch(input2, init);
14257
+ const opaque = response.type === "opaque" || response.type === "opaqueredirect";
13841
14258
  handleMeta({
13842
14259
  method,
13843
14260
  rawUrl,
@@ -13845,7 +14262,8 @@
13845
14262
  transport: "fetch",
13846
14263
  status: response.status,
13847
14264
  durationMs: Date.now() - started,
13848
- ok: response.ok
14265
+ // Opaque responses resolve successfully with status 0 — not a failure.
14266
+ ok: opaque ? true : response.ok
13849
14267
  });
13850
14268
  return response;
13851
14269
  } catch (error) {
@@ -13907,7 +14325,7 @@
13907
14325
  transport: "xhr",
13908
14326
  status,
13909
14327
  durationMs: Date.now() - (this.__talariaStarted ?? Date.now()),
13910
- ok: status >= 200 && status < 400,
14328
+ ok: isHttpOkStatus(status),
13911
14329
  ...transportFail ? {
13912
14330
  failureKind,
13913
14331
  errorName,
@@ -13916,7 +14334,7 @@
13916
14334
  } : {}
13917
14335
  });
13918
14336
  };
13919
- this.addEventListener("loadend", onDone);
14337
+ this.addEventListener("loadend", onDone, { once: true });
13920
14338
  return originalSend.call(this, body);
13921
14339
  };
13922
14340
  }
@@ -13928,10 +14346,26 @@
13928
14346
  }
13929
14347
 
13930
14348
  // src/client.ts
14349
+ var PLATFORM_JAVASCRIPT = "javascript";
14350
+ var EXTRA_LOCATION_KEYS = /* @__PURE__ */ new Set([
14351
+ "exception_class",
14352
+ "file",
14353
+ "line",
14354
+ "code",
14355
+ "filename",
14356
+ "lineno",
14357
+ "colno"
14358
+ ]);
14359
+ function networkExceptionClass(failure) {
14360
+ if (failure.failureKind === "http") return "HttpError";
14361
+ if (failure.failureKind === "timeout") return "TimeoutError";
14362
+ return "NetworkError";
14363
+ }
13931
14364
  function networkFailureTags(failure) {
13932
14365
  return {
13933
14366
  "http.method": failure.method || "GET",
13934
14367
  "network.failure_kind": failure.failureKind ?? "network",
14368
+ ...failure.party ? { "network.party": failure.party } : {},
13935
14369
  ...failure.transport ? { "network.transport": failure.transport } : {},
13936
14370
  ...failure.errorName ? { "network.error_name": failure.errorName } : {}
13937
14371
  };
@@ -13941,6 +14375,7 @@
13941
14375
  method: failure.method || "GET",
13942
14376
  url: failure.url || "(unknown url)"
13943
14377
  };
14378
+ if (failure.origin) http.origin = failure.origin;
13944
14379
  if (failure.hostname) http.hostname = failure.hostname;
13945
14380
  if (failure.pathname) http.pathname = failure.pathname;
13946
14381
  if (failure.search) http.search = failure.search;
@@ -13953,14 +14388,69 @@
13953
14388
  };
13954
14389
  if (failure.errorName) fail.name = failure.errorName;
13955
14390
  if (failure.errorMessage) fail.message = failure.errorMessage;
14391
+ const statusCode = typeof failure.status === "number" && failure.status > 0 ? failure.status : null;
13956
14392
  return {
13957
14393
  http,
13958
14394
  failure: fail,
14395
+ network: {
14396
+ party: failure.party ?? "third_party",
14397
+ durationMs: failure.durationMs,
14398
+ aborted: failure.aborted ?? false,
14399
+ ok: failure.ok ?? false
14400
+ },
14401
+ // Top-level for server fingerprint compatibility (status only —
14402
+ // exception type lives on first-class `exception.values[0].type`).
14403
+ status_code: statusCode,
13959
14404
  durationMs: failure.durationMs,
13960
14405
  aborted: failure.aborted ?? false,
13961
14406
  ok: failure.ok ?? false
13962
14407
  };
13963
14408
  }
14409
+ function networkExceptionData(failure, message) {
14410
+ return {
14411
+ values: [
14412
+ {
14413
+ type: networkExceptionClass(failure),
14414
+ value: message,
14415
+ mechanism: {
14416
+ type: "http",
14417
+ handled: true,
14418
+ synthetic: true
14419
+ }
14420
+ }
14421
+ ]
14422
+ };
14423
+ }
14424
+ function scrubLegacyExceptionExtra(extra) {
14425
+ if (!extra) return void 0;
14426
+ const out = {};
14427
+ for (const [key, value] of Object.entries(extra)) {
14428
+ if (EXTRA_LOCATION_KEYS.has(key)) continue;
14429
+ out[key] = value;
14430
+ }
14431
+ return Object.keys(out).length ? out : void 0;
14432
+ }
14433
+ function buildExceptionFromError(err, context, inAppOptions) {
14434
+ const mechanism = context?.mechanism ?? {
14435
+ type: "generic",
14436
+ handled: true
14437
+ };
14438
+ const stacktrace = applySourceLocation(
14439
+ parseStackTrace(err.stack, inAppOptions),
14440
+ context?.source,
14441
+ inAppOptions
14442
+ );
14443
+ return {
14444
+ values: [
14445
+ {
14446
+ type: err.name || "Error",
14447
+ value: err.message || err.name || "Error",
14448
+ mechanism,
14449
+ ...stacktrace ? { stacktrace } : {}
14450
+ }
14451
+ ]
14452
+ };
14453
+ }
13964
14454
  function mergeNetworkFailureContext(context, failure) {
13965
14455
  return {
13966
14456
  ...context,
@@ -13997,13 +14487,17 @@
13997
14487
  inlineStylesheet: options.inlineStylesheet ?? false,
13998
14488
  blockSelector: options.blockSelector ?? "",
13999
14489
  userId: options.userId,
14000
- tags: options.tags,
14490
+ tags: mergeTags(options.tags),
14001
14491
  disableDefaultIntegrations: options.disableDefaultIntegrations ?? false,
14002
14492
  captureFailedRequests: options.captureFailedRequests ?? true,
14003
14493
  captureNetworkErrors: options.captureNetworkErrors ?? true,
14494
+ networkErrorOrigins: options.networkErrorOrigins ?? [],
14004
14495
  includeNetworkUrlQuery: options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false,
14005
14496
  failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
14006
- failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? []
14497
+ failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
14498
+ inAppAllowUrls: options.inAppAllowUrls ?? [],
14499
+ inAppDenyUrls: options.inAppDenyUrls ?? [],
14500
+ inAppOrigins: options.inAppOrigins ?? []
14007
14501
  };
14008
14502
  }
14009
14503
  var RECENT_NETWORK_FAILURE_MS = 5e3;
@@ -14130,6 +14624,8 @@
14130
14624
  installNetworkHook({
14131
14625
  captureFailedRequests: this.options.captureFailedRequests,
14132
14626
  captureNetworkErrors: this.options.captureNetworkErrors,
14627
+ networkErrorOrigins: this.options.networkErrorOrigins,
14628
+ pageOrigin: typeof location !== "undefined" ? location.origin : void 0,
14133
14629
  includeNetworkUrlQuery: this.options.includeNetworkUrlQuery,
14134
14630
  failedRequestStatusCodes: this.options.failedRequestStatusCodes,
14135
14631
  failedRequestIgnoreUrls: this.options.failedRequestIgnoreUrls,
@@ -14148,17 +14644,21 @@
14148
14644
  failureKind: "http",
14149
14645
  aborted: false
14150
14646
  };
14151
- void this.captureMessage(
14152
- `HTTP ${status}: ${method} ${url}`,
14153
- status >= 500 ? "error" : "warning",
14154
- {
14647
+ const message = `HTTP ${status}: ${method} ${url}`;
14648
+ void this.capture({
14649
+ message,
14650
+ level: status >= 500 ? "error" : "warning",
14651
+ title: networkExceptionClass(enriched),
14652
+ exception: networkExceptionData(enriched, message),
14653
+ platform: PLATFORM_JAVASCRIPT,
14654
+ context: {
14155
14655
  tags: {
14156
14656
  ...networkFailureTags(enriched),
14157
14657
  "http.status_code": String(status)
14158
14658
  },
14159
14659
  extra: networkFailureExtra(enriched)
14160
14660
  }
14161
- );
14661
+ });
14162
14662
  },
14163
14663
  onNetworkError: (meta) => {
14164
14664
  this.rememberNetworkFailure(meta, { promoted: true });
@@ -14166,14 +14666,18 @@
14166
14666
  const url = meta.url || "(unknown url)";
14167
14667
  const errLabel = meta.errorName && meta.errorMessage ? `${meta.errorName}: ${meta.errorMessage}` : meta.errorMessage || meta.errorName || "Failed to fetch";
14168
14668
  const prefix = meta.failureKind === "timeout" ? "Timeout error" : "Network error";
14169
- void this.captureMessage(
14170
- `${prefix}: ${method} ${url} \u2014 ${errLabel}`,
14171
- "error",
14172
- {
14669
+ const message = `${prefix}: ${method} ${url} \u2014 ${errLabel}`;
14670
+ void this.capture({
14671
+ message,
14672
+ level: "error",
14673
+ title: networkExceptionClass(meta),
14674
+ exception: networkExceptionData(meta, message),
14675
+ platform: PLATFORM_JAVASCRIPT,
14676
+ context: {
14173
14677
  tags: networkFailureTags(meta),
14174
14678
  extra: networkFailureExtra(meta)
14175
14679
  }
14176
- );
14680
+ });
14177
14681
  }
14178
14682
  }),
14179
14683
  installVisibilityResumeHook(() => this.onForegroundResume())
@@ -14210,7 +14714,7 @@
14210
14714
  if (this.capturing || this.ingestDisabled) return;
14211
14715
  const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
14212
14716
  if (isAbortError(err)) return;
14213
- const filename = typeof context?.extra?.filename === "string" ? context.extra.filename : void 0;
14717
+ const filename = context?.source?.filename;
14214
14718
  if (isBrowserExtensionNoise({
14215
14719
  message: err.message,
14216
14720
  stack: err.stack,
@@ -14223,6 +14727,7 @@
14223
14727
  }
14224
14728
  const correlated = this.consumeCorrelatedNetworkFailure(err);
14225
14729
  if (correlated?.promoted) return;
14730
+ if (correlated && correlated.party === "third_party") return;
14226
14731
  const mergedContext = correlated ? mergeNetworkFailureContext(context, correlated) : context;
14227
14732
  this.capturing = true;
14228
14733
  try {
@@ -14231,6 +14736,12 @@
14231
14736
  level: "error",
14232
14737
  title: err.name || "Error",
14233
14738
  stackTrace: err.stack,
14739
+ exception: buildExceptionFromError(
14740
+ err,
14741
+ mergedContext,
14742
+ this.inAppFrameOptions()
14743
+ ),
14744
+ platform: PLATFORM_JAVASCRIPT,
14234
14745
  context: mergedContext
14235
14746
  });
14236
14747
  } finally {
@@ -14246,6 +14757,13 @@
14246
14757
  context
14247
14758
  });
14248
14759
  }
14760
+ /**
14761
+ * Returns a scoped capture facade that merges [tags] into every event.
14762
+ * Nested `withTags` calls merge further (later keys win).
14763
+ */
14764
+ withTags(tags) {
14765
+ return createScopedTalaria(this, mergeTags(tags));
14766
+ }
14249
14767
  async flush(opts) {
14250
14768
  if (!this.options || !this.transport || this.closed) return;
14251
14769
  const keepalive = opts?.keepalive ?? false;
@@ -14410,13 +14928,17 @@
14410
14928
  }
14411
14929
  const queuedMs = Math.max(0, Date.now() - occurredAt.getTime());
14412
14930
  const tags = applyReplayCaptureTags(
14413
- {
14414
- ...this.browserContext ? browserContextTags(this.browserContext) : {},
14415
- ...this.options.tags ?? {},
14416
- ...args.context?.tags ?? {}
14417
- },
14931
+ mergeTags(
14932
+ this.browserContext ? browserContextTags(this.browserContext) : {},
14933
+ this.options.tags,
14934
+ args.context?.tags
14935
+ ),
14418
14936
  errorClipOutcome
14419
14937
  );
14938
+ warnSuspiciousTags(tags, this.options.environment);
14939
+ const scrubbedContextExtra = scrubLegacyExceptionExtra(
14940
+ args.context?.extra
14941
+ );
14420
14942
  const extra = mergeReplayCaptureExtra(
14421
14943
  {
14422
14944
  ...this.browserContext ? {
@@ -14438,7 +14960,7 @@
14438
14960
  // Time spent in capture() before ingest (mostly replay flush).
14439
14961
  ...queuedMs >= 50 ? { queuedMs } : {}
14440
14962
  },
14441
- ...args.context?.extra
14963
+ ...scrubbedContextExtra ?? {}
14442
14964
  },
14443
14965
  errorClipOutcome
14444
14966
  );
@@ -14450,6 +14972,8 @@
14450
14972
  eventType: levelToEventType(args.level),
14451
14973
  title: args.title ?? args.context?.title,
14452
14974
  stackTrace: args.stackTrace,
14975
+ exception: args.exception,
14976
+ platform: args.platform,
14453
14977
  release: this.options.release,
14454
14978
  userId: args.context?.userId ?? this.options.userId,
14455
14979
  sessionId: this.sessionId ?? void 0,
@@ -14699,12 +15223,36 @@
14699
15223
  this.resetToBufferMode();
14700
15224
  }
14701
15225
  }
15226
+ inAppFrameOptions() {
15227
+ const opts = this.options;
15228
+ let pageOrigin;
15229
+ try {
15230
+ pageOrigin = resolvePageOrigin(
15231
+ typeof window !== "undefined" ? window.location?.origin : void 0
15232
+ );
15233
+ } catch {
15234
+ pageOrigin = void 0;
15235
+ }
15236
+ return {
15237
+ pageOrigin,
15238
+ allowUrls: opts?.inAppAllowUrls,
15239
+ denyUrls: opts?.inAppDenyUrls,
15240
+ inAppOrigins: opts?.inAppOrigins
15241
+ };
15242
+ }
14702
15243
  pruneRecentNetworkFailures(now = Date.now()) {
14703
15244
  this.recentNetworkFailures = this.recentNetworkFailures.filter(
14704
15245
  (f) => now - f.at <= RECENT_NETWORK_FAILURE_MS
14705
15246
  );
14706
15247
  }
14707
15248
  rememberNetworkFailure(meta, opts) {
15249
+ if (this.options) {
15250
+ const ignore = buildFailedRequestIgnoreUrls(
15251
+ this.options.failedRequestIgnoreUrls,
15252
+ this.options.baseUrl
15253
+ );
15254
+ if (urlMatchesIgnoreList(meta.url || "", ignore)) return;
15255
+ }
14708
15256
  const now = Date.now();
14709
15257
  this.pruneRecentNetworkFailures(now);
14710
15258
  for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
@@ -14715,6 +15263,8 @@
14715
15263
  existing.errorName = meta.errorName ?? existing.errorName;
14716
15264
  existing.aborted = meta.aborted ?? existing.aborted;
14717
15265
  existing.failureKind = meta.failureKind ?? existing.failureKind;
15266
+ existing.party = meta.party ?? existing.party;
15267
+ existing.origin = meta.origin ?? existing.origin;
14718
15268
  return;
14719
15269
  }
14720
15270
  }
@@ -14725,11 +15275,12 @@
14725
15275
  });
14726
15276
  }
14727
15277
  /**
14728
- * Find a recent transport failure for a bare fetch TypeError.
15278
+ * Find a recent transport failure for a bare fetch TypeError / TimeoutError.
14729
15279
  * Consumes the entry so a later duplicate path cannot reuse it.
14730
15280
  */
14731
15281
  consumeCorrelatedNetworkFailure(error) {
14732
- if (!isLikelyNetworkFetchError(error)) return null;
15282
+ if (!isCorrelatableTransportError(error)) return null;
15283
+ const wantKind = isTimeoutError(error) ? "timeout" : "network";
14733
15284
  const now = Date.now();
14734
15285
  this.pruneRecentNetworkFailures(now);
14735
15286
  const takeAt = (index2) => {
@@ -14738,14 +15289,14 @@
14738
15289
  };
14739
15290
  for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
14740
15291
  const failure = this.recentNetworkFailures[i];
14741
- if (failure.aborted || failure.failureKind !== "network") continue;
15292
+ if (failure.aborted || failure.failureKind !== wantKind) continue;
14742
15293
  if (failure.errorMessage && failure.errorMessage === error.message) {
14743
15294
  return takeAt(i);
14744
15295
  }
14745
15296
  }
14746
15297
  for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
14747
15298
  const failure = this.recentNetworkFailures[i];
14748
- if (failure.aborted || failure.failureKind !== "network") continue;
15299
+ if (failure.aborted || failure.failureKind !== wantKind) continue;
14749
15300
  return takeAt(i);
14750
15301
  }
14751
15302
  return null;
@@ -14763,7 +15314,8 @@
14763
15314
  return;
14764
15315
  }
14765
15316
  void this.captureException(error, {
14766
- extra: {
15317
+ mechanism: { type: "onerror", handled: false },
15318
+ source: {
14767
15319
  filename: event.filename,
14768
15320
  lineno: event.lineno,
14769
15321
  colno: event.colno
@@ -14785,7 +15337,9 @@
14785
15337
  })) {
14786
15338
  return;
14787
15339
  }
14788
- void this.captureException(reason);
15340
+ void this.captureException(reason, {
15341
+ mechanism: { type: "unhandledrejection", handled: false }
15342
+ });
14789
15343
  };
14790
15344
  window.addEventListener("error", onError);
14791
15345
  window.addEventListener("unhandledrejection", onRejection);
@@ -15047,6 +15601,23 @@
15047
15601
  }
15048
15602
  }
15049
15603
  };
15604
+ function createScopedTalaria(client2, scopeTags) {
15605
+ const mergeContext = (context) => ({
15606
+ ...context,
15607
+ tags: mergeTags(scopeTags, context?.tags)
15608
+ });
15609
+ return {
15610
+ captureException(error, context) {
15611
+ return client2.captureException(error, mergeContext(context));
15612
+ },
15613
+ captureMessage(message, level, context) {
15614
+ return client2.captureMessage(message, level, mergeContext(context));
15615
+ },
15616
+ withTags(tags) {
15617
+ return createScopedTalaria(client2, mergeTags(scopeTags, tags));
15618
+ }
15619
+ };
15620
+ }
15050
15621
 
15051
15622
  // src/index.ts
15052
15623
  var client = new TalariaClient();
@@ -15060,6 +15631,9 @@
15060
15631
  captureMessage(message, level, context) {
15061
15632
  return client.captureMessage(message, level, context);
15062
15633
  },
15634
+ withTags(tags) {
15635
+ return client.withTags(tags);
15636
+ },
15063
15637
  getReplayId() {
15064
15638
  return client.getReplayId();
15065
15639
  },