@hanzo/event 0.3.0 → 0.3.2

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.
@@ -1,186 +0,0 @@
1
- /** The event kinds — the closed set the server understands. An error is just
2
- * another event on the one stream (Cloud stamps type:'error' → event_type='error',
3
- * the key the error-tracking lens filters on). */
4
- type EventKind = 'pageview' | 'event' | 'identify' | 'group' | 'error';
5
- /** A captured exception. Carried on a `type:'error'` event's top-level `error`
6
- * field; Cloud folds it into properties.$exception and lenses the event into the
7
- * error-tracking view (sentry.hanzo.ai). */
8
- interface Exception {
9
- /** Constructor/class name, e.g. "TypeError". */
10
- type?: string;
11
- /** The error message. */
12
- message: string;
13
- /** Stack trace when available. */
14
- stack?: string;
15
- /** false = an unhandled/global error (window.onerror, unhandledrejection);
16
- * true = a caught error the app chose to report. Defaults true. */
17
- handled?: boolean;
18
- }
19
- /** First-touch marketing attribution, parsed once and persisted. */
20
- interface Attribution {
21
- utm: {
22
- source?: string;
23
- medium?: string;
24
- campaign?: string;
25
- term?: string;
26
- content?: string;
27
- };
28
- referrer?: string;
29
- refCode?: string;
30
- /** Derived acquisition channel: direct | organic | paid | social | referral. */
31
- channel?: string;
32
- }
33
- /** Cohort dimensions carried on every event once known (see goals.ts COHORTS). */
34
- interface Cohort {
35
- /** ISO week the person first signed up, e.g. "2026-W28". */
36
- signupWeek?: string;
37
- channel?: string;
38
- refCode?: string;
39
- }
40
- /** One event as sent on the wire — the canonical Hanzo Cloud event. Maps 1:1 to
41
- * the cloud `CaptureEvent` (camelCase JSON keys); a batch of these is POSTed to
42
- * the ONE front door `/v1/event` as `{ batch: [WireEvent, …] }`. tenant/org is
43
- * NEVER a field here — the server stamps it from the validated session/key. */
44
- interface WireEvent {
45
- messageId: string;
46
- type: EventKind;
47
- event?: string;
48
- timestamp: string;
49
- distinctId?: string;
50
- anonymousId?: string;
51
- personId?: string;
52
- sessionId?: string;
53
- product?: string;
54
- url?: string;
55
- path?: string;
56
- referrer?: string;
57
- utm?: Attribution['utm'];
58
- refCode?: string;
59
- channel?: string;
60
- groupId?: string;
61
- signupWeek?: string;
62
- productId?: string;
63
- quantity?: number;
64
- revenue?: number;
65
- currency?: string;
66
- /** Set on `type:'error'` events — the captured exception. Cloud lifts it into
67
- * properties.$exception (foldException) for the error-tracking lens. */
68
- error?: Exception;
69
- properties?: Record<string, unknown>;
70
- library?: string;
71
- libraryVersion?: string;
72
- }
73
- /** Injectable transports — overridden in tests; the default in core.ts uses fetch
74
- * (keepalive) and sendBeacon. A bearer JWT or a publishable pk_ key rides
75
- * Authorization on fetch; on a headerless beacon a publishable key rides the
76
- * ?ingest_key query. */
77
- interface Transport {
78
- /** Durable POST usable during page unload (fetch keepalive / sendBeacon). */
79
- send(url: string, body: string, opts: {
80
- beacon: boolean;
81
- token?: string;
82
- ingestKey?: string;
83
- }): void;
84
- }
85
- interface AnalyticsConfig {
86
- /** Cloud base URL. Defaults to "https://api.hanzo.ai" (the one edge). Set to
87
- * same-origin ("") for cookie-auth apps served behind the same edge
88
- * (console/admin/chat), so the browser rides the session cookie. */
89
- host?: string;
90
- /** Emitting surface: console | chat | app | site | admin. */
91
- product: string;
92
- /** Bearer token provider for token-auth apps. Omit for cookie/session apps
93
- * (the client then relies on same-origin credentials). */
94
- getToken?: () => string | undefined | null;
95
- /** Publishable ingest key (pk_…). When set, the client authenticates to the ONE
96
- * front door `/v1/event` with this key instead of a bearer/cookie: it rides
97
- * Authorization: Bearer pk_… on fetch and ?ingest_key=pk_… on a headerless
98
- * page-unload beacon, so ALL THREE lenses (web + product + error) light up with
99
- * no bearer and unload beacons work anonymously. The key is write-only (cannot
100
- * read) and safe to ship in a bundle; mint one per org via POST /v1/ingest/keys.
101
- * Recommended for marketing/public pages and the full sentry-subsuming setup. */
102
- ingestKey?: string;
103
- /** Max events buffered before an automatic flush. */
104
- batchSize?: number;
105
- /** Auto-flush cadence in ms. */
106
- flushIntervalMs?: number;
107
- /** Turn the client off entirely (e.g. opt-out / DNT). Defaults to enabled. */
108
- enabled?: boolean;
109
- /** Auto-capture unhandled errors + promise rejections (window.onerror,
110
- * unhandledrejection) as error events. Browser-only. Defaults to enabled —
111
- * this is what makes the client a drop-in @sentry replacement. */
112
- captureErrors?: boolean;
113
- /** Override the transport (tests). */
114
- transport?: Transport;
115
- /** Debug logging. */
116
- debug?: boolean;
117
- }
118
-
119
- declare const VERSION = "0.3.0";
120
- declare class Analytics {
121
- private cfg;
122
- private transport;
123
- private queue;
124
- private timer;
125
- private personId?;
126
- private attribution;
127
- private cohort;
128
- private started;
129
- constructor(config: AnalyticsConfig);
130
- /** init is idempotent and browser-only for its side effects: capture first-touch
131
- * attribution, hydrate cohort, register the unload flush, and (unless opted out)
132
- * auto-capture unhandled errors. Safe to call from a React effect on every
133
- * render. */
134
- init(): void;
135
- /** identify binds the current visitor to a stable person id (post-login). */
136
- identify(personId: string, traits?: Record<string, unknown>): void;
137
- /** group associates the visitor with an org/team (analytics grouping, not the
138
- * server tenant — the server still derives tenant from the session). */
139
- group(groupId: string, traits?: Record<string, unknown>): void;
140
- /** pageview records a $pageview for the current (or given) location. */
141
- pageview(path?: string, properties?: Record<string, unknown>): void;
142
- /** capture records a named product event with optional properties. Commerce
143
- * fields (productId/quantity/revenue/currency) may be passed for order events. */
144
- capture(event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, 'productId' | 'quantity' | 'revenue' | 'currency'>): void;
145
- /** track is an alias of capture (Segment familiarity). */
146
- track: (event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, "productId" | "quantity" | "revenue" | "currency">) => void;
147
- /** captureError records an exception as a first-class error event — the ONE
148
- * error path (subsumes @sentry). A caught error, an unhandled rejection, a
149
- * React render error, or a manual report all become a type:'error' event on the
150
- * same stream; Cloud folds the exception into properties.$exception and stamps
151
- * event_type='error', so it surfaces in the error-tracking lens. Never throws
152
- * back into the app; errors are higher-signal than pageviews, so it flushes
153
- * promptly (a crash may unload the page moments later). */
154
- captureError(err: unknown, context?: {
155
- handled?: boolean;
156
- properties?: Record<string, unknown>;
157
- }): void;
158
- /** captureException — @sentry-familiar alias of captureError. */
159
- captureException: (err: unknown, context?: {
160
- handled?: boolean;
161
- properties?: Record<string, unknown>;
162
- }) => void;
163
- /** setCohort persists cohort dimensions (e.g. signupWeek at signup) so they ride
164
- * every subsequent event. */
165
- setCohort(patch: Cohort): void;
166
- /** flush drains the buffer to the server as ONE batch through the ONE ingest
167
- * front door POST /v1/event, body { batch: [Event…] }. beacon=true selects the
168
- * unload-safe transport. Auth is orthogonal to the wire:
169
- *
170
- * • publishable key set → rides Authorization: Bearer pk_… (fetch) or
171
- * ?ingest_key=pk_… (beacon), so unload beacons work anonymously.
172
- * • else a bearer JWT rides Authorization (fetch only — sendBeacon cannot
173
- * carry a header, so token apps fall back to keepalive fetch on unload).
174
- * • else a cookie app rides same-origin credentials (beacon carries the
175
- * cookie fine).
176
- */
177
- flush(beacon?: boolean): void;
178
- private enqueue;
179
- private build;
180
- private schedule;
181
- private clearTimer;
182
- }
183
- /** createAnalytics builds a client instance. Most apps use one shared instance. */
184
- declare function createAnalytics(config: AnalyticsConfig): Analytics;
185
-
186
- export { type Attribution as A, type Cohort as C, type EventKind as E, type Transport as T, VERSION as V, type WireEvent as W, Analytics as a, type AnalyticsConfig as b, type Exception as c, createAnalytics as d };
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
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 through the ONE Hanzo Cloud ingestion front door:\n//\n// POST {host}/v1/event body: { batch: [Event, …] } -> { accepted, dropped }\n//\n// It NEVER sends the org/tenant: Cloud resolves that server-side (from the\n// validated session, or the signed publishable key) and stamps it. The client\n// only supplies its own visitor identity. Errors are just events (type:'error')\n// on the same stream — one client, one pipe, lensed server-side into product\n// analytics (insights), web analytics (analytics), and error tracking (sentry).\n//\n// Auth is orthogonal — the SAME body to the SAME door, differing only in how the\n// caller proves its tenant:\n//\n// • cookie/session app (host:'') — same-origin credentials ride the request.\n// • bearer app (getToken) — Authorization: Bearer <jwt>.\n// • publishable-key app (ingestKey: 'pk_…') — Authorization: Bearer pk_… on\n// fetch, ?ingest_key=pk_… on a headerless page-unload beacon. Write-only and\n// safe to ship in a bundle; the door HMAC-verifies it to an org server-side.\n//\n// The wire is the canonical `Event` (== the cloud CaptureEvent): its `type` field\n// is what Cloud folds to event_type='error', so a captured exception reaches the\n// error-tracking lens. (A four-field {event,distinctId,time,properties} object has\n// no `type`, so it can never be lensed as an error — this batched Event wire is\n// the one that lights up all three lenses.)\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.3.0'\n\nconst EVENT_PATH = '/v1/event' // the ONE canonical ingestion front door\nconst DEFAULT_HOST = 'https://api.hanzo.ai' // the one edge; cookie apps pass host:''\n\n/** appendQuery adds a single query param to a URL string — used to carry a\n * publishable key on a headerless sendBeacon (?ingest_key=…). */\nfunction appendQuery(url: string, key: string, value: string): string {\n return url + (url.includes('?') ? '&' : '?') + key + '=' + encodeURIComponent(value)\n}\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. A bearer (a JWT or a\n * publishable pk_ key) rides Authorization on fetch; on a beacon — which cannot\n * set headers — a publishable key rides the ?ingest_key query instead. */\nclass DefaultTransport implements Transport {\n send(url: string, body: string, opts: { beacon: boolean; token?: string; ingestKey?: string }): void {\n if (opts.beacon && isBrowser() && typeof navigator.sendBeacon === 'function') {\n const beaconUrl = opts.ingestKey ? appendQuery(url, 'ingest_key', opts.ingestKey) : url\n try {\n navigator.sendBeacon(beaconUrl, 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 const bearer = opts.ingestKey ?? opts.token\n if (bearer) headers.Authorization = `Bearer ${bearer}`\n void fetch(url, {\n method: 'POST',\n headers,\n body,\n keepalive: true,\n credentials: 'include',\n }).catch(() => {\n /* telemetry 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: DEFAULT_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, register the unload flush, and (unless opted out)\n * auto-capture unhandled errors. Safe to call from a React effect on every\n * 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, which Cloud\n // stamps event_type='error' → the sentry.hanzo.ai lens.\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, a\n * React render error, or a manual report all become a type:'error' event on the\n * same stream; Cloud folds the exception into properties.$exception and stamps\n * event_type='error', so it surfaces in the error-tracking lens. Never throws\n * back into the app; errors are higher-signal than pageviews, so it flushes\n * promptly (a crash may unload 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 through the ONE ingest\n * front door POST /v1/event, body { batch: [Event…] }. beacon=true selects the\n * unload-safe transport. Auth is orthogonal to the wire:\n *\n * • publishable key set → rides Authorization: Bearer pk_… (fetch) or\n * ?ingest_key=pk_… (beacon), so unload beacons work anonymously.\n * • else a bearer JWT rides Authorization (fetch only — sendBeacon cannot\n * carry a header, so token apps fall back to keepalive fetch on unload).\n * • else a cookie app rides same-origin credentials (beacon carries the\n * cookie fine).\n */\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\n const key = this.cfg.ingestKey?.trim() || undefined\n // A publishable key and a bearer JWT are mutually exclusive doors; the key wins.\n const token = key ? undefined : this.cfg.getToken?.() ?? undefined\n // Only a headerful bearer JWT blocks the beacon: sendBeacon cannot set an\n // Authorization header. A pk_ rides ?ingest_key; a cookie rides credentials.\n const useBeacon = beacon && !token\n const body = JSON.stringify({ batch })\n if (this.cfg.debug) console.debug('[event] flush →', EVENT_PATH, batch.length)\n this.transport.send(this.cfg.host + EVENT_PATH, body, { beacon: useBeacon, token, ingestKey: key })\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"]}
package/dist/react.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/attribution.ts","../src/events.ts","../src/storage.ts","../src/core.ts","../src/react.tsx"],"names":["uid","createContext","useMemo","useEffect","createElement","useContext","useRef","Component"],"mappings":"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","file":"react.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 through the ONE Hanzo Cloud ingestion front door:\n//\n// POST {host}/v1/event body: { batch: [Event, …] } -> { accepted, dropped }\n//\n// It NEVER sends the org/tenant: Cloud resolves that server-side (from the\n// validated session, or the signed publishable key) and stamps it. The client\n// only supplies its own visitor identity. Errors are just events (type:'error')\n// on the same stream — one client, one pipe, lensed server-side into product\n// analytics (insights), web analytics (analytics), and error tracking (sentry).\n//\n// Auth is orthogonal — the SAME body to the SAME door, differing only in how the\n// caller proves its tenant:\n//\n// • cookie/session app (host:'') — same-origin credentials ride the request.\n// • bearer app (getToken) — Authorization: Bearer <jwt>.\n// • publishable-key app (ingestKey: 'pk_…') — Authorization: Bearer pk_… on\n// fetch, ?ingest_key=pk_… on a headerless page-unload beacon. Write-only and\n// safe to ship in a bundle; the door HMAC-verifies it to an org server-side.\n//\n// The wire is the canonical `Event` (== the cloud CaptureEvent): its `type` field\n// is what Cloud folds to event_type='error', so a captured exception reaches the\n// error-tracking lens. (A four-field {event,distinctId,time,properties} object has\n// no `type`, so it can never be lensed as an error — this batched Event wire is\n// the one that lights up all three lenses.)\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.3.0'\n\nconst EVENT_PATH = '/v1/event' // the ONE canonical ingestion front door\nconst DEFAULT_HOST = 'https://api.hanzo.ai' // the one edge; cookie apps pass host:''\n\n/** appendQuery adds a single query param to a URL string — used to carry a\n * publishable key on a headerless sendBeacon (?ingest_key=…). */\nfunction appendQuery(url: string, key: string, value: string): string {\n return url + (url.includes('?') ? '&' : '?') + key + '=' + encodeURIComponent(value)\n}\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. A bearer (a JWT or a\n * publishable pk_ key) rides Authorization on fetch; on a beacon — which cannot\n * set headers — a publishable key rides the ?ingest_key query instead. */\nclass DefaultTransport implements Transport {\n send(url: string, body: string, opts: { beacon: boolean; token?: string; ingestKey?: string }): void {\n if (opts.beacon && isBrowser() && typeof navigator.sendBeacon === 'function') {\n const beaconUrl = opts.ingestKey ? appendQuery(url, 'ingest_key', opts.ingestKey) : url\n try {\n navigator.sendBeacon(beaconUrl, 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 const bearer = opts.ingestKey ?? opts.token\n if (bearer) headers.Authorization = `Bearer ${bearer}`\n void fetch(url, {\n method: 'POST',\n headers,\n body,\n keepalive: true,\n credentials: 'include',\n }).catch(() => {\n /* telemetry 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: DEFAULT_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, register the unload flush, and (unless opted out)\n * auto-capture unhandled errors. Safe to call from a React effect on every\n * 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, which Cloud\n // stamps event_type='error' → the sentry.hanzo.ai lens.\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, a\n * React render error, or a manual report all become a type:'error' event on the\n * same stream; Cloud folds the exception into properties.$exception and stamps\n * event_type='error', so it surfaces in the error-tracking lens. Never throws\n * back into the app; errors are higher-signal than pageviews, so it flushes\n * promptly (a crash may unload 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 through the ONE ingest\n * front door POST /v1/event, body { batch: [Event…] }. beacon=true selects the\n * unload-safe transport. Auth is orthogonal to the wire:\n *\n * • publishable key set → rides Authorization: Bearer pk_… (fetch) or\n * ?ingest_key=pk_… (beacon), so unload beacons work anonymously.\n * • else a bearer JWT rides Authorization (fetch only — sendBeacon cannot\n * carry a header, so token apps fall back to keepalive fetch on unload).\n * • else a cookie app rides same-origin credentials (beacon carries the\n * cookie fine).\n */\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\n const key = this.cfg.ingestKey?.trim() || undefined\n // A publishable key and a bearer JWT are mutually exclusive doors; the key wins.\n const token = key ? undefined : this.cfg.getToken?.() ?? undefined\n // Only a headerful bearer JWT blocks the beacon: sendBeacon cannot set an\n // Authorization header. A pk_ rides ?ingest_key; a cookie rides credentials.\n const useBeacon = beacon && !token\n const body = JSON.stringify({ batch })\n if (this.cfg.debug) console.debug('[event] flush →', EVENT_PATH, batch.length)\n this.transport.send(this.cfg.host + EVENT_PATH, body, { beacon: useBeacon, token, ingestKey: key })\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","// React bindings for @hanzo/event — a provider, an accessor hook, a route-change\n// pageview hook, and an error boundary. Framework-neutral: it takes the current\n// path as an argument (the Next app wires usePathname(); a Vite/router app passes\n// its own), so this file never imports next/*.\n//\n// 'use client'\n// import { AnalyticsProvider, useAnalytics, usePageview, ErrorBoundary } from '@hanzo/event/react'\n//\n// <AnalyticsProvider config={{ product: 'console' }}>…</AnalyticsProvider>\n// const a = useAnalytics(); usePageview(usePathname())\n\nimport {\n Component,\n createContext,\n createElement,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ErrorInfo,\n type ReactNode,\n} from 'react'\nimport { Analytics, createAnalytics } from './core'\nimport type { AnalyticsConfig } from './types'\n\nconst Ctx = createContext<Analytics | null>(null)\n\nexport interface AnalyticsProviderProps {\n /** Provide a pre-built client, or a config to build one. */\n client?: Analytics\n config?: AnalyticsConfig\n /** Fire a pageview for the initial load (default true). */\n autoPageview?: boolean\n children: ReactNode\n}\n\nexport function AnalyticsProvider(props: AnalyticsProviderProps) {\n const { client, config, autoPageview = true, children } = props\n const instance = useMemo<Analytics>(() => {\n if (client) return client\n if (config) return createAnalytics(config)\n throw new Error('AnalyticsProvider requires `client` or `config`')\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [client])\n\n useEffect(() => {\n instance.init()\n if (autoPageview) instance.pageview()\n }, [instance, autoPageview])\n\n return createElement(Ctx.Provider, { value: instance }, children)\n}\n\n/** useAnalytics returns the client. Outside a provider it returns a no-op-safe\n * null-guarded proxy so calls never throw during SSR/tests. */\nexport function useAnalytics(): Analytics {\n const a = useContext(Ctx)\n if (!a) return noop\n return a\n}\n\n/** usePageview fires a pageview on every route CHANGE (not the initial mount —\n * the provider's autoPageview covers that, so pages are counted exactly once). */\nexport function usePageview(path: string | null | undefined): void {\n const a = useContext(Ctx)\n const first = useRef(true)\n useEffect(() => {\n if (first.current) {\n first.current = false\n return\n }\n if (a && path) a.pageview(path)\n }, [a, path])\n}\n\n// A shared no-op client for use outside a provider — keeps call sites total.\nconst noop = new Analytics({ product: 'unknown', enabled: false })\n\nexport interface ErrorBoundaryProps {\n children: ReactNode\n /** Rendered when a child throws. A function receives the error + a reset. */\n fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode)\n /** Explicit client; defaults to the one from AnalyticsProvider. */\n client?: Analytics\n}\n\ninterface ErrorBoundaryState {\n error: Error | null\n}\n\n/** ErrorBoundary reports React render errors to the client as error events —\n * React swallows these before window.onerror sees them, so a boundary is the\n * ONLY way to capture them. This is the React half of the @sentry replacement.\n * Pair it with AnalyticsProvider (it reads the client from context), or pass an\n * explicit `client`. */\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n static contextType = Ctx\n declare context: React.ContextType<typeof Ctx>\n state: ErrorBoundaryState = { error: null }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { error }\n }\n\n componentDidCatch(error: Error, info: ErrorInfo): void {\n const client = this.props.client ?? this.context ?? undefined\n client?.captureError(error, {\n handled: false,\n properties: { componentStack: info.componentStack, react: true },\n })\n }\n\n private reset = () => this.setState({ error: null })\n\n render(): ReactNode {\n const { error } = this.state\n if (error === null) return this.props.children\n const { fallback } = this.props\n if (typeof fallback === 'function') return fallback(error, this.reset)\n return fallback ?? null\n }\n}\n"]}