@hanzo/event 0.3.22 → 0.3.24

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.d.cts CHANGED
@@ -1,13 +1,14 @@
1
- import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-CHLpJy51.cjs';
2
- export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-CHLpJy51.cjs';
3
- export { EVENTS, EXCEPTION, EventName, PAGEVIEW } from '@hanzo/events';
1
+ import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-DO8tBUy7.cjs';
2
+ export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-DO8tBUy7.cjs';
3
+ import { FunnelId } from '@hanzo/events';
4
+ export { EVENTS, EXCEPTION, EventName, FUNNELS, FunnelDef, FunnelId, FunnelStep, PAGEVIEW, PRODUCTS, ProductId, eventsOf } from '@hanzo/events';
4
5
 
5
6
  /** Read the persisted first-touch attribution. */
6
7
  declare function getFirstTouch(): Attribution | undefined;
7
8
  /** Read persisted cohort dimensions. */
8
9
  declare function getCohort(): Cohort | undefined;
9
10
 
10
- declare const VERSION = "0.3.21";
11
+ declare const VERSION = "0.3.24";
11
12
 
12
13
  /**
13
14
  * parseDsn parses "https://<version>:<hmac>@<host>/v1/sentry/<projectId>" into its
@@ -178,96 +179,6 @@ declare function exceptionProperties(err: unknown, opts: {
178
179
  level?: SentryLevel;
179
180
  }): ExceptionProperties;
180
181
 
181
- /** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
182
- * `product` is on every event, so a funnel scopes by product instead of every
183
- * surface prefixing its event names. */
184
- declare const PRODUCTS: readonly ["site", "app", "chat", "console", "admin", "cloud"];
185
- type ProductId = (typeof PRODUCTS)[number];
186
- interface FunnelStep {
187
- /** An EVENTS value (or PAGEVIEW). */
188
- event: string;
189
- /** Human label for the Insights step. */
190
- label: string;
191
- /** Property equality that qualifies the step, e.g. first_action{action:'api_call'}. */
192
- where?: {
193
- property: string;
194
- equals: string;
195
- };
196
- }
197
- interface FunnelDef {
198
- label: string;
199
- /** Surface(s) the steps are emitted from — matched against the `product` field. */
200
- products: ProductId[];
201
- /**
202
- * How steps are joined:
203
- * • 'person' — steps join on distinctId (one browser, or one logged-in
204
- * person across surfaces). The normal case.
205
- * • 'aggregate' — steps are emitted on DIFFERENT origins by a LOGGED-OUT
206
- * visitor, so there is no shared id: hanzo.ai, hanzo.app and
207
- * hanzo.chat each mint their own anonymousId in their own
208
- * storage. Read these as step-over-step COUNTS, never as a
209
- * per-person conversion. Honest by construction.
210
- */
211
- join: 'person' | 'aggregate';
212
- steps: FunnelStep[];
213
- }
214
- declare const FUNNELS: {
215
- /** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
216
- * redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
217
- readonly signup: {
218
- readonly label: "Signup";
219
- readonly products: ["site"];
220
- readonly join: "person";
221
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
222
- };
223
- /** The developer activation path: an account is worth nothing until a key has
224
- * made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
225
- * Cloud on an org's first successful /v1 request — a browser cannot see it. */
226
- readonly apiActivation: {
227
- readonly label: "API activation";
228
- readonly products: ["site", "cloud"];
229
- readonly join: "person";
230
- readonly steps: [FunnelStep, FunnelStep, FunnelStep];
231
- };
232
- /** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
233
- readonly upgrade: {
234
- readonly label: "Upgrade";
235
- readonly products: ["site", "app", "console"];
236
- readonly join: "person";
237
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
238
- };
239
- /** hanzo.app: describe → build → deploy → live URL. The whole product thesis
240
- * in five steps; `deploy_succeeded` is the moment a live URL exists. */
241
- readonly appShip: {
242
- readonly label: "Describe → ship";
243
- readonly products: ["app"];
244
- readonly join: "person";
245
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
246
- };
247
- /** hanzo.chat: visit → first message → answer. `generation_completed` is what
248
- * separates "typed something" from "got value". */
249
- readonly chatEngage: {
250
- readonly label: "Chat engagement";
251
- readonly products: ["chat"];
252
- readonly join: "person";
253
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
254
- };
255
- /** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
256
- * hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
257
- * The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
258
- * which makes the drop-off measurable without any cross-domain identity. */
259
- readonly siteToChat: {
260
- readonly label: "Site → Chat handoff";
261
- readonly products: ["site", "chat"];
262
- readonly join: "aggregate";
263
- readonly steps: [FunnelStep, FunnelStep, FunnelStep];
264
- };
265
- };
266
- type FunnelId = keyof typeof FUNNELS;
267
- /** eventsOf flattens a funnel to its ordered event names — what a goal's `funnel`
268
- * field carries, so the steps are defined exactly once (here). */
269
- declare function eventsOf(id: FunnelId): string[];
270
-
271
182
  interface GoalDef {
272
183
  /** Human label shown in Insights. */
273
184
  label: string;
@@ -304,4 +215,4 @@ declare function hasAttribution(a: Attribution): boolean;
304
215
  /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
305
216
  declare function isoWeek(d: Date): string;
306
217
 
307
- export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, FUNNELS, type FunnelDef, type FunnelId, type FunnelStep, GOALS, type GoalDef, PRODUCTS, PRODUCT_PROJECT, type ProductId, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, eventsOf, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
218
+ export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, GOALS, type GoalDef, PRODUCT_PROJECT, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,14 @@
1
- import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-CHLpJy51.js';
2
- export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-CHLpJy51.js';
3
- export { EVENTS, EXCEPTION, EventName, PAGEVIEW } from '@hanzo/events';
1
+ import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-DO8tBUy7.js';
2
+ export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-DO8tBUy7.js';
3
+ import { FunnelId } from '@hanzo/events';
4
+ export { EVENTS, EXCEPTION, EventName, FUNNELS, FunnelDef, FunnelId, FunnelStep, PAGEVIEW, PRODUCTS, ProductId, eventsOf } from '@hanzo/events';
4
5
 
5
6
  /** Read the persisted first-touch attribution. */
6
7
  declare function getFirstTouch(): Attribution | undefined;
7
8
  /** Read persisted cohort dimensions. */
8
9
  declare function getCohort(): Cohort | undefined;
9
10
 
10
- declare const VERSION = "0.3.21";
11
+ declare const VERSION = "0.3.24";
11
12
 
12
13
  /**
13
14
  * parseDsn parses "https://<version>:<hmac>@<host>/v1/sentry/<projectId>" into its
@@ -178,96 +179,6 @@ declare function exceptionProperties(err: unknown, opts: {
178
179
  level?: SentryLevel;
179
180
  }): ExceptionProperties;
180
181
 
181
- /** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
182
- * `product` is on every event, so a funnel scopes by product instead of every
183
- * surface prefixing its event names. */
184
- declare const PRODUCTS: readonly ["site", "app", "chat", "console", "admin", "cloud"];
185
- type ProductId = (typeof PRODUCTS)[number];
186
- interface FunnelStep {
187
- /** An EVENTS value (or PAGEVIEW). */
188
- event: string;
189
- /** Human label for the Insights step. */
190
- label: string;
191
- /** Property equality that qualifies the step, e.g. first_action{action:'api_call'}. */
192
- where?: {
193
- property: string;
194
- equals: string;
195
- };
196
- }
197
- interface FunnelDef {
198
- label: string;
199
- /** Surface(s) the steps are emitted from — matched against the `product` field. */
200
- products: ProductId[];
201
- /**
202
- * How steps are joined:
203
- * • 'person' — steps join on distinctId (one browser, or one logged-in
204
- * person across surfaces). The normal case.
205
- * • 'aggregate' — steps are emitted on DIFFERENT origins by a LOGGED-OUT
206
- * visitor, so there is no shared id: hanzo.ai, hanzo.app and
207
- * hanzo.chat each mint their own anonymousId in their own
208
- * storage. Read these as step-over-step COUNTS, never as a
209
- * per-person conversion. Honest by construction.
210
- */
211
- join: 'person' | 'aggregate';
212
- steps: FunnelStep[];
213
- }
214
- declare const FUNNELS: {
215
- /** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
216
- * redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
217
- readonly signup: {
218
- readonly label: "Signup";
219
- readonly products: ["site"];
220
- readonly join: "person";
221
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
222
- };
223
- /** The developer activation path: an account is worth nothing until a key has
224
- * made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
225
- * Cloud on an org's first successful /v1 request — a browser cannot see it. */
226
- readonly apiActivation: {
227
- readonly label: "API activation";
228
- readonly products: ["site", "cloud"];
229
- readonly join: "person";
230
- readonly steps: [FunnelStep, FunnelStep, FunnelStep];
231
- };
232
- /** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
233
- readonly upgrade: {
234
- readonly label: "Upgrade";
235
- readonly products: ["site", "app", "console"];
236
- readonly join: "person";
237
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
238
- };
239
- /** hanzo.app: describe → build → deploy → live URL. The whole product thesis
240
- * in five steps; `deploy_succeeded` is the moment a live URL exists. */
241
- readonly appShip: {
242
- readonly label: "Describe → ship";
243
- readonly products: ["app"];
244
- readonly join: "person";
245
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
246
- };
247
- /** hanzo.chat: visit → first message → answer. `generation_completed` is what
248
- * separates "typed something" from "got value". */
249
- readonly chatEngage: {
250
- readonly label: "Chat engagement";
251
- readonly products: ["chat"];
252
- readonly join: "person";
253
- readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
254
- };
255
- /** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
256
- * hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
257
- * The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
258
- * which makes the drop-off measurable without any cross-domain identity. */
259
- readonly siteToChat: {
260
- readonly label: "Site → Chat handoff";
261
- readonly products: ["site", "chat"];
262
- readonly join: "aggregate";
263
- readonly steps: [FunnelStep, FunnelStep, FunnelStep];
264
- };
265
- };
266
- type FunnelId = keyof typeof FUNNELS;
267
- /** eventsOf flattens a funnel to its ordered event names — what a goal's `funnel`
268
- * field carries, so the steps are defined exactly once (here). */
269
- declare function eventsOf(id: FunnelId): string[];
270
-
271
182
  interface GoalDef {
272
183
  /** Human label shown in Insights. */
273
184
  label: string;
@@ -304,4 +215,4 @@ declare function hasAttribution(a: Attribution): boolean;
304
215
  /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
305
216
  declare function isoWeek(d: Date): string;
306
217
 
307
- export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, FUNNELS, type FunnelDef, type FunnelId, type FunnelStep, GOALS, type GoalDef, PRODUCTS, PRODUCT_PROJECT, type ProductId, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, eventsOf, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
218
+ export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, GOALS, type GoalDef, PRODUCT_PROJECT, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { EVENTS, PAGEVIEW, EXCEPTION } from '@hanzo/events';
2
- export { EVENTS, EXCEPTION, PAGEVIEW } from '@hanzo/events';
1
+ import { eventsOf, EVENTS, PAGEVIEW, EXCEPTION } from '@hanzo/events';
2
+ export { EVENTS, EXCEPTION, FUNNELS, PAGEVIEW, PRODUCTS, eventsOf } from '@hanzo/events';
3
3
 
4
4
  // src/attribution.ts
5
5
  var SOCIAL_HOSTS = [
@@ -49,16 +49,16 @@ function deriveChannel(a) {
49
49
  }
50
50
  function hostOf(raw) {
51
51
  if (!raw) return "";
52
- let s2 = raw.trim();
53
- const scheme = s2.indexOf("://");
54
- if (scheme >= 0) s2 = s2.slice(scheme + 3);
55
- const cut = s2.search(/[/?#]/);
56
- if (cut >= 0) s2 = s2.slice(0, cut);
57
- const at = s2.indexOf("@");
58
- if (at >= 0) s2 = s2.slice(at + 1);
59
- const colon = s2.indexOf(":");
60
- if (colon >= 0) s2 = s2.slice(0, colon);
61
- 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();
62
62
  }
63
63
  function hasAttribution(a) {
64
64
  return Boolean(
@@ -138,8 +138,8 @@ function luhn(digits) {
138
138
  }
139
139
  return sum % 10 === 0;
140
140
  }
141
- function redactPAN(s2) {
142
- return s2.replace(RE_PAN, (m) => {
141
+ function redactPAN(s) {
142
+ return s.replace(RE_PAN, (m) => {
143
143
  const digits = m.replace(/[ -]/g, "");
144
144
  if (digits.length < 13 || digits.length > 19) return m;
145
145
  return luhn(digits) ? REDACTED : m;
@@ -184,30 +184,30 @@ var RE_CREDENTIAL_PARAM = new RegExp(
184
184
  "([?&#;](?:" + CREDENTIAL_PARAMS.join("|") + ")=)[^&#;\\s]{1,4096}",
185
185
  "gi"
186
186
  );
187
- function redactCredentialParams(s2) {
188
- 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);
189
189
  }
190
- function redactSecrets(s2) {
191
- s2 = redactCredentialParams(s2);
192
- for (const re of SECRET_PATTERNS) s2 = s2.replace(re, REDACTED);
193
- 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);
194
194
  }
195
- function scrubPII(s2) {
196
- s2 = s2.replace(RE_EMAIL, EMAIL_MARK);
197
- s2 = s2.replace(RE_IPV6, IP_MARK);
198
- s2 = s2.replace(RE_IPV4, IP_MARK);
199
- 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;
200
200
  }
201
201
  var MAX_SCRUB_LEN = 8192;
202
- function truncate(s2, max = MAX_SCRUB_LEN) {
203
- 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;
204
204
  }
205
- function scrubText(s2, capturePII = false) {
206
- if (!s2) return s2 ?? "";
207
- s2 = truncate(s2);
208
- s2 = redactSecrets(s2);
209
- if (!capturePII) s2 = scrubPII(s2);
210
- 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;
211
211
  }
212
212
 
213
213
  // src/anon.js
@@ -301,12 +301,12 @@ function hzAnonWrite(name, value) {
301
301
  }
302
302
  function hzAnonId() {
303
303
  if (typeof window === "undefined") return "";
304
- var s2 = hzAnonStore();
305
- 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();
306
306
  hzAnonMemo = id;
307
307
  hzAnonWrite(HZ_ANON_KEY, id);
308
308
  try {
309
- 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);
310
310
  } catch (e) {
311
311
  }
312
312
  return id;
@@ -318,7 +318,7 @@ function uuidv7Time(id) {
318
318
  }
319
319
 
320
320
  // src/version.ts
321
- var VERSION = "0.3.21";
321
+ var VERSION = "0.3.24";
322
322
 
323
323
  // src/sentry.ts
324
324
  var MAX_FRAMES = 50;
@@ -329,10 +329,10 @@ var MAX_TAGS = 50;
329
329
  function eventId() {
330
330
  return hzUuidv7().replace(/-/g, "");
331
331
  }
332
- function byteLen(s2) {
333
- if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s2).length;
334
- if (typeof Buffer !== "undefined") return Buffer.byteLength(s2, "utf8");
335
- 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;
336
336
  }
337
337
  function parseDsn(dsn) {
338
338
  if (!dsn) return null;
@@ -433,7 +433,7 @@ function str(v) {
433
433
  }
434
434
  }
435
435
  function coerceTag(v) {
436
- const s2 = typeof v === "string" ? v : (() => {
436
+ const s = typeof v === "string" ? v : (() => {
437
437
  try {
438
438
  return JSON.stringify(v) ?? String(v);
439
439
  } catch {
@@ -444,7 +444,7 @@ function coerceTag(v) {
444
444
  }
445
445
  }
446
446
  })();
447
- 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;
448
448
  }
449
449
  function buildSentryEvent(input) {
450
450
  const { error, options = {}, identity, capturePII = false } = input;
@@ -511,12 +511,12 @@ ${payload}
511
511
  `;
512
512
  }
513
513
  var MAX_VALUE = 4096;
514
- function digest(s2) {
514
+ function digest(s) {
515
515
  let out = "";
516
516
  for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
517
517
  let h = seed >>> 0;
518
- for (let i = 0; i < s2.length; i++) {
519
- h ^= s2.charCodeAt(i);
518
+ for (let i = 0; i < s.length; i++) {
519
+ h ^= s.charCodeAt(i);
520
520
  h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
521
521
  }
522
522
  out += h.toString(16).padStart(8, "0");
@@ -545,12 +545,12 @@ function frameOf(f) {
545
545
  module: null
546
546
  };
547
547
  }
548
- function truncate2(s2, max) {
549
- if (s2.length <= max) return s2;
548
+ function truncate2(s, max) {
549
+ if (s.length <= max) return s;
550
550
  let end = max;
551
- const c = s2.charCodeAt(end - 1);
551
+ const c = s.charCodeAt(end - 1);
552
552
  if (c >= 55296 && c <= 56319) end--;
553
- return s2.slice(0, end);
553
+ return s.slice(0, end);
554
554
  }
555
555
  function exceptionEntry(err, opts) {
556
556
  const n = normalizeError(err);
@@ -594,8 +594,8 @@ function exceptionProperties(err, opts) {
594
594
  $exception_synthetic: entry.mechanism?.synthetic ?? false,
595
595
  $exception_types: [entry.type],
596
596
  $exception_values: [entry.value],
597
- $exception_sources: frames.map((f) => f.source).filter((s2) => !!s2),
598
- $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)
599
599
  };
600
600
  }
601
601
 
@@ -618,11 +618,11 @@ function anonId() {
618
618
  return hzAnonId() || void 0;
619
619
  }
620
620
  function sessionId(now = Date.now()) {
621
- const s2 = ls();
622
- if (!s2) return void 0;
621
+ const s = ls();
622
+ if (!s) return void 0;
623
623
  let state = null;
624
624
  try {
625
- state = JSON.parse(s2.getItem(KEY.session) || "null");
625
+ state = JSON.parse(s.getItem(KEY.session) || "null");
626
626
  } catch {
627
627
  state = null;
628
628
  }
@@ -631,45 +631,45 @@ function sessionId(now = Date.now()) {
631
631
  } else {
632
632
  state.last = now;
633
633
  }
634
- s2.setItem(KEY.session, JSON.stringify(state));
634
+ s.setItem(KEY.session, JSON.stringify(state));
635
635
  return state.id;
636
636
  }
637
637
  function getFirstTouch() {
638
- const s2 = ls();
639
- if (!s2) return void 0;
638
+ const s = ls();
639
+ if (!s) return void 0;
640
640
  try {
641
- const v = s2.getItem(KEY.firstTouch);
641
+ const v = s.getItem(KEY.firstTouch);
642
642
  return v ? JSON.parse(v) : void 0;
643
643
  } catch {
644
644
  return void 0;
645
645
  }
646
646
  }
647
647
  function setFirstTouchOnce(a) {
648
- const s2 = ls();
648
+ const s = ls();
649
649
  const existing = getFirstTouch();
650
650
  if (existing) return existing;
651
- if (s2) s2.setItem(KEY.firstTouch, JSON.stringify(a));
651
+ if (s) s.setItem(KEY.firstTouch, JSON.stringify(a));
652
652
  return a;
653
653
  }
654
654
  function getCohort() {
655
- const s2 = ls();
656
- if (!s2) return void 0;
655
+ const s = ls();
656
+ if (!s) return void 0;
657
657
  try {
658
- const v = s2.getItem(KEY.cohort);
658
+ const v = s.getItem(KEY.cohort);
659
659
  return v ? JSON.parse(v) : void 0;
660
660
  } catch {
661
661
  return void 0;
662
662
  }
663
663
  }
664
664
  function mergeCohort(patch) {
665
- const s2 = ls();
665
+ const s = ls();
666
666
  const cur = getCohort() || {};
667
667
  const next = {
668
668
  signupWeek: cur.signupWeek || patch.signupWeek,
669
669
  channel: patch.channel || cur.channel,
670
670
  refCode: cur.refCode || patch.refCode
671
671
  };
672
- if (s2) s2.setItem(KEY.cohort, JSON.stringify(next));
672
+ if (s) s.setItem(KEY.cohort, JSON.stringify(next));
673
673
  return next;
674
674
  }
675
675
 
@@ -677,6 +677,7 @@ function mergeCohort(patch) {
677
677
  var EVENT_PATH = "/v1/event";
678
678
  var DEFAULT_HOST = "https://api.hanzo.ai";
679
679
  var ENVELOPE_CONTENT_TYPE = "application/x-sentry-envelope";
680
+ var BEACON_CONTENT_TYPE = "text/plain";
680
681
  function readEnvDsn() {
681
682
  try {
682
683
  if (typeof process !== "undefined" && process.env) {
@@ -721,17 +722,22 @@ function serializeBatch(batch) {
721
722
  }
722
723
  var DefaultTransport = class {
723
724
  send(url, body, opts) {
724
- const contentType = opts.contentType ?? "application/json";
725
725
  if (opts.beacon && isBrowser() && typeof navigator.sendBeacon === "function") {
726
726
  const beaconUrl = opts.ingestKey ? appendQuery(url, "ingest_key", opts.ingestKey) : url;
727
727
  try {
728
- navigator.sendBeacon(beaconUrl, new Blob([body], { type: contentType }));
729
- return;
728
+ const queued = navigator.sendBeacon(
729
+ beaconUrl,
730
+ new Blob([body], { type: BEACON_CONTENT_TYPE })
731
+ );
732
+ if (queued) return;
733
+ if (opts.debug) console.warn("[event] beacon refused, falling back to fetch");
730
734
  } catch {
731
735
  }
732
736
  }
733
737
  if (typeof fetch !== "function") return;
734
- const headers = { "Content-Type": contentType };
738
+ const headers = {
739
+ "Content-Type": opts.contentType ?? "application/json"
740
+ };
735
741
  const bearer = opts.ingestKey ?? opts.token;
736
742
  if (bearer) headers.Authorization = `Bearer ${bearer}`;
737
743
  void fetch(url, {
@@ -1029,108 +1035,6 @@ function createAnalytics(config) {
1029
1035
  return new Analytics(config);
1030
1036
  }
1031
1037
 
1032
- // src/funnels.ts
1033
- var PRODUCTS = ["site", "app", "chat", "console", "admin", "cloud"];
1034
- var s = (event, label, where) => ({
1035
- event,
1036
- label,
1037
- ...where ? { where } : {}
1038
- });
1039
- var FUNNELS = {
1040
- /** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
1041
- * redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
1042
- signup: {
1043
- label: "Signup",
1044
- products: ["site"],
1045
- join: "person",
1046
- steps: [
1047
- s(PAGEVIEW, "Landed"),
1048
- s(EVENTS.SIGNUP_VIEWED, "Opened signup"),
1049
- s(EVENTS.SIGNUP_SUBMITTED, "Redirected to Hanzo ID"),
1050
- s(EVENTS.SIGNUP_COMPLETED, "Account created"),
1051
- s(EVENTS.FIRST_ACTION, "First action")
1052
- ]
1053
- },
1054
- /** The developer activation path: an account is worth nothing until a key has
1055
- * made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
1056
- * Cloud on an org's first successful /v1 request — a browser cannot see it. */
1057
- apiActivation: {
1058
- label: "API activation",
1059
- products: ["site", "cloud"],
1060
- join: "person",
1061
- steps: [
1062
- s(EVENTS.SIGNUP_COMPLETED, "Account created"),
1063
- s(EVENTS.API_KEY_CREATED, "Key minted"),
1064
- s(EVENTS.FIRST_ACTION, "First successful API call", {
1065
- property: "action",
1066
- equals: "api_call"
1067
- })
1068
- ]
1069
- },
1070
- /** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
1071
- upgrade: {
1072
- label: "Upgrade",
1073
- products: ["site", "app", "console"],
1074
- join: "person",
1075
- steps: [
1076
- s(EVENTS.PRICING_VIEWED, "Viewed pricing"),
1077
- s(EVENTS.PLAN_CLICKED, "Chose a plan"),
1078
- s(EVENTS.CHECKOUT_STARTED, "Started checkout"),
1079
- s(EVENTS.ORDER_COMPLETED, "Paid")
1080
- ]
1081
- },
1082
- /** hanzo.app: describe → build → deploy → live URL. The whole product thesis
1083
- * in five steps; `deploy_succeeded` is the moment a live URL exists. */
1084
- appShip: {
1085
- label: "Describe \u2192 ship",
1086
- products: ["app"],
1087
- join: "person",
1088
- steps: [
1089
- s(PAGEVIEW, "Landed"),
1090
- s(EVENTS.BUILD_STARTED, "Described an app"),
1091
- s(EVENTS.GENERATION_COMPLETED, "Got a working build"),
1092
- s(EVENTS.DEPLOY_STARTED, "Hit publish"),
1093
- s(EVENTS.DEPLOY_SUCCEEDED, "Live URL")
1094
- ]
1095
- },
1096
- /** hanzo.chat: visit → first message → answer. `generation_completed` is what
1097
- * separates "typed something" from "got value". */
1098
- chatEngage: {
1099
- label: "Chat engagement",
1100
- products: ["chat"],
1101
- join: "person",
1102
- steps: [
1103
- s(PAGEVIEW, "Landed"),
1104
- s(EVENTS.CHAT_STARTED, "Started a conversation"),
1105
- s(EVENTS.CHAT_MESSAGE_SENT, "Sent a message"),
1106
- s(EVENTS.GENERATION_COMPLETED, "Got an answer")
1107
- ]
1108
- },
1109
- /** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
1110
- * hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
1111
- * The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
1112
- * which makes the drop-off measurable without any cross-domain identity. */
1113
- siteToChat: {
1114
- label: "Site \u2192 Chat handoff",
1115
- products: ["site", "chat"],
1116
- join: "aggregate",
1117
- steps: [
1118
- s(EVENTS.CHAT_STARTED, "Submitted the hanzo.ai composer", {
1119
- property: "source",
1120
- equals: "composer"
1121
- }),
1122
- s(EVENTS.CHAT_STARTED, "Landed in hanzo.chat", {
1123
- property: "referrerProduct",
1124
- equals: "site"
1125
- }),
1126
- s(EVENTS.GENERATION_COMPLETED, "Got an answer")
1127
- ]
1128
- }
1129
- };
1130
- function eventsOf(id) {
1131
- return FUNNELS[id].steps.map((step) => step.event);
1132
- }
1133
-
1134
1038
  // src/goals.ts
1135
1039
  var GOALS = {
1136
1040
  // Signup: the conversion is signup_completed, along the site signup funnel.
@@ -1191,6 +1095,6 @@ var COHORTS = {
1191
1095
  * reformat of one copy is a diff against the other.
1192
1096
  */
1193
1097
 
1194
- export { Analytics, COHORTS, FUNNELS, GOALS, 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 };
1098
+ 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 };
1195
1099
  //# sourceMappingURL=index.mjs.map
1196
1100
  //# sourceMappingURL=index.mjs.map