@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/link.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Attribution } from './types';
|
|
2
|
+
export declare const LINK_PARAMS: {
|
|
3
|
+
readonly anon: 'hz_aid';
|
|
4
|
+
readonly session: 'hz_sid';
|
|
5
|
+
readonly firstTouch: 'hz_ft';
|
|
6
|
+
};
|
|
7
|
+
export declare const isId: (v: string | null | undefined) => v is string;
|
|
8
|
+
/** What a link carries. Any field may be absent. */
|
|
9
|
+
export interface LinkState {
|
|
10
|
+
anonId?: string;
|
|
11
|
+
sessionId?: string;
|
|
12
|
+
firstTouch?: Attribution;
|
|
13
|
+
}
|
|
14
|
+
/** First touch as a compact tuple: utm source, medium, campaign, term, content, refCode,
|
|
15
|
+
* referrer, channel. Short because it rides a URL; a tuple because keys cost bytes. */
|
|
16
|
+
export declare function encodeFirstTouch(a: Attribution): string;
|
|
17
|
+
export declare function decodeFirstTouch(raw: string | null | undefined): Attribution | undefined;
|
|
18
|
+
/** linkUrl returns `url` with the visitor's identity appended when its host is
|
|
19
|
+
* Hanzo-owned and is not `fromHost`; otherwise `url` unchanged. Relative URLs
|
|
20
|
+
* resolve against `base`. Never throws. */
|
|
21
|
+
export declare function linkUrl(url: string, state: LinkState, fromHost: string, base?: string): string;
|
|
22
|
+
/** readLink parses what a link carried out of a location.search value. Invalid
|
|
23
|
+
* values are dropped, not repaired. */
|
|
24
|
+
export declare function readLink(search: string): LinkState;
|
|
25
|
+
/** stripLink returns the path, query and hash of `href` without the link parameters, or
|
|
26
|
+
* undefined when there were none. */
|
|
27
|
+
export declare function stripLink(href: string): string | undefined;
|
|
28
|
+
//# sourceMappingURL=link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAE1C,eAAO,MAAM,WAAW;aAAK,IAAI,EAAE,QAAQ;aAAE,OAAO,EAAE,QAAQ;aAAE,UAAU,EAAE,OAAO;CAAW,CAAA;AAK9F,eAAO,MAAM,IAAI,MAAO,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,CAAC,IAAI,MAA6B,CAAA;AAEtF,oDAAoD;AACpD,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,WAAW,CAAA;CACzB;AAED;wFACwF;AACxF,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAKvD;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,CAgBxF;AAED;;4CAE4C;AAC5C,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAc9F;AAED;wCACwC;AACxC,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CASlD;AAED;sCACsC;AACtC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU1D"}
|
package/dist/react.cjs
CHANGED
|
@@ -249,11 +249,17 @@ var RE_CREDENTIAL_PARAM = new RegExp(
|
|
|
249
249
|
function redactCredentialParams(s) {
|
|
250
250
|
return s.replace(RE_CREDENTIAL_PARAM, (_m, prefix) => prefix + REDACTED);
|
|
251
251
|
}
|
|
252
|
+
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;
|
|
252
253
|
function redactSecrets(s) {
|
|
253
254
|
s = redactCredentialParams(s);
|
|
254
255
|
for (const re of SECRET_PATTERNS) s = s.replace(re, REDACTED);
|
|
256
|
+
s = s.replace(RE_SECRET43, (_m, lead) => lead + REDACTED);
|
|
255
257
|
return redactPAN(s);
|
|
256
258
|
}
|
|
259
|
+
function withoutFragment(url) {
|
|
260
|
+
const at = url.indexOf("#");
|
|
261
|
+
return at < 0 ? url : url.slice(0, at);
|
|
262
|
+
}
|
|
257
263
|
function scrubPII(s) {
|
|
258
264
|
s = s.replace(RE_EMAIL, EMAIL_MARK);
|
|
259
265
|
s = s.replace(RE_IPV6, IP_MARK);
|
|
@@ -380,9 +386,18 @@ function hzAnonId() {
|
|
|
380
386
|
}
|
|
381
387
|
return id;
|
|
382
388
|
}
|
|
389
|
+
function hzAnonAdopt(id) {
|
|
390
|
+
hzAnonMemo = id;
|
|
391
|
+
hzAnonWrite(HZ_ANON_KEY, id);
|
|
392
|
+
try {
|
|
393
|
+
var s = hzAnonStore();
|
|
394
|
+
if (s) s.setItem(HZ_ANON_KEY, id);
|
|
395
|
+
} catch (e) {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
383
398
|
|
|
384
399
|
// package.json
|
|
385
|
-
var version = "0.3.
|
|
400
|
+
var version = "0.3.43";
|
|
386
401
|
|
|
387
402
|
// src/version.ts
|
|
388
403
|
var VERSION = version;
|
|
@@ -702,6 +717,13 @@ function sessionId(now = Date.now()) {
|
|
|
702
717
|
s.setItem(KEY.session, JSON.stringify(state));
|
|
703
718
|
return state.id;
|
|
704
719
|
}
|
|
720
|
+
function adoptSession(id, now = Date.now()) {
|
|
721
|
+
const s = ls();
|
|
722
|
+
if (s) s.setItem(KEY.session, JSON.stringify({ id, last: now }));
|
|
723
|
+
}
|
|
724
|
+
function adoptAnonId(id) {
|
|
725
|
+
hzAnonAdopt(id);
|
|
726
|
+
}
|
|
705
727
|
function getFirstTouch() {
|
|
706
728
|
const s = ls();
|
|
707
729
|
if (!s) return void 0;
|
|
@@ -741,6 +763,76 @@ function mergeCohort(patch) {
|
|
|
741
763
|
return next;
|
|
742
764
|
}
|
|
743
765
|
|
|
766
|
+
// src/link.ts
|
|
767
|
+
var LINK_PARAMS = { anon: "hz_aid", session: "hz_sid", firstTouch: "hz_ft" };
|
|
768
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
769
|
+
var isId = (v) => !!v && UUID.test(v);
|
|
770
|
+
function encodeFirstTouch(a) {
|
|
771
|
+
return JSON.stringify([
|
|
772
|
+
a.utm.source,
|
|
773
|
+
a.utm.medium,
|
|
774
|
+
a.utm.campaign,
|
|
775
|
+
a.utm.term,
|
|
776
|
+
a.utm.content,
|
|
777
|
+
a.refCode,
|
|
778
|
+
a.referrer,
|
|
779
|
+
a.channel
|
|
780
|
+
].map((v) => v ?? ""));
|
|
781
|
+
}
|
|
782
|
+
function decodeFirstTouch(raw) {
|
|
783
|
+
if (!raw || raw.length > 1024) return void 0;
|
|
784
|
+
try {
|
|
785
|
+
const t = JSON.parse(raw);
|
|
786
|
+
if (!Array.isArray(t) || t.length !== 8 || t.some((v) => typeof v !== "string")) return void 0;
|
|
787
|
+
const [source, medium, campaign, term, content, refCode, referrer, channel] = t;
|
|
788
|
+
const u = (v) => v || void 0;
|
|
789
|
+
return {
|
|
790
|
+
utm: { source: u(source), medium: u(medium), campaign: u(campaign), term: u(term), content: u(content) },
|
|
791
|
+
refCode: u(refCode),
|
|
792
|
+
referrer: u(referrer),
|
|
793
|
+
channel: u(channel)
|
|
794
|
+
};
|
|
795
|
+
} catch {
|
|
796
|
+
return void 0;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function linkUrl(url, state, fromHost, base) {
|
|
800
|
+
let u;
|
|
801
|
+
try {
|
|
802
|
+
u = new URL(url, base);
|
|
803
|
+
} catch {
|
|
804
|
+
return url;
|
|
805
|
+
}
|
|
806
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return url;
|
|
807
|
+
if (!orgOf(u.hostname) || u.hostname === fromHost) return url;
|
|
808
|
+
const set = (k, v) => v && u.searchParams.set(k, v);
|
|
809
|
+
if (isId(state.anonId)) set(LINK_PARAMS.anon, state.anonId);
|
|
810
|
+
if (isId(state.sessionId)) set(LINK_PARAMS.session, state.sessionId);
|
|
811
|
+
if (state.firstTouch) set(LINK_PARAMS.firstTouch, encodeFirstTouch(state.firstTouch));
|
|
812
|
+
return u.toString();
|
|
813
|
+
}
|
|
814
|
+
function readLink(search) {
|
|
815
|
+
const q = new URLSearchParams(search || "");
|
|
816
|
+
const a = q.get(LINK_PARAMS.anon);
|
|
817
|
+
const s = q.get(LINK_PARAMS.session);
|
|
818
|
+
return {
|
|
819
|
+
anonId: isId(a) ? a : void 0,
|
|
820
|
+
sessionId: isId(s) ? s : void 0,
|
|
821
|
+
firstTouch: decodeFirstTouch(q.get(LINK_PARAMS.firstTouch))
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function stripLink(href) {
|
|
825
|
+
const u = new URL(href);
|
|
826
|
+
let hit = false;
|
|
827
|
+
for (const k of Object.values(LINK_PARAMS)) {
|
|
828
|
+
if (u.searchParams.has(k)) {
|
|
829
|
+
u.searchParams.delete(k);
|
|
830
|
+
hit = true;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return hit ? u.pathname + u.search + u.hash : void 0;
|
|
834
|
+
}
|
|
835
|
+
|
|
744
836
|
// src/core.ts
|
|
745
837
|
var EVENT_PATH = "/v1/event";
|
|
746
838
|
var DEFAULT_HOST = "https://api.hanzo.ai";
|
|
@@ -901,8 +993,19 @@ var Analytics = class {
|
|
|
901
993
|
if (this.started || !this.cfg.enabled) return;
|
|
902
994
|
this.started = true;
|
|
903
995
|
if (!isBrowser()) return;
|
|
996
|
+
const linked = readLink(window.location.search);
|
|
997
|
+
if (linked.anonId) adoptAnonId(linked.anonId);
|
|
998
|
+
if (linked.sessionId) adoptSession(linked.sessionId);
|
|
999
|
+
const clean = stripLink(window.location.href);
|
|
1000
|
+
if (clean !== void 0) {
|
|
1001
|
+
try {
|
|
1002
|
+
window.history.replaceState(window.history.state, "", clean);
|
|
1003
|
+
} catch {
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
904
1006
|
const parsed = parseAttribution(window.location.search, document.referrer);
|
|
905
|
-
|
|
1007
|
+
const first = linked.firstTouch && !getFirstTouch() ? setFirstTouchOnce(linked.firstTouch) : void 0;
|
|
1008
|
+
this.attribution = first ?? (hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed);
|
|
906
1009
|
this.cohort = mergeCohort({
|
|
907
1010
|
channel: this.attribution.channel ?? deriveChannel(this.attribution),
|
|
908
1011
|
refCode: this.attribution.refCode
|
|
@@ -921,6 +1024,27 @@ var Analytics = class {
|
|
|
921
1024
|
});
|
|
922
1025
|
}
|
|
923
1026
|
}
|
|
1027
|
+
/** link returns `url` carrying this visitor's anonymous id, session and first
|
|
1028
|
+
* touch when the destination is a Hanzo-owned host, so the journey continues
|
|
1029
|
+
* across the hop. Any other URL comes back unchanged. */
|
|
1030
|
+
link(url) {
|
|
1031
|
+
if (!isBrowser()) return url;
|
|
1032
|
+
if (!this.started) this.init();
|
|
1033
|
+
return linkUrl(
|
|
1034
|
+
url,
|
|
1035
|
+
{ anonId: anonId(), sessionId: sessionId(), firstTouch: this.attribution },
|
|
1036
|
+
window.location.hostname,
|
|
1037
|
+
window.location.href
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
/** authorize decorates an OAuth authorize URL for the redirect to IAM and flushes
|
|
1041
|
+
* what is queued, because the page is about to unload. Assign the result to
|
|
1042
|
+
* `location`. */
|
|
1043
|
+
authorize(url) {
|
|
1044
|
+
const out = this.link(url);
|
|
1045
|
+
this.flush(true);
|
|
1046
|
+
return out;
|
|
1047
|
+
}
|
|
924
1048
|
/** identify binds the current visitor to a stable person id (post-login). */
|
|
925
1049
|
identify(personId, traits) {
|
|
926
1050
|
this.personId = personId;
|
|
@@ -1104,9 +1228,9 @@ var Analytics = class {
|
|
|
1104
1228
|
...extra
|
|
1105
1229
|
};
|
|
1106
1230
|
const capturePII = this.cfg.capturePII ?? false;
|
|
1107
|
-
if (wire.url) wire.url = scrubText(wire.url, capturePII);
|
|
1108
|
-
if (wire.path) wire.path = scrubText(wire.path, capturePII);
|
|
1109
|
-
if (wire.referrer) wire.referrer = scrubText(wire.referrer, capturePII);
|
|
1231
|
+
if (wire.url) wire.url = scrubText(withoutFragment(wire.url), capturePII);
|
|
1232
|
+
if (wire.path) wire.path = scrubText(withoutFragment(wire.path), capturePII);
|
|
1233
|
+
if (wire.referrer) wire.referrer = scrubText(withoutFragment(wire.referrer), capturePII);
|
|
1110
1234
|
return wire;
|
|
1111
1235
|
}
|
|
1112
1236
|
schedule() {
|