@aranova/tracking-next 0.14.2 → 0.15.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.mjs CHANGED
@@ -1,13 +1,30 @@
1
1
  // ../tracking-core/src/consent.ts
2
2
  var CONSENT_STATE_KEY = "consent_state";
3
3
  var CONSENT_TIMESTAMP_KEY = "consent_timestamp";
4
- var grantedListeners = /* @__PURE__ */ new Set();
5
- function onConsentGranted(listener) {
6
- grantedListeners.add(listener);
4
+ var CONSENT_EXPIRES_AT_KEY = "consent_expires_at";
5
+ var DEFAULT_DECLINE_TTL_DAYS = 90;
6
+ var DAY_MS = 864e5;
7
+ var DEFAULT_CHOICE = {
8
+ state: "granted",
9
+ source: "default",
10
+ updatedAt: null,
11
+ expiresAt: null
12
+ };
13
+ var changeListeners = /* @__PURE__ */ new Set();
14
+ function onConsentChange(listener) {
15
+ changeListeners.add(listener);
7
16
  return () => {
8
- grantedListeners.delete(listener);
17
+ changeListeners.delete(listener);
9
18
  };
10
19
  }
20
+ function notifyConsentChanged(choice) {
21
+ for (const listener of changeListeners) {
22
+ try {
23
+ listener(choice);
24
+ } catch {
25
+ }
26
+ }
27
+ }
11
28
  function buildConsentPayload(state) {
12
29
  return {
13
30
  ad_storage: state,
@@ -16,42 +33,88 @@ function buildConsentPayload(state) {
16
33
  analytics_storage: state
17
34
  };
18
35
  }
19
- function getConsentState() {
20
- if (typeof window === "undefined") return "pending";
36
+ function getConsentChoice() {
37
+ if (typeof window === "undefined") return DEFAULT_CHOICE;
21
38
  try {
22
- const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
23
- if (storedState === "granted" || storedState === "denied") return storedState;
39
+ const stored = window.localStorage.getItem(CONSENT_STATE_KEY);
40
+ const updatedAt = window.localStorage.getItem(CONSENT_TIMESTAMP_KEY);
41
+ if (stored === "granted")
42
+ return { state: "granted", source: "explicit", updatedAt, expiresAt: null };
43
+ if (stored === "denied") {
44
+ let expiresAt = window.localStorage.getItem(CONSENT_EXPIRES_AT_KEY);
45
+ if (!expiresAt) {
46
+ expiresAt = new Date(Date.now() + DEFAULT_DECLINE_TTL_DAYS * DAY_MS).toISOString();
47
+ try {
48
+ window.localStorage.setItem(CONSENT_EXPIRES_AT_KEY, expiresAt);
49
+ } catch {
50
+ }
51
+ }
52
+ if (!(Date.parse(expiresAt) <= Date.now()))
53
+ return { state: "denied", source: "explicit", updatedAt, expiresAt };
54
+ }
24
55
  } catch {
25
56
  }
26
- return "pending";
57
+ return DEFAULT_CHOICE;
58
+ }
59
+ function getConsentState() {
60
+ return getConsentChoice().state;
27
61
  }
28
- function setConsentState(state) {
62
+ function pushConsentToPlatforms(state) {
29
63
  if (typeof window === "undefined") return;
30
- try {
31
- window.localStorage.setItem(CONSENT_STATE_KEY, state);
32
- window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, (/* @__PURE__ */ new Date()).toISOString());
33
- } catch {
34
- }
35
64
  if (typeof window.gtag === "function")
36
65
  window.gtag("consent", "update", buildConsentPayload(state));
37
66
  if (typeof window.fbq === "function")
38
67
  window.fbq("consent", state === "granted" ? "grant" : "revoke");
39
- if (state === "granted") {
40
- for (const listener of grantedListeners) {
41
- try {
42
- listener();
43
- } catch {
44
- }
68
+ }
69
+ function setConsentState(state, options) {
70
+ if (typeof window === "undefined") return;
71
+ const requestedTtl = options?.declineTtlDays;
72
+ const ttlDays = typeof requestedTtl === "number" && Number.isFinite(requestedTtl) && requestedTtl > 0 ? requestedTtl : DEFAULT_DECLINE_TTL_DAYS;
73
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
74
+ const expiresAt = state === "denied" ? new Date(Date.now() + ttlDays * DAY_MS).toISOString() : null;
75
+ try {
76
+ window.localStorage.setItem(CONSENT_STATE_KEY, state);
77
+ window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, updatedAt);
78
+ if (expiresAt !== null) {
79
+ window.localStorage.setItem(CONSENT_EXPIRES_AT_KEY, expiresAt);
80
+ } else {
81
+ window.localStorage.removeItem(CONSENT_EXPIRES_AT_KEY);
45
82
  }
83
+ } catch {
46
84
  }
85
+ pushConsentToPlatforms(state);
86
+ notifyConsentChanged({ state, source: "explicit", updatedAt, expiresAt });
87
+ }
88
+ function optIn() {
89
+ setConsentState("granted");
90
+ }
91
+ function optOut(options) {
92
+ setConsentState("denied", options);
47
93
  }
48
94
  function resetConsent() {
49
95
  if (typeof window === "undefined") return;
50
96
  try {
51
97
  window.localStorage.removeItem(CONSENT_STATE_KEY);
52
98
  window.localStorage.removeItem(CONSENT_TIMESTAMP_KEY);
99
+ window.localStorage.removeItem(CONSENT_EXPIRES_AT_KEY);
53
100
  } catch {
54
101
  }
102
+ pushConsentToPlatforms("granted");
103
+ notifyConsentChanged(DEFAULT_CHOICE);
104
+ }
105
+ function createEffectiveConsentSnippet() {
106
+ return `
107
+ var aranovaConsent = (function () {
108
+ try {
109
+ if (window.localStorage.getItem('${CONSENT_STATE_KEY}') === 'denied') {
110
+ var exp = window.localStorage.getItem('${CONSENT_EXPIRES_AT_KEY}');
111
+ var expMs = exp ? Date.parse(exp) : NaN;
112
+ if (!(expMs <= Date.now())) return 'denied';
113
+ }
114
+ } catch (e) {}
115
+ return 'granted';
116
+ })();
117
+ `.trim();
55
118
  }
56
119
 
57
120
  // ../tracking-core/src/tracking.ts
@@ -166,12 +229,12 @@ function createConsentDefaultScript() {
166
229
  window.dataLayer = window.dataLayer || [];
167
230
  function gtag(){dataLayer.push(arguments);}
168
231
  window.gtag = gtag;
232
+ ${createEffectiveConsentSnippet()}
169
233
  gtag('consent', 'default', {
170
- ad_storage: 'denied',
171
- ad_user_data: 'denied',
172
- ad_personalization: 'denied',
173
- analytics_storage: 'denied',
174
- wait_for_update: 500,
234
+ ad_storage: aranovaConsent,
235
+ ad_user_data: aranovaConsent,
236
+ ad_personalization: aranovaConsent,
237
+ analytics_storage: aranovaConsent
175
238
  });
176
239
  `.trim();
177
240
  }
@@ -185,21 +248,6 @@ gtag('js', new Date());
185
248
  gtag('config', '${gtagId}');
186
249
  `.trim();
187
250
  }
188
- function createConsentRestoreScript() {
189
- return `
190
- try {
191
- var consentState = window.localStorage.getItem('consent_state');
192
- if (window.gtag && (consentState === 'granted' || consentState === 'denied')) {
193
- window.gtag('consent', 'update', {
194
- ad_storage: consentState,
195
- ad_user_data: consentState,
196
- ad_personalization: consentState,
197
- analytics_storage: consentState,
198
- });
199
- }
200
- } catch (e) {}
201
- `.trim();
202
- }
203
251
  var SEND_TO_RE = /^AW-[A-Za-z0-9]+\/[A-Za-z0-9_-]+$/;
204
252
  function isValidSendTo(sendTo) {
205
253
  return SEND_TO_RE.test(sendTo);
@@ -278,7 +326,8 @@ document,'script','${FB_EVENTS_SCRIPT_HOST}');`.trim();
278
326
  }
279
327
  function createMetaConsentDefaultScript() {
280
328
  return `${fbqStubScript()}
281
- fbq('consent', 'revoke');`;
329
+ ${createEffectiveConsentSnippet()}
330
+ fbq('consent', aranovaConsent === 'denied' ? 'revoke' : 'grant');`;
282
331
  }
283
332
  function createMetaPixelInitScript(pixelId) {
284
333
  if (!isValidMetaPixelId(pixelId)) return "";
@@ -291,14 +340,71 @@ function createMetaPixelInitScriptMulti(pixelIds) {
291
340
  return `${inits.join("\n")}
292
341
  fbq('track', 'PageView');`;
293
342
  }
294
- function createMetaConsentRestoreScript() {
295
- return `
296
- try {
297
- var consentState = window.localStorage.getItem('consent_state');
298
- if (window.fbq && (consentState === 'granted' || consentState === 'denied')) {
299
- window.fbq('consent', consentState === 'granted' ? 'grant' : 'revoke');
343
+
344
+ // ../tracking-core/src/landing.ts
345
+ var LANDING_STORAGE_KEY = "_aranova_track_landing";
346
+ var memoryRecord = null;
347
+ function sanitizeParams(value) {
348
+ if (typeof value !== "object" || value === null) return {};
349
+ const source = value;
350
+ return TRACKING_PARAM_KEYS.reduce((params, key) => {
351
+ const entry = source[key];
352
+ if (typeof entry === "string" && entry.length > 0) params[key] = entry;
353
+ return params;
354
+ }, {});
355
+ }
356
+ function readStoredRecord() {
357
+ try {
358
+ const raw = window.localStorage.getItem(LANDING_STORAGE_KEY);
359
+ if (!raw) return null;
360
+ const parsed = JSON.parse(raw);
361
+ if (typeof parsed.session_id !== "string" || parsed.session_id.length === 0) return null;
362
+ return { session_id: parsed.session_id, params: sanitizeParams(parsed.params) };
363
+ } catch {
364
+ return null;
365
+ }
366
+ }
367
+ function writeRecord(record) {
368
+ memoryRecord = record;
369
+ try {
370
+ window.localStorage.setItem(LANDING_STORAGE_KEY, JSON.stringify(record));
371
+ } catch {
372
+ }
373
+ }
374
+ function captureFromUrl(url) {
375
+ try {
376
+ const resolved = new URL(url ?? window.location.href, window.location.origin);
377
+ return getTrackingQueryValues(resolved.searchParams);
378
+ } catch {
379
+ return {};
380
+ }
381
+ }
382
+ function getOrCaptureLandingParams(sessionId, url) {
383
+ if (typeof window === "undefined") return {};
384
+ const stored = readStoredRecord();
385
+ if (stored && stored.session_id === sessionId) {
386
+ memoryRecord = stored;
387
+ return stored.params;
388
+ }
389
+ if (memoryRecord && memoryRecord.session_id === sessionId) {
390
+ return memoryRecord.params;
300
391
  }
301
- } catch (e) {}`.trim();
392
+ const record = { session_id: sessionId, params: captureFromUrl(url) };
393
+ writeRecord(record);
394
+ return record.params;
395
+ }
396
+ function buildLandingPayloadFields(sessionId, override) {
397
+ const params = override ?? (typeof window === "undefined" ? null : getOrCaptureLandingParams(sessionId));
398
+ if (params === null) return {};
399
+ return {
400
+ landing_gclid: params.gclid ?? null,
401
+ landing_fbclid: params.fbclid ?? null,
402
+ landing_utm_source: params.utm_source ?? null,
403
+ landing_utm_medium: params.utm_medium ?? null,
404
+ landing_utm_campaign: params.utm_campaign ?? null,
405
+ landing_utm_term: params.utm_term ?? null,
406
+ landing_utm_content: params.utm_content ?? null
407
+ };
302
408
  }
303
409
 
304
410
  // ../tracking-core/src/payloads.ts
@@ -327,6 +433,9 @@ function createTrackingSessionUpsertPayload(trackingParams, input, context) {
327
433
  utm_campaign: trackingParams.utm_campaign,
328
434
  utm_term: trackingParams.utm_term,
329
435
  utm_content: trackingParams.utm_content,
436
+ // Omitted entirely when the landing isn't observable (SSR, no override) —
437
+ // present-as-null would wrongly tell the backend "landed with no params".
438
+ ...buildLandingPayloadFields(input.sessionId, input.landingParams),
330
439
  first_page: input.firstPage ?? null,
331
440
  consent_state: input.consentState ?? null,
332
441
  context
@@ -348,8 +457,6 @@ function createTrackingEventCreatePayload(trackingParams, input, context) {
348
457
 
349
458
  // ../tracking-core/src/resources/conversion-firing.ts
350
459
  var DEDUP_PREFIX = "_aranova_conv_";
351
- var MAX_PENDING = 100;
352
- var pendingQueue = [];
353
460
  function dedupKey(input) {
354
461
  return `${DEDUP_PREFIX}${input.transactionId ?? ""}:${input.sendTo}`;
355
462
  }
@@ -373,23 +480,9 @@ function fireOnce(input) {
373
480
  if (fireGtagConversion(input)) markFired(input);
374
481
  }
375
482
  function fireConversionWithConsent(input) {
376
- const state = getConsentState();
377
- if (state === "denied") return;
378
- if (state === "pending") {
379
- if (pendingQueue.length >= MAX_PENDING) pendingQueue.shift();
380
- pendingQueue.push(input);
381
- return;
382
- }
483
+ if (getConsentState() === "denied") return;
383
484
  fireOnce(input);
384
485
  }
385
- function flushPendingConversions() {
386
- if (getConsentState() !== "granted") return;
387
- while (pendingQueue.length > 0) {
388
- const input = pendingQueue.shift();
389
- if (input) fireOnce(input);
390
- }
391
- }
392
- if (typeof window !== "undefined") onConsentGranted(flushPendingConversions);
393
486
 
394
487
  // ../tracking-core/src/resources/conversion-config.ts
395
488
  function isStringMap(value) {
@@ -897,7 +990,13 @@ function readTrackingParams() {
897
990
  }
898
991
  function consentSnapshot() {
899
992
  try {
900
- return { state: getConsentState() };
993
+ const choice = getConsentChoice();
994
+ return {
995
+ state: choice.state,
996
+ source: choice.source,
997
+ updated_at: choice.updatedAt,
998
+ expires_at: choice.expiresAt
999
+ };
901
1000
  } catch {
902
1001
  return null;
903
1002
  }
@@ -970,6 +1069,7 @@ function createTrackingClient(config) {
970
1069
  initialParams = captureTrackingParamsFromLocation();
971
1070
  } catch {
972
1071
  }
1072
+ getOrCaptureLandingParams(sessionId);
973
1073
  try {
974
1074
  captureFbc();
975
1075
  } catch {
@@ -1019,6 +1119,11 @@ function createTrackingClient(config) {
1019
1119
  utm_campaign: params.utm_campaign,
1020
1120
  utm_term: params.utm_term,
1021
1121
  utm_content: params.utm_content,
1122
+ // Landing params for the CURRENT session id — captured on the spot when
1123
+ // the session just rotated (the current URL is the rotated session's
1124
+ // landing), reused from the stored record otherwise. Keys are omitted
1125
+ // entirely when the landing isn't observable (SSR).
1126
+ ...buildLandingPayloadFields(sessionId),
1022
1127
  first_page: firstPage,
1023
1128
  consent_state: consentSnapshot(),
1024
1129
  context
@@ -1921,36 +2026,68 @@ function useTrackingParams() {
1921
2026
  }, []);
1922
2027
  return trackingParams;
1923
2028
  }
1924
- function useConsentState() {
1925
- return useConsent().state;
1926
- }
1927
- function useConsent() {
1928
- const [state, setState] = useState("pending");
2029
+ var DEFAULT_CHOICE2 = {
2030
+ state: "granted",
2031
+ source: "default",
2032
+ updatedAt: null,
2033
+ expiresAt: null
2034
+ };
2035
+ function useCookiePreferences(options) {
2036
+ const [choice, setChoice] = useState(DEFAULT_CHOICE2);
2037
+ const ttlDays = options?.declineTtlDays;
1929
2038
  useEffect(() => {
1930
- setState(getConsentState());
2039
+ const sync = () => setChoice(getConsentChoice());
2040
+ sync();
1931
2041
  const handleStorage = (event) => {
1932
- if (event.key === CONSENT_STATE_KEY) setState(getConsentState());
2042
+ if (event.key === null || event.key === CONSENT_STATE_KEY || event.key === CONSENT_EXPIRES_AT_KEY || event.key === CONSENT_TIMESTAMP_KEY)
2043
+ sync();
1933
2044
  };
1934
2045
  window.addEventListener("storage", handleStorage);
1935
- return () => window.removeEventListener("storage", handleStorage);
1936
- }, []);
1937
- const accept = useCallback(() => {
1938
- setConsentState("granted");
1939
- setState("granted");
2046
+ const unsubscribe = onConsentChange(sync);
2047
+ return () => {
2048
+ window.removeEventListener("storage", handleStorage);
2049
+ unsubscribe();
2050
+ };
1940
2051
  }, []);
1941
- const decline = useCallback(() => {
1942
- setConsentState("denied");
1943
- setState("denied");
2052
+ const optOutAction = useCallback(() => {
2053
+ optOut(ttlDays != null ? { declineTtlDays: ttlDays } : void 0);
2054
+ }, [ttlDays]);
2055
+ const optInAction = useCallback(() => {
2056
+ optIn();
1944
2057
  }, []);
1945
2058
  const reset = useCallback(() => {
1946
2059
  resetConsent();
1947
- setState("pending");
1948
2060
  }, []);
2061
+ return {
2062
+ state: choice.state,
2063
+ source: choice.source,
2064
+ isDefault: choice.source === "default",
2065
+ isGranted: choice.state === "granted",
2066
+ isDenied: choice.state === "denied",
2067
+ updatedAt: choice.updatedAt,
2068
+ expiresAt: choice.expiresAt,
2069
+ optOut: optOutAction,
2070
+ optIn: optInAction,
2071
+ reset
2072
+ };
2073
+ }
2074
+ function useConsentState() {
2075
+ return useConsent().state;
2076
+ }
2077
+ function useConsent() {
2078
+ const {
2079
+ state,
2080
+ isGranted,
2081
+ isDenied,
2082
+ optIn: accept,
2083
+ optOut: decline,
2084
+ reset
2085
+ } = useCookiePreferences();
1949
2086
  return {
1950
2087
  state,
1951
- isPending: state === "pending",
1952
- isGranted: state === "granted",
1953
- isDenied: state === "denied",
2088
+ isPending: false,
2089
+ isGranted,
2090
+ isDenied,
1954
2091
  accept,
1955
2092
  decline,
1956
2093
  reset
@@ -2166,13 +2303,11 @@ function AdPlatformTracking({
2166
2303
  strategy: "afterInteractive"
2167
2304
  }
2168
2305
  ),
2169
- /* @__PURE__ */ jsx2(Script, { id: "gtag-init", strategy: "afterInteractive", children: gtagInitScript }),
2170
- /* @__PURE__ */ jsx2(Script, { id: "consent-restore", strategy: "afterInteractive", children: createConsentRestoreScript() })
2306
+ /* @__PURE__ */ jsx2(Script, { id: "gtag-init", strategy: "afterInteractive", children: gtagInitScript })
2171
2307
  ] }) : null,
2172
2308
  metaAll.length > 0 ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
2173
2309
  /* @__PURE__ */ jsx2(Script, { id: "meta-pixel-base", strategy: "afterInteractive", children: createMetaConsentDefaultScript() }),
2174
- /* @__PURE__ */ jsx2(Script, { id: "meta-pixel-init", strategy: "afterInteractive", children: metaInitScript }),
2175
- /* @__PURE__ */ jsx2(Script, { id: "meta-pixel-consent-restore", strategy: "afterInteractive", children: createMetaConsentRestoreScript() })
2310
+ /* @__PURE__ */ jsx2(Script, { id: "meta-pixel-init", strategy: "afterInteractive", children: metaInitScript })
2176
2311
  ] }) : null
2177
2312
  ] });
2178
2313
  }
@@ -2195,8 +2330,7 @@ function GoogleAdsTracking(props) {
2195
2330
  strategy: "afterInteractive"
2196
2331
  }
2197
2332
  ),
2198
- /* @__PURE__ */ jsx3(Script2, { id: "gtag-init", strategy: "afterInteractive", children: initScript }),
2199
- /* @__PURE__ */ jsx3(Script2, { id: "consent-restore", strategy: "afterInteractive", children: createConsentRestoreScript() })
2333
+ /* @__PURE__ */ jsx3(Script2, { id: "gtag-init", strategy: "afterInteractive", children: initScript })
2200
2334
  ] });
2201
2335
  }
2202
2336
 
@@ -2204,7 +2338,7 @@ function GoogleAdsTracking(props) {
2204
2338
  import { usePathname, useSearchParams } from "next/navigation";
2205
2339
 
2206
2340
  // package.json
2207
- var version = "0.14.2";
2341
+ var version = "0.15.0";
2208
2342
 
2209
2343
  // src/factory.tsx
2210
2344
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect3, useMemo as useMemo2, useRef } from "react";
@@ -2523,6 +2657,7 @@ export {
2523
2657
  AdPlatformTracking,
2524
2658
  AranovaApiError,
2525
2659
  ConsentBanner,
2660
+ DEFAULT_DECLINE_TTL_DAYS,
2526
2661
  DEFAULT_PHONE_COUNTRY,
2527
2662
  GoogleAdsTracking,
2528
2663
  NAMED_RANGES,
@@ -2542,7 +2677,11 @@ export {
2542
2677
  formatPhone,
2543
2678
  formatPhoneAsTyped,
2544
2679
  fromMinor,
2680
+ getConsentChoice,
2545
2681
  getConsentState,
2682
+ onConsentChange,
2683
+ optIn,
2684
+ optOut,
2546
2685
  parsePhone,
2547
2686
  phoneField,
2548
2687
  resetConsent,
@@ -2557,6 +2696,7 @@ export {
2557
2696
  toMinor,
2558
2697
  useConsent,
2559
2698
  useConsentState,
2699
+ useCookiePreferences,
2560
2700
  useGclid,
2561
2701
  usePhoneConfig,
2562
2702
  usePhoneField,