@aranova/tracking-react 0.2.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.
@@ -0,0 +1,107 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
4
+
5
+ interface GoogleAdsTrackingProps {
6
+ gtagId: string;
7
+ }
8
+ declare function GoogleAdsTracking({ gtagId }: GoogleAdsTrackingProps): null;
9
+
10
+ type ConsentState = 'granted' | 'denied' | 'pending';
11
+ interface TrackingParams {
12
+ gclid: string | null;
13
+ fbclid: string | null;
14
+ utm_source: string | null;
15
+ utm_medium: string | null;
16
+ utm_campaign: string | null;
17
+ utm_term: string | null;
18
+ utm_content: string | null;
19
+ }
20
+ type TrackingInstallSurface = 'next' | 'react' | 'script';
21
+ interface TrackingClientContext {
22
+ surface: TrackingInstallSurface;
23
+ sdk_version: string | null;
24
+ package_name: string | null;
25
+ site_origin: string | null;
26
+ page_title: string | null;
27
+ referrer: string | null;
28
+ }
29
+ interface TrackingSessionUpsertPayload {
30
+ session_id: string;
31
+ gclid: string | null;
32
+ fbclid: string | null;
33
+ utm_source: string | null;
34
+ utm_medium: string | null;
35
+ utm_campaign: string | null;
36
+ utm_term: string | null;
37
+ utm_content: string | null;
38
+ first_page: string | null;
39
+ consent_state: Record<string, unknown> | null;
40
+ context: TrackingClientContext;
41
+ }
42
+ interface TrackingEventCreatePayload {
43
+ session_id: string;
44
+ event_type: string;
45
+ gclid: string | null;
46
+ fbclid: string | null;
47
+ page_url: string | null;
48
+ metadata: Record<string, unknown> | null;
49
+ context: TrackingClientContext;
50
+ }
51
+ interface TrackingInitConfig {
52
+ apiKey?: string;
53
+ endpoint?: string;
54
+ gtagId?: string;
55
+ autoCaptureTrackingParams?: boolean;
56
+ renderConsentBanner?: boolean;
57
+ cookieMaxAgeSeconds?: number;
58
+ surface?: TrackingInstallSurface;
59
+ }
60
+ declare global {
61
+ interface Window {
62
+ gtag?: (...args: unknown[]) => void;
63
+ dataLayer?: unknown[];
64
+ AranovaTracking?: {
65
+ init: (config: TrackingInitConfig) => void;
66
+ captureTrackingParams: () => TrackingParams;
67
+ getTrackingParams: () => TrackingParams;
68
+ getConsentState: () => ConsentState;
69
+ setConsentState: (state: 'granted' | 'denied') => void;
70
+ };
71
+ }
72
+ }
73
+
74
+ type GtagConsentValue = 'granted' | 'denied';
75
+ declare function getConsentState(): ConsentState;
76
+ declare function setConsentState(state: GtagConsentValue): void;
77
+
78
+ interface TrackingContextInput {
79
+ packageName?: string | null;
80
+ pageTitle?: string | null;
81
+ referrer?: string | null;
82
+ sdkVersion?: string | null;
83
+ siteOrigin?: string | null;
84
+ }
85
+ interface TrackingEventInput {
86
+ eventType: string;
87
+ metadata?: Record<string, unknown> | null;
88
+ pageUrl?: string | null;
89
+ sessionId: string;
90
+ }
91
+ interface TrackingSessionInput {
92
+ consentState?: Record<string, unknown> | null;
93
+ firstPage?: string | null;
94
+ sessionId: string;
95
+ }
96
+ declare function createTrackingClientContext(surface: TrackingInstallSurface, input?: TrackingContextInput): TrackingClientContext;
97
+ declare function createTrackingSessionUpsertPayload(trackingParams: TrackingParams, input: TrackingSessionInput, context: TrackingClientContext): TrackingSessionUpsertPayload;
98
+ declare function createTrackingEventCreatePayload(trackingParams: TrackingParams, input: TrackingEventInput, context: TrackingClientContext): TrackingEventCreatePayload;
99
+
100
+ declare const TRACKING_PARAM_KEYS: readonly ["gclid", "fbclid", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
101
+ declare function captureTrackingParamsFromLocation(url?: string, maxAgeSeconds?: number): TrackingParams;
102
+
103
+ declare function useGclid(): string | null;
104
+ declare function useTrackingParams(): TrackingParams;
105
+ declare function useConsentState(): ConsentState;
106
+
107
+ export { ConsentBanner, type ConsentState, GoogleAdsTracking, type GoogleAdsTrackingProps, TRACKING_PARAM_KEYS, type TrackingClientContext, type TrackingEventCreatePayload, type TrackingInitConfig, type TrackingInstallSurface, type TrackingParams, type TrackingSessionUpsertPayload, captureTrackingParamsFromLocation, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, getConsentState, setConsentState, useConsentState, useGclid, useTrackingParams };
@@ -0,0 +1,107 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
4
+
5
+ interface GoogleAdsTrackingProps {
6
+ gtagId: string;
7
+ }
8
+ declare function GoogleAdsTracking({ gtagId }: GoogleAdsTrackingProps): null;
9
+
10
+ type ConsentState = 'granted' | 'denied' | 'pending';
11
+ interface TrackingParams {
12
+ gclid: string | null;
13
+ fbclid: string | null;
14
+ utm_source: string | null;
15
+ utm_medium: string | null;
16
+ utm_campaign: string | null;
17
+ utm_term: string | null;
18
+ utm_content: string | null;
19
+ }
20
+ type TrackingInstallSurface = 'next' | 'react' | 'script';
21
+ interface TrackingClientContext {
22
+ surface: TrackingInstallSurface;
23
+ sdk_version: string | null;
24
+ package_name: string | null;
25
+ site_origin: string | null;
26
+ page_title: string | null;
27
+ referrer: string | null;
28
+ }
29
+ interface TrackingSessionUpsertPayload {
30
+ session_id: string;
31
+ gclid: string | null;
32
+ fbclid: string | null;
33
+ utm_source: string | null;
34
+ utm_medium: string | null;
35
+ utm_campaign: string | null;
36
+ utm_term: string | null;
37
+ utm_content: string | null;
38
+ first_page: string | null;
39
+ consent_state: Record<string, unknown> | null;
40
+ context: TrackingClientContext;
41
+ }
42
+ interface TrackingEventCreatePayload {
43
+ session_id: string;
44
+ event_type: string;
45
+ gclid: string | null;
46
+ fbclid: string | null;
47
+ page_url: string | null;
48
+ metadata: Record<string, unknown> | null;
49
+ context: TrackingClientContext;
50
+ }
51
+ interface TrackingInitConfig {
52
+ apiKey?: string;
53
+ endpoint?: string;
54
+ gtagId?: string;
55
+ autoCaptureTrackingParams?: boolean;
56
+ renderConsentBanner?: boolean;
57
+ cookieMaxAgeSeconds?: number;
58
+ surface?: TrackingInstallSurface;
59
+ }
60
+ declare global {
61
+ interface Window {
62
+ gtag?: (...args: unknown[]) => void;
63
+ dataLayer?: unknown[];
64
+ AranovaTracking?: {
65
+ init: (config: TrackingInitConfig) => void;
66
+ captureTrackingParams: () => TrackingParams;
67
+ getTrackingParams: () => TrackingParams;
68
+ getConsentState: () => ConsentState;
69
+ setConsentState: (state: 'granted' | 'denied') => void;
70
+ };
71
+ }
72
+ }
73
+
74
+ type GtagConsentValue = 'granted' | 'denied';
75
+ declare function getConsentState(): ConsentState;
76
+ declare function setConsentState(state: GtagConsentValue): void;
77
+
78
+ interface TrackingContextInput {
79
+ packageName?: string | null;
80
+ pageTitle?: string | null;
81
+ referrer?: string | null;
82
+ sdkVersion?: string | null;
83
+ siteOrigin?: string | null;
84
+ }
85
+ interface TrackingEventInput {
86
+ eventType: string;
87
+ metadata?: Record<string, unknown> | null;
88
+ pageUrl?: string | null;
89
+ sessionId: string;
90
+ }
91
+ interface TrackingSessionInput {
92
+ consentState?: Record<string, unknown> | null;
93
+ firstPage?: string | null;
94
+ sessionId: string;
95
+ }
96
+ declare function createTrackingClientContext(surface: TrackingInstallSurface, input?: TrackingContextInput): TrackingClientContext;
97
+ declare function createTrackingSessionUpsertPayload(trackingParams: TrackingParams, input: TrackingSessionInput, context: TrackingClientContext): TrackingSessionUpsertPayload;
98
+ declare function createTrackingEventCreatePayload(trackingParams: TrackingParams, input: TrackingEventInput, context: TrackingClientContext): TrackingEventCreatePayload;
99
+
100
+ declare const TRACKING_PARAM_KEYS: readonly ["gclid", "fbclid", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
101
+ declare function captureTrackingParamsFromLocation(url?: string, maxAgeSeconds?: number): TrackingParams;
102
+
103
+ declare function useGclid(): string | null;
104
+ declare function useTrackingParams(): TrackingParams;
105
+ declare function useConsentState(): ConsentState;
106
+
107
+ export { ConsentBanner, type ConsentState, GoogleAdsTracking, type GoogleAdsTrackingProps, TRACKING_PARAM_KEYS, type TrackingClientContext, type TrackingEventCreatePayload, type TrackingInitConfig, type TrackingInstallSurface, type TrackingParams, type TrackingSessionUpsertPayload, captureTrackingParamsFromLocation, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, getConsentState, setConsentState, useConsentState, useGclid, useTrackingParams };
package/dist/index.js ADDED
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ ConsentBanner: () => ConsentBanner,
24
+ GoogleAdsTracking: () => GoogleAdsTracking,
25
+ TRACKING_PARAM_KEYS: () => TRACKING_PARAM_KEYS,
26
+ captureTrackingParamsFromLocation: () => captureTrackingParamsFromLocation,
27
+ createTrackingClientContext: () => createTrackingClientContext,
28
+ createTrackingEventCreatePayload: () => createTrackingEventCreatePayload,
29
+ createTrackingSessionUpsertPayload: () => createTrackingSessionUpsertPayload,
30
+ getConsentState: () => getConsentState,
31
+ setConsentState: () => setConsentState,
32
+ useConsentState: () => useConsentState,
33
+ useGclid: () => useGclid,
34
+ useTrackingParams: () => useTrackingParams
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/ConsentBanner.tsx
39
+ var import_react = require("react");
40
+
41
+ // ../tracking-core/src/consent.ts
42
+ var CONSENT_STATE_KEY = "consent_state";
43
+ var CONSENT_TIMESTAMP_KEY = "consent_timestamp";
44
+ function buildConsentPayload(state) {
45
+ return {
46
+ ad_storage: state,
47
+ ad_user_data: state,
48
+ ad_personalization: state,
49
+ analytics_storage: state
50
+ };
51
+ }
52
+ function getConsentState() {
53
+ if (typeof window === "undefined")
54
+ return "pending";
55
+ const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
56
+ if (storedState === "granted" || storedState === "denied")
57
+ return storedState;
58
+ return "pending";
59
+ }
60
+ function setConsentState(state) {
61
+ if (typeof window === "undefined")
62
+ return;
63
+ window.localStorage.setItem(CONSENT_STATE_KEY, state);
64
+ window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, (/* @__PURE__ */ new Date()).toISOString());
65
+ if (typeof window.gtag === "function")
66
+ window.gtag("consent", "update", buildConsentPayload(state));
67
+ }
68
+ function restoreStoredConsent() {
69
+ const consentState = getConsentState();
70
+ if (consentState === "granted" || consentState === "denied")
71
+ setConsentState(consentState);
72
+ return consentState;
73
+ }
74
+
75
+ // ../tracking-core/src/payloads.ts
76
+ function createTrackingClientContext(surface, input = {}) {
77
+ return {
78
+ surface,
79
+ sdk_version: input.sdkVersion ?? null,
80
+ package_name: input.packageName ?? null,
81
+ site_origin: input.siteOrigin ?? (typeof window === "undefined" ? null : window.location.origin),
82
+ page_title: input.pageTitle ?? (typeof document === "undefined" ? null : document.title || null),
83
+ referrer: input.referrer ?? (typeof document === "undefined" ? null : document.referrer || null)
84
+ };
85
+ }
86
+ function createTrackingSessionUpsertPayload(trackingParams, input, context) {
87
+ return {
88
+ session_id: input.sessionId,
89
+ gclid: trackingParams.gclid,
90
+ fbclid: trackingParams.fbclid,
91
+ utm_source: trackingParams.utm_source,
92
+ utm_medium: trackingParams.utm_medium,
93
+ utm_campaign: trackingParams.utm_campaign,
94
+ utm_term: trackingParams.utm_term,
95
+ utm_content: trackingParams.utm_content,
96
+ first_page: input.firstPage ?? null,
97
+ consent_state: input.consentState ?? null,
98
+ context
99
+ };
100
+ }
101
+ function createTrackingEventCreatePayload(trackingParams, input, context) {
102
+ return {
103
+ session_id: input.sessionId,
104
+ event_type: input.eventType,
105
+ gclid: trackingParams.gclid,
106
+ fbclid: trackingParams.fbclid,
107
+ page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
108
+ metadata: input.metadata ?? null,
109
+ context
110
+ };
111
+ }
112
+
113
+ // ../tracking-core/src/gtag.ts
114
+ var GTAG_SCRIPT_HOST = "https://www.googletagmanager.com/gtag/js";
115
+ var TRACKING_SCRIPT_ATTRIBUTE = "data-aranova-tracking";
116
+ function getScriptMarker(id) {
117
+ return `aranova-${id}`;
118
+ }
119
+ function ensureGtagFunction() {
120
+ window.dataLayer = window.dataLayer || [];
121
+ if (typeof window.gtag === "function")
122
+ return window.gtag;
123
+ window.gtag = (...args) => {
124
+ window.dataLayer?.push(args);
125
+ };
126
+ return window.gtag;
127
+ }
128
+ function applyDefaultConsentState() {
129
+ const gtag = ensureGtagFunction();
130
+ gtag("consent", "default", {
131
+ ad_storage: "denied",
132
+ ad_user_data: "denied",
133
+ ad_personalization: "denied",
134
+ analytics_storage: "denied",
135
+ wait_for_update: 500
136
+ });
137
+ }
138
+ function loadGtagScript(gtagId) {
139
+ if (typeof document === "undefined")
140
+ return;
141
+ const marker = getScriptMarker("gtag-loader");
142
+ const existingScript = document.querySelector(`script[${TRACKING_SCRIPT_ATTRIBUTE}="${marker}"]`);
143
+ if (existingScript)
144
+ return;
145
+ const script = document.createElement("script");
146
+ script.async = true;
147
+ script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;
148
+ script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);
149
+ document.head.appendChild(script);
150
+ }
151
+ function initializeGtag(gtagId) {
152
+ const gtag = ensureGtagFunction();
153
+ gtag("js", /* @__PURE__ */ new Date());
154
+ gtag("config", gtagId);
155
+ }
156
+ function bootstrapGoogleAdsTracking(gtagId) {
157
+ if (typeof window === "undefined" || typeof document === "undefined")
158
+ return;
159
+ applyDefaultConsentState();
160
+ loadGtagScript(gtagId);
161
+ initializeGtag(gtagId);
162
+ restoreStoredConsent();
163
+ }
164
+
165
+ // ../tracking-core/src/tracking.ts
166
+ var TRACKING_COOKIE_MAX_AGE_SECONDS = 7776e3;
167
+ var TRACKING_PARAM_KEYS = [
168
+ "gclid",
169
+ "fbclid",
170
+ "utm_source",
171
+ "utm_medium",
172
+ "utm_campaign",
173
+ "utm_term",
174
+ "utm_content"
175
+ ];
176
+ function createEmptyTrackingParams() {
177
+ return {
178
+ gclid: null,
179
+ fbclid: null,
180
+ utm_source: null,
181
+ utm_medium: null,
182
+ utm_campaign: null,
183
+ utm_term: null,
184
+ utm_content: null
185
+ };
186
+ }
187
+ function normalizeTrackingCookieValue(value) {
188
+ return typeof value === "string" && value.length > 0 ? value : null;
189
+ }
190
+ function getTrackingParamsFromCookieReader(readCookie) {
191
+ return TRACKING_PARAM_KEYS.reduce((params, key) => {
192
+ params[key] = normalizeTrackingCookieValue(readCookie(key));
193
+ return params;
194
+ }, createEmptyTrackingParams());
195
+ }
196
+ function getTrackingQueryValues(searchParams) {
197
+ return TRACKING_PARAM_KEYS.reduce((params, key) => {
198
+ const value = searchParams.get(key);
199
+ if (typeof value === "string" && value.trim().length > 0) {
200
+ params[key] = value;
201
+ }
202
+ return params;
203
+ }, {});
204
+ }
205
+ function getCookieValueFromDocument(key) {
206
+ if (typeof document === "undefined")
207
+ return null;
208
+ const cookies = document.cookie ? document.cookie.split("; ") : [];
209
+ const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
210
+ if (!match)
211
+ return null;
212
+ const [, rawValue = ""] = match.split("=");
213
+ return normalizeTrackingCookieValue(decodeURIComponent(rawValue));
214
+ }
215
+ function setTrackingCookie(key, value, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
216
+ if (typeof document === "undefined")
217
+ return;
218
+ const encodedValue = encodeURIComponent(value);
219
+ document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;
220
+ }
221
+ function persistTrackingParamsFromSearchParams(searchParams, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
222
+ const trackingValues = getTrackingQueryValues(searchParams);
223
+ Object.entries(trackingValues).forEach(([key, value]) => {
224
+ setTrackingCookie(key, value, maxAgeSeconds);
225
+ });
226
+ return trackingValues;
227
+ }
228
+ function captureTrackingParamsFromLocation(url = typeof window === "undefined" ? "" : window.location.href, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
229
+ const resolvedUrl = typeof window === "undefined" ? new URL(url || "https://example.invalid") : new URL(url, window.location.origin);
230
+ persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);
231
+ return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
232
+ }
233
+
234
+ // src/ConsentBanner.tsx
235
+ var import_jsx_runtime = require("react/jsx-runtime");
236
+ function ConsentBanner() {
237
+ const [consentState, setBannerConsentState] = (0, import_react.useState)("pending");
238
+ const [isReady, setIsReady] = (0, import_react.useState)(false);
239
+ (0, import_react.useEffect)(() => {
240
+ setBannerConsentState(getConsentState());
241
+ setIsReady(true);
242
+ }, []);
243
+ if (!isReady || consentState !== "pending")
244
+ return null;
245
+ const handleAccept = () => {
246
+ setConsentState("granted");
247
+ setBannerConsentState("granted");
248
+ };
249
+ const handleDecline = () => {
250
+ setConsentState("denied");
251
+ setBannerConsentState("denied");
252
+ };
253
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between", children: [
254
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm text-gray-700", children: "We use cookies to understand ad performance and improve how our marketing works across visits. You can accept or decline this tracking." }),
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
257
+ "button",
258
+ {
259
+ type: "button",
260
+ onClick: handleDecline,
261
+ className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100",
262
+ children: "Decline"
263
+ }
264
+ ),
265
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
266
+ "button",
267
+ {
268
+ type: "button",
269
+ onClick: handleAccept,
270
+ className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800",
271
+ children: "Accept"
272
+ }
273
+ )
274
+ ] })
275
+ ] }) });
276
+ }
277
+
278
+ // src/GoogleAdsTracking.tsx
279
+ var import_react2 = require("react");
280
+ function GoogleAdsTracking({ gtagId }) {
281
+ (0, import_react2.useEffect)(() => {
282
+ bootstrapGoogleAdsTracking(gtagId);
283
+ }, [gtagId]);
284
+ return null;
285
+ }
286
+
287
+ // src/hooks.ts
288
+ var import_react3 = require("react");
289
+ function useGclid() {
290
+ const [gclid, setGclid] = (0, import_react3.useState)(null);
291
+ (0, import_react3.useEffect)(() => {
292
+ setGclid(getCookieValueFromDocument("gclid"));
293
+ }, []);
294
+ return gclid;
295
+ }
296
+ function useTrackingParams() {
297
+ const [trackingParams, setTrackingParams] = (0, import_react3.useState)(createEmptyTrackingParams());
298
+ (0, import_react3.useEffect)(() => {
299
+ setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
300
+ }, []);
301
+ return trackingParams;
302
+ }
303
+ function useConsentState() {
304
+ const [consentState, setConsentStateValue] = (0, import_react3.useState)("pending");
305
+ (0, import_react3.useEffect)(() => {
306
+ setConsentStateValue(getConsentState());
307
+ const handleStorage = (event) => {
308
+ if (event.key === "consent_state")
309
+ setConsentStateValue(getConsentState());
310
+ };
311
+ window.addEventListener("storage", handleStorage);
312
+ return () => {
313
+ window.removeEventListener("storage", handleStorage);
314
+ };
315
+ }, []);
316
+ return consentState;
317
+ }
318
+ // Annotate the CommonJS export names for ESM import in node:
319
+ 0 && (module.exports = {
320
+ ConsentBanner,
321
+ GoogleAdsTracking,
322
+ TRACKING_PARAM_KEYS,
323
+ captureTrackingParamsFromLocation,
324
+ createTrackingClientContext,
325
+ createTrackingEventCreatePayload,
326
+ createTrackingSessionUpsertPayload,
327
+ getConsentState,
328
+ setConsentState,
329
+ useConsentState,
330
+ useGclid,
331
+ useTrackingParams
332
+ });
333
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/ConsentBanner.tsx","../../tracking-core/src/consent.ts","../../tracking-core/src/payloads.ts","../../tracking-core/src/gtag.ts","../../tracking-core/src/tracking.ts","../src/GoogleAdsTracking.tsx","../src/hooks.ts"],"sourcesContent":["export { ConsentBanner } from './ConsentBanner';\nexport { GoogleAdsTracking, type GoogleAdsTrackingProps } from './GoogleAdsTracking';\nexport { useConsentState, useGclid, useTrackingParams } from './hooks';\nexport { getConsentState, setConsentState } from '../../tracking-core/src/index';\nexport {\n captureTrackingParamsFromLocation,\n createTrackingClientContext,\n createTrackingEventCreatePayload,\n createTrackingSessionUpsertPayload,\n TRACKING_PARAM_KEYS,\n} from '../../tracking-core/src/index';\nexport type {\n ConsentState,\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInitConfig,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from '../../tracking-core/src/index';\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState, setConsentState, type ConsentState } from '../../tracking-core/src/index';\n\nexport function ConsentBanner() {\n const [consentState, setBannerConsentState] = useState<ConsentState>('pending');\n const [isReady, setIsReady] = useState(false);\n\n useEffect(() => {\n setBannerConsentState(getConsentState());\n setIsReady(true);\n }, []);\n\n if (!isReady || consentState !== 'pending')\n return null;\n\n const handleAccept = (): void => {\n setConsentState('granted');\n setBannerConsentState('granted');\n };\n\n const handleDecline = (): void => {\n setConsentState('denied');\n setBannerConsentState('denied');\n };\n\n return (\n <div className=\"fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg\">\n <div className=\"mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <p className=\"text-sm text-gray-700\">\n We use cookies to understand ad performance and improve how our marketing works across visits.\n You can accept or decline this tracking.\n </p>\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <button\n type=\"button\"\n onClick={handleDecline}\n className=\"rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100\"\n >\n Decline\n </button>\n <button\n type=\"button\"\n onClick={handleAccept}\n className=\"rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800\"\n >\n Accept\n </button>\n </div>\n </div>\n </div>\n );\n}\n","import type { ConsentState } from './types';\n\nexport const CONSENT_STATE_KEY = 'consent_state';\nexport const CONSENT_TIMESTAMP_KEY = 'consent_timestamp';\n\nexport type GtagConsentValue = 'granted' | 'denied';\n\nexport interface ConsentUpdatePayload {\n ad_storage: GtagConsentValue;\n ad_user_data: GtagConsentValue;\n ad_personalization: GtagConsentValue;\n analytics_storage: GtagConsentValue;\n}\n\nexport function buildConsentPayload(state: GtagConsentValue): ConsentUpdatePayload {\n return {\n ad_storage: state,\n ad_user_data: state,\n ad_personalization: state,\n analytics_storage: state,\n };\n}\n\nexport function getConsentState(): ConsentState {\n if (typeof window === 'undefined')\n return 'pending';\n\n const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);\n\n if (storedState === 'granted' || storedState === 'denied')\n return storedState;\n\n return 'pending';\n}\n\nexport function setConsentState(state: GtagConsentValue): void {\n if (typeof window === 'undefined')\n return;\n\n window.localStorage.setItem(CONSENT_STATE_KEY, state);\n window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, new Date().toISOString());\n\n if (typeof window.gtag === 'function')\n window.gtag('consent', 'update', buildConsentPayload(state));\n}\n\nexport function restoreStoredConsent(): ConsentState {\n const consentState = getConsentState();\n\n if (consentState === 'granted' || consentState === 'denied')\n setConsentState(consentState);\n\n return consentState;\n}\n","import type {\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from './types';\n\ninterface TrackingContextInput {\n packageName?: string | null;\n pageTitle?: string | null;\n referrer?: string | null;\n sdkVersion?: string | null;\n siteOrigin?: string | null;\n}\n\ninterface TrackingEventInput {\n eventType: string;\n metadata?: Record<string, unknown> | null;\n pageUrl?: string | null;\n sessionId: string;\n}\n\ninterface TrackingSessionInput {\n consentState?: Record<string, unknown> | null;\n firstPage?: string | null;\n sessionId: string;\n}\n\nexport function createTrackingClientContext(\n surface: TrackingInstallSurface,\n input: TrackingContextInput = {},\n): TrackingClientContext {\n return {\n surface,\n sdk_version: input.sdkVersion ?? null,\n package_name: input.packageName ?? null,\n site_origin: input.siteOrigin ?? (typeof window === 'undefined' ? null : window.location.origin),\n page_title: input.pageTitle ?? (typeof document === 'undefined' ? null : document.title || null),\n referrer: input.referrer ?? (typeof document === 'undefined' ? null : document.referrer || null),\n };\n}\n\nexport function createTrackingSessionUpsertPayload(\n trackingParams: TrackingParams,\n input: TrackingSessionInput,\n context: TrackingClientContext,\n): TrackingSessionUpsertPayload {\n return {\n session_id: input.sessionId,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n utm_source: trackingParams.utm_source,\n utm_medium: trackingParams.utm_medium,\n utm_campaign: trackingParams.utm_campaign,\n utm_term: trackingParams.utm_term,\n utm_content: trackingParams.utm_content,\n first_page: input.firstPage ?? null,\n consent_state: input.consentState ?? null,\n context,\n };\n}\n\nexport function createTrackingEventCreatePayload(\n trackingParams: TrackingParams,\n input: TrackingEventInput,\n context: TrackingClientContext,\n): TrackingEventCreatePayload {\n return {\n session_id: input.sessionId,\n event_type: input.eventType,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n page_url: input.pageUrl ?? (typeof window === 'undefined' ? null : window.location.href),\n metadata: input.metadata ?? null,\n context,\n };\n}\n","import { buildConsentPayload, getConsentState, restoreStoredConsent } from './consent';\n\nexport const GTAG_SCRIPT_HOST = 'https://www.googletagmanager.com/gtag/js';\nexport const TRACKING_SCRIPT_ATTRIBUTE = 'data-aranova-tracking';\n\nfunction getScriptMarker(id: string): string {\n return `aranova-${id}`;\n}\n\nexport function createConsentDefaultScript(): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n});\n`.trim();\n}\n\nexport function createGtagInitScript(gtagId: string): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('js', new Date());\ngtag('config', '${gtagId}');\n`.trim();\n}\n\nexport function createConsentRestoreScript(): string {\n return `\nconst consentState = window.localStorage.getItem('consent_state');\nif (window.gtag && (consentState === 'granted' || consentState === 'denied')) {\n window.gtag('consent', 'update', {\n ad_storage: consentState,\n ad_user_data: consentState,\n ad_personalization: consentState,\n analytics_storage: consentState,\n });\n}\n`.trim();\n}\n\nexport function ensureGtagFunction(): NonNullable<Window['gtag']> {\n window.dataLayer = window.dataLayer || [];\n\n if (typeof window.gtag === 'function')\n return window.gtag;\n\n window.gtag = (...args: unknown[]) => {\n window.dataLayer?.push(args);\n };\n\n return window.gtag;\n}\n\nexport function applyDefaultConsentState(): void {\n const gtag = ensureGtagFunction();\n gtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n });\n}\n\nexport function loadGtagScript(gtagId: string): void {\n if (typeof document === 'undefined')\n return;\n\n const marker = getScriptMarker('gtag-loader');\n const existingScript = document.querySelector<HTMLScriptElement>(`script[${TRACKING_SCRIPT_ATTRIBUTE}=\"${marker}\"]`);\n if (existingScript)\n return;\n\n const script = document.createElement('script');\n script.async = true;\n script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;\n script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);\n document.head.appendChild(script);\n}\n\nexport function initializeGtag(gtagId: string): void {\n const gtag = ensureGtagFunction();\n gtag('js', new Date());\n gtag('config', gtagId);\n}\n\nexport function restoreConsentState(): void {\n if (typeof window === 'undefined')\n return;\n\n const consentState = getConsentState();\n if (consentState === 'granted' || consentState === 'denied')\n window.gtag?.('consent', 'update', buildConsentPayload(consentState));\n}\n\nexport function bootstrapGoogleAdsTracking(gtagId: string): void {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return;\n\n applyDefaultConsentState();\n loadGtagScript(gtagId);\n initializeGtag(gtagId);\n restoreStoredConsent();\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\nimport { bootstrapGoogleAdsTracking } from '../../tracking-core/src/index';\n\nexport interface GoogleAdsTrackingProps {\n gtagId: string;\n}\n\nexport function GoogleAdsTracking({ gtagId }: GoogleAdsTrackingProps) {\n useEffect(() => {\n bootstrapGoogleAdsTracking(gtagId);\n }, [gtagId]);\n\n return null;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState } from '../../tracking-core/src/index';\nimport {\n createEmptyTrackingParams,\n getCookieValueFromDocument,\n getTrackingParamsFromCookieReader,\n type ConsentState,\n type TrackingParams,\n} from '../../tracking-core/src/index';\n\nexport function useGclid(): string | null {\n const [gclid, setGclid] = useState<string | null>(null);\n\n useEffect(() => {\n setGclid(getCookieValueFromDocument('gclid'));\n }, []);\n\n return gclid;\n}\n\nexport function useTrackingParams(): TrackingParams {\n const [trackingParams, setTrackingParams] = useState<TrackingParams>(createEmptyTrackingParams());\n\n useEffect(() => {\n setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));\n }, []);\n\n return trackingParams;\n}\n\nexport function useConsentState(): ConsentState {\n const [consentState, setConsentStateValue] = useState<ConsentState>('pending');\n\n useEffect(() => {\n setConsentStateValue(getConsentState());\n\n const handleStorage = (event: StorageEvent): void => {\n if (event.key === 'consent_state')\n setConsentStateValue(getConsentState());\n };\n\n window.addEventListener('storage', handleStorage);\n\n return () => {\n window.removeEventListener('storage', handleStorage);\n };\n }, []);\n\n return consentState;\n}\n"],"mappings":"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","names":["import_react","import_react"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,295 @@
1
+ // src/ConsentBanner.tsx
2
+ import { useEffect, useState } from "react";
3
+
4
+ // ../tracking-core/src/consent.ts
5
+ var CONSENT_STATE_KEY = "consent_state";
6
+ var CONSENT_TIMESTAMP_KEY = "consent_timestamp";
7
+ function buildConsentPayload(state) {
8
+ return {
9
+ ad_storage: state,
10
+ ad_user_data: state,
11
+ ad_personalization: state,
12
+ analytics_storage: state
13
+ };
14
+ }
15
+ function getConsentState() {
16
+ if (typeof window === "undefined")
17
+ return "pending";
18
+ const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);
19
+ if (storedState === "granted" || storedState === "denied")
20
+ return storedState;
21
+ return "pending";
22
+ }
23
+ function setConsentState(state) {
24
+ if (typeof window === "undefined")
25
+ return;
26
+ window.localStorage.setItem(CONSENT_STATE_KEY, state);
27
+ window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, (/* @__PURE__ */ new Date()).toISOString());
28
+ if (typeof window.gtag === "function")
29
+ window.gtag("consent", "update", buildConsentPayload(state));
30
+ }
31
+ function restoreStoredConsent() {
32
+ const consentState = getConsentState();
33
+ if (consentState === "granted" || consentState === "denied")
34
+ setConsentState(consentState);
35
+ return consentState;
36
+ }
37
+
38
+ // ../tracking-core/src/payloads.ts
39
+ function createTrackingClientContext(surface, input = {}) {
40
+ return {
41
+ surface,
42
+ sdk_version: input.sdkVersion ?? null,
43
+ package_name: input.packageName ?? null,
44
+ site_origin: input.siteOrigin ?? (typeof window === "undefined" ? null : window.location.origin),
45
+ page_title: input.pageTitle ?? (typeof document === "undefined" ? null : document.title || null),
46
+ referrer: input.referrer ?? (typeof document === "undefined" ? null : document.referrer || null)
47
+ };
48
+ }
49
+ function createTrackingSessionUpsertPayload(trackingParams, input, context) {
50
+ return {
51
+ session_id: input.sessionId,
52
+ gclid: trackingParams.gclid,
53
+ fbclid: trackingParams.fbclid,
54
+ utm_source: trackingParams.utm_source,
55
+ utm_medium: trackingParams.utm_medium,
56
+ utm_campaign: trackingParams.utm_campaign,
57
+ utm_term: trackingParams.utm_term,
58
+ utm_content: trackingParams.utm_content,
59
+ first_page: input.firstPage ?? null,
60
+ consent_state: input.consentState ?? null,
61
+ context
62
+ };
63
+ }
64
+ function createTrackingEventCreatePayload(trackingParams, input, context) {
65
+ return {
66
+ session_id: input.sessionId,
67
+ event_type: input.eventType,
68
+ gclid: trackingParams.gclid,
69
+ fbclid: trackingParams.fbclid,
70
+ page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
71
+ metadata: input.metadata ?? null,
72
+ context
73
+ };
74
+ }
75
+
76
+ // ../tracking-core/src/gtag.ts
77
+ var GTAG_SCRIPT_HOST = "https://www.googletagmanager.com/gtag/js";
78
+ var TRACKING_SCRIPT_ATTRIBUTE = "data-aranova-tracking";
79
+ function getScriptMarker(id) {
80
+ return `aranova-${id}`;
81
+ }
82
+ function ensureGtagFunction() {
83
+ window.dataLayer = window.dataLayer || [];
84
+ if (typeof window.gtag === "function")
85
+ return window.gtag;
86
+ window.gtag = (...args) => {
87
+ window.dataLayer?.push(args);
88
+ };
89
+ return window.gtag;
90
+ }
91
+ function applyDefaultConsentState() {
92
+ const gtag = ensureGtagFunction();
93
+ gtag("consent", "default", {
94
+ ad_storage: "denied",
95
+ ad_user_data: "denied",
96
+ ad_personalization: "denied",
97
+ analytics_storage: "denied",
98
+ wait_for_update: 500
99
+ });
100
+ }
101
+ function loadGtagScript(gtagId) {
102
+ if (typeof document === "undefined")
103
+ return;
104
+ const marker = getScriptMarker("gtag-loader");
105
+ const existingScript = document.querySelector(`script[${TRACKING_SCRIPT_ATTRIBUTE}="${marker}"]`);
106
+ if (existingScript)
107
+ return;
108
+ const script = document.createElement("script");
109
+ script.async = true;
110
+ script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;
111
+ script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);
112
+ document.head.appendChild(script);
113
+ }
114
+ function initializeGtag(gtagId) {
115
+ const gtag = ensureGtagFunction();
116
+ gtag("js", /* @__PURE__ */ new Date());
117
+ gtag("config", gtagId);
118
+ }
119
+ function bootstrapGoogleAdsTracking(gtagId) {
120
+ if (typeof window === "undefined" || typeof document === "undefined")
121
+ return;
122
+ applyDefaultConsentState();
123
+ loadGtagScript(gtagId);
124
+ initializeGtag(gtagId);
125
+ restoreStoredConsent();
126
+ }
127
+
128
+ // ../tracking-core/src/tracking.ts
129
+ var TRACKING_COOKIE_MAX_AGE_SECONDS = 7776e3;
130
+ var TRACKING_PARAM_KEYS = [
131
+ "gclid",
132
+ "fbclid",
133
+ "utm_source",
134
+ "utm_medium",
135
+ "utm_campaign",
136
+ "utm_term",
137
+ "utm_content"
138
+ ];
139
+ function createEmptyTrackingParams() {
140
+ return {
141
+ gclid: null,
142
+ fbclid: null,
143
+ utm_source: null,
144
+ utm_medium: null,
145
+ utm_campaign: null,
146
+ utm_term: null,
147
+ utm_content: null
148
+ };
149
+ }
150
+ function normalizeTrackingCookieValue(value) {
151
+ return typeof value === "string" && value.length > 0 ? value : null;
152
+ }
153
+ function getTrackingParamsFromCookieReader(readCookie) {
154
+ return TRACKING_PARAM_KEYS.reduce((params, key) => {
155
+ params[key] = normalizeTrackingCookieValue(readCookie(key));
156
+ return params;
157
+ }, createEmptyTrackingParams());
158
+ }
159
+ function getTrackingQueryValues(searchParams) {
160
+ return TRACKING_PARAM_KEYS.reduce((params, key) => {
161
+ const value = searchParams.get(key);
162
+ if (typeof value === "string" && value.trim().length > 0) {
163
+ params[key] = value;
164
+ }
165
+ return params;
166
+ }, {});
167
+ }
168
+ function getCookieValueFromDocument(key) {
169
+ if (typeof document === "undefined")
170
+ return null;
171
+ const cookies = document.cookie ? document.cookie.split("; ") : [];
172
+ const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
173
+ if (!match)
174
+ return null;
175
+ const [, rawValue = ""] = match.split("=");
176
+ return normalizeTrackingCookieValue(decodeURIComponent(rawValue));
177
+ }
178
+ function setTrackingCookie(key, value, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
179
+ if (typeof document === "undefined")
180
+ return;
181
+ const encodedValue = encodeURIComponent(value);
182
+ document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;
183
+ }
184
+ function persistTrackingParamsFromSearchParams(searchParams, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
185
+ const trackingValues = getTrackingQueryValues(searchParams);
186
+ Object.entries(trackingValues).forEach(([key, value]) => {
187
+ setTrackingCookie(key, value, maxAgeSeconds);
188
+ });
189
+ return trackingValues;
190
+ }
191
+ function captureTrackingParamsFromLocation(url = typeof window === "undefined" ? "" : window.location.href, maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS) {
192
+ const resolvedUrl = typeof window === "undefined" ? new URL(url || "https://example.invalid") : new URL(url, window.location.origin);
193
+ persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);
194
+ return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
195
+ }
196
+
197
+ // src/ConsentBanner.tsx
198
+ import { jsx, jsxs } from "react/jsx-runtime";
199
+ function ConsentBanner() {
200
+ const [consentState, setBannerConsentState] = useState("pending");
201
+ const [isReady, setIsReady] = useState(false);
202
+ useEffect(() => {
203
+ setBannerConsentState(getConsentState());
204
+ setIsReady(true);
205
+ }, []);
206
+ if (!isReady || consentState !== "pending")
207
+ return null;
208
+ const handleAccept = () => {
209
+ setConsentState("granted");
210
+ setBannerConsentState("granted");
211
+ };
212
+ const handleDecline = () => {
213
+ setConsentState("denied");
214
+ setBannerConsentState("denied");
215
+ };
216
+ return /* @__PURE__ */ jsx("div", { className: "fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between", children: [
217
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-700", children: "We use cookies to understand ad performance and improve how our marketing works across visits. You can accept or decline this tracking." }),
218
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
219
+ /* @__PURE__ */ jsx(
220
+ "button",
221
+ {
222
+ type: "button",
223
+ onClick: handleDecline,
224
+ className: "rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100",
225
+ children: "Decline"
226
+ }
227
+ ),
228
+ /* @__PURE__ */ jsx(
229
+ "button",
230
+ {
231
+ type: "button",
232
+ onClick: handleAccept,
233
+ className: "rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800",
234
+ children: "Accept"
235
+ }
236
+ )
237
+ ] })
238
+ ] }) });
239
+ }
240
+
241
+ // src/GoogleAdsTracking.tsx
242
+ import { useEffect as useEffect2 } from "react";
243
+ function GoogleAdsTracking({ gtagId }) {
244
+ useEffect2(() => {
245
+ bootstrapGoogleAdsTracking(gtagId);
246
+ }, [gtagId]);
247
+ return null;
248
+ }
249
+
250
+ // src/hooks.ts
251
+ import { useEffect as useEffect3, useState as useState2 } from "react";
252
+ function useGclid() {
253
+ const [gclid, setGclid] = useState2(null);
254
+ useEffect3(() => {
255
+ setGclid(getCookieValueFromDocument("gclid"));
256
+ }, []);
257
+ return gclid;
258
+ }
259
+ function useTrackingParams() {
260
+ const [trackingParams, setTrackingParams] = useState2(createEmptyTrackingParams());
261
+ useEffect3(() => {
262
+ setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));
263
+ }, []);
264
+ return trackingParams;
265
+ }
266
+ function useConsentState() {
267
+ const [consentState, setConsentStateValue] = useState2("pending");
268
+ useEffect3(() => {
269
+ setConsentStateValue(getConsentState());
270
+ const handleStorage = (event) => {
271
+ if (event.key === "consent_state")
272
+ setConsentStateValue(getConsentState());
273
+ };
274
+ window.addEventListener("storage", handleStorage);
275
+ return () => {
276
+ window.removeEventListener("storage", handleStorage);
277
+ };
278
+ }, []);
279
+ return consentState;
280
+ }
281
+ export {
282
+ ConsentBanner,
283
+ GoogleAdsTracking,
284
+ TRACKING_PARAM_KEYS,
285
+ captureTrackingParamsFromLocation,
286
+ createTrackingClientContext,
287
+ createTrackingEventCreatePayload,
288
+ createTrackingSessionUpsertPayload,
289
+ getConsentState,
290
+ setConsentState,
291
+ useConsentState,
292
+ useGclid,
293
+ useTrackingParams
294
+ };
295
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ConsentBanner.tsx","../../tracking-core/src/consent.ts","../../tracking-core/src/payloads.ts","../../tracking-core/src/gtag.ts","../../tracking-core/src/tracking.ts","../src/GoogleAdsTracking.tsx","../src/hooks.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState, setConsentState, type ConsentState } from '../../tracking-core/src/index';\n\nexport function ConsentBanner() {\n const [consentState, setBannerConsentState] = useState<ConsentState>('pending');\n const [isReady, setIsReady] = useState(false);\n\n useEffect(() => {\n setBannerConsentState(getConsentState());\n setIsReady(true);\n }, []);\n\n if (!isReady || consentState !== 'pending')\n return null;\n\n const handleAccept = (): void => {\n setConsentState('granted');\n setBannerConsentState('granted');\n };\n\n const handleDecline = (): void => {\n setConsentState('denied');\n setBannerConsentState('denied');\n };\n\n return (\n <div className=\"fixed bottom-0 left-0 right-0 z-50 border-t border-gray-200 bg-white p-4 shadow-lg\">\n <div className=\"mx-auto flex max-w-5xl flex-col gap-4 md:flex-row md:items-center md:justify-between\">\n <p className=\"text-sm text-gray-700\">\n We use cookies to understand ad performance and improve how our marketing works across visits.\n You can accept or decline this tracking.\n </p>\n <div className=\"flex flex-col gap-2 sm:flex-row\">\n <button\n type=\"button\"\n onClick={handleDecline}\n className=\"rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-100\"\n >\n Decline\n </button>\n <button\n type=\"button\"\n onClick={handleAccept}\n className=\"rounded-md bg-gray-900 px-4 py-2 text-sm font-medium text-white transition hover:bg-gray-800\"\n >\n Accept\n </button>\n </div>\n </div>\n </div>\n );\n}\n","import type { ConsentState } from './types';\n\nexport const CONSENT_STATE_KEY = 'consent_state';\nexport const CONSENT_TIMESTAMP_KEY = 'consent_timestamp';\n\nexport type GtagConsentValue = 'granted' | 'denied';\n\nexport interface ConsentUpdatePayload {\n ad_storage: GtagConsentValue;\n ad_user_data: GtagConsentValue;\n ad_personalization: GtagConsentValue;\n analytics_storage: GtagConsentValue;\n}\n\nexport function buildConsentPayload(state: GtagConsentValue): ConsentUpdatePayload {\n return {\n ad_storage: state,\n ad_user_data: state,\n ad_personalization: state,\n analytics_storage: state,\n };\n}\n\nexport function getConsentState(): ConsentState {\n if (typeof window === 'undefined')\n return 'pending';\n\n const storedState = window.localStorage.getItem(CONSENT_STATE_KEY);\n\n if (storedState === 'granted' || storedState === 'denied')\n return storedState;\n\n return 'pending';\n}\n\nexport function setConsentState(state: GtagConsentValue): void {\n if (typeof window === 'undefined')\n return;\n\n window.localStorage.setItem(CONSENT_STATE_KEY, state);\n window.localStorage.setItem(CONSENT_TIMESTAMP_KEY, new Date().toISOString());\n\n if (typeof window.gtag === 'function')\n window.gtag('consent', 'update', buildConsentPayload(state));\n}\n\nexport function restoreStoredConsent(): ConsentState {\n const consentState = getConsentState();\n\n if (consentState === 'granted' || consentState === 'denied')\n setConsentState(consentState);\n\n return consentState;\n}\n","import type {\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from './types';\n\ninterface TrackingContextInput {\n packageName?: string | null;\n pageTitle?: string | null;\n referrer?: string | null;\n sdkVersion?: string | null;\n siteOrigin?: string | null;\n}\n\ninterface TrackingEventInput {\n eventType: string;\n metadata?: Record<string, unknown> | null;\n pageUrl?: string | null;\n sessionId: string;\n}\n\ninterface TrackingSessionInput {\n consentState?: Record<string, unknown> | null;\n firstPage?: string | null;\n sessionId: string;\n}\n\nexport function createTrackingClientContext(\n surface: TrackingInstallSurface,\n input: TrackingContextInput = {},\n): TrackingClientContext {\n return {\n surface,\n sdk_version: input.sdkVersion ?? null,\n package_name: input.packageName ?? null,\n site_origin: input.siteOrigin ?? (typeof window === 'undefined' ? null : window.location.origin),\n page_title: input.pageTitle ?? (typeof document === 'undefined' ? null : document.title || null),\n referrer: input.referrer ?? (typeof document === 'undefined' ? null : document.referrer || null),\n };\n}\n\nexport function createTrackingSessionUpsertPayload(\n trackingParams: TrackingParams,\n input: TrackingSessionInput,\n context: TrackingClientContext,\n): TrackingSessionUpsertPayload {\n return {\n session_id: input.sessionId,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n utm_source: trackingParams.utm_source,\n utm_medium: trackingParams.utm_medium,\n utm_campaign: trackingParams.utm_campaign,\n utm_term: trackingParams.utm_term,\n utm_content: trackingParams.utm_content,\n first_page: input.firstPage ?? null,\n consent_state: input.consentState ?? null,\n context,\n };\n}\n\nexport function createTrackingEventCreatePayload(\n trackingParams: TrackingParams,\n input: TrackingEventInput,\n context: TrackingClientContext,\n): TrackingEventCreatePayload {\n return {\n session_id: input.sessionId,\n event_type: input.eventType,\n gclid: trackingParams.gclid,\n fbclid: trackingParams.fbclid,\n page_url: input.pageUrl ?? (typeof window === 'undefined' ? null : window.location.href),\n metadata: input.metadata ?? null,\n context,\n };\n}\n","import { buildConsentPayload, getConsentState, restoreStoredConsent } from './consent';\n\nexport const GTAG_SCRIPT_HOST = 'https://www.googletagmanager.com/gtag/js';\nexport const TRACKING_SCRIPT_ATTRIBUTE = 'data-aranova-tracking';\n\nfunction getScriptMarker(id: string): string {\n return `aranova-${id}`;\n}\n\nexport function createConsentDefaultScript(): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n});\n`.trim();\n}\n\nexport function createGtagInitScript(gtagId: string): string {\n return `\nwindow.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\nwindow.gtag = gtag;\ngtag('js', new Date());\ngtag('config', '${gtagId}');\n`.trim();\n}\n\nexport function createConsentRestoreScript(): string {\n return `\nconst consentState = window.localStorage.getItem('consent_state');\nif (window.gtag && (consentState === 'granted' || consentState === 'denied')) {\n window.gtag('consent', 'update', {\n ad_storage: consentState,\n ad_user_data: consentState,\n ad_personalization: consentState,\n analytics_storage: consentState,\n });\n}\n`.trim();\n}\n\nexport function ensureGtagFunction(): NonNullable<Window['gtag']> {\n window.dataLayer = window.dataLayer || [];\n\n if (typeof window.gtag === 'function')\n return window.gtag;\n\n window.gtag = (...args: unknown[]) => {\n window.dataLayer?.push(args);\n };\n\n return window.gtag;\n}\n\nexport function applyDefaultConsentState(): void {\n const gtag = ensureGtagFunction();\n gtag('consent', 'default', {\n ad_storage: 'denied',\n ad_user_data: 'denied',\n ad_personalization: 'denied',\n analytics_storage: 'denied',\n wait_for_update: 500,\n });\n}\n\nexport function loadGtagScript(gtagId: string): void {\n if (typeof document === 'undefined')\n return;\n\n const marker = getScriptMarker('gtag-loader');\n const existingScript = document.querySelector<HTMLScriptElement>(`script[${TRACKING_SCRIPT_ATTRIBUTE}=\"${marker}\"]`);\n if (existingScript)\n return;\n\n const script = document.createElement('script');\n script.async = true;\n script.src = `${GTAG_SCRIPT_HOST}?id=${encodeURIComponent(gtagId)}`;\n script.setAttribute(TRACKING_SCRIPT_ATTRIBUTE, marker);\n document.head.appendChild(script);\n}\n\nexport function initializeGtag(gtagId: string): void {\n const gtag = ensureGtagFunction();\n gtag('js', new Date());\n gtag('config', gtagId);\n}\n\nexport function restoreConsentState(): void {\n if (typeof window === 'undefined')\n return;\n\n const consentState = getConsentState();\n if (consentState === 'granted' || consentState === 'denied')\n window.gtag?.('consent', 'update', buildConsentPayload(consentState));\n}\n\nexport function bootstrapGoogleAdsTracking(gtagId: string): void {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return;\n\n applyDefaultConsentState();\n loadGtagScript(gtagId);\n initializeGtag(gtagId);\n restoreStoredConsent();\n}\n","import type { TrackingParams } from './types';\n\nexport const TRACKING_COOKIE_MAX_AGE_SECONDS = 7_776_000;\n\nexport const TRACKING_PARAM_KEYS = [\n 'gclid',\n 'fbclid',\n 'utm_source',\n 'utm_medium',\n 'utm_campaign',\n 'utm_term',\n 'utm_content',\n] as const;\n\nexport type TrackingParamKey = (typeof TRACKING_PARAM_KEYS)[number];\n\nexport function createEmptyTrackingParams(): TrackingParams {\n return {\n gclid: null,\n fbclid: null,\n utm_source: null,\n utm_medium: null,\n utm_campaign: null,\n utm_term: null,\n utm_content: null,\n };\n}\n\nexport function normalizeTrackingCookieValue(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function getTrackingParamsFromCookieReader(\n readCookie: (key: TrackingParamKey) => unknown,\n): TrackingParams {\n return TRACKING_PARAM_KEYS.reduce<TrackingParams>((params, key) => {\n params[key] = normalizeTrackingCookieValue(readCookie(key));\n return params;\n }, createEmptyTrackingParams());\n}\n\nexport function getTrackingQueryValues(\n searchParams: Pick<URLSearchParams, 'get'>,\n): Partial<Record<TrackingParamKey, string>> {\n return TRACKING_PARAM_KEYS.reduce<Partial<Record<TrackingParamKey, string>>>((params, key) => {\n const value = searchParams.get(key);\n\n if (typeof value === 'string' && value.trim().length > 0) {\n params[key] = value;\n }\n\n return params;\n }, {});\n}\n\nexport function getCookieValueFromDocument(key: TrackingParamKey): string | null {\n if (typeof document === 'undefined')\n return null;\n\n const cookies = document.cookie ? document.cookie.split('; ') : [];\n const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));\n\n if (!match)\n return null;\n\n const [, rawValue = ''] = match.split('=');\n return normalizeTrackingCookieValue(decodeURIComponent(rawValue));\n}\n\nexport function setTrackingCookie(\n key: TrackingParamKey,\n value: string,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): void {\n if (typeof document === 'undefined')\n return;\n\n const encodedValue = encodeURIComponent(value);\n document.cookie = `${key}=${encodedValue}; Max-Age=${maxAgeSeconds}; Path=/; SameSite=Lax`;\n}\n\nexport function persistTrackingParamsFromSearchParams(\n searchParams: Pick<URLSearchParams, 'get'>,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): Partial<Record<TrackingParamKey, string>> {\n const trackingValues = getTrackingQueryValues(searchParams);\n\n Object.entries(trackingValues).forEach(([key, value]) => {\n setTrackingCookie(key as TrackingParamKey, value, maxAgeSeconds);\n });\n\n return trackingValues;\n}\n\nexport function captureTrackingParamsFromLocation(\n url = typeof window === 'undefined' ? '' : window.location.href,\n maxAgeSeconds = TRACKING_COOKIE_MAX_AGE_SECONDS,\n): TrackingParams {\n const resolvedUrl = typeof window === 'undefined'\n ? new URL(url || 'https://example.invalid')\n : new URL(url, window.location.origin);\n persistTrackingParamsFromSearchParams(resolvedUrl.searchParams, maxAgeSeconds);\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n","'use client';\n\nimport { useEffect } from 'react';\n\nimport { bootstrapGoogleAdsTracking } from '../../tracking-core/src/index';\n\nexport interface GoogleAdsTrackingProps {\n gtagId: string;\n}\n\nexport function GoogleAdsTracking({ gtagId }: GoogleAdsTrackingProps) {\n useEffect(() => {\n bootstrapGoogleAdsTracking(gtagId);\n }, [gtagId]);\n\n return null;\n}\n","'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { getConsentState } from '../../tracking-core/src/index';\nimport {\n createEmptyTrackingParams,\n getCookieValueFromDocument,\n getTrackingParamsFromCookieReader,\n type ConsentState,\n type TrackingParams,\n} from '../../tracking-core/src/index';\n\nexport function useGclid(): string | null {\n const [gclid, setGclid] = useState<string | null>(null);\n\n useEffect(() => {\n setGclid(getCookieValueFromDocument('gclid'));\n }, []);\n\n return gclid;\n}\n\nexport function useTrackingParams(): TrackingParams {\n const [trackingParams, setTrackingParams] = useState<TrackingParams>(createEmptyTrackingParams());\n\n useEffect(() => {\n setTrackingParams(getTrackingParamsFromCookieReader(getCookieValueFromDocument));\n }, []);\n\n return trackingParams;\n}\n\nexport function useConsentState(): ConsentState {\n const [consentState, setConsentStateValue] = useState<ConsentState>('pending');\n\n useEffect(() => {\n setConsentStateValue(getConsentState());\n\n const handleStorage = (event: StorageEvent): void => {\n if (event.key === 'consent_state')\n setConsentStateValue(getConsentState());\n };\n\n window.addEventListener('storage', handleStorage);\n\n return () => {\n window.removeEventListener('storage', handleStorage);\n };\n }, []);\n\n return consentState;\n}\n"],"mappings":"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","names":["useEffect","useEffect","useEffect","useState","useState","useEffect"]}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@aranova/tracking-react",
3
+ "version": "0.2.2",
4
+ "private": false,
5
+ "description": "React tracking and consent utilities for Aranova client sites",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.mjs",
11
+ "types": "./dist/index.d.ts",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/AranovaIO/aranova_internal.git",
15
+ "directory": "packages/tracking-react"
16
+ },
17
+ "homepage": "https://github.com/AranovaIO/aranova_internal/tree/master/packages/tracking-react",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ }
24
+ },
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "test": "vitest",
29
+ "test:run": "vitest run",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "peerDependencies": {
33
+ "react": ">=18.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^19.2.2",
37
+ "@types/react-dom": "^19.2.2",
38
+ "jsdom": "^29.0.1",
39
+ "react": "^19.2.0",
40
+ "react-dom": "^19.2.0",
41
+ "tsup": "^8.5.1",
42
+ "typescript": "^5.7.3",
43
+ "vitest": "^4.1.4"
44
+ },
45
+ "publishConfig": {
46
+ "registry": "https://registry.npmjs.org",
47
+ "access": "public"
48
+ }
49
+ }