@aranova/tracking-react 0.4.1 → 0.5.0

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.js CHANGED
@@ -608,26 +608,21 @@ function createTrackingClient(config) {
608
608
  };
609
609
  }
610
610
 
611
- // ../tracking-core/src/events/contact-page-visit.ts
611
+ // ../tracking-core/src/events/cta-click.ts
612
612
  var import_zod2 = require("zod");
613
- var contactPageVisitMetadataSchema = import_zod2.z.object({
613
+ var ctaClickMetadataSchema = import_zod2.z.object({
614
+ cta_name: import_zod2.z.string(),
614
615
  page: import_zod2.z.object({
615
616
  path: import_zod2.z.string()
616
- })
617
- }).strict();
618
- var contactPageVisitConfigSchema = import_zod2.z.object({
619
- // RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
620
- // real regex at createTracking() time; the factory validates with this
621
- // schema and then keeps the live RegExp reference for runtime matching.
622
- pathPattern: import_zod2.z.custom(
623
- (value) => value instanceof RegExp,
624
- { message: "pathPattern must be a RegExp" }
625
- )
617
+ }),
618
+ section: import_zod2.z.string().nullable().optional(),
619
+ destination_url: import_zod2.z.string().nullable().optional()
626
620
  }).strict();
621
+ var ctaClickConfigSchema = import_zod2.z.object({}).strict();
627
622
 
628
- // ../tracking-core/src/events/form-submit.ts
623
+ // ../tracking-core/src/events/form-start.ts
629
624
  var import_zod3 = require("zod");
630
- var formSubmitMetadataSchema = import_zod3.z.object({
625
+ var formStartMetadataSchema = import_zod3.z.object({
631
626
  form: import_zod3.z.object({
632
627
  id: import_zod3.z.string(),
633
628
  action: import_zod3.z.string().nullable()
@@ -636,32 +631,106 @@ var formSubmitMetadataSchema = import_zod3.z.object({
636
631
  path: import_zod3.z.string()
637
632
  })
638
633
  }).strict();
639
- var formSubmitConfigSchema = import_zod3.z.object({}).strict();
634
+ var formStartConfigSchema = import_zod3.z.object({
635
+ selector: import_zod3.z.string().optional()
636
+ }).strict();
640
637
 
641
- // ../tracking-core/src/events/phone-click.ts
638
+ // ../tracking-core/src/events/form-submit.ts
642
639
  var import_zod4 = require("zod");
643
- var phoneClickMetadataSchema = import_zod4.z.object({
644
- element: import_zod4.z.object({
645
- tag: import_zod4.z.string(),
646
- text: import_zod4.z.string().nullable(),
647
- href: import_zod4.z.string()
640
+ var formSubmitMetadataSchema = import_zod4.z.object({
641
+ form: import_zod4.z.object({
642
+ id: import_zod4.z.string(),
643
+ action: import_zod4.z.string().nullable(),
644
+ fields: import_zod4.z.array(
645
+ import_zod4.z.object({
646
+ name: import_zod4.z.string(),
647
+ type: import_zod4.z.string(),
648
+ label: import_zod4.z.string().nullable(),
649
+ has_value: import_zod4.z.boolean()
650
+ })
651
+ ).optional()
648
652
  }),
649
653
  page: import_zod4.z.object({
650
654
  path: import_zod4.z.string()
651
655
  })
652
656
  }).strict();
653
- var phoneClickConfigSchema = import_zod4.z.object({}).strict();
657
+ var formSubmitConfigSchema = import_zod4.z.object({}).strict();
654
658
 
655
- // ../tracking-core/src/events/time-on-site.ts
659
+ // ../tracking-core/src/events/multi-page-session.ts
656
660
  var import_zod5 = require("zod");
657
- var timeOnSiteMetadataSchema = import_zod5.z.object({
658
- duration_ms: import_zod5.z.number().int().nonnegative(),
661
+ var multiPageSessionMetadataSchema = import_zod5.z.object({
662
+ page_count: import_zod5.z.number().int().min(2),
659
663
  page: import_zod5.z.object({
660
664
  path: import_zod5.z.string()
661
665
  })
662
666
  }).strict();
663
- var timeOnSiteConfigSchema = import_zod5.z.object({
664
- thresholdSeconds: import_zod5.z.number().int().positive()
667
+ var multiPageSessionConfigSchema = import_zod5.z.object({
668
+ pageThreshold: import_zod5.z.number().int().min(2)
669
+ }).strict();
670
+
671
+ // ../tracking-core/src/events/phone-click.ts
672
+ var import_zod6 = require("zod");
673
+ var phoneClickMetadataSchema = import_zod6.z.object({
674
+ phone_number: import_zod6.z.string(),
675
+ page: import_zod6.z.object({
676
+ path: import_zod6.z.string()
677
+ }),
678
+ section: import_zod6.z.string().nullable().optional()
679
+ }).strict();
680
+ var phoneClickConfigSchema = import_zod6.z.object({}).strict();
681
+
682
+ // ../tracking-core/src/events/scroll-depth.ts
683
+ var import_zod7 = require("zod");
684
+ var scrollDepthMetadataSchema = import_zod7.z.object({
685
+ depth_percent: import_zod7.z.number().int().min(1).max(100),
686
+ page: import_zod7.z.object({
687
+ path: import_zod7.z.string()
688
+ })
689
+ }).strict();
690
+ var scrollDepthConfigSchema = import_zod7.z.object({
691
+ thresholds: import_zod7.z.array(import_zod7.z.number().int().min(1).max(100)).min(1)
692
+ }).strict();
693
+
694
+ // ../tracking-core/src/events/specific-page-visit.ts
695
+ var import_zod8 = require("zod");
696
+ var SPECIFIC_PAGE_NAMES = [
697
+ "contact_page",
698
+ "about_page",
699
+ "services_page",
700
+ "booking_page",
701
+ "location_page",
702
+ "pricing_page",
703
+ "faq_page",
704
+ "testimonials_page"
705
+ ];
706
+ var specificPageNameSchema = import_zod8.z.enum(SPECIFIC_PAGE_NAMES);
707
+ var specificPageVisitMetadataSchema = import_zod8.z.object({
708
+ page_name: specificPageNameSchema,
709
+ page: import_zod8.z.object({
710
+ path: import_zod8.z.string()
711
+ })
712
+ }).strict();
713
+ var specificPageVisitConfigSchema = import_zod8.z.object({
714
+ pages: import_zod8.z.array(
715
+ import_zod8.z.object({
716
+ name: specificPageNameSchema,
717
+ pathPattern: import_zod8.z.custom((value) => value instanceof RegExp, {
718
+ message: "pathPattern must be a RegExp"
719
+ })
720
+ })
721
+ ).min(1)
722
+ }).strict();
723
+
724
+ // ../tracking-core/src/events/time-on-site.ts
725
+ var import_zod9 = require("zod");
726
+ var timeOnSiteMetadataSchema = import_zod9.z.object({
727
+ duration_ms: import_zod9.z.number().int().nonnegative(),
728
+ page: import_zod9.z.object({
729
+ path: import_zod9.z.string()
730
+ })
731
+ }).strict();
732
+ var timeOnSiteConfigSchema = import_zod9.z.object({
733
+ thresholdSeconds: import_zod9.z.number().int().positive()
665
734
  }).strict();
666
735
 
667
736
  // ../tracking-core/src/events/registry.ts
@@ -677,10 +746,25 @@ var EVENT_REGISTRY = {
677
746
  metadataSchema: timeOnSiteMetadataSchema,
678
747
  configSchema: timeOnSiteConfigSchema
679
748
  },
680
- contact_page_visit: {
749
+ specific_page_visit: {
750
+ kind: "automatic",
751
+ metadataSchema: specificPageVisitMetadataSchema,
752
+ configSchema: specificPageVisitConfigSchema
753
+ },
754
+ scroll_depth: {
681
755
  kind: "automatic",
682
- metadataSchema: contactPageVisitMetadataSchema,
683
- configSchema: contactPageVisitConfigSchema
756
+ metadataSchema: scrollDepthMetadataSchema,
757
+ configSchema: scrollDepthConfigSchema
758
+ },
759
+ multi_page_session: {
760
+ kind: "automatic",
761
+ metadataSchema: multiPageSessionMetadataSchema,
762
+ configSchema: multiPageSessionConfigSchema
763
+ },
764
+ form_start: {
765
+ kind: "automatic",
766
+ metadataSchema: formStartMetadataSchema,
767
+ configSchema: formStartConfigSchema
684
768
  },
685
769
  // --- manual triggers ---
686
770
  form_submit: {
@@ -692,6 +776,11 @@ var EVENT_REGISTRY = {
692
776
  kind: "manual",
693
777
  metadataSchema: phoneClickMetadataSchema,
694
778
  configSchema: phoneClickConfigSchema
779
+ },
780
+ cta_click: {
781
+ kind: "manual",
782
+ metadataSchema: ctaClickMetadataSchema,
783
+ configSchema: ctaClickConfigSchema
695
784
  }
696
785
  };
697
786
  var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
@@ -782,26 +871,193 @@ function attachTimeOnSite(client, config) {
782
871
  };
783
872
  }
784
873
 
785
- // ../tracking-core/src/triggers/contact-page-visit.ts
786
- function attachContactPageVisit(client, config) {
874
+ // ../tracking-core/src/triggers/specific-page-visit.ts
875
+ function attachSpecificPageVisit(client, config) {
787
876
  if (typeof window === "undefined" || typeof history === "undefined") {
788
877
  return () => {
789
878
  };
790
879
  }
791
- const { pathPattern } = config;
792
- let lastFiredPath = null;
880
+ const { pages } = config;
881
+ const firedSet = /* @__PURE__ */ new Set();
793
882
  function check() {
794
883
  const path = window.location.pathname;
795
- if (!pathPattern.test(path)) return;
796
- if (lastFiredPath === path) return;
797
- lastFiredPath = path;
798
- client.trackEvent({
799
- eventType: "contact_page_visit",
800
- metadata: { page: { path } },
801
- pageUrl: window.location.href,
802
- occurredAt: null
884
+ for (const { name, pathPattern } of pages) {
885
+ pathPattern.lastIndex = 0;
886
+ if (!pathPattern.test(path)) continue;
887
+ const key = `${name}:${path}`;
888
+ if (firedSet.has(key)) continue;
889
+ firedSet.add(key);
890
+ client.trackEvent({
891
+ eventType: "specific_page_visit",
892
+ metadata: { page_name: name, page: { path } },
893
+ pageUrl: window.location.href,
894
+ occurredAt: null
895
+ });
896
+ }
897
+ }
898
+ const originalPushState = history.pushState.bind(history);
899
+ const originalReplaceState = history.replaceState.bind(history);
900
+ function patchedPushState(...args) {
901
+ originalPushState(...args);
902
+ setTimeout(check, 0);
903
+ }
904
+ function patchedReplaceState(...args) {
905
+ originalReplaceState(...args);
906
+ setTimeout(check, 0);
907
+ }
908
+ history.pushState = patchedPushState;
909
+ history.replaceState = patchedReplaceState;
910
+ window.addEventListener("popstate", check);
911
+ check();
912
+ return () => {
913
+ history.pushState = originalPushState;
914
+ history.replaceState = originalReplaceState;
915
+ window.removeEventListener("popstate", check);
916
+ };
917
+ }
918
+
919
+ // ../tracking-core/src/triggers/scroll-depth.ts
920
+ function attachScrollDepth(client, config) {
921
+ if (typeof window === "undefined" || typeof document === "undefined") {
922
+ return () => {
923
+ };
924
+ }
925
+ const thresholds = new Set(config.thresholds);
926
+ let firedForPath = /* @__PURE__ */ new Set();
927
+ let currentPath = window.location.pathname;
928
+ let rafId = null;
929
+ function getScrollPercent() {
930
+ const doc = document.documentElement;
931
+ const scrollTop = window.scrollY || doc.scrollTop;
932
+ const scrollHeight = doc.scrollHeight;
933
+ const clientHeight = doc.clientHeight;
934
+ if (scrollHeight <= clientHeight) return 100;
935
+ return Math.round((scrollTop + clientHeight) / scrollHeight * 100);
936
+ }
937
+ function checkThresholds() {
938
+ const percent = getScrollPercent();
939
+ for (const threshold of thresholds) {
940
+ if (percent >= threshold && !firedForPath.has(threshold)) {
941
+ firedForPath.add(threshold);
942
+ client.trackEvent({
943
+ eventType: "scroll_depth",
944
+ metadata: {
945
+ depth_percent: threshold,
946
+ page: { path: currentPath }
947
+ },
948
+ pageUrl: window.location.href,
949
+ occurredAt: null
950
+ });
951
+ }
952
+ }
953
+ }
954
+ function onScroll() {
955
+ if (rafId !== null) return;
956
+ rafId = requestAnimationFrame(() => {
957
+ rafId = null;
958
+ checkThresholds();
803
959
  });
804
960
  }
961
+ function resetIfPathChanged() {
962
+ const newPath = window.location.pathname;
963
+ if (newPath === currentPath) return;
964
+ currentPath = newPath;
965
+ firedForPath = /* @__PURE__ */ new Set();
966
+ setTimeout(checkThresholds, 0);
967
+ }
968
+ const originalPushState = history.pushState.bind(history);
969
+ const originalReplaceState = history.replaceState.bind(history);
970
+ function patchedPushState(...args) {
971
+ originalPushState(...args);
972
+ setTimeout(resetIfPathChanged, 0);
973
+ }
974
+ function patchedReplaceState(...args) {
975
+ originalReplaceState(...args);
976
+ setTimeout(resetIfPathChanged, 0);
977
+ }
978
+ history.pushState = patchedPushState;
979
+ history.replaceState = patchedReplaceState;
980
+ window.addEventListener("popstate", resetIfPathChanged);
981
+ window.addEventListener("scroll", onScroll, { passive: true });
982
+ setTimeout(checkThresholds, 0);
983
+ return () => {
984
+ if (rafId !== null) cancelAnimationFrame(rafId);
985
+ history.pushState = originalPushState;
986
+ history.replaceState = originalReplaceState;
987
+ window.removeEventListener("popstate", resetIfPathChanged);
988
+ window.removeEventListener("scroll", onScroll);
989
+ };
990
+ }
991
+
992
+ // ../tracking-core/src/triggers/multi-page-session.ts
993
+ var STORAGE_KEY = "aranova_tracking_mps_paths";
994
+ var SESSION_KEY = "aranova_tracking_mps_session";
995
+ var FIRED_KEY = "aranova_tracking_mps_fired";
996
+ function getSessionStorage() {
997
+ try {
998
+ return typeof window !== "undefined" ? window.sessionStorage : null;
999
+ } catch {
1000
+ return null;
1001
+ }
1002
+ }
1003
+ function attachMultiPageSession(client, config) {
1004
+ if (typeof window === "undefined" || typeof history === "undefined") {
1005
+ return () => {
1006
+ };
1007
+ }
1008
+ const storage = getSessionStorage();
1009
+ if (!storage) return () => {
1010
+ };
1011
+ const { pageThreshold } = config;
1012
+ let lastCheckedPath = "";
1013
+ function getDistinctPaths() {
1014
+ try {
1015
+ const raw = storage.getItem(STORAGE_KEY);
1016
+ return raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
1017
+ } catch {
1018
+ return /* @__PURE__ */ new Set();
1019
+ }
1020
+ }
1021
+ function saveDistinctPaths(paths) {
1022
+ try {
1023
+ storage.setItem(STORAGE_KEY, JSON.stringify([...paths]));
1024
+ } catch {
1025
+ }
1026
+ }
1027
+ function resetIfSessionChanged() {
1028
+ const currentSession = getOrRotateSessionId();
1029
+ const storedSession = storage.getItem(SESSION_KEY);
1030
+ if (storedSession !== currentSession) {
1031
+ storage.setItem(SESSION_KEY, currentSession);
1032
+ storage.removeItem(STORAGE_KEY);
1033
+ storage.removeItem(FIRED_KEY);
1034
+ }
1035
+ }
1036
+ function hasFired() {
1037
+ return storage.getItem(FIRED_KEY) === "1";
1038
+ }
1039
+ function check() {
1040
+ const currentPath = window.location.pathname;
1041
+ if (currentPath === lastCheckedPath) return;
1042
+ lastCheckedPath = currentPath;
1043
+ resetIfSessionChanged();
1044
+ if (hasFired()) return;
1045
+ const paths = getDistinctPaths();
1046
+ paths.add(currentPath);
1047
+ saveDistinctPaths(paths);
1048
+ if (paths.size >= pageThreshold) {
1049
+ storage.setItem(FIRED_KEY, "1");
1050
+ client.trackEvent({
1051
+ eventType: "multi_page_session",
1052
+ metadata: {
1053
+ page_count: paths.size,
1054
+ page: { path: window.location.pathname }
1055
+ },
1056
+ pageUrl: window.location.href,
1057
+ occurredAt: null
1058
+ });
1059
+ }
1060
+ }
805
1061
  const originalPushState = history.pushState.bind(history);
806
1062
  const originalReplaceState = history.replaceState.bind(history);
807
1063
  function patchedPushState(...args) {
@@ -823,6 +1079,71 @@ function attachContactPageVisit(client, config) {
823
1079
  };
824
1080
  }
825
1081
 
1082
+ // ../tracking-core/src/triggers/form-start.ts
1083
+ function attachFormStart(client, config) {
1084
+ if (typeof window === "undefined" || typeof document === "undefined") {
1085
+ return () => {
1086
+ };
1087
+ }
1088
+ const selector = config.selector ?? "form";
1089
+ let firedForms = /* @__PURE__ */ new Set();
1090
+ let currentPath = window.location.pathname;
1091
+ function getFormKey(form) {
1092
+ if (form.id) return `id:${form.id}`;
1093
+ const explicitAction = form.getAttribute("action");
1094
+ if (explicitAction) return `action:${explicitAction}`;
1095
+ const forms = Array.from(document.querySelectorAll(selector));
1096
+ return `index:${forms.indexOf(form)}`;
1097
+ }
1098
+ function onFocusIn(event) {
1099
+ const target = event.target;
1100
+ if (!(target instanceof HTMLElement)) return;
1101
+ const form = target.closest(selector);
1102
+ if (!form || form.tagName !== "FORM") return;
1103
+ const key = getFormKey(form);
1104
+ if (firedForms.has(key)) return;
1105
+ firedForms.add(key);
1106
+ client.trackEvent({
1107
+ eventType: "form_start",
1108
+ metadata: {
1109
+ form: {
1110
+ id: form.id || "",
1111
+ action: form.getAttribute("action") ?? null
1112
+ },
1113
+ page: { path: window.location.pathname }
1114
+ },
1115
+ pageUrl: window.location.href,
1116
+ occurredAt: null
1117
+ });
1118
+ }
1119
+ function resetIfPathChanged() {
1120
+ const newPath = window.location.pathname;
1121
+ if (newPath === currentPath) return;
1122
+ currentPath = newPath;
1123
+ firedForms = /* @__PURE__ */ new Set();
1124
+ }
1125
+ const originalPushState = history.pushState.bind(history);
1126
+ const originalReplaceState = history.replaceState.bind(history);
1127
+ function patchedPushState(...args) {
1128
+ originalPushState(...args);
1129
+ setTimeout(resetIfPathChanged, 0);
1130
+ }
1131
+ function patchedReplaceState(...args) {
1132
+ originalReplaceState(...args);
1133
+ setTimeout(resetIfPathChanged, 0);
1134
+ }
1135
+ history.pushState = patchedPushState;
1136
+ history.replaceState = patchedReplaceState;
1137
+ window.addEventListener("popstate", resetIfPathChanged);
1138
+ document.addEventListener("focusin", onFocusIn);
1139
+ return () => {
1140
+ history.pushState = originalPushState;
1141
+ history.replaceState = originalReplaceState;
1142
+ window.removeEventListener("popstate", resetIfPathChanged);
1143
+ document.removeEventListener("focusin", onFocusIn);
1144
+ };
1145
+ }
1146
+
826
1147
  // src/ConsentBanner.tsx
827
1148
  var import_jsx_runtime = require("react/jsx-runtime");
828
1149
  function ConsentBanner() {
@@ -910,6 +1231,11 @@ function useConsentState() {
910
1231
 
911
1232
  // src/factory.tsx
912
1233
  var import_react4 = require("react");
1234
+
1235
+ // package.json
1236
+ var version = "0.5.0";
1237
+
1238
+ // src/factory.tsx
913
1239
  var import_jsx_runtime2 = require("react/jsx-runtime");
914
1240
  var NOOP_CLIENT = {
915
1241
  trackEvent: () => {
@@ -942,6 +1268,7 @@ function createTracking(options) {
942
1268
  endpoint,
943
1269
  surface: "react",
944
1270
  packageName: "@aranova/tracking-react",
1271
+ sdkVersion: version,
945
1272
  debug
946
1273
  }),
947
1274
  triggers,
@@ -968,9 +1295,21 @@ function createTracking(options) {
968
1295
  if (timeOnSite) {
969
1296
  detachers.push(attachTimeOnSite(rawClient, timeOnSite));
970
1297
  }
971
- const contactPageVisit = triggers.automatic.contact_page_visit;
972
- if (contactPageVisit) {
973
- detachers.push(attachContactPageVisit(rawClient, contactPageVisit));
1298
+ const specificPageVisit = triggers.automatic.specific_page_visit;
1299
+ if (specificPageVisit) {
1300
+ detachers.push(attachSpecificPageVisit(rawClient, specificPageVisit));
1301
+ }
1302
+ const scrollDepth = triggers.automatic.scroll_depth;
1303
+ if (scrollDepth) {
1304
+ detachers.push(attachScrollDepth(rawClient, scrollDepth));
1305
+ }
1306
+ const multiPageSession = triggers.automatic.multi_page_session;
1307
+ if (multiPageSession) {
1308
+ detachers.push(attachMultiPageSession(rawClient, multiPageSession));
1309
+ }
1310
+ const formStart = triggers.automatic.form_start;
1311
+ if (formStart) {
1312
+ detachers.push(attachFormStart(rawClient, formStart));
974
1313
  }
975
1314
  return () => {
976
1315
  for (let i = detachers.length - 1; i >= 0; i--) {