@aranova/tracking-react 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
@@ -758,8 +758,8 @@ function thresholdMet(goal, eventType, metadata) {
758
758
  return typeof metadata.page_name === "string" && metadata.page_name === t.page_name;
759
759
  case "page_view":
760
760
  case "form_start":
761
+ case "phone_click":
761
762
  return true;
762
- // no threshold — fire whenever the detector emits
763
763
  default:
764
764
  return false;
765
765
  }
@@ -1134,6 +1134,27 @@ function getOrCreateTrackingClient(config) {
1134
1134
  globalClientKey = key;
1135
1135
  return globalClient;
1136
1136
  }
1137
+ var clientCaptureRegistry = /* @__PURE__ */ new WeakMap();
1138
+ function attachClientCapturesOnce(client, build) {
1139
+ let entry = clientCaptureRegistry.get(client);
1140
+ if (entry === void 0) {
1141
+ entry = { detach: build(), refCount: 0 };
1142
+ clientCaptureRegistry.set(client, entry);
1143
+ }
1144
+ entry.refCount += 1;
1145
+ let released = false;
1146
+ return () => {
1147
+ if (released) return;
1148
+ released = true;
1149
+ const current = clientCaptureRegistry.get(client);
1150
+ if (current === void 0) return;
1151
+ current.refCount -= 1;
1152
+ if (current.refCount <= 0) {
1153
+ current.detach();
1154
+ clientCaptureRegistry.delete(client);
1155
+ }
1156
+ };
1157
+ }
1137
1158
  function createTrackingClient(config) {
1138
1159
  const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
1139
1160
  const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);
@@ -1418,7 +1439,11 @@ var phoneClickMetadataSchema = z8.object({
1418
1439
  }).strict(),
1419
1440
  section: z8.string().nullable().optional()
1420
1441
  }).strict();
1421
- var phoneClickConfigSchema = z8.object({}).strict();
1442
+ var phoneClickConfigSchema = z8.object({
1443
+ autoCapture: z8.object({
1444
+ selector: z8.string().optional()
1445
+ }).strict().optional()
1446
+ }).strict();
1422
1447
 
1423
1448
  // ../tracking-core/src/events/scroll-depth.ts
1424
1449
  import { z as z9 } from "zod";
@@ -2054,6 +2079,99 @@ function attachCtaClickCapture(client, config) {
2054
2079
  };
2055
2080
  }
2056
2081
 
2082
+ // ../tracking-core/src/phone.ts
2083
+ import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
2084
+ var DEFAULT_PHONE_COUNTRY = "CA";
2085
+ function parsePhone(raw, country) {
2086
+ const region = country ?? DEFAULT_PHONE_COUNTRY;
2087
+ const parsed = parsePhoneNumberFromString(raw ?? "", region);
2088
+ if (!parsed) {
2089
+ return { e164: null, national: "", international: "", country: region, isValid: false };
2090
+ }
2091
+ const isValid = parsed.isValid();
2092
+ return {
2093
+ // E.164 is only surfaced for a *valid* number — a possible-but-invalid input
2094
+ // (e.g. too few digits) still parses but must not be transmitted.
2095
+ e164: isValid ? parsed.number : null,
2096
+ national: parsed.formatNational(),
2097
+ international: parsed.formatInternational(),
2098
+ country: parsed.country ?? region,
2099
+ isValid
2100
+ };
2101
+ }
2102
+ function toE164(raw, country) {
2103
+ return parsePhone(raw, country).e164;
2104
+ }
2105
+ function formatPhone(value, format = "national", country) {
2106
+ const parsed = parsePhone(value, country);
2107
+ if (typeof format === "function") return format(parsed);
2108
+ switch (format) {
2109
+ case "international":
2110
+ return parsed.international || value;
2111
+ case "e164":
2112
+ return parsed.e164 ?? value;
2113
+ case "national":
2114
+ default:
2115
+ return parsed.national || value;
2116
+ }
2117
+ }
2118
+ function formatPhoneAsTyped(raw, country) {
2119
+ return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
2120
+ }
2121
+
2122
+ // ../tracking-core/src/triggers/phone-click-capture.ts
2123
+ var DEFAULT_TEL_SELECTOR = 'a[href^="tel:"]';
2124
+ function safeDecodeURIComponent(value) {
2125
+ try {
2126
+ return decodeURIComponent(value);
2127
+ } catch {
2128
+ return value;
2129
+ }
2130
+ }
2131
+ function resolvePhoneNumber(el) {
2132
+ const href = el instanceof HTMLAnchorElement ? el.href : el.getAttribute("href") ?? "";
2133
+ const raw = safeDecodeURIComponent(href.replace(/^tel:/i, "").split(";")[0]).trim();
2134
+ return toE164(raw) ?? raw;
2135
+ }
2136
+ function attachPhoneClickCapture(client, config) {
2137
+ if (typeof window === "undefined" || typeof document === "undefined") {
2138
+ return () => {
2139
+ };
2140
+ }
2141
+ const autoCapture = config.autoCapture;
2142
+ if (!autoCapture) {
2143
+ return () => {
2144
+ };
2145
+ }
2146
+ const selector = autoCapture.selector ?? DEFAULT_TEL_SELECTOR;
2147
+ function onClick(event) {
2148
+ const target = event.target;
2149
+ if (!(target instanceof Element)) return;
2150
+ let matched = null;
2151
+ try {
2152
+ matched = target.closest(selector);
2153
+ } catch {
2154
+ return;
2155
+ }
2156
+ if (matched === null) return;
2157
+ const metadata = {
2158
+ phone_number: resolvePhoneNumber(matched),
2159
+ page: { path: window.location.pathname },
2160
+ section: matched.getAttribute("data-aranova-section")
2161
+ };
2162
+ client.trackEvent({
2163
+ eventType: "phone_click",
2164
+ metadata,
2165
+ pageUrl: window.location.href,
2166
+ occurredAt: null
2167
+ });
2168
+ }
2169
+ document.addEventListener("click", onClick, true);
2170
+ return () => {
2171
+ document.removeEventListener("click", onClick, true);
2172
+ };
2173
+ }
2174
+
2057
2175
  // ../tracking-core/src/resources/sales/errors.ts
2058
2176
  var AranovaApiError = class extends Error {
2059
2177
  constructor(message, options) {
@@ -2381,46 +2499,6 @@ async function fetchServices(config) {
2381
2499
  return salesRequest(config, "GET", "/services");
2382
2500
  }
2383
2501
 
2384
- // ../tracking-core/src/phone.ts
2385
- import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
2386
- var DEFAULT_PHONE_COUNTRY = "CA";
2387
- function parsePhone(raw, country) {
2388
- const region = country ?? DEFAULT_PHONE_COUNTRY;
2389
- const parsed = parsePhoneNumberFromString(raw ?? "", region);
2390
- if (!parsed) {
2391
- return { e164: null, national: "", international: "", country: region, isValid: false };
2392
- }
2393
- const isValid = parsed.isValid();
2394
- return {
2395
- // E.164 is only surfaced for a *valid* number — a possible-but-invalid input
2396
- // (e.g. too few digits) still parses but must not be transmitted.
2397
- e164: isValid ? parsed.number : null,
2398
- national: parsed.formatNational(),
2399
- international: parsed.formatInternational(),
2400
- country: parsed.country ?? region,
2401
- isValid
2402
- };
2403
- }
2404
- function toE164(raw, country) {
2405
- return parsePhone(raw, country).e164;
2406
- }
2407
- function formatPhone(value, format = "national", country) {
2408
- const parsed = parsePhone(value, country);
2409
- if (typeof format === "function") return format(parsed);
2410
- switch (format) {
2411
- case "international":
2412
- return parsed.international || value;
2413
- case "e164":
2414
- return parsed.e164 ?? value;
2415
- case "national":
2416
- default:
2417
- return parsed.national || value;
2418
- }
2419
- }
2420
- function formatPhoneAsTyped(raw, country) {
2421
- return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
2422
- }
2423
-
2424
2502
  // ../tracking-core/src/phone-field.ts
2425
2503
  function phoneField(name, raw, country) {
2426
2504
  return { name, type: "phone", value: toE164(raw, country) };
@@ -2743,7 +2821,7 @@ function GoogleAdsTracking(props) {
2743
2821
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect5, useMemo as useMemo4 } from "react";
2744
2822
 
2745
2823
  // package.json
2746
- var version = "0.17.0";
2824
+ var version = "0.17.2";
2747
2825
 
2748
2826
  // ../tracking-core/src/phone-react.tsx
2749
2827
  import {
@@ -2913,7 +2991,6 @@ function createTracking(options) {
2913
2991
  }
2914
2992
  }, [metaPixelId, metaPixelIdsKey]);
2915
2993
  useEffect5(() => {
2916
- const detachers = [];
2917
2994
  const rawClient = getOrCreateTrackingClient({
2918
2995
  apiKey,
2919
2996
  endpoint,
@@ -2924,43 +3001,50 @@ function createTracking(options) {
2924
3001
  activeGtagIds: resolvedGtagIds,
2925
3002
  debug
2926
3003
  });
2927
- const conversionStore = conversionConfig ? resolveConversionConfig({
2928
- cdnUrl: conversionConfig.cdnUrl,
2929
- baked: conversionConfig.baked
2930
- }) : null;
2931
- const detectorClient = conversionStore ? withConversionAutoFire(rawClient, createConversionAutoFire(conversionStore)) : rawClient;
2932
- detachers.push(attachAutoPageView(detectorClient));
2933
- detachers.push(attachBfcacheRestore(detectorClient));
2934
- detachers.push(attachPageExit(detectorClient));
2935
- const timeOnSite = triggers.automatic.time_on_site;
2936
- if (timeOnSite) {
2937
- detachers.push(attachTimeOnSite(detectorClient, timeOnSite));
2938
- }
2939
- const specificPageVisit = triggers.automatic.specific_page_visit;
2940
- if (specificPageVisit) {
2941
- detachers.push(attachSpecificPageVisit(detectorClient, specificPageVisit));
2942
- }
2943
- const scrollDepth = triggers.automatic.scroll_depth;
2944
- if (scrollDepth) {
2945
- detachers.push(attachScrollDepth(detectorClient, scrollDepth));
2946
- }
2947
- const multiPageSession = triggers.automatic.multi_page_session;
2948
- if (multiPageSession) {
2949
- detachers.push(attachMultiPageSession(detectorClient, multiPageSession));
2950
- }
2951
- const formStart = triggers.automatic.form_start;
2952
- if (formStart) {
2953
- detachers.push(attachFormStart(detectorClient, formStart));
2954
- }
2955
- const ctaClick = triggers.manual?.cta_click;
2956
- if (ctaClick) {
2957
- detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
2958
- }
2959
- return () => {
2960
- for (let i = detachers.length - 1; i >= 0; i--) {
2961
- detachers[i]();
3004
+ return attachClientCapturesOnce(rawClient, () => {
3005
+ const detachers = [];
3006
+ const conversionStore = conversionConfig ? resolveConversionConfig({
3007
+ cdnUrl: conversionConfig.cdnUrl,
3008
+ baked: conversionConfig.baked
3009
+ }) : null;
3010
+ const detectorClient = conversionStore ? withConversionAutoFire(rawClient, createConversionAutoFire(conversionStore)) : rawClient;
3011
+ detachers.push(attachAutoPageView(detectorClient));
3012
+ detachers.push(attachBfcacheRestore(detectorClient));
3013
+ detachers.push(attachPageExit(detectorClient));
3014
+ const timeOnSite = triggers.automatic.time_on_site;
3015
+ if (timeOnSite) {
3016
+ detachers.push(attachTimeOnSite(detectorClient, timeOnSite));
3017
+ }
3018
+ const specificPageVisit = triggers.automatic.specific_page_visit;
3019
+ if (specificPageVisit) {
3020
+ detachers.push(attachSpecificPageVisit(detectorClient, specificPageVisit));
2962
3021
  }
2963
- };
3022
+ const scrollDepth = triggers.automatic.scroll_depth;
3023
+ if (scrollDepth) {
3024
+ detachers.push(attachScrollDepth(detectorClient, scrollDepth));
3025
+ }
3026
+ const multiPageSession = triggers.automatic.multi_page_session;
3027
+ if (multiPageSession) {
3028
+ detachers.push(attachMultiPageSession(detectorClient, multiPageSession));
3029
+ }
3030
+ const formStart = triggers.automatic.form_start;
3031
+ if (formStart) {
3032
+ detachers.push(attachFormStart(detectorClient, formStart));
3033
+ }
3034
+ const ctaClick = triggers.manual?.cta_click;
3035
+ if (ctaClick) {
3036
+ detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
3037
+ }
3038
+ const phoneClick = triggers.manual?.phone_click;
3039
+ if (phoneClick) {
3040
+ detachers.push(attachPhoneClickCapture(detectorClient, phoneClick));
3041
+ }
3042
+ return () => {
3043
+ for (let i = detachers.length - 1; i >= 0; i--) {
3044
+ detachers[i]();
3045
+ }
3046
+ };
3047
+ });
2964
3048
  }, []);
2965
3049
  return /* @__PURE__ */ jsx3(TrackingContext.Provider, { value: client, children: /* @__PURE__ */ jsx3(PhoneConfigProvider, { value: phone ?? null, children }) });
2966
3050
  }