@hanzo/event 0.3.8 → 0.3.10

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.
@@ -100,14 +100,26 @@ interface AnalyticsConfig {
100
100
  /** Bearer token provider for token-auth apps. Omit for cookie/session apps
101
101
  * (the client then relies on same-origin credentials). */
102
102
  getToken?: () => string | undefined | null;
103
- /** Publishable ingest key (pk_…). When set, the client authenticates to the ONE
104
- * front door `/v1/event` with this key instead of a bearer/cookie: it rides
105
- * Authorization: Bearer pk_… on fetch and ?ingest_key=pk_… on a headerless
106
- * page-unload beacon, so anonymous traffic is accepted and unload beacons work
107
- * without a bearer. The key is write-only (cannot read) and safe to ship in a
108
- * bundle; mint one per org via POST /v1/ingest/keys. This authenticates the
109
- * EVENT STREAM only — the error plane authenticates independently with `dsn`,
110
- * and one does not stand in for the other. */
103
+ /** Publishable ingest key (pk-…). When set, the client attributes writes to the
104
+ * ONE front door `/v1/event` with this key instead of a bearer/cookie: it rides
105
+ * Authorization: Bearer pk-… on fetch and ?ingest_key=pk-… on a headerless
106
+ * page-unload beacon, so ANONYMOUS traffic is attributed and unload beacons work
107
+ * without a bearer. The key is write-only it attributes a write and never mints
108
+ * a reading principal so it is safe to ship in a bundle. Mint one per org with
109
+ * POST /v1/keys {"type":"publishable"}.
110
+ *
111
+ * Omit it and the client reads NEXT_PUBLIC_HANZO_EVENT_KEY, then HANZO_EVENT_KEY,
112
+ * from the inlined build env — the same resolution `dsn` uses, so a surface
113
+ * declares BOTH planes the same way and neither needs code to switch on.
114
+ *
115
+ * A surface with no key at all still reports for whoever is SIGNED IN (the
116
+ * session credential attributes them), and drops every logged-out visitor: the
117
+ * door refuses an unattributable write rather than filing it where its owner
118
+ * cannot read it. That failure is invisible from the page, which is why the key
119
+ * belongs in the env next to the DSN and not in a checklist.
120
+ *
121
+ * This attributes the EVENT STREAM only — the error plane authenticates
122
+ * independently with `dsn`, and one does not stand in for the other. */
111
123
  ingestKey?: string;
112
124
  /** Max events buffered before an automatic flush. */
113
125
  batchSize?: number;
@@ -100,14 +100,26 @@ interface AnalyticsConfig {
100
100
  /** Bearer token provider for token-auth apps. Omit for cookie/session apps
101
101
  * (the client then relies on same-origin credentials). */
102
102
  getToken?: () => string | undefined | null;
103
- /** Publishable ingest key (pk_…). When set, the client authenticates to the ONE
104
- * front door `/v1/event` with this key instead of a bearer/cookie: it rides
105
- * Authorization: Bearer pk_… on fetch and ?ingest_key=pk_… on a headerless
106
- * page-unload beacon, so anonymous traffic is accepted and unload beacons work
107
- * without a bearer. The key is write-only (cannot read) and safe to ship in a
108
- * bundle; mint one per org via POST /v1/ingest/keys. This authenticates the
109
- * EVENT STREAM only — the error plane authenticates independently with `dsn`,
110
- * and one does not stand in for the other. */
103
+ /** Publishable ingest key (pk-…). When set, the client attributes writes to the
104
+ * ONE front door `/v1/event` with this key instead of a bearer/cookie: it rides
105
+ * Authorization: Bearer pk-… on fetch and ?ingest_key=pk-… on a headerless
106
+ * page-unload beacon, so ANONYMOUS traffic is attributed and unload beacons work
107
+ * without a bearer. The key is write-only it attributes a write and never mints
108
+ * a reading principal so it is safe to ship in a bundle. Mint one per org with
109
+ * POST /v1/keys {"type":"publishable"}.
110
+ *
111
+ * Omit it and the client reads NEXT_PUBLIC_HANZO_EVENT_KEY, then HANZO_EVENT_KEY,
112
+ * from the inlined build env — the same resolution `dsn` uses, so a surface
113
+ * declares BOTH planes the same way and neither needs code to switch on.
114
+ *
115
+ * A surface with no key at all still reports for whoever is SIGNED IN (the
116
+ * session credential attributes them), and drops every logged-out visitor: the
117
+ * door refuses an unattributable write rather than filing it where its owner
118
+ * cannot read it. That failure is invisible from the page, which is why the key
119
+ * belongs in the env next to the DSN and not in a checklist.
120
+ *
121
+ * This attributes the EVENT STREAM only — the error plane authenticates
122
+ * independently with `dsn`, and one does not stand in for the other. */
111
123
  ingestKey?: string;
112
124
  /** Max events buffered before an automatic flush. */
113
125
  batchSize?: number;
package/dist/index.cjs CHANGED
@@ -219,8 +219,34 @@ function scrubText(s2, capturePII = false) {
219
219
  return s2;
220
220
  }
221
221
 
222
+ // src/uid.ts
223
+ var HEX = Array.from({ length: 256 }, (_, i) => (i + 256).toString(16).slice(1));
224
+ function fill(b) {
225
+ const c = typeof crypto !== "undefined" ? crypto : void 0;
226
+ if (c && typeof c.getRandomValues === "function") {
227
+ c.getRandomValues(b);
228
+ return b;
229
+ }
230
+ for (let i = 0; i < b.length; i++) b[i] = Math.random() * 256 | 0;
231
+ return b;
232
+ }
233
+ function uuidv7(now = Date.now()) {
234
+ const b = fill(new Uint8Array(16));
235
+ let t = Math.floor(now);
236
+ for (let i = 5; i >= 0; i--) {
237
+ b[i] = t % 256;
238
+ t = Math.floor(t / 256);
239
+ }
240
+ b[6] = 112 | b[6] & 15;
241
+ b[8] = 128 | b[8] & 63;
242
+ return HEX[b[0]] + HEX[b[1]] + HEX[b[2]] + HEX[b[3]] + "-" + HEX[b[4]] + HEX[b[5]] + "-" + HEX[b[6]] + HEX[b[7]] + "-" + HEX[b[8]] + HEX[b[9]] + "-" + HEX[b[10]] + HEX[b[11]] + HEX[b[12]] + HEX[b[13]] + HEX[b[14]] + HEX[b[15]];
243
+ }
244
+ function uuidv7Time(id) {
245
+ return parseInt(id.slice(0, 8) + id.slice(9, 13), 16);
246
+ }
247
+
222
248
  // src/version.ts
223
- var VERSION = "0.3.8";
249
+ var VERSION = "0.3.9";
224
250
 
225
251
  // src/sentry.ts
226
252
  var MAX_FRAMES = 50;
@@ -229,11 +255,7 @@ var MAX_LINE_LEN = 2048;
229
255
  var MAX_TAG_LEN = 1024;
230
256
  var MAX_TAGS = 50;
231
257
  function eventId() {
232
- const c = typeof crypto !== "undefined" ? crypto : void 0;
233
- if (c && "randomUUID" in c) return c.randomUUID().replace(/-/g, "");
234
- let s2 = "";
235
- for (let i = 0; i < 32; i++) s2 += Math.floor(Math.random() * 16).toString(16);
236
- return s2;
258
+ return uuidv7().replace(/-/g, "");
237
259
  }
238
260
  function byteLen(s2) {
239
261
  if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s2).length;
@@ -433,17 +455,12 @@ function ls() {
433
455
  return void 0;
434
456
  }
435
457
  }
436
- function uid() {
437
- const c = typeof crypto !== "undefined" ? crypto : void 0;
438
- if (c && "randomUUID" in c) return c.randomUUID();
439
- return "a-" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
440
- }
441
458
  function anonId() {
442
459
  const s2 = ls();
443
460
  if (!s2) return void 0;
444
461
  let v = s2.getItem(KEY.anon);
445
462
  if (!v) {
446
- v = uid();
463
+ v = uuidv7();
447
464
  s2.setItem(KEY.anon, v);
448
465
  }
449
466
  return v;
@@ -458,7 +475,7 @@ function sessionId(now = Date.now()) {
458
475
  state = null;
459
476
  }
460
477
  if (!state || now - state.last > SESSION_TTL_MS) {
461
- state = { id: uid(), last: now };
478
+ state = { id: uuidv7(now), last: now };
462
479
  } else {
463
480
  state.last = now;
464
481
  }
@@ -527,11 +544,6 @@ function readEnv(name) {
527
544
  function appendQuery(url, key, value) {
528
545
  return url + (url.includes("?") ? "&" : "?") + key + "=" + encodeURIComponent(value);
529
546
  }
530
- function uid2() {
531
- const c = typeof crypto !== "undefined" ? crypto : void 0;
532
- if (c && "randomUUID" in c) return c.randomUUID();
533
- return "m-" + Date.now().toString(36) + Math.random().toString(36).slice(2, 10);
534
- }
535
547
  function normalizeError2(err) {
536
548
  const n = normalizeError(err);
537
549
  return { type: n.name, message: n.message, stack: n.stack };
@@ -604,7 +616,15 @@ var Analytics = class {
604
616
  flushIntervalMs: 5e3,
605
617
  enabled: true,
606
618
  captureErrors: true,
607
- ...config
619
+ ...config,
620
+ // The publishable key resolves the SAME way the DSN below does: an explicit
621
+ // config wins, else the inlined build-time env. Without this the key was the
622
+ // one piece of wiring a surface could not declare the way it declares every
623
+ // other piece, so every surface that shipped without passing it in code sent
624
+ // its beacons unattributed — and an unattributed write is refused (401
625
+ // ingest_key_required), which is silent in the page and invisible until you
626
+ // read the warehouse and find the host missing entirely.
627
+ ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_HANZO_EVENT_KEY") ?? readEnv("HANZO_EVENT_KEY")
608
628
  };
609
629
  this.transport = config.transport ?? new DefaultTransport();
610
630
  this.dsn = parseDsn(config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product));
@@ -783,7 +803,7 @@ var Analytics = class {
783
803
  build(kind, event, extra) {
784
804
  const anon = anonId();
785
805
  const wire = {
786
- messageId: uid2(),
806
+ messageId: uuidv7(),
787
807
  type: kind,
788
808
  event,
789
809
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1006,5 +1026,7 @@ exports.parseDsn = parseDsn;
1006
1026
  exports.redactSecrets = redactSecrets;
1007
1027
  exports.scrubPII = scrubPII;
1008
1028
  exports.scrubText = scrubText;
1029
+ exports.uuidv7 = uuidv7;
1030
+ exports.uuidv7Time = uuidv7Time;
1009
1031
  //# sourceMappingURL=index.cjs.map
1010
1032
  //# sourceMappingURL=index.cjs.map