@hanzo/event 0.3.26 → 0.3.28

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/dist/react.cjs CHANGED
@@ -309,7 +309,7 @@ function hzAnonId() {
309
309
  }
310
310
 
311
311
  // src/version.ts
312
- var VERSION = "0.3.26";
312
+ var VERSION = "0.3.28";
313
313
 
314
314
  // src/sentry.ts
315
315
  var MAX_FRAMES = 50;
@@ -786,6 +786,20 @@ var Analytics = class {
786
786
  config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
787
787
  );
788
788
  }
789
+ /** adopt gives this client a credential it does not have. The key belongs to
790
+ * the stream, not to whichever caller happened to ask for the handle first,
791
+ * so a later caller carrying one hands it over. The error plane derives from
792
+ * the key, so it comes up here too when it was inert for want of one.
793
+ * Present fields are never overwritten: the first caller's key stays. */
794
+ adopt(config) {
795
+ if (config.ingestKey && !this.cfg.ingestKey) this.cfg.ingestKey = config.ingestKey;
796
+ if (config.getToken && !this.cfg.getToken) this.cfg.getToken = config.getToken;
797
+ if (!this.dsn) {
798
+ this.dsn = parseDsn(
799
+ config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
800
+ );
801
+ }
802
+ }
789
803
  /** errorPlaneEnabled reports whether captured exceptions can actually reach the
790
804
  * error host. False means a DSN was never configured — the documented
791
805
  * fail-safe. Exposed so an app (or a test) can assert its wiring instead of
@@ -836,9 +850,14 @@ var Analytics = class {
836
850
  group(groupId, traits) {
837
851
  this.enqueue("group", void 0, { groupId, properties: traits });
838
852
  }
839
- /** pageview records a $pageview for the current (or given) location. */
853
+ /** pageview records a $pageview for the current (or given) location, once per
854
+ * view. A view is the path plus the full location, so a query or hash change
855
+ * is a new one and a repeat call for the same place is not. */
840
856
  pageview(path, properties) {
841
857
  const p = path ?? (isBrowser() ? window.location.pathname : void 0);
858
+ const view = (p ?? "") + "\0" + (isBrowser() ? window.location.href : "");
859
+ if (view === this.counted) return;
860
+ this.counted = view;
842
861
  this.enqueue("pageview", events.PAGEVIEW, { path: p, properties });
843
862
  }
844
863
  /** capture records a named product event with optional properties. Commerce
@@ -899,8 +918,8 @@ var Analytics = class {
899
918
  * front door POST /v1/event, body { batch: [Event…] }. beacon=true selects the
900
919
  * unload-safe transport. Auth is orthogonal to the wire:
901
920
  *
902
- * • publishable key set → rides Authorization: Bearer pk_… (fetch) or
903
- * ?ingest_key=pk_… (beacon), so unload beacons work anonymously.
921
+ * • publishable key set → rides ?ingest_key=pk-… on both sends, keeping each
922
+ * a CORS-simple request that any origin may send.
904
923
  * • else a bearer JWT rides Authorization (fetch only — sendBeacon cannot
905
924
  * carry a header, so token apps fall back to keepalive fetch on unload).
906
925
  * • else a cookie app rides same-origin credentials (beacon carries the
@@ -1023,19 +1042,35 @@ var Analytics = class {
1023
1042
  }
1024
1043
  }
1025
1044
  };
1045
+ var CLIENTS = /* @__PURE__ */ Symbol.for("hanzo.event.clients");
1046
+ function registry() {
1047
+ const g = globalThis;
1048
+ const existing = g[CLIENTS];
1049
+ if (existing) return existing;
1050
+ const fresh = /* @__PURE__ */ new Map();
1051
+ g[CLIENTS] = fresh;
1052
+ return fresh;
1053
+ }
1026
1054
  function createAnalytics(config) {
1027
- return new Analytics(config);
1055
+ if (!isBrowser() || config.enabled === false) return new Analytics(config);
1056
+ const key = (config.host ?? DEFAULT_HOST) + "\0" + config.product;
1057
+ const clients = registry();
1058
+ const existing = clients.get(key);
1059
+ if (existing) {
1060
+ existing.adopt(config);
1061
+ return existing;
1062
+ }
1063
+ const fresh = new Analytics(config);
1064
+ clients.set(key, fresh);
1065
+ return fresh;
1028
1066
  }
1029
1067
 
1030
1068
  // src/react.tsx
1031
1069
  var Ctx = react.createContext(null);
1032
1070
  function AnalyticsProvider(props) {
1033
1071
  const { client, config, autoPageview = true, children } = props;
1034
- const instance = react.useMemo(() => {
1035
- if (client) return client;
1036
- if (config) return createAnalytics(config);
1037
- throw new Error("AnalyticsProvider requires `client` or `config`");
1038
- }, [client]);
1072
+ if (!client && !config) throw new Error("AnalyticsProvider requires `client` or `config`");
1073
+ const instance = client ?? createAnalytics(config);
1039
1074
  react.useEffect(() => {
1040
1075
  instance.init();
1041
1076
  if (autoPageview) instance.pageview();