@hanzo/event 0.3.21 → 0.3.23

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.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ import { eventsOf, EVENTS, PAGEVIEW, EXCEPTION } from '@hanzo/events';
2
+ export { EVENTS, EXCEPTION, FUNNELS, PAGEVIEW, PRODUCTS, eventsOf } from '@hanzo/events';
3
+
1
4
  // src/attribution.ts
2
5
  var SOCIAL_HOSTS = [
3
6
  "facebook.",
@@ -46,16 +49,16 @@ function deriveChannel(a) {
46
49
  }
47
50
  function hostOf(raw) {
48
51
  if (!raw) return "";
49
- let s2 = raw.trim();
50
- const scheme = s2.indexOf("://");
51
- if (scheme >= 0) s2 = s2.slice(scheme + 3);
52
- const cut = s2.search(/[/?#]/);
53
- if (cut >= 0) s2 = s2.slice(0, cut);
54
- const at = s2.indexOf("@");
55
- if (at >= 0) s2 = s2.slice(at + 1);
56
- const colon = s2.indexOf(":");
57
- if (colon >= 0) s2 = s2.slice(0, colon);
58
- return s2.toLowerCase().trim();
52
+ let s = raw.trim();
53
+ const scheme = s.indexOf("://");
54
+ if (scheme >= 0) s = s.slice(scheme + 3);
55
+ const cut = s.search(/[/?#]/);
56
+ if (cut >= 0) s = s.slice(0, cut);
57
+ const at = s.indexOf("@");
58
+ if (at >= 0) s = s.slice(at + 1);
59
+ const colon = s.indexOf(":");
60
+ if (colon >= 0) s = s.slice(0, colon);
61
+ return s.toLowerCase().trim();
59
62
  }
60
63
  function hasAttribution(a) {
61
64
  return Boolean(
@@ -87,59 +90,6 @@ function dsnForProduct(product, key) {
87
90
  return `https://${key}@api.hanzo.ai/v1/sentry/${projectId}`;
88
91
  }
89
92
 
90
- // src/events.ts
91
- var EVENTS = {
92
- // Signup funnel: view -> submit -> verify -> completed -> first action.
93
- SIGNUP_VIEWED: "signup_viewed",
94
- SIGNUP_SUBMITTED: "signup_submitted",
95
- SIGNUP_VERIFIED: "signup_verified",
96
- SIGNUP_COMPLETED: "signup_completed",
97
- /** A RETURNING user authenticated — the non-signup half of the IAM callback.
98
- * Keeping it distinct is what stops returning logins from inflating signups. */
99
- LOGIN_COMPLETED: "login_completed",
100
- /** Activation: the first moment of real value. ONE event for every product —
101
- * the product-specific moment is the `action` property (api_call, app_live,
102
- * chat_reply), never a new event name. */
103
- FIRST_ACTION: "first_action",
104
- // Waitlist + referral.
105
- WAITLIST_JOINED: "waitlist_joined",
106
- WAITLIST_SHARED: "waitlist_shared",
107
- REFERRAL_USED: "referral_used",
108
- REFERRAL_CLAIMED: "referral_claimed",
109
- // Upgrade-intent + purchase.
110
- PRICING_VIEWED: "pricing_viewed",
111
- PLAN_CLICKED: "plan_clicked",
112
- CHECKOUT_STARTED: "checkout_started",
113
- ORDER_COMPLETED: "order_completed",
114
- // Feature usage — generic + the common key surfaces across products.
115
- FEATURE_USED: "feature_used",
116
- API_KEY_CREATED: "api_key_created",
117
- APP_CREATED: "app_created",
118
- PROJECT_CREATED: "project_created",
119
- AGENT_CREATED: "agent_created",
120
- CHAT_STARTED: "chat_started",
121
- CHAT_MESSAGE_SENT: "chat_message_sent",
122
- /** The user switched model/endpoint — the single strongest quality signal a
123
- * chat surface emits (a switch usually follows a bad answer). */
124
- MODEL_SWITCHED: "model_switched",
125
- TASK_STARTED: "task_started",
126
- TASK_COMPLETED: "task_completed",
127
- // Build → ship. `build_*` is a MODEL producing an artifact; `deploy_*` is that
128
- // artifact going live. Intent (build_started) is never the same event as the
129
- // artifact existing (app_created) — conflating them makes the funnel lie.
130
- BUILD_STARTED: "build_started",
131
- /** A model finished producing an artifact (an app build, a chat reply, an agent
132
- * run). Carries `durationMs` — the outcome event owns its own duration, so no
133
- * paired start event is needed. */
134
- GENERATION_COMPLETED: "generation_completed",
135
- GENERATION_FAILED: "generation_failed",
136
- DEPLOY_STARTED: "deploy_started",
137
- DEPLOY_SUCCEEDED: "deploy_succeeded",
138
- DEPLOY_FAILED: "deploy_failed"
139
- };
140
- var PAGEVIEW = "$pageview";
141
- var EXCEPTION = "$exception";
142
-
143
93
  // src/scrub.ts
144
94
  var REDACTED = "[redacted]";
145
95
  var EMAIL_MARK = "[email]";
@@ -188,8 +138,8 @@ function luhn(digits) {
188
138
  }
189
139
  return sum % 10 === 0;
190
140
  }
191
- function redactPAN(s2) {
192
- return s2.replace(RE_PAN, (m) => {
141
+ function redactPAN(s) {
142
+ return s.replace(RE_PAN, (m) => {
193
143
  const digits = m.replace(/[ -]/g, "");
194
144
  if (digits.length < 13 || digits.length > 19) return m;
195
145
  return luhn(digits) ? REDACTED : m;
@@ -234,30 +184,30 @@ var RE_CREDENTIAL_PARAM = new RegExp(
234
184
  "([?&#;](?:" + CREDENTIAL_PARAMS.join("|") + ")=)[^&#;\\s]{1,4096}",
235
185
  "gi"
236
186
  );
237
- function redactCredentialParams(s2) {
238
- return s2.replace(RE_CREDENTIAL_PARAM, (_m, prefix) => prefix + REDACTED);
187
+ function redactCredentialParams(s) {
188
+ return s.replace(RE_CREDENTIAL_PARAM, (_m, prefix) => prefix + REDACTED);
239
189
  }
240
- function redactSecrets(s2) {
241
- s2 = redactCredentialParams(s2);
242
- for (const re of SECRET_PATTERNS) s2 = s2.replace(re, REDACTED);
243
- return redactPAN(s2);
190
+ function redactSecrets(s) {
191
+ s = redactCredentialParams(s);
192
+ for (const re of SECRET_PATTERNS) s = s.replace(re, REDACTED);
193
+ return redactPAN(s);
244
194
  }
245
- function scrubPII(s2) {
246
- s2 = s2.replace(RE_EMAIL, EMAIL_MARK);
247
- s2 = s2.replace(RE_IPV6, IP_MARK);
248
- s2 = s2.replace(RE_IPV4, IP_MARK);
249
- return s2;
195
+ function scrubPII(s) {
196
+ s = s.replace(RE_EMAIL, EMAIL_MARK);
197
+ s = s.replace(RE_IPV6, IP_MARK);
198
+ s = s.replace(RE_IPV4, IP_MARK);
199
+ return s;
250
200
  }
251
201
  var MAX_SCRUB_LEN = 8192;
252
- function truncate(s2, max = MAX_SCRUB_LEN) {
253
- return s2.length > max ? s2.slice(0, max) + "\u2026 [truncated]" : s2;
202
+ function truncate(s, max = MAX_SCRUB_LEN) {
203
+ return s.length > max ? s.slice(0, max) + "\u2026 [truncated]" : s;
254
204
  }
255
- function scrubText(s2, capturePII = false) {
256
- if (!s2) return s2 ?? "";
257
- s2 = truncate(s2);
258
- s2 = redactSecrets(s2);
259
- if (!capturePII) s2 = scrubPII(s2);
260
- return s2;
205
+ function scrubText(s, capturePII = false) {
206
+ if (!s) return s ?? "";
207
+ s = truncate(s);
208
+ s = redactSecrets(s);
209
+ if (!capturePII) s = scrubPII(s);
210
+ return s;
261
211
  }
262
212
 
263
213
  // src/anon.js
@@ -351,12 +301,12 @@ function hzAnonWrite(name, value) {
351
301
  }
352
302
  function hzAnonId() {
353
303
  if (typeof window === "undefined") return "";
354
- var s2 = hzAnonStore();
355
- var id = hzAnonCookie(HZ_ANON_KEY) || hzAnonItem(s2, HZ_ANON_KEY) || hzAnonItem(s2, HZ_ANON_LEGACY_KEY) || hzAnonMemo || hzUuidv7();
304
+ var s = hzAnonStore();
305
+ var id = hzAnonCookie(HZ_ANON_KEY) || hzAnonItem(s, HZ_ANON_KEY) || hzAnonItem(s, HZ_ANON_LEGACY_KEY) || hzAnonMemo || hzUuidv7();
356
306
  hzAnonMemo = id;
357
307
  hzAnonWrite(HZ_ANON_KEY, id);
358
308
  try {
359
- if (s2 && s2.getItem(HZ_ANON_KEY) !== id) s2.setItem(HZ_ANON_KEY, id);
309
+ if (s && s.getItem(HZ_ANON_KEY) !== id) s.setItem(HZ_ANON_KEY, id);
360
310
  } catch (e) {
361
311
  }
362
312
  return id;
@@ -368,7 +318,7 @@ function uuidv7Time(id) {
368
318
  }
369
319
 
370
320
  // src/version.ts
371
- var VERSION = "0.3.21";
321
+ var VERSION = "0.3.23";
372
322
 
373
323
  // src/sentry.ts
374
324
  var MAX_FRAMES = 50;
@@ -379,10 +329,10 @@ var MAX_TAGS = 50;
379
329
  function eventId() {
380
330
  return hzUuidv7().replace(/-/g, "");
381
331
  }
382
- function byteLen(s2) {
383
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s2).length;
384
- if (typeof Buffer !== "undefined") return Buffer.byteLength(s2, "utf8");
385
- return s2.length;
332
+ function byteLen(s) {
333
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
334
+ if (typeof Buffer !== "undefined") return Buffer.byteLength(s, "utf8");
335
+ return s.length;
386
336
  }
387
337
  function parseDsn(dsn) {
388
338
  if (!dsn) return null;
@@ -483,7 +433,7 @@ function str(v) {
483
433
  }
484
434
  }
485
435
  function coerceTag(v) {
486
- const s2 = typeof v === "string" ? v : (() => {
436
+ const s = typeof v === "string" ? v : (() => {
487
437
  try {
488
438
  return JSON.stringify(v) ?? String(v);
489
439
  } catch {
@@ -494,7 +444,7 @@ function coerceTag(v) {
494
444
  }
495
445
  }
496
446
  })();
497
- return s2.length > MAX_TAG_LEN ? s2.slice(0, MAX_TAG_LEN) : s2;
447
+ return s.length > MAX_TAG_LEN ? s.slice(0, MAX_TAG_LEN) : s;
498
448
  }
499
449
  function buildSentryEvent(input) {
500
450
  const { error, options = {}, identity, capturePII = false } = input;
@@ -561,12 +511,12 @@ ${payload}
561
511
  `;
562
512
  }
563
513
  var MAX_VALUE = 4096;
564
- function digest(s2) {
514
+ function digest(s) {
565
515
  let out = "";
566
516
  for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
567
517
  let h = seed >>> 0;
568
- for (let i = 0; i < s2.length; i++) {
569
- h ^= s2.charCodeAt(i);
518
+ for (let i = 0; i < s.length; i++) {
519
+ h ^= s.charCodeAt(i);
570
520
  h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
571
521
  }
572
522
  out += h.toString(16).padStart(8, "0");
@@ -595,12 +545,12 @@ function frameOf(f) {
595
545
  module: null
596
546
  };
597
547
  }
598
- function truncate2(s2, max) {
599
- if (s2.length <= max) return s2;
548
+ function truncate2(s, max) {
549
+ if (s.length <= max) return s;
600
550
  let end = max;
601
- const c = s2.charCodeAt(end - 1);
551
+ const c = s.charCodeAt(end - 1);
602
552
  if (c >= 55296 && c <= 56319) end--;
603
- return s2.slice(0, end);
553
+ return s.slice(0, end);
604
554
  }
605
555
  function exceptionEntry(err, opts) {
606
556
  const n = normalizeError(err);
@@ -644,8 +594,8 @@ function exceptionProperties(err, opts) {
644
594
  $exception_synthetic: entry.mechanism?.synthetic ?? false,
645
595
  $exception_types: [entry.type],
646
596
  $exception_values: [entry.value],
647
- $exception_sources: frames.map((f) => f.source).filter((s2) => !!s2),
648
- $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s2) => !!s2)
597
+ $exception_sources: frames.map((f) => f.source).filter((s) => !!s),
598
+ $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
649
599
  };
650
600
  }
651
601
 
@@ -668,11 +618,11 @@ function anonId() {
668
618
  return hzAnonId() || void 0;
669
619
  }
670
620
  function sessionId(now = Date.now()) {
671
- const s2 = ls();
672
- if (!s2) return void 0;
621
+ const s = ls();
622
+ if (!s) return void 0;
673
623
  let state = null;
674
624
  try {
675
- state = JSON.parse(s2.getItem(KEY.session) || "null");
625
+ state = JSON.parse(s.getItem(KEY.session) || "null");
676
626
  } catch {
677
627
  state = null;
678
628
  }
@@ -681,45 +631,45 @@ function sessionId(now = Date.now()) {
681
631
  } else {
682
632
  state.last = now;
683
633
  }
684
- s2.setItem(KEY.session, JSON.stringify(state));
634
+ s.setItem(KEY.session, JSON.stringify(state));
685
635
  return state.id;
686
636
  }
687
637
  function getFirstTouch() {
688
- const s2 = ls();
689
- if (!s2) return void 0;
638
+ const s = ls();
639
+ if (!s) return void 0;
690
640
  try {
691
- const v = s2.getItem(KEY.firstTouch);
641
+ const v = s.getItem(KEY.firstTouch);
692
642
  return v ? JSON.parse(v) : void 0;
693
643
  } catch {
694
644
  return void 0;
695
645
  }
696
646
  }
697
647
  function setFirstTouchOnce(a) {
698
- const s2 = ls();
648
+ const s = ls();
699
649
  const existing = getFirstTouch();
700
650
  if (existing) return existing;
701
- if (s2) s2.setItem(KEY.firstTouch, JSON.stringify(a));
651
+ if (s) s.setItem(KEY.firstTouch, JSON.stringify(a));
702
652
  return a;
703
653
  }
704
654
  function getCohort() {
705
- const s2 = ls();
706
- if (!s2) return void 0;
655
+ const s = ls();
656
+ if (!s) return void 0;
707
657
  try {
708
- const v = s2.getItem(KEY.cohort);
658
+ const v = s.getItem(KEY.cohort);
709
659
  return v ? JSON.parse(v) : void 0;
710
660
  } catch {
711
661
  return void 0;
712
662
  }
713
663
  }
714
664
  function mergeCohort(patch) {
715
- const s2 = ls();
665
+ const s = ls();
716
666
  const cur = getCohort() || {};
717
667
  const next = {
718
668
  signupWeek: cur.signupWeek || patch.signupWeek,
719
669
  channel: patch.channel || cur.channel,
720
670
  refCode: cur.refCode || patch.refCode
721
671
  };
722
- if (s2) s2.setItem(KEY.cohort, JSON.stringify(next));
672
+ if (s) s.setItem(KEY.cohort, JSON.stringify(next));
723
673
  return next;
724
674
  }
725
675
 
@@ -1079,108 +1029,6 @@ function createAnalytics(config) {
1079
1029
  return new Analytics(config);
1080
1030
  }
1081
1031
 
1082
- // src/funnels.ts
1083
- var PRODUCTS = ["site", "app", "chat", "console", "admin", "cloud"];
1084
- var s = (event, label, where) => ({
1085
- event,
1086
- label,
1087
- ...where ? { where } : {}
1088
- });
1089
- var FUNNELS = {
1090
- /** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
1091
- * redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
1092
- signup: {
1093
- label: "Signup",
1094
- products: ["site"],
1095
- join: "person",
1096
- steps: [
1097
- s(PAGEVIEW, "Landed"),
1098
- s(EVENTS.SIGNUP_VIEWED, "Opened signup"),
1099
- s(EVENTS.SIGNUP_SUBMITTED, "Redirected to Hanzo ID"),
1100
- s(EVENTS.SIGNUP_COMPLETED, "Account created"),
1101
- s(EVENTS.FIRST_ACTION, "First action")
1102
- ]
1103
- },
1104
- /** The developer activation path: an account is worth nothing until a key has
1105
- * made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
1106
- * Cloud on an org's first successful /v1 request — a browser cannot see it. */
1107
- apiActivation: {
1108
- label: "API activation",
1109
- products: ["site", "cloud"],
1110
- join: "person",
1111
- steps: [
1112
- s(EVENTS.SIGNUP_COMPLETED, "Account created"),
1113
- s(EVENTS.API_KEY_CREATED, "Key minted"),
1114
- s(EVENTS.FIRST_ACTION, "First successful API call", {
1115
- property: "action",
1116
- equals: "api_call"
1117
- })
1118
- ]
1119
- },
1120
- /** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
1121
- upgrade: {
1122
- label: "Upgrade",
1123
- products: ["site", "app", "console"],
1124
- join: "person",
1125
- steps: [
1126
- s(EVENTS.PRICING_VIEWED, "Viewed pricing"),
1127
- s(EVENTS.PLAN_CLICKED, "Chose a plan"),
1128
- s(EVENTS.CHECKOUT_STARTED, "Started checkout"),
1129
- s(EVENTS.ORDER_COMPLETED, "Paid")
1130
- ]
1131
- },
1132
- /** hanzo.app: describe → build → deploy → live URL. The whole product thesis
1133
- * in five steps; `deploy_succeeded` is the moment a live URL exists. */
1134
- appShip: {
1135
- label: "Describe \u2192 ship",
1136
- products: ["app"],
1137
- join: "person",
1138
- steps: [
1139
- s(PAGEVIEW, "Landed"),
1140
- s(EVENTS.BUILD_STARTED, "Described an app"),
1141
- s(EVENTS.GENERATION_COMPLETED, "Got a working build"),
1142
- s(EVENTS.DEPLOY_STARTED, "Hit publish"),
1143
- s(EVENTS.DEPLOY_SUCCEEDED, "Live URL")
1144
- ]
1145
- },
1146
- /** hanzo.chat: visit → first message → answer. `generation_completed` is what
1147
- * separates "typed something" from "got value". */
1148
- chatEngage: {
1149
- label: "Chat engagement",
1150
- products: ["chat"],
1151
- join: "person",
1152
- steps: [
1153
- s(PAGEVIEW, "Landed"),
1154
- s(EVENTS.CHAT_STARTED, "Started a conversation"),
1155
- s(EVENTS.CHAT_MESSAGE_SENT, "Sent a message"),
1156
- s(EVENTS.GENERATION_COMPLETED, "Got an answer")
1157
- ]
1158
- },
1159
- /** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
1160
- * hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
1161
- * The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
1162
- * which makes the drop-off measurable without any cross-domain identity. */
1163
- siteToChat: {
1164
- label: "Site \u2192 Chat handoff",
1165
- products: ["site", "chat"],
1166
- join: "aggregate",
1167
- steps: [
1168
- s(EVENTS.CHAT_STARTED, "Submitted the hanzo.ai composer", {
1169
- property: "source",
1170
- equals: "composer"
1171
- }),
1172
- s(EVENTS.CHAT_STARTED, "Landed in hanzo.chat", {
1173
- property: "referrerProduct",
1174
- equals: "site"
1175
- }),
1176
- s(EVENTS.GENERATION_COMPLETED, "Got an answer")
1177
- ]
1178
- }
1179
- };
1180
- function eventsOf(id) {
1181
- return FUNNELS[id].steps.map((step) => step.event);
1182
- }
1183
-
1184
1032
  // src/goals.ts
1185
1033
  var GOALS = {
1186
1034
  // Signup: the conversion is signup_completed, along the site signup funnel.
@@ -1241,6 +1089,6 @@ var COHORTS = {
1241
1089
  * reformat of one copy is a diff against the other.
1242
1090
  */
1243
1091
 
1244
- export { Analytics, COHORTS, EVENTS, EXCEPTION, FUNNELS, GOALS, PAGEVIEW, PRODUCTS, PRODUCT_PROJECT, VERSION, buildEnvelope, buildSentryEvent, createAnalytics, deriveChannel, digest, dsnForProduct, eventsOf, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
1092
+ export { Analytics, COHORTS, GOALS, PRODUCT_PROJECT, VERSION, buildEnvelope, buildSentryEvent, createAnalytics, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
1245
1093
  //# sourceMappingURL=index.mjs.map
1246
1094
  //# sourceMappingURL=index.mjs.map