@hanzo/event 0.3.48 → 0.3.50

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/react.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createContext, Component, useEffect, createElement, useContext, useRef, useSyncExternalStore } from 'react';
2
- import { PAGEVIEW, EXCEPTION } from '@hanzo/events';
2
+ import { PAGEVIEW } from '@hanzo/events';
3
3
 
4
4
  // src/react.tsx
5
5
 
@@ -68,22 +68,6 @@ function hasAttribution(a) {
68
68
  );
69
69
  }
70
70
 
71
- // src/dsn.ts
72
- var PRODUCT_PROJECT = Object.freeze({
73
- console: "019fa40b-94ae-7f1d-8f7b-e92f123fad42",
74
- // console.hanzo.ai
75
- app: "019f9b1e-57eb-7171-9d92-72c0b85e4b4b",
76
- // hanzo.app
77
- site: "019f9b1e-5785-7359-ad0b-f75db8e58c99"
78
- // hanzo.ai (marketing; product `site`)
79
- });
80
- function dsnForProduct(product, key) {
81
- if (!product || !key) return void 0;
82
- const projectId = PRODUCT_PROJECT[product];
83
- if (!projectId) return void 0;
84
- return `https://${key}@api.hanzo.ai/v1/event/${projectId}`;
85
- }
86
-
87
71
  // src/org.ts
88
72
  var ORG_DOMAIN = Object.freeze({
89
73
  // A brand's domains are not a pattern — `hanzoskills.com` is Hanzo's and does
@@ -92,11 +76,15 @@ var ORG_DOMAIN = Object.freeze({
92
76
  // separate fact, which is the whole reason this is a table.
93
77
  hanzo: Object.freeze([
94
78
  "hanzo.ai",
79
+ "hanzo.agency",
95
80
  "hanzo.app",
96
81
  "hanzo.bot",
97
82
  "hanzo.chat",
98
83
  "hanzo.codes",
99
84
  "hanzo.id",
85
+ "hanzo.industries",
86
+ "hanzo.market",
87
+ "hanzo.network",
100
88
  "hanzo.sh",
101
89
  "hanzo.team",
102
90
  "hanzo.ventures",
@@ -116,7 +104,7 @@ var ORG_DOMAIN = Object.freeze({
116
104
  zoo: Object.freeze(["zoo.ngo", "zoo.network", "zoolabs.id", "zoolabs.io"])
117
105
  });
118
106
  var ORG_KEY = Object.freeze({
119
- hanzo: "pk-rM_CdaF2MQckGCrla113SrR1oH4zvqN8xh2I95Z9tY8",
107
+ hanzo: "pk-CmfLA2K6kvsPflrS9DSkt06H_kSoQB_21sjedt6VJdc",
120
108
  lux: "pk-gUZp6ZVfhJzSwK-rb4oLbVkpCnMBx5uSCpxf_5yEhQk",
121
109
  zoo: "pk-3TKpKnERV9AQSsBUERWkZejC1O1mUxc1jRzsP3MPbs4"
122
110
  });
@@ -276,6 +264,93 @@ function scrubText(s, capturePII = false) {
276
264
  return s;
277
265
  }
278
266
 
267
+ // src/throwable.ts
268
+ var MAX_FRAMES = 50;
269
+ var MAX_LINES = 500;
270
+ var MAX_LINE_LEN = 2048;
271
+ var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
272
+ var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
273
+ function inApp(file) {
274
+ if (!file) return false;
275
+ return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
276
+ }
277
+ function framesFromStack(stack) {
278
+ if (!stack) return [];
279
+ const lines = stack.split("\n", MAX_LINES);
280
+ const frames = [];
281
+ for (const raw of lines) {
282
+ if (raw.length > MAX_LINE_LEN) continue;
283
+ const line = raw.trimEnd();
284
+ if (!line) continue;
285
+ let fn;
286
+ let file = "";
287
+ let lineno = 0;
288
+ let colno = 0;
289
+ const v = V8_FRAME.exec(line);
290
+ if (v) {
291
+ fn = v[1];
292
+ if (v[2]) {
293
+ file = v[2];
294
+ lineno = Number(v[3]) || 0;
295
+ colno = Number(v[4]) || 0;
296
+ } else {
297
+ file = (v[5] || "").trim();
298
+ }
299
+ } else {
300
+ const f = MOZ_FRAME.exec(line);
301
+ if (!f) continue;
302
+ fn = f[1];
303
+ file = f[2];
304
+ lineno = Number(f[3]) || 0;
305
+ colno = Number(f[4]) || 0;
306
+ }
307
+ if (!file && !fn) continue;
308
+ frames.push({
309
+ function: fn || "<anonymous>",
310
+ filename: file,
311
+ abs_path: file,
312
+ lineno,
313
+ colno,
314
+ in_app: inApp(file)
315
+ });
316
+ }
317
+ frames.reverse();
318
+ if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
319
+ return frames;
320
+ }
321
+ function normalizeError(err) {
322
+ if (err instanceof Error) {
323
+ const name = read(err, "name");
324
+ const message = read(err, "message");
325
+ const stack = read(err, "stack");
326
+ return {
327
+ name: typeof name === "string" && name ? name : "Error",
328
+ message: typeof message === "string" && message ? message : str(err),
329
+ stack: typeof stack === "string" ? stack : void 0
330
+ };
331
+ }
332
+ if (typeof err === "string") return { name: "Error", message: err };
333
+ try {
334
+ return { name: "Error", message: JSON.stringify(err) ?? str(err) };
335
+ } catch {
336
+ return { name: "Error", message: str(err) };
337
+ }
338
+ }
339
+ function read(o, k) {
340
+ try {
341
+ return o[k];
342
+ } catch {
343
+ return void 0;
344
+ }
345
+ }
346
+ function str(v) {
347
+ try {
348
+ return String(v);
349
+ } catch {
350
+ return "[unstringifiable]";
351
+ }
352
+ }
353
+
279
354
  // src/anon.js
280
355
  var HZ_ANON_KEY = "iam-anon-id";
281
356
  var HZ_ANON_ADOPT = ["hz_anon_id", "hz_id"];
@@ -394,292 +469,6 @@ function hzAnonAdopt(id) {
394
469
  }
395
470
  }
396
471
 
397
- // package.json
398
- var version = "0.3.48";
399
-
400
- // src/version.ts
401
- var VERSION = version;
402
-
403
- // src/sentry.ts
404
- var MAX_FRAMES = 50;
405
- var MAX_LINES = 500;
406
- var MAX_LINE_LEN = 2048;
407
- var MAX_TAG_LEN = 1024;
408
- var MAX_TAGS = 50;
409
- function eventId() {
410
- return hzUuidv7().replace(/-/g, "");
411
- }
412
- function byteLen(s) {
413
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
414
- if (typeof Buffer !== "undefined") return Buffer.byteLength(s, "utf8");
415
- return s.length;
416
- }
417
- function parseDsn(dsn) {
418
- if (!dsn) return null;
419
- const m = /^(https?):\/\/([^@]+)@([^/]+)(\/.*)?$/.exec(dsn.trim());
420
- if (!m) return null;
421
- const scheme = m[1];
422
- const publicKey = m[2];
423
- const host = m[3];
424
- const path = m[4] ?? "";
425
- if (!publicKey || !host) return null;
426
- const segs = path.split("/").filter(Boolean);
427
- const projectId = segs.length > 0 ? segs[segs.length - 1] : "";
428
- if (!projectId) return null;
429
- const origin = `${scheme}://${host}`;
430
- const base = `${origin}/${segs.map(encodeURIComponent).join("/")}`;
431
- const ingestUrl = `${base}/envelope/?sentry_key=${encodeURIComponent(publicKey)}`;
432
- return { publicKey, origin, projectId, base, ingestUrl };
433
- }
434
- var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
435
- var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
436
- function inApp(file) {
437
- if (!file) return false;
438
- return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
439
- }
440
- function framesFromStack(stack) {
441
- if (!stack) return [];
442
- const lines = stack.split("\n", MAX_LINES);
443
- const frames = [];
444
- for (const raw of lines) {
445
- if (raw.length > MAX_LINE_LEN) continue;
446
- const line = raw.trimEnd();
447
- if (!line) continue;
448
- let fn;
449
- let file = "";
450
- let lineno = 0;
451
- let colno = 0;
452
- const v = V8_FRAME.exec(line);
453
- if (v) {
454
- fn = v[1];
455
- if (v[2]) {
456
- file = v[2];
457
- lineno = Number(v[3]) || 0;
458
- colno = Number(v[4]) || 0;
459
- } else {
460
- file = (v[5] || "").trim();
461
- }
462
- } else {
463
- const f = MOZ_FRAME.exec(line);
464
- if (!f) continue;
465
- fn = f[1];
466
- file = f[2];
467
- lineno = Number(f[3]) || 0;
468
- colno = Number(f[4]) || 0;
469
- }
470
- if (!file && !fn) continue;
471
- frames.push({
472
- function: fn || "<anonymous>",
473
- filename: file,
474
- abs_path: file,
475
- lineno,
476
- colno,
477
- in_app: inApp(file)
478
- });
479
- }
480
- frames.reverse();
481
- if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
482
- return frames;
483
- }
484
- function normalizeError(err) {
485
- if (err instanceof Error) {
486
- const name = read(err, "name");
487
- const message = read(err, "message");
488
- const stack = read(err, "stack");
489
- return {
490
- name: typeof name === "string" && name ? name : "Error",
491
- message: typeof message === "string" && message ? message : str(err),
492
- stack: typeof stack === "string" ? stack : void 0
493
- };
494
- }
495
- if (typeof err === "string") return { name: "Error", message: err };
496
- try {
497
- return { name: "Error", message: JSON.stringify(err) ?? str(err) };
498
- } catch {
499
- return { name: "Error", message: str(err) };
500
- }
501
- }
502
- function read(o, k) {
503
- try {
504
- return o[k];
505
- } catch {
506
- return void 0;
507
- }
508
- }
509
- function str(v) {
510
- try {
511
- return String(v);
512
- } catch {
513
- return "[unstringifiable]";
514
- }
515
- }
516
- function coerceTag(v) {
517
- const s = typeof v === "string" ? v : (() => {
518
- try {
519
- return JSON.stringify(v) ?? String(v);
520
- } catch {
521
- try {
522
- return String(v);
523
- } catch {
524
- return "[unstringifiable]";
525
- }
526
- }
527
- })();
528
- return s.length > MAX_TAG_LEN ? s.slice(0, MAX_TAG_LEN) : s;
529
- }
530
- function buildSentryEvent(input) {
531
- const { error, options = {}, identity, capturePII = false } = input;
532
- const norm = normalizeError(error);
533
- const handled = options.handled !== false;
534
- const level = options.level ?? (handled ? "error" : "fatal");
535
- const tags = { handled: String(handled) };
536
- if (identity.product) tags.product = identity.product;
537
- if (identity.sessionId) tags.session = identity.sessionId;
538
- try {
539
- const props = options.properties ?? {};
540
- let n = 0;
541
- for (const k of Object.keys(props)) {
542
- if (n >= MAX_TAGS) break;
543
- try {
544
- const val = props[k];
545
- if (val === void 0 || val === null) continue;
546
- tags[k] = scrubText(coerceTag(val), capturePII);
547
- n++;
548
- } catch {
549
- continue;
550
- }
551
- }
552
- } catch {
553
- }
554
- const event = {
555
- event_id: input.id ?? eventId(),
556
- timestamp: (input.now ?? Date.now()) / 1e3,
557
- platform: "javascript",
558
- level,
559
- logger: identity.product,
560
- environment: identity.environment,
561
- release: identity.release,
562
- exception: {
563
- values: [
564
- {
565
- type: norm.name,
566
- value: scrubText(norm.message, capturePII),
567
- stacktrace: { frames: framesFromStack(norm.stack) }
568
- }
569
- ]
570
- },
571
- tags,
572
- sdk: { name: "@hanzo/event", version: VERSION }
573
- };
574
- if (identity.userId) event.user = { id: identity.userId };
575
- return event;
576
- }
577
- function buildEnvelope(event, dsn, sentAt) {
578
- const payload = JSON.stringify(event);
579
- const header = JSON.stringify({
580
- event_id: event.event_id,
581
- dsn: dsn.base,
582
- sent_at: (/* @__PURE__ */ new Date()).toISOString()
583
- });
584
- const itemHeader = JSON.stringify({
585
- type: "event",
586
- content_type: "application/json",
587
- length: byteLen(payload)
588
- });
589
- return `${header}
590
- ${itemHeader}
591
- ${payload}
592
- `;
593
- }
594
- var MAX_VALUE = 4096;
595
- function digest(s) {
596
- let out = "";
597
- for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
598
- let h = seed >>> 0;
599
- for (let i = 0; i < s.length; i++) {
600
- h ^= s.charCodeAt(i);
601
- h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
602
- }
603
- out += h.toString(16).padStart(8, "0");
604
- }
605
- return out;
606
- }
607
- function frameOf(f) {
608
- const source = f.filename ?? "";
609
- const name = f.function ?? "<anonymous>";
610
- const line = f.lineno ?? 0;
611
- const column = f.colno ?? 0;
612
- return {
613
- // Stable per code location, so the same frame groups and re-renders under one
614
- // key. The '/part' suffix is the product's format for one raw frame expanding
615
- // into several resolved ones; a browser frame is always part 0.
616
- raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
617
- mangled_name: name,
618
- source,
619
- line,
620
- column,
621
- in_app: f.in_app ?? false,
622
- lang: "javascript",
623
- resolved: false,
624
- resolve_failure: "no symbol set uploaded for this release",
625
- resolved_name: null,
626
- module: null
627
- };
628
- }
629
- function truncate2(s, max) {
630
- if (s.length <= max) return s;
631
- let end = max;
632
- const c = s.charCodeAt(end - 1);
633
- if (c >= 55296 && c <= 56319) end--;
634
- return s.slice(0, end);
635
- }
636
- function exceptionEntry(err, opts) {
637
- const n = normalizeError(err);
638
- const frames = framesFromStack(n.stack).slice(-50);
639
- const entry = {
640
- id: opts.id,
641
- type: n.name,
642
- value: truncate2(n.message, MAX_VALUE),
643
- mechanism: {
644
- type: "generic",
645
- handled: opts.handled,
646
- // An exception the app reported by hand was constructed, not thrown by the
647
- // runtime at a real call site.
648
- synthetic: !(err instanceof Error)
649
- }
650
- };
651
- if (frames.length > 0) {
652
- entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
653
- }
654
- return entry;
655
- }
656
- function fingerprint(entry) {
657
- const frames = entry.stacktrace?.frames ?? [];
658
- const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
659
- return digest([entry.type, ...pieces].join("\n"));
660
- }
661
- function exceptionProperties(err, opts) {
662
- const entry = exceptionEntry(err, opts);
663
- const frames = entry.stacktrace?.frames ?? [];
664
- const fp = fingerprint(entry);
665
- return {
666
- $exception_list: [entry],
667
- $exception_fingerprint: fp,
668
- // Records WHY this fingerprint holds. 'manual' is the honest value: the client
669
- // supplied the key rather than a server grouper deriving one.
670
- $exception_fingerprint_record: [{ type: "manual" }],
671
- $exception_type: entry.type,
672
- $exception_message: entry.value,
673
- $exception_level: opts.level ?? "error",
674
- $exception_handled: opts.handled,
675
- $exception_synthetic: entry.mechanism?.synthetic ?? false,
676
- $exception_types: [entry.type],
677
- $exception_values: [entry.value],
678
- $exception_sources: frames.map((f) => f.source).filter((s) => !!s),
679
- $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
680
- };
681
- }
682
-
683
472
  // src/storage.ts
684
473
  var KEY = {
685
474
  session: "hz_session",
@@ -831,20 +620,16 @@ function stripLink(href) {
831
620
  return hit ? u.pathname + u.search + u.hash : void 0;
832
621
  }
833
622
 
623
+ // package.json
624
+ var version = "0.3.50";
625
+
626
+ // src/version.ts
627
+ var VERSION = version;
628
+
834
629
  // src/core.ts
835
630
  var EVENT_PATH = "/v1/event";
836
631
  var DEFAULT_HOST = "https://api.hanzo.ai";
837
- var ENVELOPE_CONTENT_TYPE = "application/x-sentry-envelope";
838
632
  var BEACON_CONTENT_TYPE = "text/plain";
839
- function readEnvDsn() {
840
- try {
841
- if (typeof process !== "undefined" && process.env) {
842
- return process.env.NEXT_PUBLIC_HANZO_EVENT_DSN || process.env.HANZO_EVENT_DSN || void 0;
843
- }
844
- } catch {
845
- }
846
- return void 0;
847
- }
848
633
  function readEnv(name) {
849
634
  try {
850
635
  if (typeof process !== "undefined" && process.env) return process.env[name] || void 0;
@@ -857,8 +642,10 @@ function appendQuery(url, key, value) {
857
642
  }
858
643
  function normalizeError2(err) {
859
644
  const n = normalizeError(err);
860
- return { type: n.name, message: n.message, stack: n.stack };
645
+ const frames = framesFromStack(n.stack).slice(-50).map((f) => ({ function: f.function, file: f.filename, line: f.lineno, column: f.colno }));
646
+ return { type: n.name, message: n.message, stack: n.stack, frames };
861
647
  }
648
+ var viewport = () => isBrowser() ? { screen: `${window.screen?.width ?? 0}x${window.screen?.height ?? 0}`, language: navigator.language || "" } : {};
862
649
  var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
863
650
  function serializeBatch(batch) {
864
651
  try {
@@ -893,13 +680,13 @@ var DefaultTransport = class {
893
680
  }
894
681
  }
895
682
  if (typeof fetch !== "function") return;
896
- const simple = opts.ingestKey !== void 0 && opts.contentType === void 0;
683
+ const simple = opts.ingestKey !== void 0 && opts.token === void 0 && opts.contentType === void 0;
897
684
  const headers = {
898
685
  "Content-Type": simple ? BEACON_CONTENT_TYPE : opts.contentType ?? "application/json"
899
686
  };
900
- const bearer = simple ? void 0 : opts.ingestKey ?? opts.token;
687
+ const bearer = simple ? void 0 : opts.token ?? opts.ingestKey;
901
688
  if (bearer) headers.Authorization = `Bearer ${bearer}`;
902
- void fetch(simple ? appendQuery(url, "ingest_key", opts.ingestKey) : url, {
689
+ void fetch(opts.ingestKey ? appendQuery(url, "ingest_key", opts.ingestKey) : url, {
903
690
  method: "POST",
904
691
  headers,
905
692
  body,
@@ -953,35 +740,13 @@ var Analytics = class {
953
740
  ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY") ?? keyForPage()
954
741
  };
955
742
  this.transport = config.transport ?? new DefaultTransport();
956
- this.dsn = parseDsn(
957
- config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
958
- );
959
743
  }
960
744
  /** adopt gives this client a credential it does not have. The key belongs to
961
745
  * the stream, not to whichever caller happened to ask for the handle first,
962
- * so a later caller carrying one hands it over. The error plane derives from
963
- * the key, so it comes up here too when it was inert for want of one.
964
- * Present fields are never overwritten: the first caller's key stays. */
746
+ * so a later caller carrying one hands it over. Present fields are never overwritten: the first caller's key stays. */
965
747
  adopt(config) {
966
748
  if (config.ingestKey && !this.cfg.ingestKey) this.cfg.ingestKey = config.ingestKey;
967
749
  if (config.getToken && !this.cfg.getToken) this.cfg.getToken = config.getToken;
968
- if (!this.dsn) {
969
- this.dsn = parseDsn(
970
- config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
971
- );
972
- }
973
- }
974
- /** errorPlaneEnabled reports whether captured exceptions can actually reach the
975
- * error host. False means a DSN was never configured — the documented
976
- * fail-safe. Exposed so an app (or a test) can assert its wiring instead of
977
- * discovering months later that nothing was ever reported. */
978
- get errorPlaneEnabled() {
979
- return this.dsn !== null;
980
- }
981
- /** errorIngestUrl is the fully-derived envelope endpoint, or undefined when the
982
- * plane is inert. Diagnostics only. */
983
- get errorIngestUrl() {
984
- return this.dsn?.ingestUrl;
985
750
  }
986
751
  /** init is idempotent and browser-only for its side effects: capture first-touch
987
752
  * attribution, hydrate cohort, register the unload flush, and (unless opted out)
@@ -1061,7 +826,7 @@ var Analytics = class {
1061
826
  const view = (p ?? "") + "\0" + (isBrowser() ? window.location.href : "");
1062
827
  if (view === this.counted) return;
1063
828
  this.counted = view;
1064
- this.enqueue("pageview", PAGEVIEW, { path: p, properties });
829
+ this.enqueue("pageview", PAGEVIEW, { path: p, properties: { ...viewport(), ...properties } });
1065
830
  }
1066
831
  /** capture records a named product event with optional properties. Commerce
1067
832
  * fields (productId/quantity/revenue/currency) may be passed for order events. */
@@ -1069,45 +834,28 @@ var Analytics = class {
1069
834
  this.enqueue("event", event, { properties, ...commerce });
1070
835
  }
1071
836
  /** captureError reports a caught error, an unhandled rejection, a React render
1072
- * error, or a manual report to BOTH planes, from one call:
1073
- *
1074
- * - the ERROR PLANE — a real Sentry envelope to the DSN host. This is the one
1075
- * that produces an issue in sentry.hanzo.ai (grouping, stack frames, AST).
1076
- * Inert when no DSN is configured.
1077
- * - the EVENT STREAM — a `type:'error'` row in the cloud event warehouse, so
1078
- * an error stays correlated with the session's pageviews for product
1079
- * analysis (readable via GET /v1/errors).
1080
- *
1081
- * Both carry the SAME session and subject id, so an error and the pageview
1082
- * before it join up. Never throws back into the app; errors are higher-signal
1083
- * than pageviews, so both planes flush promptly (a crash may unload the page
1084
- * moments later). */
837
+ * error, or a manual report as ONE event of type 'error' on the one stream.
838
+ * It carries the exception (type, message, frames, stack, handled) and the
839
+ * release, environment, site, product and level that group and scope it, and
840
+ * flushes promptly (a crash may unload the page moments later). Never throws
841
+ * back into the app. */
1085
842
  captureError(err, context) {
1086
843
  if (this.reentrant) return;
1087
844
  this.reentrant = true;
1088
845
  try {
1089
- try {
1090
- this.sendError(err, context);
1091
- } catch {
1092
- }
1093
- try {
1094
- const handled = context?.handled ?? true;
1095
- const ex = normalizeError2(err);
1096
- ex.handled = handled;
1097
- this.enqueue("event", EXCEPTION, {
1098
- error: ex,
1099
- properties: {
1100
- ...context?.properties,
1101
- ...exceptionProperties(err, {
1102
- handled,
1103
- id: hzUuidv7(),
1104
- level: context?.level
1105
- })
1106
- }
1107
- });
1108
- this.flush();
1109
- } catch {
1110
- }
846
+ const handled = context?.handled ?? true;
847
+ const ex = normalizeError2(err);
848
+ ex.handled = handled;
849
+ this.enqueue("error", void 0, {
850
+ error: ex,
851
+ level: context?.level ?? (handled ? "error" : "fatal"),
852
+ release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
853
+ environment: this.cfg.environment ?? readEnv("NODE_ENV"),
854
+ site: isBrowser() ? window.location.hostname : void 0,
855
+ properties: context?.properties
856
+ });
857
+ this.flush();
858
+ } catch {
1111
859
  } finally {
1112
860
  this.reentrant = false;
1113
861
  }
@@ -1134,7 +882,7 @@ var Analytics = class {
1134
882
  this.queue = [];
1135
883
  this.clearTimer();
1136
884
  const token = this.cfg.getToken?.() ?? void 0;
1137
- const key = token ? void 0 : this.cfg.ingestKey?.trim() || void 0;
885
+ const key = this.cfg.ingestKey?.trim() || void 0;
1138
886
  const useBeacon = beacon && !token;
1139
887
  const body = serializeBatch(batch);
1140
888
  if (body === null) {
@@ -1150,39 +898,6 @@ var Analytics = class {
1150
898
  });
1151
899
  }
1152
900
  // ── internals ────────────────────────────────────────────────────────────
1153
- /** sendError frames one exception as a Sentry envelope and posts it to the DSN's
1154
- * ingest URL. The DSN's own key rides ?sentry_key= (the credential channel the
1155
- * server trusts, and the only one a headerless beacon can carry), so NO bearer
1156
- * or publishable key is attached here — the two planes authenticate
1157
- * independently. Errors are sent one envelope per event, immediately: batching
1158
- * a crash report is how you lose it. */
1159
- sendError(err, options) {
1160
- if (!this.cfg.enabled || !this.dsn) return;
1161
- const event = buildSentryEvent({
1162
- error: err,
1163
- options,
1164
- identity: this.errorIdentity(),
1165
- capturePII: this.cfg.capturePII ?? false
1166
- });
1167
- const body = buildEnvelope(event, this.dsn);
1168
- if (this.cfg.debug) console.debug("[event] error \u2192", this.dsn.ingestUrl, event.event_id);
1169
- this.transport.send(this.dsn.ingestUrl, body, {
1170
- beacon: false,
1171
- contentType: ENVELOPE_CONTENT_TYPE,
1172
- debug: this.cfg.debug
1173
- });
1174
- }
1175
- /** errorIdentity is the SAME identity the event stream stamps — the OIDC subject
1176
- * once identify() has run, else the anon id. Never email/PII. */
1177
- errorIdentity() {
1178
- return {
1179
- userId: this.personId ?? anonId(),
1180
- sessionId: sessionId(),
1181
- product: this.cfg.product,
1182
- release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
1183
- environment: this.cfg.environment ?? readEnv("NODE_ENV")
1184
- };
1185
- }
1186
901
  enqueue(kind, event, extra) {
1187
902
  if (!this.cfg.enabled) return;
1188
903
  if (!this.started) this.init();