@hanzo/event 0.3.32 → 0.3.34
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 +17 -19
- package/dist/index.cjs +93 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -3
- package/dist/index.d.ts +77 -3
- package/dist/index.mjs +89 -24
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +88 -23
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +88 -23
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -3
- package/src/anon.d.ts +2 -2
- package/src/anon.js +43 -23
- package/src/anon.test.ts +17 -38
- package/src/core.ts +16 -6
- package/src/index.ts +7 -0
- package/src/org.test.ts +113 -0
- package/src/org.ts +129 -0
- package/src/stack.ts +144 -0
- package/src/storage.test.ts +3 -3
- package/src/storage.ts +1 -1
- package/src/uid.ts +1 -1
- package/src/version.ts +1 -1
- package/hz.js +0 -511
- package/src/hz.test.ts +0 -376
package/README.md
CHANGED
|
@@ -27,29 +27,27 @@ starve the other:
|
|
|
27
27
|
> plane is **inert**: nothing is sent, nothing throws, analytics is unaffected.
|
|
28
28
|
> Assert `client.errorPlaneEnabled` if you want to know which you have.
|
|
29
29
|
|
|
30
|
-
### No bundler? Use the
|
|
30
|
+
### No bundler? Use the hosted tag
|
|
31
31
|
|
|
32
32
|
```html
|
|
33
|
-
<script
|
|
34
|
-
data-product="hanzo.ai"
|
|
35
|
-
data-ingest-key="pk-…"
|
|
36
|
-
data-capture="1"></script>
|
|
33
|
+
<script defer src="https://api.hanzo.ai/v1/event.js" data-key="pk-…"></script>
|
|
37
34
|
```
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 door that eats the
|
|
38
|
+
events, so a caller allowlists ONE host and the tag can never drift from the wire
|
|
39
|
+
it posts to. It adds DOM **autocapture** — `$click` (with an element locator),
|
|
40
|
+
`$outbound`, `$scroll`, `$form`, `$vitals` — which a bundled app does not need and
|
|
41
|
+
a plain page cannot get, and it carries the site's tag config from `/v1/tags`, so a
|
|
42
|
+
pixel is configured on the project rather than pasted into the page.
|
|
43
|
+
|
|
44
|
+
> **`data-key` is a publishable `pk-`, and without one the tag is INERT.** A write
|
|
45
|
+
> the door cannot attribute is refused (`401 ingest_key_required`) silently, so a
|
|
46
|
+
> keyless tag measures fine in the browser and files nothing. Mint one with
|
|
47
|
+
> `POST /v1/keys {"type":"publishable"}`.
|
|
48
|
+
|
|
49
|
+
This package is the client for a surface that **builds**. There is no second
|
|
50
|
+
script-tag distribution here: one wire, one door, one tag.
|
|
53
51
|
|
|
54
52
|
It honours the same consent sources the bundled stack does: an explicit stored
|
|
55
53
|
choice (`hz_consent`, the key a Hanzo consent banner writes) outranks the browser
|
package/dist/index.cjs
CHANGED
|
@@ -91,6 +91,56 @@ function dsnForProduct(product, key) {
|
|
|
91
91
|
return `https://${key}@api.hanzo.ai/v1/event/${projectId}`;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// src/org.ts
|
|
95
|
+
var ORG_DOMAIN = Object.freeze({
|
|
96
|
+
hanzo: Object.freeze(["hanzo.ai", "hanzo.app", "hanzo.chat", "hanzo.id", "hanzo.bot", "hanzo.sh"]),
|
|
97
|
+
lux: Object.freeze([
|
|
98
|
+
"lux.network",
|
|
99
|
+
"lux.exchange",
|
|
100
|
+
"lux.market",
|
|
101
|
+
"lux.finance",
|
|
102
|
+
"lux.financial",
|
|
103
|
+
"lux.credit",
|
|
104
|
+
"lux.fund",
|
|
105
|
+
"lux.id"
|
|
106
|
+
]),
|
|
107
|
+
zoo: Object.freeze(["zoo.ngo", "zoo.network", "zoolabs.id"])
|
|
108
|
+
});
|
|
109
|
+
var ORG_KEY = Object.freeze({
|
|
110
|
+
hanzo: "pk-rM_CdaF2MQckGCrla113SrR1oH4zvqN8xh2I95Z9tY8",
|
|
111
|
+
lux: "pk-gUZp6ZVfhJzSwK-rb4oLbVkpCnMBx5uSCpxf_5yEhQk",
|
|
112
|
+
zoo: "pk-3TKpKnERV9AQSsBUERWkZejC1O1mUxc1jRzsP3MPbs4"
|
|
113
|
+
});
|
|
114
|
+
var PUBLISHABLE = "pk-";
|
|
115
|
+
var normalize = (host) => host.trim().toLowerCase().replace(/\.$/, "").split(":")[0] ?? "";
|
|
116
|
+
function orgOf(host) {
|
|
117
|
+
const h = normalize(host);
|
|
118
|
+
if (!h) return void 0;
|
|
119
|
+
let best;
|
|
120
|
+
let bestLen = 0;
|
|
121
|
+
for (const [org, domains] of Object.entries(ORG_DOMAIN)) {
|
|
122
|
+
for (const d of domains) {
|
|
123
|
+
if ((h === d || h.endsWith(`.${d}`)) && d.length > bestLen) {
|
|
124
|
+
best = org;
|
|
125
|
+
bestLen = d.length;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return best;
|
|
130
|
+
}
|
|
131
|
+
function keyFor(host, keyring = ORG_KEY) {
|
|
132
|
+
const org = orgOf(host);
|
|
133
|
+
if (!org) return void 0;
|
|
134
|
+
const key = keyring[org];
|
|
135
|
+
if (typeof key !== "string") return void 0;
|
|
136
|
+
const trimmed = key.trim();
|
|
137
|
+
return trimmed.startsWith(PUBLISHABLE) ? trimmed : void 0;
|
|
138
|
+
}
|
|
139
|
+
function keyForPage(keyring = ORG_KEY) {
|
|
140
|
+
if (typeof location === "undefined") return void 0;
|
|
141
|
+
return keyFor(location.hostname, keyring);
|
|
142
|
+
}
|
|
143
|
+
|
|
94
144
|
// src/scrub.ts
|
|
95
145
|
var REDACTED = "[redacted]";
|
|
96
146
|
var EMAIL_MARK = "[email]";
|
|
@@ -212,8 +262,8 @@ function scrubText(s, capturePII = false) {
|
|
|
212
262
|
}
|
|
213
263
|
|
|
214
264
|
// src/anon.js
|
|
215
|
-
var HZ_ANON_KEY = "
|
|
216
|
-
var
|
|
265
|
+
var HZ_ANON_KEY = "iam-anon-id";
|
|
266
|
+
var HZ_ANON_ADOPT = ["hz_anon_id", "hz_id"];
|
|
217
267
|
var HZ_ANON_DOMAIN = "hanzo.ai";
|
|
218
268
|
var HZ_ANON_MAX_AGE = 2 * 365 * 24 * 60 * 60;
|
|
219
269
|
var hzAnonMemo;
|
|
@@ -300,10 +350,17 @@ function hzAnonWrite(name, value) {
|
|
|
300
350
|
} catch (e) {
|
|
301
351
|
}
|
|
302
352
|
}
|
|
353
|
+
function hzAnonAdopted(s) {
|
|
354
|
+
for (var i = 0; i < HZ_ANON_ADOPT.length; i++) {
|
|
355
|
+
var id = hzAnonCookie(HZ_ANON_ADOPT[i]) || hzAnonItem(s, HZ_ANON_ADOPT[i]);
|
|
356
|
+
if (id) return id;
|
|
357
|
+
}
|
|
358
|
+
return "";
|
|
359
|
+
}
|
|
303
360
|
function hzAnonId() {
|
|
304
361
|
if (typeof window === "undefined") return "";
|
|
305
362
|
var s = hzAnonStore();
|
|
306
|
-
var id = hzAnonCookie(HZ_ANON_KEY) || hzAnonItem(s, HZ_ANON_KEY) ||
|
|
363
|
+
var id = hzAnonCookie(HZ_ANON_KEY) || hzAnonItem(s, HZ_ANON_KEY) || hzAnonAdopted(s) || hzAnonMemo || hzUuidv7();
|
|
307
364
|
hzAnonMemo = id;
|
|
308
365
|
hzAnonWrite(HZ_ANON_KEY, id);
|
|
309
366
|
try {
|
|
@@ -319,7 +376,7 @@ function uuidv7Time(id) {
|
|
|
319
376
|
}
|
|
320
377
|
|
|
321
378
|
// src/version.ts
|
|
322
|
-
var VERSION = "0.3.
|
|
379
|
+
var VERSION = "0.3.34";
|
|
323
380
|
|
|
324
381
|
// src/sentry.ts
|
|
325
382
|
var MAX_FRAMES = 50;
|
|
@@ -781,12 +838,20 @@ var Analytics = class {
|
|
|
781
838
|
// carries end to end — KMS `deploy/PUBLISHABLE_KEY` -> the PUBLISHABLE_KEY
|
|
782
839
|
// build-arg -> the NEXT_PUBLIC_ prefix Next inlines.
|
|
783
840
|
//
|
|
784
|
-
//
|
|
785
|
-
//
|
|
786
|
-
//
|
|
787
|
-
//
|
|
788
|
-
//
|
|
789
|
-
|
|
841
|
+
// 3. the org that owns the HOST this page is served from (`org.ts`).
|
|
842
|
+
//
|
|
843
|
+
// Step 3 fires only when a surface configured NOTHING, so it never overrides
|
|
844
|
+
// the KMS chain above — it replaces going dark with reporting to the right
|
|
845
|
+
// brand. That is what a static export needs: `output: export` inlines no env,
|
|
846
|
+
// so before this the only way to report was to commit a key literal, and the
|
|
847
|
+
// fleet grew one copy per site of a value with a single source.
|
|
848
|
+
//
|
|
849
|
+
// A surface no brand claims still has no key, and the door REFUSES an
|
|
850
|
+
// unattributed event (401 `ingest_key_required`) — the reserved `$public`
|
|
851
|
+
// tenant that once caught keyless beacons is retired, and anonymous ingest is
|
|
852
|
+
// refused at every door on every brand host. So there is no quiet fallback to
|
|
853
|
+
// rely on: an event lands in the org a credential names, or it does not land.
|
|
854
|
+
ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY") ?? keyForPage()
|
|
790
855
|
};
|
|
791
856
|
this.transport = config.transport ?? new DefaultTransport();
|
|
792
857
|
this.dsn = parseDsn(
|
|
@@ -1110,26 +1175,26 @@ var COHORTS = {
|
|
|
1110
1175
|
channel: { field: "channel", label: "Acquisition channel" },
|
|
1111
1176
|
refCode: { field: "ref_code", label: "Referral code" }
|
|
1112
1177
|
};
|
|
1113
|
-
/*! anon.js — THE anonymous-identity chain. ONE implementation,
|
|
1178
|
+
/*! anon.js — THE anonymous-identity chain. ONE implementation, two distributions.
|
|
1114
1179
|
*
|
|
1115
1180
|
* One browser is ONE person on every Hanzo surface, whichever client a page
|
|
1116
|
-
* happens to have loaded. There were three implementations writing TWO keys —
|
|
1117
|
-
* `hz_anon_id`
|
|
1118
|
-
*
|
|
1181
|
+
* happens to have loaded. There were once three implementations writing TWO keys —
|
|
1182
|
+
* `hz_anon_id` and `hz_id` — so the same visitor was several people depending on
|
|
1183
|
+
* which snippet the surface shipped.
|
|
1119
1184
|
*
|
|
1120
|
-
* The
|
|
1185
|
+
* The two call sites:
|
|
1121
1186
|
* 1. src/storage.ts — the bundled npm client; IMPORTS this file.
|
|
1122
|
-
* 2.
|
|
1123
|
-
* 3. hanzoai/cloud apps/analytics/tag.js — the tag the door hosts at
|
|
1187
|
+
* 2. hanzoai/cloud apps/analytics/tag.js — the tag the door hosts at
|
|
1124
1188
|
* /v1/event.js; vendors this file and its tag.go serves the marked region
|
|
1125
1189
|
* with the tag as one asset, so the door holds no second copy either.
|
|
1126
1190
|
*
|
|
1127
|
-
* (2)
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1191
|
+
* (2) has no bundler and cannot import anything, which is why the chain lives in a
|
|
1192
|
+
* file that is plain ES5 rather than in a .ts: the region between the BEGIN and END
|
|
1193
|
+
* markers is COPIED VERBATIM. Keep it ES5, dependency-free, `hz`-prefixed (it is
|
|
1194
|
+
* spliced into other people's scopes) and unformatted — a reformat here is a diff
|
|
1195
|
+
* against the vendored copy. After editing, resync the door:
|
|
1196
|
+
*
|
|
1197
|
+
* curl -fsSL https://unpkg.com/@hanzo/event/src/anon.js -o apps/analytics/anon.js
|
|
1133
1198
|
*/
|
|
1134
1199
|
|
|
1135
1200
|
Object.defineProperty(exports, "EVENTS", {
|
|
@@ -1159,6 +1224,8 @@ Object.defineProperty(exports, "eventsOf", {
|
|
|
1159
1224
|
exports.Analytics = Analytics;
|
|
1160
1225
|
exports.COHORTS = COHORTS;
|
|
1161
1226
|
exports.GOALS = GOALS;
|
|
1227
|
+
exports.ORG_DOMAIN = ORG_DOMAIN;
|
|
1228
|
+
exports.ORG_KEY = ORG_KEY;
|
|
1162
1229
|
exports.PRODUCT_PROJECT = PRODUCT_PROJECT;
|
|
1163
1230
|
exports.VERSION = VERSION;
|
|
1164
1231
|
exports.buildEnvelope = buildEnvelope;
|
|
@@ -1176,6 +1243,9 @@ exports.getFirstTouch = getFirstTouch;
|
|
|
1176
1243
|
exports.hasAttribution = hasAttribution;
|
|
1177
1244
|
exports.hostOf = hostOf;
|
|
1178
1245
|
exports.isoWeek = isoWeek;
|
|
1246
|
+
exports.keyFor = keyFor;
|
|
1247
|
+
exports.keyForPage = keyForPage;
|
|
1248
|
+
exports.orgOf = orgOf;
|
|
1179
1249
|
exports.parseAttribution = parseAttribution;
|
|
1180
1250
|
exports.parseDsn = parseDsn;
|
|
1181
1251
|
exports.redactSecrets = redactSecrets;
|