@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.
package/dist/index.js CHANGED
@@ -270,9 +270,262 @@ async function collectBrowserContext() {
270
270
  return base;
271
271
  }
272
272
 
273
+ // src/utils/tags.ts
274
+ var MAX_TAGS_PER_EVENT = 20;
275
+ var MAX_TAG_KEY_LENGTH = 64;
276
+ var MAX_TAG_VALUE_LENGTH = 128;
277
+ var MAX_TAG_TOTAL_UTF8_BYTES = 2048;
278
+ var KEY_PATTERN = /^[a-z0-9_.-]+$/;
279
+ var HIGH_CARDINALITY_TAG_KEYS = /* @__PURE__ */ new Set([
280
+ "user_id",
281
+ "userid",
282
+ "request_id",
283
+ "requestid",
284
+ "business_id",
285
+ "order_id",
286
+ "email",
287
+ "url",
288
+ "uuid",
289
+ "session_id",
290
+ "trace_id",
291
+ "span_id"
292
+ ]);
293
+ 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;
294
+ var EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
295
+ var LONG_NUMERIC_RE = /^\d{8,}$/;
296
+ function utf8Bytes(s) {
297
+ if (typeof TextEncoder !== "undefined") {
298
+ return new TextEncoder().encode(s).length;
299
+ }
300
+ return unescape(encodeURIComponent(s)).length;
301
+ }
302
+ function normalizeKey(raw) {
303
+ const key = raw.trim().toLowerCase();
304
+ if (!key || key.length > MAX_TAG_KEY_LENGTH) return null;
305
+ if (!KEY_PATTERN.test(key)) return null;
306
+ return key;
307
+ }
308
+ function normalizeValue(raw) {
309
+ if (raw == null) return null;
310
+ const value = String(raw).trim();
311
+ if (!value) return null;
312
+ return value.length <= MAX_TAG_VALUE_LENGTH ? value : value.slice(0, MAX_TAG_VALUE_LENGTH);
313
+ }
314
+ function normalizeTags(tags) {
315
+ if (!tags) return {};
316
+ const result2 = {};
317
+ let totalBytes = 0;
318
+ for (const [rawKey, rawValue] of Object.entries(tags)) {
319
+ if (Object.keys(result2).length >= MAX_TAGS_PER_EVENT) break;
320
+ const key = normalizeKey(rawKey);
321
+ if (!key) continue;
322
+ const value = normalizeValue(rawValue);
323
+ if (value == null) continue;
324
+ if (result2[key] != null) {
325
+ totalBytes -= utf8Bytes(key) + utf8Bytes(result2[key]);
326
+ }
327
+ const entryBytes = utf8Bytes(key) + utf8Bytes(value);
328
+ if (totalBytes + entryBytes > MAX_TAG_TOTAL_UTF8_BYTES) continue;
329
+ result2[key] = value;
330
+ totalBytes += entryBytes;
331
+ }
332
+ return result2;
333
+ }
334
+ function mergeTags(...parts) {
335
+ const merged = {};
336
+ for (const part of parts) {
337
+ if (!part) continue;
338
+ Object.assign(merged, part);
339
+ }
340
+ return normalizeTags(merged);
341
+ }
342
+ function looksHighCardinalityValue(value) {
343
+ return UUID_RE.test(value) || EMAIL_RE.test(value) || LONG_NUMERIC_RE.test(value);
344
+ }
345
+ function warnSuspiciousTags(tags, environment) {
346
+ if (!environment || environment === "production") return;
347
+ if (typeof console === "undefined" || typeof console.warn !== "function") {
348
+ return;
349
+ }
350
+ for (const [key, value] of Object.entries(tags)) {
351
+ if (HIGH_CARDINALITY_TAG_KEYS.has(key)) {
352
+ console.warn(
353
+ `@newtalaria/browser: Tag "${key}" appears to have high cardinality. Consider moving it to context/extra.`
354
+ );
355
+ continue;
356
+ }
357
+ if (looksHighCardinalityValue(value)) {
358
+ console.warn(
359
+ `@newtalaria/browser: Tag "${key}" value looks high-cardinality. Consider moving it to context/extra.`
360
+ );
361
+ }
362
+ }
363
+ }
364
+ var RESERVED_TAG_KEYS = [
365
+ "service",
366
+ "platform",
367
+ "feature",
368
+ "operation",
369
+ "component",
370
+ "runtime",
371
+ "runtime_version"
372
+ ];
373
+
374
+ // src/utils/stacktrace.ts
375
+ var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(.+?):(\d+):(\d+)\)?\s*$/;
376
+ var V8_FRAME_BARE = /^\s*at\s+(.+)\s*$/;
377
+ var EXTENSION_URL2 = /(?:chrome|moz|safari|safari-web|ms-browser)-extension:\/\//i;
378
+ function parseStackTrace(stack, inAppOptions) {
379
+ if (!stack || !stack.trim()) return void 0;
380
+ const parsed = [];
381
+ for (const line of stack.split("\n")) {
382
+ const frame = parseStackLine(line, inAppOptions);
383
+ if (frame) parsed.push(frame);
384
+ }
385
+ if (parsed.length === 0) return void 0;
386
+ parsed.reverse();
387
+ return { frames: parsed };
388
+ }
389
+ function parseStackLine(line, inAppOptions) {
390
+ if (!/^\s*at\s+/.test(line)) return null;
391
+ const v8 = V8_FRAME.exec(line);
392
+ if (v8) {
393
+ const functionName = cleanFunction(v8[1]);
394
+ const absPath = stripQuery(v8[2] ?? "");
395
+ const lineno = Number.parseInt(v8[3] ?? "", 10);
396
+ const colno = Number.parseInt(v8[4] ?? "", 10);
397
+ return {
398
+ functionName,
399
+ absPath: absPath || void 0,
400
+ filename: basename(absPath),
401
+ lineno: Number.isFinite(lineno) ? lineno : void 0,
402
+ colno: Number.isFinite(colno) ? colno : void 0,
403
+ inApp: isInAppFrame(absPath, inAppOptions),
404
+ platform: "javascript"
405
+ };
406
+ }
407
+ const bare = V8_FRAME_BARE.exec(line);
408
+ if (bare) {
409
+ const functionName = cleanFunction(bare[1]);
410
+ if (!functionName) return null;
411
+ return {
412
+ functionName,
413
+ inApp: false,
414
+ platform: "javascript"
415
+ };
416
+ }
417
+ return null;
418
+ }
419
+ function applySourceLocation(stacktrace, source, inAppOptions) {
420
+ if (!source) return stacktrace;
421
+ const filename = source.filename?.trim() || void 0;
422
+ const lineno = typeof source.lineno === "number" && source.lineno > 0 ? source.lineno : void 0;
423
+ const colno = typeof source.colno === "number" && source.colno >= 0 ? source.colno : void 0;
424
+ if (!filename && lineno == null && colno == null) return stacktrace;
425
+ if (!stacktrace || stacktrace.frames.length === 0) {
426
+ if (!filename && lineno == null) return stacktrace;
427
+ const absPath = filename;
428
+ return {
429
+ frames: [
430
+ {
431
+ filename: absPath ? basename(absPath) : void 0,
432
+ absPath,
433
+ lineno,
434
+ colno,
435
+ inApp: absPath ? isInAppFrame(absPath, inAppOptions) : true,
436
+ platform: "javascript"
437
+ }
438
+ ]
439
+ };
440
+ }
441
+ const frames = stacktrace.frames.slice();
442
+ const top = { ...frames[frames.length - 1] };
443
+ if (filename) {
444
+ top.absPath = filename;
445
+ top.filename = basename(filename);
446
+ top.inApp = isInAppFrame(filename, inAppOptions);
447
+ }
448
+ if (lineno != null) top.lineno = lineno;
449
+ if (colno != null) top.colno = colno;
450
+ frames[frames.length - 1] = top;
451
+ return { ...stacktrace, frames };
452
+ }
453
+ function isInAppFrame(path, options) {
454
+ const lower = path.toLowerCase();
455
+ if (!lower) return true;
456
+ if (lower.includes("node_modules")) return false;
457
+ if (EXTENSION_URL2.test(path) || lower.includes("webkit-masked-url://")) {
458
+ return false;
459
+ }
460
+ if (lower.includes("@newtalaria/browser")) return false;
461
+ if (matchesUrlList(path, options?.denyUrls)) return false;
462
+ if (matchesUrlList(path, options?.allowUrls)) return true;
463
+ const frameOrigin = tryParseOrigin(path);
464
+ if (frameOrigin != null) {
465
+ const pageOrigin = normalizeOrigin(options?.pageOrigin);
466
+ if (pageOrigin && frameOrigin === pageOrigin) return true;
467
+ const extra = options?.inAppOrigins ?? [];
468
+ for (const origin of extra) {
469
+ if (normalizeOrigin(origin) === frameOrigin) return true;
470
+ }
471
+ return false;
472
+ }
473
+ return true;
474
+ }
475
+ function matchesUrlList(path, patterns) {
476
+ if (!patterns || patterns.length === 0) return false;
477
+ for (const pattern of patterns) {
478
+ if (typeof pattern === "string") {
479
+ if (pattern && path.includes(pattern)) return true;
480
+ } else if (pattern.test(path)) {
481
+ return true;
482
+ }
483
+ }
484
+ return false;
485
+ }
486
+ function tryParseOrigin(path) {
487
+ if (!/^https?:\/\//i.test(path)) return null;
488
+ try {
489
+ return new URL(path).origin;
490
+ } catch {
491
+ return null;
492
+ }
493
+ }
494
+ function normalizeOrigin(origin) {
495
+ if (!origin || !origin.trim()) return null;
496
+ const trimmed = origin.trim();
497
+ try {
498
+ if (/^https?:\/\//i.test(trimmed)) {
499
+ return new URL(trimmed).origin;
500
+ }
501
+ return trimmed;
502
+ } catch {
503
+ return trimmed;
504
+ }
505
+ }
506
+ function resolvePageOrigin(originOrUrl) {
507
+ return normalizeOrigin(originOrUrl) ?? void 0;
508
+ }
509
+ function cleanFunction(raw) {
510
+ if (raw == null) return void 0;
511
+ const value = raw.trim();
512
+ return value.length === 0 ? void 0 : value;
513
+ }
514
+ function stripQuery(path) {
515
+ const q = path.indexOf("?");
516
+ return q >= 0 ? path.slice(0, q) : path;
517
+ }
518
+ function basename(path) {
519
+ if (!path) return void 0;
520
+ const normalized = stripQuery(path);
521
+ const parts = normalized.split(/[/\\]/);
522
+ const last = parts[parts.length - 1];
523
+ return last || normalized;
524
+ }
525
+
273
526
  // src/sdk_meta.ts
274
527
  var SDK_NAME = "@newtalaria/browser";
275
- var SDK_VERSION = "0.1.17";
528
+ var SDK_VERSION = "0.1.18";
276
529
 
277
530
  // src/transport/serverpod.ts
278
531
  var ServerpodTransport = class {
@@ -330,6 +583,9 @@ async function ingestEvent(transport, params) {
330
583
  if (params.eventType) input2.eventType = params.eventType;
331
584
  if (params.title) input2.title = params.title;
332
585
  if (params.stackTrace) input2.stackTrace = params.stackTrace;
586
+ if (params.exception) input2.exception = serializeException(params.exception);
587
+ if (params.debugMeta) input2.debugMeta = serializeDebugMeta(params.debugMeta);
588
+ if (params.platform) input2.platform = params.platform;
333
589
  if (params.release) input2.release = params.release;
334
590
  if (params.userId) input2.userId = params.userId;
335
591
  if (params.sessionId) input2.sessionId = params.sessionId;
@@ -346,6 +602,89 @@ async function ingestEvent(transport, params) {
346
602
  { keepalive: params.keepalive }
347
603
  );
348
604
  }
605
+ function serializeException(data) {
606
+ return {
607
+ __className__: "ExceptionDataDto",
608
+ values: data.values.map(serializeExceptionValue)
609
+ };
610
+ }
611
+ function serializeExceptionValue(value) {
612
+ const out = {
613
+ __className__: "ExceptionValueDto"
614
+ };
615
+ if (value.type != null) out.type = value.type;
616
+ if (value.value != null) out.value = value.value;
617
+ if (value.module != null) out.module = value.module;
618
+ if (value.threadId != null) out.threadId = value.threadId;
619
+ if (value.code != null) out.code = value.code;
620
+ if (value.mechanism) out.mechanism = serializeMechanism(value.mechanism);
621
+ if (value.stacktrace) out.stacktrace = serializeStackTrace(value.stacktrace);
622
+ return out;
623
+ }
624
+ function serializeMechanism(mechanism) {
625
+ const out = {
626
+ __className__: "ExceptionMechanismDto",
627
+ type: mechanism.type
628
+ };
629
+ if (mechanism.handled != null) out.handled = mechanism.handled;
630
+ if (mechanism.synthetic != null) out.synthetic = mechanism.synthetic;
631
+ return out;
632
+ }
633
+ function serializeStackTrace(stack) {
634
+ const out = {
635
+ __className__: "StackTraceDto",
636
+ frames: stack.frames.map(serializeStackFrame)
637
+ };
638
+ if (stack.registers) out.registers = stack.registers;
639
+ return out;
640
+ }
641
+ function serializeStackFrame(frame) {
642
+ const out = {
643
+ __className__: "StackFrameDto"
644
+ };
645
+ if (frame.filename != null) out.filename = frame.filename;
646
+ if (frame.absPath != null) out.absPath = frame.absPath;
647
+ if (frame.functionName != null) out.functionName = frame.functionName;
648
+ if (frame.rawFunction != null) out.rawFunction = frame.rawFunction;
649
+ if (frame.module != null) out.module = frame.module;
650
+ if (frame.package != null) out.package = frame.package;
651
+ if (frame.platform != null) out.platform = frame.platform;
652
+ if (frame.lineno != null) out.lineno = frame.lineno;
653
+ if (frame.colno != null) out.colno = frame.colno;
654
+ if (frame.inApp != null) out.inApp = frame.inApp;
655
+ if (frame.instructionAddr != null) out.instructionAddr = frame.instructionAddr;
656
+ if (frame.symbolAddr != null) out.symbolAddr = frame.symbolAddr;
657
+ if (frame.imageAddr != null) out.imageAddr = frame.imageAddr;
658
+ if (frame.addrMode != null) out.addrMode = frame.addrMode;
659
+ if (frame.contextLine != null) out.contextLine = frame.contextLine;
660
+ if (frame.preContext != null) out.preContext = frame.preContext;
661
+ if (frame.postContext != null) out.postContext = frame.postContext;
662
+ if (frame.vars != null) out.vars = frame.vars;
663
+ if (frame.stackStart != null) out.stackStart = frame.stackStart;
664
+ return out;
665
+ }
666
+ function serializeDebugMeta(meta) {
667
+ const out = {
668
+ __className__: "DebugMetaDto"
669
+ };
670
+ if (meta.images) {
671
+ out.images = meta.images.map((image) => {
672
+ const img = {
673
+ __className__: "DebugImageDto"
674
+ };
675
+ if (image.type != null) img.type = image.type;
676
+ if (image.imageAddr != null) img.imageAddr = image.imageAddr;
677
+ if (image.imageSize != null) img.imageSize = image.imageSize;
678
+ if (image.debugId != null) img.debugId = image.debugId;
679
+ if (image.debugFile != null) img.debugFile = image.debugFile;
680
+ if (image.codeId != null) img.codeId = image.codeId;
681
+ if (image.codeFile != null) img.codeFile = image.codeFile;
682
+ if (image.arch != null) img.arch = image.arch;
683
+ return img;
684
+ });
685
+ }
686
+ return out;
687
+ }
349
688
 
350
689
  // src/utils/gzip.ts
351
690
  async function gzipBytes(data) {
@@ -13482,18 +13821,22 @@ var { freezePage } = record;
13482
13821
  var { takeFullSnapshot } = record;
13483
13822
 
13484
13823
  // src/replay/privacy.ts
13485
- var SENSITIVE_QUERY = /(?:^|[?&])(token|secret|password|auth|api[_-]?key|access[_-]?token)=([^&#]*)/gi;
13824
+ var SENSITIVE_QUERY_KEY = /^(?:token|secret|password|auth|api[_-]?key|access[_-]?token|gclid|fbclid|gcl_au|msclkid|_ga(?:_.*)?|cid|sid)$/i;
13825
+ var SENSITIVE_QUERY_LEGACY = /(?:^|[?&])(token|secret|password|auth|api[_-]?key|access[_-]?token|gclid|fbclid|gcl_au|msclkid|_ga(?:_[^=&#]*)?|cid|sid)=([^&#]*)/gi;
13826
+ function isSensitiveQueryKey(key) {
13827
+ return SENSITIVE_QUERY_KEY.test(key);
13828
+ }
13486
13829
  function redactUrl(raw) {
13487
13830
  try {
13488
13831
  const url = new URL(raw, typeof location !== "undefined" ? location.href : void 0);
13489
13832
  for (const key of [...url.searchParams.keys()]) {
13490
- if (/token|secret|password|auth|api[_-]?key|access[_-]?token/i.test(key)) {
13833
+ if (isSensitiveQueryKey(key)) {
13491
13834
  url.searchParams.set(key, "[Filtered]");
13492
13835
  }
13493
13836
  }
13494
13837
  return url.toString();
13495
13838
  } catch {
13496
- return raw.replace(SENSITIVE_QUERY, (_m, name) => `${name}=[Filtered]`);
13839
+ return raw.replace(SENSITIVE_QUERY_LEGACY, (_m, name) => `${name}=[Filtered]`);
13497
13840
  }
13498
13841
  }
13499
13842
  function sanitizeNetworkUrl(raw, opts) {
@@ -13590,6 +13933,9 @@ function startRecorder(options) {
13590
13933
  function isAbortError(error) {
13591
13934
  return error instanceof Error && error.name === "AbortError";
13592
13935
  }
13936
+ function isTimeoutError(error) {
13937
+ return error instanceof Error && error.name === "TimeoutError";
13938
+ }
13593
13939
  function isLikelyNetworkFetchError(error) {
13594
13940
  if (!(error instanceof Error)) return false;
13595
13941
  if (error.name === "AbortError" || error.name === "TimeoutError") return false;
@@ -13597,6 +13943,9 @@ function isLikelyNetworkFetchError(error) {
13597
13943
  const msg = error.message.toLowerCase().trim();
13598
13944
  return msg === "failed to fetch" || msg === "load failed" || msg === "networkerror when attempting to fetch resource." || msg.startsWith("networkerror") || msg.includes("failed to fetch");
13599
13945
  }
13946
+ function isCorrelatableTransportError(error) {
13947
+ return isLikelyNetworkFetchError(error) || isTimeoutError(error);
13948
+ }
13600
13949
  function describeUnknownError(error) {
13601
13950
  if (error instanceof Error) {
13602
13951
  return {
@@ -13744,6 +14093,56 @@ function urlMatchesIgnoreList(url, ignoreUrls) {
13744
14093
  }
13745
14094
  return false;
13746
14095
  }
14096
+ function normalizeNetworkOrigin(raw) {
14097
+ const trimmed = (raw || "").trim();
14098
+ if (!trimmed || trimmed === "*") return trimmed;
14099
+ try {
14100
+ return new URL(trimmed).origin;
14101
+ } catch {
14102
+ try {
14103
+ return new URL(`https://${trimmed}`).origin;
14104
+ } catch {
14105
+ return trimmed.replace(/\/+$/, "");
14106
+ }
14107
+ }
14108
+ }
14109
+ function resolvePageOrigin2(pageOrigin) {
14110
+ if (pageOrigin) {
14111
+ try {
14112
+ return new URL(pageOrigin).origin;
14113
+ } catch {
14114
+ return pageOrigin.replace(/\/+$/, "");
14115
+ }
14116
+ }
14117
+ if (typeof location !== "undefined" && location.origin) {
14118
+ return location.origin;
14119
+ }
14120
+ return "";
14121
+ }
14122
+ function resolveRequestOrigin(rawUrl, pageOrigin) {
14123
+ const base = pageOrigin || (typeof location !== "undefined" ? location.href : void 0);
14124
+ try {
14125
+ return new URL(rawUrl, base).origin;
14126
+ } catch {
14127
+ return "";
14128
+ }
14129
+ }
14130
+ function isAllowedNetworkOrigin(requestOrigin, opts) {
14131
+ if (!requestOrigin) return false;
14132
+ const allow = opts.networkErrorOrigins.map(normalizeNetworkOrigin);
14133
+ if (allow.includes("*")) return true;
14134
+ const page = resolvePageOrigin2(opts.pageOrigin);
14135
+ if (page && requestOrigin === page) return true;
14136
+ return allow.includes(requestOrigin);
14137
+ }
14138
+ function classifyNetworkParty(requestOrigin, pageOrigin) {
14139
+ const page = resolvePageOrigin2(pageOrigin);
14140
+ if (page && requestOrigin && requestOrigin === page) return "first_party";
14141
+ return "third_party";
14142
+ }
14143
+ function isHttpOkStatus(status) {
14144
+ return status >= 200 && status < 300;
14145
+ }
13747
14146
  function shouldPromoteFailedRequest(meta, opts) {
13748
14147
  if (!opts.captureFailedRequests) return false;
13749
14148
  if (typeof meta.status !== "number" || Number.isNaN(meta.status)) return false;
@@ -13754,6 +14153,13 @@ function shouldPromoteFailedRequest(meta, opts) {
13754
14153
  opts.talariaBaseUrl
13755
14154
  );
13756
14155
  if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
14156
+ const requestOrigin = meta.origin || resolveRequestOrigin(meta.url || "", opts.pageOrigin);
14157
+ if (!isAllowedNetworkOrigin(requestOrigin, {
14158
+ networkErrorOrigins: opts.networkErrorOrigins ?? [],
14159
+ pageOrigin: opts.pageOrigin
14160
+ })) {
14161
+ return false;
14162
+ }
13757
14163
  return true;
13758
14164
  }
13759
14165
  function shouldPromoteNetworkError(meta, opts) {
@@ -13767,6 +14173,13 @@ function shouldPromoteNetworkError(meta, opts) {
13767
14173
  opts.talariaBaseUrl
13768
14174
  );
13769
14175
  if (urlMatchesIgnoreList(meta.url || "", ignore)) return false;
14176
+ const requestOrigin = meta.origin || resolveRequestOrigin(meta.url || "", opts.pageOrigin);
14177
+ if (!isAllowedNetworkOrigin(requestOrigin, {
14178
+ networkErrorOrigins: opts.networkErrorOrigins ?? [],
14179
+ pageOrigin: opts.pageOrigin
14180
+ })) {
14181
+ return false;
14182
+ }
13770
14183
  return true;
13771
14184
  }
13772
14185
  function enrichNetworkMeta(meta) {
@@ -13777,7 +14190,8 @@ function enrichNetworkMeta(meta) {
13777
14190
  failureKind: meta.ok === false ? "http" : meta.failureKind
13778
14191
  };
13779
14192
  }
13780
- if (meta.ok === false || meta.errorName || meta.errorMessage || meta.aborted) {
14193
+ const hasTransportSignal = !!meta.errorName || !!meta.errorMessage || !!meta.aborted || meta.failureKind === "abort" || meta.failureKind === "timeout" || meta.failureKind === "network";
14194
+ if (hasTransportSignal) {
13781
14195
  const failureKind = meta.failureKind === "abort" || meta.failureKind === "timeout" || meta.failureKind === "network" ? meta.failureKind : classifyTransportFailure({
13782
14196
  aborted: meta.aborted,
13783
14197
  errorName: meta.errorName
@@ -13790,10 +14204,13 @@ function enrichNetworkMeta(meta) {
13790
14204
  }
13791
14205
  return meta;
13792
14206
  }
13793
- function networkUrlParts(rawUrl, includeQuery) {
13794
- const parts = sanitizeNetworkUrl(rawUrl, { includeQuery });
14207
+ function networkUrlParts(rawUrl, includeQuery, pageOrigin) {
14208
+ const baseHref = pageOrigin || (typeof location !== "undefined" ? location.href : void 0);
14209
+ const parts = sanitizeNetworkUrl(rawUrl, { includeQuery, baseHref });
14210
+ const origin = resolveRequestOrigin(rawUrl, pageOrigin);
13795
14211
  return {
13796
14212
  url: parts.url,
14213
+ origin: origin || void 0,
13797
14214
  hostname: parts.hostname,
13798
14215
  pathname: parts.pathname,
13799
14216
  ...parts.search ? { search: parts.search } : {}
@@ -13809,14 +14226,22 @@ function installNetworkHook(options = {}) {
13809
14226
  captureNetworkErrors: options.captureNetworkErrors ?? true,
13810
14227
  failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
13811
14228
  failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
14229
+ networkErrorOrigins: options.networkErrorOrigins ?? [],
14230
+ pageOrigin: options.pageOrigin,
13812
14231
  talariaBaseUrl: options.talariaBaseUrl
13813
14232
  };
13814
14233
  const includeQuery = options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false;
13815
14234
  const handleMeta = (meta) => {
13816
14235
  const { rawUrl, ...rest } = meta;
14236
+ const parts = networkUrlParts(rawUrl, includeQuery, matchOpts.pageOrigin);
14237
+ const party = classifyNetworkParty(
14238
+ parts.origin || "",
14239
+ matchOpts.pageOrigin
14240
+ );
13817
14241
  const enriched = enrichNetworkMeta({
13818
14242
  ...rest,
13819
- ...networkUrlParts(rawUrl, includeQuery)
14243
+ ...parts,
14244
+ party
13820
14245
  });
13821
14246
  try {
13822
14247
  emitNetwork(enriched);
@@ -13836,6 +14261,7 @@ function installNetworkHook(options = {}) {
13836
14261
  const rawUrl = typeof input2 === "string" ? input2 : input2 instanceof URL ? input2.toString() : input2.url;
13837
14262
  try {
13838
14263
  const response = await originalFetch(input2, init);
14264
+ const opaque = response.type === "opaque" || response.type === "opaqueredirect";
13839
14265
  handleMeta({
13840
14266
  method,
13841
14267
  rawUrl,
@@ -13843,7 +14269,8 @@ function installNetworkHook(options = {}) {
13843
14269
  transport: "fetch",
13844
14270
  status: response.status,
13845
14271
  durationMs: Date.now() - started,
13846
- ok: response.ok
14272
+ // Opaque responses resolve successfully with status 0 — not a failure.
14273
+ ok: opaque ? true : response.ok
13847
14274
  });
13848
14275
  return response;
13849
14276
  } catch (error) {
@@ -13905,7 +14332,7 @@ function installNetworkHook(options = {}) {
13905
14332
  transport: "xhr",
13906
14333
  status,
13907
14334
  durationMs: Date.now() - (this.__talariaStarted ?? Date.now()),
13908
- ok: status >= 200 && status < 400,
14335
+ ok: isHttpOkStatus(status),
13909
14336
  ...transportFail ? {
13910
14337
  failureKind,
13911
14338
  errorName,
@@ -13914,7 +14341,7 @@ function installNetworkHook(options = {}) {
13914
14341
  } : {}
13915
14342
  });
13916
14343
  };
13917
- this.addEventListener("loadend", onDone);
14344
+ this.addEventListener("loadend", onDone, { once: true });
13918
14345
  return originalSend.call(this, body);
13919
14346
  };
13920
14347
  }
@@ -13926,10 +14353,26 @@ function installNetworkHook(options = {}) {
13926
14353
  }
13927
14354
 
13928
14355
  // src/client.ts
14356
+ var PLATFORM_JAVASCRIPT = "javascript";
14357
+ var EXTRA_LOCATION_KEYS = /* @__PURE__ */ new Set([
14358
+ "exception_class",
14359
+ "file",
14360
+ "line",
14361
+ "code",
14362
+ "filename",
14363
+ "lineno",
14364
+ "colno"
14365
+ ]);
14366
+ function networkExceptionClass(failure) {
14367
+ if (failure.failureKind === "http") return "HttpError";
14368
+ if (failure.failureKind === "timeout") return "TimeoutError";
14369
+ return "NetworkError";
14370
+ }
13929
14371
  function networkFailureTags(failure) {
13930
14372
  return {
13931
14373
  "http.method": failure.method || "GET",
13932
14374
  "network.failure_kind": failure.failureKind ?? "network",
14375
+ ...failure.party ? { "network.party": failure.party } : {},
13933
14376
  ...failure.transport ? { "network.transport": failure.transport } : {},
13934
14377
  ...failure.errorName ? { "network.error_name": failure.errorName } : {}
13935
14378
  };
@@ -13939,6 +14382,7 @@ function networkFailureExtra(failure) {
13939
14382
  method: failure.method || "GET",
13940
14383
  url: failure.url || "(unknown url)"
13941
14384
  };
14385
+ if (failure.origin) http.origin = failure.origin;
13942
14386
  if (failure.hostname) http.hostname = failure.hostname;
13943
14387
  if (failure.pathname) http.pathname = failure.pathname;
13944
14388
  if (failure.search) http.search = failure.search;
@@ -13951,14 +14395,69 @@ function networkFailureExtra(failure) {
13951
14395
  };
13952
14396
  if (failure.errorName) fail.name = failure.errorName;
13953
14397
  if (failure.errorMessage) fail.message = failure.errorMessage;
14398
+ const statusCode = typeof failure.status === "number" && failure.status > 0 ? failure.status : null;
13954
14399
  return {
13955
14400
  http,
13956
14401
  failure: fail,
14402
+ network: {
14403
+ party: failure.party ?? "third_party",
14404
+ durationMs: failure.durationMs,
14405
+ aborted: failure.aborted ?? false,
14406
+ ok: failure.ok ?? false
14407
+ },
14408
+ // Top-level for server fingerprint compatibility (status only —
14409
+ // exception type lives on first-class `exception.values[0].type`).
14410
+ status_code: statusCode,
13957
14411
  durationMs: failure.durationMs,
13958
14412
  aborted: failure.aborted ?? false,
13959
14413
  ok: failure.ok ?? false
13960
14414
  };
13961
14415
  }
14416
+ function networkExceptionData(failure, message) {
14417
+ return {
14418
+ values: [
14419
+ {
14420
+ type: networkExceptionClass(failure),
14421
+ value: message,
14422
+ mechanism: {
14423
+ type: "http",
14424
+ handled: true,
14425
+ synthetic: true
14426
+ }
14427
+ }
14428
+ ]
14429
+ };
14430
+ }
14431
+ function scrubLegacyExceptionExtra(extra) {
14432
+ if (!extra) return void 0;
14433
+ const out = {};
14434
+ for (const [key, value] of Object.entries(extra)) {
14435
+ if (EXTRA_LOCATION_KEYS.has(key)) continue;
14436
+ out[key] = value;
14437
+ }
14438
+ return Object.keys(out).length ? out : void 0;
14439
+ }
14440
+ function buildExceptionFromError(err, context, inAppOptions) {
14441
+ const mechanism = context?.mechanism ?? {
14442
+ type: "generic",
14443
+ handled: true
14444
+ };
14445
+ const stacktrace = applySourceLocation(
14446
+ parseStackTrace(err.stack, inAppOptions),
14447
+ context?.source,
14448
+ inAppOptions
14449
+ );
14450
+ return {
14451
+ values: [
14452
+ {
14453
+ type: err.name || "Error",
14454
+ value: err.message || err.name || "Error",
14455
+ mechanism,
14456
+ ...stacktrace ? { stacktrace } : {}
14457
+ }
14458
+ ]
14459
+ };
14460
+ }
13962
14461
  function mergeNetworkFailureContext(context, failure) {
13963
14462
  return {
13964
14463
  ...context,
@@ -13995,13 +14494,17 @@ function resolveOptions(options) {
13995
14494
  inlineStylesheet: options.inlineStylesheet ?? false,
13996
14495
  blockSelector: options.blockSelector ?? "",
13997
14496
  userId: options.userId,
13998
- tags: options.tags,
14497
+ tags: mergeTags(options.tags),
13999
14498
  disableDefaultIntegrations: options.disableDefaultIntegrations ?? false,
14000
14499
  captureFailedRequests: options.captureFailedRequests ?? true,
14001
14500
  captureNetworkErrors: options.captureNetworkErrors ?? true,
14501
+ networkErrorOrigins: options.networkErrorOrigins ?? [],
14002
14502
  includeNetworkUrlQuery: options.captureRequestQueryParameters ?? options.includeNetworkUrlQuery ?? false,
14003
14503
  failedRequestStatusCodes: options.failedRequestStatusCodes ?? [[500, 599]],
14004
- failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? []
14504
+ failedRequestIgnoreUrls: options.failedRequestIgnoreUrls ?? [],
14505
+ inAppAllowUrls: options.inAppAllowUrls ?? [],
14506
+ inAppDenyUrls: options.inAppDenyUrls ?? [],
14507
+ inAppOrigins: options.inAppOrigins ?? []
14005
14508
  };
14006
14509
  }
14007
14510
  var RECENT_NETWORK_FAILURE_MS = 5e3;
@@ -14128,6 +14631,8 @@ var TalariaClient = class {
14128
14631
  installNetworkHook({
14129
14632
  captureFailedRequests: this.options.captureFailedRequests,
14130
14633
  captureNetworkErrors: this.options.captureNetworkErrors,
14634
+ networkErrorOrigins: this.options.networkErrorOrigins,
14635
+ pageOrigin: typeof location !== "undefined" ? location.origin : void 0,
14131
14636
  includeNetworkUrlQuery: this.options.includeNetworkUrlQuery,
14132
14637
  failedRequestStatusCodes: this.options.failedRequestStatusCodes,
14133
14638
  failedRequestIgnoreUrls: this.options.failedRequestIgnoreUrls,
@@ -14146,17 +14651,21 @@ var TalariaClient = class {
14146
14651
  failureKind: "http",
14147
14652
  aborted: false
14148
14653
  };
14149
- void this.captureMessage(
14150
- `HTTP ${status}: ${method} ${url}`,
14151
- status >= 500 ? "error" : "warning",
14152
- {
14654
+ const message = `HTTP ${status}: ${method} ${url}`;
14655
+ void this.capture({
14656
+ message,
14657
+ level: status >= 500 ? "error" : "warning",
14658
+ title: networkExceptionClass(enriched),
14659
+ exception: networkExceptionData(enriched, message),
14660
+ platform: PLATFORM_JAVASCRIPT,
14661
+ context: {
14153
14662
  tags: {
14154
14663
  ...networkFailureTags(enriched),
14155
14664
  "http.status_code": String(status)
14156
14665
  },
14157
14666
  extra: networkFailureExtra(enriched)
14158
14667
  }
14159
- );
14668
+ });
14160
14669
  },
14161
14670
  onNetworkError: (meta) => {
14162
14671
  this.rememberNetworkFailure(meta, { promoted: true });
@@ -14164,14 +14673,18 @@ var TalariaClient = class {
14164
14673
  const url = meta.url || "(unknown url)";
14165
14674
  const errLabel = meta.errorName && meta.errorMessage ? `${meta.errorName}: ${meta.errorMessage}` : meta.errorMessage || meta.errorName || "Failed to fetch";
14166
14675
  const prefix = meta.failureKind === "timeout" ? "Timeout error" : "Network error";
14167
- void this.captureMessage(
14168
- `${prefix}: ${method} ${url} \u2014 ${errLabel}`,
14169
- "error",
14170
- {
14676
+ const message = `${prefix}: ${method} ${url} \u2014 ${errLabel}`;
14677
+ void this.capture({
14678
+ message,
14679
+ level: "error",
14680
+ title: networkExceptionClass(meta),
14681
+ exception: networkExceptionData(meta, message),
14682
+ platform: PLATFORM_JAVASCRIPT,
14683
+ context: {
14171
14684
  tags: networkFailureTags(meta),
14172
14685
  extra: networkFailureExtra(meta)
14173
14686
  }
14174
- );
14687
+ });
14175
14688
  }
14176
14689
  }),
14177
14690
  installVisibilityResumeHook(() => this.onForegroundResume())
@@ -14208,7 +14721,7 @@ var TalariaClient = class {
14208
14721
  if (this.capturing || this.ingestDisabled) return;
14209
14722
  const err = error instanceof Error ? error : new Error(typeof error === "string" ? error : "Unknown error");
14210
14723
  if (isAbortError(err)) return;
14211
- const filename = typeof context?.extra?.filename === "string" ? context.extra.filename : void 0;
14724
+ const filename = context?.source?.filename;
14212
14725
  if (isBrowserExtensionNoise({
14213
14726
  message: err.message,
14214
14727
  stack: err.stack,
@@ -14221,6 +14734,7 @@ var TalariaClient = class {
14221
14734
  }
14222
14735
  const correlated = this.consumeCorrelatedNetworkFailure(err);
14223
14736
  if (correlated?.promoted) return;
14737
+ if (correlated && correlated.party === "third_party") return;
14224
14738
  const mergedContext = correlated ? mergeNetworkFailureContext(context, correlated) : context;
14225
14739
  this.capturing = true;
14226
14740
  try {
@@ -14229,6 +14743,12 @@ var TalariaClient = class {
14229
14743
  level: "error",
14230
14744
  title: err.name || "Error",
14231
14745
  stackTrace: err.stack,
14746
+ exception: buildExceptionFromError(
14747
+ err,
14748
+ mergedContext,
14749
+ this.inAppFrameOptions()
14750
+ ),
14751
+ platform: PLATFORM_JAVASCRIPT,
14232
14752
  context: mergedContext
14233
14753
  });
14234
14754
  } finally {
@@ -14244,6 +14764,13 @@ var TalariaClient = class {
14244
14764
  context
14245
14765
  });
14246
14766
  }
14767
+ /**
14768
+ * Returns a scoped capture facade that merges [tags] into every event.
14769
+ * Nested `withTags` calls merge further (later keys win).
14770
+ */
14771
+ withTags(tags) {
14772
+ return createScopedTalaria(this, mergeTags(tags));
14773
+ }
14247
14774
  async flush(opts) {
14248
14775
  if (!this.options || !this.transport || this.closed) return;
14249
14776
  const keepalive = opts?.keepalive ?? false;
@@ -14408,13 +14935,17 @@ var TalariaClient = class {
14408
14935
  }
14409
14936
  const queuedMs = Math.max(0, Date.now() - occurredAt.getTime());
14410
14937
  const tags = applyReplayCaptureTags(
14411
- {
14412
- ...this.browserContext ? browserContextTags(this.browserContext) : {},
14413
- ...this.options.tags ?? {},
14414
- ...args.context?.tags ?? {}
14415
- },
14938
+ mergeTags(
14939
+ this.browserContext ? browserContextTags(this.browserContext) : {},
14940
+ this.options.tags,
14941
+ args.context?.tags
14942
+ ),
14416
14943
  errorClipOutcome
14417
14944
  );
14945
+ warnSuspiciousTags(tags, this.options.environment);
14946
+ const scrubbedContextExtra = scrubLegacyExceptionExtra(
14947
+ args.context?.extra
14948
+ );
14418
14949
  const extra = mergeReplayCaptureExtra(
14419
14950
  {
14420
14951
  ...this.browserContext ? {
@@ -14436,7 +14967,7 @@ var TalariaClient = class {
14436
14967
  // Time spent in capture() before ingest (mostly replay flush).
14437
14968
  ...queuedMs >= 50 ? { queuedMs } : {}
14438
14969
  },
14439
- ...args.context?.extra
14970
+ ...scrubbedContextExtra ?? {}
14440
14971
  },
14441
14972
  errorClipOutcome
14442
14973
  );
@@ -14448,6 +14979,8 @@ var TalariaClient = class {
14448
14979
  eventType: levelToEventType(args.level),
14449
14980
  title: args.title ?? args.context?.title,
14450
14981
  stackTrace: args.stackTrace,
14982
+ exception: args.exception,
14983
+ platform: args.platform,
14451
14984
  release: this.options.release,
14452
14985
  userId: args.context?.userId ?? this.options.userId,
14453
14986
  sessionId: this.sessionId ?? void 0,
@@ -14697,12 +15230,36 @@ var TalariaClient = class {
14697
15230
  this.resetToBufferMode();
14698
15231
  }
14699
15232
  }
15233
+ inAppFrameOptions() {
15234
+ const opts = this.options;
15235
+ let pageOrigin;
15236
+ try {
15237
+ pageOrigin = resolvePageOrigin(
15238
+ typeof window !== "undefined" ? window.location?.origin : void 0
15239
+ );
15240
+ } catch {
15241
+ pageOrigin = void 0;
15242
+ }
15243
+ return {
15244
+ pageOrigin,
15245
+ allowUrls: opts?.inAppAllowUrls,
15246
+ denyUrls: opts?.inAppDenyUrls,
15247
+ inAppOrigins: opts?.inAppOrigins
15248
+ };
15249
+ }
14700
15250
  pruneRecentNetworkFailures(now = Date.now()) {
14701
15251
  this.recentNetworkFailures = this.recentNetworkFailures.filter(
14702
15252
  (f) => now - f.at <= RECENT_NETWORK_FAILURE_MS
14703
15253
  );
14704
15254
  }
14705
15255
  rememberNetworkFailure(meta, opts) {
15256
+ if (this.options) {
15257
+ const ignore = buildFailedRequestIgnoreUrls(
15258
+ this.options.failedRequestIgnoreUrls,
15259
+ this.options.baseUrl
15260
+ );
15261
+ if (urlMatchesIgnoreList(meta.url || "", ignore)) return;
15262
+ }
14706
15263
  const now = Date.now();
14707
15264
  this.pruneRecentNetworkFailures(now);
14708
15265
  for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
@@ -14713,6 +15270,8 @@ var TalariaClient = class {
14713
15270
  existing.errorName = meta.errorName ?? existing.errorName;
14714
15271
  existing.aborted = meta.aborted ?? existing.aborted;
14715
15272
  existing.failureKind = meta.failureKind ?? existing.failureKind;
15273
+ existing.party = meta.party ?? existing.party;
15274
+ existing.origin = meta.origin ?? existing.origin;
14716
15275
  return;
14717
15276
  }
14718
15277
  }
@@ -14723,11 +15282,12 @@ var TalariaClient = class {
14723
15282
  });
14724
15283
  }
14725
15284
  /**
14726
- * Find a recent transport failure for a bare fetch TypeError.
15285
+ * Find a recent transport failure for a bare fetch TypeError / TimeoutError.
14727
15286
  * Consumes the entry so a later duplicate path cannot reuse it.
14728
15287
  */
14729
15288
  consumeCorrelatedNetworkFailure(error) {
14730
- if (!isLikelyNetworkFetchError(error)) return null;
15289
+ if (!isCorrelatableTransportError(error)) return null;
15290
+ const wantKind = isTimeoutError(error) ? "timeout" : "network";
14731
15291
  const now = Date.now();
14732
15292
  this.pruneRecentNetworkFailures(now);
14733
15293
  const takeAt = (index2) => {
@@ -14736,14 +15296,14 @@ var TalariaClient = class {
14736
15296
  };
14737
15297
  for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
14738
15298
  const failure = this.recentNetworkFailures[i];
14739
- if (failure.aborted || failure.failureKind !== "network") continue;
15299
+ if (failure.aborted || failure.failureKind !== wantKind) continue;
14740
15300
  if (failure.errorMessage && failure.errorMessage === error.message) {
14741
15301
  return takeAt(i);
14742
15302
  }
14743
15303
  }
14744
15304
  for (let i = this.recentNetworkFailures.length - 1; i >= 0; i--) {
14745
15305
  const failure = this.recentNetworkFailures[i];
14746
- if (failure.aborted || failure.failureKind !== "network") continue;
15306
+ if (failure.aborted || failure.failureKind !== wantKind) continue;
14747
15307
  return takeAt(i);
14748
15308
  }
14749
15309
  return null;
@@ -14761,7 +15321,8 @@ var TalariaClient = class {
14761
15321
  return;
14762
15322
  }
14763
15323
  void this.captureException(error, {
14764
- extra: {
15324
+ mechanism: { type: "onerror", handled: false },
15325
+ source: {
14765
15326
  filename: event.filename,
14766
15327
  lineno: event.lineno,
14767
15328
  colno: event.colno
@@ -14783,7 +15344,9 @@ var TalariaClient = class {
14783
15344
  })) {
14784
15345
  return;
14785
15346
  }
14786
- void this.captureException(reason);
15347
+ void this.captureException(reason, {
15348
+ mechanism: { type: "unhandledrejection", handled: false }
15349
+ });
14787
15350
  };
14788
15351
  window.addEventListener("error", onError);
14789
15352
  window.addEventListener("unhandledrejection", onRejection);
@@ -15045,6 +15608,23 @@ var TalariaClient = class {
15045
15608
  }
15046
15609
  }
15047
15610
  };
15611
+ function createScopedTalaria(client2, scopeTags) {
15612
+ const mergeContext = (context) => ({
15613
+ ...context,
15614
+ tags: mergeTags(scopeTags, context?.tags)
15615
+ });
15616
+ return {
15617
+ captureException(error, context) {
15618
+ return client2.captureException(error, mergeContext(context));
15619
+ },
15620
+ captureMessage(message, level, context) {
15621
+ return client2.captureMessage(message, level, mergeContext(context));
15622
+ },
15623
+ withTags(tags) {
15624
+ return createScopedTalaria(client2, mergeTags(scopeTags, tags));
15625
+ }
15626
+ };
15627
+ }
15048
15628
 
15049
15629
  // src/index.ts
15050
15630
  var client = new TalariaClient();
@@ -15058,6 +15638,9 @@ var Talaria = {
15058
15638
  captureMessage(message, level, context) {
15059
15639
  return client.captureMessage(message, level, context);
15060
15640
  },
15641
+ withTags(tags) {
15642
+ return client.withTags(tags);
15643
+ },
15061
15644
  getReplayId() {
15062
15645
  return client.getReplayId();
15063
15646
  },
@@ -15075,14 +15658,22 @@ export {
15075
15658
  MAX_SEGMENTS_ERROR_CLIP,
15076
15659
  REPLAY_CAPTURE_REASON_TAG,
15077
15660
  REPLAY_CAPTURE_TAG,
15661
+ RESERVED_TAG_KEYS,
15078
15662
  TARGET_COMPRESSED_SEGMENT_BYTES,
15079
15663
  Talaria,
15080
15664
  TalariaClient,
15665
+ applySourceLocation,
15081
15666
  computeErrorClipDeadlineMs,
15082
15667
  index_default as default,
15083
15668
  fitCompressedPrefix,
15084
15669
  isErrorClipBudgetExhausted,
15085
- planOversizedRetry
15670
+ isInAppFrame,
15671
+ mergeTags,
15672
+ normalizeTags,
15673
+ parseStackLine,
15674
+ parseStackTrace,
15675
+ planOversizedRetry,
15676
+ resolvePageOrigin
15086
15677
  };
15087
15678
  /*! Bundled license information:
15088
15679