@aranova/tracking-react 0.15.0 → 0.16.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.js CHANGED
@@ -1357,6 +1357,7 @@ function createTrackingClient(config) {
1357
1357
  return {
1358
1358
  trackEvent,
1359
1359
  flush,
1360
+ flushBeacon: flushOnUnload,
1360
1361
  getSessionId: () => sessionId,
1361
1362
  getVisitorId: () => visitorId,
1362
1363
  destroy: () => {
@@ -1381,9 +1382,17 @@ var ctaClickMetadataSchema = import_zod2.z.object({
1381
1382
  path: import_zod2.z.string()
1382
1383
  }).strict(),
1383
1384
  section: import_zod2.z.string().nullable().optional(),
1384
- destination_url: import_zod2.z.string().nullable().optional()
1385
+ destination_url: import_zod2.z.string().nullable().optional(),
1386
+ // Set by auto-capture (and available to manual callers): the link target
1387
+ // and a short element descriptor (tag#id) for tying clicks to specific UI.
1388
+ href: import_zod2.z.string().nullable().optional(),
1389
+ element: import_zod2.z.string().nullable().optional()
1390
+ }).strict();
1391
+ var ctaClickConfigSchema = import_zod2.z.object({
1392
+ autoCapture: import_zod2.z.object({
1393
+ selector: import_zod2.z.string().optional()
1394
+ }).strict().optional()
1385
1395
  }).strict();
1386
- var ctaClickConfigSchema = import_zod2.z.object({}).strict();
1387
1396
 
1388
1397
  // ../tracking-core/src/events/sdk-heartbeat.ts
1389
1398
  var import_zod3 = require("zod");
@@ -1459,31 +1468,44 @@ var multiPageSessionConfigSchema = import_zod6.z.object({
1459
1468
  pageThreshold: import_zod6.z.number().int().min(2)
1460
1469
  }).strict();
1461
1470
 
1462
- // ../tracking-core/src/events/phone-click.ts
1471
+ // ../tracking-core/src/events/page-exit.ts
1463
1472
  var import_zod7 = require("zod");
1464
- var phoneClickMetadataSchema = import_zod7.z.object({
1465
- phone_number: import_zod7.z.string(),
1473
+ var pageExitMetadataSchema = import_zod7.z.object({
1474
+ dwell_ms: import_zod7.z.number().int().min(0),
1475
+ // null = left without any scroll signal; floor is 0 so a valid 0% is never
1476
+ // rejected (a single bad field 422s the whole keepalive beacon batch).
1477
+ max_scroll_percent: import_zod7.z.number().int().min(0).max(100).nullable(),
1466
1478
  page: import_zod7.z.object({
1467
1479
  path: import_zod7.z.string()
1468
- }).strict(),
1469
- section: import_zod7.z.string().nullable().optional()
1480
+ }).strict()
1470
1481
  }).strict();
1471
- var phoneClickConfigSchema = import_zod7.z.object({}).strict();
1482
+ var pageExitConfigSchema = import_zod7.z.object({}).strict();
1472
1483
 
1473
- // ../tracking-core/src/events/scroll-depth.ts
1484
+ // ../tracking-core/src/events/phone-click.ts
1474
1485
  var import_zod8 = require("zod");
1475
- var scrollDepthMetadataSchema = import_zod8.z.object({
1476
- depth_percent: import_zod8.z.number().int().min(1).max(100),
1486
+ var phoneClickMetadataSchema = import_zod8.z.object({
1487
+ phone_number: import_zod8.z.string(),
1477
1488
  page: import_zod8.z.object({
1478
1489
  path: import_zod8.z.string()
1490
+ }).strict(),
1491
+ section: import_zod8.z.string().nullable().optional()
1492
+ }).strict();
1493
+ var phoneClickConfigSchema = import_zod8.z.object({}).strict();
1494
+
1495
+ // ../tracking-core/src/events/scroll-depth.ts
1496
+ var import_zod9 = require("zod");
1497
+ var scrollDepthMetadataSchema = import_zod9.z.object({
1498
+ depth_percent: import_zod9.z.number().int().min(1).max(100),
1499
+ page: import_zod9.z.object({
1500
+ path: import_zod9.z.string()
1479
1501
  }).strict()
1480
1502
  }).strict();
1481
- var scrollDepthConfigSchema = import_zod8.z.object({
1482
- thresholds: import_zod8.z.array(import_zod8.z.number().int().min(1).max(100)).min(1)
1503
+ var scrollDepthConfigSchema = import_zod9.z.object({
1504
+ thresholds: import_zod9.z.array(import_zod9.z.number().int().min(1).max(100)).min(1)
1483
1505
  }).strict();
1484
1506
 
1485
1507
  // ../tracking-core/src/events/specific-page-visit.ts
1486
- var import_zod9 = require("zod");
1508
+ var import_zod10 = require("zod");
1487
1509
  var SPECIFIC_PAGE_NAMES = [
1488
1510
  "contact_page",
1489
1511
  "about_page",
@@ -1494,18 +1516,18 @@ var SPECIFIC_PAGE_NAMES = [
1494
1516
  "faq_page",
1495
1517
  "testimonials_page"
1496
1518
  ];
1497
- var specificPageNameSchema = import_zod9.z.enum(SPECIFIC_PAGE_NAMES);
1498
- var specificPageVisitMetadataSchema = import_zod9.z.object({
1519
+ var specificPageNameSchema = import_zod10.z.enum(SPECIFIC_PAGE_NAMES);
1520
+ var specificPageVisitMetadataSchema = import_zod10.z.object({
1499
1521
  page_name: specificPageNameSchema,
1500
- page: import_zod9.z.object({
1501
- path: import_zod9.z.string()
1522
+ page: import_zod10.z.object({
1523
+ path: import_zod10.z.string()
1502
1524
  }).strict()
1503
1525
  }).strict();
1504
- var specificPageVisitConfigSchema = import_zod9.z.object({
1505
- pages: import_zod9.z.array(
1506
- import_zod9.z.object({
1526
+ var specificPageVisitConfigSchema = import_zod10.z.object({
1527
+ pages: import_zod10.z.array(
1528
+ import_zod10.z.object({
1507
1529
  name: specificPageNameSchema,
1508
- pathPattern: import_zod9.z.custom((value) => value instanceof RegExp, {
1530
+ pathPattern: import_zod10.z.custom((value) => value instanceof RegExp, {
1509
1531
  message: "pathPattern must be a RegExp"
1510
1532
  })
1511
1533
  }).strict()
@@ -1513,15 +1535,15 @@ var specificPageVisitConfigSchema = import_zod9.z.object({
1513
1535
  }).strict();
1514
1536
 
1515
1537
  // ../tracking-core/src/events/time-on-site.ts
1516
- var import_zod10 = require("zod");
1517
- var timeOnSiteMetadataSchema = import_zod10.z.object({
1518
- duration_ms: import_zod10.z.number().int().nonnegative(),
1519
- page: import_zod10.z.object({
1520
- path: import_zod10.z.string()
1538
+ var import_zod11 = require("zod");
1539
+ var timeOnSiteMetadataSchema = import_zod11.z.object({
1540
+ duration_ms: import_zod11.z.number().int().nonnegative(),
1541
+ page: import_zod11.z.object({
1542
+ path: import_zod11.z.string()
1521
1543
  }).strict()
1522
1544
  }).strict();
1523
- var timeOnSiteConfigSchema = import_zod10.z.object({
1524
- thresholdSeconds: import_zod10.z.number().int().positive()
1545
+ var timeOnSiteConfigSchema = import_zod11.z.object({
1546
+ thresholdSeconds: import_zod11.z.number().int().positive()
1525
1547
  }).strict();
1526
1548
 
1527
1549
  // ../tracking-core/src/events/registry.ts
@@ -1563,6 +1585,11 @@ var EVENT_REGISTRY = {
1563
1585
  metadataSchema: sdkHeartbeatMetadataSchema,
1564
1586
  configSchema: sdkHeartbeatConfigSchema
1565
1587
  },
1588
+ page_exit: {
1589
+ kind: "automatic",
1590
+ metadataSchema: pageExitMetadataSchema,
1591
+ configSchema: pageExitConfigSchema
1592
+ },
1566
1593
  // --- manual triggers ---
1567
1594
  form_submit: {
1568
1595
  kind: "manual",
@@ -1941,6 +1968,163 @@ function attachFormStart(client, config) {
1941
1968
  };
1942
1969
  }
1943
1970
 
1971
+ // ../tracking-core/src/triggers/page-exit.ts
1972
+ function attachPageExit(client) {
1973
+ if (typeof window === "undefined" || typeof document === "undefined") {
1974
+ return () => {
1975
+ };
1976
+ }
1977
+ let currentPath2 = window.location.pathname;
1978
+ let activeSince = document.visibilityState === "visible" ? Date.now() : null;
1979
+ let accumulatedMs = 0;
1980
+ let maxScrollPercent = null;
1981
+ let rafId = null;
1982
+ function getScrollPercent() {
1983
+ const doc = document.documentElement;
1984
+ const scrollTop = window.scrollY || doc.scrollTop;
1985
+ const scrollHeight = doc.scrollHeight;
1986
+ const clientHeight = doc.clientHeight;
1987
+ if (scrollHeight <= clientHeight) return 100;
1988
+ return Math.round((scrollTop + clientHeight) / scrollHeight * 100);
1989
+ }
1990
+ function onScroll() {
1991
+ if (rafId !== null) return;
1992
+ rafId = requestAnimationFrame(() => {
1993
+ rafId = null;
1994
+ const percent = getScrollPercent();
1995
+ if (percent >= 1 && (maxScrollPercent === null || percent > maxScrollPercent)) {
1996
+ maxScrollPercent = Math.min(percent, 100);
1997
+ }
1998
+ });
1999
+ }
2000
+ function settledDwellMs() {
2001
+ let total = accumulatedMs;
2002
+ if (activeSince !== null) {
2003
+ total += Date.now() - activeSince;
2004
+ }
2005
+ return Math.max(0, Math.round(total));
2006
+ }
2007
+ function emitSegment(path, flush) {
2008
+ const dwell = settledDwellMs();
2009
+ if (dwell === 0) return;
2010
+ client.trackEvent({
2011
+ eventType: "page_exit",
2012
+ metadata: {
2013
+ dwell_ms: dwell,
2014
+ max_scroll_percent: maxScrollPercent,
2015
+ page: { path }
2016
+ },
2017
+ pageUrl: window.location.href,
2018
+ occurredAt: null
2019
+ });
2020
+ if (flush) {
2021
+ client.flushBeacon();
2022
+ }
2023
+ accumulatedMs = 0;
2024
+ activeSince = document.visibilityState === "visible" ? Date.now() : null;
2025
+ }
2026
+ function onNavigate() {
2027
+ const newPath = window.location.pathname;
2028
+ if (newPath === currentPath2) return;
2029
+ emitSegment(currentPath2, false);
2030
+ currentPath2 = newPath;
2031
+ maxScrollPercent = null;
2032
+ accumulatedMs = 0;
2033
+ activeSince = document.visibilityState === "visible" ? Date.now() : null;
2034
+ }
2035
+ function onVisibilityChange() {
2036
+ if (document.visibilityState === "hidden") {
2037
+ emitSegment(currentPath2, true);
2038
+ } else {
2039
+ activeSince = Date.now();
2040
+ }
2041
+ }
2042
+ function onPageHide() {
2043
+ emitSegment(currentPath2, true);
2044
+ }
2045
+ const originalPushState = history.pushState.bind(history);
2046
+ const originalReplaceState = history.replaceState.bind(history);
2047
+ function patchedPushState(...args) {
2048
+ originalPushState(...args);
2049
+ setTimeout(onNavigate, 0);
2050
+ }
2051
+ function patchedReplaceState(...args) {
2052
+ originalReplaceState(...args);
2053
+ setTimeout(onNavigate, 0);
2054
+ }
2055
+ history.pushState = patchedPushState;
2056
+ history.replaceState = patchedReplaceState;
2057
+ window.addEventListener("popstate", onNavigate);
2058
+ window.addEventListener("scroll", onScroll, { passive: true });
2059
+ document.addEventListener("visibilitychange", onVisibilityChange);
2060
+ window.addEventListener("pagehide", onPageHide);
2061
+ return () => {
2062
+ if (rafId !== null) cancelAnimationFrame(rafId);
2063
+ history.pushState = originalPushState;
2064
+ history.replaceState = originalReplaceState;
2065
+ window.removeEventListener("popstate", onNavigate);
2066
+ window.removeEventListener("scroll", onScroll);
2067
+ document.removeEventListener("visibilitychange", onVisibilityChange);
2068
+ window.removeEventListener("pagehide", onPageHide);
2069
+ };
2070
+ }
2071
+
2072
+ // ../tracking-core/src/triggers/cta-click-capture.ts
2073
+ var DEFAULT_CTA_SELECTOR = "[data-aranova-cta]";
2074
+ var CTA_NAME_MAX_LENGTH = 120;
2075
+ function describeElement(el) {
2076
+ const tag = el.tagName.toLowerCase();
2077
+ return el.id ? `${tag}#${el.id}` : tag;
2078
+ }
2079
+ function resolveCtaName(el) {
2080
+ const explicit = el.getAttribute("data-aranova-cta");
2081
+ if (explicit && explicit.trim().length > 0) return explicit.trim();
2082
+ const text = (el.textContent ?? "").trim().replace(/\s+/g, " ");
2083
+ if (text.length > 0) return text.slice(0, CTA_NAME_MAX_LENGTH);
2084
+ return describeElement(el);
2085
+ }
2086
+ function attachCtaClickCapture(client, config) {
2087
+ if (typeof window === "undefined" || typeof document === "undefined") {
2088
+ return () => {
2089
+ };
2090
+ }
2091
+ const autoCapture = config.autoCapture;
2092
+ if (!autoCapture) {
2093
+ return () => {
2094
+ };
2095
+ }
2096
+ const selector = autoCapture.selector ?? DEFAULT_CTA_SELECTOR;
2097
+ function onClick(event) {
2098
+ const target = event.target;
2099
+ if (!(target instanceof Element)) return;
2100
+ let matched = null;
2101
+ try {
2102
+ matched = target.closest(selector);
2103
+ } catch {
2104
+ return;
2105
+ }
2106
+ if (matched === null) return;
2107
+ const href = matched instanceof HTMLAnchorElement ? matched.href || null : matched.getAttribute("href");
2108
+ client.trackEvent({
2109
+ eventType: "cta_click",
2110
+ metadata: {
2111
+ cta_name: resolveCtaName(matched),
2112
+ page: { path: window.location.pathname },
2113
+ section: matched.getAttribute("data-aranova-section"),
2114
+ destination_url: href,
2115
+ href,
2116
+ element: describeElement(matched)
2117
+ },
2118
+ pageUrl: window.location.href,
2119
+ occurredAt: null
2120
+ });
2121
+ }
2122
+ document.addEventListener("click", onClick, true);
2123
+ return () => {
2124
+ document.removeEventListener("click", onClick, true);
2125
+ };
2126
+ }
2127
+
1944
2128
  // ../tracking-core/src/resources/sales/errors.ts
1945
2129
  var AranovaApiError = class extends Error {
1946
2130
  constructor(message, options) {
@@ -2147,42 +2331,42 @@ function createSalesClient(config) {
2147
2331
  }
2148
2332
 
2149
2333
  // ../tracking-core/src/resources/sales/schema.ts
2150
- var import_zod11 = require("zod");
2334
+ var import_zod12 = require("zod");
2151
2335
  var SUPPORTED_CURRENCIES = ["USD", "CAD"];
2152
2336
  var TRACKING_ENVIRONMENTS = ["production", "development"];
2153
- var currencySchema = import_zod11.z.enum(SUPPORTED_CURRENCIES);
2154
- var centsSchema = import_zod11.z.number().int().nonnegative();
2155
- var quantitySchema = import_zod11.z.string().regex(/^\d+(\.\d{1,3})?$/);
2156
- var metadataSchema = import_zod11.z.record(import_zod11.z.unknown());
2157
- var saleItemSchema = import_zod11.z.object({
2158
- external_item_id: import_zod11.z.string().nullable().optional(),
2159
- name: import_zod11.z.string().nullable().optional(),
2160
- category: import_zod11.z.string().nullable().optional(),
2337
+ var currencySchema = import_zod12.z.enum(SUPPORTED_CURRENCIES);
2338
+ var centsSchema = import_zod12.z.number().int().nonnegative();
2339
+ var quantitySchema = import_zod12.z.string().regex(/^\d+(\.\d{1,3})?$/);
2340
+ var metadataSchema = import_zod12.z.record(import_zod12.z.unknown());
2341
+ var saleItemSchema = import_zod12.z.object({
2342
+ external_item_id: import_zod12.z.string().nullable().optional(),
2343
+ name: import_zod12.z.string().nullable().optional(),
2344
+ category: import_zod12.z.string().nullable().optional(),
2161
2345
  quantity: quantitySchema,
2162
2346
  unit_price_cents: centsSchema,
2163
2347
  // Non-negativity validated on the wire — same contract as the other cents
2164
2348
  // fields — and backstopped by the DB CHECK.
2165
2349
  unit_cost_cents: centsSchema.nullable().optional()
2166
2350
  }).strict();
2167
- var saleServiceSchema = import_zod11.z.object({
2168
- service: import_zod11.z.string(),
2351
+ var saleServiceSchema = import_zod12.z.object({
2352
+ service: import_zod12.z.string(),
2169
2353
  amount_cents: centsSchema
2170
2354
  }).strict();
2171
- var customerNameSchema = import_zod11.z.string().max(200);
2172
- var customerPhoneSchema = import_zod11.z.string().max(64);
2173
- var customerEmailSchema = import_zod11.z.string().max(320).email();
2355
+ var customerNameSchema = import_zod12.z.string().max(200);
2356
+ var customerPhoneSchema = import_zod12.z.string().max(64);
2357
+ var customerEmailSchema = import_zod12.z.string().max(320).email();
2174
2358
  function refineServiceXor(val, ctx, { requireAmount }) {
2175
2359
  if (val.services != null) {
2176
2360
  if (val.service != null) {
2177
2361
  ctx.addIssue({
2178
- code: import_zod11.z.ZodIssueCode.custom,
2362
+ code: import_zod12.z.ZodIssueCode.custom,
2179
2363
  message: "pass either `service` or `services`, not both",
2180
2364
  path: ["services"]
2181
2365
  });
2182
2366
  }
2183
2367
  if (val.services.length === 0) {
2184
2368
  ctx.addIssue({
2185
- code: import_zod11.z.ZodIssueCode.custom,
2369
+ code: import_zod12.z.ZodIssueCode.custom,
2186
2370
  message: "`services` must not be empty",
2187
2371
  path: ["services"]
2188
2372
  });
@@ -2190,7 +2374,7 @@ function refineServiceXor(val, ctx, { requireAmount }) {
2190
2374
  const keys = val.services.map((s) => s.service);
2191
2375
  if (new Set(keys).size !== keys.length) {
2192
2376
  ctx.addIssue({
2193
- code: import_zod11.z.ZodIssueCode.custom,
2377
+ code: import_zod12.z.ZodIssueCode.custom,
2194
2378
  message: "`services` must not list the same service more than once",
2195
2379
  path: ["services"]
2196
2380
  });
@@ -2199,7 +2383,7 @@ function refineServiceXor(val, ctx, { requireAmount }) {
2199
2383
  const sum = val.services.reduce((acc, s) => acc + s.amount_cents, 0);
2200
2384
  if (val.amount_total_cents !== sum) {
2201
2385
  ctx.addIssue({
2202
- code: import_zod11.z.ZodIssueCode.custom,
2386
+ code: import_zod12.z.ZodIssueCode.custom,
2203
2387
  message: "amount_total_cents must equal the sum of the services amounts (omit it to derive it automatically)",
2204
2388
  path: ["amount_total_cents"]
2205
2389
  });
@@ -2207,37 +2391,37 @@ function refineServiceXor(val, ctx, { requireAmount }) {
2207
2391
  }
2208
2392
  } else if (requireAmount && val.amount_total_cents == null) {
2209
2393
  ctx.addIssue({
2210
- code: import_zod11.z.ZodIssueCode.custom,
2394
+ code: import_zod12.z.ZodIssueCode.custom,
2211
2395
  message: "amount_total_cents is required unless `services` is provided",
2212
2396
  path: ["amount_total_cents"]
2213
2397
  });
2214
2398
  }
2215
2399
  }
2216
- var saleCreateSchema = import_zod11.z.object({
2217
- external_id: import_zod11.z.string().nullable().optional(),
2218
- description: import_zod11.z.string().nullable().optional(),
2219
- service: import_zod11.z.string().nullable().optional(),
2220
- services: import_zod11.z.array(saleServiceSchema).nullable().optional(),
2400
+ var saleCreateSchema = import_zod12.z.object({
2401
+ external_id: import_zod12.z.string().nullable().optional(),
2402
+ description: import_zod12.z.string().nullable().optional(),
2403
+ service: import_zod12.z.string().nullable().optional(),
2404
+ services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
2221
2405
  currency: currencySchema,
2222
2406
  // Optional only because the plural `services` form derives it from the sum
2223
2407
  // (see refineServiceXor); the singular/serviceless path still requires it.
2224
2408
  amount_total_cents: centsSchema.nullable().optional(),
2225
- occurred_at: import_zod11.z.string().datetime(),
2226
- environment: import_zod11.z.enum(TRACKING_ENVIRONMENTS).default("production"),
2227
- items: import_zod11.z.array(saleItemSchema).default([]),
2409
+ occurred_at: import_zod12.z.string().datetime(),
2410
+ environment: import_zod12.z.enum(TRACKING_ENVIRONMENTS).default("production"),
2411
+ items: import_zod12.z.array(saleItemSchema).default([]),
2228
2412
  metadata: metadataSchema.nullable().optional(),
2229
2413
  customer_name: customerNameSchema.nullable().optional(),
2230
2414
  customer_phone: customerPhoneSchema.nullable().optional(),
2231
2415
  customer_email: customerEmailSchema.nullable().optional()
2232
2416
  }).strict().superRefine((val, ctx) => refineServiceXor(val, ctx, { requireAmount: true }));
2233
- var saleUpdateSchema = import_zod11.z.object({
2234
- description: import_zod11.z.string().nullable().optional(),
2235
- service: import_zod11.z.string().nullable().optional(),
2236
- services: import_zod11.z.array(saleServiceSchema).nullable().optional(),
2417
+ var saleUpdateSchema = import_zod12.z.object({
2418
+ description: import_zod12.z.string().nullable().optional(),
2419
+ service: import_zod12.z.string().nullable().optional(),
2420
+ services: import_zod12.z.array(saleServiceSchema).nullable().optional(),
2237
2421
  currency: currencySchema.optional(),
2238
2422
  amount_total_cents: centsSchema.optional(),
2239
- occurred_at: import_zod11.z.string().datetime().optional(),
2240
- items: import_zod11.z.array(saleItemSchema).optional(),
2423
+ occurred_at: import_zod12.z.string().datetime().optional(),
2424
+ items: import_zod12.z.array(saleItemSchema).optional(),
2241
2425
  metadata: metadataSchema.nullable().optional(),
2242
2426
  customer_name: customerNameSchema.nullable().optional(),
2243
2427
  customer_phone: customerPhoneSchema.nullable().optional(),
@@ -2625,7 +2809,7 @@ function GoogleAdsTracking(props) {
2625
2809
  var import_react6 = require("react");
2626
2810
 
2627
2811
  // package.json
2628
- var version = "0.15.0";
2812
+ var version = "0.16.1";
2629
2813
 
2630
2814
  // ../tracking-core/src/phone-react.tsx
2631
2815
  var import_react5 = require("react");
@@ -2806,6 +2990,7 @@ function createTracking(options) {
2806
2990
  const detectorClient = conversionStore ? withConversionAutoFire(rawClient, createConversionAutoFire(conversionStore)) : rawClient;
2807
2991
  detachers.push(attachAutoPageView(detectorClient));
2808
2992
  detachers.push(attachBfcacheRestore(detectorClient));
2993
+ detachers.push(attachPageExit(detectorClient));
2809
2994
  const timeOnSite = triggers.automatic.time_on_site;
2810
2995
  if (timeOnSite) {
2811
2996
  detachers.push(attachTimeOnSite(detectorClient, timeOnSite));
@@ -2826,6 +3011,10 @@ function createTracking(options) {
2826
3011
  if (formStart) {
2827
3012
  detachers.push(attachFormStart(detectorClient, formStart));
2828
3013
  }
3014
+ const ctaClick = triggers.manual?.cta_click;
3015
+ if (ctaClick) {
3016
+ detachers.push(attachCtaClickCapture(detectorClient, ctaClick));
3017
+ }
2829
3018
  return () => {
2830
3019
  for (let i = detachers.length - 1; i >= 0; i--) {
2831
3020
  detachers[i]();