@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/index.cjs CHANGED
@@ -75,22 +75,6 @@ function isoWeek(d) {
75
75
  return `${date.getUTCFullYear()}-W${String(week).padStart(2, "0")}`;
76
76
  }
77
77
 
78
- // src/dsn.ts
79
- var PRODUCT_PROJECT = Object.freeze({
80
- console: "019fa40b-94ae-7f1d-8f7b-e92f123fad42",
81
- // console.hanzo.ai
82
- app: "019f9b1e-57eb-7171-9d92-72c0b85e4b4b",
83
- // hanzo.app
84
- site: "019f9b1e-5785-7359-ad0b-f75db8e58c99"
85
- // hanzo.ai (marketing; product `site`)
86
- });
87
- function dsnForProduct(product, key) {
88
- if (!product || !key) return void 0;
89
- const projectId = PRODUCT_PROJECT[product];
90
- if (!projectId) return void 0;
91
- return `https://${key}@api.hanzo.ai/v1/event/${projectId}`;
92
- }
93
-
94
78
  // src/org.ts
95
79
  var ORG_DOMAIN = Object.freeze({
96
80
  // A brand's domains are not a pattern — `hanzoskills.com` is Hanzo's and does
@@ -99,11 +83,15 @@ var ORG_DOMAIN = Object.freeze({
99
83
  // separate fact, which is the whole reason this is a table.
100
84
  hanzo: Object.freeze([
101
85
  "hanzo.ai",
86
+ "hanzo.agency",
102
87
  "hanzo.app",
103
88
  "hanzo.bot",
104
89
  "hanzo.chat",
105
90
  "hanzo.codes",
106
91
  "hanzo.id",
92
+ "hanzo.industries",
93
+ "hanzo.market",
94
+ "hanzo.network",
107
95
  "hanzo.sh",
108
96
  "hanzo.team",
109
97
  "hanzo.ventures",
@@ -123,7 +111,7 @@ var ORG_DOMAIN = Object.freeze({
123
111
  zoo: Object.freeze(["zoo.ngo", "zoo.network", "zoolabs.id", "zoolabs.io"])
124
112
  });
125
113
  var ORG_KEY = Object.freeze({
126
- hanzo: "pk-rM_CdaF2MQckGCrla113SrR1oH4zvqN8xh2I95Z9tY8",
114
+ hanzo: "pk-CmfLA2K6kvsPflrS9DSkt06H_kSoQB_21sjedt6VJdc",
127
115
  lux: "pk-gUZp6ZVfhJzSwK-rb4oLbVkpCnMBx5uSCpxf_5yEhQk",
128
116
  zoo: "pk-3TKpKnERV9AQSsBUERWkZejC1O1mUxc1jRzsP3MPbs4"
129
117
  });
@@ -283,6 +271,93 @@ function scrubText(s, capturePII = false) {
283
271
  return s;
284
272
  }
285
273
 
274
+ // src/throwable.ts
275
+ var MAX_FRAMES = 50;
276
+ var MAX_LINES = 500;
277
+ var MAX_LINE_LEN = 2048;
278
+ var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
279
+ var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
280
+ function inApp(file) {
281
+ if (!file) return false;
282
+ return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
283
+ }
284
+ function framesFromStack(stack) {
285
+ if (!stack) return [];
286
+ const lines = stack.split("\n", MAX_LINES);
287
+ const frames = [];
288
+ for (const raw of lines) {
289
+ if (raw.length > MAX_LINE_LEN) continue;
290
+ const line = raw.trimEnd();
291
+ if (!line) continue;
292
+ let fn;
293
+ let file = "";
294
+ let lineno = 0;
295
+ let colno = 0;
296
+ const v = V8_FRAME.exec(line);
297
+ if (v) {
298
+ fn = v[1];
299
+ if (v[2]) {
300
+ file = v[2];
301
+ lineno = Number(v[3]) || 0;
302
+ colno = Number(v[4]) || 0;
303
+ } else {
304
+ file = (v[5] || "").trim();
305
+ }
306
+ } else {
307
+ const f = MOZ_FRAME.exec(line);
308
+ if (!f) continue;
309
+ fn = f[1];
310
+ file = f[2];
311
+ lineno = Number(f[3]) || 0;
312
+ colno = Number(f[4]) || 0;
313
+ }
314
+ if (!file && !fn) continue;
315
+ frames.push({
316
+ function: fn || "<anonymous>",
317
+ filename: file,
318
+ abs_path: file,
319
+ lineno,
320
+ colno,
321
+ in_app: inApp(file)
322
+ });
323
+ }
324
+ frames.reverse();
325
+ if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
326
+ return frames;
327
+ }
328
+ function normalizeError(err) {
329
+ if (err instanceof Error) {
330
+ const name = read(err, "name");
331
+ const message = read(err, "message");
332
+ const stack = read(err, "stack");
333
+ return {
334
+ name: typeof name === "string" && name ? name : "Error",
335
+ message: typeof message === "string" && message ? message : str(err),
336
+ stack: typeof stack === "string" ? stack : void 0
337
+ };
338
+ }
339
+ if (typeof err === "string") return { name: "Error", message: err };
340
+ try {
341
+ return { name: "Error", message: JSON.stringify(err) ?? str(err) };
342
+ } catch {
343
+ return { name: "Error", message: str(err) };
344
+ }
345
+ }
346
+ function read(o, k) {
347
+ try {
348
+ return o[k];
349
+ } catch {
350
+ return void 0;
351
+ }
352
+ }
353
+ function str(v) {
354
+ try {
355
+ return String(v);
356
+ } catch {
357
+ return "[unstringifiable]";
358
+ }
359
+ }
360
+
286
361
  // src/anon.js
287
362
  var HZ_ANON_KEY = "iam-anon-id";
288
363
  var HZ_ANON_ADOPT = ["hz_anon_id", "hz_id"];
@@ -406,292 +481,6 @@ function uuidv7Time(id) {
406
481
  return parseInt(id.slice(0, 8) + id.slice(9, 13), 16);
407
482
  }
408
483
 
409
- // package.json
410
- var version = "0.3.48";
411
-
412
- // src/version.ts
413
- var VERSION = version;
414
-
415
- // src/sentry.ts
416
- var MAX_FRAMES = 50;
417
- var MAX_LINES = 500;
418
- var MAX_LINE_LEN = 2048;
419
- var MAX_TAG_LEN = 1024;
420
- var MAX_TAGS = 50;
421
- function eventId() {
422
- return hzUuidv7().replace(/-/g, "");
423
- }
424
- function byteLen(s) {
425
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
426
- if (typeof Buffer !== "undefined") return Buffer.byteLength(s, "utf8");
427
- return s.length;
428
- }
429
- function parseDsn(dsn) {
430
- if (!dsn) return null;
431
- const m = /^(https?):\/\/([^@]+)@([^/]+)(\/.*)?$/.exec(dsn.trim());
432
- if (!m) return null;
433
- const scheme = m[1];
434
- const publicKey = m[2];
435
- const host = m[3];
436
- const path = m[4] ?? "";
437
- if (!publicKey || !host) return null;
438
- const segs = path.split("/").filter(Boolean);
439
- const projectId = segs.length > 0 ? segs[segs.length - 1] : "";
440
- if (!projectId) return null;
441
- const origin = `${scheme}://${host}`;
442
- const base = `${origin}/${segs.map(encodeURIComponent).join("/")}`;
443
- const ingestUrl = `${base}/envelope/?sentry_key=${encodeURIComponent(publicKey)}`;
444
- return { publicKey, origin, projectId, base, ingestUrl };
445
- }
446
- var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
447
- var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
448
- function inApp(file) {
449
- if (!file) return false;
450
- return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
451
- }
452
- function framesFromStack(stack) {
453
- if (!stack) return [];
454
- const lines = stack.split("\n", MAX_LINES);
455
- const frames = [];
456
- for (const raw of lines) {
457
- if (raw.length > MAX_LINE_LEN) continue;
458
- const line = raw.trimEnd();
459
- if (!line) continue;
460
- let fn;
461
- let file = "";
462
- let lineno = 0;
463
- let colno = 0;
464
- const v = V8_FRAME.exec(line);
465
- if (v) {
466
- fn = v[1];
467
- if (v[2]) {
468
- file = v[2];
469
- lineno = Number(v[3]) || 0;
470
- colno = Number(v[4]) || 0;
471
- } else {
472
- file = (v[5] || "").trim();
473
- }
474
- } else {
475
- const f = MOZ_FRAME.exec(line);
476
- if (!f) continue;
477
- fn = f[1];
478
- file = f[2];
479
- lineno = Number(f[3]) || 0;
480
- colno = Number(f[4]) || 0;
481
- }
482
- if (!file && !fn) continue;
483
- frames.push({
484
- function: fn || "<anonymous>",
485
- filename: file,
486
- abs_path: file,
487
- lineno,
488
- colno,
489
- in_app: inApp(file)
490
- });
491
- }
492
- frames.reverse();
493
- if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
494
- return frames;
495
- }
496
- function normalizeError(err) {
497
- if (err instanceof Error) {
498
- const name = read(err, "name");
499
- const message = read(err, "message");
500
- const stack = read(err, "stack");
501
- return {
502
- name: typeof name === "string" && name ? name : "Error",
503
- message: typeof message === "string" && message ? message : str(err),
504
- stack: typeof stack === "string" ? stack : void 0
505
- };
506
- }
507
- if (typeof err === "string") return { name: "Error", message: err };
508
- try {
509
- return { name: "Error", message: JSON.stringify(err) ?? str(err) };
510
- } catch {
511
- return { name: "Error", message: str(err) };
512
- }
513
- }
514
- function read(o, k) {
515
- try {
516
- return o[k];
517
- } catch {
518
- return void 0;
519
- }
520
- }
521
- function str(v) {
522
- try {
523
- return String(v);
524
- } catch {
525
- return "[unstringifiable]";
526
- }
527
- }
528
- function coerceTag(v) {
529
- const s = typeof v === "string" ? v : (() => {
530
- try {
531
- return JSON.stringify(v) ?? String(v);
532
- } catch {
533
- try {
534
- return String(v);
535
- } catch {
536
- return "[unstringifiable]";
537
- }
538
- }
539
- })();
540
- return s.length > MAX_TAG_LEN ? s.slice(0, MAX_TAG_LEN) : s;
541
- }
542
- function buildSentryEvent(input) {
543
- const { error, options: options2 = {}, identity, capturePII = false } = input;
544
- const norm = normalizeError(error);
545
- const handled = options2.handled !== false;
546
- const level = options2.level ?? (handled ? "error" : "fatal");
547
- const tags2 = { handled: String(handled) };
548
- if (identity.product) tags2.product = identity.product;
549
- if (identity.sessionId) tags2.session = identity.sessionId;
550
- try {
551
- const props = options2.properties ?? {};
552
- let n = 0;
553
- for (const k of Object.keys(props)) {
554
- if (n >= MAX_TAGS) break;
555
- try {
556
- const val = props[k];
557
- if (val === void 0 || val === null) continue;
558
- tags2[k] = scrubText(coerceTag(val), capturePII);
559
- n++;
560
- } catch {
561
- continue;
562
- }
563
- }
564
- } catch {
565
- }
566
- const event = {
567
- event_id: input.id ?? eventId(),
568
- timestamp: (input.now ?? Date.now()) / 1e3,
569
- platform: "javascript",
570
- level,
571
- logger: identity.product,
572
- environment: identity.environment,
573
- release: identity.release,
574
- exception: {
575
- values: [
576
- {
577
- type: norm.name,
578
- value: scrubText(norm.message, capturePII),
579
- stacktrace: { frames: framesFromStack(norm.stack) }
580
- }
581
- ]
582
- },
583
- tags: tags2,
584
- sdk: { name: "@hanzo/event", version: VERSION }
585
- };
586
- if (identity.userId) event.user = { id: identity.userId };
587
- return event;
588
- }
589
- function buildEnvelope(event, dsn, sentAt) {
590
- const payload = JSON.stringify(event);
591
- const header = JSON.stringify({
592
- event_id: event.event_id,
593
- dsn: dsn.base,
594
- sent_at: sentAt ?? (/* @__PURE__ */ new Date()).toISOString()
595
- });
596
- const itemHeader = JSON.stringify({
597
- type: "event",
598
- content_type: "application/json",
599
- length: byteLen(payload)
600
- });
601
- return `${header}
602
- ${itemHeader}
603
- ${payload}
604
- `;
605
- }
606
- var MAX_VALUE = 4096;
607
- function digest(s) {
608
- let out = "";
609
- for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
610
- let h = seed >>> 0;
611
- for (let i = 0; i < s.length; i++) {
612
- h ^= s.charCodeAt(i);
613
- h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
614
- }
615
- out += h.toString(16).padStart(8, "0");
616
- }
617
- return out;
618
- }
619
- function frameOf(f) {
620
- const source = f.filename ?? "";
621
- const name = f.function ?? "<anonymous>";
622
- const line = f.lineno ?? 0;
623
- const column = f.colno ?? 0;
624
- return {
625
- // Stable per code location, so the same frame groups and re-renders under one
626
- // key. The '/part' suffix is the product's format for one raw frame expanding
627
- // into several resolved ones; a browser frame is always part 0.
628
- raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
629
- mangled_name: name,
630
- source,
631
- line,
632
- column,
633
- in_app: f.in_app ?? false,
634
- lang: "javascript",
635
- resolved: false,
636
- resolve_failure: "no symbol set uploaded for this release",
637
- resolved_name: null,
638
- module: null
639
- };
640
- }
641
- function truncate2(s, max) {
642
- if (s.length <= max) return s;
643
- let end = max;
644
- const c = s.charCodeAt(end - 1);
645
- if (c >= 55296 && c <= 56319) end--;
646
- return s.slice(0, end);
647
- }
648
- function exceptionEntry(err, opts) {
649
- const n = normalizeError(err);
650
- const frames = framesFromStack(n.stack).slice(-50);
651
- const entry = {
652
- id: opts.id,
653
- type: n.name,
654
- value: truncate2(n.message, MAX_VALUE),
655
- mechanism: {
656
- type: "generic",
657
- handled: opts.handled,
658
- // An exception the app reported by hand was constructed, not thrown by the
659
- // runtime at a real call site.
660
- synthetic: !(err instanceof Error)
661
- }
662
- };
663
- if (frames.length > 0) {
664
- entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
665
- }
666
- return entry;
667
- }
668
- function fingerprint(entry) {
669
- const frames = entry.stacktrace?.frames ?? [];
670
- const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
671
- return digest([entry.type, ...pieces].join("\n"));
672
- }
673
- function exceptionProperties(err, opts) {
674
- const entry = exceptionEntry(err, opts);
675
- const frames = entry.stacktrace?.frames ?? [];
676
- const fp = fingerprint(entry);
677
- return {
678
- $exception_list: [entry],
679
- $exception_fingerprint: fp,
680
- // Records WHY this fingerprint holds. 'manual' is the honest value: the client
681
- // supplied the key rather than a server grouper deriving one.
682
- $exception_fingerprint_record: [{ type: "manual" }],
683
- $exception_type: entry.type,
684
- $exception_message: entry.value,
685
- $exception_level: opts.level ?? "error",
686
- $exception_handled: opts.handled,
687
- $exception_synthetic: entry.mechanism?.synthetic ?? false,
688
- $exception_types: [entry.type],
689
- $exception_values: [entry.value],
690
- $exception_sources: frames.map((f) => f.source).filter((s) => !!s),
691
- $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
692
- };
693
- }
694
-
695
484
  // src/storage.ts
696
485
  var KEY = {
697
486
  session: "hz_session",
@@ -843,20 +632,16 @@ function stripLink(href) {
843
632
  return hit ? u.pathname + u.search + u.hash : void 0;
844
633
  }
845
634
 
635
+ // package.json
636
+ var version = "0.3.50";
637
+
638
+ // src/version.ts
639
+ var VERSION = version;
640
+
846
641
  // src/core.ts
847
642
  var EVENT_PATH = "/v1/event";
848
643
  var DEFAULT_HOST = "https://api.hanzo.ai";
849
- var ENVELOPE_CONTENT_TYPE = "application/x-sentry-envelope";
850
644
  var BEACON_CONTENT_TYPE = "text/plain";
851
- function readEnvDsn() {
852
- try {
853
- if (typeof process !== "undefined" && process.env) {
854
- return process.env.NEXT_PUBLIC_HANZO_EVENT_DSN || process.env.HANZO_EVENT_DSN || void 0;
855
- }
856
- } catch {
857
- }
858
- return void 0;
859
- }
860
645
  function readEnv(name) {
861
646
  try {
862
647
  if (typeof process !== "undefined" && process.env) return process.env[name] || void 0;
@@ -869,8 +654,10 @@ function appendQuery(url, key, value) {
869
654
  }
870
655
  function normalizeError2(err) {
871
656
  const n = normalizeError(err);
872
- return { type: n.name, message: n.message, stack: n.stack };
657
+ const frames = framesFromStack(n.stack).slice(-50).map((f) => ({ function: f.function, file: f.filename, line: f.lineno, column: f.colno }));
658
+ return { type: n.name, message: n.message, stack: n.stack, frames };
873
659
  }
660
+ var viewport = () => isBrowser() ? { screen: `${window.screen?.width ?? 0}x${window.screen?.height ?? 0}`, language: navigator.language || "" } : {};
874
661
  var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
875
662
  function serializeBatch(batch) {
876
663
  try {
@@ -905,13 +692,13 @@ var DefaultTransport = class {
905
692
  }
906
693
  }
907
694
  if (typeof fetch !== "function") return;
908
- const simple = opts.ingestKey !== void 0 && opts.contentType === void 0;
695
+ const simple = opts.ingestKey !== void 0 && opts.token === void 0 && opts.contentType === void 0;
909
696
  const headers = {
910
697
  "Content-Type": simple ? BEACON_CONTENT_TYPE : opts.contentType ?? "application/json"
911
698
  };
912
- const bearer = simple ? void 0 : opts.ingestKey ?? opts.token;
699
+ const bearer = simple ? void 0 : opts.token ?? opts.ingestKey;
913
700
  if (bearer) headers.Authorization = `Bearer ${bearer}`;
914
- void fetch(simple ? appendQuery(url, "ingest_key", opts.ingestKey) : url, {
701
+ void fetch(opts.ingestKey ? appendQuery(url, "ingest_key", opts.ingestKey) : url, {
915
702
  method: "POST",
916
703
  headers,
917
704
  body,
@@ -965,35 +752,13 @@ var Analytics = class {
965
752
  ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY") ?? keyForPage()
966
753
  };
967
754
  this.transport = config.transport ?? new DefaultTransport();
968
- this.dsn = parseDsn(
969
- config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
970
- );
971
755
  }
972
756
  /** adopt gives this client a credential it does not have. The key belongs to
973
757
  * the stream, not to whichever caller happened to ask for the handle first,
974
- * so a later caller carrying one hands it over. The error plane derives from
975
- * the key, so it comes up here too when it was inert for want of one.
976
- * Present fields are never overwritten: the first caller's key stays. */
758
+ * so a later caller carrying one hands it over. Present fields are never overwritten: the first caller's key stays. */
977
759
  adopt(config) {
978
760
  if (config.ingestKey && !this.cfg.ingestKey) this.cfg.ingestKey = config.ingestKey;
979
761
  if (config.getToken && !this.cfg.getToken) this.cfg.getToken = config.getToken;
980
- if (!this.dsn) {
981
- this.dsn = parseDsn(
982
- config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
983
- );
984
- }
985
- }
986
- /** errorPlaneEnabled reports whether captured exceptions can actually reach the
987
- * error host. False means a DSN was never configured — the documented
988
- * fail-safe. Exposed so an app (or a test) can assert its wiring instead of
989
- * discovering months later that nothing was ever reported. */
990
- get errorPlaneEnabled() {
991
- return this.dsn !== null;
992
- }
993
- /** errorIngestUrl is the fully-derived envelope endpoint, or undefined when the
994
- * plane is inert. Diagnostics only. */
995
- get errorIngestUrl() {
996
- return this.dsn?.ingestUrl;
997
762
  }
998
763
  /** init is idempotent and browser-only for its side effects: capture first-touch
999
764
  * attribution, hydrate cohort, register the unload flush, and (unless opted out)
@@ -1073,7 +838,7 @@ var Analytics = class {
1073
838
  const view = (p ?? "") + "\0" + (isBrowser() ? window.location.href : "");
1074
839
  if (view === this.counted) return;
1075
840
  this.counted = view;
1076
- this.enqueue("pageview", events.PAGEVIEW, { path: p, properties });
841
+ this.enqueue("pageview", events.PAGEVIEW, { path: p, properties: { ...viewport(), ...properties } });
1077
842
  }
1078
843
  /** capture records a named product event with optional properties. Commerce
1079
844
  * fields (productId/quantity/revenue/currency) may be passed for order events. */
@@ -1081,45 +846,28 @@ var Analytics = class {
1081
846
  this.enqueue("event", event, { properties, ...commerce });
1082
847
  }
1083
848
  /** captureError reports a caught error, an unhandled rejection, a React render
1084
- * error, or a manual report to BOTH planes, from one call:
1085
- *
1086
- * - the ERROR PLANE — a real Sentry envelope to the DSN host. This is the one
1087
- * that produces an issue in sentry.hanzo.ai (grouping, stack frames, AST).
1088
- * Inert when no DSN is configured.
1089
- * - the EVENT STREAM — a `type:'error'` row in the cloud event warehouse, so
1090
- * an error stays correlated with the session's pageviews for product
1091
- * analysis (readable via GET /v1/errors).
1092
- *
1093
- * Both carry the SAME session and subject id, so an error and the pageview
1094
- * before it join up. Never throws back into the app; errors are higher-signal
1095
- * than pageviews, so both planes flush promptly (a crash may unload the page
1096
- * moments later). */
849
+ * error, or a manual report as ONE event of type 'error' on the one stream.
850
+ * It carries the exception (type, message, frames, stack, handled) and the
851
+ * release, environment, site, product and level that group and scope it, and
852
+ * flushes promptly (a crash may unload the page moments later). Never throws
853
+ * back into the app. */
1097
854
  captureError(err, context) {
1098
855
  if (this.reentrant) return;
1099
856
  this.reentrant = true;
1100
857
  try {
1101
- try {
1102
- this.sendError(err, context);
1103
- } catch {
1104
- }
1105
- try {
1106
- const handled = context?.handled ?? true;
1107
- const ex = normalizeError2(err);
1108
- ex.handled = handled;
1109
- this.enqueue("event", events.EXCEPTION, {
1110
- error: ex,
1111
- properties: {
1112
- ...context?.properties,
1113
- ...exceptionProperties(err, {
1114
- handled,
1115
- id: hzUuidv7(),
1116
- level: context?.level
1117
- })
1118
- }
1119
- });
1120
- this.flush();
1121
- } catch {
1122
- }
858
+ const handled = context?.handled ?? true;
859
+ const ex = normalizeError2(err);
860
+ ex.handled = handled;
861
+ this.enqueue("error", void 0, {
862
+ error: ex,
863
+ level: context?.level ?? (handled ? "error" : "fatal"),
864
+ release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
865
+ environment: this.cfg.environment ?? readEnv("NODE_ENV"),
866
+ site: isBrowser() ? window.location.hostname : void 0,
867
+ properties: context?.properties
868
+ });
869
+ this.flush();
870
+ } catch {
1123
871
  } finally {
1124
872
  this.reentrant = false;
1125
873
  }
@@ -1146,7 +894,7 @@ var Analytics = class {
1146
894
  this.queue = [];
1147
895
  this.clearTimer();
1148
896
  const token = this.cfg.getToken?.() ?? void 0;
1149
- const key = token ? void 0 : this.cfg.ingestKey?.trim() || void 0;
897
+ const key = this.cfg.ingestKey?.trim() || void 0;
1150
898
  const useBeacon = beacon && !token;
1151
899
  const body = serializeBatch(batch);
1152
900
  if (body === null) {
@@ -1162,39 +910,6 @@ var Analytics = class {
1162
910
  });
1163
911
  }
1164
912
  // ── internals ────────────────────────────────────────────────────────────
1165
- /** sendError frames one exception as a Sentry envelope and posts it to the DSN's
1166
- * ingest URL. The DSN's own key rides ?sentry_key= (the credential channel the
1167
- * server trusts, and the only one a headerless beacon can carry), so NO bearer
1168
- * or publishable key is attached here — the two planes authenticate
1169
- * independently. Errors are sent one envelope per event, immediately: batching
1170
- * a crash report is how you lose it. */
1171
- sendError(err, options2) {
1172
- if (!this.cfg.enabled || !this.dsn) return;
1173
- const event = buildSentryEvent({
1174
- error: err,
1175
- options: options2,
1176
- identity: this.errorIdentity(),
1177
- capturePII: this.cfg.capturePII ?? false
1178
- });
1179
- const body = buildEnvelope(event, this.dsn);
1180
- if (this.cfg.debug) console.debug("[event] error \u2192", this.dsn.ingestUrl, event.event_id);
1181
- this.transport.send(this.dsn.ingestUrl, body, {
1182
- beacon: false,
1183
- contentType: ENVELOPE_CONTENT_TYPE,
1184
- debug: this.cfg.debug
1185
- });
1186
- }
1187
- /** errorIdentity is the SAME identity the event stream stamps — the OIDC subject
1188
- * once identify() has run, else the anon id. Never email/PII. */
1189
- errorIdentity() {
1190
- return {
1191
- userId: this.personId ?? anonId(),
1192
- sessionId: sessionId(),
1193
- product: this.cfg.product,
1194
- release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
1195
- environment: this.cfg.environment ?? readEnv("NODE_ENV")
1196
- };
1197
- }
1198
913
  enqueue(kind, event, extra) {
1199
914
  if (!this.cfg.enabled) return;
1200
915
  if (!this.started) this.init();
@@ -1283,6 +998,94 @@ function createAnalytics(config) {
1283
998
  clients.set(key, fresh);
1284
999
  return fresh;
1285
1000
  }
1001
+ var MAX_VALUE = 4096;
1002
+ function digest(s) {
1003
+ let out = "";
1004
+ for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
1005
+ let h = seed >>> 0;
1006
+ for (let i = 0; i < s.length; i++) {
1007
+ h ^= s.charCodeAt(i);
1008
+ h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
1009
+ }
1010
+ out += h.toString(16).padStart(8, "0");
1011
+ }
1012
+ return out;
1013
+ }
1014
+ function frameOf(f) {
1015
+ const source = f.filename ?? "";
1016
+ const name = f.function ?? "<anonymous>";
1017
+ const line = f.lineno ?? 0;
1018
+ const column = f.colno ?? 0;
1019
+ return {
1020
+ // Stable per code location, so the same frame groups and re-renders under one
1021
+ // key. The '/part' suffix is the product's format for one raw frame expanding
1022
+ // into several resolved ones; a browser frame is always part 0.
1023
+ raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
1024
+ mangled_name: name,
1025
+ source,
1026
+ line,
1027
+ column,
1028
+ in_app: f.in_app ?? false,
1029
+ lang: "javascript",
1030
+ resolved: false,
1031
+ resolve_failure: "no symbol set uploaded for this release",
1032
+ resolved_name: null,
1033
+ module: null
1034
+ };
1035
+ }
1036
+ function truncate2(s, max) {
1037
+ if (s.length <= max) return s;
1038
+ let end = max;
1039
+ const c = s.charCodeAt(end - 1);
1040
+ if (c >= 55296 && c <= 56319) end--;
1041
+ return s.slice(0, end);
1042
+ }
1043
+ function exceptionEntry(err, opts) {
1044
+ const n = normalizeError(err);
1045
+ const frames = framesFromStack(n.stack).slice(-50);
1046
+ const entry = {
1047
+ id: opts.id,
1048
+ type: n.name,
1049
+ value: truncate2(n.message, MAX_VALUE),
1050
+ mechanism: {
1051
+ type: "generic",
1052
+ handled: opts.handled,
1053
+ // An exception the app reported by hand was constructed, not thrown by the
1054
+ // runtime at a real call site.
1055
+ synthetic: !(err instanceof Error)
1056
+ }
1057
+ };
1058
+ if (frames.length > 0) {
1059
+ entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
1060
+ }
1061
+ return entry;
1062
+ }
1063
+ function fingerprint(entry) {
1064
+ const frames = entry.stacktrace?.frames ?? [];
1065
+ const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
1066
+ return digest([entry.type, ...pieces].join("\n"));
1067
+ }
1068
+ function exceptionProperties(err, opts) {
1069
+ const entry = exceptionEntry(err, opts);
1070
+ const frames = entry.stacktrace?.frames ?? [];
1071
+ const fp = fingerprint(entry);
1072
+ return {
1073
+ $exception_list: [entry],
1074
+ $exception_fingerprint: fp,
1075
+ // Records WHY this fingerprint holds. 'manual' is the honest value: the client
1076
+ // supplied the key rather than a server grouper deriving one.
1077
+ $exception_fingerprint_record: [{ type: "manual" }],
1078
+ $exception_type: entry.type,
1079
+ $exception_message: entry.value,
1080
+ $exception_level: opts.level ?? "error",
1081
+ $exception_handled: opts.handled,
1082
+ $exception_synthetic: entry.mechanism?.synthetic ?? false,
1083
+ $exception_types: [entry.type],
1084
+ $exception_values: [entry.value],
1085
+ $exception_sources: frames.map((f) => f.source).filter((s) => !!s),
1086
+ $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
1087
+ };
1088
+ }
1286
1089
 
1287
1090
  // src/cookie.ts
1288
1091
  var DAY = 86400;
@@ -1399,6 +1202,11 @@ var SITE_KEY = Object.freeze({
1399
1202
  "platform.hanzo.ai": "pk-My1RpZLEUnTj8vAdPbWKYdDUuhxZJ4dVjHcwjZN4rZ8",
1400
1203
  "docs.hanzo.ai": "pk-jukhtjMT2ymoeBDAeFjINQWlBv-v9sNn1TPztiCrrwk",
1401
1204
  "hanzo.bot": "pk-W5d7Mn7ZukT7igyscIy6Pqe8JpA0Ge604Yn4xNR4JCU",
1205
+ "hanzo.chat": "pk-ATxWeB2jNyRSxDgqnkGoDj3CCbzrmTxSktUh9zlrO-E",
1206
+ "hanzo.id": "pk-_GVF3KlaqprlPHDumBMRFeFU1lIGOaarQfgimNVDIo8",
1207
+ "hanzo.industries": "pk-Vh1wvL2HqIAv9qpVvmrznTI9uMHrTD-jyIC-vnlBsgY",
1208
+ "hanzo.market": "pk-DN3xQFa0H61epUVg9OkiOYLzCdteNt9hE6f-S-ednjQ",
1209
+ "hanzo.network": "pk-YJ2eiA_7ckLStMPte8Ar8FsmRp_ycmeIeRbpGprZYmM",
1402
1210
  "cloud.hanzo.ai": "pk-RAfEGHPoNdCEU9fnA_cPd_Xo9Tci44rlYQV9xuJ1Ob0"
1403
1211
  });
1404
1212
  var page = () => window;
@@ -1416,6 +1224,21 @@ function settled() {
1416
1224
  function flush() {
1417
1225
  if (settled() || leaving) held.splice(0).forEach((send) => send());
1418
1226
  }
1227
+ function ready(limit = 3e3) {
1228
+ if (typeof window === "undefined") return Promise.resolve();
1229
+ const start2 = Date.now();
1230
+ const meta = () => {
1231
+ const fbq = page().fbq;
1232
+ return ![...loaded].some((k) => k.startsWith("meta:")) || typeof fbq?.callMethod === "function";
1233
+ };
1234
+ return new Promise((resolve) => {
1235
+ const tick = () => {
1236
+ if (configured && settled() && meta() || Date.now() - start2 >= limit) return resolve();
1237
+ setTimeout(tick, 100);
1238
+ };
1239
+ tick();
1240
+ });
1241
+ }
1419
1242
  function script(src, onload, onerror) {
1420
1243
  const s = document.createElement("script");
1421
1244
  s.async = true;
@@ -1826,19 +1649,15 @@ exports.GOALS = GOALS;
1826
1649
  exports.LINK_PARAMS = LINK_PARAMS;
1827
1650
  exports.ORG_DOMAIN = ORG_DOMAIN;
1828
1651
  exports.ORG_KEY = ORG_KEY;
1829
- exports.PRODUCT_PROJECT = PRODUCT_PROJECT;
1830
1652
  exports.SITE_KEY = SITE_KEY;
1831
1653
  exports.VERSION = VERSION;
1832
1654
  exports.acceptAll = acceptAll;
1833
1655
  exports.asks = asks;
1834
- exports.buildEnvelope = buildEnvelope;
1835
- exports.buildSentryEvent = buildSentryEvent;
1836
1656
  exports.captureClick = capture;
1837
1657
  exports.commerceItem = commerceItem;
1838
1658
  exports.createAnalytics = createAnalytics;
1839
1659
  exports.deriveChannel = deriveChannel;
1840
1660
  exports.digest = digest;
1841
- exports.dsnForProduct = dsnForProduct;
1842
1661
  exports.exceptionEntry = exceptionEntry;
1843
1662
  exports.exceptionProperties = exceptionProperties;
1844
1663
  exports.fingerprint = fingerprint;
@@ -1855,7 +1674,6 @@ exports.linkUrl = linkUrl;
1855
1674
  exports.mirror = mirror;
1856
1675
  exports.orgOf = orgOf;
1857
1676
  exports.parseAttribution = parseAttribution;
1858
- exports.parseDsn = parseDsn;
1859
1677
  exports.reach = reach;
1860
1678
  exports.readConsent = read2;
1861
1679
  exports.readLink = readLink;
@@ -1871,6 +1689,7 @@ exports.scrubText = scrubText;
1871
1689
  exports.startTags = start;
1872
1690
  exports.storedConsent = stored;
1873
1691
  exports.stripLink = stripLink;
1692
+ exports.tagsReady = ready;
1874
1693
  exports.touch = touch;
1875
1694
  exports.track = track;
1876
1695
  exports.uuidv7 = hzUuidv7;