@hanzo/event 0.2.0

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.js ADDED
@@ -0,0 +1,433 @@
1
+ 'use strict';
2
+
3
+ // src/attribution.ts
4
+ var SOCIAL_HOSTS = [
5
+ "facebook.",
6
+ "instagram.",
7
+ "twitter.",
8
+ "x.com",
9
+ "t.co",
10
+ "linkedin.",
11
+ "reddit.",
12
+ "youtube.",
13
+ "tiktok.",
14
+ "pinterest.",
15
+ "news.ycombinator.com"
16
+ ];
17
+ var SEARCH_HOSTS = ["google.", "bing.", "duckduckgo.", "yahoo.", "baidu.", "ecosia."];
18
+ function parseAttribution(search, referrer) {
19
+ const q = new URLSearchParams(search || "");
20
+ const get = (k) => {
21
+ const v = q.get(k);
22
+ return v ? v.trim() : void 0;
23
+ };
24
+ const a = {
25
+ utm: {
26
+ source: get("utm_source"),
27
+ medium: get("utm_medium"),
28
+ campaign: get("utm_campaign"),
29
+ term: get("utm_term"),
30
+ content: get("utm_content")
31
+ },
32
+ referrer: referrer ? referrer.trim() : void 0,
33
+ refCode: get("ref") || get("refCode") || get("ref_code") || void 0
34
+ };
35
+ a.channel = deriveChannel(a);
36
+ return a;
37
+ }
38
+ function deriveChannel(a) {
39
+ const medium = (a.utm.medium || "").toLowerCase();
40
+ if (/(cpc|ppc|paid|paidsearch|display|cpm)/.test(medium)) return "paid";
41
+ if (a.utm.source || a.utm.campaign) return "campaign";
42
+ if (a.refCode) return "referral";
43
+ const host = hostOf(a.referrer);
44
+ if (!host) return "direct";
45
+ if (SOCIAL_HOSTS.some((h) => host.includes(h))) return "social";
46
+ if (SEARCH_HOSTS.some((h) => host.includes(h))) return "organic";
47
+ return "referral";
48
+ }
49
+ function hostOf(raw) {
50
+ if (!raw) return "";
51
+ let s = raw.trim();
52
+ const scheme = s.indexOf("://");
53
+ if (scheme >= 0) s = s.slice(scheme + 3);
54
+ const cut = s.search(/[/?#]/);
55
+ if (cut >= 0) s = s.slice(0, cut);
56
+ const at = s.indexOf("@");
57
+ if (at >= 0) s = s.slice(at + 1);
58
+ const colon = s.indexOf(":");
59
+ if (colon >= 0) s = s.slice(0, colon);
60
+ return s.toLowerCase().trim();
61
+ }
62
+ function hasAttribution(a) {
63
+ return Boolean(
64
+ a.utm.source || a.utm.medium || a.utm.campaign || a.utm.term || a.utm.content || a.refCode || a.referrer && hostOf(a.referrer)
65
+ );
66
+ }
67
+ function isoWeek(d) {
68
+ const date = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
69
+ const day = date.getUTCDay() || 7;
70
+ date.setUTCDate(date.getUTCDate() + 4 - day);
71
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
72
+ const week = Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
73
+ return `${date.getUTCFullYear()}-W${String(week).padStart(2, "0")}`;
74
+ }
75
+
76
+ // src/events.ts
77
+ var EVENTS = {
78
+ // Signup funnel: view -> submit -> verify -> completed -> first action.
79
+ SIGNUP_VIEWED: "signup_viewed",
80
+ SIGNUP_SUBMITTED: "signup_submitted",
81
+ SIGNUP_VERIFIED: "signup_verified",
82
+ SIGNUP_COMPLETED: "signup_completed",
83
+ FIRST_ACTION: "first_action",
84
+ // Waitlist + referral.
85
+ WAITLIST_JOINED: "waitlist_joined",
86
+ WAITLIST_SHARED: "waitlist_shared",
87
+ REFERRAL_USED: "referral_used",
88
+ REFERRAL_CLAIMED: "referral_claimed",
89
+ // Upgrade-intent + purchase.
90
+ PRICING_VIEWED: "pricing_viewed",
91
+ PLAN_CLICKED: "plan_clicked",
92
+ CHECKOUT_STARTED: "checkout_started",
93
+ ORDER_COMPLETED: "order_completed",
94
+ // Feature usage — generic + the common key surfaces across products.
95
+ FEATURE_USED: "feature_used",
96
+ API_KEY_CREATED: "api_key_created",
97
+ APP_CREATED: "app_created",
98
+ DEPLOY_STARTED: "deploy_started",
99
+ PROJECT_CREATED: "project_created",
100
+ AGENT_CREATED: "agent_created",
101
+ CHAT_STARTED: "chat_started",
102
+ CHAT_MESSAGE_SENT: "chat_message_sent",
103
+ TASK_STARTED: "task_started",
104
+ TASK_COMPLETED: "task_completed"
105
+ };
106
+ var PAGEVIEW = "$pageview";
107
+
108
+ // src/storage.ts
109
+ var KEY = {
110
+ anon: "hz_anon_id",
111
+ session: "hz_session",
112
+ firstTouch: "hz_first_touch",
113
+ cohort: "hz_cohort"
114
+ };
115
+ var SESSION_TTL_MS = 30 * 60 * 1e3;
116
+ function ls() {
117
+ try {
118
+ if (typeof window === "undefined" || !window.localStorage) return void 0;
119
+ return window.localStorage;
120
+ } catch {
121
+ return void 0;
122
+ }
123
+ }
124
+ function uid() {
125
+ const c = typeof crypto !== "undefined" ? crypto : void 0;
126
+ if (c && "randomUUID" in c) return c.randomUUID();
127
+ return "a-" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
128
+ }
129
+ function anonId() {
130
+ const s = ls();
131
+ if (!s) return void 0;
132
+ let v = s.getItem(KEY.anon);
133
+ if (!v) {
134
+ v = uid();
135
+ s.setItem(KEY.anon, v);
136
+ }
137
+ return v;
138
+ }
139
+ function sessionId(now = Date.now()) {
140
+ const s = ls();
141
+ if (!s) return void 0;
142
+ let state = null;
143
+ try {
144
+ state = JSON.parse(s.getItem(KEY.session) || "null");
145
+ } catch {
146
+ state = null;
147
+ }
148
+ if (!state || now - state.last > SESSION_TTL_MS) {
149
+ state = { id: uid(), last: now };
150
+ } else {
151
+ state.last = now;
152
+ }
153
+ s.setItem(KEY.session, JSON.stringify(state));
154
+ return state.id;
155
+ }
156
+ function getFirstTouch() {
157
+ const s = ls();
158
+ if (!s) return void 0;
159
+ try {
160
+ const v = s.getItem(KEY.firstTouch);
161
+ return v ? JSON.parse(v) : void 0;
162
+ } catch {
163
+ return void 0;
164
+ }
165
+ }
166
+ function setFirstTouchOnce(a) {
167
+ const s = ls();
168
+ const existing = getFirstTouch();
169
+ if (existing) return existing;
170
+ if (s) s.setItem(KEY.firstTouch, JSON.stringify(a));
171
+ return a;
172
+ }
173
+ function getCohort() {
174
+ const s = ls();
175
+ if (!s) return void 0;
176
+ try {
177
+ const v = s.getItem(KEY.cohort);
178
+ return v ? JSON.parse(v) : void 0;
179
+ } catch {
180
+ return void 0;
181
+ }
182
+ }
183
+ function mergeCohort(patch) {
184
+ const s = ls();
185
+ const cur = getCohort() || {};
186
+ const next = {
187
+ signupWeek: cur.signupWeek || patch.signupWeek,
188
+ channel: patch.channel || cur.channel,
189
+ refCode: cur.refCode || patch.refCode
190
+ };
191
+ if (s) s.setItem(KEY.cohort, JSON.stringify(next));
192
+ return next;
193
+ }
194
+
195
+ // src/core.ts
196
+ var VERSION = "0.2.0";
197
+ var ANALYTICS_PATH = "/v1/analytics";
198
+ var TRACKER_PATH = "/v1/tracker";
199
+ function uid2() {
200
+ const c = typeof crypto !== "undefined" ? crypto : void 0;
201
+ if (c && "randomUUID" in c) return c.randomUUID();
202
+ return "m-" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
203
+ }
204
+ function normalizeError(err) {
205
+ if (err instanceof Error) {
206
+ return { type: err.name, message: err.message, stack: err.stack };
207
+ }
208
+ if (typeof err === "string") return { message: err };
209
+ try {
210
+ return { message: JSON.stringify(err) };
211
+ } catch {
212
+ return { message: String(err) };
213
+ }
214
+ }
215
+ var isBrowser = () => typeof window !== "undefined";
216
+ var DefaultTransport = class {
217
+ send(url, body, opts) {
218
+ if (opts.beacon && isBrowser() && typeof navigator.sendBeacon === "function") {
219
+ try {
220
+ navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
221
+ return;
222
+ } catch {
223
+ }
224
+ }
225
+ if (typeof fetch !== "function") return;
226
+ const headers = { "Content-Type": "application/json" };
227
+ if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
228
+ void fetch(url, {
229
+ method: "POST",
230
+ headers,
231
+ body,
232
+ keepalive: true,
233
+ credentials: "include"
234
+ }).catch(() => {
235
+ });
236
+ }
237
+ };
238
+ var Analytics = class {
239
+ constructor(config) {
240
+ this.queue = [];
241
+ this.timer = null;
242
+ this.attribution = { utm: {} };
243
+ this.cohort = {};
244
+ this.started = false;
245
+ /** track is an alias of capture (Segment familiarity). */
246
+ this.track = this.capture.bind(this);
247
+ /** captureException — @sentry-familiar alias of captureError. */
248
+ this.captureException = this.captureError.bind(this);
249
+ this.cfg = {
250
+ host: "",
251
+ batchSize: 20,
252
+ flushIntervalMs: 5e3,
253
+ enabled: true,
254
+ captureErrors: true,
255
+ ...config
256
+ };
257
+ this.transport = config.transport ?? new DefaultTransport();
258
+ }
259
+ /** init is idempotent and browser-only for its side effects: capture first-touch
260
+ * attribution, hydrate cohort, and register the unload flush. Safe to call from
261
+ * a React effect on every render. */
262
+ init() {
263
+ if (this.started || !this.cfg.enabled) return;
264
+ this.started = true;
265
+ if (!isBrowser()) return;
266
+ const parsed = parseAttribution(window.location.search, document.referrer);
267
+ this.attribution = hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed;
268
+ this.cohort = mergeCohort({
269
+ channel: this.attribution.channel ?? deriveChannel(this.attribution),
270
+ refCode: this.attribution.refCode
271
+ });
272
+ const flushHidden = () => {
273
+ if (document.visibilityState === "hidden") this.flush(true);
274
+ };
275
+ window.addEventListener("visibilitychange", flushHidden);
276
+ window.addEventListener("pagehide", () => this.flush(true));
277
+ if (this.cfg.captureErrors) {
278
+ window.addEventListener("error", (e) => {
279
+ this.captureError(e.error ?? e.message, { handled: false });
280
+ });
281
+ window.addEventListener("unhandledrejection", (e) => {
282
+ this.captureError(e.reason, { handled: false });
283
+ });
284
+ }
285
+ }
286
+ /** identify binds the current visitor to a stable person id (post-login). */
287
+ identify(personId, traits) {
288
+ this.personId = personId;
289
+ this.enqueue("identify", void 0, { properties: traits });
290
+ }
291
+ /** group associates the visitor with an org/team (analytics grouping, not the
292
+ * server tenant — the server still derives tenant from the session). */
293
+ group(groupId, traits) {
294
+ this.enqueue("group", void 0, { groupId, properties: traits });
295
+ }
296
+ /** pageview records a $pageview for the current (or given) location. */
297
+ pageview(path, properties) {
298
+ const url = isBrowser() ? window.location.href : void 0;
299
+ const p = path ?? (isBrowser() ? window.location.pathname : void 0);
300
+ this.enqueue("pageview", PAGEVIEW, { url, path: p, properties });
301
+ }
302
+ /** capture records a named product event with optional properties. Commerce
303
+ * fields (productId/quantity/revenue/currency) may be passed for order events. */
304
+ capture(event, properties, commerce) {
305
+ this.enqueue("event", event, { properties, ...commerce });
306
+ }
307
+ /** captureError records an exception as a first-class error event — the ONE
308
+ * error path (subsumes @sentry). A caught error, an unhandled rejection, or a
309
+ * manual report all become a type:'error' event on the same stream, lensed to
310
+ * the error-tracking view server-side. Never throws back into the app; errors
311
+ * are higher-signal than pageviews, so it flushes promptly (a crash may unload
312
+ * the page moments later). */
313
+ captureError(err, context) {
314
+ const ex = normalizeError(err);
315
+ ex.handled = context?.handled ?? true;
316
+ this.enqueue("error", ex.message, { error: ex, properties: context?.properties });
317
+ this.flush();
318
+ }
319
+ /** setCohort persists cohort dimensions (e.g. signupWeek at signup) so they ride
320
+ * every subsequent event. */
321
+ setCohort(patch) {
322
+ this.cohort = mergeCohort(patch);
323
+ }
324
+ /** flush drains the buffer to the server as one batch. beacon=true uses the
325
+ * unload-safe path. */
326
+ flush(beacon = false) {
327
+ if (!this.cfg.enabled || this.queue.length === 0) return;
328
+ const batch = this.queue;
329
+ this.queue = [];
330
+ this.clearTimer();
331
+ const token = this.cfg.getToken?.() ?? void 0;
332
+ const useBeacon = beacon && !token;
333
+ const path = useBeacon ? TRACKER_PATH : ANALYTICS_PATH;
334
+ const body = JSON.stringify({ batch });
335
+ if (this.cfg.debug) console.debug("[analytics] flush", batch.length, path);
336
+ this.transport.send(this.cfg.host + path, body, { beacon: useBeacon, token: token ?? void 0 });
337
+ }
338
+ // ── internals ────────────────────────────────────────────────────────────
339
+ enqueue(kind, event, extra) {
340
+ if (!this.cfg.enabled) return;
341
+ if (!this.started) this.init();
342
+ this.queue.push(this.build(kind, event, extra));
343
+ if (this.queue.length >= this.cfg.batchSize) this.flush();
344
+ else this.schedule();
345
+ }
346
+ build(kind, event, extra) {
347
+ const anon = anonId();
348
+ return {
349
+ messageId: uid2(),
350
+ type: kind,
351
+ event,
352
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
353
+ distinctId: this.personId ?? anon,
354
+ anonymousId: anon,
355
+ personId: this.personId,
356
+ sessionId: sessionId(),
357
+ product: this.cfg.product,
358
+ referrer: this.attribution.referrer,
359
+ utm: this.attribution.utm,
360
+ refCode: this.cohort.refCode ?? this.attribution.refCode,
361
+ channel: this.cohort.channel ?? this.attribution.channel,
362
+ signupWeek: this.cohort.signupWeek,
363
+ library: "@hanzo/event",
364
+ libraryVersion: VERSION,
365
+ ...extra
366
+ };
367
+ }
368
+ schedule() {
369
+ if (this.timer || !this.cfg.enabled) return;
370
+ this.timer = setTimeout(() => {
371
+ this.timer = null;
372
+ this.flush();
373
+ }, this.cfg.flushIntervalMs);
374
+ }
375
+ clearTimer() {
376
+ if (this.timer) {
377
+ clearTimeout(this.timer);
378
+ this.timer = null;
379
+ }
380
+ }
381
+ };
382
+ function createAnalytics(config) {
383
+ return new Analytics(config);
384
+ }
385
+
386
+ // src/goals.ts
387
+ var GOALS = {
388
+ // Signup: the conversion is signup_completed; the funnel is the four steps.
389
+ signup: {
390
+ label: "Signup",
391
+ event: EVENTS.SIGNUP_COMPLETED,
392
+ funnel: [
393
+ EVENTS.SIGNUP_VIEWED,
394
+ EVENTS.SIGNUP_SUBMITTED,
395
+ EVENTS.SIGNUP_VERIFIED,
396
+ EVENTS.FIRST_ACTION
397
+ ]
398
+ },
399
+ // Sale: a completed order qualified as a plan purchase (kind=plan).
400
+ sale: {
401
+ label: "Sale",
402
+ event: EVENTS.ORDER_COMPLETED,
403
+ filter: { property: "kind", equals: "plan" }
404
+ },
405
+ // Upgrade intent: a plan click; pricing_viewed is the top of its funnel.
406
+ upgradeIntent: {
407
+ label: "Upgrade Intent",
408
+ event: EVENTS.PLAN_CLICKED,
409
+ funnel: [EVENTS.PRICING_VIEWED, EVENTS.PLAN_CLICKED, EVENTS.CHECKOUT_STARTED]
410
+ }
411
+ };
412
+ var COHORTS = {
413
+ signupWeek: { field: "signup_week", label: "Signup week" },
414
+ channel: { field: "channel", label: "Acquisition channel" },
415
+ refCode: { field: "ref_code", label: "Referral code" }
416
+ };
417
+
418
+ exports.Analytics = Analytics;
419
+ exports.COHORTS = COHORTS;
420
+ exports.EVENTS = EVENTS;
421
+ exports.GOALS = GOALS;
422
+ exports.PAGEVIEW = PAGEVIEW;
423
+ exports.VERSION = VERSION;
424
+ exports.createAnalytics = createAnalytics;
425
+ exports.deriveChannel = deriveChannel;
426
+ exports.getCohort = getCohort;
427
+ exports.getFirstTouch = getFirstTouch;
428
+ exports.hasAttribution = hasAttribution;
429
+ exports.hostOf = hostOf;
430
+ exports.isoWeek = isoWeek;
431
+ exports.parseAttribution = parseAttribution;
432
+ //# sourceMappingURL=index.js.map
433
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/attribution.ts","../src/events.ts","../src/storage.ts","../src/core.ts","../src/goals.ts"],"names":["uid"],"mappings":"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","file":"index.js","sourcesContent":["// Pure attribution helpers: parse first-touch UTM/referrer/refCode from a URL,\n// derive the acquisition channel, and compute the ISO week for the signup cohort.\n// No I/O, no globals — trivially testable.\n\nimport type { Attribution } from './types'\n\nconst SOCIAL_HOSTS = [\n 'facebook.', 'instagram.', 'twitter.', 'x.com', 't.co', 'linkedin.',\n 'reddit.', 'youtube.', 'tiktok.', 'pinterest.', 'news.ycombinator.com',\n]\nconst SEARCH_HOSTS = ['google.', 'bing.', 'duckduckgo.', 'yahoo.', 'baidu.', 'ecosia.']\n\n/** parseAttribution reads UTM params + ref/refCode from a query string and pairs\n * them with the referrer. `search` is a location.search value (\"?utm_source=…\"). */\nexport function parseAttribution(search: string, referrer: string): Attribution {\n const q = new URLSearchParams(search || '')\n const get = (k: string) => {\n const v = q.get(k)\n return v ? v.trim() : undefined\n }\n const a: Attribution = {\n utm: {\n source: get('utm_source'),\n medium: get('utm_medium'),\n campaign: get('utm_campaign'),\n term: get('utm_term'),\n content: get('utm_content'),\n },\n referrer: referrer ? referrer.trim() : undefined,\n refCode: get('ref') || get('refCode') || get('ref_code') || undefined,\n }\n a.channel = deriveChannel(a)\n return a\n}\n\n/** deriveChannel classifies the visit: paid | referral | social | organic | direct. */\nexport function deriveChannel(a: Attribution): string {\n const medium = (a.utm.medium || '').toLowerCase()\n if (/(cpc|ppc|paid|paidsearch|display|cpm)/.test(medium)) return 'paid'\n if (a.utm.source || a.utm.campaign) return 'campaign'\n if (a.refCode) return 'referral'\n const host = hostOf(a.referrer)\n if (!host) return 'direct'\n if (SOCIAL_HOSTS.some((h) => host.includes(h))) return 'social'\n if (SEARCH_HOSTS.some((h) => host.includes(h))) return 'organic'\n return 'referral'\n}\n\n/** hostOf extracts a bare lowercase host from a URL; \"\" when unparseable. */\nexport function hostOf(raw?: string): string {\n if (!raw) return ''\n let s = raw.trim()\n const scheme = s.indexOf('://')\n if (scheme >= 0) s = s.slice(scheme + 3)\n const cut = s.search(/[/?#]/)\n if (cut >= 0) s = s.slice(0, cut)\n const at = s.indexOf('@')\n if (at >= 0) s = s.slice(at + 1)\n const colon = s.indexOf(':')\n if (colon >= 0) s = s.slice(0, colon)\n return s.toLowerCase().trim()\n}\n\n/** hasAttribution reports whether anything was captured (so we don't persist an\n * empty first-touch that would shadow a later real one). */\nexport function hasAttribution(a: Attribution): boolean {\n return Boolean(\n a.utm.source || a.utm.medium || a.utm.campaign || a.utm.term ||\n a.utm.content || a.refCode || (a.referrer && hostOf(a.referrer)),\n )\n}\n\n/** isoWeek returns the ISO-8601 week label, e.g. \"2026-W28\". */\nexport function isoWeek(d: Date): string {\n // Copy to UTC midnight; ISO week: Thursday-anchored.\n const date = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()))\n const day = date.getUTCDay() || 7\n date.setUTCDate(date.getUTCDate() + 4 - day)\n const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1))\n const week = Math.ceil(((date.getTime() - yearStart.getTime()) / 86400000 + 1) / 7)\n return `${date.getUTCFullYear()}-W${String(week).padStart(2, '0')}`\n}\n","// The ONE product-analytics vocabulary. Every Hanzo surface emits these exact\n// names so funnels, goals, and cohorts line up across console/chat/app/site/admin.\n// Pageviews use the reserved \"$pageview\" name (emitted by analytics.pageview()),\n// matching the server read lens.\n\nexport const EVENTS = {\n // Signup funnel: view -> submit -> verify -> completed -> first action.\n SIGNUP_VIEWED: 'signup_viewed',\n SIGNUP_SUBMITTED: 'signup_submitted',\n SIGNUP_VERIFIED: 'signup_verified',\n SIGNUP_COMPLETED: 'signup_completed',\n FIRST_ACTION: 'first_action',\n\n // Waitlist + referral.\n WAITLIST_JOINED: 'waitlist_joined',\n WAITLIST_SHARED: 'waitlist_shared',\n REFERRAL_USED: 'referral_used',\n REFERRAL_CLAIMED: 'referral_claimed',\n\n // Upgrade-intent + purchase.\n PRICING_VIEWED: 'pricing_viewed',\n PLAN_CLICKED: 'plan_clicked',\n CHECKOUT_STARTED: 'checkout_started',\n ORDER_COMPLETED: 'order_completed',\n\n // Feature usage — generic + the common key surfaces across products.\n FEATURE_USED: 'feature_used',\n API_KEY_CREATED: 'api_key_created',\n APP_CREATED: 'app_created',\n DEPLOY_STARTED: 'deploy_started',\n PROJECT_CREATED: 'project_created',\n AGENT_CREATED: 'agent_created',\n CHAT_STARTED: 'chat_started',\n CHAT_MESSAGE_SENT: 'chat_message_sent',\n TASK_STARTED: 'task_started',\n TASK_COMPLETED: 'task_completed',\n} as const\n\nexport type EventName = (typeof EVENTS)[keyof typeof EVENTS]\n\n/** The reserved event name a pageview is stored under (server + read lens). */\nexport const PAGEVIEW = '$pageview'\n","// SSR-safe browser storage for stable identifiers and first-touch state. Every\n// accessor no-ops (returns undefined) when there is no window/localStorage, so the\n// client imports cleanly in a Next.js server component.\n\nimport type { Attribution, Cohort } from './types'\n\nconst KEY = {\n anon: 'hz_anon_id',\n session: 'hz_session',\n firstTouch: 'hz_first_touch',\n cohort: 'hz_cohort',\n} as const\n\n/** 30-minute inactivity window defines a session (PostHog/GA convention). */\nconst SESSION_TTL_MS = 30 * 60 * 1000\n\nfunction ls(): Storage | undefined {\n try {\n if (typeof window === 'undefined' || !window.localStorage) return undefined\n return window.localStorage\n } catch {\n return undefined // Safari private mode / blocked storage\n }\n}\n\nfunction uid(): string {\n const c = typeof crypto !== 'undefined' ? crypto : undefined\n if (c && 'randomUUID' in c) return c.randomUUID()\n return 'a-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 10)\n}\n\n/** Stable anonymous id, minted once per browser and reused across sessions. */\nexport function anonId(): string | undefined {\n const s = ls()\n if (!s) return undefined\n let v = s.getItem(KEY.anon)\n if (!v) {\n v = uid()\n s.setItem(KEY.anon, v)\n }\n return v\n}\n\ninterface SessionState {\n id: string\n last: number\n}\n\n/** Current session id, rotated after SESSION_TTL_MS of inactivity. */\nexport function sessionId(now = Date.now()): string | undefined {\n const s = ls()\n if (!s) return undefined\n let state: SessionState | null = null\n try {\n state = JSON.parse(s.getItem(KEY.session) || 'null')\n } catch {\n state = null\n }\n if (!state || now - state.last > SESSION_TTL_MS) {\n state = { id: uid(), last: now }\n } else {\n state.last = now\n }\n s.setItem(KEY.session, JSON.stringify(state))\n return state.id\n}\n\n/** Read the persisted first-touch attribution. */\nexport function getFirstTouch(): Attribution | undefined {\n const s = ls()\n if (!s) return undefined\n try {\n const v = s.getItem(KEY.firstTouch)\n return v ? (JSON.parse(v) as Attribution) : undefined\n } catch {\n return undefined\n }\n}\n\n/** Persist first-touch attribution ONCE — never overwrite an existing record. */\nexport function setFirstTouchOnce(a: Attribution): Attribution {\n const s = ls()\n const existing = getFirstTouch()\n if (existing) return existing\n if (s) s.setItem(KEY.firstTouch, JSON.stringify(a))\n return a\n}\n\n/** Read persisted cohort dimensions. */\nexport function getCohort(): Cohort | undefined {\n const s = ls()\n if (!s) return undefined\n try {\n const v = s.getItem(KEY.cohort)\n return v ? (JSON.parse(v) as Cohort) : undefined\n } catch {\n return undefined\n }\n}\n\n/** Merge + persist cohort dimensions (signupWeek set once). */\nexport function mergeCohort(patch: Cohort): Cohort {\n const s = ls()\n const cur = getCohort() || {}\n const next: Cohort = {\n signupWeek: cur.signupWeek || patch.signupWeek,\n channel: patch.channel || cur.channel,\n refCode: cur.refCode || patch.refCode,\n }\n if (s) s.setItem(KEY.cohort, JSON.stringify(next))\n return next\n}\n","// The framework-agnostic event client. Buffers events and flushes them as one\n// batch to Hanzo Cloud — /v1/analytics normally, /v1/tracker via sendBeacon on\n// page unload. It NEVER sends the org/tenant: the server stamps that from the\n// validated session. The client only supplies its own visitor identity. Errors\n// are just events (type:'error') on the same stream — one client, one pipe.\n\nimport {\n parseAttribution,\n hasAttribution,\n deriveChannel,\n} from './attribution'\nimport { PAGEVIEW } from './events'\nimport {\n anonId,\n sessionId,\n getFirstTouch,\n setFirstTouchOnce,\n getCohort,\n mergeCohort,\n} from './storage'\nimport type {\n AnalyticsConfig,\n Attribution,\n Cohort,\n EventKind,\n Exception,\n Transport,\n WireEvent,\n} from './types'\n\nexport const VERSION = '0.2.0'\n\nconst ANALYTICS_PATH = '/v1/analytics'\nconst TRACKER_PATH = '/v1/tracker' // beacon-on-unload alias\n\nfunction uid(): string {\n const c = typeof crypto !== 'undefined' ? crypto : undefined\n if (c && 'randomUUID' in c) return c.randomUUID()\n return 'm-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 10)\n}\n\n/** Normalize anything thrown (Error | string | unknown) into an Exception. */\nfunction normalizeError(err: unknown): Exception {\n if (err instanceof Error) {\n return { type: err.name, message: err.message, stack: err.stack }\n }\n if (typeof err === 'string') return { message: err }\n try {\n return { message: JSON.stringify(err) }\n } catch {\n return { message: String(err) }\n }\n}\n\nconst isBrowser = () => typeof window !== 'undefined'\n\n/** DefaultTransport: fetch(keepalive) for authenticated/normal sends;\n * navigator.sendBeacon for headerless page-unload beacons. */\nclass DefaultTransport implements Transport {\n send(url: string, body: string, opts: { beacon: boolean; token?: string }): void {\n if (opts.beacon && isBrowser() && typeof navigator.sendBeacon === 'function') {\n try {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n return\n } catch {\n /* fall through to fetch */\n }\n }\n if (typeof fetch !== 'function') return\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n if (opts.token) headers.Authorization = `Bearer ${opts.token}`\n void fetch(url, {\n method: 'POST',\n headers,\n body,\n keepalive: true,\n credentials: 'include',\n }).catch(() => {\n /* analytics loss is acceptable; never throw into the app */\n })\n }\n}\n\nexport class Analytics {\n private cfg: Required<\n Pick<AnalyticsConfig, 'product' | 'batchSize' | 'flushIntervalMs' | 'enabled' | 'captureErrors'>\n > &\n AnalyticsConfig\n private transport: Transport\n private queue: WireEvent[] = []\n private timer: ReturnType<typeof setTimeout> | null = null\n private personId?: string\n private attribution: Attribution = { utm: {} }\n private cohort: Cohort = {}\n private started = false\n\n constructor(config: AnalyticsConfig) {\n this.cfg = {\n host: '',\n batchSize: 20,\n flushIntervalMs: 5000,\n enabled: true,\n captureErrors: true,\n ...config,\n }\n this.transport = config.transport ?? new DefaultTransport()\n }\n\n /** init is idempotent and browser-only for its side effects: capture first-touch\n * attribution, hydrate cohort, and register the unload flush. Safe to call from\n * a React effect on every render. */\n init(): void {\n if (this.started || !this.cfg.enabled) return\n this.started = true\n if (!isBrowser()) return\n\n const parsed = parseAttribution(window.location.search, document.referrer)\n this.attribution = hasAttribution(parsed)\n ? setFirstTouchOnce(parsed)\n : getFirstTouch() ?? parsed\n this.cohort = mergeCohort({\n channel: this.attribution.channel ?? deriveChannel(this.attribution),\n refCode: this.attribution.refCode,\n })\n\n const flushHidden = () => {\n if (document.visibilityState === 'hidden') this.flush(true)\n }\n window.addEventListener('visibilitychange', flushHidden)\n window.addEventListener('pagehide', () => this.flush(true))\n\n // Auto error capture — the drop-in @sentry replacement. Unhandled errors and\n // rejected promises become type:'error' events on the same stream.\n if (this.cfg.captureErrors) {\n window.addEventListener('error', (e: ErrorEvent) => {\n this.captureError(e.error ?? e.message, { handled: false })\n })\n window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => {\n this.captureError(e.reason, { handled: false })\n })\n }\n }\n\n /** identify binds the current visitor to a stable person id (post-login). */\n identify(personId: string, traits?: Record<string, unknown>): void {\n this.personId = personId\n this.enqueue('identify', undefined, { properties: traits })\n }\n\n /** group associates the visitor with an org/team (analytics grouping, not the\n * server tenant — the server still derives tenant from the session). */\n group(groupId: string, traits?: Record<string, unknown>): void {\n this.enqueue('group', undefined, { groupId, properties: traits })\n }\n\n /** pageview records a $pageview for the current (or given) location. */\n pageview(path?: string, properties?: Record<string, unknown>): void {\n const url = isBrowser() ? window.location.href : undefined\n const p = path ?? (isBrowser() ? window.location.pathname : undefined)\n this.enqueue('pageview', PAGEVIEW, { url, path: p, properties })\n }\n\n /** capture records a named product event with optional properties. Commerce\n * fields (productId/quantity/revenue/currency) may be passed for order events. */\n capture(\n event: string,\n properties?: Record<string, unknown>,\n commerce?: Pick<WireEvent, 'productId' | 'quantity' | 'revenue' | 'currency'>,\n ): void {\n this.enqueue('event', event, { properties, ...commerce })\n }\n\n /** track is an alias of capture (Segment familiarity). */\n track = this.capture.bind(this)\n\n /** captureError records an exception as a first-class error event — the ONE\n * error path (subsumes @sentry). A caught error, an unhandled rejection, or a\n * manual report all become a type:'error' event on the same stream, lensed to\n * the error-tracking view server-side. Never throws back into the app; errors\n * are higher-signal than pageviews, so it flushes promptly (a crash may unload\n * the page moments later). */\n captureError(\n err: unknown,\n context?: { handled?: boolean; properties?: Record<string, unknown> },\n ): void {\n const ex = normalizeError(err)\n ex.handled = context?.handled ?? true\n this.enqueue('error', ex.message, { error: ex, properties: context?.properties })\n this.flush()\n }\n\n /** captureException — @sentry-familiar alias of captureError. */\n captureException = this.captureError.bind(this)\n\n /** setCohort persists cohort dimensions (e.g. signupWeek at signup) so they ride\n * every subsequent event. */\n setCohort(patch: Cohort): void {\n this.cohort = mergeCohort(patch)\n }\n\n /** flush drains the buffer to the server as one batch. beacon=true uses the\n * unload-safe path. */\n flush(beacon = false): void {\n if (!this.cfg.enabled || this.queue.length === 0) return\n const batch = this.queue\n this.queue = []\n this.clearTimer()\n const token = this.cfg.getToken?.() ?? undefined\n // sendBeacon cannot carry an Authorization header, so token apps always use\n // keepalive fetch; cookie apps may beacon to the tracker route on unload.\n const useBeacon = beacon && !token\n const path = useBeacon ? TRACKER_PATH : ANALYTICS_PATH\n const body = JSON.stringify({ batch })\n if (this.cfg.debug) console.debug('[analytics] flush', batch.length, path)\n this.transport.send(this.cfg.host + path, body, { beacon: useBeacon, token: token ?? undefined })\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private enqueue(kind: EventKind, event: string | undefined, extra: Partial<WireEvent>): void {\n if (!this.cfg.enabled) return\n if (!this.started) this.init()\n this.queue.push(this.build(kind, event, extra))\n if (this.queue.length >= this.cfg.batchSize) this.flush()\n else this.schedule()\n }\n\n private build(kind: EventKind, event: string | undefined, extra: Partial<WireEvent>): WireEvent {\n const anon = anonId()\n return {\n messageId: uid(),\n type: kind,\n event,\n timestamp: new Date().toISOString(),\n distinctId: this.personId ?? anon,\n anonymousId: anon,\n personId: this.personId,\n sessionId: sessionId(),\n product: this.cfg.product,\n referrer: this.attribution.referrer,\n utm: this.attribution.utm,\n refCode: this.cohort.refCode ?? this.attribution.refCode,\n channel: this.cohort.channel ?? this.attribution.channel,\n signupWeek: this.cohort.signupWeek,\n library: '@hanzo/event',\n libraryVersion: VERSION,\n ...extra,\n }\n }\n\n private schedule(): void {\n if (this.timer || !this.cfg.enabled) return\n this.timer = setTimeout(() => {\n this.timer = null\n this.flush()\n }, this.cfg.flushIntervalMs)\n }\n\n private clearTimer(): void {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n }\n}\n\n/** createAnalytics builds a client instance. Most apps use one shared instance. */\nexport function createAnalytics(config: AnalyticsConfig): Analytics {\n return new Analytics(config)\n}\n\n// Re-export the hydrate helpers so consumers can read persisted cohort/attribution\n// (e.g. to send refCode to the referrals API) without reaching into storage.\nexport { getCohort, getFirstTouch }\n","// Insights goals + cohorts, defined once as data so the console/insights UI and\n// every product agree on what \"a Signup\", \"a Sale\", and \"upgrade intent\" mean.\n// This is the machine-readable spec — the shared source of truth a sync step can\n// push into Insights, and what the guide documents.\n\nimport { EVENTS } from './events'\n\nexport interface GoalDef {\n /** Human label shown in Insights. */\n label: string\n /** The event whose occurrence counts as the goal conversion. */\n event: string\n /** Optional ordered funnel leading to the goal (for funnel insights). */\n funnel?: string[]\n /** Optional property equality filter that qualifies the conversion. */\n filter?: { property: string; equals: string }\n}\n\nexport const GOALS: Record<'signup' | 'sale' | 'upgradeIntent', GoalDef> = {\n // Signup: the conversion is signup_completed; the funnel is the four steps.\n signup: {\n label: 'Signup',\n event: EVENTS.SIGNUP_COMPLETED,\n funnel: [\n EVENTS.SIGNUP_VIEWED,\n EVENTS.SIGNUP_SUBMITTED,\n EVENTS.SIGNUP_VERIFIED,\n EVENTS.FIRST_ACTION,\n ],\n },\n // Sale: a completed order qualified as a plan purchase (kind=plan).\n sale: {\n label: 'Sale',\n event: EVENTS.ORDER_COMPLETED,\n filter: { property: 'kind', equals: 'plan' },\n },\n // Upgrade intent: a plan click; pricing_viewed is the top of its funnel.\n upgradeIntent: {\n label: 'Upgrade Intent',\n event: EVENTS.PLAN_CLICKED,\n funnel: [EVENTS.PRICING_VIEWED, EVENTS.PLAN_CLICKED, EVENTS.CHECKOUT_STARTED],\n },\n}\n\nexport interface CohortDef {\n /** The hanzo.events column the cohort dimension maps to. */\n field: string\n label: string\n}\n\nexport const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef> = {\n signupWeek: { field: 'signup_week', label: 'Signup week' },\n channel: { field: 'channel', label: 'Acquisition channel' },\n refCode: { field: 'ref_code', label: 'Referral code' },\n}\n"]}