@aranova/tracking-next 0.16.2 → 0.17.1

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
  }
@@ -1324,7 +1324,11 @@ var phoneClickMetadataSchema = z8.object({
1324
1324
  }).strict(),
1325
1325
  section: z8.string().nullable().optional()
1326
1326
  }).strict();
1327
- var phoneClickConfigSchema = z8.object({}).strict();
1327
+ var phoneClickConfigSchema = z8.object({
1328
+ autoCapture: z8.object({
1329
+ selector: z8.string().optional()
1330
+ }).strict().optional()
1331
+ }).strict();
1328
1332
 
1329
1333
  // ../tracking-core/src/events/scroll-depth.ts
1330
1334
  import { z as z9 } from "zod";
@@ -1825,6 +1829,99 @@ function attachCtaClickCapture(client, config) {
1825
1829
  };
1826
1830
  }
1827
1831
 
1832
+ // ../tracking-core/src/phone.ts
1833
+ import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
1834
+ var DEFAULT_PHONE_COUNTRY = "CA";
1835
+ function parsePhone(raw, country) {
1836
+ const region = country ?? DEFAULT_PHONE_COUNTRY;
1837
+ const parsed = parsePhoneNumberFromString(raw ?? "", region);
1838
+ if (!parsed) {
1839
+ return { e164: null, national: "", international: "", country: region, isValid: false };
1840
+ }
1841
+ const isValid = parsed.isValid();
1842
+ return {
1843
+ // E.164 is only surfaced for a *valid* number — a possible-but-invalid input
1844
+ // (e.g. too few digits) still parses but must not be transmitted.
1845
+ e164: isValid ? parsed.number : null,
1846
+ national: parsed.formatNational(),
1847
+ international: parsed.formatInternational(),
1848
+ country: parsed.country ?? region,
1849
+ isValid
1850
+ };
1851
+ }
1852
+ function toE164(raw, country) {
1853
+ return parsePhone(raw, country).e164;
1854
+ }
1855
+ function formatPhone(value, format = "national", country) {
1856
+ const parsed = parsePhone(value, country);
1857
+ if (typeof format === "function") return format(parsed);
1858
+ switch (format) {
1859
+ case "international":
1860
+ return parsed.international || value;
1861
+ case "e164":
1862
+ return parsed.e164 ?? value;
1863
+ case "national":
1864
+ default:
1865
+ return parsed.national || value;
1866
+ }
1867
+ }
1868
+ function formatPhoneAsTyped(raw, country) {
1869
+ return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
1870
+ }
1871
+
1872
+ // ../tracking-core/src/triggers/phone-click-capture.ts
1873
+ var DEFAULT_TEL_SELECTOR = 'a[href^="tel:"]';
1874
+ function safeDecodeURIComponent(value) {
1875
+ try {
1876
+ return decodeURIComponent(value);
1877
+ } catch {
1878
+ return value;
1879
+ }
1880
+ }
1881
+ function resolvePhoneNumber(el) {
1882
+ const href = el instanceof HTMLAnchorElement ? el.href : el.getAttribute("href") ?? "";
1883
+ const raw = safeDecodeURIComponent(href.replace(/^tel:/i, "").split(";")[0]).trim();
1884
+ return toE164(raw) ?? raw;
1885
+ }
1886
+ function attachPhoneClickCapture(client, config) {
1887
+ if (typeof window === "undefined" || typeof document === "undefined") {
1888
+ return () => {
1889
+ };
1890
+ }
1891
+ const autoCapture = config.autoCapture;
1892
+ if (!autoCapture) {
1893
+ return () => {
1894
+ };
1895
+ }
1896
+ const selector = autoCapture.selector ?? DEFAULT_TEL_SELECTOR;
1897
+ function onClick(event) {
1898
+ const target = event.target;
1899
+ if (!(target instanceof Element)) return;
1900
+ let matched = null;
1901
+ try {
1902
+ matched = target.closest(selector);
1903
+ } catch {
1904
+ return;
1905
+ }
1906
+ if (matched === null) return;
1907
+ const metadata = {
1908
+ phone_number: resolvePhoneNumber(matched),
1909
+ page: { path: window.location.pathname },
1910
+ section: matched.getAttribute("data-aranova-section")
1911
+ };
1912
+ client.trackEvent({
1913
+ eventType: "phone_click",
1914
+ metadata,
1915
+ pageUrl: window.location.href,
1916
+ occurredAt: null
1917
+ });
1918
+ }
1919
+ document.addEventListener("click", onClick, true);
1920
+ return () => {
1921
+ document.removeEventListener("click", onClick, true);
1922
+ };
1923
+ }
1924
+
1828
1925
  // ../tracking-core/src/resources/sales/errors.ts
1829
1926
  var AranovaApiError = class extends Error {
1830
1927
  constructor(message, options) {
@@ -2112,7 +2209,10 @@ var saleCreateSchema = z12.object({
2112
2209
  metadata: metadataSchema.nullable().optional(),
2113
2210
  customer_name: customerNameSchema.nullable().optional(),
2114
2211
  customer_phone: customerPhoneSchema.nullable().optional(),
2115
- customer_email: customerEmailSchema.nullable().optional()
2212
+ customer_email: customerEmailSchema.nullable().optional(),
2213
+ // CASL consent attestation: the customer agreed to receive SMS. Recorded
2214
+ // with a timestamp server-side; every SMS send path gates on it.
2215
+ sms_consent: z12.boolean().default(false)
2116
2216
  }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
2117
2217
  var saleUpdateSchema = z12.object({
2118
2218
  description: z12.string().nullable().optional(),
@@ -2125,7 +2225,9 @@ var saleUpdateSchema = z12.object({
2125
2225
  metadata: metadataSchema.nullable().optional(),
2126
2226
  customer_name: customerNameSchema.nullable().optional(),
2127
2227
  customer_phone: customerPhoneSchema.nullable().optional(),
2128
- customer_email: customerEmailSchema.nullable().optional()
2228
+ customer_email: customerEmailSchema.nullable().optional(),
2229
+ // NOT NULL server-side: omit to leave unchanged (explicit null is rejected).
2230
+ sms_consent: z12.boolean().optional()
2129
2231
  }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: false }));
2130
2232
  var TRACKING_RANGES = ["24h", "7d", "30d"];
2131
2233
  var NAMED_RANGES = [
@@ -2147,46 +2249,6 @@ async function fetchServices(config) {
2147
2249
  return salesRequest(config, "GET", "/services");
2148
2250
  }
2149
2251
 
2150
- // ../tracking-core/src/phone.ts
2151
- import { AsYouType, parsePhoneNumberFromString } from "libphonenumber-js";
2152
- var DEFAULT_PHONE_COUNTRY = "CA";
2153
- function parsePhone(raw, country) {
2154
- const region = country ?? DEFAULT_PHONE_COUNTRY;
2155
- const parsed = parsePhoneNumberFromString(raw ?? "", region);
2156
- if (!parsed) {
2157
- return { e164: null, national: "", international: "", country: region, isValid: false };
2158
- }
2159
- const isValid = parsed.isValid();
2160
- return {
2161
- // E.164 is only surfaced for a *valid* number — a possible-but-invalid input
2162
- // (e.g. too few digits) still parses but must not be transmitted.
2163
- e164: isValid ? parsed.number : null,
2164
- national: parsed.formatNational(),
2165
- international: parsed.formatInternational(),
2166
- country: parsed.country ?? region,
2167
- isValid
2168
- };
2169
- }
2170
- function toE164(raw, country) {
2171
- return parsePhone(raw, country).e164;
2172
- }
2173
- function formatPhone(value, format = "national", country) {
2174
- const parsed = parsePhone(value, country);
2175
- if (typeof format === "function") return format(parsed);
2176
- switch (format) {
2177
- case "international":
2178
- return parsed.international || value;
2179
- case "e164":
2180
- return parsed.e164 ?? value;
2181
- case "national":
2182
- default:
2183
- return parsed.national || value;
2184
- }
2185
- }
2186
- function formatPhoneAsTyped(raw, country) {
2187
- return new AsYouType(country ?? DEFAULT_PHONE_COUNTRY).input(raw ?? "");
2188
- }
2189
-
2190
2252
  // ../tracking-core/src/phone-field.ts
2191
2253
  function phoneField(name, raw, country) {
2192
2254
  return { name, type: "phone", value: toE164(raw, country) };
@@ -2523,7 +2585,7 @@ function GoogleAdsTracking(props) {
2523
2585
  import { usePathname, useSearchParams } from "next/navigation";
2524
2586
 
2525
2587
  // package.json
2526
- var version = "0.16.2";
2588
+ var version = "0.17.1";
2527
2589
 
2528
2590
  // src/factory.tsx
2529
2591
  import {
@@ -2839,6 +2901,10 @@ function createTracking(options) {
2839
2901
  if (ctaClick) {
2840
2902
  detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
2841
2903
  }
2904
+ const phoneClick = triggers.manual?.phone_click;
2905
+ if (phoneClick) {
2906
+ detachers.push(attachPhoneClickCapture(detectorClient, phoneClick));
2907
+ }
2842
2908
  return () => {
2843
2909
  for (let i = detachers.length - 1; i >= 0; i--) {
2844
2910
  detachers[i]();