@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/core.d.ts
CHANGED
|
@@ -37,6 +37,14 @@ export declare class Analytics {
|
|
|
37
37
|
* auto-capture unhandled errors. Safe to call from a React effect on every
|
|
38
38
|
* render. */
|
|
39
39
|
init(): void;
|
|
40
|
+
/** link returns `url` carrying this visitor's anonymous id, session and first
|
|
41
|
+
* touch when the destination is a Hanzo-owned host, so the journey continues
|
|
42
|
+
* across the hop. Any other URL comes back unchanged. */
|
|
43
|
+
link(url: string): string;
|
|
44
|
+
/** authorize decorates an OAuth authorize URL for the redirect to IAM and flushes
|
|
45
|
+
* what is queued, because the page is about to unload. Assign the result to
|
|
46
|
+
* `location`. */
|
|
47
|
+
authorize(url: string): string;
|
|
40
48
|
/** identify binds the current visitor to a stable person id (post-login). */
|
|
41
49
|
identify(personId: string, traits?: Record<string, unknown>): void;
|
|
42
50
|
/** group associates the visitor with an org/team (analytics grouping, not the
|
package/dist/core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AA2DA,OAAO,
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AA2DA,OAAO,EAKL,aAAa,EAEb,SAAS,EAEV,MAAM,WAAW,CAAA;AAGlB,OAAO,KAAK,EACV,eAAe,EAEf,mBAAmB,EACnB,MAAM,EAKN,SAAS,EACV,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,OAAO,EAAE,OAAO,EAAE,CAAA;AA2KlB,qBAAa,SAAS;IACpB,OAAO,CAAC,GAAG,CAGM;IACjB,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,KAAK,CAAkB;IAC/B,OAAO,CAAC,KAAK,CAA6C;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,OAAO,CAAQ;IACvB,0DAA0D;IAC1D,OAAO,CAAC,OAAO,CAAC,CAAQ;IACxB,+DAA+D;IAC/D,OAAO,CAAC,GAAG,CAAY;IACvB,6EAA6E;IAC7E,OAAO,CAAC,SAAS,CAAQ;IAEzB,YAAY,MAAM,EAAE,eAAe,EAwClC;IAED;;;;8EAI0E;IAC1E,KAAK,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAQnC;IAED;;;mEAG+D;IAC/D,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED;4CACwC;IACxC,IAAI,cAAc,IAAI,MAAM,GAAG,SAAS,CAEvC;IAED;;;kBAGc;IACd,IAAI,IAAI,IAAI,CAgDX;IAED;;8DAE0D;IAC1D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASxB;IAED;;sBAEkB;IAClB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI7B;IAED,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAGjE;IAED;6EACyE;IACzE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAE7D;IAED;;oEAEgE;IAChE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAclE;IAED;uFACmF;IACnF,OAAO,CACL,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,QAAQ,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC,GAC5E,IAAI,CAEN;IAED;;;;;;;;;;;;;0BAasB;IACtB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAuD9D;IAED;kCAC8B;IAC9B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE7B;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,MAAM,UAAQ,GAAG,IAAI,CAmC1B;IAID;;;;;6CAKyC;IACzC,OAAO,CAAC,SAAS;IAiBjB;sEACkE;IAClE,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,KAAK;IAkEb,OAAO,CAAC,QAAQ;IAQhB,OAAO,CAAC,UAAU;CAMnB;AAiCD;;;;;;;;;;;4DAW4D;AAC5D,wBAAgB,YAAY,IAAI,IAAI,CAGnC;AAED;;iEAEiE;AACjE,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAmBlE;AAID,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CAAA"}
|
package/dist/events.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* and there is still exactly one definition.
|
|
10
10
|
*
|
|
11
11
|
* Nothing about the API moved. Every existing import keeps working, the names
|
|
12
|
-
* and their values are unchanged, and `
|
|
12
|
+
* and their values are unchanged, and `pkg/events/test/catalog.mjs` fails the
|
|
13
13
|
* build if a name and its meaning ever disagree.
|
|
14
14
|
*
|
|
15
15
|
* The naming convention lives with the names, in @hanzo/events and TAXONOMY.md:
|
package/dist/index.cjs
CHANGED
|
@@ -254,11 +254,17 @@ var RE_CREDENTIAL_PARAM = new RegExp(
|
|
|
254
254
|
function redactCredentialParams(s) {
|
|
255
255
|
return s.replace(RE_CREDENTIAL_PARAM, (_m, prefix) => prefix + REDACTED);
|
|
256
256
|
}
|
|
257
|
+
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;
|
|
257
258
|
function redactSecrets(s) {
|
|
258
259
|
s = redactCredentialParams(s);
|
|
259
260
|
for (const re of SECRET_PATTERNS) s = s.replace(re, REDACTED);
|
|
261
|
+
s = s.replace(RE_SECRET43, (_m, lead) => lead + REDACTED);
|
|
260
262
|
return redactPAN(s);
|
|
261
263
|
}
|
|
264
|
+
function withoutFragment(url) {
|
|
265
|
+
const at = url.indexOf("#");
|
|
266
|
+
return at < 0 ? url : url.slice(0, at);
|
|
267
|
+
}
|
|
262
268
|
function scrubPII(s) {
|
|
263
269
|
s = s.replace(RE_EMAIL, EMAIL_MARK);
|
|
264
270
|
s = s.replace(RE_IPV6, IP_MARK);
|
|
@@ -385,6 +391,15 @@ function hzAnonId() {
|
|
|
385
391
|
}
|
|
386
392
|
return id;
|
|
387
393
|
}
|
|
394
|
+
function hzAnonAdopt(id) {
|
|
395
|
+
hzAnonMemo = id;
|
|
396
|
+
hzAnonWrite(HZ_ANON_KEY, id);
|
|
397
|
+
try {
|
|
398
|
+
var s = hzAnonStore();
|
|
399
|
+
if (s) s.setItem(HZ_ANON_KEY, id);
|
|
400
|
+
} catch (e) {
|
|
401
|
+
}
|
|
402
|
+
}
|
|
388
403
|
|
|
389
404
|
// src/uid.ts
|
|
390
405
|
function uuidv7Time(id) {
|
|
@@ -392,7 +407,7 @@ function uuidv7Time(id) {
|
|
|
392
407
|
}
|
|
393
408
|
|
|
394
409
|
// package.json
|
|
395
|
-
var version = "0.3.
|
|
410
|
+
var version = "0.3.43";
|
|
396
411
|
|
|
397
412
|
// src/version.ts
|
|
398
413
|
var VERSION = version;
|
|
@@ -712,6 +727,13 @@ function sessionId(now = Date.now()) {
|
|
|
712
727
|
s.setItem(KEY.session, JSON.stringify(state));
|
|
713
728
|
return state.id;
|
|
714
729
|
}
|
|
730
|
+
function adoptSession(id, now = Date.now()) {
|
|
731
|
+
const s = ls();
|
|
732
|
+
if (s) s.setItem(KEY.session, JSON.stringify({ id, last: now }));
|
|
733
|
+
}
|
|
734
|
+
function adoptAnonId(id) {
|
|
735
|
+
hzAnonAdopt(id);
|
|
736
|
+
}
|
|
715
737
|
function getFirstTouch() {
|
|
716
738
|
const s = ls();
|
|
717
739
|
if (!s) return void 0;
|
|
@@ -751,6 +773,76 @@ function mergeCohort(patch) {
|
|
|
751
773
|
return next;
|
|
752
774
|
}
|
|
753
775
|
|
|
776
|
+
// src/link.ts
|
|
777
|
+
var LINK_PARAMS = { anon: "hz_aid", session: "hz_sid", firstTouch: "hz_ft" };
|
|
778
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
779
|
+
var isId = (v) => !!v && UUID.test(v);
|
|
780
|
+
function encodeFirstTouch(a) {
|
|
781
|
+
return JSON.stringify([
|
|
782
|
+
a.utm.source,
|
|
783
|
+
a.utm.medium,
|
|
784
|
+
a.utm.campaign,
|
|
785
|
+
a.utm.term,
|
|
786
|
+
a.utm.content,
|
|
787
|
+
a.refCode,
|
|
788
|
+
a.referrer,
|
|
789
|
+
a.channel
|
|
790
|
+
].map((v) => v ?? ""));
|
|
791
|
+
}
|
|
792
|
+
function decodeFirstTouch(raw) {
|
|
793
|
+
if (!raw || raw.length > 1024) return void 0;
|
|
794
|
+
try {
|
|
795
|
+
const t = JSON.parse(raw);
|
|
796
|
+
if (!Array.isArray(t) || t.length !== 8 || t.some((v) => typeof v !== "string")) return void 0;
|
|
797
|
+
const [source, medium, campaign, term, content, refCode, referrer, channel] = t;
|
|
798
|
+
const u = (v) => v || void 0;
|
|
799
|
+
return {
|
|
800
|
+
utm: { source: u(source), medium: u(medium), campaign: u(campaign), term: u(term), content: u(content) },
|
|
801
|
+
refCode: u(refCode),
|
|
802
|
+
referrer: u(referrer),
|
|
803
|
+
channel: u(channel)
|
|
804
|
+
};
|
|
805
|
+
} catch {
|
|
806
|
+
return void 0;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function linkUrl(url, state, fromHost, base) {
|
|
810
|
+
let u;
|
|
811
|
+
try {
|
|
812
|
+
u = new URL(url, base);
|
|
813
|
+
} catch {
|
|
814
|
+
return url;
|
|
815
|
+
}
|
|
816
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return url;
|
|
817
|
+
if (!orgOf(u.hostname) || u.hostname === fromHost) return url;
|
|
818
|
+
const set = (k, v) => v && u.searchParams.set(k, v);
|
|
819
|
+
if (isId(state.anonId)) set(LINK_PARAMS.anon, state.anonId);
|
|
820
|
+
if (isId(state.sessionId)) set(LINK_PARAMS.session, state.sessionId);
|
|
821
|
+
if (state.firstTouch) set(LINK_PARAMS.firstTouch, encodeFirstTouch(state.firstTouch));
|
|
822
|
+
return u.toString();
|
|
823
|
+
}
|
|
824
|
+
function readLink(search) {
|
|
825
|
+
const q = new URLSearchParams(search || "");
|
|
826
|
+
const a = q.get(LINK_PARAMS.anon);
|
|
827
|
+
const s = q.get(LINK_PARAMS.session);
|
|
828
|
+
return {
|
|
829
|
+
anonId: isId(a) ? a : void 0,
|
|
830
|
+
sessionId: isId(s) ? s : void 0,
|
|
831
|
+
firstTouch: decodeFirstTouch(q.get(LINK_PARAMS.firstTouch))
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function stripLink(href) {
|
|
835
|
+
const u = new URL(href);
|
|
836
|
+
let hit = false;
|
|
837
|
+
for (const k of Object.values(LINK_PARAMS)) {
|
|
838
|
+
if (u.searchParams.has(k)) {
|
|
839
|
+
u.searchParams.delete(k);
|
|
840
|
+
hit = true;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
return hit ? u.pathname + u.search + u.hash : void 0;
|
|
844
|
+
}
|
|
845
|
+
|
|
754
846
|
// src/core.ts
|
|
755
847
|
var EVENT_PATH = "/v1/event";
|
|
756
848
|
var DEFAULT_HOST = "https://api.hanzo.ai";
|
|
@@ -911,8 +1003,19 @@ var Analytics = class {
|
|
|
911
1003
|
if (this.started || !this.cfg.enabled) return;
|
|
912
1004
|
this.started = true;
|
|
913
1005
|
if (!isBrowser()) return;
|
|
1006
|
+
const linked = readLink(window.location.search);
|
|
1007
|
+
if (linked.anonId) adoptAnonId(linked.anonId);
|
|
1008
|
+
if (linked.sessionId) adoptSession(linked.sessionId);
|
|
1009
|
+
const clean = stripLink(window.location.href);
|
|
1010
|
+
if (clean !== void 0) {
|
|
1011
|
+
try {
|
|
1012
|
+
window.history.replaceState(window.history.state, "", clean);
|
|
1013
|
+
} catch {
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
914
1016
|
const parsed = parseAttribution(window.location.search, document.referrer);
|
|
915
|
-
|
|
1017
|
+
const first = linked.firstTouch && !getFirstTouch() ? setFirstTouchOnce(linked.firstTouch) : void 0;
|
|
1018
|
+
this.attribution = first ?? (hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed);
|
|
916
1019
|
this.cohort = mergeCohort({
|
|
917
1020
|
channel: this.attribution.channel ?? deriveChannel(this.attribution),
|
|
918
1021
|
refCode: this.attribution.refCode
|
|
@@ -931,6 +1034,27 @@ var Analytics = class {
|
|
|
931
1034
|
});
|
|
932
1035
|
}
|
|
933
1036
|
}
|
|
1037
|
+
/** link returns `url` carrying this visitor's anonymous id, session and first
|
|
1038
|
+
* touch when the destination is a Hanzo-owned host, so the journey continues
|
|
1039
|
+
* across the hop. Any other URL comes back unchanged. */
|
|
1040
|
+
link(url) {
|
|
1041
|
+
if (!isBrowser()) return url;
|
|
1042
|
+
if (!this.started) this.init();
|
|
1043
|
+
return linkUrl(
|
|
1044
|
+
url,
|
|
1045
|
+
{ anonId: anonId(), sessionId: sessionId(), firstTouch: this.attribution },
|
|
1046
|
+
window.location.hostname,
|
|
1047
|
+
window.location.href
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
/** authorize decorates an OAuth authorize URL for the redirect to IAM and flushes
|
|
1051
|
+
* what is queued, because the page is about to unload. Assign the result to
|
|
1052
|
+
* `location`. */
|
|
1053
|
+
authorize(url) {
|
|
1054
|
+
const out = this.link(url);
|
|
1055
|
+
this.flush(true);
|
|
1056
|
+
return out;
|
|
1057
|
+
}
|
|
934
1058
|
/** identify binds the current visitor to a stable person id (post-login). */
|
|
935
1059
|
identify(personId, traits) {
|
|
936
1060
|
this.personId = personId;
|
|
@@ -1114,9 +1238,9 @@ var Analytics = class {
|
|
|
1114
1238
|
...extra
|
|
1115
1239
|
};
|
|
1116
1240
|
const capturePII = this.cfg.capturePII ?? false;
|
|
1117
|
-
if (wire.url) wire.url = scrubText(wire.url, capturePII);
|
|
1118
|
-
if (wire.path) wire.path = scrubText(wire.path, capturePII);
|
|
1119
|
-
if (wire.referrer) wire.referrer = scrubText(wire.referrer, capturePII);
|
|
1241
|
+
if (wire.url) wire.url = scrubText(withoutFragment(wire.url), capturePII);
|
|
1242
|
+
if (wire.path) wire.path = scrubText(withoutFragment(wire.path), capturePII);
|
|
1243
|
+
if (wire.referrer) wire.referrer = scrubText(withoutFragment(wire.referrer), capturePII);
|
|
1120
1244
|
return wire;
|
|
1121
1245
|
}
|
|
1122
1246
|
schedule() {
|
|
@@ -1247,6 +1371,7 @@ Object.defineProperty(exports, "eventsOf", {
|
|
|
1247
1371
|
exports.Analytics = Analytics;
|
|
1248
1372
|
exports.COHORTS = COHORTS;
|
|
1249
1373
|
exports.GOALS = GOALS;
|
|
1374
|
+
exports.LINK_PARAMS = LINK_PARAMS;
|
|
1250
1375
|
exports.ORG_DOMAIN = ORG_DOMAIN;
|
|
1251
1376
|
exports.ORG_KEY = ORG_KEY;
|
|
1252
1377
|
exports.PRODUCT_PROJECT = PRODUCT_PROJECT;
|
|
@@ -1268,13 +1393,16 @@ exports.hostOf = hostOf;
|
|
|
1268
1393
|
exports.isoWeek = isoWeek;
|
|
1269
1394
|
exports.keyFor = keyFor;
|
|
1270
1395
|
exports.keyForPage = keyForPage;
|
|
1396
|
+
exports.linkUrl = linkUrl;
|
|
1271
1397
|
exports.orgOf = orgOf;
|
|
1272
1398
|
exports.parseAttribution = parseAttribution;
|
|
1273
1399
|
exports.parseDsn = parseDsn;
|
|
1400
|
+
exports.readLink = readLink;
|
|
1274
1401
|
exports.redactSecrets = redactSecrets;
|
|
1275
1402
|
exports.resetClients = resetClients;
|
|
1276
1403
|
exports.scrubPII = scrubPII;
|
|
1277
1404
|
exports.scrubText = scrubText;
|
|
1405
|
+
exports.stripLink = stripLink;
|
|
1278
1406
|
exports.uuidv7 = hzUuidv7;
|
|
1279
1407
|
exports.uuidv7Time = uuidv7Time;
|
|
1280
1408
|
//# sourceMappingURL=index.cjs.map
|