@hanzo/event 0.3.44 → 0.3.49

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.
Files changed (50) hide show
  1. package/README.md +11 -50
  2. package/dist/core.d.ts +6 -36
  3. package/dist/core.d.ts.map +1 -1
  4. package/dist/index.cjs +242 -414
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +5 -5
  7. package/dist/index.d.ts +5 -5
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.mjs +241 -411
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/items.d.ts +22 -0
  12. package/dist/items.d.ts.map +1 -0
  13. package/dist/org.d.ts.map +1 -1
  14. package/dist/react.cjs +181 -415
  15. package/dist/react.cjs.map +1 -1
  16. package/dist/react.d.cts +8 -0
  17. package/dist/react.d.ts +8 -0
  18. package/dist/react.d.ts.map +1 -1
  19. package/dist/react.mjs +183 -418
  20. package/dist/react.mjs.map +1 -1
  21. package/dist/tags.d.ts +14 -1
  22. package/dist/tags.d.ts.map +1 -1
  23. package/dist/throwable.d.ts +15 -0
  24. package/dist/throwable.d.ts.map +1 -0
  25. package/dist/types.d.ts +19 -74
  26. package/dist/types.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/core.test.ts +28 -300
  29. package/src/core.ts +36 -185
  30. package/src/exception.ts +1 -1
  31. package/src/index.ts +4 -6
  32. package/src/items.test.ts +22 -0
  33. package/src/items.ts +44 -0
  34. package/src/org.ts +4 -0
  35. package/src/react.tsx +22 -0
  36. package/src/stream.test.ts +0 -7
  37. package/src/tags.test.ts +18 -0
  38. package/src/tags.ts +38 -5
  39. package/src/throwable.test.ts +72 -0
  40. package/src/throwable.ts +126 -0
  41. package/src/types.ts +15 -75
  42. package/src/uid.test.ts +1 -1
  43. package/dist/dsn.d.ts +0 -43
  44. package/dist/dsn.d.ts.map +0 -1
  45. package/dist/sentry.d.ts +0 -62
  46. package/dist/sentry.d.ts.map +0 -1
  47. package/src/dsn.test.ts +0 -75
  48. package/src/dsn.ts +0 -56
  49. package/src/sentry.test.ts +0 -312
  50. package/src/sentry.ts +0 -311
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { eventsOf, EVENTS, PAGEVIEW, EXCEPTION, namesOn } from '@hanzo/events';
1
+ import { eventsOf, EVENTS, PAGEVIEW, namesOn } from '@hanzo/events';
2
2
  export { EVENTS, EXCEPTION, FUNNELS, PAGEVIEW, PRODUCTS, eventsOf } from '@hanzo/events';
3
3
 
4
4
  // src/attribution.ts
@@ -74,22 +74,6 @@ function isoWeek(d) {
74
74
  return `${date.getUTCFullYear()}-W${String(week).padStart(2, "0")}`;
75
75
  }
76
76
 
77
- // src/dsn.ts
78
- var PRODUCT_PROJECT = Object.freeze({
79
- console: "019fa40b-94ae-7f1d-8f7b-e92f123fad42",
80
- // console.hanzo.ai
81
- app: "019f9b1e-57eb-7171-9d92-72c0b85e4b4b",
82
- // hanzo.app
83
- site: "019f9b1e-5785-7359-ad0b-f75db8e58c99"
84
- // hanzo.ai (marketing; product `site`)
85
- });
86
- function dsnForProduct(product, key) {
87
- if (!product || !key) return void 0;
88
- const projectId = PRODUCT_PROJECT[product];
89
- if (!projectId) return void 0;
90
- return `https://${key}@api.hanzo.ai/v1/event/${projectId}`;
91
- }
92
-
93
77
  // src/org.ts
94
78
  var ORG_DOMAIN = Object.freeze({
95
79
  // A brand's domains are not a pattern — `hanzoskills.com` is Hanzo's and does
@@ -98,11 +82,15 @@ var ORG_DOMAIN = Object.freeze({
98
82
  // separate fact, which is the whole reason this is a table.
99
83
  hanzo: Object.freeze([
100
84
  "hanzo.ai",
85
+ "hanzo.agency",
101
86
  "hanzo.app",
102
87
  "hanzo.bot",
103
88
  "hanzo.chat",
104
89
  "hanzo.codes",
105
90
  "hanzo.id",
91
+ "hanzo.industries",
92
+ "hanzo.market",
93
+ "hanzo.network",
106
94
  "hanzo.sh",
107
95
  "hanzo.team",
108
96
  "hanzo.ventures",
@@ -282,6 +270,93 @@ function scrubText(s, capturePII = false) {
282
270
  return s;
283
271
  }
284
272
 
273
+ // src/throwable.ts
274
+ var MAX_FRAMES = 50;
275
+ var MAX_LINES = 500;
276
+ var MAX_LINE_LEN = 2048;
277
+ var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
278
+ var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
279
+ function inApp(file) {
280
+ if (!file) return false;
281
+ return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
282
+ }
283
+ function framesFromStack(stack) {
284
+ if (!stack) return [];
285
+ const lines = stack.split("\n", MAX_LINES);
286
+ const frames = [];
287
+ for (const raw of lines) {
288
+ if (raw.length > MAX_LINE_LEN) continue;
289
+ const line = raw.trimEnd();
290
+ if (!line) continue;
291
+ let fn;
292
+ let file = "";
293
+ let lineno = 0;
294
+ let colno = 0;
295
+ const v = V8_FRAME.exec(line);
296
+ if (v) {
297
+ fn = v[1];
298
+ if (v[2]) {
299
+ file = v[2];
300
+ lineno = Number(v[3]) || 0;
301
+ colno = Number(v[4]) || 0;
302
+ } else {
303
+ file = (v[5] || "").trim();
304
+ }
305
+ } else {
306
+ const f = MOZ_FRAME.exec(line);
307
+ if (!f) continue;
308
+ fn = f[1];
309
+ file = f[2];
310
+ lineno = Number(f[3]) || 0;
311
+ colno = Number(f[4]) || 0;
312
+ }
313
+ if (!file && !fn) continue;
314
+ frames.push({
315
+ function: fn || "<anonymous>",
316
+ filename: file,
317
+ abs_path: file,
318
+ lineno,
319
+ colno,
320
+ in_app: inApp(file)
321
+ });
322
+ }
323
+ frames.reverse();
324
+ if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
325
+ return frames;
326
+ }
327
+ function normalizeError(err) {
328
+ if (err instanceof Error) {
329
+ const name = read(err, "name");
330
+ const message = read(err, "message");
331
+ const stack = read(err, "stack");
332
+ return {
333
+ name: typeof name === "string" && name ? name : "Error",
334
+ message: typeof message === "string" && message ? message : str(err),
335
+ stack: typeof stack === "string" ? stack : void 0
336
+ };
337
+ }
338
+ if (typeof err === "string") return { name: "Error", message: err };
339
+ try {
340
+ return { name: "Error", message: JSON.stringify(err) ?? str(err) };
341
+ } catch {
342
+ return { name: "Error", message: str(err) };
343
+ }
344
+ }
345
+ function read(o, k) {
346
+ try {
347
+ return o[k];
348
+ } catch {
349
+ return void 0;
350
+ }
351
+ }
352
+ function str(v) {
353
+ try {
354
+ return String(v);
355
+ } catch {
356
+ return "[unstringifiable]";
357
+ }
358
+ }
359
+
285
360
  // src/anon.js
286
361
  var HZ_ANON_KEY = "iam-anon-id";
287
362
  var HZ_ANON_ADOPT = ["hz_anon_id", "hz_id"];
@@ -405,292 +480,6 @@ function uuidv7Time(id) {
405
480
  return parseInt(id.slice(0, 8) + id.slice(9, 13), 16);
406
481
  }
407
482
 
408
- // package.json
409
- var version = "0.3.44";
410
-
411
- // src/version.ts
412
- var VERSION = version;
413
-
414
- // src/sentry.ts
415
- var MAX_FRAMES = 50;
416
- var MAX_LINES = 500;
417
- var MAX_LINE_LEN = 2048;
418
- var MAX_TAG_LEN = 1024;
419
- var MAX_TAGS = 50;
420
- function eventId() {
421
- return hzUuidv7().replace(/-/g, "");
422
- }
423
- function byteLen(s) {
424
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
425
- if (typeof Buffer !== "undefined") return Buffer.byteLength(s, "utf8");
426
- return s.length;
427
- }
428
- function parseDsn(dsn) {
429
- if (!dsn) return null;
430
- const m = /^(https?):\/\/([^@]+)@([^/]+)(\/.*)?$/.exec(dsn.trim());
431
- if (!m) return null;
432
- const scheme = m[1];
433
- const publicKey = m[2];
434
- const host = m[3];
435
- const path = m[4] ?? "";
436
- if (!publicKey || !host) return null;
437
- const segs = path.split("/").filter(Boolean);
438
- const projectId = segs.length > 0 ? segs[segs.length - 1] : "";
439
- if (!projectId) return null;
440
- const origin = `${scheme}://${host}`;
441
- const base = `${origin}/${segs.map(encodeURIComponent).join("/")}`;
442
- const ingestUrl = `${base}/envelope/?sentry_key=${encodeURIComponent(publicKey)}`;
443
- return { publicKey, origin, projectId, base, ingestUrl };
444
- }
445
- var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
446
- var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
447
- function inApp(file) {
448
- if (!file) return false;
449
- return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
450
- }
451
- function framesFromStack(stack) {
452
- if (!stack) return [];
453
- const lines = stack.split("\n", MAX_LINES);
454
- const frames = [];
455
- for (const raw of lines) {
456
- if (raw.length > MAX_LINE_LEN) continue;
457
- const line = raw.trimEnd();
458
- if (!line) continue;
459
- let fn;
460
- let file = "";
461
- let lineno = 0;
462
- let colno = 0;
463
- const v = V8_FRAME.exec(line);
464
- if (v) {
465
- fn = v[1];
466
- if (v[2]) {
467
- file = v[2];
468
- lineno = Number(v[3]) || 0;
469
- colno = Number(v[4]) || 0;
470
- } else {
471
- file = (v[5] || "").trim();
472
- }
473
- } else {
474
- const f = MOZ_FRAME.exec(line);
475
- if (!f) continue;
476
- fn = f[1];
477
- file = f[2];
478
- lineno = Number(f[3]) || 0;
479
- colno = Number(f[4]) || 0;
480
- }
481
- if (!file && !fn) continue;
482
- frames.push({
483
- function: fn || "<anonymous>",
484
- filename: file,
485
- abs_path: file,
486
- lineno,
487
- colno,
488
- in_app: inApp(file)
489
- });
490
- }
491
- frames.reverse();
492
- if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
493
- return frames;
494
- }
495
- function normalizeError(err) {
496
- if (err instanceof Error) {
497
- const name = read(err, "name");
498
- const message = read(err, "message");
499
- const stack = read(err, "stack");
500
- return {
501
- name: typeof name === "string" && name ? name : "Error",
502
- message: typeof message === "string" && message ? message : str(err),
503
- stack: typeof stack === "string" ? stack : void 0
504
- };
505
- }
506
- if (typeof err === "string") return { name: "Error", message: err };
507
- try {
508
- return { name: "Error", message: JSON.stringify(err) ?? str(err) };
509
- } catch {
510
- return { name: "Error", message: str(err) };
511
- }
512
- }
513
- function read(o, k) {
514
- try {
515
- return o[k];
516
- } catch {
517
- return void 0;
518
- }
519
- }
520
- function str(v) {
521
- try {
522
- return String(v);
523
- } catch {
524
- return "[unstringifiable]";
525
- }
526
- }
527
- function coerceTag(v) {
528
- const s = typeof v === "string" ? v : (() => {
529
- try {
530
- return JSON.stringify(v) ?? String(v);
531
- } catch {
532
- try {
533
- return String(v);
534
- } catch {
535
- return "[unstringifiable]";
536
- }
537
- }
538
- })();
539
- return s.length > MAX_TAG_LEN ? s.slice(0, MAX_TAG_LEN) : s;
540
- }
541
- function buildSentryEvent(input) {
542
- const { error, options: options2 = {}, identity, capturePII = false } = input;
543
- const norm = normalizeError(error);
544
- const handled = options2.handled !== false;
545
- const level = options2.level ?? (handled ? "error" : "fatal");
546
- const tags2 = { handled: String(handled) };
547
- if (identity.product) tags2.product = identity.product;
548
- if (identity.sessionId) tags2.session = identity.sessionId;
549
- try {
550
- const props = options2.properties ?? {};
551
- let n = 0;
552
- for (const k of Object.keys(props)) {
553
- if (n >= MAX_TAGS) break;
554
- try {
555
- const val = props[k];
556
- if (val === void 0 || val === null) continue;
557
- tags2[k] = scrubText(coerceTag(val), capturePII);
558
- n++;
559
- } catch {
560
- continue;
561
- }
562
- }
563
- } catch {
564
- }
565
- const event = {
566
- event_id: input.id ?? eventId(),
567
- timestamp: (input.now ?? Date.now()) / 1e3,
568
- platform: "javascript",
569
- level,
570
- logger: identity.product,
571
- environment: identity.environment,
572
- release: identity.release,
573
- exception: {
574
- values: [
575
- {
576
- type: norm.name,
577
- value: scrubText(norm.message, capturePII),
578
- stacktrace: { frames: framesFromStack(norm.stack) }
579
- }
580
- ]
581
- },
582
- tags: tags2,
583
- sdk: { name: "@hanzo/event", version: VERSION }
584
- };
585
- if (identity.userId) event.user = { id: identity.userId };
586
- return event;
587
- }
588
- function buildEnvelope(event, dsn, sentAt) {
589
- const payload = JSON.stringify(event);
590
- const header = JSON.stringify({
591
- event_id: event.event_id,
592
- dsn: dsn.base,
593
- sent_at: sentAt ?? (/* @__PURE__ */ new Date()).toISOString()
594
- });
595
- const itemHeader = JSON.stringify({
596
- type: "event",
597
- content_type: "application/json",
598
- length: byteLen(payload)
599
- });
600
- return `${header}
601
- ${itemHeader}
602
- ${payload}
603
- `;
604
- }
605
- var MAX_VALUE = 4096;
606
- function digest(s) {
607
- let out = "";
608
- for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
609
- let h = seed >>> 0;
610
- for (let i = 0; i < s.length; i++) {
611
- h ^= s.charCodeAt(i);
612
- h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
613
- }
614
- out += h.toString(16).padStart(8, "0");
615
- }
616
- return out;
617
- }
618
- function frameOf(f) {
619
- const source = f.filename ?? "";
620
- const name = f.function ?? "<anonymous>";
621
- const line = f.lineno ?? 0;
622
- const column = f.colno ?? 0;
623
- return {
624
- // Stable per code location, so the same frame groups and re-renders under one
625
- // key. The '/part' suffix is the product's format for one raw frame expanding
626
- // into several resolved ones; a browser frame is always part 0.
627
- raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
628
- mangled_name: name,
629
- source,
630
- line,
631
- column,
632
- in_app: f.in_app ?? false,
633
- lang: "javascript",
634
- resolved: false,
635
- resolve_failure: "no symbol set uploaded for this release",
636
- resolved_name: null,
637
- module: null
638
- };
639
- }
640
- function truncate2(s, max) {
641
- if (s.length <= max) return s;
642
- let end = max;
643
- const c = s.charCodeAt(end - 1);
644
- if (c >= 55296 && c <= 56319) end--;
645
- return s.slice(0, end);
646
- }
647
- function exceptionEntry(err, opts) {
648
- const n = normalizeError(err);
649
- const frames = framesFromStack(n.stack).slice(-50);
650
- const entry = {
651
- id: opts.id,
652
- type: n.name,
653
- value: truncate2(n.message, MAX_VALUE),
654
- mechanism: {
655
- type: "generic",
656
- handled: opts.handled,
657
- // An exception the app reported by hand was constructed, not thrown by the
658
- // runtime at a real call site.
659
- synthetic: !(err instanceof Error)
660
- }
661
- };
662
- if (frames.length > 0) {
663
- entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
664
- }
665
- return entry;
666
- }
667
- function fingerprint(entry) {
668
- const frames = entry.stacktrace?.frames ?? [];
669
- const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
670
- return digest([entry.type, ...pieces].join("\n"));
671
- }
672
- function exceptionProperties(err, opts) {
673
- const entry = exceptionEntry(err, opts);
674
- const frames = entry.stacktrace?.frames ?? [];
675
- const fp = fingerprint(entry);
676
- return {
677
- $exception_list: [entry],
678
- $exception_fingerprint: fp,
679
- // Records WHY this fingerprint holds. 'manual' is the honest value: the client
680
- // supplied the key rather than a server grouper deriving one.
681
- $exception_fingerprint_record: [{ type: "manual" }],
682
- $exception_type: entry.type,
683
- $exception_message: entry.value,
684
- $exception_level: opts.level ?? "error",
685
- $exception_handled: opts.handled,
686
- $exception_synthetic: entry.mechanism?.synthetic ?? false,
687
- $exception_types: [entry.type],
688
- $exception_values: [entry.value],
689
- $exception_sources: frames.map((f) => f.source).filter((s) => !!s),
690
- $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
691
- };
692
- }
693
-
694
483
  // src/storage.ts
695
484
  var KEY = {
696
485
  session: "hz_session",
@@ -842,20 +631,16 @@ function stripLink(href) {
842
631
  return hit ? u.pathname + u.search + u.hash : void 0;
843
632
  }
844
633
 
634
+ // package.json
635
+ var version = "0.3.49";
636
+
637
+ // src/version.ts
638
+ var VERSION = version;
639
+
845
640
  // src/core.ts
846
641
  var EVENT_PATH = "/v1/event";
847
642
  var DEFAULT_HOST = "https://api.hanzo.ai";
848
- var ENVELOPE_CONTENT_TYPE = "application/x-sentry-envelope";
849
643
  var BEACON_CONTENT_TYPE = "text/plain";
850
- function readEnvDsn() {
851
- try {
852
- if (typeof process !== "undefined" && process.env) {
853
- return process.env.NEXT_PUBLIC_HANZO_EVENT_DSN || process.env.HANZO_EVENT_DSN || void 0;
854
- }
855
- } catch {
856
- }
857
- return void 0;
858
- }
859
644
  function readEnv(name) {
860
645
  try {
861
646
  if (typeof process !== "undefined" && process.env) return process.env[name] || void 0;
@@ -868,7 +653,8 @@ function appendQuery(url, key, value) {
868
653
  }
869
654
  function normalizeError2(err) {
870
655
  const n = normalizeError(err);
871
- return { type: n.name, message: n.message, stack: n.stack };
656
+ const frames = framesFromStack(n.stack).slice(-50).map((f) => ({ function: f.function, file: f.filename, line: f.lineno, column: f.colno }));
657
+ return { type: n.name, message: n.message, stack: n.stack, frames };
872
658
  }
873
659
  var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
874
660
  function serializeBatch(batch) {
@@ -964,35 +750,13 @@ var Analytics = class {
964
750
  ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY") ?? keyForPage()
965
751
  };
966
752
  this.transport = config.transport ?? new DefaultTransport();
967
- this.dsn = parseDsn(
968
- config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
969
- );
970
753
  }
971
754
  /** adopt gives this client a credential it does not have. The key belongs to
972
755
  * the stream, not to whichever caller happened to ask for the handle first,
973
- * so a later caller carrying one hands it over. The error plane derives from
974
- * the key, so it comes up here too when it was inert for want of one.
975
- * Present fields are never overwritten: the first caller's key stays. */
756
+ * so a later caller carrying one hands it over. Present fields are never overwritten: the first caller's key stays. */
976
757
  adopt(config) {
977
758
  if (config.ingestKey && !this.cfg.ingestKey) this.cfg.ingestKey = config.ingestKey;
978
759
  if (config.getToken && !this.cfg.getToken) this.cfg.getToken = config.getToken;
979
- if (!this.dsn) {
980
- this.dsn = parseDsn(
981
- config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
982
- );
983
- }
984
- }
985
- /** errorPlaneEnabled reports whether captured exceptions can actually reach the
986
- * error host. False means a DSN was never configured — the documented
987
- * fail-safe. Exposed so an app (or a test) can assert its wiring instead of
988
- * discovering months later that nothing was ever reported. */
989
- get errorPlaneEnabled() {
990
- return this.dsn !== null;
991
- }
992
- /** errorIngestUrl is the fully-derived envelope endpoint, or undefined when the
993
- * plane is inert. Diagnostics only. */
994
- get errorIngestUrl() {
995
- return this.dsn?.ingestUrl;
996
760
  }
997
761
  /** init is idempotent and browser-only for its side effects: capture first-touch
998
762
  * attribution, hydrate cohort, register the unload flush, and (unless opted out)
@@ -1080,45 +844,28 @@ var Analytics = class {
1080
844
  this.enqueue("event", event, { properties, ...commerce });
1081
845
  }
1082
846
  /** captureError reports a caught error, an unhandled rejection, a React render
1083
- * error, or a manual report to BOTH planes, from one call:
1084
- *
1085
- * - the ERROR PLANE — a real Sentry envelope to the DSN host. This is the one
1086
- * that produces an issue in sentry.hanzo.ai (grouping, stack frames, AST).
1087
- * Inert when no DSN is configured.
1088
- * - the EVENT STREAM — a `type:'error'` row in the cloud event warehouse, so
1089
- * an error stays correlated with the session's pageviews for product
1090
- * analysis (readable via GET /v1/errors).
1091
- *
1092
- * Both carry the SAME session and subject id, so an error and the pageview
1093
- * before it join up. Never throws back into the app; errors are higher-signal
1094
- * than pageviews, so both planes flush promptly (a crash may unload the page
1095
- * moments later). */
847
+ * error, or a manual report as ONE event of type 'error' on the one stream.
848
+ * It carries the exception (type, message, frames, stack, handled) and the
849
+ * release, environment, site, product and level that group and scope it, and
850
+ * flushes promptly (a crash may unload the page moments later). Never throws
851
+ * back into the app. */
1096
852
  captureError(err, context) {
1097
853
  if (this.reentrant) return;
1098
854
  this.reentrant = true;
1099
855
  try {
1100
- try {
1101
- this.sendError(err, context);
1102
- } catch {
1103
- }
1104
- try {
1105
- const handled = context?.handled ?? true;
1106
- const ex = normalizeError2(err);
1107
- ex.handled = handled;
1108
- this.enqueue("event", EXCEPTION, {
1109
- error: ex,
1110
- properties: {
1111
- ...context?.properties,
1112
- ...exceptionProperties(err, {
1113
- handled,
1114
- id: hzUuidv7(),
1115
- level: context?.level
1116
- })
1117
- }
1118
- });
1119
- this.flush();
1120
- } catch {
1121
- }
856
+ const handled = context?.handled ?? true;
857
+ const ex = normalizeError2(err);
858
+ ex.handled = handled;
859
+ this.enqueue("error", void 0, {
860
+ error: ex,
861
+ level: context?.level ?? (handled ? "error" : "fatal"),
862
+ release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
863
+ environment: this.cfg.environment ?? readEnv("NODE_ENV"),
864
+ site: isBrowser() ? window.location.hostname : void 0,
865
+ properties: context?.properties
866
+ });
867
+ this.flush();
868
+ } catch {
1122
869
  } finally {
1123
870
  this.reentrant = false;
1124
871
  }
@@ -1161,39 +908,6 @@ var Analytics = class {
1161
908
  });
1162
909
  }
1163
910
  // ── internals ────────────────────────────────────────────────────────────
1164
- /** sendError frames one exception as a Sentry envelope and posts it to the DSN's
1165
- * ingest URL. The DSN's own key rides ?sentry_key= (the credential channel the
1166
- * server trusts, and the only one a headerless beacon can carry), so NO bearer
1167
- * or publishable key is attached here — the two planes authenticate
1168
- * independently. Errors are sent one envelope per event, immediately: batching
1169
- * a crash report is how you lose it. */
1170
- sendError(err, options2) {
1171
- if (!this.cfg.enabled || !this.dsn) return;
1172
- const event = buildSentryEvent({
1173
- error: err,
1174
- options: options2,
1175
- identity: this.errorIdentity(),
1176
- capturePII: this.cfg.capturePII ?? false
1177
- });
1178
- const body = buildEnvelope(event, this.dsn);
1179
- if (this.cfg.debug) console.debug("[event] error \u2192", this.dsn.ingestUrl, event.event_id);
1180
- this.transport.send(this.dsn.ingestUrl, body, {
1181
- beacon: false,
1182
- contentType: ENVELOPE_CONTENT_TYPE,
1183
- debug: this.cfg.debug
1184
- });
1185
- }
1186
- /** errorIdentity is the SAME identity the event stream stamps — the OIDC subject
1187
- * once identify() has run, else the anon id. Never email/PII. */
1188
- errorIdentity() {
1189
- return {
1190
- userId: this.personId ?? anonId(),
1191
- sessionId: sessionId(),
1192
- product: this.cfg.product,
1193
- release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
1194
- environment: this.cfg.environment ?? readEnv("NODE_ENV")
1195
- };
1196
- }
1197
911
  enqueue(kind, event, extra) {
1198
912
  if (!this.cfg.enabled) return;
1199
913
  if (!this.started) this.init();
@@ -1282,6 +996,94 @@ function createAnalytics(config) {
1282
996
  clients.set(key, fresh);
1283
997
  return fresh;
1284
998
  }
999
+ var MAX_VALUE = 4096;
1000
+ function digest(s) {
1001
+ let out = "";
1002
+ for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
1003
+ let h = seed >>> 0;
1004
+ for (let i = 0; i < s.length; i++) {
1005
+ h ^= s.charCodeAt(i);
1006
+ h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
1007
+ }
1008
+ out += h.toString(16).padStart(8, "0");
1009
+ }
1010
+ return out;
1011
+ }
1012
+ function frameOf(f) {
1013
+ const source = f.filename ?? "";
1014
+ const name = f.function ?? "<anonymous>";
1015
+ const line = f.lineno ?? 0;
1016
+ const column = f.colno ?? 0;
1017
+ return {
1018
+ // Stable per code location, so the same frame groups and re-renders under one
1019
+ // key. The '/part' suffix is the product's format for one raw frame expanding
1020
+ // into several resolved ones; a browser frame is always part 0.
1021
+ raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
1022
+ mangled_name: name,
1023
+ source,
1024
+ line,
1025
+ column,
1026
+ in_app: f.in_app ?? false,
1027
+ lang: "javascript",
1028
+ resolved: false,
1029
+ resolve_failure: "no symbol set uploaded for this release",
1030
+ resolved_name: null,
1031
+ module: null
1032
+ };
1033
+ }
1034
+ function truncate2(s, max) {
1035
+ if (s.length <= max) return s;
1036
+ let end = max;
1037
+ const c = s.charCodeAt(end - 1);
1038
+ if (c >= 55296 && c <= 56319) end--;
1039
+ return s.slice(0, end);
1040
+ }
1041
+ function exceptionEntry(err, opts) {
1042
+ const n = normalizeError(err);
1043
+ const frames = framesFromStack(n.stack).slice(-50);
1044
+ const entry = {
1045
+ id: opts.id,
1046
+ type: n.name,
1047
+ value: truncate2(n.message, MAX_VALUE),
1048
+ mechanism: {
1049
+ type: "generic",
1050
+ handled: opts.handled,
1051
+ // An exception the app reported by hand was constructed, not thrown by the
1052
+ // runtime at a real call site.
1053
+ synthetic: !(err instanceof Error)
1054
+ }
1055
+ };
1056
+ if (frames.length > 0) {
1057
+ entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
1058
+ }
1059
+ return entry;
1060
+ }
1061
+ function fingerprint(entry) {
1062
+ const frames = entry.stacktrace?.frames ?? [];
1063
+ const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
1064
+ return digest([entry.type, ...pieces].join("\n"));
1065
+ }
1066
+ function exceptionProperties(err, opts) {
1067
+ const entry = exceptionEntry(err, opts);
1068
+ const frames = entry.stacktrace?.frames ?? [];
1069
+ const fp = fingerprint(entry);
1070
+ return {
1071
+ $exception_list: [entry],
1072
+ $exception_fingerprint: fp,
1073
+ // Records WHY this fingerprint holds. 'manual' is the honest value: the client
1074
+ // supplied the key rather than a server grouper deriving one.
1075
+ $exception_fingerprint_record: [{ type: "manual" }],
1076
+ $exception_type: entry.type,
1077
+ $exception_message: entry.value,
1078
+ $exception_level: opts.level ?? "error",
1079
+ $exception_handled: opts.handled,
1080
+ $exception_synthetic: entry.mechanism?.synthetic ?? false,
1081
+ $exception_types: [entry.type],
1082
+ $exception_values: [entry.value],
1083
+ $exception_sources: frames.map((f) => f.source).filter((s) => !!s),
1084
+ $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
1085
+ };
1086
+ }
1285
1087
 
1286
1088
  // src/cookie.ts
1287
1089
  var DAY = 86400;
@@ -1389,6 +1191,17 @@ function touch(c) {
1389
1191
  }
1390
1192
  return out;
1391
1193
  }
1194
+ var SITE_KEY = Object.freeze({
1195
+ "hanzo.ai": "pk-CmfLA2K6kvsPflrS9DSkt06H_kSoQB_21sjedt6VJdc",
1196
+ "www.hanzo.ai": "pk-CmfLA2K6kvsPflrS9DSkt06H_kSoQB_21sjedt6VJdc",
1197
+ "hanzo.app": "pk-wlnXN2a9_vmCm60yTFtQ629Q8TyuaxBNZbY1RWT72gQ",
1198
+ "hanzo.team": "pk-NCzD2FiHpZv8KUpkCX4olT1LJOJsMxBC_Z8NkiQsOFQ",
1199
+ "pay.hanzo.ai": "pk-eX6kv7JZNoiYn1WkeJH3tT_8OvkVYScmCXnLMwxTKf8",
1200
+ "platform.hanzo.ai": "pk-My1RpZLEUnTj8vAdPbWKYdDUuhxZJ4dVjHcwjZN4rZ8",
1201
+ "docs.hanzo.ai": "pk-jukhtjMT2ymoeBDAeFjINQWlBv-v9sNn1TPztiCrrwk",
1202
+ "hanzo.bot": "pk-W5d7Mn7ZukT7igyscIy6Pqe8JpA0Ge604Yn4xNR4JCU",
1203
+ "cloud.hanzo.ai": "pk-RAfEGHPoNdCEU9fnA_cPd_Xo9Tci44rlYQV9xuJ1Ob0"
1204
+ });
1392
1205
  var page = () => window;
1393
1206
  var tags = [];
1394
1207
  var configured = false;
@@ -1566,8 +1379,10 @@ function start(o = {}) {
1566
1379
  configured = true;
1567
1380
  const base = (o.base ?? "https://api.hanzo.ai").replace(/\/$/, "");
1568
1381
  const q = new URLSearchParams();
1569
- if (o.key) q.set("key", o.key);
1570
- q.set("host", o.host ?? window.location.hostname);
1382
+ const host = o.host ?? window.location.hostname;
1383
+ const key = o.key ?? SITE_KEY[host];
1384
+ if (key) q.set("key", key);
1385
+ q.set("host", host);
1571
1386
  const answer = (list) => {
1572
1387
  tags = list;
1573
1388
  configAnswered = true;
@@ -1679,16 +1494,17 @@ function mirror(name, params, id, to = reach()) {
1679
1494
  }
1680
1495
  }
1681
1496
  }
1682
- function track(stream, name, params = {}) {
1497
+ function track(stream, name, params = {}, only) {
1683
1498
  if (typeof window === "undefined") return;
1684
1499
  if (!leaving && !settled()) {
1685
- held.push(() => track(stream, name, params));
1500
+ held.push(() => track(stream, name, params, only));
1686
1501
  return;
1687
1502
  }
1688
1503
  const c = read2();
1689
- const event_id = crypto.randomUUID();
1504
+ const event_id = typeof params.event_id === "string" && params.event_id ? params.event_id : crypto.randomUUID();
1690
1505
  const to = reach();
1691
1506
  mirror(name, params, event_id, to);
1507
+ if (only === "tags") return;
1692
1508
  stream?.capture(name, {
1693
1509
  ...params,
1694
1510
  ...touch(c),
@@ -1704,6 +1520,20 @@ function visit() {
1704
1520
  return { ...touch(c), consent: render(c), tags: reach(), userAgent: navigator.userAgent, url: window.location.href };
1705
1521
  }
1706
1522
 
1523
+ // src/items.ts
1524
+ function commerceItem(p) {
1525
+ const annual = /^(year|yearly|annual)$/i.test(p.interval);
1526
+ return {
1527
+ item_id: p.id,
1528
+ ...p.name ? { item_name: p.name } : {},
1529
+ item_category: p.team ? "team" : "individual",
1530
+ item_variant: annual ? "annual" : "monthly",
1531
+ price: p.price ?? 0,
1532
+ quantity: p.quantity ?? 1
1533
+ };
1534
+ }
1535
+ var worth = (i) => i.price * i.quantity;
1536
+
1707
1537
  // src/goals.ts
1708
1538
  var GOALS = {
1709
1539
  // Signup: the conversion is signup_completed, along the site signup funnel.
@@ -1764,6 +1594,6 @@ var COHORTS = {
1764
1594
  * curl -fsSL https://unpkg.com/@hanzo/event/src/anon.js -o apps/analytics/anon.js
1765
1595
  */
1766
1596
 
1767
- export { Analytics, CLICK_IDS, COHORTS, CONSENT_COOKIE, CONSENT_EVENT, GOALS, LINK_PARAMS, ORG_DOMAIN, ORG_KEY, PRODUCT_PROJECT, VERSION, acceptAll, asks, buildEnvelope, buildSentryEvent, capture as captureClick, createAnalytics, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, gpc, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, linkUrl, mirror, orgOf, parseAttribution, parseDsn, reach, read2 as readConsent, readLink, redactSecrets, refused, region, rejectAll, render as renderConsent, resetClients, save as saveConsent, scrubPII, scrubText, start as startTags, stored as storedConsent, stripLink, touch, track, hzUuidv7 as uuidv7, uuidv7Time, visit };
1597
+ export { Analytics, CLICK_IDS, COHORTS, CONSENT_COOKIE, CONSENT_EVENT, GOALS, LINK_PARAMS, ORG_DOMAIN, ORG_KEY, SITE_KEY, VERSION, acceptAll, asks, capture as captureClick, commerceItem, createAnalytics, deriveChannel, digest, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, gpc, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, linkUrl, mirror, orgOf, parseAttribution, reach, read2 as readConsent, readLink, redactSecrets, refused, region, rejectAll, render as renderConsent, resetClients, save as saveConsent, scrubPII, scrubText, start as startTags, stored as storedConsent, stripLink, touch, track, hzUuidv7 as uuidv7, uuidv7Time, visit, worth };
1768
1598
  //# sourceMappingURL=index.mjs.map
1769
1599
  //# sourceMappingURL=index.mjs.map