@hanzo/event 0.3.42 → 0.3.44

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/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { eventsOf, EVENTS, PAGEVIEW, EXCEPTION } from '@hanzo/events';
1
+ import { eventsOf, EVENTS, PAGEVIEW, EXCEPTION, namesOn } from '@hanzo/events';
2
2
  export { EVENTS, EXCEPTION, FUNNELS, PAGEVIEW, PRODUCTS, eventsOf } from '@hanzo/events';
3
3
 
4
4
  // src/attribution.ts
@@ -18,20 +18,20 @@ var SOCIAL_HOSTS = [
18
18
  var SEARCH_HOSTS = ["google.", "bing.", "duckduckgo.", "yahoo.", "baidu.", "ecosia."];
19
19
  function parseAttribution(search, referrer) {
20
20
  const q = new URLSearchParams(search || "");
21
- const get = (k) => {
21
+ const get2 = (k) => {
22
22
  const v = q.get(k);
23
23
  return v ? v.trim() : void 0;
24
24
  };
25
25
  const a = {
26
26
  utm: {
27
- source: get("utm_source"),
28
- medium: get("utm_medium"),
29
- campaign: get("utm_campaign"),
30
- term: get("utm_term"),
31
- content: get("utm_content")
27
+ source: get2("utm_source"),
28
+ medium: get2("utm_medium"),
29
+ campaign: get2("utm_campaign"),
30
+ term: get2("utm_term"),
31
+ content: get2("utm_content")
32
32
  },
33
33
  referrer: referrer ? referrer.trim() : void 0,
34
- refCode: get("ref") || get("refCode") || get("ref_code") || void 0
34
+ refCode: get2("ref") || get2("refCode") || get2("ref_code") || void 0
35
35
  };
36
36
  a.channel = deriveChannel(a);
37
37
  return a;
@@ -288,13 +288,13 @@ var HZ_ANON_ADOPT = ["hz_anon_id", "hz_id"];
288
288
  var HZ_ANON_DOMAIN = "hanzo.ai";
289
289
  var HZ_ANON_MAX_AGE = 2 * 365 * 24 * 60 * 60;
290
290
  var hzAnonMemo;
291
- function hzUuidv7(now) {
291
+ function hzUuidv7(now2) {
292
292
  var b = new Uint8Array(16);
293
293
  var i;
294
294
  var c = typeof crypto !== "undefined" ? crypto : void 0;
295
295
  if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
296
296
  else for (i = 0; i < 16; i++) b[i] = Math.random() * 256 | 0;
297
- var t = Math.floor(now === void 0 ? Date.now() : now);
297
+ var t = Math.floor(now2 === void 0 ? Date.now() : now2);
298
298
  for (i = 5; i >= 0; i--) {
299
299
  b[i] = t % 256;
300
300
  t = Math.floor(t / 256);
@@ -390,6 +390,15 @@ function hzAnonId() {
390
390
  }
391
391
  return id;
392
392
  }
393
+ function hzAnonAdopt(id) {
394
+ hzAnonMemo = id;
395
+ hzAnonWrite(HZ_ANON_KEY, id);
396
+ try {
397
+ var s = hzAnonStore();
398
+ if (s) s.setItem(HZ_ANON_KEY, id);
399
+ } catch (e) {
400
+ }
401
+ }
393
402
 
394
403
  // src/uid.ts
395
404
  function uuidv7Time(id) {
@@ -397,7 +406,7 @@ function uuidv7Time(id) {
397
406
  }
398
407
 
399
408
  // package.json
400
- var version = "0.3.42";
409
+ var version = "0.3.44";
401
410
 
402
411
  // src/version.ts
403
412
  var VERSION = version;
@@ -530,22 +539,22 @@ function coerceTag(v) {
530
539
  return s.length > MAX_TAG_LEN ? s.slice(0, MAX_TAG_LEN) : s;
531
540
  }
532
541
  function buildSentryEvent(input) {
533
- const { error, options = {}, identity, capturePII = false } = input;
542
+ const { error, options: options2 = {}, identity, capturePII = false } = input;
534
543
  const norm = normalizeError(error);
535
- const handled = options.handled !== false;
536
- const level = options.level ?? (handled ? "error" : "fatal");
537
- const tags = { handled: String(handled) };
538
- if (identity.product) tags.product = identity.product;
539
- if (identity.sessionId) tags.session = identity.sessionId;
544
+ const handled = options2.handled !== false;
545
+ const level = options2.level ?? (handled ? "error" : "fatal");
546
+ const tags2 = { handled: String(handled) };
547
+ if (identity.product) tags2.product = identity.product;
548
+ if (identity.sessionId) tags2.session = identity.sessionId;
540
549
  try {
541
- const props = options.properties ?? {};
550
+ const props = options2.properties ?? {};
542
551
  let n = 0;
543
552
  for (const k of Object.keys(props)) {
544
553
  if (n >= MAX_TAGS) break;
545
554
  try {
546
555
  const val = props[k];
547
556
  if (val === void 0 || val === null) continue;
548
- tags[k] = scrubText(coerceTag(val), capturePII);
557
+ tags2[k] = scrubText(coerceTag(val), capturePII);
549
558
  n++;
550
559
  } catch {
551
560
  continue;
@@ -570,7 +579,7 @@ function buildSentryEvent(input) {
570
579
  }
571
580
  ]
572
581
  },
573
- tags,
582
+ tags: tags2,
574
583
  sdk: { name: "@hanzo/event", version: VERSION }
575
584
  };
576
585
  if (identity.userId) event.user = { id: identity.userId };
@@ -700,7 +709,7 @@ function ls() {
700
709
  function anonId() {
701
710
  return hzAnonId() || void 0;
702
711
  }
703
- function sessionId(now = Date.now()) {
712
+ function sessionId(now2 = Date.now()) {
704
713
  const s = ls();
705
714
  if (!s) return void 0;
706
715
  let state = null;
@@ -709,14 +718,21 @@ function sessionId(now = Date.now()) {
709
718
  } catch {
710
719
  state = null;
711
720
  }
712
- if (!state || now - state.last > SESSION_TTL_MS) {
713
- state = { id: hzUuidv7(now), last: now };
721
+ if (!state || now2 - state.last > SESSION_TTL_MS) {
722
+ state = { id: hzUuidv7(now2), last: now2 };
714
723
  } else {
715
- state.last = now;
724
+ state.last = now2;
716
725
  }
717
726
  s.setItem(KEY.session, JSON.stringify(state));
718
727
  return state.id;
719
728
  }
729
+ function adoptSession(id, now2 = Date.now()) {
730
+ const s = ls();
731
+ if (s) s.setItem(KEY.session, JSON.stringify({ id, last: now2 }));
732
+ }
733
+ function adoptAnonId(id) {
734
+ hzAnonAdopt(id);
735
+ }
720
736
  function getFirstTouch() {
721
737
  const s = ls();
722
738
  if (!s) return void 0;
@@ -756,6 +772,76 @@ function mergeCohort(patch) {
756
772
  return next;
757
773
  }
758
774
 
775
+ // src/link.ts
776
+ var LINK_PARAMS = { anon: "hz_aid", session: "hz_sid", firstTouch: "hz_ft" };
777
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
778
+ var isId = (v) => !!v && UUID.test(v);
779
+ function encodeFirstTouch(a) {
780
+ return JSON.stringify([
781
+ a.utm.source,
782
+ a.utm.medium,
783
+ a.utm.campaign,
784
+ a.utm.term,
785
+ a.utm.content,
786
+ a.refCode,
787
+ a.referrer,
788
+ a.channel
789
+ ].map((v) => v ?? ""));
790
+ }
791
+ function decodeFirstTouch(raw) {
792
+ if (!raw || raw.length > 1024) return void 0;
793
+ try {
794
+ const t = JSON.parse(raw);
795
+ if (!Array.isArray(t) || t.length !== 8 || t.some((v) => typeof v !== "string")) return void 0;
796
+ const [source, medium, campaign, term, content, refCode, referrer, channel] = t;
797
+ const u = (v) => v || void 0;
798
+ return {
799
+ utm: { source: u(source), medium: u(medium), campaign: u(campaign), term: u(term), content: u(content) },
800
+ refCode: u(refCode),
801
+ referrer: u(referrer),
802
+ channel: u(channel)
803
+ };
804
+ } catch {
805
+ return void 0;
806
+ }
807
+ }
808
+ function linkUrl(url, state, fromHost, base) {
809
+ let u;
810
+ try {
811
+ u = new URL(url, base);
812
+ } catch {
813
+ return url;
814
+ }
815
+ if (u.protocol !== "https:" && u.protocol !== "http:") return url;
816
+ if (!orgOf(u.hostname) || u.hostname === fromHost) return url;
817
+ const set2 = (k, v) => v && u.searchParams.set(k, v);
818
+ if (isId(state.anonId)) set2(LINK_PARAMS.anon, state.anonId);
819
+ if (isId(state.sessionId)) set2(LINK_PARAMS.session, state.sessionId);
820
+ if (state.firstTouch) set2(LINK_PARAMS.firstTouch, encodeFirstTouch(state.firstTouch));
821
+ return u.toString();
822
+ }
823
+ function readLink(search) {
824
+ const q = new URLSearchParams(search || "");
825
+ const a = q.get(LINK_PARAMS.anon);
826
+ const s = q.get(LINK_PARAMS.session);
827
+ return {
828
+ anonId: isId(a) ? a : void 0,
829
+ sessionId: isId(s) ? s : void 0,
830
+ firstTouch: decodeFirstTouch(q.get(LINK_PARAMS.firstTouch))
831
+ };
832
+ }
833
+ function stripLink(href) {
834
+ const u = new URL(href);
835
+ let hit = false;
836
+ for (const k of Object.values(LINK_PARAMS)) {
837
+ if (u.searchParams.has(k)) {
838
+ u.searchParams.delete(k);
839
+ hit = true;
840
+ }
841
+ }
842
+ return hit ? u.pathname + u.search + u.hash : void 0;
843
+ }
844
+
759
845
  // src/core.ts
760
846
  var EVENT_PATH = "/v1/event";
761
847
  var DEFAULT_HOST = "https://api.hanzo.ai";
@@ -916,8 +1002,19 @@ var Analytics = class {
916
1002
  if (this.started || !this.cfg.enabled) return;
917
1003
  this.started = true;
918
1004
  if (!isBrowser()) return;
1005
+ const linked = readLink(window.location.search);
1006
+ if (linked.anonId) adoptAnonId(linked.anonId);
1007
+ if (linked.sessionId) adoptSession(linked.sessionId);
1008
+ const clean = stripLink(window.location.href);
1009
+ if (clean !== void 0) {
1010
+ try {
1011
+ window.history.replaceState(window.history.state, "", clean);
1012
+ } catch {
1013
+ }
1014
+ }
919
1015
  const parsed = parseAttribution(window.location.search, document.referrer);
920
- this.attribution = hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed;
1016
+ const first = linked.firstTouch && !getFirstTouch() ? setFirstTouchOnce(linked.firstTouch) : void 0;
1017
+ this.attribution = first ?? (hasAttribution(parsed) ? setFirstTouchOnce(parsed) : getFirstTouch() ?? parsed);
921
1018
  this.cohort = mergeCohort({
922
1019
  channel: this.attribution.channel ?? deriveChannel(this.attribution),
923
1020
  refCode: this.attribution.refCode
@@ -936,6 +1033,27 @@ var Analytics = class {
936
1033
  });
937
1034
  }
938
1035
  }
1036
+ /** link returns `url` carrying this visitor's anonymous id, session and first
1037
+ * touch when the destination is a Hanzo-owned host, so the journey continues
1038
+ * across the hop. Any other URL comes back unchanged. */
1039
+ link(url) {
1040
+ if (!isBrowser()) return url;
1041
+ if (!this.started) this.init();
1042
+ return linkUrl(
1043
+ url,
1044
+ { anonId: anonId(), sessionId: sessionId(), firstTouch: this.attribution },
1045
+ window.location.hostname,
1046
+ window.location.href
1047
+ );
1048
+ }
1049
+ /** authorize decorates an OAuth authorize URL for the redirect to IAM and flushes
1050
+ * what is queued, because the page is about to unload. Assign the result to
1051
+ * `location`. */
1052
+ authorize(url) {
1053
+ const out = this.link(url);
1054
+ this.flush(true);
1055
+ return out;
1056
+ }
939
1057
  /** identify binds the current visitor to a stable person id (post-login). */
940
1058
  identify(personId, traits) {
941
1059
  this.personId = personId;
@@ -1049,11 +1167,11 @@ var Analytics = class {
1049
1167
  * or publishable key is attached here — the two planes authenticate
1050
1168
  * independently. Errors are sent one envelope per event, immediately: batching
1051
1169
  * a crash report is how you lose it. */
1052
- sendError(err, options) {
1170
+ sendError(err, options2) {
1053
1171
  if (!this.cfg.enabled || !this.dsn) return;
1054
1172
  const event = buildSentryEvent({
1055
1173
  error: err,
1056
- options,
1174
+ options: options2,
1057
1175
  identity: this.errorIdentity(),
1058
1176
  capturePII: this.cfg.capturePII ?? false
1059
1177
  });
@@ -1140,16 +1258,16 @@ var Analytics = class {
1140
1258
  };
1141
1259
  var CLIENTS = /* @__PURE__ */ Symbol.for("hanzo.event.clients");
1142
1260
  function registry() {
1143
- const g = globalThis;
1144
- const existing = g[CLIENTS];
1261
+ const g2 = globalThis;
1262
+ const existing = g2[CLIENTS];
1145
1263
  if (existing) return existing;
1146
1264
  const fresh = /* @__PURE__ */ new Map();
1147
- g[CLIENTS] = fresh;
1265
+ g2[CLIENTS] = fresh;
1148
1266
  return fresh;
1149
1267
  }
1150
1268
  function resetClients() {
1151
- const g = globalThis;
1152
- delete g[CLIENTS];
1269
+ const g2 = globalThis;
1270
+ delete g2[CLIENTS];
1153
1271
  }
1154
1272
  function createAnalytics(config) {
1155
1273
  if (!isBrowser() || config.enabled === false) return new Analytics(config);
@@ -1165,6 +1283,427 @@ function createAnalytics(config) {
1165
1283
  return fresh;
1166
1284
  }
1167
1285
 
1286
+ // src/cookie.ts
1287
+ var DAY = 86400;
1288
+ function domain() {
1289
+ const h = window.location.hostname;
1290
+ if (h === "localhost" || /^[\d.]+$/.test(h) || h.includes(":")) return "";
1291
+ const parts = h.split(".");
1292
+ return parts.length > 2 ? `; Domain=.${parts.slice(-2).join(".")}` : `; Domain=.${h}`;
1293
+ }
1294
+ function get(name) {
1295
+ if (typeof document === "undefined") return void 0;
1296
+ const hit = document.cookie.split("; ").find((c) => c.startsWith(`${name}=`));
1297
+ return hit ? decodeURIComponent(hit.slice(name.length + 1)) : void 0;
1298
+ }
1299
+ function set(name, value, days) {
1300
+ if (typeof document === "undefined") return;
1301
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
1302
+ document.cookie = `${name}=${encodeURIComponent(value)}; Max-Age=${days * DAY}; Path=/; SameSite=Lax${domain()}${secure}`;
1303
+ }
1304
+
1305
+ // src/consent.ts
1306
+ var CONSENT_COOKIE = "hz_consent";
1307
+ var CONSENT_EVENT = "hzconsent";
1308
+ var US = /^(America\/(New_York|Chicago|Denver|Los_Angeles|Phoenix|Anchorage|Adak|Boise|Detroit|Juneau|Sitka|Nome|Yakutat|Menominee|Metlakatla|Indiana\/.*|Kentucky\/.*|North_Dakota\/.*)|Pacific\/Honolulu|US\/.*)$/;
1309
+ function gpc() {
1310
+ if (typeof navigator === "undefined") return false;
1311
+ return navigator.globalPrivacyControl === true;
1312
+ }
1313
+ function refused() {
1314
+ if (typeof window === "undefined") return false;
1315
+ const n = navigator;
1316
+ const dnt = n.doNotTrack ?? window.doNotTrack ?? n.msDoNotTrack;
1317
+ return gpc() || dnt === "1" || dnt === "yes";
1318
+ }
1319
+ function region() {
1320
+ try {
1321
+ const zone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "";
1322
+ return US.test(zone) ? "opt-out" : "opt-in";
1323
+ } catch {
1324
+ return "opt-in";
1325
+ }
1326
+ }
1327
+ function stored() {
1328
+ return get(CONSENT_COOKIE) !== void 0;
1329
+ }
1330
+ function read2() {
1331
+ const kept = get(CONSENT_COOKIE);
1332
+ const on = kept !== void 0 ? kept.split(",") : region() === "opt-in" || refused() ? [] : ["analytics", "marketing", "ads"];
1333
+ const has = (k) => on.includes(k);
1334
+ return { analytics: has("analytics"), marketing: has("marketing") && !gpc(), ads: has("ads") && !gpc() };
1335
+ }
1336
+ function asks() {
1337
+ return typeof document !== "undefined" && region() === "opt-in" && !stored();
1338
+ }
1339
+ function render(c) {
1340
+ return ["analytics", "marketing", "ads"].filter((k) => c[k]).join(",");
1341
+ }
1342
+ function save(c) {
1343
+ const out = { analytics: c.analytics, marketing: c.marketing && !gpc(), ads: c.ads && !gpc() };
1344
+ set(CONSENT_COOKIE, render(out), 395);
1345
+ if (typeof window !== "undefined") window.dispatchEvent(new Event(CONSENT_EVENT));
1346
+ }
1347
+ var acceptAll = () => save({ analytics: true, marketing: true, ads: true });
1348
+ var rejectAll = () => save({ analytics: false, marketing: false, ads: false });
1349
+
1350
+ // src/touch.ts
1351
+ var CLICK_IDS = ["gclid", "gbraid", "wbraid", "fbclid", "li_fat_id", "twclid", "ttclid"];
1352
+ var DAYS = 90;
1353
+ var now = () => Math.floor(Date.now() / 1e3);
1354
+ var rand = () => Math.floor(Math.random() * 2 ** 31);
1355
+ function capture(c) {
1356
+ if (typeof window === "undefined") return;
1357
+ if (c.marketing) {
1358
+ const q = new URLSearchParams(window.location.search);
1359
+ const click = {};
1360
+ for (const k of CLICK_IDS) {
1361
+ const v = q.get(k)?.trim();
1362
+ if (v) click[k] = v;
1363
+ }
1364
+ if (Object.keys(click).length) {
1365
+ click.clicked = now();
1366
+ set("hz_touch", JSON.stringify(click), DAYS);
1367
+ if (click.fbclid) set("_fbc", `fb.1.${Date.now()}.${click.fbclid}`, DAYS);
1368
+ }
1369
+ if (!get("_fbp")) set("_fbp", `fb.1.${Date.now()}.${rand()}`, DAYS);
1370
+ }
1371
+ if (c.analytics && !get("_ga") && !get("hz_cid")) set("hz_cid", `${rand()}.${now()}`, 730);
1372
+ }
1373
+ function touch(c) {
1374
+ const out = {};
1375
+ if (c.marketing) {
1376
+ try {
1377
+ Object.assign(out, JSON.parse(get("hz_touch") ?? "{}"));
1378
+ } catch {
1379
+ }
1380
+ const fbc = get("_fbc");
1381
+ const fbp = get("_fbp");
1382
+ if (fbc) out.fbc = fbc;
1383
+ if (fbp) out.fbp = fbp;
1384
+ }
1385
+ if (c.analytics) {
1386
+ const ga = get("_ga")?.split(".");
1387
+ const cid = ga && ga.length >= 4 ? ga.slice(-2).join(".") : get("hz_cid");
1388
+ if (cid) out.cid = cid;
1389
+ }
1390
+ return out;
1391
+ }
1392
+ var page = () => window;
1393
+ var tags = [];
1394
+ var configured = false;
1395
+ var options = {};
1396
+ var loaded = /* @__PURE__ */ new Set();
1397
+ var google = "idle";
1398
+ var configAnswered = false;
1399
+ var held = [];
1400
+ var leaving = false;
1401
+ function settled() {
1402
+ return configAnswered && google !== "wait";
1403
+ }
1404
+ function flush() {
1405
+ if (settled() || leaving) held.splice(0).forEach((send) => send());
1406
+ }
1407
+ function script(src, onload, onerror) {
1408
+ const s = document.createElement("script");
1409
+ s.async = true;
1410
+ s.src = src;
1411
+ if (onload) s.onload = onload;
1412
+ if (onerror) s.onerror = onerror;
1413
+ document.head.appendChild(s);
1414
+ }
1415
+ function gtagStub() {
1416
+ const p = page();
1417
+ p.dataLayer = p.dataLayer || [];
1418
+ if (!p.gtag) {
1419
+ p.gtag = function() {
1420
+ p.dataLayer.push(arguments);
1421
+ };
1422
+ }
1423
+ return p.gtag;
1424
+ }
1425
+ var g = (on) => on ? "granted" : "denied";
1426
+ function consentMode(c, first) {
1427
+ const gtag = gtagStub();
1428
+ const state = {
1429
+ analytics_storage: g(c.analytics),
1430
+ ad_storage: g(c.marketing),
1431
+ ad_user_data: g(c.marketing),
1432
+ ad_personalization: g(c.marketing && c.ads)
1433
+ };
1434
+ if (first) {
1435
+ gtag("consent", "default", {
1436
+ analytics_storage: "denied",
1437
+ ad_storage: "denied",
1438
+ ad_user_data: "denied",
1439
+ ad_personalization: "denied",
1440
+ wait_for_update: 500
1441
+ });
1442
+ }
1443
+ gtag("consent", "update", state);
1444
+ }
1445
+ function loadGoogle(ids) {
1446
+ const gtag = gtagStub();
1447
+ const fresh = ids.filter((id) => !loaded.has(id));
1448
+ if (!fresh.length) return;
1449
+ if (google === "idle") {
1450
+ google = "wait";
1451
+ gtag("js", /* @__PURE__ */ new Date());
1452
+ const done = (to) => () => {
1453
+ if (google !== "wait") return;
1454
+ google = to;
1455
+ flush();
1456
+ };
1457
+ script(`https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(fresh[0])}`, done("loaded"), done("failed"));
1458
+ setTimeout(done("failed"), 8e3);
1459
+ }
1460
+ for (const id of fresh) {
1461
+ loaded.add(id);
1462
+ gtag("config", id, options.domains ? { linker: { domains: options.domains } } : {});
1463
+ }
1464
+ }
1465
+ function loadMeta(id, c) {
1466
+ const p = page();
1467
+ if (!p.fbq) {
1468
+ const n = p.fbq = function(...a) {
1469
+ if (n.callMethod) n.callMethod(...a);
1470
+ else n.queue.push(a);
1471
+ };
1472
+ if (!p._fbq) p._fbq = n;
1473
+ n.push = n;
1474
+ n.loaded = true;
1475
+ n.version = "2.0";
1476
+ n.queue = [];
1477
+ script("https://connect.facebook.net/en_US/fbevents.js");
1478
+ }
1479
+ if (!c.ads) p.fbq("dataProcessingOptions", ["LDU"], 0, 0);
1480
+ p.fbq("init", id);
1481
+ p.fbq("track", "PageView");
1482
+ }
1483
+ function loadLinkedIn(id) {
1484
+ const p = page();
1485
+ p._linkedin_data_partner_ids = p._linkedin_data_partner_ids || [];
1486
+ p._linkedin_data_partner_ids.push(id);
1487
+ if (!p.lintrk) {
1488
+ const l = p.lintrk = function(...a) {
1489
+ l.q.push(a);
1490
+ };
1491
+ l.q = [];
1492
+ }
1493
+ script("https://snap.licdn.com/li.lms-analytics/insight.min.js");
1494
+ }
1495
+ function loadX(id) {
1496
+ const p = page();
1497
+ if (!p.twq) {
1498
+ const s = p.twq = function(...a) {
1499
+ if (s.exe) s.exe(...a);
1500
+ else s.queue.push(a);
1501
+ };
1502
+ s.version = "1.1";
1503
+ s.queue = [];
1504
+ script("https://static.ads-twitter.com/uwt.js");
1505
+ }
1506
+ p.twq("config", id);
1507
+ }
1508
+ var TIKTOK_METHODS = ["page", "track", "identify", "instances", "debug", "on", "off", "once", "ready", "alias", "group", "enableCookie", "disableCookie"];
1509
+ function loadTikTok(id) {
1510
+ const p = page();
1511
+ p.TiktokAnalyticsObject = "ttq";
1512
+ const q = p.ttq = p.ttq || [];
1513
+ if (!q._i) {
1514
+ q.methods = TIKTOK_METHODS;
1515
+ for (const m of TIKTOK_METHODS) q[m] = (...a) => q.push([m, ...a]);
1516
+ q._i = {};
1517
+ q._t = {};
1518
+ q._o = {};
1519
+ q.load = (sdk) => {
1520
+ q._i[sdk] = [];
1521
+ q._t[sdk] = Date.now();
1522
+ script(`https://analytics.tiktok.com/i18n/pixel/events.js?sdkid=${encodeURIComponent(sdk)}&lib=ttq`);
1523
+ };
1524
+ }
1525
+ q.load(id);
1526
+ q.page();
1527
+ }
1528
+ var NEEDS = {
1529
+ ga: "analytics",
1530
+ gads: "marketing",
1531
+ meta: "marketing",
1532
+ linkedin: "marketing",
1533
+ x: "marketing",
1534
+ tiktok: "marketing"
1535
+ };
1536
+ var allowed = (t, c) => t.type === "ga" ? c.analytics || c.marketing : Boolean(NEEDS[t.type] && c[NEEDS[t.type]]);
1537
+ function apply() {
1538
+ if (typeof window === "undefined" || !configured) return;
1539
+ const c = read2();
1540
+ capture(c);
1541
+ const on = tags.filter((t) => allowed(t, c));
1542
+ const ids = on.filter((t) => t.type === "ga" || t.type === "gads").map((t) => t.id);
1543
+ if (ids.length) {
1544
+ consentMode(c, !loaded.has("consent"));
1545
+ loaded.add("consent");
1546
+ loadGoogle(ids);
1547
+ }
1548
+ for (const t of on) {
1549
+ const k = `${t.type}:${t.id}`;
1550
+ if (loaded.has(k)) continue;
1551
+ if (t.type === "meta") loadMeta(t.id, c);
1552
+ else if (t.type === "linkedin") loadLinkedIn(t.id);
1553
+ else if (t.type === "x") loadX(t.id);
1554
+ else if (t.type === "tiktok") loadTikTok(t.id);
1555
+ else continue;
1556
+ loaded.add(k);
1557
+ }
1558
+ flush();
1559
+ }
1560
+ function start(o = {}) {
1561
+ if (typeof window === "undefined") return () => void 0;
1562
+ options = o;
1563
+ addEventListener(CONSENT_EVENT, apply);
1564
+ addEventListener("pagehide", pagehide);
1565
+ if (!configured) {
1566
+ configured = true;
1567
+ const base = (o.base ?? "https://api.hanzo.ai").replace(/\/$/, "");
1568
+ const q = new URLSearchParams();
1569
+ if (o.key) q.set("key", o.key);
1570
+ q.set("host", o.host ?? window.location.hostname);
1571
+ const answer = (list) => {
1572
+ tags = list;
1573
+ configAnswered = true;
1574
+ apply();
1575
+ };
1576
+ const cap = setTimeout(() => answer([]), 3e3);
1577
+ fetch(`${base}/v1/project/tags?${q}`).then((r) => r.ok ? r.json() : { tags: [] }).then((j) => {
1578
+ clearTimeout(cap);
1579
+ answer(Array.isArray(j.tags) ? j.tags : []);
1580
+ }).catch(() => {
1581
+ clearTimeout(cap);
1582
+ answer([]);
1583
+ });
1584
+ }
1585
+ return () => {
1586
+ removeEventListener(CONSENT_EVENT, apply);
1587
+ removeEventListener("pagehide", pagehide);
1588
+ };
1589
+ }
1590
+ function pagehide() {
1591
+ leaving = true;
1592
+ flush();
1593
+ }
1594
+ function reach() {
1595
+ const c = read2();
1596
+ const to = [];
1597
+ for (const t of tags) {
1598
+ if (!allowed(t, c)) continue;
1599
+ const running = t.type === "ga" ? google === "loaded" && c.analytics : t.type === "gads" ? google === "loaded" && c.marketing : loaded.has(`${t.type}:${t.id}`);
1600
+ if (running && !to.includes(t.type)) to.push(t.type);
1601
+ }
1602
+ return to.join(",");
1603
+ }
1604
+ var META_STANDARD = /* @__PURE__ */ new Set([
1605
+ "AddPaymentInfo",
1606
+ "AddToCart",
1607
+ "AddToWishlist",
1608
+ "CompleteRegistration",
1609
+ "Contact",
1610
+ "CustomizeProduct",
1611
+ "Donate",
1612
+ "FindLocation",
1613
+ "InitiateCheckout",
1614
+ "Lead",
1615
+ "Purchase",
1616
+ "Schedule",
1617
+ "Search",
1618
+ "StartTrial",
1619
+ "SubmitApplication",
1620
+ "Subscribe",
1621
+ "ViewContent"
1622
+ ]);
1623
+ function shape(platform, p) {
1624
+ const items = Array.isArray(p.items) ? p.items : [];
1625
+ const out = {};
1626
+ if (typeof p.value === "number") out.value = p.value;
1627
+ out.currency = typeof p.currency === "string" ? p.currency : "USD";
1628
+ if (!items.length) return out;
1629
+ if (platform === "meta") {
1630
+ out.content_type = "product";
1631
+ out.content_ids = items.map((i) => i.item_id);
1632
+ out.contents = items.map((i) => ({ id: i.item_id, quantity: i.quantity ?? 1, item_price: i.price }));
1633
+ out.content_name = items[0].item_name;
1634
+ out.num_items = items.reduce((n, i) => n + (i.quantity ?? 1), 0);
1635
+ } else if (platform === "tiktok") {
1636
+ out.content_type = "product";
1637
+ out.contents = items.map((i) => ({ content_id: i.item_id, content_name: i.item_name, quantity: i.quantity ?? 1, price: i.price }));
1638
+ } else {
1639
+ out.contents = items.map((i) => ({ content_id: i.item_id, content_name: i.item_name, content_price: i.price, num_items: i.quantity ?? 1 }));
1640
+ }
1641
+ return out;
1642
+ }
1643
+ function unpriced(p) {
1644
+ const out = { ...p, value: 0 };
1645
+ if (Array.isArray(p.items)) out.items = p.items.map((i) => ({ ...i, price: 0 }));
1646
+ return out;
1647
+ }
1648
+ function mirror(name, params, id, to = reach()) {
1649
+ const p = page();
1650
+ const on = new Set(to.split(",").filter(Boolean));
1651
+ const ga4 = namesOn(name, "ga4");
1652
+ const test = params.test === true;
1653
+ if (test) params = unpriced(params);
1654
+ const transaction = typeof params.order_id === "string" ? { transaction_id: params.order_id } : {};
1655
+ for (const t of tags) {
1656
+ if (!on.has(t.type)) continue;
1657
+ const rule = t.events?.[name];
1658
+ if (t.type === "ga") {
1659
+ for (const n of ga4) {
1660
+ p.gtag?.("event", n, { ...params, ...transaction, event_id: id, transport_type: "beacon", ...test ? { debug_mode: true } : {} });
1661
+ }
1662
+ } else if (t.type === "gads" && rule) {
1663
+ p.gtag?.("event", "conversion", {
1664
+ send_to: rule,
1665
+ value: typeof params.value === "number" ? params.value : void 0,
1666
+ currency: typeof params.currency === "string" ? params.currency : "USD",
1667
+ ...transaction
1668
+ });
1669
+ } else if (t.type === "meta") {
1670
+ for (const n of namesOn(name, "meta")) {
1671
+ p.fbq?.(META_STANDARD.has(n) ? "track" : "trackCustom", n, shape("meta", params), { eventID: id });
1672
+ }
1673
+ } else if (t.type === "tiktok") {
1674
+ for (const n of namesOn(name, "tiktok")) p.ttq?.track?.(n, shape("tiktok", params), { event_id: id });
1675
+ } else if (t.type === "linkedin" && rule && namesOn(name, "linkedin").length) {
1676
+ p.lintrk?.("track", { conversion_id: Number(rule) });
1677
+ } else if (t.type === "x" && rule && namesOn(name, "x").length) {
1678
+ p.twq?.("event", rule, { ...shape("x", params), conversion_id: id });
1679
+ }
1680
+ }
1681
+ }
1682
+ function track(stream, name, params = {}) {
1683
+ if (typeof window === "undefined") return;
1684
+ if (!leaving && !settled()) {
1685
+ held.push(() => track(stream, name, params));
1686
+ return;
1687
+ }
1688
+ const c = read2();
1689
+ const event_id = crypto.randomUUID();
1690
+ const to = reach();
1691
+ mirror(name, params, event_id, to);
1692
+ stream?.capture(name, {
1693
+ ...params,
1694
+ ...touch(c),
1695
+ event_id,
1696
+ consent: render(c),
1697
+ tags: to,
1698
+ userAgent: navigator.userAgent,
1699
+ url: window.location.href
1700
+ });
1701
+ }
1702
+ function visit() {
1703
+ const c = read2();
1704
+ return { ...touch(c), consent: render(c), tags: reach(), userAgent: navigator.userAgent, url: window.location.href };
1705
+ }
1706
+
1168
1707
  // src/goals.ts
1169
1708
  var GOALS = {
1170
1709
  // Signup: the conversion is signup_completed, along the site signup funnel.
@@ -1225,6 +1764,6 @@ var COHORTS = {
1225
1764
  * curl -fsSL https://unpkg.com/@hanzo/event/src/anon.js -o apps/analytics/anon.js
1226
1765
  */
1227
1766
 
1228
- export { Analytics, COHORTS, GOALS, ORG_DOMAIN, ORG_KEY, PRODUCT_PROJECT, VERSION, buildEnvelope, buildSentryEvent, createAnalytics, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, orgOf, parseAttribution, parseDsn, redactSecrets, resetClients, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
1767
+ export { Analytics, CLICK_IDS, COHORTS, CONSENT_COOKIE, CONSENT_EVENT, GOALS, LINK_PARAMS, ORG_DOMAIN, ORG_KEY, PRODUCT_PROJECT, VERSION, acceptAll, asks, buildEnvelope, buildSentryEvent, capture as captureClick, createAnalytics, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, gpc, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, linkUrl, mirror, orgOf, parseAttribution, parseDsn, reach, read2 as readConsent, readLink, redactSecrets, refused, region, rejectAll, render as renderConsent, resetClients, save as saveConsent, scrubPII, scrubText, start as startTags, stored as storedConsent, stripLink, touch, track, hzUuidv7 as uuidv7, uuidv7Time, visit };
1229
1768
  //# sourceMappingURL=index.mjs.map
1230
1769
  //# sourceMappingURL=index.mjs.map