@hanzo/event 0.3.1 → 0.3.3
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/README.md +81 -17
- package/dist/core-B1XEdWLd.d.cts +296 -0
- package/dist/core-B1XEdWLd.d.ts +296 -0
- package/dist/index.cjs +590 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +177 -6
- package/dist/index.d.ts +177 -6
- package/dist/index.mjs +581 -64
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +413 -25
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.mjs +413 -25
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -2
- package/src/core.test.ts +258 -5
- package/src/core.ts +237 -45
- package/src/events.ts +30 -1
- package/src/funnels.test.ts +92 -0
- package/src/funnels.ts +154 -0
- package/src/goals.ts +25 -11
- package/src/index.ts +10 -0
- package/src/scrub.test.ts +96 -0
- package/src/scrub.ts +117 -0
- package/src/sentry.test.ts +312 -0
- package/src/sentry.ts +309 -0
- package/src/types.ts +117 -15
- package/src/version.ts +4 -0
- package/dist/core-CrbiAQhN.d.cts +0 -186
- package/dist/core-CrbiAQhN.d.ts +0 -186
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,84 @@
|
|
|
1
|
-
import { C as Cohort, A as Attribution } from './core-
|
|
2
|
-
export {
|
|
1
|
+
import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame } from './core-B1XEdWLd.js';
|
|
2
|
+
export { c as Analytics, d as AnalyticsConfig, E as EventKind, e as Exception, f as SentryLevel, T as Transport, W as WireEvent, g as createAnalytics } from './core-B1XEdWLd.js';
|
|
3
3
|
|
|
4
4
|
/** Read the persisted first-touch attribution. */
|
|
5
5
|
declare function getFirstTouch(): Attribution | undefined;
|
|
6
6
|
/** Read persisted cohort dimensions. */
|
|
7
7
|
declare function getCohort(): Cohort | undefined;
|
|
8
8
|
|
|
9
|
+
declare const VERSION = "0.3.3";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* parseDsn parses "https://<version>:<hmac>@<host>/v1/sentry/<projectId>" into its
|
|
13
|
+
* public key + the derived envelope ingest URL. The key (which itself contains a
|
|
14
|
+
* ':') is taken verbatim as the userinfo — we do NOT split it as user:pass. The
|
|
15
|
+
* key rides ?sentry_key= (not the DSN in the body) because that is the credential
|
|
16
|
+
* channel the server trusts AND the only one sendBeacon can carry on unload.
|
|
17
|
+
* Returns null for anything malformed (fail-safe: the caller then stays inert).
|
|
18
|
+
*/
|
|
19
|
+
declare function parseDsn(dsn: string | undefined | null): Dsn | null;
|
|
20
|
+
/**
|
|
21
|
+
* framesFromStack parses a browser Error.stack into Sentry frames, OLDEST-FIRST
|
|
22
|
+
* (Sentry orders caller->callee; the crash site is last — matching the server's
|
|
23
|
+
* pickCrashFrame). Handles both V8 ("at fn (file:li:co)") and
|
|
24
|
+
* Firefox/Safari ("fn@file:li:co"). Unparseable lines are skipped.
|
|
25
|
+
*/
|
|
26
|
+
declare function framesFromStack(stack: string | undefined): SentryFrame[];
|
|
27
|
+
/** Identity carried onto every error event — the SAME ids analytics uses. */
|
|
28
|
+
interface ErrorIdentity {
|
|
29
|
+
/** OIDC sub (post-identify) or anon id. NEVER email/PII. */
|
|
30
|
+
userId?: string;
|
|
31
|
+
sessionId?: string;
|
|
32
|
+
product?: string;
|
|
33
|
+
release?: string;
|
|
34
|
+
environment?: string;
|
|
35
|
+
}
|
|
36
|
+
interface BuildEventInput {
|
|
37
|
+
error: unknown;
|
|
38
|
+
options?: CaptureErrorOptions;
|
|
39
|
+
identity: ErrorIdentity;
|
|
40
|
+
capturePII?: boolean;
|
|
41
|
+
/** Injectable for deterministic tests. */
|
|
42
|
+
now?: number;
|
|
43
|
+
id?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* buildSentryEvent turns a throwable + identity into a Sentry `event`. The message
|
|
47
|
+
* (the leak surface) is scrubbed client-side; the user is ONLY the stable subject
|
|
48
|
+
* id — never email/username/ip. Level defaults to error, or fatal for uncaught.
|
|
49
|
+
*/
|
|
50
|
+
declare function buildSentryEvent(input: BuildEventInput): SentryEvent;
|
|
51
|
+
/**
|
|
52
|
+
* buildEnvelope frames a Sentry event into a newline-delimited envelope:
|
|
53
|
+
*
|
|
54
|
+
* {"event_id","dsn","sent_at"}\n
|
|
55
|
+
* {"type":"event","content_type":"application/json","length":N}\n
|
|
56
|
+
* <event json>\n
|
|
57
|
+
*
|
|
58
|
+
* The item is length-delimited (N = UTF-8 byte length) — the framing the server's
|
|
59
|
+
* parseEnvelope reads first (falling back to newline-delimited otherwise).
|
|
60
|
+
*/
|
|
61
|
+
declare function buildEnvelope(event: SentryEvent, dsn: Dsn, sentAt?: string): string;
|
|
62
|
+
|
|
63
|
+
/** redactSecrets removes known secret shapes. Always applied. */
|
|
64
|
+
declare function redactSecrets(s: string): string;
|
|
65
|
+
/** scrubPII masks emails and IPs. Applied unless PII capture is enabled. */
|
|
66
|
+
declare function scrubPII(s: string): string;
|
|
67
|
+
/** scrubText applies the redaction policy to a free-text field. Input is capped
|
|
68
|
+
* first: unbounded text is a denial-of-service surface, not just a size problem. */
|
|
69
|
+
declare function scrubText(s: string | undefined, capturePII?: boolean): string;
|
|
70
|
+
|
|
9
71
|
declare const EVENTS: {
|
|
10
72
|
readonly SIGNUP_VIEWED: "signup_viewed";
|
|
11
73
|
readonly SIGNUP_SUBMITTED: "signup_submitted";
|
|
12
74
|
readonly SIGNUP_VERIFIED: "signup_verified";
|
|
13
75
|
readonly SIGNUP_COMPLETED: "signup_completed";
|
|
76
|
+
/** A RETURNING user authenticated — the non-signup half of the IAM callback.
|
|
77
|
+
* Keeping it distinct is what stops returning logins from inflating signups. */
|
|
78
|
+
readonly LOGIN_COMPLETED: "login_completed";
|
|
79
|
+
/** Activation: the first moment of real value. ONE event for every product —
|
|
80
|
+
* the product-specific moment is the `action` property (api_call, app_live,
|
|
81
|
+
* chat_reply), never a new event name. */
|
|
14
82
|
readonly FIRST_ACTION: "first_action";
|
|
15
83
|
readonly WAITLIST_JOINED: "waitlist_joined";
|
|
16
84
|
readonly WAITLIST_SHARED: "waitlist_shared";
|
|
@@ -23,24 +91,127 @@ declare const EVENTS: {
|
|
|
23
91
|
readonly FEATURE_USED: "feature_used";
|
|
24
92
|
readonly API_KEY_CREATED: "api_key_created";
|
|
25
93
|
readonly APP_CREATED: "app_created";
|
|
26
|
-
readonly DEPLOY_STARTED: "deploy_started";
|
|
27
94
|
readonly PROJECT_CREATED: "project_created";
|
|
28
95
|
readonly AGENT_CREATED: "agent_created";
|
|
29
96
|
readonly CHAT_STARTED: "chat_started";
|
|
30
97
|
readonly CHAT_MESSAGE_SENT: "chat_message_sent";
|
|
98
|
+
/** The user switched model/endpoint — the single strongest quality signal a
|
|
99
|
+
* chat surface emits (a switch usually follows a bad answer). */
|
|
100
|
+
readonly MODEL_SWITCHED: "model_switched";
|
|
31
101
|
readonly TASK_STARTED: "task_started";
|
|
32
102
|
readonly TASK_COMPLETED: "task_completed";
|
|
103
|
+
readonly BUILD_STARTED: "build_started";
|
|
104
|
+
/** A model finished producing an artifact (an app build, a chat reply, an agent
|
|
105
|
+
* run). Carries `durationMs` — the outcome event owns its own duration, so no
|
|
106
|
+
* paired start event is needed. */
|
|
107
|
+
readonly GENERATION_COMPLETED: "generation_completed";
|
|
108
|
+
readonly GENERATION_FAILED: "generation_failed";
|
|
109
|
+
readonly DEPLOY_STARTED: "deploy_started";
|
|
110
|
+
readonly DEPLOY_SUCCEEDED: "deploy_succeeded";
|
|
111
|
+
readonly DEPLOY_FAILED: "deploy_failed";
|
|
33
112
|
};
|
|
34
113
|
type EventName = (typeof EVENTS)[keyof typeof EVENTS];
|
|
35
114
|
/** The reserved event name a pageview is stored under (server + read lens). */
|
|
36
115
|
declare const PAGEVIEW = "$pageview";
|
|
37
116
|
|
|
117
|
+
/** The emitting surfaces — the closed set of `AnalyticsConfig.product` values.
|
|
118
|
+
* `product` is on every event, so a funnel scopes by product instead of every
|
|
119
|
+
* surface prefixing its event names. */
|
|
120
|
+
declare const PRODUCTS: readonly ["site", "app", "chat", "console", "admin", "cloud"];
|
|
121
|
+
type ProductId = (typeof PRODUCTS)[number];
|
|
122
|
+
interface FunnelStep {
|
|
123
|
+
/** An EVENTS value (or PAGEVIEW). */
|
|
124
|
+
event: string;
|
|
125
|
+
/** Human label for the Insights step. */
|
|
126
|
+
label: string;
|
|
127
|
+
/** Property equality that qualifies the step, e.g. first_action{action:'api_call'}. */
|
|
128
|
+
where?: {
|
|
129
|
+
property: string;
|
|
130
|
+
equals: string;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
interface FunnelDef {
|
|
134
|
+
label: string;
|
|
135
|
+
/** Surface(s) the steps are emitted from — matched against the `product` field. */
|
|
136
|
+
products: ProductId[];
|
|
137
|
+
/**
|
|
138
|
+
* How steps are joined:
|
|
139
|
+
* • 'person' — steps join on distinctId (one browser, or one logged-in
|
|
140
|
+
* person across surfaces). The normal case.
|
|
141
|
+
* • 'aggregate' — steps are emitted on DIFFERENT origins by a LOGGED-OUT
|
|
142
|
+
* visitor, so there is no shared id: hanzo.ai, hanzo.app and
|
|
143
|
+
* hanzo.chat each mint their own anonymousId in their own
|
|
144
|
+
* storage. Read these as step-over-step COUNTS, never as a
|
|
145
|
+
* per-person conversion. Honest by construction.
|
|
146
|
+
*/
|
|
147
|
+
join: 'person' | 'aggregate';
|
|
148
|
+
steps: FunnelStep[];
|
|
149
|
+
}
|
|
150
|
+
declare const FUNNELS: {
|
|
151
|
+
/** hanzo.ai: land → sign up. IAM hosts the form, so `signup_submitted` is the
|
|
152
|
+
* redirect INTO IAM and `signup_completed` is the return at /auth/callback. */
|
|
153
|
+
readonly signup: {
|
|
154
|
+
readonly label: "Signup";
|
|
155
|
+
readonly products: ["site"];
|
|
156
|
+
readonly join: "person";
|
|
157
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
158
|
+
};
|
|
159
|
+
/** The developer activation path: an account is worth nothing until a key has
|
|
160
|
+
* made a call. `first_action{action:'api_call'}` is emitted SERVER-SIDE by
|
|
161
|
+
* Cloud on an org's first successful /v1 request — a browser cannot see it. */
|
|
162
|
+
readonly apiActivation: {
|
|
163
|
+
readonly label: "API activation";
|
|
164
|
+
readonly products: ["site", "cloud"];
|
|
165
|
+
readonly join: "person";
|
|
166
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep];
|
|
167
|
+
};
|
|
168
|
+
/** Upgrade intent → revenue. `order_completed{kind:'plan'}` is the Sale goal. */
|
|
169
|
+
readonly upgrade: {
|
|
170
|
+
readonly label: "Upgrade";
|
|
171
|
+
readonly products: ["site", "app", "console"];
|
|
172
|
+
readonly join: "person";
|
|
173
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
174
|
+
};
|
|
175
|
+
/** hanzo.app: describe → build → deploy → live URL. The whole product thesis
|
|
176
|
+
* in five steps; `deploy_succeeded` is the moment a live URL exists. */
|
|
177
|
+
readonly appShip: {
|
|
178
|
+
readonly label: "Describe → ship";
|
|
179
|
+
readonly products: ["app"];
|
|
180
|
+
readonly join: "person";
|
|
181
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
182
|
+
};
|
|
183
|
+
/** hanzo.chat: visit → first message → answer. `generation_completed` is what
|
|
184
|
+
* separates "typed something" from "got value". */
|
|
185
|
+
readonly chatEngage: {
|
|
186
|
+
readonly label: "Chat engagement";
|
|
187
|
+
readonly products: ["chat"];
|
|
188
|
+
readonly join: "person";
|
|
189
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep, FunnelStep];
|
|
190
|
+
};
|
|
191
|
+
/** The cross-surface handoff: the hanzo.ai composer forwards its prompt to
|
|
192
|
+
* hanzo.chat. Two origins, two anonymousIds — so this is an AGGREGATE funnel.
|
|
193
|
+
* The join is the `referrerProduct` property hanzo.chat reads off `?hz_ref=`,
|
|
194
|
+
* which makes the drop-off measurable without any cross-domain identity. */
|
|
195
|
+
readonly siteToChat: {
|
|
196
|
+
readonly label: "Site → Chat handoff";
|
|
197
|
+
readonly products: ["site", "chat"];
|
|
198
|
+
readonly join: "aggregate";
|
|
199
|
+
readonly steps: [FunnelStep, FunnelStep, FunnelStep];
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
type FunnelId = keyof typeof FUNNELS;
|
|
203
|
+
/** eventsOf flattens a funnel to its ordered event names — what a goal's `funnel`
|
|
204
|
+
* field carries, so the steps are defined exactly once (here). */
|
|
205
|
+
declare function eventsOf(id: FunnelId): string[];
|
|
206
|
+
|
|
38
207
|
interface GoalDef {
|
|
39
208
|
/** Human label shown in Insights. */
|
|
40
209
|
label: string;
|
|
41
210
|
/** The event whose occurrence counts as the goal conversion. */
|
|
42
211
|
event: string;
|
|
43
|
-
/**
|
|
212
|
+
/** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
|
|
213
|
+
funnelId?: FunnelId;
|
|
214
|
+
/** The ordered event names of `funnelId`, derived — never hand-written. */
|
|
44
215
|
funnel?: string[];
|
|
45
216
|
/** Optional property equality filter that qualifies the conversion. */
|
|
46
217
|
filter?: {
|
|
@@ -48,7 +219,7 @@ interface GoalDef {
|
|
|
48
219
|
equals: string;
|
|
49
220
|
};
|
|
50
221
|
}
|
|
51
|
-
declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent', GoalDef>;
|
|
222
|
+
declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
|
|
52
223
|
interface CohortDef {
|
|
53
224
|
/** The hanzo.events column the cohort dimension maps to. */
|
|
54
225
|
field: string;
|
|
@@ -69,4 +240,4 @@ declare function hasAttribution(a: Attribution): boolean;
|
|
|
69
240
|
/** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
|
|
70
241
|
declare function isoWeek(d: Date): string;
|
|
71
242
|
|
|
72
|
-
export { Attribution, COHORTS, Cohort, type CohortDef, EVENTS, type EventName, GOALS, type GoalDef, PAGEVIEW, deriveChannel, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution };
|
|
243
|
+
export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, EVENTS, type ErrorIdentity, type EventName, FUNNELS, type FunnelDef, type FunnelId, type FunnelStep, GOALS, type GoalDef, PAGEVIEW, PRODUCTS, type ProductId, SentryEvent, SentryFrame, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, eventsOf, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText };
|