@hanzo/event 0.3.22 → 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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 hanzo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs CHANGED
@@ -50,16 +50,16 @@ function deriveChannel(a) {
50
50
  }
51
51
  function hostOf(raw) {
52
52
  if (!raw) return "";
53
- let s2 = raw.trim();
54
- const scheme = s2.indexOf("://");
55
- if (scheme >= 0) s2 = s2.slice(scheme + 3);
56
- const cut = s2.search(/[/?#]/);
57
- if (cut >= 0) s2 = s2.slice(0, cut);
58
- const at = s2.indexOf("@");
59
- if (at >= 0) s2 = s2.slice(at + 1);
60
- const colon = s2.indexOf(":");
61
- if (colon >= 0) s2 = s2.slice(0, colon);
62
- return s2.toLowerCase().trim();
53
+ let s = raw.trim();
54
+ const scheme = s.indexOf("://");
55
+ if (scheme >= 0) s = s.slice(scheme + 3);
56
+ const cut = s.search(/[/?#]/);
57
+ if (cut >= 0) s = s.slice(0, cut);
58
+ const at = s.indexOf("@");
59
+ if (at >= 0) s = s.slice(at + 1);
60
+ const colon = s.indexOf(":");
61
+ if (colon >= 0) s = s.slice(0, colon);
62
+ return s.toLowerCase().trim();
63
63
  }
64
64
  function hasAttribution(a) {
65
65
  return Boolean(
@@ -139,8 +139,8 @@ function luhn(digits) {
139
139
  }
140
140
  return sum % 10 === 0;
141
141
  }
142
- function redactPAN(s2) {
143
- return s2.replace(RE_PAN, (m) => {
142
+ function redactPAN(s) {
143
+ return s.replace(RE_PAN, (m) => {
144
144
  const digits = m.replace(/[ -]/g, "");
145
145
  if (digits.length < 13 || digits.length > 19) return m;
146
146
  return luhn(digits) ? REDACTED : m;
@@ -185,30 +185,30 @@ var RE_CREDENTIAL_PARAM = new RegExp(
185
185
  "([?&#;](?:" + CREDENTIAL_PARAMS.join("|") + ")=)[^&#;\\s]{1,4096}",
186
186
  "gi"
187
187
  );
188
- function redactCredentialParams(s2) {
189
- return s2.replace(RE_CREDENTIAL_PARAM, (_m, prefix) => prefix + REDACTED);
188
+ function redactCredentialParams(s) {
189
+ return s.replace(RE_CREDENTIAL_PARAM, (_m, prefix) => prefix + REDACTED);
190
190
  }
191
- function redactSecrets(s2) {
192
- s2 = redactCredentialParams(s2);
193
- for (const re of SECRET_PATTERNS) s2 = s2.replace(re, REDACTED);
194
- return redactPAN(s2);
191
+ function redactSecrets(s) {
192
+ s = redactCredentialParams(s);
193
+ for (const re of SECRET_PATTERNS) s = s.replace(re, REDACTED);
194
+ return redactPAN(s);
195
195
  }
196
- function scrubPII(s2) {
197
- s2 = s2.replace(RE_EMAIL, EMAIL_MARK);
198
- s2 = s2.replace(RE_IPV6, IP_MARK);
199
- s2 = s2.replace(RE_IPV4, IP_MARK);
200
- return s2;
196
+ function scrubPII(s) {
197
+ s = s.replace(RE_EMAIL, EMAIL_MARK);
198
+ s = s.replace(RE_IPV6, IP_MARK);
199
+ s = s.replace(RE_IPV4, IP_MARK);
200
+ return s;
201
201
  }
202
202
  var MAX_SCRUB_LEN = 8192;
203
- function truncate(s2, max = MAX_SCRUB_LEN) {
204
- return s2.length > max ? s2.slice(0, max) + "\u2026 [truncated]" : s2;
203
+ function truncate(s, max = MAX_SCRUB_LEN) {
204
+ return s.length > max ? s.slice(0, max) + "\u2026 [truncated]" : s;
205
205
  }
206
- function scrubText(s2, capturePII = false) {
207
- if (!s2) return s2 ?? "";
208
- s2 = truncate(s2);
209
- s2 = redactSecrets(s2);
210
- if (!capturePII) s2 = scrubPII(s2);
211
- return s2;
206
+ function scrubText(s, capturePII = false) {
207
+ if (!s) return s ?? "";
208
+ s = truncate(s);
209
+ s = redactSecrets(s);
210
+ if (!capturePII) s = scrubPII(s);
211
+ return s;
212
212
  }
213
213
 
214
214
  // src/anon.js
@@ -302,12 +302,12 @@ function hzAnonWrite(name, value) {
302
302
  }
303
303
  function hzAnonId() {
304
304
  if (typeof window === "undefined") return "";
305
- var s2 = hzAnonStore();
306
- var id = hzAnonCookie(HZ_ANON_KEY) || hzAnonItem(s2, HZ_ANON_KEY) || hzAnonItem(s2, HZ_ANON_LEGACY_KEY) || hzAnonMemo || hzUuidv7();
305
+ var s = hzAnonStore();
306
+ var id = hzAnonCookie(HZ_ANON_KEY) || hzAnonItem(s, HZ_ANON_KEY) || hzAnonItem(s, HZ_ANON_LEGACY_KEY) || hzAnonMemo || hzUuidv7();
307
307
  hzAnonMemo = id;
308
308
  hzAnonWrite(HZ_ANON_KEY, id);
309
309
  try {
310
- if (s2 && s2.getItem(HZ_ANON_KEY) !== id) s2.setItem(HZ_ANON_KEY, id);
310
+ if (s && s.getItem(HZ_ANON_KEY) !== id) s.setItem(HZ_ANON_KEY, id);
311
311
  } catch (e) {
312
312
  }
313
313
  return id;
@@ -319,7 +319,7 @@ function uuidv7Time(id) {
319
319
  }
320
320
 
321
321
  // src/version.ts
322
- var VERSION = "0.3.21";
322
+ var VERSION = "0.3.23";
323
323
 
324
324
  // src/sentry.ts
325
325
  var MAX_FRAMES = 50;
@@ -330,10 +330,10 @@ var MAX_TAGS = 50;
330
330
  function eventId() {
331
331
  return hzUuidv7().replace(/-/g, "");
332
332
  }
333
- function byteLen(s2) {
334
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s2).length;
335
- if (typeof Buffer !== "undefined") return Buffer.byteLength(s2, "utf8");
336
- return s2.length;
333
+ function byteLen(s) {
334
+ if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
335
+ if (typeof Buffer !== "undefined") return Buffer.byteLength(s, "utf8");
336
+ return s.length;
337
337
  }
338
338
  function parseDsn(dsn) {
339
339
  if (!dsn) return null;
@@ -434,7 +434,7 @@ function str(v) {
434
434
  }
435
435
  }
436
436
  function coerceTag(v) {
437
- const s2 = typeof v === "string" ? v : (() => {
437
+ const s = typeof v === "string" ? v : (() => {
438
438
  try {
439
439
  return JSON.stringify(v) ?? String(v);
440
440
  } catch {
@@ -445,7 +445,7 @@ function coerceTag(v) {
445
445
  }
446
446
  }
447
447
  })();
448
- return s2.length > MAX_TAG_LEN ? s2.slice(0, MAX_TAG_LEN) : s2;
448
+ return s.length > MAX_TAG_LEN ? s.slice(0, MAX_TAG_LEN) : s;
449
449
  }
450
450
  function buildSentryEvent(input) {
451
451
  const { error, options = {}, identity, capturePII = false } = input;
@@ -512,12 +512,12 @@ ${payload}
512
512
  `;
513
513
  }
514
514
  var MAX_VALUE = 4096;
515
- function digest(s2) {
515
+ function digest(s) {
516
516
  let out = "";
517
517
  for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
518
518
  let h = seed >>> 0;
519
- for (let i = 0; i < s2.length; i++) {
520
- h ^= s2.charCodeAt(i);
519
+ for (let i = 0; i < s.length; i++) {
520
+ h ^= s.charCodeAt(i);
521
521
  h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
522
522
  }
523
523
  out += h.toString(16).padStart(8, "0");
@@ -546,12 +546,12 @@ function frameOf(f) {
546
546
  module: null
547
547
  };
548
548
  }
549
- function truncate2(s2, max) {
550
- if (s2.length <= max) return s2;
549
+ function truncate2(s, max) {
550
+ if (s.length <= max) return s;
551
551
  let end = max;
552
- const c = s2.charCodeAt(end - 1);
552
+ const c = s.charCodeAt(end - 1);
553
553
  if (c >= 55296 && c <= 56319) end--;
554
- return s2.slice(0, end);
554
+ return s.slice(0, end);
555
555
  }
556
556
  function exceptionEntry(err, opts) {
557
557
  const n = normalizeError(err);
@@ -595,8 +595,8 @@ function exceptionProperties(err, opts) {
595
595
  $exception_synthetic: entry.mechanism?.synthetic ?? false,
596
596
  $exception_types: [entry.type],
597
597
  $exception_values: [entry.value],
598
- $exception_sources: frames.map((f) => f.source).filter((s2) => !!s2),
599
- $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s2) => !!s2)
598
+ $exception_sources: frames.map((f) => f.source).filter((s) => !!s),
599
+ $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
600
600
  };
601
601
  }
602
602
 
@@ -619,11 +619,11 @@ function anonId() {
619
619
  return hzAnonId() || void 0;
620
620
  }
621
621
  function sessionId(now = Date.now()) {
622
- const s2 = ls();
623
- if (!s2) return void 0;
622
+ const s = ls();
623
+ if (!s) return void 0;
624
624
  let state = null;
625
625
  try {
626
- state = JSON.parse(s2.getItem(KEY.session) || "null");
626
+ state = JSON.parse(s.getItem(KEY.session) || "null");
627
627
  } catch {
628
628
  state = null;
629
629
  }
@@ -632,45 +632,45 @@ function sessionId(now = Date.now()) {
632
632
  } else {
633
633
  state.last = now;
634
634
  }
635
- s2.setItem(KEY.session, JSON.stringify(state));
635
+ s.setItem(KEY.session, JSON.stringify(state));
636
636
  return state.id;
637
637
  }
638
638
  function getFirstTouch() {
639
- const s2 = ls();
640
- if (!s2) return void 0;
639
+ const s = ls();
640
+ if (!s) return void 0;
641
641
  try {
642
- const v = s2.getItem(KEY.firstTouch);
642
+ const v = s.getItem(KEY.firstTouch);
643
643
  return v ? JSON.parse(v) : void 0;
644
644
  } catch {
645
645
  return void 0;
646
646
  }
647
647
  }
648
648
  function setFirstTouchOnce(a) {
649
- const s2 = ls();
649
+ const s = ls();
650
650
  const existing = getFirstTouch();
651
651
  if (existing) return existing;
652
- if (s2) s2.setItem(KEY.firstTouch, JSON.stringify(a));
652
+ if (s) s.setItem(KEY.firstTouch, JSON.stringify(a));
653
653
  return a;
654
654
  }
655
655
  function getCohort() {
656
- const s2 = ls();
657
- if (!s2) return void 0;
656
+ const s = ls();
657
+ if (!s) return void 0;
658
658
  try {
659
- const v = s2.getItem(KEY.cohort);
659
+ const v = s.getItem(KEY.cohort);
660
660
  return v ? JSON.parse(v) : void 0;
661
661
  } catch {
662
662
  return void 0;
663
663
  }
664
664
  }
665
665
  function mergeCohort(patch) {
666
- const s2 = ls();
666
+ const s = ls();
667
667
  const cur = getCohort() || {};
668
668
  const next = {
669
669
  signupWeek: cur.signupWeek || patch.signupWeek,
670
670
  channel: patch.channel || cur.channel,
671
671
  refCode: cur.refCode || patch.refCode
672
672
  };
673
- if (s2) s2.setItem(KEY.cohort, JSON.stringify(next));
673
+ if (s) s.setItem(KEY.cohort, JSON.stringify(next));
674
674
  return next;
675
675
  }
676
676
 
@@ -1030,108 +1030,6 @@ function createAnalytics(config) {
1030
1030
  return new Analytics(config);
1031
1031
  }
1032
1032
 
1033
- // src/funnels.ts
1034
- var PRODUCTS = ["site", "app", "chat", "console", "admin", "cloud"];
1035
- var s = (event, label, where) => ({
1036
- event,
1037
- label,
1038
- ...where ? { where } : {}
1039
- });
1040
- var FUNNELS = {
1041
- /** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
1042
- * redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
1043
- signup: {
1044
- label: "Signup",
1045
- products: ["site"],
1046
- join: "person",
1047
- steps: [
1048
- s(events.PAGEVIEW, "Landed"),
1049
- s(events.EVENTS.SIGNUP_VIEWED, "Opened signup"),
1050
- s(events.EVENTS.SIGNUP_SUBMITTED, "Redirected to Hanzo ID"),
1051
- s(events.EVENTS.SIGNUP_COMPLETED, "Account created"),
1052
- s(events.EVENTS.FIRST_ACTION, "First action")
1053
- ]
1054
- },
1055
- /** The developer activation path: an account is worth nothing until a key has
1056
- * made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
1057
- * Cloud on an org's first successful /v1 request — a browser cannot see it. */
1058
- apiActivation: {
1059
- label: "API activation",
1060
- products: ["site", "cloud"],
1061
- join: "person",
1062
- steps: [
1063
- s(events.EVENTS.SIGNUP_COMPLETED, "Account created"),
1064
- s(events.EVENTS.API_KEY_CREATED, "Key minted"),
1065
- s(events.EVENTS.FIRST_ACTION, "First successful API call", {
1066
- property: "action",
1067
- equals: "api_call"
1068
- })
1069
- ]
1070
- },
1071
- /** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
1072
- upgrade: {
1073
- label: "Upgrade",
1074
- products: ["site", "app", "console"],
1075
- join: "person",
1076
- steps: [
1077
- s(events.EVENTS.PRICING_VIEWED, "Viewed pricing"),
1078
- s(events.EVENTS.PLAN_CLICKED, "Chose a plan"),
1079
- s(events.EVENTS.CHECKOUT_STARTED, "Started checkout"),
1080
- s(events.EVENTS.ORDER_COMPLETED, "Paid")
1081
- ]
1082
- },
1083
- /** hanzo.app: describe → build → deploy → live URL. The whole product thesis
1084
- * in five steps; `deploy_succeeded` is the moment a live URL exists. */
1085
- appShip: {
1086
- label: "Describe \u2192 ship",
1087
- products: ["app"],
1088
- join: "person",
1089
- steps: [
1090
- s(events.PAGEVIEW, "Landed"),
1091
- s(events.EVENTS.BUILD_STARTED, "Described an app"),
1092
- s(events.EVENTS.GENERATION_COMPLETED, "Got a working build"),
1093
- s(events.EVENTS.DEPLOY_STARTED, "Hit publish"),
1094
- s(events.EVENTS.DEPLOY_SUCCEEDED, "Live URL")
1095
- ]
1096
- },
1097
- /** hanzo.chat: visit → first message → answer. `generation_completed` is what
1098
- * separates "typed something" from "got value". */
1099
- chatEngage: {
1100
- label: "Chat engagement",
1101
- products: ["chat"],
1102
- join: "person",
1103
- steps: [
1104
- s(events.PAGEVIEW, "Landed"),
1105
- s(events.EVENTS.CHAT_STARTED, "Started a conversation"),
1106
- s(events.EVENTS.CHAT_MESSAGE_SENT, "Sent a message"),
1107
- s(events.EVENTS.GENERATION_COMPLETED, "Got an answer")
1108
- ]
1109
- },
1110
- /** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
1111
- * hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
1112
- * The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
1113
- * which makes the drop-off measurable without any cross-domain identity. */
1114
- siteToChat: {
1115
- label: "Site \u2192 Chat handoff",
1116
- products: ["site", "chat"],
1117
- join: "aggregate",
1118
- steps: [
1119
- s(events.EVENTS.CHAT_STARTED, "Submitted the hanzo.ai composer", {
1120
- property: "source",
1121
- equals: "composer"
1122
- }),
1123
- s(events.EVENTS.CHAT_STARTED, "Landed in hanzo.chat", {
1124
- property: "referrerProduct",
1125
- equals: "site"
1126
- }),
1127
- s(events.EVENTS.GENERATION_COMPLETED, "Got an answer")
1128
- ]
1129
- }
1130
- };
1131
- function eventsOf(id) {
1132
- return FUNNELS[id].steps.map((step) => step.event);
1133
- }
1134
-
1135
1033
  // src/goals.ts
1136
1034
  var GOALS = {
1137
1035
  // Signup: the conversion is signup_completed, along the site signup funnel.
@@ -1139,14 +1037,14 @@ var GOALS = {
1139
1037
  label: "Signup",
1140
1038
  event: events.EVENTS.SIGNUP_COMPLETED,
1141
1039
  funnelId: "signup",
1142
- funnel: eventsOf("signup")
1040
+ funnel: events.eventsOf("signup")
1143
1041
  },
1144
1042
  // Sale: a completed order qualified as a plan purchase (kind=plan).
1145
1043
  sale: {
1146
1044
  label: "Sale",
1147
1045
  event: events.EVENTS.ORDER_COMPLETED,
1148
1046
  funnelId: "upgrade",
1149
- funnel: eventsOf("upgrade"),
1047
+ funnel: events.eventsOf("upgrade"),
1150
1048
  filter: { property: "kind", equals: "plan" }
1151
1049
  },
1152
1050
  // Upgrade intent: a plan click; pricing_viewed is the top of its funnel.
@@ -1154,7 +1052,7 @@ var GOALS = {
1154
1052
  label: "Upgrade Intent",
1155
1053
  event: events.EVENTS.PLAN_CLICKED,
1156
1054
  funnelId: "upgrade",
1157
- funnel: eventsOf("upgrade")
1055
+ funnel: events.eventsOf("upgrade")
1158
1056
  },
1159
1057
  // Activation: the ONE north-star conversion — an account that did the first
1160
1058
  // valuable thing (a successful API call, a live app, a chat answer).
@@ -1162,7 +1060,7 @@ var GOALS = {
1162
1060
  label: "Activation",
1163
1061
  event: events.EVENTS.FIRST_ACTION,
1164
1062
  funnelId: "apiActivation",
1165
- funnel: eventsOf("apiActivation")
1063
+ funnel: events.eventsOf("apiActivation")
1166
1064
  }
1167
1065
  };
1168
1066
  var COHORTS = {
@@ -1200,15 +1098,25 @@ Object.defineProperty(exports, "EXCEPTION", {
1200
1098
  enumerable: true,
1201
1099
  get: function () { return events.EXCEPTION; }
1202
1100
  });
1101
+ Object.defineProperty(exports, "FUNNELS", {
1102
+ enumerable: true,
1103
+ get: function () { return events.FUNNELS; }
1104
+ });
1203
1105
  Object.defineProperty(exports, "PAGEVIEW", {
1204
1106
  enumerable: true,
1205
1107
  get: function () { return events.PAGEVIEW; }
1206
1108
  });
1109
+ Object.defineProperty(exports, "PRODUCTS", {
1110
+ enumerable: true,
1111
+ get: function () { return events.PRODUCTS; }
1112
+ });
1113
+ Object.defineProperty(exports, "eventsOf", {
1114
+ enumerable: true,
1115
+ get: function () { return events.eventsOf; }
1116
+ });
1207
1117
  exports.Analytics = Analytics;
1208
1118
  exports.COHORTS = COHORTS;
1209
- exports.FUNNELS = FUNNELS;
1210
1119
  exports.GOALS = GOALS;
1211
- exports.PRODUCTS = PRODUCTS;
1212
1120
  exports.PRODUCT_PROJECT = PRODUCT_PROJECT;
1213
1121
  exports.VERSION = VERSION;
1214
1122
  exports.buildEnvelope = buildEnvelope;
@@ -1217,7 +1125,6 @@ exports.createAnalytics = createAnalytics;
1217
1125
  exports.deriveChannel = deriveChannel;
1218
1126
  exports.digest = digest;
1219
1127
  exports.dsnForProduct = dsnForProduct;
1220
- exports.eventsOf = eventsOf;
1221
1128
  exports.exceptionEntry = exceptionEntry;
1222
1129
  exports.exceptionProperties = exceptionProperties;
1223
1130
  exports.fingerprint = fingerprint;