@hanzo/event 0.3.36 → 0.3.38

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 (60) hide show
  1. package/README.md +8 -8
  2. package/TAXONOMY.md +1 -1
  3. package/dist/attribution.d.ts +14 -0
  4. package/dist/attribution.d.ts.map +1 -0
  5. package/dist/core.d.ts +102 -0
  6. package/dist/core.d.ts.map +1 -0
  7. package/dist/dsn.d.ts +43 -0
  8. package/dist/dsn.d.ts.map +1 -0
  9. package/dist/events.d.ts +21 -0
  10. package/dist/events.d.ts.map +1 -0
  11. package/dist/exception.d.ts +50 -0
  12. package/dist/exception.d.ts.map +1 -0
  13. package/dist/funnels.d.ts +14 -0
  14. package/dist/funnels.d.ts.map +1 -0
  15. package/dist/goals.d.ts +24 -0
  16. package/dist/goals.d.ts.map +1 -0
  17. package/dist/index.cjs +11 -11
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +22 -292
  20. package/dist/index.d.ts +22 -292
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.mjs +11 -11
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/org.d.ts +77 -0
  25. package/dist/org.d.ts.map +1 -0
  26. package/dist/react.cjs +11 -11
  27. package/dist/react.cjs.map +1 -1
  28. package/dist/react.d.cts +13 -14
  29. package/dist/react.d.ts +13 -14
  30. package/dist/react.d.ts.map +1 -0
  31. package/dist/react.mjs +11 -11
  32. package/dist/react.mjs.map +1 -1
  33. package/dist/scrub.d.ts +20 -0
  34. package/dist/scrub.d.ts.map +1 -0
  35. package/dist/sentry.d.ts +62 -0
  36. package/dist/sentry.d.ts.map +1 -0
  37. package/dist/stack.d.ts +25 -0
  38. package/dist/stack.d.ts.map +1 -0
  39. package/dist/storage.d.ts +32 -0
  40. package/dist/storage.d.ts.map +1 -0
  41. package/dist/{core-CIhI2R7S.d.cts → types.d.ts} +21 -119
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/uid.d.ts +10 -0
  44. package/dist/uid.d.ts.map +1 -0
  45. package/dist/version.d.ts +2 -0
  46. package/dist/version.d.ts.map +1 -0
  47. package/package.json +12 -12
  48. package/src/anon.d.ts +1 -1
  49. package/src/anon.js +3 -3
  50. package/src/anon.test.ts +3 -3
  51. package/src/core.test.ts +6 -6
  52. package/src/core.ts +11 -11
  53. package/src/events.ts +1 -1
  54. package/src/exception.ts +1 -1
  55. package/src/org.ts +7 -4
  56. package/src/stack.ts +1 -1
  57. package/src/types.ts +3 -3
  58. package/src/uid.ts +2 -2
  59. package/src/version.ts +1 -1
  60. package/dist/core-CIhI2R7S.d.ts +0 -380
package/src/core.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // planes, sharing one session and one identity:
3
3
  //
4
4
  // 1. EVENT STREAM — buffered pageview/event/identify/group, flushed as ONE
5
- // batch to the Hanzo Cloud front door:
5
+ // batch to the Hanzo Cloud entry point:
6
6
  // POST {host}/v1/event body: { batch: [Event, …] } -> { accepted, dropped }
7
7
  // Cloud resolves the tenant server-side (validated session, or the signed
8
8
  // publishable key) and stamps it; the client NEVER sends the org.
@@ -17,20 +17,20 @@
17
17
  // GET /v1/errors) — that is product signal, not error tracking. There is no
18
18
  // server-side fan-out from /v1/event into Sentry; without the envelope below,
19
19
  // nothing ever reaches sentry.hanzo.ai. An earlier revision of this file claimed
20
- // the one door was "lensed server-side into … error tracking (sentry)". It was
20
+ // the one entry point was "lensed server-side into … error tracking (sentry)". It was
21
21
  // wrong, and it silently cost the fleet all of its error telemetry.
22
22
  //
23
23
  // The error plane is inert (fail-safe) when no DSN is configured: nothing is
24
24
  // sent, nothing throws, and the event stream is unaffected.
25
25
  //
26
- // Auth is orthogonal — the SAME body to the SAME door, differing only in how the
26
+ // Auth is orthogonal — the SAME body to the SAME endpoint, differing only in how the
27
27
  // caller proves its tenant:
28
28
  //
29
29
  // • cookie/session app (host:'') — same-origin credentials ride the request.
30
30
  // • bearer app (getToken) — Authorization: Bearer <jwt>.
31
31
  // • publishable-key app (ingestKey: 'pk-…') — ?ingest_key=pk-… on the query, on
32
32
  // the fetch and the beacon alike, with a text/plain body. Write-only and safe
33
- // to ship in a bundle; the door resolves it to an org server-side. The query
33
+ // to ship in a bundle; the endpoint resolves it to an org server-side. The query
34
34
  // is the carrier neither send needs a header for, which is what keeps both
35
35
  // CORS-simple and therefore sendable from a customer's own origin.
36
36
  //
@@ -81,13 +81,13 @@ import { VERSION } from './version'
81
81
 
82
82
  export { VERSION }
83
83
 
84
- const EVENT_PATH = '/v1/event' // the ONE canonical ingestion front door
84
+ const EVENT_PATH = '/v1/event' // the ONE canonical ingestion entry point
85
85
  const DEFAULT_HOST = 'https://api.hanzo.ai' // the one edge; cookie apps pass host:''
86
86
  const ENVELOPE_CONTENT_TYPE = 'application/x-sentry-envelope'
87
87
  // The beacon body's type. text/plain is CORS-SAFELISTED, which is the whole
88
88
  // property: a safelisted type makes the POST a SIMPLE request, and a simple
89
89
  // request needs no preflight. An unloading document does not get a second round
90
- // trip, so cross-origin a preflighted beacon is never sent at all. The door reads
90
+ // trip, so cross-origin a preflighted beacon is never sent at all. The endpoint reads
91
91
  // the raw body and dispatches on its first non-space byte, so the type names the
92
92
  // CORS class and nothing else.
93
93
  const BEACON_CONTENT_TYPE = 'text/plain'
@@ -292,10 +292,10 @@ export class Analytics {
292
292
  // so before this the only way to report was to commit a key literal, and the
293
293
  // fleet grew one copy per site of a value with a single source.
294
294
  //
295
- // A surface no brand claims still has no key, and the door REFUSES an
295
+ // A surface no brand claims still has no key, and the edge REFUSES an
296
296
  // unattributed event (401 `ingest_key_required`) — the reserved `$public`
297
297
  // tenant that once caught keyless beacons is retired, and anonymous ingest is
298
- // refused at every door on every brand host. So there is no quiet fallback to
298
+ // refused at every edge on every brand host. So there is no quiet fallback to
299
299
  // rely on: an event lands in the org a credential names, or it does not land.
300
300
  ingestKey:
301
301
  config.ingestKey ?? readEnv('NEXT_PUBLIC_PUBLISHABLE_KEY') ?? keyForPage(),
@@ -497,7 +497,7 @@ export class Analytics {
497
497
  }
498
498
 
499
499
  /** flush drains the buffer to the server as ONE batch through the ONE ingest
500
- * front door POST /v1/event, body { batch: [Event…] }. beacon=true selects the
500
+ * endpoint POST /v1/event, body { batch: [Event…] }. beacon=true selects the
501
501
  * unload-safe transport. Auth is orthogonal to the wire:
502
502
  *
503
503
  * • publishable key set → rides ?ingest_key=pk-… on both sends, keeping each
@@ -513,7 +513,7 @@ export class Analytics {
513
513
  this.queue = []
514
514
  this.clearTimer()
515
515
 
516
- // A publishable key and a bearer JWT are mutually exclusive doors, and the
516
+ // A publishable key and a bearer JWT are mutually exclusive credentials, and the
517
517
  // BEARER WINS. It names a real principal and resolves to THAT person's org;
518
518
  // a pk- names one org for everybody holding it. So the key is what attributes
519
519
  // a visitor nobody has vouched for, and it must never displace someone who
@@ -672,7 +672,7 @@ export class Analytics {
672
672
  //
673
673
  // A page has ONE event stream per (host, product): one queue, one anon id, one
674
674
  // credential. Two clients on it split the batch and double the pageview, and
675
- // the half built by the caller that had no key is refused at the door — so the
675
+ // the half built by the caller that had no key is refused at the edge — so the
676
676
  // same page is both over- and under-counted, and neither number says so.
677
677
  //
678
678
  // Two callers ask for that stream on any real page and both are right to: the
package/src/events.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * These names used to be declared here. They moved to @hanzo/events — the
5
5
  * plural package is the catalog (many events, what each means, what each
6
6
  * carries), this one is the client (one call sends one event). Splitting them
7
- * that way is what lets the ingest door read the same catalog: the door is Go
7
+ * that way is what lets the ingest endpoint read the same catalog: it is Go
8
8
  * and cannot import TypeScript, so @hanzo/events also ships the data as JSON,
9
9
  * and there is still exactly one definition.
10
10
  *
package/src/exception.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  // WHY THE FULLY-DERIVED SHAPE. Upstream, a server stage (Cymbal) symbolicates
13
13
  // frames and computes `$exception_fingerprint`, `$exception_types`, `_values`,
14
14
  // `_sources`, `_functions`, then REPLACES the property bag. That stage is not in
15
- // Hanzo's path: the door writes `event.fact` and a materialized view projects it
15
+ // Hanzo's path: the ingest endpoint writes `event.fact` and a materialized view projects it
16
16
  // into `insights.events`, so nothing between the client and the warehouse derives
17
17
  // anything. Whatever the product reads, the client has to have sent. Two
18
18
  // consequences are load-bearing rather than cosmetic:
package/src/org.ts CHANGED
@@ -72,18 +72,21 @@ export const ORG_DOMAIN: Readonly<Record<string, readonly string[]>> = Object.fr
72
72
  'lux.fund',
73
73
  'lux.id',
74
74
  ]),
75
- zoo: Object.freeze(['zoo.ngo', 'zoo.network', 'zoolabs.id']),
75
+ zoo: Object.freeze(['zoo.ngo', 'zoo.network', 'zoolabs.id', 'zoolabs.io']),
76
76
  })
77
77
 
78
78
  /**
79
79
  * Each org's publishable key — the same values `universe`'s `SPA_INGEST_KEYRING`
80
80
  * serves to the identity hosts, which are each brand's own insights team token.
81
81
  * Add an org here the day its project exists, never before.
82
+ *
83
+ * A key is only as real as the project it names: recreate the project and this
84
+ * file moves with it, or every beacon answers 403 and reports nowhere.
82
85
  */
83
86
  export const ORG_KEY: Keyring = Object.freeze({
84
- hanzo: 'pk-rM_CdaF2MQckGCrla113SrR1oH4zvqN8xh2I95Z9tY8',
85
- lux: 'pk-gUZp6ZVfhJzSwK-rb4oLbVkpCnMBx5uSCpxf_5yEhQk',
86
- zoo: 'pk-3TKpKnERV9AQSsBUERWkZejC1O1mUxc1jRzsP3MPbs4',
87
+ hanzo: 'pk-bCcNBDOLwdIovV349jVGH1ShEOq7rAcaKLsX_bLnRMo',
88
+ lux: 'pk-GxCDhpEn89qajMyuMdMs6mcE4UVQBkv8F2swEKHC67Y',
89
+ zoo: 'pk-bS-xKESMhuhK_wfSmpRf1H_IH8A3x6Y5NV70TD7ycWE',
87
90
  })
88
91
 
89
92
  /** `pk-` is publishable; `sk-` is not, and there is no third thing. Checked at the
package/src/stack.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // These frames ride the error Event to POST /v1/event so the warehouse stores
6
6
  // WHERE a crash happened, not just that one did. Nothing here talks to Sentry:
7
- // the client has one door and one credential. The frame shape is deliberately the
7
+ // the client has one entry point and one credential. The frame shape is deliberately the
8
8
  // conventional one (function/filename/abs_path/lineno/colno/in_app) because it is
9
9
  // what every stack tool already speaks — including a future grouper built over
10
10
  // the warehouse.
package/src/types.ts CHANGED
@@ -100,7 +100,7 @@ export interface Cohort {
100
100
 
101
101
  /** One event as sent on the wire — the canonical Hanzo Cloud event. Maps 1:1 to
102
102
  * the cloud `CaptureEvent` (camelCase JSON keys); a batch of these is POSTed to
103
- * the ONE front door `/v1/event` as `{ batch: [WireEvent, …] }`. tenant/org is
103
+ * the ONE entry point `/v1/event` as `{ batch: [WireEvent, …] }`. tenant/org is
104
104
  * NEVER a field here — the server stamps it from the validated session/key. */
105
105
  export interface WireEvent {
106
106
  messageId: string
@@ -168,7 +168,7 @@ export interface AnalyticsConfig {
168
168
  * (the client then relies on same-origin credentials). */
169
169
  getToken?: () => string | undefined | null
170
170
  /** Publishable ingest key (pk-…). When set, the client attributes writes to the
171
- * ONE front door `/v1/event` with this key instead of a bearer/cookie: it rides
171
+ * ONE entry point `/v1/event` with this key instead of a bearer/cookie: it rides
172
172
  * Authorization: Bearer pk-… on fetch and ?ingest_key=pk-… on a headerless
173
173
  * page-unload beacon, so ANONYMOUS traffic is attributed and unload beacons work
174
174
  * without a bearer. The key is write-only — it attributes a write and never mints
@@ -184,7 +184,7 @@ export interface AnalyticsConfig {
184
184
  *
185
185
  * A surface with no key at all still reports for whoever is SIGNED IN (the
186
186
  * session credential attributes them), and drops every logged-out visitor: the
187
- * door refuses an unattributable write rather than filing it where its owner
187
+ * edge refuses an unattributable write rather than filing it where its owner
188
188
  * cannot read it. That failure is invisible from the page, which is why the key
189
189
  * belongs in the env next to the DSN and not in a checklist.
190
190
  *
package/src/uid.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // The implementation is `hzUuidv7` in ./anon.js and this file only re-exports it.
4
4
  // It lives there because the anonymous-id chain has to mint too, and that chain is
5
5
  // inlined verbatim by the distribution that has no bundler (the tag
6
- // the door hosts) — a minter here as well would be a second implementation, and
6
+ // served at /v1/event.js) — a minter here as well would be a second implementation, and
7
7
  // the version nibble it produces is exactly the thing that must never diverge.
8
8
  //
9
9
  // WHY NOT crypto.randomUUID(): it mints v4, whose 122 bits are pure entropy and
@@ -12,7 +12,7 @@
12
12
  // `fromUnixTimestamp(intDiv(toUInt64(bitShiftRight(session_id_v7, 80)), 1000))` —
13
13
  // so they admit only ids whose version nibble is 7
14
14
  // (`bitAnd(bitShiftRight(toUInt128(session_id), 76), 15) = 7`). A v4 session id is
15
- // not merely unordered there: it is DISCARDED at the door, silently, and the
15
+ // not merely unordered there: it is DISCARDED by that predicate, silently, and the
16
16
  // rollup stays empty forever. Minting v7 is the whole reason those rollups can
17
17
  // exist; it also clusters index writes by time instead of scattering them across
18
18
  // the keyspace.
package/src/version.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  // The library version, stamped on every event (`libraryVersion`) and on the
2
2
  // Sentry `sdk` block. It lives alone so `sentry.ts` can read it without importing
3
3
  // `core.ts` — core imports sentry, so the reverse would be an import cycle.
4
- export const VERSION = '0.3.36'
4
+ export const VERSION = '0.3.37'
@@ -1,380 +0,0 @@
1
- /** The event kinds — the closed set the server understands. `error` marks the
2
- * breadcrumb an exception leaves on the event stream (Cloud stamps
3
- * event_type='error' for the warehouse, GET /v1/errors). It does NOT reach the
4
- * Sentry dashboard — the envelope on the error plane does that. */
5
- type EventKind = 'pageview' | 'event' | 'identify' | 'group' | 'error';
6
- /** A captured exception as it rides the EVENT STREAM — Cloud folds it into
7
- * properties.$exception for the warehouse. The richer copy (parsed stack frames,
8
- * grouping, release) travels on the error plane as a Sentry envelope; see
9
- * AnalyticsConfig.dsn. */
10
- interface Exception {
11
- /** Constructor/class name, e.g. "TypeError". */
12
- type?: string;
13
- /** The error message. */
14
- message: string;
15
- /** Stack trace when available. */
16
- stack?: string;
17
- /** false = an unhandled/global error (window.onerror, unhandledrejection);
18
- * true = a caught error the app chose to report. Defaults true. */
19
- handled?: boolean;
20
- }
21
- /** One stack frame as Error Tracking renders it. The key names are the product's
22
- * POST-symbolication vocabulary (`mangled_name`/`source`/`line`/`column`), which
23
- * is what the issue view reads straight off `$exception_list`. */
24
- interface ExceptionFrame {
25
- /** "<hash>/<part>" — stable per code location; the frame's identity. */
26
- raw_id: string;
27
- /** The function name as it appears in the shipped bundle. */
28
- mangled_name: string;
29
- /** File/URL the frame is in. */
30
- source: string;
31
- line: number;
32
- column: number;
33
- /** First-party code. Frames without this are hidden by default in the product. */
34
- in_app: boolean;
35
- lang: string;
36
- /** Whether a symbol set mapped this frame back to original source. */
37
- resolved: boolean;
38
- resolve_failure?: string;
39
- resolved_name?: string | null;
40
- module?: string | null;
41
- }
42
- /** One exception in `$exception_list`. */
43
- interface ExceptionEntry {
44
- id: string;
45
- type: string;
46
- value: string;
47
- mechanism?: {
48
- type: 'generic';
49
- handled: boolean;
50
- synthetic?: boolean;
51
- };
52
- /** `type` MUST be 'resolved' — the renderer draws frames on no other value. */
53
- stacktrace?: {
54
- type: 'resolved';
55
- frames: ExceptionFrame[];
56
- };
57
- }
58
- /** The `$exception_*` property bag Error Tracking reads off a `$exception` event. */
59
- interface ExceptionProperties {
60
- $exception_list: ExceptionEntry[];
61
- /** Issue grouping key. An event without one is dropped by the issue query. */
62
- $exception_fingerprint: string;
63
- $exception_fingerprint_record: {
64
- type: 'manual';
65
- }[];
66
- $exception_type: string;
67
- $exception_message: string;
68
- $exception_level: SentryLevel;
69
- $exception_handled: boolean;
70
- $exception_synthetic: boolean;
71
- $exception_types: string[];
72
- $exception_values: string[];
73
- $exception_sources: string[];
74
- $exception_functions: string[];
75
- }
76
- /** First-touch marketing attribution, parsed once and persisted. */
77
- interface Attribution {
78
- utm: {
79
- source?: string;
80
- medium?: string;
81
- campaign?: string;
82
- term?: string;
83
- content?: string;
84
- };
85
- referrer?: string;
86
- refCode?: string;
87
- /** Derived acquisition channel: direct | organic | paid | social | referral. */
88
- channel?: string;
89
- }
90
- /** Cohort dimensions carried on every event once known (see goals.ts COHORTS). */
91
- interface Cohort {
92
- /** ISO week the person first signed up, e.g. "2026-W28". */
93
- signupWeek?: string;
94
- channel?: string;
95
- refCode?: string;
96
- }
97
- /** One event as sent on the wire — the canonical Hanzo Cloud event. Maps 1:1 to
98
- * the cloud `CaptureEvent` (camelCase JSON keys); a batch of these is POSTed to
99
- * the ONE front door `/v1/event` as `{ batch: [WireEvent, …] }`. tenant/org is
100
- * NEVER a field here — the server stamps it from the validated session/key. */
101
- interface WireEvent {
102
- messageId: string;
103
- type: EventKind;
104
- event?: string;
105
- timestamp: string;
106
- distinctId?: string;
107
- anonymousId?: string;
108
- personId?: string;
109
- sessionId?: string;
110
- product?: string;
111
- url?: string;
112
- path?: string;
113
- referrer?: string;
114
- utm?: Attribution['utm'];
115
- refCode?: string;
116
- channel?: string;
117
- groupId?: string;
118
- signupWeek?: string;
119
- productId?: string;
120
- quantity?: number;
121
- revenue?: number;
122
- currency?: string;
123
- /** Set on `type:'error'` events — the captured exception. Cloud lifts it into
124
- * properties.$exception (foldException) for the event warehouse. Not a Sentry
125
- * path: the envelope on the error plane is what feeds the dashboard. */
126
- error?: Exception;
127
- properties?: Record<string, unknown>;
128
- library?: string;
129
- libraryVersion?: string;
130
- }
131
- /** Injectable transports — overridden in tests; the default in core.ts uses fetch
132
- * (keepalive) and sendBeacon. A bearer JWT or a publishable pk_ key rides
133
- * Authorization on fetch; on a headerless beacon a publishable key rides the
134
- * ?ingest_key query. */
135
- interface Transport {
136
- /** Durable POST usable during page unload (fetch keepalive / sendBeacon).
137
- * `contentType` names the FETCH request's Content-Type — it defaults to
138
- * application/json, and the error plane sets application/x-sentry-envelope. A
139
- * beacon body carries a CORS-safelisted type so the POST stays a simple request;
140
- * that is a property of the transport, not a caller's choice. */
141
- send(url: string, body: string, opts: {
142
- beacon: boolean;
143
- token?: string;
144
- ingestKey?: string;
145
- contentType?: string;
146
- /** Surface non-OK / failed ingest on the console. Never on by default. */
147
- debug?: boolean;
148
- }): void;
149
- }
150
- interface AnalyticsConfig {
151
- /** Cloud base URL. Defaults to "https://api.hanzo.ai" (the one edge). Set to
152
- * same-origin ("") for cookie-auth apps served behind the same edge
153
- * (console/admin/chat), so the browser rides the session cookie. */
154
- host?: string;
155
- /** Emitting surface: console | chat | app | site | admin. */
156
- product: string;
157
- /** Bearer token provider for token-auth apps. Omit for cookie/session apps
158
- * (the client then relies on same-origin credentials). */
159
- getToken?: () => string | undefined | null;
160
- /** Publishable ingest key (pk-…). When set, the client attributes writes to the
161
- * ONE front door `/v1/event` with this key instead of a bearer/cookie: it rides
162
- * Authorization: Bearer pk-… on fetch and ?ingest_key=pk-… on a headerless
163
- * page-unload beacon, so ANONYMOUS traffic is attributed and unload beacons work
164
- * without a bearer. The key is write-only — it attributes a write and never mints
165
- * a reading principal — so it is safe to ship in a bundle. Mint one per org with
166
- * POST /v1/keys {"type":"publishable"}.
167
- *
168
- * Omit it and the client reads NEXT_PUBLIC_PUBLISHABLE_KEY from the inlined
169
- * build env, the same way `dsn` falls back — so a surface declares BOTH planes
170
- * in its build and neither needs code to switch on. That is the ONE spelling
171
- * the fleet already carries: KMS holds deploy/PUBLISHABLE_KEY, and each
172
- * Dockerfile takes PUBLISHABLE_KEY as a build-arg and re-exports it with the
173
- * NEXT_PUBLIC_ prefix that makes Next inline it.
174
- *
175
- * A surface with no key at all still reports for whoever is SIGNED IN (the
176
- * session credential attributes them), and drops every logged-out visitor: the
177
- * door refuses an unattributable write rather than filing it where its owner
178
- * cannot read it. That failure is invisible from the page, which is why the key
179
- * belongs in the env next to the DSN and not in a checklist.
180
- *
181
- * This attributes the EVENT STREAM only — the error plane authenticates
182
- * independently with `dsn`, and one does not stand in for the other. */
183
- ingestKey?: string;
184
- /** Max events buffered before an automatic flush. */
185
- batchSize?: number;
186
- /** Auto-flush cadence in ms. */
187
- flushIntervalMs?: number;
188
- /** Turn the client off entirely (e.g. opt-out / DNT). Defaults to enabled. */
189
- enabled?: boolean;
190
- /** Auto-capture unhandled errors + promise rejections (window.onerror,
191
- * unhandledrejection). Browser-only, defaults to enabled. Together with `dsn`
192
- * this is what makes the client a drop-in @sentry replacement — without a
193
- * `dsn` the captures never reach the Sentry dashboard. */
194
- captureErrors?: boolean;
195
- /** Override the transport (tests). */
196
- transport?: Transport;
197
- /** Debug logging. */
198
- debug?: boolean;
199
- /** Hanzo-minted Sentry DSN: "https://<version>:<hmac>@<host>/v1/event/<projectId>".
200
- * Publishable — the key authorizes writes to ONE project and can read nothing,
201
- * so it is safe in a browser bundle (same trust class as `ingestKey`). When
202
- * absent the client reads NEXT_PUBLIC_HANZO_EVENT_DSN; when neither is set the
203
- * error plane is inert (fail-safe: nothing sent, nothing thrown, analytics
204
- * unaffected). Mint one per property: POST /v1/sentry/projects. */
205
- dsn?: string;
206
- /** Release stamped on error events (a git SHA / app version). */
207
- release?: string;
208
- /** Deployment environment for error events (production | staging | …). */
209
- environment?: string;
210
- /** Retain end-user PII (emails/IPs) in error text. Default false = scrub
211
- * client-side before anything leaves the device (the server scrubs again). */
212
- capturePII?: boolean;
213
- }
214
- type SentryLevel = 'fatal' | 'error' | 'warning' | 'info' | 'debug';
215
- interface SentryFrame {
216
- filename?: string;
217
- function?: string;
218
- module?: string;
219
- abs_path?: string;
220
- lineno?: number;
221
- colno?: number;
222
- in_app?: boolean;
223
- }
224
- interface SentryExceptionValue {
225
- type?: string;
226
- value?: string;
227
- module?: string;
228
- stacktrace?: {
229
- frames: SentryFrame[];
230
- };
231
- }
232
- interface SentryUser {
233
- /** Stable subject id (OIDC sub / anon id). NEVER email/username/ip. */
234
- id?: string;
235
- }
236
- interface SentryEvent {
237
- event_id: string;
238
- timestamp: number;
239
- platform: 'javascript';
240
- level: SentryLevel;
241
- logger?: string;
242
- environment?: string;
243
- release?: string;
244
- transaction?: string;
245
- fingerprint?: string[];
246
- message?: string;
247
- exception?: {
248
- values: SentryExceptionValue[];
249
- };
250
- tags?: Record<string, string>;
251
- user?: SentryUser;
252
- contexts?: Record<string, Record<string, unknown>>;
253
- sdk?: {
254
- name: string;
255
- version: string;
256
- };
257
- }
258
- /** Parsed DSN — the public key + the derived ingest URL. */
259
- interface Dsn {
260
- /** "<version>:<hmac>" public key presented via ?sentry_key= (beacon-safe). */
261
- publicKey: string;
262
- /** Ingest origin, e.g. "https://sentry.hanzo.ai". */
263
- origin: string;
264
- /** Project id segment. */
265
- projectId: string;
266
- /** The DSN's own origin + path, e.g. "https://api.hanzo.ai/v1/event/<projectId>".
267
- * Every URL below is derived from this, so the ingest address is named once —
268
- * in dsnForProduct — and nowhere else. */
269
- base: string;
270
- /** Fully-derived envelope ingest URL incl. ?sentry_key=. */
271
- ingestUrl: string;
272
- }
273
- /** Options for Analytics.captureError. */
274
- interface CaptureErrorOptions {
275
- /** false => uncaught (window.onerror / unhandledrejection / render crash). */
276
- handled?: boolean;
277
- /** Severity + free-form context; merged into the event's tags. */
278
- properties?: Record<string, unknown>;
279
- /** Override the event level (default: error, or fatal when handled === false). */
280
- level?: SentryLevel;
281
- }
282
-
283
- declare class Analytics {
284
- private cfg;
285
- private transport;
286
- private queue;
287
- private timer;
288
- private personId?;
289
- private attribution;
290
- private cohort;
291
- private started;
292
- /** The view pageview() last counted — path + location. */
293
- private counted?;
294
- /** Parsed error-plane DSN, or null when the plane is inert. */
295
- private dsn;
296
- /** Guards against an error thrown *inside* the error path re-entering it. */
297
- private reentrant;
298
- constructor(config: AnalyticsConfig);
299
- /** adopt gives this client a credential it does not have. The key belongs to
300
- * the stream, not to whichever caller happened to ask for the handle first,
301
- * so a later caller carrying one hands it over. The error plane derives from
302
- * the key, so it comes up here too when it was inert for want of one.
303
- * Present fields are never overwritten: the first caller's key stays. */
304
- adopt(config: AnalyticsConfig): void;
305
- /** errorPlaneEnabled reports whether captured exceptions can actually reach the
306
- * error host. False means a DSN was never configured — the documented
307
- * fail-safe. Exposed so an app (or a test) can assert its wiring instead of
308
- * discovering months later that nothing was ever reported. */
309
- get errorPlaneEnabled(): boolean;
310
- /** errorIngestUrl is the fully-derived envelope endpoint, or undefined when the
311
- * plane is inert. Diagnostics only. */
312
- get errorIngestUrl(): string | undefined;
313
- /** init is idempotent and browser-only for its side effects: capture first-touch
314
- * attribution, hydrate cohort, register the unload flush, and (unless opted out)
315
- * auto-capture unhandled errors. Safe to call from a React effect on every
316
- * render. */
317
- init(): void;
318
- /** identify binds the current visitor to a stable person id (post-login). */
319
- identify(personId: string, traits?: Record<string, unknown>): void;
320
- /** group associates the visitor with an org/team (analytics grouping, not the
321
- * server tenant — the server still derives tenant from the session). */
322
- group(groupId: string, traits?: Record<string, unknown>): void;
323
- /** pageview records a $pageview for the current (or given) location, once per
324
- * view. A view is the path plus the full location, so a query or hash change
325
- * is a new one and a repeat call for the same place is not. */
326
- pageview(path?: string, properties?: Record<string, unknown>): void;
327
- /** capture records a named product event with optional properties. Commerce
328
- * fields (productId/quantity/revenue/currency) may be passed for order events. */
329
- capture(event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, 'productId' | 'quantity' | 'revenue' | 'currency'>): void;
330
- /** captureError reports a caught error, an unhandled rejection, a React render
331
- * error, or a manual report to BOTH planes, from one call:
332
- *
333
- * - the ERROR PLANE — a real Sentry envelope to the DSN host. This is the one
334
- * that produces an issue in sentry.hanzo.ai (grouping, stack frames, AST).
335
- * Inert when no DSN is configured.
336
- * - the EVENT STREAM — a `type:'error'` row in the cloud event warehouse, so
337
- * an error stays correlated with the session's pageviews for product
338
- * analysis (readable via GET /v1/errors).
339
- *
340
- * Both carry the SAME session and subject id, so an error and the pageview
341
- * before it join up. Never throws back into the app; errors are higher-signal
342
- * than pageviews, so both planes flush promptly (a crash may unload the page
343
- * moments later). */
344
- captureError(err: unknown, context?: CaptureErrorOptions): void;
345
- /** setCohort persists cohort dimensions (e.g. signupWeek at signup) so they ride
346
- * every subsequent event. */
347
- setCohort(patch: Cohort): void;
348
- /** flush drains the buffer to the server as ONE batch through the ONE ingest
349
- * front door POST /v1/event, body { batch: [Event…] }. beacon=true selects the
350
- * unload-safe transport. Auth is orthogonal to the wire:
351
- *
352
- * • publishable key set → rides ?ingest_key=pk-… on both sends, keeping each
353
- * a CORS-simple request that any origin may send.
354
- * • else a bearer JWT rides Authorization (fetch only — sendBeacon cannot
355
- * carry a header, so token apps fall back to keepalive fetch on unload).
356
- * • else a cookie app rides same-origin credentials (beacon carries the
357
- * cookie fine).
358
- */
359
- flush(beacon?: boolean): void;
360
- /** sendError frames one exception as a Sentry envelope and posts it to the DSN's
361
- * ingest URL. The DSN's own key rides ?sentry_key= (the credential channel the
362
- * server trusts, and the only one a headerless beacon can carry), so NO bearer
363
- * or publishable key is attached here — the two planes authenticate
364
- * independently. Errors are sent one envelope per event, immediately: batching
365
- * a crash report is how you lose it. */
366
- private sendError;
367
- /** errorIdentity is the SAME identity the event stream stamps — the OIDC subject
368
- * once identify() has run, else the anon id. Never email/PII. */
369
- private errorIdentity;
370
- private enqueue;
371
- private build;
372
- private schedule;
373
- private clearTimer;
374
- }
375
- /** createAnalytics returns the client for a stream, building it on first ask.
376
- * This is the ONE way to get a client: `new Analytics` bypasses the registry
377
- * and is for tests and for a deliberately separate instance. */
378
- declare function createAnalytics(config: AnalyticsConfig): Analytics;
379
-
380
- export { type Attribution as A, type Cohort as C, type Dsn as D, type ExceptionEntry as E, type SentryEvent as S, type Transport as T, type WireEvent as W, type CaptureErrorOptions as a, type SentryFrame as b, type SentryLevel as c, type ExceptionProperties as d, Analytics as e, type AnalyticsConfig as f, type EventKind as g, type Exception as h, type ExceptionFrame as i, createAnalytics as j };