@hanzo/event 0.3.33 → 0.3.35

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 CHANGED
@@ -30,21 +30,33 @@ starve the other:
30
30
  ### No bundler? Use the hosted tag
31
31
 
32
32
  ```html
33
- <script defer src="https://api.hanzo.ai/v1/event.js" data-key="pk-…"></script>
33
+ <script defer src="https://api.hanzo.ai/v1/event/tag.js" data-key="pk-…"></script>
34
34
  ```
35
35
 
36
36
  That one line is the whole install, and it is the same line for a published site,
37
37
  hanzo.team and a customer's own page. The tag is served by the door that eats the
38
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.
39
+ it posts to `.js` is part of a segment rather than a child of one, so the tag
40
+ sits UNDER `/v1/event` rather than beside it, and the old sibling `/v1/event.js`
41
+ 404s.
42
+
43
+ It autocaptures pageviews — the first one and every SPA navigation, since
44
+ `history.pushState` fires no event of its own — plus uncaught errors and rejected
45
+ promises. Anything a page means on purpose goes through `window.hanzo`
46
+ (`track`, `identify`, `page`, `error`, `flush`). It also carries the site's tag
47
+ config from `/v1/projects/tags`, so a pixel is configured on the project rather
48
+ than pasted into the page.
49
+
50
+ > A bundled app should NOT add this tag. Both clients post pageviews to the same
51
+ > door, so a page running `@hanzo/event` and the tag counts every pageview twice.
52
+ > One surface, one client.
43
53
 
44
54
  > **`data-key` is a publishable `pk-`, and without one the tag is INERT.** A write
45
55
  > 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"}`.
56
+ > keyless tag measures fine in the browser and files nothing. A project mints one
57
+ > with itself: `POST /v1/projects`. A key that names no project is refused
58
+ > `403 ingest_key_unknown` — the door fails closed rather than filing a write it
59
+ > cannot attribute.
48
60
 
49
61
  This package is the client for a surface that **builds**. There is no second
50
62
  script-tag distribution here: one wire, one door, one tag.
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 = "hz_anon_id";
216
- var HZ_ANON_LEGACY_KEY = "hz_id";
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) || hzAnonItem(s, HZ_ANON_LEGACY_KEY) || hzAnonMemo || hzUuidv7();
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.33";
379
+ var VERSION = "0.3.35";
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
- // A surface that provides neither has no key, and its anonymous traffic
785
- // files under `$public` (which drops track/identify/group and answers 200);
786
- // the fix is to give it the KMS-sourced env, not to hardcode the org key
787
- // here. The key is a credential-class value: its home is KMS, and the ONE
788
- // build reads it from there.
789
- ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY")
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(
@@ -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;