@hanzo/event 0.3.41 → 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/core.d.ts +8 -0
- package/dist/core.d.ts.map +1 -1
- package/dist/events.d.ts +1 -1
- package/dist/index.cjs +133 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +130 -6
- package/dist/index.mjs.map +1 -1
- package/dist/link.d.ts +28 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/react.cjs +129 -5
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +129 -5
- package/dist/react.mjs.map +1 -1
- package/dist/scrub.d.ts +5 -0
- package/dist/scrub.d.ts.map +1 -1
- package/dist/storage.d.ts +6 -0
- package/dist/storage.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/anon.d.ts +3 -0
- package/src/anon.js +19 -1
- package/src/core.test.ts +19 -0
- package/src/core.ts +52 -7
- package/src/events.ts +1 -1
- package/src/index.ts +2 -0
- package/src/link.test.ts +222 -0
- package/src/link.ts +103 -0
- package/src/scrub.test.ts +34 -1
- package/src/scrub.ts +20 -0
- package/src/storage.ts +14 -1
package/dist/react.mjs
CHANGED
|
@@ -247,11 +247,17 @@ var RE_CREDENTIAL_PARAM = new RegExp(
|
|
|
247
247
|
function redactCredentialParams(s) {
|
|
248
248
|
return s.replace(RE_CREDENTIAL_PARAM, (_m, prefix) => prefix + REDACTED);
|
|
249
249
|
}
|
|
250
|
+
var RE_SECRET43 = /(^|[^A-Za-z0-9_-]|%[0-9A-Fa-f]{2})((?=[A-Za-z0-9_-]{0,42}[a-z])(?=[A-Za-z0-9_-]{0,42}[A-Z])[A-Za-z0-9_-]{43})(?![A-Za-z0-9_-])/g;
|
|
250
251
|
function redactSecrets(s) {
|
|
251
252
|
s = redactCredentialParams(s);
|
|
252
253
|
for (const re of SECRET_PATTERNS) s = s.replace(re, REDACTED);
|
|
254
|
+
s = s.replace(RE_SECRET43, (_m, lead) => lead + REDACTED);
|
|
253
255
|
return redactPAN(s);
|
|
254
256
|
}
|
|
257
|
+
function withoutFragment(url) {
|
|
258
|
+
const at = url.indexOf("#");
|
|
259
|
+
return at < 0 ? url : url.slice(0, at);
|
|
260
|
+
}
|
|
255
261
|
function scrubPII(s) {
|
|
256
262
|
s = s.replace(RE_EMAIL, EMAIL_MARK);
|
|
257
263
|
s = s.replace(RE_IPV6, IP_MARK);
|
|
@@ -378,9 +384,18 @@ function hzAnonId() {
|
|
|
378
384
|
}
|
|
379
385
|
return id;
|
|
380
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
|
+
}
|
|
381
396
|
|
|
382
397
|
// package.json
|
|
383
|
-
var version = "0.3.
|
|
398
|
+
var version = "0.3.43";
|
|
384
399
|
|
|
385
400
|
// src/version.ts
|
|
386
401
|
var VERSION = version;
|
|
@@ -700,6 +715,13 @@ function sessionId(now = Date.now()) {
|
|
|
700
715
|
s.setItem(KEY.session, JSON.stringify(state));
|
|
701
716
|
return state.id;
|
|
702
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
|
+
}
|
|
703
725
|
function getFirstTouch() {
|
|
704
726
|
const s = ls();
|
|
705
727
|
if (!s) return void 0;
|
|
@@ -739,6 +761,76 @@ function mergeCohort(patch) {
|
|
|
739
761
|
return next;
|
|
740
762
|
}
|
|
741
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
|
+
|
|
742
834
|
// src/core.ts
|
|
743
835
|
var EVENT_PATH = "/v1/event";
|
|
744
836
|
var DEFAULT_HOST = "https://api.hanzo.ai";
|
|
@@ -899,8 +991,19 @@ var Analytics = class {
|
|
|
899
991
|
if (this.started || !this.cfg.enabled) return;
|
|
900
992
|
this.started = true;
|
|
901
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
|
+
}
|
|
902
1004
|
const parsed = parseAttribution(window.location.search, document.referrer);
|
|
903
|
-
|
|
1005
|
+
const first = linked.firstTouch && !getFirstTouch() ? setFirstTouchOnce(linked.firstTouch) : void 0;
|
|
1006
|
+
this.attribution = first ?? (hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed);
|
|
904
1007
|
this.cohort = mergeCohort({
|
|
905
1008
|
channel: this.attribution.channel ?? deriveChannel(this.attribution),
|
|
906
1009
|
refCode: this.attribution.refCode
|
|
@@ -919,6 +1022,27 @@ var Analytics = class {
|
|
|
919
1022
|
});
|
|
920
1023
|
}
|
|
921
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
|
+
}
|
|
922
1046
|
/** identify binds the current visitor to a stable person id (post-login). */
|
|
923
1047
|
identify(personId, traits) {
|
|
924
1048
|
this.personId = personId;
|
|
@@ -1102,9 +1226,9 @@ var Analytics = class {
|
|
|
1102
1226
|
...extra
|
|
1103
1227
|
};
|
|
1104
1228
|
const capturePII = this.cfg.capturePII ?? false;
|
|
1105
|
-
if (wire.url) wire.url = scrubText(wire.url, capturePII);
|
|
1106
|
-
if (wire.path) wire.path = scrubText(wire.path, capturePII);
|
|
1107
|
-
if (wire.referrer) wire.referrer = scrubText(wire.referrer, capturePII);
|
|
1229
|
+
if (wire.url) wire.url = scrubText(withoutFragment(wire.url), capturePII);
|
|
1230
|
+
if (wire.path) wire.path = scrubText(withoutFragment(wire.path), capturePII);
|
|
1231
|
+
if (wire.referrer) wire.referrer = scrubText(withoutFragment(wire.referrer), capturePII);
|
|
1108
1232
|
return wire;
|
|
1109
1233
|
}
|
|
1110
1234
|
schedule() {
|