@aranova/tracking-next 0.4.2 → 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
@@ -555,26 +555,21 @@ function createTrackingClient(config) {
555
555
  };
556
556
  }
557
557
 
558
- // ../tracking-core/src/events/contact-page-visit.ts
558
+ // ../tracking-core/src/events/cta-click.ts
559
559
  var import_zod2 = require("zod");
560
- var contactPageVisitMetadataSchema = import_zod2.z.object({
560
+ var ctaClickMetadataSchema = import_zod2.z.object({
561
+ cta_name: import_zod2.z.string(),
561
562
  page: import_zod2.z.object({
562
563
  path: import_zod2.z.string()
563
- })
564
- }).strict();
565
- var contactPageVisitConfigSchema = import_zod2.z.object({
566
- // RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
567
- // real regex at createTracking() time; the factory validates with this
568
- // schema and then keeps the live RegExp reference for runtime matching.
569
- pathPattern: import_zod2.z.custom(
570
- (value) => value instanceof RegExp,
571
- { message: "pathPattern must be a RegExp" }
572
- )
564
+ }),
565
+ section: import_zod2.z.string().nullable().optional(),
566
+ destination_url: import_zod2.z.string().nullable().optional()
573
567
  }).strict();
568
+ var ctaClickConfigSchema = import_zod2.z.object({}).strict();
574
569
 
575
- // ../tracking-core/src/events/form-submit.ts
570
+ // ../tracking-core/src/events/form-start.ts
576
571
  var import_zod3 = require("zod");
577
- var formSubmitMetadataSchema = import_zod3.z.object({
572
+ var formStartMetadataSchema = import_zod3.z.object({
578
573
  form: import_zod3.z.object({
579
574
  id: import_zod3.z.string(),
580
575
  action: import_zod3.z.string().nullable()
@@ -583,32 +578,106 @@ var formSubmitMetadataSchema = import_zod3.z.object({
583
578
  path: import_zod3.z.string()
584
579
  })
585
580
  }).strict();
586
- var formSubmitConfigSchema = import_zod3.z.object({}).strict();
581
+ var formStartConfigSchema = import_zod3.z.object({
582
+ selector: import_zod3.z.string().optional()
583
+ }).strict();
587
584
 
588
- // ../tracking-core/src/events/phone-click.ts
585
+ // ../tracking-core/src/events/form-submit.ts
589
586
  var import_zod4 = require("zod");
590
- var phoneClickMetadataSchema = import_zod4.z.object({
591
- element: import_zod4.z.object({
592
- tag: import_zod4.z.string(),
593
- text: import_zod4.z.string().nullable(),
594
- href: import_zod4.z.string()
587
+ var formSubmitMetadataSchema = import_zod4.z.object({
588
+ form: import_zod4.z.object({
589
+ id: import_zod4.z.string(),
590
+ action: import_zod4.z.string().nullable(),
591
+ fields: import_zod4.z.array(
592
+ import_zod4.z.object({
593
+ name: import_zod4.z.string(),
594
+ type: import_zod4.z.string(),
595
+ label: import_zod4.z.string().nullable(),
596
+ has_value: import_zod4.z.boolean()
597
+ })
598
+ ).optional()
595
599
  }),
596
600
  page: import_zod4.z.object({
597
601
  path: import_zod4.z.string()
598
602
  })
599
603
  }).strict();
600
- var phoneClickConfigSchema = import_zod4.z.object({}).strict();
604
+ var formSubmitConfigSchema = import_zod4.z.object({}).strict();
601
605
 
602
- // ../tracking-core/src/events/time-on-site.ts
606
+ // ../tracking-core/src/events/multi-page-session.ts
603
607
  var import_zod5 = require("zod");
604
- var timeOnSiteMetadataSchema = import_zod5.z.object({
605
- duration_ms: import_zod5.z.number().int().nonnegative(),
608
+ var multiPageSessionMetadataSchema = import_zod5.z.object({
609
+ page_count: import_zod5.z.number().int().min(2),
606
610
  page: import_zod5.z.object({
607
611
  path: import_zod5.z.string()
608
612
  })
609
613
  }).strict();
610
- var timeOnSiteConfigSchema = import_zod5.z.object({
611
- thresholdSeconds: import_zod5.z.number().int().positive()
614
+ var multiPageSessionConfigSchema = import_zod5.z.object({
615
+ pageThreshold: import_zod5.z.number().int().min(2)
616
+ }).strict();
617
+
618
+ // ../tracking-core/src/events/phone-click.ts
619
+ var import_zod6 = require("zod");
620
+ var phoneClickMetadataSchema = import_zod6.z.object({
621
+ phone_number: import_zod6.z.string(),
622
+ page: import_zod6.z.object({
623
+ path: import_zod6.z.string()
624
+ }),
625
+ section: import_zod6.z.string().nullable().optional()
626
+ }).strict();
627
+ var phoneClickConfigSchema = import_zod6.z.object({}).strict();
628
+
629
+ // ../tracking-core/src/events/scroll-depth.ts
630
+ var import_zod7 = require("zod");
631
+ var scrollDepthMetadataSchema = import_zod7.z.object({
632
+ depth_percent: import_zod7.z.number().int().min(1).max(100),
633
+ page: import_zod7.z.object({
634
+ path: import_zod7.z.string()
635
+ })
636
+ }).strict();
637
+ var scrollDepthConfigSchema = import_zod7.z.object({
638
+ thresholds: import_zod7.z.array(import_zod7.z.number().int().min(1).max(100)).min(1)
639
+ }).strict();
640
+
641
+ // ../tracking-core/src/events/specific-page-visit.ts
642
+ var import_zod8 = require("zod");
643
+ var SPECIFIC_PAGE_NAMES = [
644
+ "contact_page",
645
+ "about_page",
646
+ "services_page",
647
+ "booking_page",
648
+ "location_page",
649
+ "pricing_page",
650
+ "faq_page",
651
+ "testimonials_page"
652
+ ];
653
+ var specificPageNameSchema = import_zod8.z.enum(SPECIFIC_PAGE_NAMES);
654
+ var specificPageVisitMetadataSchema = import_zod8.z.object({
655
+ page_name: specificPageNameSchema,
656
+ page: import_zod8.z.object({
657
+ path: import_zod8.z.string()
658
+ })
659
+ }).strict();
660
+ var specificPageVisitConfigSchema = import_zod8.z.object({
661
+ pages: import_zod8.z.array(
662
+ import_zod8.z.object({
663
+ name: specificPageNameSchema,
664
+ pathPattern: import_zod8.z.custom((value) => value instanceof RegExp, {
665
+ message: "pathPattern must be a RegExp"
666
+ })
667
+ })
668
+ ).min(1)
669
+ }).strict();
670
+
671
+ // ../tracking-core/src/events/time-on-site.ts
672
+ var import_zod9 = require("zod");
673
+ var timeOnSiteMetadataSchema = import_zod9.z.object({
674
+ duration_ms: import_zod9.z.number().int().nonnegative(),
675
+ page: import_zod9.z.object({
676
+ path: import_zod9.z.string()
677
+ })
678
+ }).strict();
679
+ var timeOnSiteConfigSchema = import_zod9.z.object({
680
+ thresholdSeconds: import_zod9.z.number().int().positive()
612
681
  }).strict();
613
682
 
614
683
  // ../tracking-core/src/events/registry.ts
@@ -624,10 +693,25 @@ var EVENT_REGISTRY = {
624
693
  metadataSchema: timeOnSiteMetadataSchema,
625
694
  configSchema: timeOnSiteConfigSchema
626
695
  },
627
- contact_page_visit: {
696
+ specific_page_visit: {
628
697
  kind: "automatic",
629
- metadataSchema: contactPageVisitMetadataSchema,
630
- configSchema: contactPageVisitConfigSchema
698
+ metadataSchema: specificPageVisitMetadataSchema,
699
+ configSchema: specificPageVisitConfigSchema
700
+ },
701
+ scroll_depth: {
702
+ kind: "automatic",
703
+ metadataSchema: scrollDepthMetadataSchema,
704
+ configSchema: scrollDepthConfigSchema
705
+ },
706
+ multi_page_session: {
707
+ kind: "automatic",
708
+ metadataSchema: multiPageSessionMetadataSchema,
709
+ configSchema: multiPageSessionConfigSchema
710
+ },
711
+ form_start: {
712
+ kind: "automatic",
713
+ metadataSchema: formStartMetadataSchema,
714
+ configSchema: formStartConfigSchema
631
715
  },
632
716
  // --- manual triggers ---
633
717
  form_submit: {
@@ -639,6 +723,11 @@ var EVENT_REGISTRY = {
639
723
  kind: "manual",
640
724
  metadataSchema: phoneClickMetadataSchema,
641
725
  configSchema: phoneClickConfigSchema
726
+ },
727
+ cta_click: {
728
+ kind: "manual",
729
+ metadataSchema: ctaClickMetadataSchema,
730
+ configSchema: ctaClickConfigSchema
642
731
  }
643
732
  };
644
733
  var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
@@ -729,6 +818,144 @@ function attachTimeOnSite(client, config) {
729
818
  };
730
819
  }
731
820
 
821
+ // ../tracking-core/src/triggers/scroll-depth.ts
822
+ function attachScrollDepth(client, config) {
823
+ if (typeof window === "undefined" || typeof document === "undefined") {
824
+ return () => {
825
+ };
826
+ }
827
+ const thresholds = new Set(config.thresholds);
828
+ let firedForPath = /* @__PURE__ */ new Set();
829
+ let currentPath = window.location.pathname;
830
+ let rafId = null;
831
+ function getScrollPercent() {
832
+ const doc = document.documentElement;
833
+ const scrollTop = window.scrollY || doc.scrollTop;
834
+ const scrollHeight = doc.scrollHeight;
835
+ const clientHeight = doc.clientHeight;
836
+ if (scrollHeight <= clientHeight) return 100;
837
+ return Math.round((scrollTop + clientHeight) / scrollHeight * 100);
838
+ }
839
+ function checkThresholds() {
840
+ const percent = getScrollPercent();
841
+ for (const threshold of thresholds) {
842
+ if (percent >= threshold && !firedForPath.has(threshold)) {
843
+ firedForPath.add(threshold);
844
+ client.trackEvent({
845
+ eventType: "scroll_depth",
846
+ metadata: {
847
+ depth_percent: threshold,
848
+ page: { path: currentPath }
849
+ },
850
+ pageUrl: window.location.href,
851
+ occurredAt: null
852
+ });
853
+ }
854
+ }
855
+ }
856
+ function onScroll() {
857
+ if (rafId !== null) return;
858
+ rafId = requestAnimationFrame(() => {
859
+ rafId = null;
860
+ checkThresholds();
861
+ });
862
+ }
863
+ function resetIfPathChanged() {
864
+ const newPath = window.location.pathname;
865
+ if (newPath === currentPath) return;
866
+ currentPath = newPath;
867
+ firedForPath = /* @__PURE__ */ new Set();
868
+ setTimeout(checkThresholds, 0);
869
+ }
870
+ const originalPushState = history.pushState.bind(history);
871
+ const originalReplaceState = history.replaceState.bind(history);
872
+ function patchedPushState(...args) {
873
+ originalPushState(...args);
874
+ setTimeout(resetIfPathChanged, 0);
875
+ }
876
+ function patchedReplaceState(...args) {
877
+ originalReplaceState(...args);
878
+ setTimeout(resetIfPathChanged, 0);
879
+ }
880
+ history.pushState = patchedPushState;
881
+ history.replaceState = patchedReplaceState;
882
+ window.addEventListener("popstate", resetIfPathChanged);
883
+ window.addEventListener("scroll", onScroll, { passive: true });
884
+ setTimeout(checkThresholds, 0);
885
+ return () => {
886
+ if (rafId !== null) cancelAnimationFrame(rafId);
887
+ history.pushState = originalPushState;
888
+ history.replaceState = originalReplaceState;
889
+ window.removeEventListener("popstate", resetIfPathChanged);
890
+ window.removeEventListener("scroll", onScroll);
891
+ };
892
+ }
893
+
894
+ // ../tracking-core/src/triggers/form-start.ts
895
+ function attachFormStart(client, config) {
896
+ if (typeof window === "undefined" || typeof document === "undefined") {
897
+ return () => {
898
+ };
899
+ }
900
+ const selector = config.selector ?? "form";
901
+ let firedForms = /* @__PURE__ */ new Set();
902
+ let currentPath = window.location.pathname;
903
+ function getFormKey(form) {
904
+ if (form.id) return `id:${form.id}`;
905
+ const explicitAction = form.getAttribute("action");
906
+ if (explicitAction) return `action:${explicitAction}`;
907
+ const forms = Array.from(document.querySelectorAll(selector));
908
+ return `index:${forms.indexOf(form)}`;
909
+ }
910
+ function onFocusIn(event) {
911
+ const target = event.target;
912
+ if (!(target instanceof HTMLElement)) return;
913
+ const form = target.closest(selector);
914
+ if (!form || form.tagName !== "FORM") return;
915
+ const key = getFormKey(form);
916
+ if (firedForms.has(key)) return;
917
+ firedForms.add(key);
918
+ client.trackEvent({
919
+ eventType: "form_start",
920
+ metadata: {
921
+ form: {
922
+ id: form.id || "",
923
+ action: form.getAttribute("action") ?? null
924
+ },
925
+ page: { path: window.location.pathname }
926
+ },
927
+ pageUrl: window.location.href,
928
+ occurredAt: null
929
+ });
930
+ }
931
+ function resetIfPathChanged() {
932
+ const newPath = window.location.pathname;
933
+ if (newPath === currentPath) return;
934
+ currentPath = newPath;
935
+ firedForms = /* @__PURE__ */ new Set();
936
+ }
937
+ const originalPushState = history.pushState.bind(history);
938
+ const originalReplaceState = history.replaceState.bind(history);
939
+ function patchedPushState(...args) {
940
+ originalPushState(...args);
941
+ setTimeout(resetIfPathChanged, 0);
942
+ }
943
+ function patchedReplaceState(...args) {
944
+ originalReplaceState(...args);
945
+ setTimeout(resetIfPathChanged, 0);
946
+ }
947
+ history.pushState = patchedPushState;
948
+ history.replaceState = patchedReplaceState;
949
+ window.addEventListener("popstate", resetIfPathChanged);
950
+ document.addEventListener("focusin", onFocusIn);
951
+ return () => {
952
+ history.pushState = originalPushState;
953
+ history.replaceState = originalReplaceState;
954
+ window.removeEventListener("popstate", resetIfPathChanged);
955
+ document.removeEventListener("focusin", onFocusIn);
956
+ };
957
+ }
958
+
732
959
  // src/ConsentBanner.tsx
733
960
  var import_react = require("react");
734
961
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -828,7 +1055,7 @@ function GoogleAdsTracking({ gtagId }) {
828
1055
  var import_navigation = require("next/navigation");
829
1056
 
830
1057
  // package.json
831
- var version = "0.4.2";
1058
+ var version = "0.5.0";
832
1059
 
833
1060
  // src/factory.tsx
834
1061
  var import_react3 = require("react");
@@ -855,7 +1082,7 @@ function createTracking(options) {
855
1082
  };
856
1083
  }
857
1084
  const TrackingContext = (0, import_react3.createContext)(null);
858
- const contactPageVisitConfig = triggers.automatic.contact_page_visit ?? null;
1085
+ const specificPageVisitConfig = triggers.automatic.specific_page_visit ?? null;
859
1086
  const timeOnSiteConfig = triggers.automatic.time_on_site ?? null;
860
1087
  function TrackingProvider({ children }) {
861
1088
  const pathname = (0, import_navigation.usePathname)();
@@ -885,12 +1112,9 @@ function createTracking(options) {
885
1112
  });
886
1113
  fireManualPageView(rawClient);
887
1114
  }, [pathname, searchParams]);
888
- const lastContactPathRef = (0, import_react3.useRef)(null);
1115
+ const firedPagesRef = (0, import_react3.useRef)(/* @__PURE__ */ new Set());
889
1116
  (0, import_react3.useEffect)(() => {
890
- if (!contactPageVisitConfig || pathname === null) return;
891
- if (!contactPageVisitConfig.pathPattern.test(pathname)) return;
892
- if (lastContactPathRef.current === pathname) return;
893
- lastContactPathRef.current = pathname;
1117
+ if (!specificPageVisitConfig || pathname === null) return;
894
1118
  const rawClient = getOrCreateTrackingClient({
895
1119
  apiKey,
896
1120
  endpoint,
@@ -898,12 +1122,18 @@ function createTracking(options) {
898
1122
  packageName: "@aranova/tracking-next",
899
1123
  debug
900
1124
  });
901
- rawClient.trackEvent({
902
- eventType: "contact_page_visit",
903
- metadata: { page: { path: pathname } },
904
- pageUrl: null,
905
- occurredAt: null
906
- });
1125
+ for (const { name, pathPattern } of specificPageVisitConfig.pages) {
1126
+ if (!pathPattern.test(pathname)) continue;
1127
+ const key = `${name}:${pathname}`;
1128
+ if (firedPagesRef.current.has(key)) continue;
1129
+ firedPagesRef.current.add(key);
1130
+ rawClient.trackEvent({
1131
+ eventType: "specific_page_visit",
1132
+ metadata: { page_name: name, page: { path: pathname } },
1133
+ pageUrl: null,
1134
+ occurredAt: null
1135
+ });
1136
+ }
907
1137
  }, [pathname]);
908
1138
  (0, import_react3.useEffect)(() => {
909
1139
  const rawClient = getOrCreateTrackingClient({
@@ -926,6 +1156,74 @@ function createTracking(options) {
926
1156
  });
927
1157
  return attachTimeOnSite(rawClient, timeOnSiteConfig);
928
1158
  }, []);
1159
+ const multiPageSessionConfig = triggers.automatic.multi_page_session ?? null;
1160
+ const mpsLastPathRef = (0, import_react3.useRef)(null);
1161
+ (0, import_react3.useEffect)(() => {
1162
+ if (!multiPageSessionConfig || pathname === null) return;
1163
+ if (pathname === mpsLastPathRef.current) return;
1164
+ mpsLastPathRef.current = pathname;
1165
+ const MPS_PATHS_KEY = "aranova_tracking_mps_paths";
1166
+ const MPS_SESSION_KEY = "aranova_tracking_mps_session";
1167
+ const MPS_FIRED_KEY = "aranova_tracking_mps_fired";
1168
+ try {
1169
+ const store = window.sessionStorage;
1170
+ const currentSession = getOrRotateSessionId();
1171
+ const storedSession = store.getItem(MPS_SESSION_KEY);
1172
+ if (storedSession !== currentSession) {
1173
+ store.setItem(MPS_SESSION_KEY, currentSession);
1174
+ store.removeItem(MPS_PATHS_KEY);
1175
+ store.removeItem(MPS_FIRED_KEY);
1176
+ }
1177
+ if (store.getItem(MPS_FIRED_KEY) === "1") return;
1178
+ const raw = store.getItem(MPS_PATHS_KEY);
1179
+ const paths = raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
1180
+ paths.add(pathname);
1181
+ store.setItem(MPS_PATHS_KEY, JSON.stringify([...paths]));
1182
+ if (paths.size >= multiPageSessionConfig.pageThreshold) {
1183
+ store.setItem(MPS_FIRED_KEY, "1");
1184
+ const rawClient = getOrCreateTrackingClient({
1185
+ apiKey,
1186
+ endpoint,
1187
+ surface: "next",
1188
+ packageName: "@aranova/tracking-next",
1189
+ debug
1190
+ });
1191
+ rawClient.trackEvent({
1192
+ eventType: "multi_page_session",
1193
+ metadata: {
1194
+ page_count: paths.size,
1195
+ page: { path: pathname }
1196
+ },
1197
+ pageUrl: null,
1198
+ occurredAt: null
1199
+ });
1200
+ }
1201
+ } catch {
1202
+ }
1203
+ }, [pathname]);
1204
+ (0, import_react3.useEffect)(() => {
1205
+ const detachers = [];
1206
+ const rawClient = getOrCreateTrackingClient({
1207
+ apiKey,
1208
+ endpoint,
1209
+ surface: "next",
1210
+ packageName: "@aranova/tracking-next",
1211
+ debug
1212
+ });
1213
+ const scrollDepth = triggers.automatic.scroll_depth;
1214
+ if (scrollDepth) {
1215
+ detachers.push(attachScrollDepth(rawClient, scrollDepth));
1216
+ }
1217
+ const formStart = triggers.automatic.form_start;
1218
+ if (formStart) {
1219
+ detachers.push(attachFormStart(rawClient, formStart));
1220
+ }
1221
+ return () => {
1222
+ for (let i = detachers.length - 1; i >= 0; i--) {
1223
+ detachers[i]();
1224
+ }
1225
+ };
1226
+ }, []);
929
1227
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TrackingContext.Provider, { value: client, children });
930
1228
  }
931
1229
  function useTracking() {