@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.
- 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 +11 -11
- 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 +11 -11
- package/dist/index.mjs.map +1 -1
- package/dist/org.d.ts +77 -0
- package/dist/org.d.ts.map +1 -0
- package/dist/react.cjs +11 -11
- 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 +11 -11
- 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 +12 -12
- 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/org.ts +7 -4
- 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/README.md
CHANGED
|
@@ -34,8 +34,8 @@ starve the other:
|
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
That one line is the whole install, and it is the same line for a published site,
|
|
37
|
-
hanzo.team and a customer's own page. The tag is served by the
|
|
38
|
-
events, so a caller allowlists ONE host and the tag can never drift from the wire
|
|
37
|
+
hanzo.team and a customer's own page. The tag is served by the endpoint that eats
|
|
38
|
+
the events, so a caller allowlists ONE host and the tag can never drift from the wire
|
|
39
39
|
it posts to — `.js` is part of a segment rather than a child of one, so the tag
|
|
40
40
|
sits UNDER `/v1/event` rather than beside it, and the old sibling `/v1/event.js`
|
|
41
41
|
404s.
|
|
@@ -48,18 +48,18 @@ config from `/v1/projects/tags`, so a pixel is configured on the project rather
|
|
|
48
48
|
than pasted into the page.
|
|
49
49
|
|
|
50
50
|
> A bundled app should NOT add this tag. Both clients post pageviews to the same
|
|
51
|
-
>
|
|
51
|
+
> endpoint, so a page running `@hanzo/event` and the tag counts every pageview twice.
|
|
52
52
|
> One surface, one client.
|
|
53
53
|
|
|
54
54
|
> **`data-key` is a publishable `pk-`, and without one the tag is INERT.** A write
|
|
55
|
-
> the
|
|
55
|
+
> the edge cannot attribute is refused (`401 ingest_key_required`) silently, so a
|
|
56
56
|
> keyless tag measures fine in the browser and files nothing. A project mints one
|
|
57
57
|
> with itself: `POST /v1/projects`. A key that names no project is refused
|
|
58
|
-
> `403 ingest_key_unknown` — the
|
|
58
|
+
> `403 ingest_key_unknown` — the edge fails closed rather than filing a write it
|
|
59
59
|
> cannot attribute.
|
|
60
60
|
|
|
61
61
|
This package is the client for a surface that **builds**. There is no second
|
|
62
|
-
script-tag distribution here: one wire, one
|
|
62
|
+
script-tag distribution here: one wire, one endpoint, one tag.
|
|
63
63
|
|
|
64
64
|
It honours the same consent sources the bundled stack does: an explicit stored
|
|
65
65
|
choice (`hz_consent`, the key a Hanzo consent banner writes) outranks the browser
|
|
@@ -72,8 +72,8 @@ refusals. A React app does not need this file — mount `<Hanzo analytics>` from
|
|
|
72
72
|
> behind an identical path spelling, served by a second collector with its own
|
|
73
73
|
> database. `POST api.hanzo.ai/v1/event {"batch":[]}` answered 200 while
|
|
74
74
|
> `POST analytics.hanzo.ai/v1/event []` answered 204, and a client pointed at the
|
|
75
|
-
> wrong host failed silently. Both the second
|
|
76
|
-
> deleted. One wire, one
|
|
75
|
+
> wrong host failed silently. Both the second endpoint and the second collector
|
|
76
|
+
> are deleted. One wire, one endpoint, one client home — this package.
|
|
77
77
|
|
|
78
78
|
- **Batched** with a size + interval flush, and **beacon-on-unload**
|
|
79
79
|
(`sendBeacon` for cookie/publishable-key apps, `fetch(keepalive)` for token apps).
|
package/TAXONOMY.md
CHANGED
|
@@ -238,7 +238,7 @@ Verified against the trees at the time of writing. ✅ = emitting, ❌ = not emi
|
|
|
238
238
|
4. **No org on hanzo.chat.** `group()` is absent because the chat session carries
|
|
239
239
|
no org today; chat funnels are person-scoped only.
|
|
240
240
|
5. **Logged-out reach depends on a publishable key.** Anonymous events need
|
|
241
|
-
`ingestKey` (`pk_…`, write-only) or they fail closed at the
|
|
241
|
+
`ingestKey` (`pk_…`, write-only) or they fail closed at the edge.
|
|
242
242
|
`NEXT_PUBLIC_HANZO_INGEST_KEY` (hanzo.ai), `NEXT_PUBLIC_PUBLISHABLE_KEY`
|
|
243
243
|
(hanzo.app), `VITE_HANZO_INGEST_KEY` (hanzo.chat) are read but must be
|
|
244
244
|
provisioned per org via `POST /v1/ingest/keys`. Config, not code.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Attribution } from './types';
|
|
2
|
+
/** parseAttribution reads UTM params + ref/refCode from a query string and pairs
|
|
3
|
+
* them with the referrer. `search` is a location.search value ("?utm_source=…"). */
|
|
4
|
+
export declare function parseAttribution(search: string, referrer: string): Attribution;
|
|
5
|
+
/** deriveChannel classifies the visit: paid | referral | social | organic | direct. */
|
|
6
|
+
export declare function deriveChannel(a: Attribution): string;
|
|
7
|
+
/** hostOf extracts a bare lowercase host from a URL; "" when unparseable. */
|
|
8
|
+
export declare function hostOf(raw?: string): string;
|
|
9
|
+
/** hasAttribution reports whether anything was captured (so we don't persist an
|
|
10
|
+
* empty first-touch that would shadow a later real one). */
|
|
11
|
+
export declare function hasAttribution(a: Attribution): boolean;
|
|
12
|
+
/** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
|
|
13
|
+
export declare function isoWeek(d: Date): string;
|
|
14
|
+
//# sourceMappingURL=attribution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attribution.d.ts","sourceRoot":"","sources":["../src/attribution.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAQ1C;qFACqF;AACrF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAmB9E;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAUpD;AAED,6EAA6E;AAC7E,wBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAY3C;AAED;6DAC6D;AAC7D,wBAAgB,cAAc,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAKtD;AAED,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAQvC"}
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { getFirstTouch, getCohort } from './storage';
|
|
2
|
+
import type { AnalyticsConfig, CaptureErrorOptions, Cohort, WireEvent } from './types';
|
|
3
|
+
import { VERSION } from './version';
|
|
4
|
+
export { VERSION };
|
|
5
|
+
export declare class Analytics {
|
|
6
|
+
private cfg;
|
|
7
|
+
private transport;
|
|
8
|
+
private queue;
|
|
9
|
+
private timer;
|
|
10
|
+
private personId?;
|
|
11
|
+
private attribution;
|
|
12
|
+
private cohort;
|
|
13
|
+
private started;
|
|
14
|
+
/** The view pageview() last counted — path + location. */
|
|
15
|
+
private counted?;
|
|
16
|
+
/** Parsed error-plane DSN, or null when the plane is inert. */
|
|
17
|
+
private dsn;
|
|
18
|
+
/** Guards against an error thrown *inside* the error path re-entering it. */
|
|
19
|
+
private reentrant;
|
|
20
|
+
constructor(config: AnalyticsConfig);
|
|
21
|
+
/** adopt gives this client a credential it does not have. The key belongs to
|
|
22
|
+
* the stream, not to whichever caller happened to ask for the handle first,
|
|
23
|
+
* so a later caller carrying one hands it over. The error plane derives from
|
|
24
|
+
* the key, so it comes up here too when it was inert for want of one.
|
|
25
|
+
* Present fields are never overwritten: the first caller's key stays. */
|
|
26
|
+
adopt(config: AnalyticsConfig): void;
|
|
27
|
+
/** errorPlaneEnabled reports whether captured exceptions can actually reach the
|
|
28
|
+
* error host. False means a DSN was never configured — the documented
|
|
29
|
+
* fail-safe. Exposed so an app (or a test) can assert its wiring instead of
|
|
30
|
+
* discovering months later that nothing was ever reported. */
|
|
31
|
+
get errorPlaneEnabled(): boolean;
|
|
32
|
+
/** errorIngestUrl is the fully-derived envelope endpoint, or undefined when the
|
|
33
|
+
* plane is inert. Diagnostics only. */
|
|
34
|
+
get errorIngestUrl(): string | undefined;
|
|
35
|
+
/** init is idempotent and browser-only for its side effects: capture first-touch
|
|
36
|
+
* attribution, hydrate cohort, register the unload flush, and (unless opted out)
|
|
37
|
+
* auto-capture unhandled errors. Safe to call from a React effect on every
|
|
38
|
+
* render. */
|
|
39
|
+
init(): void;
|
|
40
|
+
/** identify binds the current visitor to a stable person id (post-login). */
|
|
41
|
+
identify(personId: string, traits?: Record<string, unknown>): void;
|
|
42
|
+
/** group associates the visitor with an org/team (analytics grouping, not the
|
|
43
|
+
* server tenant — the server still derives tenant from the session). */
|
|
44
|
+
group(groupId: string, traits?: Record<string, unknown>): void;
|
|
45
|
+
/** pageview records a $pageview for the current (or given) location, once per
|
|
46
|
+
* view. A view is the path plus the full location, so a query or hash change
|
|
47
|
+
* is a new one and a repeat call for the same place is not. */
|
|
48
|
+
pageview(path?: string, properties?: Record<string, unknown>): void;
|
|
49
|
+
/** capture records a named product event with optional properties. Commerce
|
|
50
|
+
* fields (productId/quantity/revenue/currency) may be passed for order events. */
|
|
51
|
+
capture(event: string, properties?: Record<string, unknown>, commerce?: Pick<WireEvent, 'productId' | 'quantity' | 'revenue' | 'currency'>): void;
|
|
52
|
+
/** captureError reports a caught error, an unhandled rejection, a React render
|
|
53
|
+
* error, or a manual report to BOTH planes, from one call:
|
|
54
|
+
*
|
|
55
|
+
* - the ERROR PLANE — a real Sentry envelope to the DSN host. This is the one
|
|
56
|
+
* that produces an issue in sentry.hanzo.ai (grouping, stack frames, AST).
|
|
57
|
+
* Inert when no DSN is configured.
|
|
58
|
+
* - the EVENT STREAM — a `type:'error'` row in the cloud event warehouse, so
|
|
59
|
+
* an error stays correlated with the session's pageviews for product
|
|
60
|
+
* analysis (readable via GET /v1/errors).
|
|
61
|
+
*
|
|
62
|
+
* Both carry the SAME session and subject id, so an error and the pageview
|
|
63
|
+
* before it join up. Never throws back into the app; errors are higher-signal
|
|
64
|
+
* than pageviews, so both planes flush promptly (a crash may unload the page
|
|
65
|
+
* moments later). */
|
|
66
|
+
captureError(err: unknown, context?: CaptureErrorOptions): void;
|
|
67
|
+
/** setCohort persists cohort dimensions (e.g. signupWeek at signup) so they ride
|
|
68
|
+
* every subsequent event. */
|
|
69
|
+
setCohort(patch: Cohort): void;
|
|
70
|
+
/** flush drains the buffer to the server as ONE batch through the ONE ingest
|
|
71
|
+
* endpoint POST /v1/event, body { batch: [Event…] }. beacon=true selects the
|
|
72
|
+
* unload-safe transport. Auth is orthogonal to the wire:
|
|
73
|
+
*
|
|
74
|
+
* • publishable key set → rides ?ingest_key=pk-… on both sends, keeping each
|
|
75
|
+
* a CORS-simple request that any origin may send.
|
|
76
|
+
* • else a bearer JWT rides Authorization (fetch only — sendBeacon cannot
|
|
77
|
+
* carry a header, so token apps fall back to keepalive fetch on unload).
|
|
78
|
+
* • else a cookie app rides same-origin credentials (beacon carries the
|
|
79
|
+
* cookie fine).
|
|
80
|
+
*/
|
|
81
|
+
flush(beacon?: boolean): void;
|
|
82
|
+
/** sendError frames one exception as a Sentry envelope and posts it to the DSN's
|
|
83
|
+
* ingest URL. The DSN's own key rides ?sentry_key= (the credential channel the
|
|
84
|
+
* server trusts, and the only one a headerless beacon can carry), so NO bearer
|
|
85
|
+
* or publishable key is attached here — the two planes authenticate
|
|
86
|
+
* independently. Errors are sent one envelope per event, immediately: batching
|
|
87
|
+
* a crash report is how you lose it. */
|
|
88
|
+
private sendError;
|
|
89
|
+
/** errorIdentity is the SAME identity the event stream stamps — the OIDC subject
|
|
90
|
+
* once identify() has run, else the anon id. Never email/PII. */
|
|
91
|
+
private errorIdentity;
|
|
92
|
+
private enqueue;
|
|
93
|
+
private build;
|
|
94
|
+
private schedule;
|
|
95
|
+
private clearTimer;
|
|
96
|
+
}
|
|
97
|
+
/** createAnalytics returns the client for a stream, building it on first ask.
|
|
98
|
+
* This is the ONE way to get a client: `new Analytics` bypasses the registry
|
|
99
|
+
* and is for tests and for a deliberately separate instance. */
|
|
100
|
+
export declare function createAnalytics(config: AnalyticsConfig): Analytics;
|
|
101
|
+
export { getCohort, getFirstTouch };
|
|
102
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AA2DA,OAAO,EAGL,aAAa,EAEb,SAAS,EAEV,MAAM,WAAW,CAAA;AAElB,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EACnB,MAAM,EAKN,SAAS,EACV,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EAAE,OAAO,EAAE,CAAA;AA2KlB,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAGM;IACjB,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAAQ;IACvB,0DAA0D;IAC1D,OAAO,CAAC,OAAO,CAAC,CAAQ;IACxB,+DAA+D;IAC/D,OAAO,CAAC,GAAG,CAAY;IACvB,6EAA6E;IAC7E,OAAO,CAAC,SAAS,CAAQ;IAEzB,YAAY,MAAM,EAAE,eAAe,EAwClC;IAED;;;;8EAI0E;IAC1E,KAAK,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAQnC;IAED;;;mEAG+D;IAC/D,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED;4CACwC;IACxC,IAAI,cAAc,IAAI,MAAM,GAAG,SAAS,CAEvC;IAED;;;kBAGc;IACd,IAAI,IAAI,IAAI,CAgCX;IAED,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAGjE;IAED;6EACyE;IACzE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7D;IAED;;oEAEgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAclE;IAED;uFACmF;IACnF,OAAO,CACL,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,QAAQ,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC,GAC5E,IAAI,CAEN;IAED;;;;;;;;;;;;;0BAasB;IACtB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAuD9D;IAED;kCAC8B;IAC9B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7B;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,MAAM,UAAQ,GAAG,IAAI,CAmC1B;IAID;;;;;6CAKyC;IACzC,OAAO,CAAC,SAAS;IAiBjB;sEACkE;IAClE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,KAAK;IA+Db,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,UAAU;CAMnB;AAiCD;;iEAEiE;AACjE,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAmBlE;AAID,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CAAA"}
|
package/dist/dsn.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The product → Sentinel DSN registry.
|
|
3
|
+
*
|
|
4
|
+
* An app declares WHAT it is (`product: 'console'`); this module knows WHERE its
|
|
5
|
+
* errors go. That split is the whole point: no surface has to learn a DSN, carry
|
|
6
|
+
* a build argument, or grow a config file to report errors — declaring the
|
|
7
|
+
* product it already declares is enough.
|
|
8
|
+
*
|
|
9
|
+
* A Sentinel DSN is PUBLIC by construction. It ships inside the client bundle and
|
|
10
|
+
* is readable in devtools on any deployed page, and it grants exactly one
|
|
11
|
+
* capability: submitting new events. It cannot read issues, projects, or any
|
|
12
|
+
* other data. So committing it is not leaking a secret — it is recording a public
|
|
13
|
+
* identifier next to the code that needs it. (Contrast the server-side collector
|
|
14
|
+
* DSN in the `team-analytics-sentry` Secret, which is HMAC-derived and revocable
|
|
15
|
+
* precisely because a server-side credential is NOT public.)
|
|
16
|
+
*
|
|
17
|
+
* Why a literal map instead of deriving `hanzo-${product}`: the projects predate
|
|
18
|
+
* this registry and do not derive cleanly — `site` lives in `hanzo-ai`, not
|
|
19
|
+
* `hanzo-site`. An explicit map is honest about that; a derivation rule plus an
|
|
20
|
+
* exception table is the same data with a trap in it.
|
|
21
|
+
*
|
|
22
|
+
* Projects are org-scoped and named `<org>-<app>`. To add one: create the project
|
|
23
|
+
* (POST /v1/sentinel/projects with X-Org-Id), then add its `dsn` here keyed by the
|
|
24
|
+
* product name the app passes to `createAnalytics`.
|
|
25
|
+
*/
|
|
26
|
+
/** PRODUCT_PROJECT maps a `product` to its Sentinel project id. The DSN's KEY is no
|
|
27
|
+
* longer a per-project secret — it is the ONE org publishable key (below), so a
|
|
28
|
+
* surface's errors ride the SAME key its events do. The id only names WHICH
|
|
29
|
+
* project the errors group under, and cloud auto-provisions that project on first
|
|
30
|
+
* keyed ingest, so a new id needs nothing minted. `site` lives in the `hanzo-ai`
|
|
31
|
+
* project — an explicit map, because the projects predate this and do not derive
|
|
32
|
+
* cleanly from the product name. */
|
|
33
|
+
export declare const PRODUCT_PROJECT: Readonly<Record<string, string>>;
|
|
34
|
+
/** dsnForProduct builds the product's Sentinel DSN from the caller's resolved
|
|
35
|
+
* publishable `key` and the product's project — the SAME key the event stream
|
|
36
|
+
* carries, at the product's envelope endpoint. Returns undefined when there is no
|
|
37
|
+
* key or no project for the product, leaving the error plane inert rather than
|
|
38
|
+
* posting into the wrong one. The key is NOT baked here: it is the value the
|
|
39
|
+
* surface resolved (an explicit `ingestKey`, or the KMS-sourced
|
|
40
|
+
* NEXT_PUBLIC_PUBLISHABLE_KEY the build inlines) — the ONE live source, never a
|
|
41
|
+
* literal committed beside the code. */
|
|
42
|
+
export declare function dsnForProduct(product: string | undefined, key: string | undefined): string | undefined;
|
|
43
|
+
//# sourceMappingURL=dsn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dsn.d.ts","sourceRoot":"","sources":["../src/dsn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH;;;;;;qCAMqC;AACrC,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAI3D,CAAA;AAEF;;;;;;;yCAOyC;AACzC,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,MAAM,GAAG,SAAS,CAKpB"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary, re-exported from where it is now defined.
|
|
3
|
+
*
|
|
4
|
+
* These names used to be declared here. They moved to @hanzo/events — the
|
|
5
|
+
* plural package is the catalog (many events, what each means, what each
|
|
6
|
+
* carries), this one is the client (one call sends one event). Splitting them
|
|
7
|
+
* that way is what lets the ingest endpoint read the same catalog: it is Go
|
|
8
|
+
* and cannot import TypeScript, so @hanzo/events also ships the data as JSON,
|
|
9
|
+
* and there is still exactly one definition.
|
|
10
|
+
*
|
|
11
|
+
* Nothing about the API moved. Every existing import keeps working, the names
|
|
12
|
+
* and their values are unchanged, and `pkgs/events/test/catalog.mjs` fails the
|
|
13
|
+
* build if a name and its meaning ever disagree.
|
|
14
|
+
*
|
|
15
|
+
* The naming convention lives with the names, in @hanzo/events and TAXONOMY.md:
|
|
16
|
+
* snake_case `<object>_<verb-past>`, a dimension is a PROPERTY and never part
|
|
17
|
+
* of the name, one name per user-visible moment shared by every surface.
|
|
18
|
+
*/
|
|
19
|
+
export { EVENTS, PAGEVIEW, EXCEPTION } from '@hanzo/events';
|
|
20
|
+
export type { EventName } from '@hanzo/events';
|
|
21
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC3D,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ExceptionEntry, ExceptionProperties, SentryLevel } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* digest is a stable 32-hex-char (128-bit) content hash, computed synchronously.
|
|
4
|
+
*
|
|
5
|
+
* Grouping keys are needed on the capture path, which is synchronous and may be
|
|
6
|
+
* running inside an unload handler — SubtleCrypto is async and unavailable on
|
|
7
|
+
* insecure origins, so it cannot be used here. This is FNV-1a run over four seeds
|
|
8
|
+
* and concatenated. It is a GROUPING key, never a security boundary: it is not
|
|
9
|
+
* collision-resistant against a chosen-input adversary, and nothing authorizes or
|
|
10
|
+
* authenticates on it. The product treats the value as opaque.
|
|
11
|
+
*/
|
|
12
|
+
export declare function digest(s: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* exceptionEntry builds the single `$exception_list` entry for a throwable.
|
|
15
|
+
*
|
|
16
|
+
* One entry, not a chain: `Error.cause` chaining is a distinct fact with its own
|
|
17
|
+
* ordering rules, and emitting it wrongly is worse than not emitting it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function exceptionEntry(err: unknown, opts: {
|
|
20
|
+
handled: boolean;
|
|
21
|
+
id: string;
|
|
22
|
+
}): ExceptionEntry;
|
|
23
|
+
/**
|
|
24
|
+
* fingerprint is the issue grouping key.
|
|
25
|
+
*
|
|
26
|
+
* Keyed on exception type plus each in-app frame's function and source — the same
|
|
27
|
+
* pieces the server-side grouper records ("Exception Type", "Resolved function
|
|
28
|
+
* name", "Source file name"). Deliberately NOT the message: `Loading chunk 3324
|
|
29
|
+
* failed` and `Loading chunk 998 failed` are one bug, and grouping on message is
|
|
30
|
+
* precisely the mistake that made every distinct error string its own event name.
|
|
31
|
+
*
|
|
32
|
+
* Falls back to the type alone when no in-app frame survived, which keeps
|
|
33
|
+
* stackless errors (`Script error.`, cross-origin) in one issue instead of
|
|
34
|
+
* scattering them.
|
|
35
|
+
*/
|
|
36
|
+
export declare function fingerprint(entry: ExceptionEntry): string;
|
|
37
|
+
/**
|
|
38
|
+
* exceptionProperties builds the full `$exception_*` property bag for one captured
|
|
39
|
+
* throwable — everything Error Tracking reads off the event.
|
|
40
|
+
*
|
|
41
|
+
* The denormalized arrays are ordered like `frames`: last element is the throw
|
|
42
|
+
* site, which is the element the issue list indexes at -1 for its source/function
|
|
43
|
+
* columns.
|
|
44
|
+
*/
|
|
45
|
+
export declare function exceptionProperties(err: unknown, opts: {
|
|
46
|
+
handled: boolean;
|
|
47
|
+
id: string;
|
|
48
|
+
level?: SentryLevel;
|
|
49
|
+
}): ExceptionProperties;
|
|
50
|
+
//# sourceMappingURL=exception.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception.d.ts","sourceRoot":"","sources":["../src/exception.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EACV,cAAc,EAEd,mBAAmB,EAEnB,WAAW,EACZ,MAAM,SAAS,CAAA;AAQhB;;;;;;;;;GASG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAYxC;AA6CD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACrC,cAAc,CAqBhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAMzD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,GAC1D,mBAAmB,CAsBrB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The funnel registry, re-exported from where it is now defined.
|
|
3
|
+
*
|
|
4
|
+
* The funnels moved to @hanzo/events for the reason the names did: the read lens
|
|
5
|
+
* is Go and cannot import TypeScript, and that package already writes its data
|
|
6
|
+
* out as JSON. Defining a funnel beside the vocabulary also makes the anti-drift
|
|
7
|
+
* check local — a step naming an event that does not exist is caught where both
|
|
8
|
+
* are declared.
|
|
9
|
+
*
|
|
10
|
+
* Nothing about the API moved. Every existing import keeps working.
|
|
11
|
+
*/
|
|
12
|
+
export { FUNNELS, PRODUCTS, eventsOf } from '@hanzo/events';
|
|
13
|
+
export type { FunnelDef, FunnelStep, FunnelId, ProductId } from '@hanzo/events';
|
|
14
|
+
//# sourceMappingURL=funnels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"funnels.d.ts","sourceRoot":"","sources":["../src/funnels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC3D,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA"}
|
package/dist/goals.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type FunnelId } from './funnels';
|
|
2
|
+
export interface GoalDef {
|
|
3
|
+
/** Human label shown in Insights. */
|
|
4
|
+
label: string;
|
|
5
|
+
/** The event whose occurrence counts as the goal conversion. */
|
|
6
|
+
event: string;
|
|
7
|
+
/** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
|
|
8
|
+
funnelId?: FunnelId;
|
|
9
|
+
/** The ordered event names of `funnelId`, derived — never hand-written. */
|
|
10
|
+
funnel?: string[];
|
|
11
|
+
/** Optional property equality filter that qualifies the conversion. */
|
|
12
|
+
filter?: {
|
|
13
|
+
property: string;
|
|
14
|
+
equals: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
|
|
18
|
+
export interface CohortDef {
|
|
19
|
+
/** The hanzo.events column the cohort dimension maps to. */
|
|
20
|
+
field: string;
|
|
21
|
+
label: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef>;
|
|
24
|
+
//# sourceMappingURL=goals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"goals.d.ts","sourceRoot":"","sources":["../src/goals.ts"],"names":[],"mappings":"AAUA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAA;AAEnD,MAAM,WAAW,OAAO;IACtB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAA;IACb,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC9C;AAED,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,GAAG,eAAe,GAAG,YAAY,EAAE,OAAO,CA+BrF,CAAA;AAED,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,YAAY,GAAG,SAAS,GAAG,SAAS,EAAE,SAAS,CAI3E,CAAA"}
|
package/dist/index.cjs
CHANGED
|
@@ -120,12 +120,12 @@ var ORG_DOMAIN = Object.freeze({
|
|
|
120
120
|
"lux.fund",
|
|
121
121
|
"lux.id"
|
|
122
122
|
]),
|
|
123
|
-
zoo: Object.freeze(["zoo.ngo", "zoo.network", "zoolabs.id"])
|
|
123
|
+
zoo: Object.freeze(["zoo.ngo", "zoo.network", "zoolabs.id", "zoolabs.io"])
|
|
124
124
|
});
|
|
125
125
|
var ORG_KEY = Object.freeze({
|
|
126
|
-
hanzo: "pk-
|
|
127
|
-
lux: "pk-
|
|
128
|
-
zoo: "pk-
|
|
126
|
+
hanzo: "pk-bCcNBDOLwdIovV349jVGH1ShEOq7rAcaKLsX_bLnRMo",
|
|
127
|
+
lux: "pk-GxCDhpEn89qajMyuMdMs6mcE4UVQBkv8F2swEKHC67Y",
|
|
128
|
+
zoo: "pk-bS-xKESMhuhK_wfSmpRf1H_IH8A3x6Y5NV70TD7ycWE"
|
|
129
129
|
});
|
|
130
130
|
var PUBLISHABLE = "pk-";
|
|
131
131
|
var normalize = (host) => host.trim().toLowerCase().replace(/\.$/, "").split(":")[0] ?? "";
|
|
@@ -392,7 +392,7 @@ function uuidv7Time(id) {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
// src/version.ts
|
|
395
|
-
var VERSION = "0.3.
|
|
395
|
+
var VERSION = "0.3.37";
|
|
396
396
|
|
|
397
397
|
// src/sentry.ts
|
|
398
398
|
var MAX_FRAMES = 50;
|
|
@@ -862,10 +862,10 @@ var Analytics = class {
|
|
|
862
862
|
// so before this the only way to report was to commit a key literal, and the
|
|
863
863
|
// fleet grew one copy per site of a value with a single source.
|
|
864
864
|
//
|
|
865
|
-
// A surface no brand claims still has no key, and the
|
|
865
|
+
// A surface no brand claims still has no key, and the edge REFUSES an
|
|
866
866
|
// unattributed event (401 `ingest_key_required`) — the reserved `$public`
|
|
867
867
|
// tenant that once caught keyless beacons is retired, and anonymous ingest is
|
|
868
|
-
// refused at every
|
|
868
|
+
// refused at every edge on every brand host. So there is no quiet fallback to
|
|
869
869
|
// rely on: an event lands in the org a credential names, or it does not land.
|
|
870
870
|
ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY") ?? keyForPage()
|
|
871
871
|
};
|
|
@@ -1003,7 +1003,7 @@ var Analytics = class {
|
|
|
1003
1003
|
this.cohort = mergeCohort(patch);
|
|
1004
1004
|
}
|
|
1005
1005
|
/** flush drains the buffer to the server as ONE batch through the ONE ingest
|
|
1006
|
-
*
|
|
1006
|
+
* endpoint POST /v1/event, body { batch: [Event…] }. beacon=true selects the
|
|
1007
1007
|
* unload-safe transport. Auth is orthogonal to the wire:
|
|
1008
1008
|
*
|
|
1009
1009
|
* • publishable key set → rides ?ingest_key=pk-… on both sends, keeping each
|
|
@@ -1200,15 +1200,15 @@ var COHORTS = {
|
|
|
1200
1200
|
*
|
|
1201
1201
|
* The two call sites:
|
|
1202
1202
|
* 1. src/storage.ts — the bundled npm client; IMPORTS this file.
|
|
1203
|
-
* 2. hanzoai/cloud apps/analytics/tag.js — the tag
|
|
1203
|
+
* 2. hanzoai/cloud apps/analytics/tag.js — the tag served at
|
|
1204
1204
|
* /v1/event.js; vendors this file and its tag.go serves the marked region
|
|
1205
|
-
* with the tag as one asset, so
|
|
1205
|
+
* with the tag as one asset, so cloud holds no second copy either.
|
|
1206
1206
|
*
|
|
1207
1207
|
* (2) has no bundler and cannot import anything, which is why the chain lives in a
|
|
1208
1208
|
* file that is plain ES5 rather than in a .ts: the region between the BEGIN and END
|
|
1209
1209
|
* markers is COPIED VERBATIM. Keep it ES5, dependency-free, `hz`-prefixed (it is
|
|
1210
1210
|
* spliced into other people's scopes) and unformatted — a reformat here is a diff
|
|
1211
|
-
* against the vendored copy. After editing, resync
|
|
1211
|
+
* against the vendored copy. After editing, resync that copy:
|
|
1212
1212
|
*
|
|
1213
1213
|
* curl -fsSL https://unpkg.com/@hanzo/event/src/anon.js -o apps/analytics/anon.js
|
|
1214
1214
|
*/
|