@aranova/tracking-next 0.17.0 → 0.17.2

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
@@ -702,8 +702,8 @@ function thresholdMet(goal, eventType, metadata) {
702
702
  return typeof metadata.page_name === "string" && metadata.page_name === t.page_name;
703
703
  case "page_view":
704
704
  case "form_start":
705
+ case "phone_click":
705
706
  return true;
706
- // no threshold — fire whenever the detector emits
707
707
  default:
708
708
  return false;
709
709
  }
@@ -1040,6 +1040,27 @@ function getOrCreateTrackingClient(config) {
1040
1040
  globalClientKey = key;
1041
1041
  return globalClient;
1042
1042
  }
1043
+ var clientCaptureRegistry = /* @__PURE__ */ new WeakMap();
1044
+ function attachClientCapturesOnce(client, build) {
1045
+ let entry = clientCaptureRegistry.get(client);
1046
+ if (entry === void 0) {
1047
+ entry = { detach: build(), refCount: 0 };
1048
+ clientCaptureRegistry.set(client, entry);
1049
+ }
1050
+ entry.refCount += 1;
1051
+ let released = false;
1052
+ return () => {
1053
+ if (released) return;
1054
+ released = true;
1055
+ const current = clientCaptureRegistry.get(client);
1056
+ if (current === void 0) return;
1057
+ current.refCount -= 1;
1058
+ if (current.refCount <= 0) {
1059
+ current.detach();
1060
+ clientCaptureRegistry.delete(client);
1061
+ }
1062
+ };
1063
+ }
1043
1064
  function createTrackingClient(config) {
1044
1065
  const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
1045
1066
  const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);
@@ -1324,7 +1345,11 @@ var phoneClickMetadataSchema = z8.object({
1324
1345
  }).strict(),
1325
1346
  section: z8.string().nullable().optional()
1326
1347
  }).strict();
1327
- var phoneClickConfigSchema = z8.object({}).strict();
1348
+ var phoneClickConfigSchema = z8.object({
1349
+ autoCapture: z8.object({
1350
+ selector: z8.string().optional()
1351
+ }).strict().optional()
1352
+ }).strict();
1328
1353
 
1329
1354
  // ../tracking-core/src/events/scroll-depth.ts
1330
1355
  import { z as z9 } from "zod";
@@ -1825,6 +1850,99 @@ function attachCtaClickCapture(client, config) {
1825
1850
  };
1826
1851
  }
1827
1852
 
1853
+ // ../tracking-core/src/phone.ts
1854
+ import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
1855
+ var DEFAULT_PHONE_COUNTRY = "CA";
1856
+ function parsePhone(raw, country) {
1857
+ const region = country ?? DEFAULT_PHONE_COUNTRY;
1858
+ const parsed = parsePhoneNumberFromString(raw ?? "", region);
1859
+ if (!parsed) {
1860
+ return { e164: null, national: "", international: "", country: region, isValid: false };
1861
+ }
1862
+ const isValid = parsed.isValid();
1863
+ return {
1864
+ // E.164 is only surfaced for a *valid* number — a possible-but-invalid input
1865
+ // (e.g. too few digits) still parses but must not be transmitted.
1866
+ e164: isValid ? parsed.number : null,
1867
+ national: parsed.formatNational(),
1868
+ international: parsed.formatInternational(),
1869
+ country: parsed.country ?? region,
1870
+ isValid
1871
+ };
1872
+ }
1873
+ function toE164(raw, country) {
1874
+ return parsePhone(raw, country).e164;
1875
+ }
1876
+ function formatPhone(value, format = "national", country) {
1877
+ const parsed = parsePhone(value, country);
1878
+ if (typeof format === "function") return format(parsed);
1879
+ switch (format) {
1880
+ case "international":
1881
+ return parsed.international || value;
1882
+ case "e164":
1883
+ return parsed.e164 ?? value;
1884
+ case "national":
1885
+ default:
1886
+ return parsed.national || value;
1887
+ }
1888
+ }
1889
+ function formatPhoneAsTyped(raw, country) {
1890
+ return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
1891
+ }
1892
+
1893
+ // ../tracking-core/src/triggers/phone-click-capture.ts
1894
+ var DEFAULT_TEL_SELECTOR = 'a[href^="tel:"]';
1895
+ function safeDecodeURIComponent(value) {
1896
+ try {
1897
+ return decodeURIComponent(value);
1898
+ } catch {
1899
+ return value;
1900
+ }
1901
+ }
1902
+ function resolvePhoneNumber(el) {
1903
+ const href = el instanceof HTMLAnchorElement ? el.href : el.getAttribute("href") ?? "";
1904
+ const raw = safeDecodeURIComponent(href.replace(/^tel:/i, "").split(";")[0]).trim();
1905
+ return toE164(raw) ?? raw;
1906
+ }
1907
+ function attachPhoneClickCapture(client, config) {
1908
+ if (typeof window === "undefined" || typeof document === "undefined") {
1909
+ return () => {
1910
+ };
1911
+ }
1912
+ const autoCapture = config.autoCapture;
1913
+ if (!autoCapture) {
1914
+ return () => {
1915
+ };
1916
+ }
1917
+ const selector = autoCapture.selector ?? DEFAULT_TEL_SELECTOR;
1918
+ function onClick(event) {
1919
+ const target = event.target;
1920
+ if (!(target instanceof Element)) return;
1921
+ let matched = null;
1922
+ try {
1923
+ matched = target.closest(selector);
1924
+ } catch {
1925
+ return;
1926
+ }
1927
+ if (matched === null) return;
1928
+ const metadata = {
1929
+ phone_number: resolvePhoneNumber(matched),
1930
+ page: { path: window.location.pathname },
1931
+ section: matched.getAttribute("data-aranova-section")
1932
+ };
1933
+ client.trackEvent({
1934
+ eventType: "phone_click",
1935
+ metadata,
1936
+ pageUrl: window.location.href,
1937
+ occurredAt: null
1938
+ });
1939
+ }
1940
+ document.addEventListener("click", onClick, true);
1941
+ return () => {
1942
+ document.removeEventListener("click", onClick, true);
1943
+ };
1944
+ }
1945
+
1828
1946
  // ../tracking-core/src/resources/sales/errors.ts
1829
1947
  var AranovaApiError = class extends Error {
1830
1948
  constructor(message, options) {
@@ -2152,46 +2270,6 @@ async function fetchServices(config) {
2152
2270
  return salesRequest(config, "GET", "/services");
2153
2271
  }
2154
2272
 
2155
- // ../tracking-core/src/phone.ts
2156
- import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
2157
- var DEFAULT_PHONE_COUNTRY = "CA";
2158
- function parsePhone(raw, country) {
2159
- const region = country ?? DEFAULT_PHONE_COUNTRY;
2160
- const parsed = parsePhoneNumberFromString(raw ?? "", region);
2161
- if (!parsed) {
2162
- return { e164: null, national: "", international: "", country: region, isValid: false };
2163
- }
2164
- const isValid = parsed.isValid();
2165
- return {
2166
- // E.164 is only surfaced for a *valid* number — a possible-but-invalid input
2167
- // (e.g. too few digits) still parses but must not be transmitted.
2168
- e164: isValid ? parsed.number : null,
2169
- national: parsed.formatNational(),
2170
- international: parsed.formatInternational(),
2171
- country: parsed.country ?? region,
2172
- isValid
2173
- };
2174
- }
2175
- function toE164(raw, country) {
2176
- return parsePhone(raw, country).e164;
2177
- }
2178
- function formatPhone(value, format = "national", country) {
2179
- const parsed = parsePhone(value, country);
2180
- if (typeof format === "function") return format(parsed);
2181
- switch (format) {
2182
- case "international":
2183
- return parsed.international || value;
2184
- case "e164":
2185
- return parsed.e164 ?? value;
2186
- case "national":
2187
- default:
2188
- return parsed.national || value;
2189
- }
2190
- }
2191
- function formatPhoneAsTyped(raw, country) {
2192
- return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
2193
- }
2194
-
2195
2273
  // ../tracking-core/src/phone-field.ts
2196
2274
  function phoneField(name, raw, country) {
2197
2275
  return { name, type: "phone", value: toE164(raw, country) };
@@ -2528,7 +2606,7 @@ function GoogleAdsTracking(props) {
2528
2606
  import { usePathname, useSearchParams } from "next/navigation";
2529
2607
 
2530
2608
  // package.json
2531
- var version = "0.17.0";
2609
+ var version = "0.17.2";
2532
2610
 
2533
2611
  // src/factory.tsx
2534
2612
  import {
@@ -2742,33 +2820,6 @@ function createTracking(options) {
2742
2820
  });
2743
2821
  }
2744
2822
  }, [pathname]);
2745
- useEffect3(() => {
2746
- const rawClient = getOrCreateTrackingClient({
2747
- apiKey,
2748
- endpoint,
2749
- surface: "next",
2750
- packageName: "@aranova/tracking-next",
2751
- triggers,
2752
- environment,
2753
- activeGtagIds: gtagIds,
2754
- debug
2755
- });
2756
- return attachBfcacheRestore(wrapClient(rawClient));
2757
- }, []);
2758
- useEffect3(() => {
2759
- if (!timeOnSiteConfig) return;
2760
- const rawClient = getOrCreateTrackingClient({
2761
- apiKey,
2762
- endpoint,
2763
- surface: "next",
2764
- packageName: "@aranova/tracking-next",
2765
- triggers,
2766
- environment,
2767
- activeGtagIds: gtagIds,
2768
- debug
2769
- });
2770
- return attachTimeOnSite(wrapClient(rawClient), timeOnSiteConfig);
2771
- }, []);
2772
2823
  const multiPageSessionConfig = triggers.automatic.multi_page_session ?? null;
2773
2824
  const mpsLastPathRef = useRef(null);
2774
2825
  useEffect3(() => {
@@ -2819,7 +2870,6 @@ function createTracking(options) {
2819
2870
  }
2820
2871
  }, [pathname]);
2821
2872
  useEffect3(() => {
2822
- const detachers = [];
2823
2873
  const rawClient = getOrCreateTrackingClient({
2824
2874
  apiKey,
2825
2875
  endpoint,
@@ -2830,25 +2880,36 @@ function createTracking(options) {
2830
2880
  activeGtagIds: gtagIds,
2831
2881
  debug
2832
2882
  });
2833
- const detectorClient = wrapClient(rawClient);
2834
- detachers.push(attachPageExit(detectorClient));
2835
- const scrollDepth = triggers.automatic.scroll_depth;
2836
- if (scrollDepth) {
2837
- detachers.push(attachScrollDepth(detectorClient, scrollDepth));
2838
- }
2839
- const formStart = triggers.automatic.form_start;
2840
- if (formStart) {
2841
- detachers.push(attachFormStart(detectorClient, formStart));
2842
- }
2843
- const ctaClick = triggers.manual?.cta_click;
2844
- if (ctaClick) {
2845
- detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
2846
- }
2847
- return () => {
2848
- for (let i = detachers.length - 1; i >= 0; i--) {
2849
- detachers[i]();
2883
+ return attachClientCapturesOnce(rawClient, () => {
2884
+ const detachers = [];
2885
+ const detectorClient = wrapClient(rawClient);
2886
+ detachers.push(attachPageExit(detectorClient));
2887
+ detachers.push(attachBfcacheRestore(detectorClient));
2888
+ if (timeOnSiteConfig) {
2889
+ detachers.push(attachTimeOnSite(detectorClient, timeOnSiteConfig));
2890
+ }
2891
+ const scrollDepth = triggers.automatic.scroll_depth;
2892
+ if (scrollDepth) {
2893
+ detachers.push(attachScrollDepth(detectorClient, scrollDepth));
2894
+ }
2895
+ const formStart = triggers.automatic.form_start;
2896
+ if (formStart) {
2897
+ detachers.push(attachFormStart(detectorClient, formStart));
2898
+ }
2899
+ const ctaClick = triggers.manual?.cta_click;
2900
+ if (ctaClick) {
2901
+ detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
2850
2902
  }
2851
- };
2903
+ const phoneClick = triggers.manual?.phone_click;
2904
+ if (phoneClick) {
2905
+ detachers.push(attachPhoneClickCapture(detectorClient, phoneClick));
2906
+ }
2907
+ return () => {
2908
+ for (let i = detachers.length - 1; i >= 0; i--) {
2909
+ detachers[i]();
2910
+ }
2911
+ };
2912
+ });
2852
2913
  }, []);
2853
2914
  return /* @__PURE__ */ jsxs4(TrackingContext.Provider, { value: client, children: [
2854
2915
  /* @__PURE__ */ jsx5(Suspense, { fallback: null, children: /* @__PURE__ */ jsx5(PageViewTracker, { fire: firePageView }) }),