@hanzo/event 0.3.42 → 0.3.43

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.mjs CHANGED
@@ -384,9 +384,18 @@ function hzAnonId() {
384
384
  }
385
385
  return id;
386
386
  }
387
+ function hzAnonAdopt(id) {
388
+ hzAnonMemo = id;
389
+ hzAnonWrite(HZ_ANON_KEY, id);
390
+ try {
391
+ var s = hzAnonStore();
392
+ if (s) s.setItem(HZ_ANON_KEY, id);
393
+ } catch (e) {
394
+ }
395
+ }
387
396
 
388
397
  // package.json
389
- var version = "0.3.42";
398
+ var version = "0.3.43";
390
399
 
391
400
  // src/version.ts
392
401
  var VERSION = version;
@@ -706,6 +715,13 @@ function sessionId(now = Date.now()) {
706
715
  s.setItem(KEY.session, JSON.stringify(state));
707
716
  return state.id;
708
717
  }
718
+ function adoptSession(id, now = Date.now()) {
719
+ const s = ls();
720
+ if (s) s.setItem(KEY.session, JSON.stringify({ id, last: now }));
721
+ }
722
+ function adoptAnonId(id) {
723
+ hzAnonAdopt(id);
724
+ }
709
725
  function getFirstTouch() {
710
726
  const s = ls();
711
727
  if (!s) return void 0;
@@ -745,6 +761,76 @@ function mergeCohort(patch) {
745
761
  return next;
746
762
  }
747
763
 
764
+ // src/link.ts
765
+ var LINK_PARAMS = { anon: "hz_aid", session: "hz_sid", firstTouch: "hz_ft" };
766
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
767
+ var isId = (v) => !!v && UUID.test(v);
768
+ function encodeFirstTouch(a) {
769
+ return JSON.stringify([
770
+ a.utm.source,
771
+ a.utm.medium,
772
+ a.utm.campaign,
773
+ a.utm.term,
774
+ a.utm.content,
775
+ a.refCode,
776
+ a.referrer,
777
+ a.channel
778
+ ].map((v) => v ?? ""));
779
+ }
780
+ function decodeFirstTouch(raw) {
781
+ if (!raw || raw.length > 1024) return void 0;
782
+ try {
783
+ const t = JSON.parse(raw);
784
+ if (!Array.isArray(t) || t.length !== 8 || t.some((v) => typeof v !== "string")) return void 0;
785
+ const [source, medium, campaign, term, content, refCode, referrer, channel] = t;
786
+ const u = (v) => v || void 0;
787
+ return {
788
+ utm: { source: u(source), medium: u(medium), campaign: u(campaign), term: u(term), content: u(content) },
789
+ refCode: u(refCode),
790
+ referrer: u(referrer),
791
+ channel: u(channel)
792
+ };
793
+ } catch {
794
+ return void 0;
795
+ }
796
+ }
797
+ function linkUrl(url, state, fromHost, base) {
798
+ let u;
799
+ try {
800
+ u = new URL(url, base);
801
+ } catch {
802
+ return url;
803
+ }
804
+ if (u.protocol !== "https:" && u.protocol !== "http:") return url;
805
+ if (!orgOf(u.hostname) || u.hostname === fromHost) return url;
806
+ const set = (k, v) => v && u.searchParams.set(k, v);
807
+ if (isId(state.anonId)) set(LINK_PARAMS.anon, state.anonId);
808
+ if (isId(state.sessionId)) set(LINK_PARAMS.session, state.sessionId);
809
+ if (state.firstTouch) set(LINK_PARAMS.firstTouch, encodeFirstTouch(state.firstTouch));
810
+ return u.toString();
811
+ }
812
+ function readLink(search) {
813
+ const q = new URLSearchParams(search || "");
814
+ const a = q.get(LINK_PARAMS.anon);
815
+ const s = q.get(LINK_PARAMS.session);
816
+ return {
817
+ anonId: isId(a) ? a : void 0,
818
+ sessionId: isId(s) ? s : void 0,
819
+ firstTouch: decodeFirstTouch(q.get(LINK_PARAMS.firstTouch))
820
+ };
821
+ }
822
+ function stripLink(href) {
823
+ const u = new URL(href);
824
+ let hit = false;
825
+ for (const k of Object.values(LINK_PARAMS)) {
826
+ if (u.searchParams.has(k)) {
827
+ u.searchParams.delete(k);
828
+ hit = true;
829
+ }
830
+ }
831
+ return hit ? u.pathname + u.search + u.hash : void 0;
832
+ }
833
+
748
834
  // src/core.ts
749
835
  var EVENT_PATH = "/v1/event";
750
836
  var DEFAULT_HOST = "https://api.hanzo.ai";
@@ -905,8 +991,19 @@ var Analytics = class {
905
991
  if (this.started || !this.cfg.enabled) return;
906
992
  this.started = true;
907
993
  if (!isBrowser()) return;
994
+ const linked = readLink(window.location.search);
995
+ if (linked.anonId) adoptAnonId(linked.anonId);
996
+ if (linked.sessionId) adoptSession(linked.sessionId);
997
+ const clean = stripLink(window.location.href);
998
+ if (clean !== void 0) {
999
+ try {
1000
+ window.history.replaceState(window.history.state, "", clean);
1001
+ } catch {
1002
+ }
1003
+ }
908
1004
  const parsed = parseAttribution(window.location.search, document.referrer);
909
- this.attribution = hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed;
1005
+ const first = linked.firstTouch && !getFirstTouch() ? setFirstTouchOnce(linked.firstTouch) : void 0;
1006
+ this.attribution = first ?? (hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed);
910
1007
  this.cohort = mergeCohort({
911
1008
  channel: this.attribution.channel ?? deriveChannel(this.attribution),
912
1009
  refCode: this.attribution.refCode
@@ -925,6 +1022,27 @@ var Analytics = class {
925
1022
  });
926
1023
  }
927
1024
  }
1025
+ /** link returns `url` carrying this visitor's anonymous id, session and first
1026
+ * touch when the destination is a Hanzo-owned host, so the journey continues
1027
+ * across the hop. Any other URL comes back unchanged. */
1028
+ link(url) {
1029
+ if (!isBrowser()) return url;
1030
+ if (!this.started) this.init();
1031
+ return linkUrl(
1032
+ url,
1033
+ { anonId: anonId(), sessionId: sessionId(), firstTouch: this.attribution },
1034
+ window.location.hostname,
1035
+ window.location.href
1036
+ );
1037
+ }
1038
+ /** authorize decorates an OAuth authorize URL for the redirect to IAM and flushes
1039
+ * what is queued, because the page is about to unload. Assign the result to
1040
+ * `location`. */
1041
+ authorize(url) {
1042
+ const out = this.link(url);
1043
+ this.flush(true);
1044
+ return out;
1045
+ }
928
1046
  /** identify binds the current visitor to a stable person id (post-login). */
929
1047
  identify(personId, traits) {
930
1048
  this.personId = personId;