@hanzo/event 0.3.35 → 0.3.37

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.
Files changed (62) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +8 -8
  3. package/TAXONOMY.md +1 -1
  4. package/dist/attribution.d.ts +14 -0
  5. package/dist/attribution.d.ts.map +1 -0
  6. package/dist/core.d.ts +102 -0
  7. package/dist/core.d.ts.map +1 -0
  8. package/dist/dsn.d.ts +43 -0
  9. package/dist/dsn.d.ts.map +1 -0
  10. package/dist/events.d.ts +21 -0
  11. package/dist/events.d.ts.map +1 -0
  12. package/dist/exception.d.ts +50 -0
  13. package/dist/exception.d.ts.map +1 -0
  14. package/dist/funnels.d.ts +14 -0
  15. package/dist/funnels.d.ts.map +1 -0
  16. package/dist/goals.d.ts +24 -0
  17. package/dist/goals.d.ts.map +1 -0
  18. package/dist/index.cjs +24 -8
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.cts +22 -292
  21. package/dist/index.d.ts +22 -292
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.mjs +24 -8
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/org.d.ts +74 -0
  26. package/dist/org.d.ts.map +1 -0
  27. package/dist/react.cjs +24 -8
  28. package/dist/react.cjs.map +1 -1
  29. package/dist/react.d.cts +13 -14
  30. package/dist/react.d.ts +13 -14
  31. package/dist/react.d.ts.map +1 -0
  32. package/dist/react.mjs +24 -8
  33. package/dist/react.mjs.map +1 -1
  34. package/dist/scrub.d.ts +20 -0
  35. package/dist/scrub.d.ts.map +1 -0
  36. package/dist/sentry.d.ts +62 -0
  37. package/dist/sentry.d.ts.map +1 -0
  38. package/dist/stack.d.ts +25 -0
  39. package/dist/stack.d.ts.map +1 -0
  40. package/dist/storage.d.ts +32 -0
  41. package/dist/storage.d.ts.map +1 -0
  42. package/dist/{core-CIhI2R7S.d.cts → types.d.ts} +21 -119
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/uid.d.ts +10 -0
  45. package/dist/uid.d.ts.map +1 -0
  46. package/dist/version.d.ts +2 -0
  47. package/dist/version.d.ts.map +1 -0
  48. package/package.json +11 -11
  49. package/src/anon.d.ts +1 -1
  50. package/src/anon.js +3 -3
  51. package/src/anon.test.ts +3 -3
  52. package/src/core.test.ts +6 -6
  53. package/src/core.ts +11 -11
  54. package/src/events.ts +1 -1
  55. package/src/exception.ts +1 -1
  56. package/src/org.test.ts +16 -0
  57. package/src/org.ts +17 -1
  58. package/src/stack.ts +1 -1
  59. package/src/types.ts +3 -3
  60. package/src/uid.ts +2 -2
  61. package/src/version.ts +1 -1
  62. package/dist/core-CIhI2R7S.d.ts +0 -380
package/dist/index.d.cts CHANGED
@@ -1,292 +1,22 @@
1
- import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-CIhI2R7S.cjs';
2
- export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-CIhI2R7S.cjs';
3
- import { FunnelId } from '@hanzo/events';
4
- export { EVENTS, EXCEPTION, EventName, FUNNELS, FunnelDef, FunnelId, FunnelStep, PAGEVIEW, PRODUCTS, ProductId, eventsOf } from '@hanzo/events';
5
-
6
- /** Read the persisted first-touch attribution. */
7
- declare function getFirstTouch(): Attribution | undefined;
8
- /** Read persisted cohort dimensions. */
9
- declare function getCohort(): Cohort | undefined;
10
-
11
- declare const VERSION = "0.3.35";
12
-
13
- /**
14
- * parseDsn parses "https://<version>:<hmac>@<host>/v1/event/<projectId>" into its
15
- * public key + the derived envelope ingest URL. The key (which itself contains a
16
- * ':') is taken verbatim as the userinfo we do NOT split it as user:pass. The
17
- * key rides ?sentry_key= (not the DSN in the body) because that is the credential
18
- * channel the server trusts AND the only one sendBeacon can carry on unload.
19
- * Returns null for anything malformed (fail-safe: the caller then stays inert).
20
- */
21
- declare function parseDsn(dsn: string | undefined | null): Dsn | null;
22
- /**
23
- * framesFromStack parses a browser Error.stack into Sentry frames, OLDEST-FIRST
24
- * (Sentry orders caller->callee; the crash site is last — matching the server's
25
- * pickCrashFrame). Handles both V8 ("at fn (file:li:co)") and
26
- * Firefox/Safari ("fn@file:li:co"). Unparseable lines are skipped.
27
- */
28
- declare function framesFromStack(stack: string | undefined): SentryFrame[];
29
- /** Identity carried onto every error event — the SAME ids analytics uses. */
30
- interface ErrorIdentity {
31
- /** OIDC sub (post-identify) or anon id. NEVER email/PII. */
32
- userId?: string;
33
- sessionId?: string;
34
- product?: string;
35
- release?: string;
36
- environment?: string;
37
- }
38
- interface BuildEventInput {
39
- error: unknown;
40
- options?: CaptureErrorOptions;
41
- identity: ErrorIdentity;
42
- capturePII?: boolean;
43
- /** Injectable for deterministic tests. */
44
- now?: number;
45
- id?: string;
46
- }
47
- /**
48
- * buildSentryEvent turns a throwable + identity into a Sentry `event`. The message
49
- * (the leak surface) is scrubbed client-side; the user is ONLY the stable subject
50
- * id — never email/username/ip. Level defaults to error, or fatal for uncaught.
51
- */
52
- declare function buildSentryEvent(input: BuildEventInput): SentryEvent;
53
- /**
54
- * buildEnvelope frames a Sentry event into a newline-delimited envelope:
55
- *
56
- * {"event_id","dsn","sent_at"}\n
57
- * {"type":"event","content_type":"application/json","length":N}\n
58
- * <event json>\n
59
- *
60
- * The item is length-delimited (N = UTF-8 byte length) — the framing the server's
61
- * parseEnvelope reads first (falling back to newline-delimited otherwise).
62
- */
63
- declare function buildEnvelope(event: SentryEvent, dsn: Dsn, sentAt?: string): string;
64
-
65
- // Types for anon.js, which is hand-written ES5 rather than TypeScript because
66
- // The door's hosted tag inlines it VERBATIM and has no compiler.
67
- // The declarations are here so the bundled client still imports it typed.
68
-
69
- /** Mints a time-ordered UUIDv7 (RFC 9562 §5.7) for `now` in epoch milliseconds. */
70
- declare function hzUuidv7(now?: number): string
71
-
72
- /**
73
- * uuidv7 mints a time-ordered UUIDv7 for `now` (epoch milliseconds).
74
- *
75
- * Two ids minted in the same millisecond sort arbitrarily between themselves; ids
76
- * from different milliseconds sort by time, lexically and numerically alike.
77
- */
78
-
79
- /** The millisecond timestamp a v7 id was minted at — the inverse of uuidv7. */
80
- declare function uuidv7Time(id: string): number;
81
-
82
- /**
83
- * The product → Sentinel DSN registry.
84
- *
85
- * An app declares WHAT it is (`product: 'console'`); this module knows WHERE its
86
- * errors go. That split is the whole point: no surface has to learn a DSN, carry
87
- * a build argument, or grow a config file to report errors — declaring the
88
- * product it already declares is enough.
89
- *
90
- * A Sentinel DSN is PUBLIC by construction. It ships inside the client bundle and
91
- * is readable in devtools on any deployed page, and it grants exactly one
92
- * capability: submitting new events. It cannot read issues, projects, or any
93
- * other data. So committing it is not leaking a secret — it is recording a public
94
- * identifier next to the code that needs it. (Contrast the server-side collector
95
- * DSN in the `team-analytics-sentry` Secret, which is HMAC-derived and revocable
96
- * precisely because a server-side credential is NOT public.)
97
- *
98
- * Why a literal map instead of deriving `hanzo-${product}`: the projects predate
99
- * this registry and do not derive cleanly — `site` lives in `hanzo-ai`, not
100
- * `hanzo-site`. An explicit map is honest about that; a derivation rule plus an
101
- * exception table is the same data with a trap in it.
102
- *
103
- * Projects are org-scoped and named `<org>-<app>`. To add one: create the project
104
- * (POST /v1/sentinel/projects with X-Org-Id), then add its `dsn` here keyed by the
105
- * product name the app passes to `createAnalytics`.
106
- */
107
- /** PRODUCT_PROJECT maps a `product` to its Sentinel project id. The DSN's KEY is no
108
- * longer a per-project secret — it is the ONE org publishable key (below), so a
109
- * surface's errors ride the SAME key its events do. The id only names WHICH
110
- * project the errors group under, and cloud auto-provisions that project on first
111
- * keyed ingest, so a new id needs nothing minted. `site` lives in the `hanzo-ai`
112
- * project — an explicit map, because the projects predate this and do not derive
113
- * cleanly from the product name. */
114
- declare const PRODUCT_PROJECT: Readonly<Record<string, string>>;
115
- /** dsnForProduct builds the product's Sentinel DSN from the caller's resolved
116
- * publishable `key` and the product's project — the SAME key the event stream
117
- * carries, at the product's envelope endpoint. Returns undefined when there is no
118
- * key or no project for the product, leaving the error plane inert rather than
119
- * posting into the wrong one. The key is NOT baked here: it is the value the
120
- * surface resolved (an explicit `ingestKey`, or the KMS-sourced
121
- * NEXT_PUBLIC_PUBLISHABLE_KEY the build inlines) — the ONE live source, never a
122
- * literal committed beside the code. */
123
- declare function dsnForProduct(product: string | undefined, key: string | undefined): string | undefined;
124
-
125
- /** redactSecrets removes known secret shapes. Always applied. */
126
- declare function redactSecrets(s: string): string;
127
- /** scrubPII masks emails and IPs. Applied unless PII capture is enabled. */
128
- declare function scrubPII(s: string): string;
129
- /** scrubText applies the redaction policy to a free-text field. Input is capped
130
- * first: unbounded text is a denial-of-service surface, not just a size problem. */
131
- declare function scrubText(s: string | undefined, capturePII?: boolean): string;
132
-
133
- /**
134
- * digest is a stable 32-hex-char (128-bit) content hash, computed synchronously.
135
- *
136
- * Grouping keys are needed on the capture path, which is synchronous and may be
137
- * running inside an unload handler — SubtleCrypto is async and unavailable on
138
- * insecure origins, so it cannot be used here. This is FNV-1a run over four seeds
139
- * and concatenated. It is a GROUPING key, never a security boundary: it is not
140
- * collision-resistant against a chosen-input adversary, and nothing authorizes or
141
- * authenticates on it. The product treats the value as opaque.
142
- */
143
- declare function digest(s: string): string;
144
- /**
145
- * exceptionEntry builds the single `$exception_list` entry for a throwable.
146
- *
147
- * One entry, not a chain: `Error.cause` chaining is a distinct fact with its own
148
- * ordering rules, and emitting it wrongly is worse than not emitting it.
149
- */
150
- declare function exceptionEntry(err: unknown, opts: {
151
- handled: boolean;
152
- id: string;
153
- }): ExceptionEntry;
154
- /**
155
- * fingerprint is the issue grouping key.
156
- *
157
- * Keyed on exception type plus each in-app frame's function and source — the same
158
- * pieces the server-side grouper records ("Exception Type", "Resolved function
159
- * name", "Source file name"). Deliberately NOT the message: `Loading chunk 3324
160
- * failed` and `Loading chunk 998 failed` are one bug, and grouping on message is
161
- * precisely the mistake that made every distinct error string its own event name.
162
- *
163
- * Falls back to the type alone when no in-app frame survived, which keeps
164
- * stackless errors (`Script error.`, cross-origin) in one issue instead of
165
- * scattering them.
166
- */
167
- declare function fingerprint(entry: ExceptionEntry): string;
168
- /**
169
- * exceptionProperties builds the full `$exception_*` property bag for one captured
170
- * throwable — everything Error Tracking reads off the event.
171
- *
172
- * The denormalized arrays are ordered like `frames`: last element is the throw
173
- * site, which is the element the issue list indexes at -1 for its source/function
174
- * columns.
175
- */
176
- declare function exceptionProperties(err: unknown, opts: {
177
- handled: boolean;
178
- id: string;
179
- level?: SentryLevel;
180
- }): ExceptionProperties;
181
-
182
- interface GoalDef {
183
- /** Human label shown in Insights. */
184
- label: string;
185
- /** The event whose occurrence counts as the goal conversion. */
186
- event: string;
187
- /** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
188
- funnelId?: FunnelId;
189
- /** The ordered event names of `funnelId`, derived — never hand-written. */
190
- funnel?: string[];
191
- /** Optional property equality filter that qualifies the conversion. */
192
- filter?: {
193
- property: string;
194
- equals: string;
195
- };
196
- }
197
- declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
198
- interface CohortDef {
199
- /** The hanzo.events column the cohort dimension maps to. */
200
- field: string;
201
- label: string;
202
- }
203
- declare const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef>;
204
-
205
- /** parseAttribution reads UTM params + ref/refCode from a query string and pairs
206
- * them with the referrer. `search` is a location.search value ("?utm_source=…"). */
207
- declare function parseAttribution(search: string, referrer: string): Attribution;
208
- /** deriveChannel classifies the visit: paid | referral | social | organic | direct. */
209
- declare function deriveChannel(a: Attribution): string;
210
- /** hostOf extracts a bare lowercase host from a URL; "" when unparseable. */
211
- declare function hostOf(raw?: string): string;
212
- /** hasAttribution reports whether anything was captured (so we don't persist an
213
- * empty first-touch that would shadow a later real one). */
214
- declare function hasAttribution(a: Attribution): boolean;
215
- /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
216
- declare function isoWeek(d: Date): string;
217
-
218
- /**
219
- * Which org owns a host's telemetry.
220
- *
221
- * A surface should not have to LEARN its key. It already knows the domain it is
222
- * served from, and a domain belongs to exactly one brand, so the key is derivable
223
- * and asking a surface to carry one is asking it to restate something already
224
- * true. This module answers `orgOf(location.hostname)` and hands back that org's
225
- * publishable key, which is what lets a static export report correctly while
226
- * configuring nothing.
227
- *
228
- * This is the SAME question `hanzo.id` answers at runtime for the identity hosts
229
- * (`pkgs/shared/src/ingest.ts`): one image serves every brand, so the key cannot
230
- * be a property of the build. It was, and the bill is on record — Lux's, Zoo's,
231
- * Osage's and Pars' visitors were all filed in HANZO's project, because one
232
- * build-time key was inlined for every brand. That is the white-label boundary
233
- * crossed in the direction that shows up latest. The marketing sites carry the
234
- * identical defect in its other form: each commits its OWN key literal, so the
235
- * fleet holds N copies of a value with one source, and a site added tomorrow
236
- * reports nothing until somebody remembers to paste one in.
237
- *
238
- * A `pk-` is PUBLISHABLE: it authorizes a write into one org and mints no reading
239
- * principal. It is readable in devtools on every deployed page that sends an
240
- * event, so stating it here exposes nothing that serving the page did not. What
241
- * it does buy is that correct attribution becomes the DEFAULT rather than
242
- * something each surface opts into.
243
- *
244
- * Keyed by ORG, never by host: a brand's facts repeat on every alias it owns
245
- * (`lux.id`, `id.lux.network`), and a key stated per host would have to be
246
- * repeated too — so the day someone adds an alias and forgets the key, that host
247
- * silently stops reporting. Stated once per org, a new alias inherits its brand's
248
- * key by naming its brand's domain, which is all an alias ever says.
249
- */
250
- /** One publishable key per org. */
251
- type Keyring = Readonly<Record<string, string>>;
252
- /**
253
- * Each org's registrable domains. A host matches a domain when it IS that domain
254
- * or a subdomain of it, so `explore.lux.network` needs no entry of its own.
255
- *
256
- * An org absent from this table resolves to undefined, and that is deliberate:
257
- * `osage`, `pars` and `bootnode` have no project of their own yet, and reporting
258
- * NOTHING is the honest answer. Filing them under a brand that is not theirs is
259
- * the defect this module exists to prevent — it is silent, it reads as working,
260
- * and it is only visible later in someone else's warehouse.
261
- */
262
- declare const ORG_DOMAIN: Readonly<Record<string, readonly string[]>>;
263
- /**
264
- * Each org's publishable key — the same values `universe`'s `SPA_INGEST_KEYRING`
265
- * serves to the identity hosts, which are each brand's own insights team token.
266
- * Add an org here the day its project exists, never before.
267
- */
268
- declare const ORG_KEY: Keyring;
269
- /**
270
- * The org that owns a host, or undefined when no brand claims it.
271
- *
272
- * Longest match wins, so a brand owning both a domain and a subdomain of another
273
- * brand's cannot be decided by table order. Pure and total.
274
- */
275
- declare function orgOf(host: string): string | undefined;
276
- /**
277
- * The publishable key for a host, or undefined when there is not exactly one to
278
- * give.
279
- *
280
- * Deliberately WITHOUT a fallback: returning Hanzo's key for an unrecognised host
281
- * is precisely the defect described above. Undefined is the honest answer and the
282
- * caller reports nothing.
283
- *
284
- * `keyring` is a parameter so the identity runtime — which receives its keyring
285
- * from `/config.json` because one image serves every brand — resolves through
286
- * this SAME function rather than a second copy of it.
287
- */
288
- declare function keyFor(host: string, keyring?: Keyring): string | undefined;
289
- /** The key for the page this code is running on; undefined off a browser. */
290
- declare function keyForPage(keyring?: Keyring): string | undefined;
291
-
292
- export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, GOALS, type GoalDef, type Keyring, ORG_DOMAIN, ORG_KEY, PRODUCT_PROJECT, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, orgOf, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
1
+ export { Analytics, createAnalytics, VERSION, getCohort, getFirstTouch } from './core';
2
+ export { parseDsn, buildSentryEvent, buildEnvelope, framesFromStack } from './sentry';
3
+ export { uuidv7, uuidv7Time } from './uid';
4
+ export { PRODUCT_PROJECT, dsnForProduct } from './dsn';
5
+ export type { ErrorIdentity } from './sentry';
6
+ export { scrubText, redactSecrets, scrubPII } from './scrub';
7
+ export { EVENTS, EXCEPTION, PAGEVIEW } from './events';
8
+ export { exceptionEntry, exceptionProperties, fingerprint, digest } from './exception';
9
+ export type { EventName } from './events';
10
+ export { GOALS, COHORTS } from './goals';
11
+ export type { GoalDef, CohortDef } from './goals';
12
+ export { FUNNELS, PRODUCTS, eventsOf } from './funnels';
13
+ export type { FunnelDef, FunnelStep, FunnelId, ProductId } from './funnels';
14
+ export { parseAttribution, deriveChannel, hasAttribution, hostOf, isoWeek, } from './attribution';
15
+ export type { AnalyticsConfig, Attribution, CaptureErrorOptions, Cohort, Dsn, EventKind, Exception, ExceptionEntry, ExceptionFrame, ExceptionProperties, SentryEvent, SentryFrame, SentryLevel, Transport, WireEvent, } from './types';
16
+ /** Which org owns a host's telemetrythe resolution that lets a surface report
17
+ * correctly while configuring nothing. `keyFor` takes an optional keyring so a
18
+ * runtime that receives one (hanzo.id serves every brand from one image) resolves
19
+ * through this same function rather than a second copy of it. */
20
+ export { ORG_DOMAIN, ORG_KEY, orgOf, keyFor, keyForPage } from './org';
21
+ export type { Keyring } from './org';
22
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,292 +1,22 @@
1
- import { C as Cohort, A as Attribution, S as SentryEvent, D as Dsn, a as CaptureErrorOptions, b as SentryFrame, E as ExceptionEntry, c as SentryLevel, d as ExceptionProperties } from './core-CIhI2R7S.js';
2
- export { e as Analytics, f as AnalyticsConfig, g as EventKind, h as Exception, i as ExceptionFrame, T as Transport, W as WireEvent, j as createAnalytics } from './core-CIhI2R7S.js';
3
- import { FunnelId } from '@hanzo/events';
4
- export { EVENTS, EXCEPTION, EventName, FUNNELS, FunnelDef, FunnelId, FunnelStep, PAGEVIEW, PRODUCTS, ProductId, eventsOf } from '@hanzo/events';
5
-
6
- /** Read the persisted first-touch attribution. */
7
- declare function getFirstTouch(): Attribution | undefined;
8
- /** Read persisted cohort dimensions. */
9
- declare function getCohort(): Cohort | undefined;
10
-
11
- declare const VERSION = "0.3.35";
12
-
13
- /**
14
- * parseDsn parses "https://<version>:<hmac>@<host>/v1/event/<projectId>" into its
15
- * public key + the derived envelope ingest URL. The key (which itself contains a
16
- * ':') is taken verbatim as the userinfo we do NOT split it as user:pass. The
17
- * key rides ?sentry_key= (not the DSN in the body) because that is the credential
18
- * channel the server trusts AND the only one sendBeacon can carry on unload.
19
- * Returns null for anything malformed (fail-safe: the caller then stays inert).
20
- */
21
- declare function parseDsn(dsn: string | undefined | null): Dsn | null;
22
- /**
23
- * framesFromStack parses a browser Error.stack into Sentry frames, OLDEST-FIRST
24
- * (Sentry orders caller->callee; the crash site is last — matching the server's
25
- * pickCrashFrame). Handles both V8 ("at fn (file:li:co)") and
26
- * Firefox/Safari ("fn@file:li:co"). Unparseable lines are skipped.
27
- */
28
- declare function framesFromStack(stack: string | undefined): SentryFrame[];
29
- /** Identity carried onto every error event — the SAME ids analytics uses. */
30
- interface ErrorIdentity {
31
- /** OIDC sub (post-identify) or anon id. NEVER email/PII. */
32
- userId?: string;
33
- sessionId?: string;
34
- product?: string;
35
- release?: string;
36
- environment?: string;
37
- }
38
- interface BuildEventInput {
39
- error: unknown;
40
- options?: CaptureErrorOptions;
41
- identity: ErrorIdentity;
42
- capturePII?: boolean;
43
- /** Injectable for deterministic tests. */
44
- now?: number;
45
- id?: string;
46
- }
47
- /**
48
- * buildSentryEvent turns a throwable + identity into a Sentry `event`. The message
49
- * (the leak surface) is scrubbed client-side; the user is ONLY the stable subject
50
- * id — never email/username/ip. Level defaults to error, or fatal for uncaught.
51
- */
52
- declare function buildSentryEvent(input: BuildEventInput): SentryEvent;
53
- /**
54
- * buildEnvelope frames a Sentry event into a newline-delimited envelope:
55
- *
56
- * {"event_id","dsn","sent_at"}\n
57
- * {"type":"event","content_type":"application/json","length":N}\n
58
- * <event json>\n
59
- *
60
- * The item is length-delimited (N = UTF-8 byte length) — the framing the server's
61
- * parseEnvelope reads first (falling back to newline-delimited otherwise).
62
- */
63
- declare function buildEnvelope(event: SentryEvent, dsn: Dsn, sentAt?: string): string;
64
-
65
- // Types for anon.js, which is hand-written ES5 rather than TypeScript because
66
- // The door's hosted tag inlines it VERBATIM and has no compiler.
67
- // The declarations are here so the bundled client still imports it typed.
68
-
69
- /** Mints a time-ordered UUIDv7 (RFC 9562 §5.7) for `now` in epoch milliseconds. */
70
- declare function hzUuidv7(now?: number): string
71
-
72
- /**
73
- * uuidv7 mints a time-ordered UUIDv7 for `now` (epoch milliseconds).
74
- *
75
- * Two ids minted in the same millisecond sort arbitrarily between themselves; ids
76
- * from different milliseconds sort by time, lexically and numerically alike.
77
- */
78
-
79
- /** The millisecond timestamp a v7 id was minted at — the inverse of uuidv7. */
80
- declare function uuidv7Time(id: string): number;
81
-
82
- /**
83
- * The product → Sentinel DSN registry.
84
- *
85
- * An app declares WHAT it is (`product: 'console'`); this module knows WHERE its
86
- * errors go. That split is the whole point: no surface has to learn a DSN, carry
87
- * a build argument, or grow a config file to report errors — declaring the
88
- * product it already declares is enough.
89
- *
90
- * A Sentinel DSN is PUBLIC by construction. It ships inside the client bundle and
91
- * is readable in devtools on any deployed page, and it grants exactly one
92
- * capability: submitting new events. It cannot read issues, projects, or any
93
- * other data. So committing it is not leaking a secret — it is recording a public
94
- * identifier next to the code that needs it. (Contrast the server-side collector
95
- * DSN in the `team-analytics-sentry` Secret, which is HMAC-derived and revocable
96
- * precisely because a server-side credential is NOT public.)
97
- *
98
- * Why a literal map instead of deriving `hanzo-${product}`: the projects predate
99
- * this registry and do not derive cleanly — `site` lives in `hanzo-ai`, not
100
- * `hanzo-site`. An explicit map is honest about that; a derivation rule plus an
101
- * exception table is the same data with a trap in it.
102
- *
103
- * Projects are org-scoped and named `<org>-<app>`. To add one: create the project
104
- * (POST /v1/sentinel/projects with X-Org-Id), then add its `dsn` here keyed by the
105
- * product name the app passes to `createAnalytics`.
106
- */
107
- /** PRODUCT_PROJECT maps a `product` to its Sentinel project id. The DSN's KEY is no
108
- * longer a per-project secret — it is the ONE org publishable key (below), so a
109
- * surface's errors ride the SAME key its events do. The id only names WHICH
110
- * project the errors group under, and cloud auto-provisions that project on first
111
- * keyed ingest, so a new id needs nothing minted. `site` lives in the `hanzo-ai`
112
- * project — an explicit map, because the projects predate this and do not derive
113
- * cleanly from the product name. */
114
- declare const PRODUCT_PROJECT: Readonly<Record<string, string>>;
115
- /** dsnForProduct builds the product's Sentinel DSN from the caller's resolved
116
- * publishable `key` and the product's project — the SAME key the event stream
117
- * carries, at the product's envelope endpoint. Returns undefined when there is no
118
- * key or no project for the product, leaving the error plane inert rather than
119
- * posting into the wrong one. The key is NOT baked here: it is the value the
120
- * surface resolved (an explicit `ingestKey`, or the KMS-sourced
121
- * NEXT_PUBLIC_PUBLISHABLE_KEY the build inlines) — the ONE live source, never a
122
- * literal committed beside the code. */
123
- declare function dsnForProduct(product: string | undefined, key: string | undefined): string | undefined;
124
-
125
- /** redactSecrets removes known secret shapes. Always applied. */
126
- declare function redactSecrets(s: string): string;
127
- /** scrubPII masks emails and IPs. Applied unless PII capture is enabled. */
128
- declare function scrubPII(s: string): string;
129
- /** scrubText applies the redaction policy to a free-text field. Input is capped
130
- * first: unbounded text is a denial-of-service surface, not just a size problem. */
131
- declare function scrubText(s: string | undefined, capturePII?: boolean): string;
132
-
133
- /**
134
- * digest is a stable 32-hex-char (128-bit) content hash, computed synchronously.
135
- *
136
- * Grouping keys are needed on the capture path, which is synchronous and may be
137
- * running inside an unload handler — SubtleCrypto is async and unavailable on
138
- * insecure origins, so it cannot be used here. This is FNV-1a run over four seeds
139
- * and concatenated. It is a GROUPING key, never a security boundary: it is not
140
- * collision-resistant against a chosen-input adversary, and nothing authorizes or
141
- * authenticates on it. The product treats the value as opaque.
142
- */
143
- declare function digest(s: string): string;
144
- /**
145
- * exceptionEntry builds the single `$exception_list` entry for a throwable.
146
- *
147
- * One entry, not a chain: `Error.cause` chaining is a distinct fact with its own
148
- * ordering rules, and emitting it wrongly is worse than not emitting it.
149
- */
150
- declare function exceptionEntry(err: unknown, opts: {
151
- handled: boolean;
152
- id: string;
153
- }): ExceptionEntry;
154
- /**
155
- * fingerprint is the issue grouping key.
156
- *
157
- * Keyed on exception type plus each in-app frame's function and source — the same
158
- * pieces the server-side grouper records ("Exception Type", "Resolved function
159
- * name", "Source file name"). Deliberately NOT the message: `Loading chunk 3324
160
- * failed` and `Loading chunk 998 failed` are one bug, and grouping on message is
161
- * precisely the mistake that made every distinct error string its own event name.
162
- *
163
- * Falls back to the type alone when no in-app frame survived, which keeps
164
- * stackless errors (`Script error.`, cross-origin) in one issue instead of
165
- * scattering them.
166
- */
167
- declare function fingerprint(entry: ExceptionEntry): string;
168
- /**
169
- * exceptionProperties builds the full `$exception_*` property bag for one captured
170
- * throwable — everything Error Tracking reads off the event.
171
- *
172
- * The denormalized arrays are ordered like `frames`: last element is the throw
173
- * site, which is the element the issue list indexes at -1 for its source/function
174
- * columns.
175
- */
176
- declare function exceptionProperties(err: unknown, opts: {
177
- handled: boolean;
178
- id: string;
179
- level?: SentryLevel;
180
- }): ExceptionProperties;
181
-
182
- interface GoalDef {
183
- /** Human label shown in Insights. */
184
- label: string;
185
- /** The event whose occurrence counts as the goal conversion. */
186
- event: string;
187
- /** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
188
- funnelId?: FunnelId;
189
- /** The ordered event names of `funnelId`, derived — never hand-written. */
190
- funnel?: string[];
191
- /** Optional property equality filter that qualifies the conversion. */
192
- filter?: {
193
- property: string;
194
- equals: string;
195
- };
196
- }
197
- declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
198
- interface CohortDef {
199
- /** The hanzo.events column the cohort dimension maps to. */
200
- field: string;
201
- label: string;
202
- }
203
- declare const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef>;
204
-
205
- /** parseAttribution reads UTM params + ref/refCode from a query string and pairs
206
- * them with the referrer. `search` is a location.search value ("?utm_source=…"). */
207
- declare function parseAttribution(search: string, referrer: string): Attribution;
208
- /** deriveChannel classifies the visit: paid | referral | social | organic | direct. */
209
- declare function deriveChannel(a: Attribution): string;
210
- /** hostOf extracts a bare lowercase host from a URL; "" when unparseable. */
211
- declare function hostOf(raw?: string): string;
212
- /** hasAttribution reports whether anything was captured (so we don't persist an
213
- * empty first-touch that would shadow a later real one). */
214
- declare function hasAttribution(a: Attribution): boolean;
215
- /** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
216
- declare function isoWeek(d: Date): string;
217
-
218
- /**
219
- * Which org owns a host's telemetry.
220
- *
221
- * A surface should not have to LEARN its key. It already knows the domain it is
222
- * served from, and a domain belongs to exactly one brand, so the key is derivable
223
- * and asking a surface to carry one is asking it to restate something already
224
- * true. This module answers `orgOf(location.hostname)` and hands back that org's
225
- * publishable key, which is what lets a static export report correctly while
226
- * configuring nothing.
227
- *
228
- * This is the SAME question `hanzo.id` answers at runtime for the identity hosts
229
- * (`pkgs/shared/src/ingest.ts`): one image serves every brand, so the key cannot
230
- * be a property of the build. It was, and the bill is on record — Lux's, Zoo's,
231
- * Osage's and Pars' visitors were all filed in HANZO's project, because one
232
- * build-time key was inlined for every brand. That is the white-label boundary
233
- * crossed in the direction that shows up latest. The marketing sites carry the
234
- * identical defect in its other form: each commits its OWN key literal, so the
235
- * fleet holds N copies of a value with one source, and a site added tomorrow
236
- * reports nothing until somebody remembers to paste one in.
237
- *
238
- * A `pk-` is PUBLISHABLE: it authorizes a write into one org and mints no reading
239
- * principal. It is readable in devtools on every deployed page that sends an
240
- * event, so stating it here exposes nothing that serving the page did not. What
241
- * it does buy is that correct attribution becomes the DEFAULT rather than
242
- * something each surface opts into.
243
- *
244
- * Keyed by ORG, never by host: a brand's facts repeat on every alias it owns
245
- * (`lux.id`, `id.lux.network`), and a key stated per host would have to be
246
- * repeated too — so the day someone adds an alias and forgets the key, that host
247
- * silently stops reporting. Stated once per org, a new alias inherits its brand's
248
- * key by naming its brand's domain, which is all an alias ever says.
249
- */
250
- /** One publishable key per org. */
251
- type Keyring = Readonly<Record<string, string>>;
252
- /**
253
- * Each org's registrable domains. A host matches a domain when it IS that domain
254
- * or a subdomain of it, so `explore.lux.network` needs no entry of its own.
255
- *
256
- * An org absent from this table resolves to undefined, and that is deliberate:
257
- * `osage`, `pars` and `bootnode` have no project of their own yet, and reporting
258
- * NOTHING is the honest answer. Filing them under a brand that is not theirs is
259
- * the defect this module exists to prevent — it is silent, it reads as working,
260
- * and it is only visible later in someone else's warehouse.
261
- */
262
- declare const ORG_DOMAIN: Readonly<Record<string, readonly string[]>>;
263
- /**
264
- * Each org's publishable key — the same values `universe`'s `SPA_INGEST_KEYRING`
265
- * serves to the identity hosts, which are each brand's own insights team token.
266
- * Add an org here the day its project exists, never before.
267
- */
268
- declare const ORG_KEY: Keyring;
269
- /**
270
- * The org that owns a host, or undefined when no brand claims it.
271
- *
272
- * Longest match wins, so a brand owning both a domain and a subdomain of another
273
- * brand's cannot be decided by table order. Pure and total.
274
- */
275
- declare function orgOf(host: string): string | undefined;
276
- /**
277
- * The publishable key for a host, or undefined when there is not exactly one to
278
- * give.
279
- *
280
- * Deliberately WITHOUT a fallback: returning Hanzo's key for an unrecognised host
281
- * is precisely the defect described above. Undefined is the honest answer and the
282
- * caller reports nothing.
283
- *
284
- * `keyring` is a parameter so the identity runtime — which receives its keyring
285
- * from `/config.json` because one image serves every brand — resolves through
286
- * this SAME function rather than a second copy of it.
287
- */
288
- declare function keyFor(host: string, keyring?: Keyring): string | undefined;
289
- /** The key for the page this code is running on; undefined off a browser. */
290
- declare function keyForPage(keyring?: Keyring): string | undefined;
291
-
292
- export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, GOALS, type GoalDef, type Keyring, ORG_DOMAIN, ORG_KEY, PRODUCT_PROJECT, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, orgOf, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
1
+ export { Analytics, createAnalytics, VERSION, getCohort, getFirstTouch } from './core';
2
+ export { parseDsn, buildSentryEvent, buildEnvelope, framesFromStack } from './sentry';
3
+ export { uuidv7, uuidv7Time } from './uid';
4
+ export { PRODUCT_PROJECT, dsnForProduct } from './dsn';
5
+ export type { ErrorIdentity } from './sentry';
6
+ export { scrubText, redactSecrets, scrubPII } from './scrub';
7
+ export { EVENTS, EXCEPTION, PAGEVIEW } from './events';
8
+ export { exceptionEntry, exceptionProperties, fingerprint, digest } from './exception';
9
+ export type { EventName } from './events';
10
+ export { GOALS, COHORTS } from './goals';
11
+ export type { GoalDef, CohortDef } from './goals';
12
+ export { FUNNELS, PRODUCTS, eventsOf } from './funnels';
13
+ export type { FunnelDef, FunnelStep, FunnelId, ProductId } from './funnels';
14
+ export { parseAttribution, deriveChannel, hasAttribution, hostOf, isoWeek, } from './attribution';
15
+ export type { AnalyticsConfig, Attribution, CaptureErrorOptions, Cohort, Dsn, EventKind, Exception, ExceptionEntry, ExceptionFrame, ExceptionProperties, SentryEvent, SentryFrame, SentryLevel, Transport, WireEvent, } from './types';
16
+ /** Which org owns a host's telemetrythe resolution that lets a surface report
17
+ * correctly while configuring nothing. `keyFor` takes an optional keyring so a
18
+ * runtime that receives one (hanzo.id serves every brand from one image) resolves
19
+ * through this same function rather than a second copy of it. */
20
+ export { ORG_DOMAIN, ORG_KEY, orgOf, keyFor, keyForPage } from './org';
21
+ export type { Keyring } from './org';
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AACtF,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACrF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACtF,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACxC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACvD,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC3E,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,MAAM,EACN,OAAO,GACR,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,eAAe,EACf,WAAW,EACX,mBAAmB,EACnB,MAAM,EACN,GAAG,EACH,SAAS,EACT,SAAS,EACT,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,GACV,MAAM,SAAS,CAAA;AAEhB;;;kEAGkE;AAClE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AACtE,YAAY,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA"}