@hanzo/event 0.3.36 → 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.
- package/LICENSE.md +21 -0
- package/README.md +8 -8
- package/TAXONOMY.md +1 -1
- package/dist/attribution.d.ts +14 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/core.d.ts +102 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/dsn.d.ts +43 -0
- package/dist/dsn.d.ts.map +1 -0
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/exception.d.ts +50 -0
- package/dist/exception.d.ts.map +1 -0
- package/dist/funnels.d.ts +14 -0
- package/dist/funnels.d.ts.map +1 -0
- package/dist/goals.d.ts +24 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -292
- package/dist/index.d.ts +22 -292
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7 -7
- package/dist/index.mjs.map +1 -1
- package/dist/org.d.ts +74 -0
- package/dist/org.d.ts.map +1 -0
- package/dist/react.cjs +7 -7
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +13 -14
- package/dist/react.d.ts +13 -14
- package/dist/react.d.ts.map +1 -0
- package/dist/react.mjs +7 -7
- package/dist/react.mjs.map +1 -1
- package/dist/scrub.d.ts +20 -0
- package/dist/scrub.d.ts.map +1 -0
- package/dist/sentry.d.ts +62 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/stack.d.ts +25 -0
- package/dist/stack.d.ts.map +1 -0
- package/dist/storage.d.ts +32 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/{core-CIhI2R7S.d.cts → types.d.ts} +21 -119
- package/dist/types.d.ts.map +1 -0
- package/dist/uid.d.ts +10 -0
- package/dist/uid.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +11 -11
- package/src/anon.d.ts +1 -1
- package/src/anon.js +3 -3
- package/src/anon.test.ts +3 -3
- package/src/core.test.ts +6 -6
- package/src/core.ts +11 -11
- package/src/events.ts +1 -1
- package/src/exception.ts +1 -1
- package/src/stack.ts +1 -1
- package/src/types.ts +3 -3
- package/src/uid.ts +2 -2
- package/src/version.ts +1 -1
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
4
|
+
export const VERSION = '0.3.37'
|
package/dist/core-CIhI2R7S.d.ts
DELETED
|
@@ -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 };
|