@aranova/tracking-react 0.2.3 → 0.3.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.d.mts +40 -1
- package/dist/index.d.ts +40 -1
- package/dist/index.js +337 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +335 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
|
|
4
5
|
|
|
@@ -28,6 +29,7 @@ interface TrackingClientContext {
|
|
|
28
29
|
}
|
|
29
30
|
interface TrackingSessionUpsertPayload {
|
|
30
31
|
session_id: string;
|
|
32
|
+
visitor_id: string | null;
|
|
31
33
|
gclid: string | null;
|
|
32
34
|
fbclid: string | null;
|
|
33
35
|
utm_source: string | null;
|
|
@@ -67,6 +69,12 @@ declare global {
|
|
|
67
69
|
getTrackingParams: () => TrackingParams;
|
|
68
70
|
getConsentState: () => ConsentState;
|
|
69
71
|
setConsentState: (state: 'granted' | 'denied') => void;
|
|
72
|
+
trackEvent: (input: {
|
|
73
|
+
eventType: string;
|
|
74
|
+
pageUrl?: string | null;
|
|
75
|
+
metadata?: Record<string, unknown> | null;
|
|
76
|
+
occurredAt?: Date | string | null;
|
|
77
|
+
}) => void;
|
|
70
78
|
};
|
|
71
79
|
}
|
|
72
80
|
}
|
|
@@ -92,6 +100,7 @@ interface TrackingSessionInput {
|
|
|
92
100
|
consentState?: Record<string, unknown> | null;
|
|
93
101
|
firstPage?: string | null;
|
|
94
102
|
sessionId: string;
|
|
103
|
+
visitorId?: string | null;
|
|
95
104
|
}
|
|
96
105
|
declare function createTrackingClientContext(surface: TrackingInstallSurface, input?: TrackingContextInput): TrackingClientContext;
|
|
97
106
|
declare function createTrackingSessionUpsertPayload(trackingParams: TrackingParams, input: TrackingSessionInput, context: TrackingClientContext): TrackingSessionUpsertPayload;
|
|
@@ -100,8 +109,38 @@ declare function createTrackingEventCreatePayload(trackingParams: TrackingParams
|
|
|
100
109
|
declare const TRACKING_PARAM_KEYS: readonly ["gclid", "fbclid", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
|
101
110
|
declare function captureTrackingParamsFromLocation(url?: string, maxAgeSeconds?: number): TrackingParams;
|
|
102
111
|
|
|
112
|
+
interface TrackEventInput {
|
|
113
|
+
eventType: string;
|
|
114
|
+
pageUrl?: string | null;
|
|
115
|
+
metadata?: Record<string, unknown> | null;
|
|
116
|
+
occurredAt?: Date | string | null;
|
|
117
|
+
}
|
|
118
|
+
interface TrackingClient {
|
|
119
|
+
trackEvent: (input: TrackEventInput) => void;
|
|
120
|
+
flush: () => Promise<void>;
|
|
121
|
+
getSessionId: () => string;
|
|
122
|
+
getVisitorId: () => string;
|
|
123
|
+
destroy: () => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
103
126
|
declare function useGclid(): string | null;
|
|
104
127
|
declare function useTrackingParams(): TrackingParams;
|
|
105
128
|
declare function useConsentState(): ConsentState;
|
|
106
129
|
|
|
107
|
-
|
|
130
|
+
interface TrackingProviderProps {
|
|
131
|
+
/** Public tracking API key issued for this business. Required. */
|
|
132
|
+
apiKey: string;
|
|
133
|
+
/** Full ingest endpoint base URL, e.g. https://api.aranova.io/tracking. Required. */
|
|
134
|
+
endpoint: string;
|
|
135
|
+
/** Optional Google Ads gtag id. If omitted, no gtag script is loaded. */
|
|
136
|
+
gtagId?: string;
|
|
137
|
+
/** Disable automatic page_view emission (manual tracking only). */
|
|
138
|
+
disableAutoPageView?: boolean;
|
|
139
|
+
/** Optional debug flag — currently a no-op, reserved for verbose logging. */
|
|
140
|
+
debug?: boolean;
|
|
141
|
+
children: ReactNode;
|
|
142
|
+
}
|
|
143
|
+
declare function TrackingProvider({ apiKey, endpoint, gtagId, disableAutoPageView, debug, children, }: TrackingProviderProps): react_jsx_runtime.JSX.Element;
|
|
144
|
+
declare function useTracking(): TrackingClient;
|
|
145
|
+
|
|
146
|
+
export { ConsentBanner, type ConsentState, GoogleAdsTracking, type GoogleAdsTrackingProps, TRACKING_PARAM_KEYS, type TrackingClient, type TrackingClientContext, type TrackingEventCreatePayload, type TrackingInitConfig, type TrackingInstallSurface, type TrackingParams, TrackingProvider, type TrackingProviderProps, type TrackingSessionUpsertPayload, captureTrackingParamsFromLocation, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, getConsentState, setConsentState, useConsentState, useGclid, useTracking, useTrackingParams };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
2
3
|
|
|
3
4
|
declare function ConsentBanner(): react_jsx_runtime.JSX.Element | null;
|
|
4
5
|
|
|
@@ -28,6 +29,7 @@ interface TrackingClientContext {
|
|
|
28
29
|
}
|
|
29
30
|
interface TrackingSessionUpsertPayload {
|
|
30
31
|
session_id: string;
|
|
32
|
+
visitor_id: string | null;
|
|
31
33
|
gclid: string | null;
|
|
32
34
|
fbclid: string | null;
|
|
33
35
|
utm_source: string | null;
|
|
@@ -67,6 +69,12 @@ declare global {
|
|
|
67
69
|
getTrackingParams: () => TrackingParams;
|
|
68
70
|
getConsentState: () => ConsentState;
|
|
69
71
|
setConsentState: (state: 'granted' | 'denied') => void;
|
|
72
|
+
trackEvent: (input: {
|
|
73
|
+
eventType: string;
|
|
74
|
+
pageUrl?: string | null;
|
|
75
|
+
metadata?: Record<string, unknown> | null;
|
|
76
|
+
occurredAt?: Date | string | null;
|
|
77
|
+
}) => void;
|
|
70
78
|
};
|
|
71
79
|
}
|
|
72
80
|
}
|
|
@@ -92,6 +100,7 @@ interface TrackingSessionInput {
|
|
|
92
100
|
consentState?: Record<string, unknown> | null;
|
|
93
101
|
firstPage?: string | null;
|
|
94
102
|
sessionId: string;
|
|
103
|
+
visitorId?: string | null;
|
|
95
104
|
}
|
|
96
105
|
declare function createTrackingClientContext(surface: TrackingInstallSurface, input?: TrackingContextInput): TrackingClientContext;
|
|
97
106
|
declare function createTrackingSessionUpsertPayload(trackingParams: TrackingParams, input: TrackingSessionInput, context: TrackingClientContext): TrackingSessionUpsertPayload;
|
|
@@ -100,8 +109,38 @@ declare function createTrackingEventCreatePayload(trackingParams: TrackingParams
|
|
|
100
109
|
declare const TRACKING_PARAM_KEYS: readonly ["gclid", "fbclid", "utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
|
|
101
110
|
declare function captureTrackingParamsFromLocation(url?: string, maxAgeSeconds?: number): TrackingParams;
|
|
102
111
|
|
|
112
|
+
interface TrackEventInput {
|
|
113
|
+
eventType: string;
|
|
114
|
+
pageUrl?: string | null;
|
|
115
|
+
metadata?: Record<string, unknown> | null;
|
|
116
|
+
occurredAt?: Date | string | null;
|
|
117
|
+
}
|
|
118
|
+
interface TrackingClient {
|
|
119
|
+
trackEvent: (input: TrackEventInput) => void;
|
|
120
|
+
flush: () => Promise<void>;
|
|
121
|
+
getSessionId: () => string;
|
|
122
|
+
getVisitorId: () => string;
|
|
123
|
+
destroy: () => void;
|
|
124
|
+
}
|
|
125
|
+
|
|
103
126
|
declare function useGclid(): string | null;
|
|
104
127
|
declare function useTrackingParams(): TrackingParams;
|
|
105
128
|
declare function useConsentState(): ConsentState;
|
|
106
129
|
|
|
107
|
-
|
|
130
|
+
interface TrackingProviderProps {
|
|
131
|
+
/** Public tracking API key issued for this business. Required. */
|
|
132
|
+
apiKey: string;
|
|
133
|
+
/** Full ingest endpoint base URL, e.g. https://api.aranova.io/tracking. Required. */
|
|
134
|
+
endpoint: string;
|
|
135
|
+
/** Optional Google Ads gtag id. If omitted, no gtag script is loaded. */
|
|
136
|
+
gtagId?: string;
|
|
137
|
+
/** Disable automatic page_view emission (manual tracking only). */
|
|
138
|
+
disableAutoPageView?: boolean;
|
|
139
|
+
/** Optional debug flag — currently a no-op, reserved for verbose logging. */
|
|
140
|
+
debug?: boolean;
|
|
141
|
+
children: ReactNode;
|
|
142
|
+
}
|
|
143
|
+
declare function TrackingProvider({ apiKey, endpoint, gtagId, disableAutoPageView, debug, children, }: TrackingProviderProps): react_jsx_runtime.JSX.Element;
|
|
144
|
+
declare function useTracking(): TrackingClient;
|
|
145
|
+
|
|
146
|
+
export { ConsentBanner, type ConsentState, GoogleAdsTracking, type GoogleAdsTrackingProps, TRACKING_PARAM_KEYS, type TrackingClient, type TrackingClientContext, type TrackingEventCreatePayload, type TrackingInitConfig, type TrackingInstallSurface, type TrackingParams, TrackingProvider, type TrackingProviderProps, type TrackingSessionUpsertPayload, captureTrackingParamsFromLocation, createTrackingClientContext, createTrackingEventCreatePayload, createTrackingSessionUpsertPayload, getConsentState, setConsentState, useConsentState, useGclid, useTracking, useTrackingParams };
|
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ __export(src_exports, {
|
|
|
23
23
|
ConsentBanner: () => ConsentBanner,
|
|
24
24
|
GoogleAdsTracking: () => GoogleAdsTracking,
|
|
25
25
|
TRACKING_PARAM_KEYS: () => TRACKING_PARAM_KEYS,
|
|
26
|
+
TrackingProvider: () => TrackingProvider,
|
|
26
27
|
captureTrackingParamsFromLocation: () => captureTrackingParamsFromLocation,
|
|
27
28
|
createTrackingClientContext: () => createTrackingClientContext,
|
|
28
29
|
createTrackingEventCreatePayload: () => createTrackingEventCreatePayload,
|
|
@@ -31,6 +32,7 @@ __export(src_exports, {
|
|
|
31
32
|
setConsentState: () => setConsentState,
|
|
32
33
|
useConsentState: () => useConsentState,
|
|
33
34
|
useGclid: () => useGclid,
|
|
35
|
+
useTracking: () => useTracking,
|
|
34
36
|
useTrackingParams: () => useTrackingParams
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -86,6 +88,7 @@ function createTrackingClientContext(surface, input = {}) {
|
|
|
86
88
|
function createTrackingSessionUpsertPayload(trackingParams, input, context) {
|
|
87
89
|
return {
|
|
88
90
|
session_id: input.sessionId,
|
|
91
|
+
visitor_id: input.visitorId ?? null,
|
|
89
92
|
gclid: trackingParams.gclid,
|
|
90
93
|
fbclid: trackingParams.fbclid,
|
|
91
94
|
utm_source: trackingParams.utm_source,
|
|
@@ -231,6 +234,282 @@ function captureTrackingParamsFromLocation(url = typeof window === "undefined" ?
|
|
|
231
234
|
return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
|
|
232
235
|
}
|
|
233
236
|
|
|
237
|
+
// ../tracking-core/src/session.ts
|
|
238
|
+
var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
|
|
239
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
240
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
241
|
+
function safeUuid() {
|
|
242
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
|
|
243
|
+
return crypto.randomUUID();
|
|
244
|
+
return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
|
|
245
|
+
}
|
|
246
|
+
function readLocalStorage(key) {
|
|
247
|
+
try {
|
|
248
|
+
return window.localStorage.getItem(key);
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function writeLocalStorage(key, value) {
|
|
254
|
+
try {
|
|
255
|
+
window.localStorage.setItem(key, value);
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function getVisitorId() {
|
|
260
|
+
if (typeof window === "undefined")
|
|
261
|
+
return safeUuid();
|
|
262
|
+
const existing = readLocalStorage(VISITOR_STORAGE_KEY);
|
|
263
|
+
if (existing && existing.length > 0)
|
|
264
|
+
return existing;
|
|
265
|
+
const fresh = safeUuid();
|
|
266
|
+
writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
|
|
267
|
+
return fresh;
|
|
268
|
+
}
|
|
269
|
+
function getOrRotateSessionId(now = Date.now()) {
|
|
270
|
+
if (typeof window === "undefined")
|
|
271
|
+
return safeUuid();
|
|
272
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
273
|
+
if (raw) {
|
|
274
|
+
try {
|
|
275
|
+
const parsed = JSON.parse(raw);
|
|
276
|
+
if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
|
|
277
|
+
if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
|
|
278
|
+
const refreshed = { id: parsed.id, last_event_at: now };
|
|
279
|
+
writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
|
|
280
|
+
return parsed.id;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const fresh = { id: safeUuid(), last_event_at: now };
|
|
287
|
+
writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
|
|
288
|
+
return fresh.id;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ../tracking-core/src/ingest.ts
|
|
292
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
|
|
293
|
+
var DEFAULT_MAX_QUEUE_SIZE = 10;
|
|
294
|
+
var HARD_MAX_BATCH = 50;
|
|
295
|
+
var API_KEY_HEADER = "X-Aranova-Api-Key";
|
|
296
|
+
function buildContext(surface, sdkVersion, packageName) {
|
|
297
|
+
return {
|
|
298
|
+
surface,
|
|
299
|
+
sdk_version: sdkVersion,
|
|
300
|
+
package_name: packageName,
|
|
301
|
+
site_origin: typeof window === "undefined" ? null : window.location.origin,
|
|
302
|
+
page_title: typeof document === "undefined" ? null : document.title || null,
|
|
303
|
+
referrer: typeof document === "undefined" ? null : document.referrer || null
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
function readTrackingParams() {
|
|
307
|
+
if (typeof window === "undefined")
|
|
308
|
+
return createEmptyTrackingParams();
|
|
309
|
+
try {
|
|
310
|
+
captureTrackingParamsFromLocation();
|
|
311
|
+
} catch {
|
|
312
|
+
}
|
|
313
|
+
return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
|
|
314
|
+
}
|
|
315
|
+
function consentSnapshot() {
|
|
316
|
+
try {
|
|
317
|
+
return { state: getConsentState() };
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function postWithFetch(url, body, apiKey, keepalive) {
|
|
323
|
+
if (typeof fetch !== "function")
|
|
324
|
+
return;
|
|
325
|
+
try {
|
|
326
|
+
await fetch(url, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers: {
|
|
329
|
+
"Content-Type": "application/json",
|
|
330
|
+
[API_KEY_HEADER]: apiKey
|
|
331
|
+
},
|
|
332
|
+
body,
|
|
333
|
+
keepalive,
|
|
334
|
+
// CORS is open on the tracking endpoint; never send cookies.
|
|
335
|
+
credentials: "omit",
|
|
336
|
+
mode: "cors"
|
|
337
|
+
});
|
|
338
|
+
} catch {
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function createTrackingClient(config) {
|
|
342
|
+
const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
343
|
+
const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);
|
|
344
|
+
const sdkVersion = config.sdkVersion ?? null;
|
|
345
|
+
const packageName = config.packageName ?? null;
|
|
346
|
+
const endpointBase = config.endpoint.replace(/\/$/, "");
|
|
347
|
+
const eventsUrl = `${endpointBase}/events`;
|
|
348
|
+
let queue = [];
|
|
349
|
+
let flushTimer = null;
|
|
350
|
+
let firstPage = null;
|
|
351
|
+
let destroyed = false;
|
|
352
|
+
const visitorId = getVisitorId();
|
|
353
|
+
let sessionId = getOrRotateSessionId();
|
|
354
|
+
if (typeof window !== "undefined")
|
|
355
|
+
firstPage = window.location.href;
|
|
356
|
+
function buildSessionPayload() {
|
|
357
|
+
sessionId = getOrRotateSessionId();
|
|
358
|
+
const params = readTrackingParams();
|
|
359
|
+
const context = buildContext(config.surface, sdkVersion, packageName);
|
|
360
|
+
return {
|
|
361
|
+
session_id: sessionId,
|
|
362
|
+
visitor_id: visitorId,
|
|
363
|
+
gclid: params.gclid,
|
|
364
|
+
fbclid: params.fbclid,
|
|
365
|
+
utm_source: params.utm_source,
|
|
366
|
+
utm_medium: params.utm_medium,
|
|
367
|
+
utm_campaign: params.utm_campaign,
|
|
368
|
+
utm_term: params.utm_term,
|
|
369
|
+
utm_content: params.utm_content,
|
|
370
|
+
first_page: firstPage,
|
|
371
|
+
consent_state: consentSnapshot(),
|
|
372
|
+
context
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function scheduleFlush() {
|
|
376
|
+
if (flushTimer !== null || destroyed)
|
|
377
|
+
return;
|
|
378
|
+
flushTimer = setTimeout(() => {
|
|
379
|
+
flushTimer = null;
|
|
380
|
+
void flush();
|
|
381
|
+
}, flushIntervalMs);
|
|
382
|
+
}
|
|
383
|
+
function clearScheduledFlush() {
|
|
384
|
+
if (flushTimer !== null) {
|
|
385
|
+
clearTimeout(flushTimer);
|
|
386
|
+
flushTimer = null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function flush() {
|
|
390
|
+
if (queue.length === 0)
|
|
391
|
+
return;
|
|
392
|
+
const events = queue.slice(0, HARD_MAX_BATCH);
|
|
393
|
+
queue = queue.slice(events.length);
|
|
394
|
+
clearScheduledFlush();
|
|
395
|
+
const body = {
|
|
396
|
+
session: buildSessionPayload(),
|
|
397
|
+
events
|
|
398
|
+
};
|
|
399
|
+
const serialized = JSON.stringify(body);
|
|
400
|
+
await postWithFetch(eventsUrl, serialized, config.apiKey, false);
|
|
401
|
+
}
|
|
402
|
+
function trackEvent(input) {
|
|
403
|
+
if (destroyed)
|
|
404
|
+
return;
|
|
405
|
+
if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
|
|
406
|
+
return;
|
|
407
|
+
const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
408
|
+
queue.push({
|
|
409
|
+
event_type: input.eventType,
|
|
410
|
+
page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
|
|
411
|
+
metadata: input.metadata ?? null,
|
|
412
|
+
occurred_at: occurredAt
|
|
413
|
+
});
|
|
414
|
+
if (queue.length >= maxQueueSize) {
|
|
415
|
+
void flush();
|
|
416
|
+
} else {
|
|
417
|
+
scheduleFlush();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function flushOnUnload() {
|
|
421
|
+
if (queue.length === 0)
|
|
422
|
+
return;
|
|
423
|
+
const events = queue.slice(0, HARD_MAX_BATCH);
|
|
424
|
+
queue = queue.slice(events.length);
|
|
425
|
+
clearScheduledFlush();
|
|
426
|
+
const body = {
|
|
427
|
+
session: buildSessionPayload(),
|
|
428
|
+
events
|
|
429
|
+
};
|
|
430
|
+
const serialized = JSON.stringify(body);
|
|
431
|
+
void postWithFetch(eventsUrl, serialized, config.apiKey, true);
|
|
432
|
+
}
|
|
433
|
+
if (typeof window !== "undefined") {
|
|
434
|
+
window.addEventListener("pagehide", flushOnUnload);
|
|
435
|
+
window.addEventListener("visibilitychange", () => {
|
|
436
|
+
if (document.visibilityState === "hidden") flushOnUnload();
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
return {
|
|
440
|
+
trackEvent,
|
|
441
|
+
flush,
|
|
442
|
+
getSessionId: () => sessionId,
|
|
443
|
+
getVisitorId: () => visitorId,
|
|
444
|
+
destroy: () => {
|
|
445
|
+
destroyed = true;
|
|
446
|
+
clearScheduledFlush();
|
|
447
|
+
queue = [];
|
|
448
|
+
if (typeof window !== "undefined") {
|
|
449
|
+
window.removeEventListener("pagehide", flushOnUnload);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ../tracking-core/src/page-view.ts
|
|
456
|
+
function buildPageViewMetadata() {
|
|
457
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
458
|
+
return null;
|
|
459
|
+
return {
|
|
460
|
+
page: {
|
|
461
|
+
title: document.title || null,
|
|
462
|
+
path: window.location.pathname,
|
|
463
|
+
search: window.location.search,
|
|
464
|
+
hash: window.location.hash
|
|
465
|
+
},
|
|
466
|
+
referrer: document.referrer || null,
|
|
467
|
+
viewport: { w: window.innerWidth, h: window.innerHeight }
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function fireManualPageView(client) {
|
|
471
|
+
const metadata = buildPageViewMetadata();
|
|
472
|
+
client.trackEvent({
|
|
473
|
+
eventType: "page_view",
|
|
474
|
+
pageUrl: typeof window === "undefined" ? null : window.location.href,
|
|
475
|
+
metadata
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
function attachAutoPageView(client, options = {}) {
|
|
479
|
+
if (typeof window === "undefined" || typeof history === "undefined") {
|
|
480
|
+
return () => {
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
let lastPath = window.location.pathname + window.location.search;
|
|
484
|
+
function maybeFire() {
|
|
485
|
+
const current = window.location.pathname + window.location.search;
|
|
486
|
+
if (current === lastPath)
|
|
487
|
+
return;
|
|
488
|
+
lastPath = current;
|
|
489
|
+
fireManualPageView(client);
|
|
490
|
+
}
|
|
491
|
+
const originalPushState = history.pushState.bind(history);
|
|
492
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
493
|
+
function patchedPushState(...args) {
|
|
494
|
+
originalPushState(...args);
|
|
495
|
+
setTimeout(maybeFire, 0);
|
|
496
|
+
}
|
|
497
|
+
function patchedReplaceState(...args) {
|
|
498
|
+
originalReplaceState(...args);
|
|
499
|
+
setTimeout(maybeFire, 0);
|
|
500
|
+
}
|
|
501
|
+
history.pushState = patchedPushState;
|
|
502
|
+
history.replaceState = patchedReplaceState;
|
|
503
|
+
window.addEventListener("popstate", maybeFire);
|
|
504
|
+
if (!options.skipInitial)
|
|
505
|
+
fireManualPageView(client);
|
|
506
|
+
return () => {
|
|
507
|
+
history.pushState = originalPushState;
|
|
508
|
+
history.replaceState = originalReplaceState;
|
|
509
|
+
window.removeEventListener("popstate", maybeFire);
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
234
513
|
// src/ConsentBanner.tsx
|
|
235
514
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
236
515
|
function ConsentBanner() {
|
|
@@ -315,11 +594,68 @@ function useConsentState() {
|
|
|
315
594
|
}, []);
|
|
316
595
|
return consentState;
|
|
317
596
|
}
|
|
597
|
+
|
|
598
|
+
// src/TrackingProvider.tsx
|
|
599
|
+
var import_react4 = require("react");
|
|
600
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
601
|
+
var TrackingContext = (0, import_react4.createContext)(null);
|
|
602
|
+
function TrackingProvider({
|
|
603
|
+
apiKey,
|
|
604
|
+
endpoint,
|
|
605
|
+
gtagId,
|
|
606
|
+
disableAutoPageView,
|
|
607
|
+
debug,
|
|
608
|
+
children
|
|
609
|
+
}) {
|
|
610
|
+
if (!apiKey)
|
|
611
|
+
throw new Error("TrackingProvider: apiKey is required");
|
|
612
|
+
if (!endpoint)
|
|
613
|
+
throw new Error("TrackingProvider: endpoint is required");
|
|
614
|
+
const clientRef = (0, import_react4.useRef)(null);
|
|
615
|
+
if (clientRef.current === null) {
|
|
616
|
+
clientRef.current = createTrackingClient({
|
|
617
|
+
apiKey,
|
|
618
|
+
endpoint,
|
|
619
|
+
surface: "react",
|
|
620
|
+
packageName: "@aranova/tracking-react",
|
|
621
|
+
debug
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
(0, import_react4.useEffect)(() => {
|
|
625
|
+
if (!gtagId)
|
|
626
|
+
return;
|
|
627
|
+
bootstrapGoogleAdsTracking(gtagId);
|
|
628
|
+
}, [gtagId]);
|
|
629
|
+
(0, import_react4.useEffect)(() => {
|
|
630
|
+
const client = clientRef.current;
|
|
631
|
+
if (client === null || disableAutoPageView)
|
|
632
|
+
return;
|
|
633
|
+
const detach = attachAutoPageView(client);
|
|
634
|
+
return () => {
|
|
635
|
+
detach();
|
|
636
|
+
};
|
|
637
|
+
}, [disableAutoPageView]);
|
|
638
|
+
(0, import_react4.useEffect)(() => {
|
|
639
|
+
return () => {
|
|
640
|
+
clientRef.current?.destroy();
|
|
641
|
+
clientRef.current = null;
|
|
642
|
+
};
|
|
643
|
+
}, []);
|
|
644
|
+
const contextValue = (0, import_react4.useMemo)(() => clientRef.current, []);
|
|
645
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrackingContext.Provider, { value: contextValue, children });
|
|
646
|
+
}
|
|
647
|
+
function useTracking() {
|
|
648
|
+
const client = (0, import_react4.useContext)(TrackingContext);
|
|
649
|
+
if (client === null)
|
|
650
|
+
throw new Error("useTracking must be called inside a <TrackingProvider>");
|
|
651
|
+
return client;
|
|
652
|
+
}
|
|
318
653
|
// Annotate the CommonJS export names for ESM import in node:
|
|
319
654
|
0 && (module.exports = {
|
|
320
655
|
ConsentBanner,
|
|
321
656
|
GoogleAdsTracking,
|
|
322
657
|
TRACKING_PARAM_KEYS,
|
|
658
|
+
TrackingProvider,
|
|
323
659
|
captureTrackingParamsFromLocation,
|
|
324
660
|
createTrackingClientContext,
|
|
325
661
|
createTrackingEventCreatePayload,
|
|
@@ -328,6 +664,7 @@ function useConsentState() {
|
|
|
328
664
|
setConsentState,
|
|
329
665
|
useConsentState,
|
|
330
666
|
useGclid,
|
|
667
|
+
useTracking,
|
|
331
668
|
useTrackingParams
|
|
332
669
|
});
|
|
333
670
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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":["// Public entry for @aranova/tracking-react — see docs/tracking-package/install-modes.md\nexport { 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"]}
|
|
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","../../tracking-core/src/session.ts","../../tracking-core/src/ingest.ts","../../tracking-core/src/page-view.ts","../src/GoogleAdsTracking.tsx","../src/hooks.ts","../src/TrackingProvider.tsx"],"sourcesContent":["// Public entry for @aranova/tracking-react — see docs/tracking-package/install-modes.md\nexport { ConsentBanner } from './ConsentBanner';\nexport { GoogleAdsTracking, type GoogleAdsTrackingProps } from './GoogleAdsTracking';\nexport { useConsentState, useGclid, useTrackingParams } from './hooks';\nexport { TrackingProvider, useTracking, type TrackingProviderProps } from './TrackingProvider';\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 TrackingClient,\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 visitorId?: string | null;\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 visitor_id: input.visitorId ?? null,\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","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const raw = readLocalStorage(SESSION_STORAGE_KEY);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredSession>;\n if (typeof parsed.id === 'string' && typeof parsed.last_event_at === 'number') {\n if (now - parsed.last_event_at <= SESSION_IDLE_MS) {\n const refreshed: StoredSession = { id: parsed.id, last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));\n return parsed.id;\n }\n }\n } catch {\n // Fall through to a fresh session.\n }\n }\n\n const fresh: StoredSession = { id: safeUuid(), last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n return fresh.id;\n}\n\n/** Test/debug helper — wipes both ids. */\nexport function resetTrackingIdentity(): void {\n if (typeof window === 'undefined')\n return;\n try {\n window.localStorage.removeItem(VISITOR_STORAGE_KEY);\n window.localStorage.removeItem(SESSION_STORAGE_KEY);\n } catch {\n // ignored\n }\n}\n","// Tracking ingest client. Queues events, debounce-flushes them to the\n// /tracking/events endpoint, and degrades silently on errors so a broken\n// network never breaks the host site.\n\nimport { getConsentState } from './consent';\nimport {\n captureTrackingParamsFromLocation,\n createEmptyTrackingParams,\n getCookieValueFromDocument,\n getTrackingParamsFromCookieReader,\n} from './tracking';\nimport { getOrRotateSessionId, getVisitorId } from './session';\nimport type {\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from './types';\n\nexport const DEFAULT_FLUSH_INTERVAL_MS = 2000;\nexport const DEFAULT_MAX_QUEUE_SIZE = 10;\nexport const HARD_MAX_BATCH = 50;\nexport const API_KEY_HEADER = 'X-Aranova-Api-Key';\n\nexport interface TrackingClientConfig {\n apiKey: string;\n endpoint: string;\n surface: TrackingInstallSurface;\n sdkVersion?: string;\n packageName?: string;\n /** Override the default 2s debounce window. */\n flushIntervalMs?: number;\n /** Override the default 10-event batch trigger. */\n maxQueueSize?: number;\n /** When true, swallow nothing — useful for tests. */\n debug?: boolean;\n}\n\nexport interface TrackEventInput {\n eventType: string;\n pageUrl?: string | null;\n metadata?: Record<string, unknown> | null;\n occurredAt?: Date | string | null;\n}\n\ninterface QueuedEvent {\n event_type: string;\n page_url: string | null;\n metadata: Record<string, unknown> | null;\n occurred_at: string | null;\n}\n\nexport interface TrackingClient {\n trackEvent: (input: TrackEventInput) => void;\n flush: () => Promise<void>;\n getSessionId: () => string;\n getVisitorId: () => string;\n destroy: () => void;\n}\n\ninterface IngestRequestBody {\n session: TrackingSessionUpsertPayload;\n events: Array<{\n event_type: string;\n page_url: string | null;\n metadata: Record<string, unknown> | null;\n occurred_at: string | null;\n }>;\n}\n\nfunction buildContext(\n surface: TrackingInstallSurface,\n sdkVersion: string | null,\n packageName: string | null,\n): TrackingClientContext {\n return {\n surface,\n sdk_version: sdkVersion,\n package_name: packageName,\n site_origin: typeof window === 'undefined' ? null : window.location.origin,\n page_title: typeof document === 'undefined' ? null : document.title || null,\n referrer: typeof document === 'undefined' ? null : document.referrer || null,\n };\n}\n\nfunction readTrackingParams(): TrackingParams {\n if (typeof window === 'undefined')\n return createEmptyTrackingParams();\n // Capture from URL on every read so the first event in a session reflects the\n // landing-page params even if the cookie helper hasn't run yet.\n try {\n captureTrackingParamsFromLocation();\n } catch {\n // ignore\n }\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n\nfunction consentSnapshot(): Record<string, unknown> | null {\n try {\n return { state: getConsentState() };\n } catch {\n return null;\n }\n}\n\nasync function postWithFetch(url: string, body: string, apiKey: string, keepalive: boolean): Promise<void> {\n if (typeof fetch !== 'function')\n return;\n try {\n await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [API_KEY_HEADER]: apiKey,\n },\n body,\n keepalive,\n // CORS is open on the tracking endpoint; never send cookies.\n credentials: 'omit',\n mode: 'cors',\n });\n } catch {\n // fire-and-forget — never throw to the host site\n }\n}\n\nexport function createTrackingClient(config: TrackingClientConfig): TrackingClient {\n const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);\n const sdkVersion = config.sdkVersion ?? null;\n const packageName = config.packageName ?? null;\n const endpointBase = config.endpoint.replace(/\\/$/, '');\n const eventsUrl = `${endpointBase}/events`;\n\n let queue: QueuedEvent[] = [];\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n let firstPage: string | null = null;\n let destroyed = false;\n\n // Initialize identity early so the first POST has stable values.\n const visitorId = getVisitorId();\n let sessionId = getOrRotateSessionId();\n\n if (typeof window !== 'undefined')\n firstPage = window.location.href;\n\n function buildSessionPayload(): TrackingSessionUpsertPayload {\n sessionId = getOrRotateSessionId();\n const params = readTrackingParams();\n const context = buildContext(config.surface, sdkVersion, packageName);\n return {\n session_id: sessionId,\n visitor_id: visitorId,\n gclid: params.gclid,\n fbclid: params.fbclid,\n utm_source: params.utm_source,\n utm_medium: params.utm_medium,\n utm_campaign: params.utm_campaign,\n utm_term: params.utm_term,\n utm_content: params.utm_content,\n first_page: firstPage,\n consent_state: consentSnapshot(),\n context,\n };\n }\n\n function scheduleFlush(): void {\n if (flushTimer !== null || destroyed)\n return;\n flushTimer = setTimeout(() => {\n flushTimer = null;\n void flush();\n }, flushIntervalMs);\n }\n\n function clearScheduledFlush(): void {\n if (flushTimer !== null) {\n clearTimeout(flushTimer);\n flushTimer = null;\n }\n }\n\n async function flush(): Promise<void> {\n if (queue.length === 0)\n return;\n\n const events = queue.slice(0, HARD_MAX_BATCH);\n queue = queue.slice(events.length);\n clearScheduledFlush();\n\n const body: IngestRequestBody = {\n session: buildSessionPayload(),\n events,\n };\n\n const serialized = JSON.stringify(body);\n\n // Prefer fetch + keepalive for better error visibility. sendBeacon is the\n // pagehide fallback path because it survives navigation away.\n await postWithFetch(eventsUrl, serialized, config.apiKey, false);\n }\n\n function trackEvent(input: TrackEventInput): void {\n if (destroyed)\n return;\n if (!input || typeof input.eventType !== 'string' || input.eventType.length === 0)\n return;\n\n const occurredAt =\n input.occurredAt instanceof Date\n ? input.occurredAt.toISOString()\n : typeof input.occurredAt === 'string'\n ? input.occurredAt\n : new Date().toISOString();\n\n queue.push({\n event_type: input.eventType,\n page_url:\n input.pageUrl ?? (typeof window === 'undefined' ? null : window.location.href),\n metadata: input.metadata ?? null,\n occurred_at: occurredAt,\n });\n\n if (queue.length >= maxQueueSize) {\n void flush();\n } else {\n scheduleFlush();\n }\n }\n\n function flushOnUnload(): void {\n if (queue.length === 0)\n return;\n const events = queue.slice(0, HARD_MAX_BATCH);\n queue = queue.slice(events.length);\n clearScheduledFlush();\n\n const body: IngestRequestBody = {\n session: buildSessionPayload(),\n events,\n };\n const serialized = JSON.stringify(body);\n void postWithFetch(eventsUrl, serialized, config.apiKey, true);\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', flushOnUnload);\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') flushOnUnload();\n });\n }\n\n return {\n trackEvent,\n flush,\n getSessionId: () => sessionId,\n getVisitorId: () => visitorId,\n destroy: () => {\n destroyed = true;\n clearScheduledFlush();\n queue = [];\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', flushOnUnload);\n }\n },\n };\n}\n","// Auto page_view emission. Fires once on attach, then again on every SPA\n// navigation. Detects navigation via three signals:\n// - history.pushState (patched)\n// - history.replaceState (patched)\n// - popstate event (back/forward buttons)\n//\n// The Next.js provider can opt out of this and use `usePathname` instead so\n// it picks up App Router transitions reliably without monkey-patching globals.\n\nimport type { TrackingClient } from './ingest';\n\nexport interface PageViewMetadata {\n page: {\n title: string | null;\n path: string;\n search: string;\n hash: string;\n };\n referrer: string | null;\n viewport?: { w: number; h: number };\n}\n\nexport function buildPageViewMetadata(): PageViewMetadata | null {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return null;\n return {\n page: {\n title: document.title || null,\n path: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n },\n referrer: document.referrer || null,\n viewport: { w: window.innerWidth, h: window.innerHeight },\n };\n}\n\nexport function fireManualPageView(client: TrackingClient): void {\n const metadata = buildPageViewMetadata();\n client.trackEvent({\n eventType: 'page_view',\n pageUrl: typeof window === 'undefined' ? null : window.location.href,\n metadata: metadata as unknown as Record<string, unknown> | null,\n });\n}\n\nexport interface AttachAutoPageViewOptions {\n /** Skip the initial fire on attach (useful when the host already fired one). */\n skipInitial?: boolean;\n}\n\nexport function attachAutoPageView(\n client: TrackingClient,\n options: AttachAutoPageViewOptions = {},\n): () => void {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return () => {};\n }\n\n let lastPath = window.location.pathname + window.location.search;\n\n function maybeFire(): void {\n const current = window.location.pathname + window.location.search;\n if (current === lastPath)\n return;\n lastPath = current;\n fireManualPageView(client);\n }\n\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n function patchedPushState(this: History, ...args: Parameters<History['pushState']>): void {\n originalPushState(...args);\n // Defer one tick so the URL is settled before we read it.\n setTimeout(maybeFire, 0);\n }\n function patchedReplaceState(this: History, ...args: Parameters<History['replaceState']>): void {\n originalReplaceState(...args);\n setTimeout(maybeFire, 0);\n }\n\n history.pushState = patchedPushState as History['pushState'];\n history.replaceState = patchedReplaceState as History['replaceState'];\n window.addEventListener('popstate', maybeFire);\n\n if (!options.skipInitial)\n fireManualPageView(client);\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n window.removeEventListener('popstate', maybeFire);\n };\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","'use client';\n\nimport { createContext, useContext, useEffect, useMemo, useRef, type ReactNode } from 'react';\n\nimport {\n attachAutoPageView,\n bootstrapGoogleAdsTracking,\n createTrackingClient,\n type TrackingClient,\n} from '../../tracking-core/src/index';\n\nexport interface TrackingProviderProps {\n /** Public tracking API key issued for this business. Required. */\n apiKey: string;\n /** Full ingest endpoint base URL, e.g. https://api.aranova.io/tracking. Required. */\n endpoint: string;\n /** Optional Google Ads gtag id. If omitted, no gtag script is loaded. */\n gtagId?: string;\n /** Disable automatic page_view emission (manual tracking only). */\n disableAutoPageView?: boolean;\n /** Optional debug flag — currently a no-op, reserved for verbose logging. */\n debug?: boolean;\n children: ReactNode;\n}\n\nconst TrackingContext = createContext<TrackingClient | null>(null);\n\nexport function TrackingProvider({\n apiKey,\n endpoint,\n gtagId,\n disableAutoPageView,\n debug,\n children,\n}: TrackingProviderProps) {\n if (!apiKey)\n throw new Error('TrackingProvider: apiKey is required');\n if (!endpoint)\n throw new Error('TrackingProvider: endpoint is required');\n\n const clientRef = useRef<TrackingClient | null>(null);\n if (clientRef.current === null) {\n clientRef.current = createTrackingClient({\n apiKey,\n endpoint,\n surface: 'react',\n packageName: '@aranova/tracking-react',\n debug,\n });\n }\n\n useEffect(() => {\n if (!gtagId)\n return;\n bootstrapGoogleAdsTracking(gtagId);\n }, [gtagId]);\n\n useEffect(() => {\n const client = clientRef.current;\n if (client === null || disableAutoPageView)\n return;\n const detach = attachAutoPageView(client);\n return () => {\n detach();\n };\n }, [disableAutoPageView]);\n\n useEffect(() => {\n return () => {\n clientRef.current?.destroy();\n clientRef.current = null;\n };\n }, []);\n\n const contextValue = useMemo(() => clientRef.current, []);\n\n return (\n <TrackingContext.Provider value={contextValue}>\n {children}\n </TrackingContext.Provider>\n );\n}\n\nexport function useTracking(): TrackingClient {\n const client = useContext(TrackingContext);\n if (client === null)\n throw new Error('useTracking must be called inside a <TrackingProvider>');\n return client;\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_jsx_runtime"]}
|
package/dist/index.mjs
CHANGED
|
@@ -49,6 +49,7 @@ function createTrackingClientContext(surface, input = {}) {
|
|
|
49
49
|
function createTrackingSessionUpsertPayload(trackingParams, input, context) {
|
|
50
50
|
return {
|
|
51
51
|
session_id: input.sessionId,
|
|
52
|
+
visitor_id: input.visitorId ?? null,
|
|
52
53
|
gclid: trackingParams.gclid,
|
|
53
54
|
fbclid: trackingParams.fbclid,
|
|
54
55
|
utm_source: trackingParams.utm_source,
|
|
@@ -194,6 +195,282 @@ function captureTrackingParamsFromLocation(url = typeof window === "undefined" ?
|
|
|
194
195
|
return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
|
|
195
196
|
}
|
|
196
197
|
|
|
198
|
+
// ../tracking-core/src/session.ts
|
|
199
|
+
var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
|
|
200
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
201
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
202
|
+
function safeUuid() {
|
|
203
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
|
|
204
|
+
return crypto.randomUUID();
|
|
205
|
+
return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
|
|
206
|
+
}
|
|
207
|
+
function readLocalStorage(key) {
|
|
208
|
+
try {
|
|
209
|
+
return window.localStorage.getItem(key);
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function writeLocalStorage(key, value) {
|
|
215
|
+
try {
|
|
216
|
+
window.localStorage.setItem(key, value);
|
|
217
|
+
} catch {
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function getVisitorId() {
|
|
221
|
+
if (typeof window === "undefined")
|
|
222
|
+
return safeUuid();
|
|
223
|
+
const existing = readLocalStorage(VISITOR_STORAGE_KEY);
|
|
224
|
+
if (existing && existing.length > 0)
|
|
225
|
+
return existing;
|
|
226
|
+
const fresh = safeUuid();
|
|
227
|
+
writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
|
|
228
|
+
return fresh;
|
|
229
|
+
}
|
|
230
|
+
function getOrRotateSessionId(now = Date.now()) {
|
|
231
|
+
if (typeof window === "undefined")
|
|
232
|
+
return safeUuid();
|
|
233
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
234
|
+
if (raw) {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(raw);
|
|
237
|
+
if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
|
|
238
|
+
if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
|
|
239
|
+
const refreshed = { id: parsed.id, last_event_at: now };
|
|
240
|
+
writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
|
|
241
|
+
return parsed.id;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const fresh = { id: safeUuid(), last_event_at: now };
|
|
248
|
+
writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
|
|
249
|
+
return fresh.id;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ../tracking-core/src/ingest.ts
|
|
253
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
|
|
254
|
+
var DEFAULT_MAX_QUEUE_SIZE = 10;
|
|
255
|
+
var HARD_MAX_BATCH = 50;
|
|
256
|
+
var API_KEY_HEADER = "X-Aranova-Api-Key";
|
|
257
|
+
function buildContext(surface, sdkVersion, packageName) {
|
|
258
|
+
return {
|
|
259
|
+
surface,
|
|
260
|
+
sdk_version: sdkVersion,
|
|
261
|
+
package_name: packageName,
|
|
262
|
+
site_origin: typeof window === "undefined" ? null : window.location.origin,
|
|
263
|
+
page_title: typeof document === "undefined" ? null : document.title || null,
|
|
264
|
+
referrer: typeof document === "undefined" ? null : document.referrer || null
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function readTrackingParams() {
|
|
268
|
+
if (typeof window === "undefined")
|
|
269
|
+
return createEmptyTrackingParams();
|
|
270
|
+
try {
|
|
271
|
+
captureTrackingParamsFromLocation();
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
|
|
275
|
+
}
|
|
276
|
+
function consentSnapshot() {
|
|
277
|
+
try {
|
|
278
|
+
return { state: getConsentState() };
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function postWithFetch(url, body, apiKey, keepalive) {
|
|
284
|
+
if (typeof fetch !== "function")
|
|
285
|
+
return;
|
|
286
|
+
try {
|
|
287
|
+
await fetch(url, {
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: {
|
|
290
|
+
"Content-Type": "application/json",
|
|
291
|
+
[API_KEY_HEADER]: apiKey
|
|
292
|
+
},
|
|
293
|
+
body,
|
|
294
|
+
keepalive,
|
|
295
|
+
// CORS is open on the tracking endpoint; never send cookies.
|
|
296
|
+
credentials: "omit",
|
|
297
|
+
mode: "cors"
|
|
298
|
+
});
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function createTrackingClient(config) {
|
|
303
|
+
const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
304
|
+
const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);
|
|
305
|
+
const sdkVersion = config.sdkVersion ?? null;
|
|
306
|
+
const packageName = config.packageName ?? null;
|
|
307
|
+
const endpointBase = config.endpoint.replace(/\/$/, "");
|
|
308
|
+
const eventsUrl = `${endpointBase}/events`;
|
|
309
|
+
let queue = [];
|
|
310
|
+
let flushTimer = null;
|
|
311
|
+
let firstPage = null;
|
|
312
|
+
let destroyed = false;
|
|
313
|
+
const visitorId = getVisitorId();
|
|
314
|
+
let sessionId = getOrRotateSessionId();
|
|
315
|
+
if (typeof window !== "undefined")
|
|
316
|
+
firstPage = window.location.href;
|
|
317
|
+
function buildSessionPayload() {
|
|
318
|
+
sessionId = getOrRotateSessionId();
|
|
319
|
+
const params = readTrackingParams();
|
|
320
|
+
const context = buildContext(config.surface, sdkVersion, packageName);
|
|
321
|
+
return {
|
|
322
|
+
session_id: sessionId,
|
|
323
|
+
visitor_id: visitorId,
|
|
324
|
+
gclid: params.gclid,
|
|
325
|
+
fbclid: params.fbclid,
|
|
326
|
+
utm_source: params.utm_source,
|
|
327
|
+
utm_medium: params.utm_medium,
|
|
328
|
+
utm_campaign: params.utm_campaign,
|
|
329
|
+
utm_term: params.utm_term,
|
|
330
|
+
utm_content: params.utm_content,
|
|
331
|
+
first_page: firstPage,
|
|
332
|
+
consent_state: consentSnapshot(),
|
|
333
|
+
context
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function scheduleFlush() {
|
|
337
|
+
if (flushTimer !== null || destroyed)
|
|
338
|
+
return;
|
|
339
|
+
flushTimer = setTimeout(() => {
|
|
340
|
+
flushTimer = null;
|
|
341
|
+
void flush();
|
|
342
|
+
}, flushIntervalMs);
|
|
343
|
+
}
|
|
344
|
+
function clearScheduledFlush() {
|
|
345
|
+
if (flushTimer !== null) {
|
|
346
|
+
clearTimeout(flushTimer);
|
|
347
|
+
flushTimer = null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function flush() {
|
|
351
|
+
if (queue.length === 0)
|
|
352
|
+
return;
|
|
353
|
+
const events = queue.slice(0, HARD_MAX_BATCH);
|
|
354
|
+
queue = queue.slice(events.length);
|
|
355
|
+
clearScheduledFlush();
|
|
356
|
+
const body = {
|
|
357
|
+
session: buildSessionPayload(),
|
|
358
|
+
events
|
|
359
|
+
};
|
|
360
|
+
const serialized = JSON.stringify(body);
|
|
361
|
+
await postWithFetch(eventsUrl, serialized, config.apiKey, false);
|
|
362
|
+
}
|
|
363
|
+
function trackEvent(input) {
|
|
364
|
+
if (destroyed)
|
|
365
|
+
return;
|
|
366
|
+
if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
|
|
367
|
+
return;
|
|
368
|
+
const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
369
|
+
queue.push({
|
|
370
|
+
event_type: input.eventType,
|
|
371
|
+
page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
|
|
372
|
+
metadata: input.metadata ?? null,
|
|
373
|
+
occurred_at: occurredAt
|
|
374
|
+
});
|
|
375
|
+
if (queue.length >= maxQueueSize) {
|
|
376
|
+
void flush();
|
|
377
|
+
} else {
|
|
378
|
+
scheduleFlush();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function flushOnUnload() {
|
|
382
|
+
if (queue.length === 0)
|
|
383
|
+
return;
|
|
384
|
+
const events = queue.slice(0, HARD_MAX_BATCH);
|
|
385
|
+
queue = queue.slice(events.length);
|
|
386
|
+
clearScheduledFlush();
|
|
387
|
+
const body = {
|
|
388
|
+
session: buildSessionPayload(),
|
|
389
|
+
events
|
|
390
|
+
};
|
|
391
|
+
const serialized = JSON.stringify(body);
|
|
392
|
+
void postWithFetch(eventsUrl, serialized, config.apiKey, true);
|
|
393
|
+
}
|
|
394
|
+
if (typeof window !== "undefined") {
|
|
395
|
+
window.addEventListener("pagehide", flushOnUnload);
|
|
396
|
+
window.addEventListener("visibilitychange", () => {
|
|
397
|
+
if (document.visibilityState === "hidden") flushOnUnload();
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
trackEvent,
|
|
402
|
+
flush,
|
|
403
|
+
getSessionId: () => sessionId,
|
|
404
|
+
getVisitorId: () => visitorId,
|
|
405
|
+
destroy: () => {
|
|
406
|
+
destroyed = true;
|
|
407
|
+
clearScheduledFlush();
|
|
408
|
+
queue = [];
|
|
409
|
+
if (typeof window !== "undefined") {
|
|
410
|
+
window.removeEventListener("pagehide", flushOnUnload);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ../tracking-core/src/page-view.ts
|
|
417
|
+
function buildPageViewMetadata() {
|
|
418
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
419
|
+
return null;
|
|
420
|
+
return {
|
|
421
|
+
page: {
|
|
422
|
+
title: document.title || null,
|
|
423
|
+
path: window.location.pathname,
|
|
424
|
+
search: window.location.search,
|
|
425
|
+
hash: window.location.hash
|
|
426
|
+
},
|
|
427
|
+
referrer: document.referrer || null,
|
|
428
|
+
viewport: { w: window.innerWidth, h: window.innerHeight }
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
function fireManualPageView(client) {
|
|
432
|
+
const metadata = buildPageViewMetadata();
|
|
433
|
+
client.trackEvent({
|
|
434
|
+
eventType: "page_view",
|
|
435
|
+
pageUrl: typeof window === "undefined" ? null : window.location.href,
|
|
436
|
+
metadata
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function attachAutoPageView(client, options = {}) {
|
|
440
|
+
if (typeof window === "undefined" || typeof history === "undefined") {
|
|
441
|
+
return () => {
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
let lastPath = window.location.pathname + window.location.search;
|
|
445
|
+
function maybeFire() {
|
|
446
|
+
const current = window.location.pathname + window.location.search;
|
|
447
|
+
if (current === lastPath)
|
|
448
|
+
return;
|
|
449
|
+
lastPath = current;
|
|
450
|
+
fireManualPageView(client);
|
|
451
|
+
}
|
|
452
|
+
const originalPushState = history.pushState.bind(history);
|
|
453
|
+
const originalReplaceState = history.replaceState.bind(history);
|
|
454
|
+
function patchedPushState(...args) {
|
|
455
|
+
originalPushState(...args);
|
|
456
|
+
setTimeout(maybeFire, 0);
|
|
457
|
+
}
|
|
458
|
+
function patchedReplaceState(...args) {
|
|
459
|
+
originalReplaceState(...args);
|
|
460
|
+
setTimeout(maybeFire, 0);
|
|
461
|
+
}
|
|
462
|
+
history.pushState = patchedPushState;
|
|
463
|
+
history.replaceState = patchedReplaceState;
|
|
464
|
+
window.addEventListener("popstate", maybeFire);
|
|
465
|
+
if (!options.skipInitial)
|
|
466
|
+
fireManualPageView(client);
|
|
467
|
+
return () => {
|
|
468
|
+
history.pushState = originalPushState;
|
|
469
|
+
history.replaceState = originalReplaceState;
|
|
470
|
+
window.removeEventListener("popstate", maybeFire);
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
197
474
|
// src/ConsentBanner.tsx
|
|
198
475
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
199
476
|
function ConsentBanner() {
|
|
@@ -278,10 +555,67 @@ function useConsentState() {
|
|
|
278
555
|
}, []);
|
|
279
556
|
return consentState;
|
|
280
557
|
}
|
|
558
|
+
|
|
559
|
+
// src/TrackingProvider.tsx
|
|
560
|
+
import { createContext, useContext, useEffect as useEffect4, useMemo, useRef } from "react";
|
|
561
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
562
|
+
var TrackingContext = createContext(null);
|
|
563
|
+
function TrackingProvider({
|
|
564
|
+
apiKey,
|
|
565
|
+
endpoint,
|
|
566
|
+
gtagId,
|
|
567
|
+
disableAutoPageView,
|
|
568
|
+
debug,
|
|
569
|
+
children
|
|
570
|
+
}) {
|
|
571
|
+
if (!apiKey)
|
|
572
|
+
throw new Error("TrackingProvider: apiKey is required");
|
|
573
|
+
if (!endpoint)
|
|
574
|
+
throw new Error("TrackingProvider: endpoint is required");
|
|
575
|
+
const clientRef = useRef(null);
|
|
576
|
+
if (clientRef.current === null) {
|
|
577
|
+
clientRef.current = createTrackingClient({
|
|
578
|
+
apiKey,
|
|
579
|
+
endpoint,
|
|
580
|
+
surface: "react",
|
|
581
|
+
packageName: "@aranova/tracking-react",
|
|
582
|
+
debug
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
useEffect4(() => {
|
|
586
|
+
if (!gtagId)
|
|
587
|
+
return;
|
|
588
|
+
bootstrapGoogleAdsTracking(gtagId);
|
|
589
|
+
}, [gtagId]);
|
|
590
|
+
useEffect4(() => {
|
|
591
|
+
const client = clientRef.current;
|
|
592
|
+
if (client === null || disableAutoPageView)
|
|
593
|
+
return;
|
|
594
|
+
const detach = attachAutoPageView(client);
|
|
595
|
+
return () => {
|
|
596
|
+
detach();
|
|
597
|
+
};
|
|
598
|
+
}, [disableAutoPageView]);
|
|
599
|
+
useEffect4(() => {
|
|
600
|
+
return () => {
|
|
601
|
+
clientRef.current?.destroy();
|
|
602
|
+
clientRef.current = null;
|
|
603
|
+
};
|
|
604
|
+
}, []);
|
|
605
|
+
const contextValue = useMemo(() => clientRef.current, []);
|
|
606
|
+
return /* @__PURE__ */ jsx2(TrackingContext.Provider, { value: contextValue, children });
|
|
607
|
+
}
|
|
608
|
+
function useTracking() {
|
|
609
|
+
const client = useContext(TrackingContext);
|
|
610
|
+
if (client === null)
|
|
611
|
+
throw new Error("useTracking must be called inside a <TrackingProvider>");
|
|
612
|
+
return client;
|
|
613
|
+
}
|
|
281
614
|
export {
|
|
282
615
|
ConsentBanner,
|
|
283
616
|
GoogleAdsTracking,
|
|
284
617
|
TRACKING_PARAM_KEYS,
|
|
618
|
+
TrackingProvider,
|
|
285
619
|
captureTrackingParamsFromLocation,
|
|
286
620
|
createTrackingClientContext,
|
|
287
621
|
createTrackingEventCreatePayload,
|
|
@@ -290,6 +624,7 @@ export {
|
|
|
290
624
|
setConsentState,
|
|
291
625
|
useConsentState,
|
|
292
626
|
useGclid,
|
|
627
|
+
useTracking,
|
|
293
628
|
useTrackingParams
|
|
294
629
|
};
|
|
295
630
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +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"]}
|
|
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","../../tracking-core/src/session.ts","../../tracking-core/src/ingest.ts","../../tracking-core/src/page-view.ts","../src/GoogleAdsTracking.tsx","../src/hooks.ts","../src/TrackingProvider.tsx"],"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 visitorId?: string | null;\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 visitor_id: input.visitorId ?? null,\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","// Visitor + session identity for the tracking SDK.\n//\n// Visitor: persistent localStorage UUID, never expires until the user clears\n// browser storage. Used for cross-session correlation.\n//\n// Session: rolling 30-minute idle window. Regenerated when more than\n// SESSION_IDLE_MS has passed since the last event. Matches the behavior of\n// GA4, PostHog, Mixpanel, etc., so analytics is comparable.\n\nexport const VISITOR_STORAGE_KEY = 'aranova_tracking_visitor';\nexport const SESSION_STORAGE_KEY = 'aranova_tracking_session';\nexport const SESSION_IDLE_MS = 30 * 60 * 1000;\n\nexport interface StoredSession {\n id: string;\n last_event_at: number;\n}\n\nfunction safeUuid(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function')\n return crypto.randomUUID();\n // Fallback for ancient browsers — not cryptographically perfect but unique enough.\n return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\nfunction readLocalStorage(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorage(key: string, value: string): void {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Storage may be denied (private mode, blocked cookies, quota). Caller is\n // responsible for degrading gracefully.\n }\n}\n\nexport function getVisitorId(): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const existing = readLocalStorage(VISITOR_STORAGE_KEY);\n if (existing && existing.length > 0)\n return existing;\n\n const fresh = safeUuid();\n writeLocalStorage(VISITOR_STORAGE_KEY, fresh);\n return fresh;\n}\n\nexport function getOrRotateSessionId(now: number = Date.now()): string {\n if (typeof window === 'undefined')\n return safeUuid();\n\n const raw = readLocalStorage(SESSION_STORAGE_KEY);\n if (raw) {\n try {\n const parsed = JSON.parse(raw) as Partial<StoredSession>;\n if (typeof parsed.id === 'string' && typeof parsed.last_event_at === 'number') {\n if (now - parsed.last_event_at <= SESSION_IDLE_MS) {\n const refreshed: StoredSession = { id: parsed.id, last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));\n return parsed.id;\n }\n }\n } catch {\n // Fall through to a fresh session.\n }\n }\n\n const fresh: StoredSession = { id: safeUuid(), last_event_at: now };\n writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));\n return fresh.id;\n}\n\n/** Test/debug helper — wipes both ids. */\nexport function resetTrackingIdentity(): void {\n if (typeof window === 'undefined')\n return;\n try {\n window.localStorage.removeItem(VISITOR_STORAGE_KEY);\n window.localStorage.removeItem(SESSION_STORAGE_KEY);\n } catch {\n // ignored\n }\n}\n","// Tracking ingest client. Queues events, debounce-flushes them to the\n// /tracking/events endpoint, and degrades silently on errors so a broken\n// network never breaks the host site.\n\nimport { getConsentState } from './consent';\nimport {\n captureTrackingParamsFromLocation,\n createEmptyTrackingParams,\n getCookieValueFromDocument,\n getTrackingParamsFromCookieReader,\n} from './tracking';\nimport { getOrRotateSessionId, getVisitorId } from './session';\nimport type {\n TrackingClientContext,\n TrackingEventCreatePayload,\n TrackingInstallSurface,\n TrackingParams,\n TrackingSessionUpsertPayload,\n} from './types';\n\nexport const DEFAULT_FLUSH_INTERVAL_MS = 2000;\nexport const DEFAULT_MAX_QUEUE_SIZE = 10;\nexport const HARD_MAX_BATCH = 50;\nexport const API_KEY_HEADER = 'X-Aranova-Api-Key';\n\nexport interface TrackingClientConfig {\n apiKey: string;\n endpoint: string;\n surface: TrackingInstallSurface;\n sdkVersion?: string;\n packageName?: string;\n /** Override the default 2s debounce window. */\n flushIntervalMs?: number;\n /** Override the default 10-event batch trigger. */\n maxQueueSize?: number;\n /** When true, swallow nothing — useful for tests. */\n debug?: boolean;\n}\n\nexport interface TrackEventInput {\n eventType: string;\n pageUrl?: string | null;\n metadata?: Record<string, unknown> | null;\n occurredAt?: Date | string | null;\n}\n\ninterface QueuedEvent {\n event_type: string;\n page_url: string | null;\n metadata: Record<string, unknown> | null;\n occurred_at: string | null;\n}\n\nexport interface TrackingClient {\n trackEvent: (input: TrackEventInput) => void;\n flush: () => Promise<void>;\n getSessionId: () => string;\n getVisitorId: () => string;\n destroy: () => void;\n}\n\ninterface IngestRequestBody {\n session: TrackingSessionUpsertPayload;\n events: Array<{\n event_type: string;\n page_url: string | null;\n metadata: Record<string, unknown> | null;\n occurred_at: string | null;\n }>;\n}\n\nfunction buildContext(\n surface: TrackingInstallSurface,\n sdkVersion: string | null,\n packageName: string | null,\n): TrackingClientContext {\n return {\n surface,\n sdk_version: sdkVersion,\n package_name: packageName,\n site_origin: typeof window === 'undefined' ? null : window.location.origin,\n page_title: typeof document === 'undefined' ? null : document.title || null,\n referrer: typeof document === 'undefined' ? null : document.referrer || null,\n };\n}\n\nfunction readTrackingParams(): TrackingParams {\n if (typeof window === 'undefined')\n return createEmptyTrackingParams();\n // Capture from URL on every read so the first event in a session reflects the\n // landing-page params even if the cookie helper hasn't run yet.\n try {\n captureTrackingParamsFromLocation();\n } catch {\n // ignore\n }\n return getTrackingParamsFromCookieReader(getCookieValueFromDocument);\n}\n\nfunction consentSnapshot(): Record<string, unknown> | null {\n try {\n return { state: getConsentState() };\n } catch {\n return null;\n }\n}\n\nasync function postWithFetch(url: string, body: string, apiKey: string, keepalive: boolean): Promise<void> {\n if (typeof fetch !== 'function')\n return;\n try {\n await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n [API_KEY_HEADER]: apiKey,\n },\n body,\n keepalive,\n // CORS is open on the tracking endpoint; never send cookies.\n credentials: 'omit',\n mode: 'cors',\n });\n } catch {\n // fire-and-forget — never throw to the host site\n }\n}\n\nexport function createTrackingClient(config: TrackingClientConfig): TrackingClient {\n const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;\n const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);\n const sdkVersion = config.sdkVersion ?? null;\n const packageName = config.packageName ?? null;\n const endpointBase = config.endpoint.replace(/\\/$/, '');\n const eventsUrl = `${endpointBase}/events`;\n\n let queue: QueuedEvent[] = [];\n let flushTimer: ReturnType<typeof setTimeout> | null = null;\n let firstPage: string | null = null;\n let destroyed = false;\n\n // Initialize identity early so the first POST has stable values.\n const visitorId = getVisitorId();\n let sessionId = getOrRotateSessionId();\n\n if (typeof window !== 'undefined')\n firstPage = window.location.href;\n\n function buildSessionPayload(): TrackingSessionUpsertPayload {\n sessionId = getOrRotateSessionId();\n const params = readTrackingParams();\n const context = buildContext(config.surface, sdkVersion, packageName);\n return {\n session_id: sessionId,\n visitor_id: visitorId,\n gclid: params.gclid,\n fbclid: params.fbclid,\n utm_source: params.utm_source,\n utm_medium: params.utm_medium,\n utm_campaign: params.utm_campaign,\n utm_term: params.utm_term,\n utm_content: params.utm_content,\n first_page: firstPage,\n consent_state: consentSnapshot(),\n context,\n };\n }\n\n function scheduleFlush(): void {\n if (flushTimer !== null || destroyed)\n return;\n flushTimer = setTimeout(() => {\n flushTimer = null;\n void flush();\n }, flushIntervalMs);\n }\n\n function clearScheduledFlush(): void {\n if (flushTimer !== null) {\n clearTimeout(flushTimer);\n flushTimer = null;\n }\n }\n\n async function flush(): Promise<void> {\n if (queue.length === 0)\n return;\n\n const events = queue.slice(0, HARD_MAX_BATCH);\n queue = queue.slice(events.length);\n clearScheduledFlush();\n\n const body: IngestRequestBody = {\n session: buildSessionPayload(),\n events,\n };\n\n const serialized = JSON.stringify(body);\n\n // Prefer fetch + keepalive for better error visibility. sendBeacon is the\n // pagehide fallback path because it survives navigation away.\n await postWithFetch(eventsUrl, serialized, config.apiKey, false);\n }\n\n function trackEvent(input: TrackEventInput): void {\n if (destroyed)\n return;\n if (!input || typeof input.eventType !== 'string' || input.eventType.length === 0)\n return;\n\n const occurredAt =\n input.occurredAt instanceof Date\n ? input.occurredAt.toISOString()\n : typeof input.occurredAt === 'string'\n ? input.occurredAt\n : new Date().toISOString();\n\n queue.push({\n event_type: input.eventType,\n page_url:\n input.pageUrl ?? (typeof window === 'undefined' ? null : window.location.href),\n metadata: input.metadata ?? null,\n occurred_at: occurredAt,\n });\n\n if (queue.length >= maxQueueSize) {\n void flush();\n } else {\n scheduleFlush();\n }\n }\n\n function flushOnUnload(): void {\n if (queue.length === 0)\n return;\n const events = queue.slice(0, HARD_MAX_BATCH);\n queue = queue.slice(events.length);\n clearScheduledFlush();\n\n const body: IngestRequestBody = {\n session: buildSessionPayload(),\n events,\n };\n const serialized = JSON.stringify(body);\n void postWithFetch(eventsUrl, serialized, config.apiKey, true);\n }\n\n if (typeof window !== 'undefined') {\n window.addEventListener('pagehide', flushOnUnload);\n window.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') flushOnUnload();\n });\n }\n\n return {\n trackEvent,\n flush,\n getSessionId: () => sessionId,\n getVisitorId: () => visitorId,\n destroy: () => {\n destroyed = true;\n clearScheduledFlush();\n queue = [];\n if (typeof window !== 'undefined') {\n window.removeEventListener('pagehide', flushOnUnload);\n }\n },\n };\n}\n","// Auto page_view emission. Fires once on attach, then again on every SPA\n// navigation. Detects navigation via three signals:\n// - history.pushState (patched)\n// - history.replaceState (patched)\n// - popstate event (back/forward buttons)\n//\n// The Next.js provider can opt out of this and use `usePathname` instead so\n// it picks up App Router transitions reliably without monkey-patching globals.\n\nimport type { TrackingClient } from './ingest';\n\nexport interface PageViewMetadata {\n page: {\n title: string | null;\n path: string;\n search: string;\n hash: string;\n };\n referrer: string | null;\n viewport?: { w: number; h: number };\n}\n\nexport function buildPageViewMetadata(): PageViewMetadata | null {\n if (typeof window === 'undefined' || typeof document === 'undefined')\n return null;\n return {\n page: {\n title: document.title || null,\n path: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n },\n referrer: document.referrer || null,\n viewport: { w: window.innerWidth, h: window.innerHeight },\n };\n}\n\nexport function fireManualPageView(client: TrackingClient): void {\n const metadata = buildPageViewMetadata();\n client.trackEvent({\n eventType: 'page_view',\n pageUrl: typeof window === 'undefined' ? null : window.location.href,\n metadata: metadata as unknown as Record<string, unknown> | null,\n });\n}\n\nexport interface AttachAutoPageViewOptions {\n /** Skip the initial fire on attach (useful when the host already fired one). */\n skipInitial?: boolean;\n}\n\nexport function attachAutoPageView(\n client: TrackingClient,\n options: AttachAutoPageViewOptions = {},\n): () => void {\n if (typeof window === 'undefined' || typeof history === 'undefined') {\n return () => {};\n }\n\n let lastPath = window.location.pathname + window.location.search;\n\n function maybeFire(): void {\n const current = window.location.pathname + window.location.search;\n if (current === lastPath)\n return;\n lastPath = current;\n fireManualPageView(client);\n }\n\n const originalPushState = history.pushState.bind(history);\n const originalReplaceState = history.replaceState.bind(history);\n\n function patchedPushState(this: History, ...args: Parameters<History['pushState']>): void {\n originalPushState(...args);\n // Defer one tick so the URL is settled before we read it.\n setTimeout(maybeFire, 0);\n }\n function patchedReplaceState(this: History, ...args: Parameters<History['replaceState']>): void {\n originalReplaceState(...args);\n setTimeout(maybeFire, 0);\n }\n\n history.pushState = patchedPushState as History['pushState'];\n history.replaceState = patchedReplaceState as History['replaceState'];\n window.addEventListener('popstate', maybeFire);\n\n if (!options.skipInitial)\n fireManualPageView(client);\n\n return () => {\n history.pushState = originalPushState;\n history.replaceState = originalReplaceState;\n window.removeEventListener('popstate', maybeFire);\n };\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","'use client';\n\nimport { createContext, useContext, useEffect, useMemo, useRef, type ReactNode } from 'react';\n\nimport {\n attachAutoPageView,\n bootstrapGoogleAdsTracking,\n createTrackingClient,\n type TrackingClient,\n} from '../../tracking-core/src/index';\n\nexport interface TrackingProviderProps {\n /** Public tracking API key issued for this business. Required. */\n apiKey: string;\n /** Full ingest endpoint base URL, e.g. https://api.aranova.io/tracking. Required. */\n endpoint: string;\n /** Optional Google Ads gtag id. If omitted, no gtag script is loaded. */\n gtagId?: string;\n /** Disable automatic page_view emission (manual tracking only). */\n disableAutoPageView?: boolean;\n /** Optional debug flag — currently a no-op, reserved for verbose logging. */\n debug?: boolean;\n children: ReactNode;\n}\n\nconst TrackingContext = createContext<TrackingClient | null>(null);\n\nexport function TrackingProvider({\n apiKey,\n endpoint,\n gtagId,\n disableAutoPageView,\n debug,\n children,\n}: TrackingProviderProps) {\n if (!apiKey)\n throw new Error('TrackingProvider: apiKey is required');\n if (!endpoint)\n throw new Error('TrackingProvider: endpoint is required');\n\n const clientRef = useRef<TrackingClient | null>(null);\n if (clientRef.current === null) {\n clientRef.current = createTrackingClient({\n apiKey,\n endpoint,\n surface: 'react',\n packageName: '@aranova/tracking-react',\n debug,\n });\n }\n\n useEffect(() => {\n if (!gtagId)\n return;\n bootstrapGoogleAdsTracking(gtagId);\n }, [gtagId]);\n\n useEffect(() => {\n const client = clientRef.current;\n if (client === null || disableAutoPageView)\n return;\n const detach = attachAutoPageView(client);\n return () => {\n detach();\n };\n }, [disableAutoPageView]);\n\n useEffect(() => {\n return () => {\n clientRef.current?.destroy();\n clientRef.current = null;\n };\n }, []);\n\n const contextValue = useMemo(() => clientRef.current, []);\n\n return (\n <TrackingContext.Provider value={contextValue}>\n {children}\n </TrackingContext.Provider>\n );\n}\n\nexport function useTracking(): TrackingClient {\n const client = useContext(TrackingContext);\n if (client === null)\n throw new Error('useTracking must be called inside a <TrackingProvider>');\n return client;\n}\n"],"mappings":"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","names":["useEffect","useEffect","useEffect","useState","useState","useEffect","useEffect","jsx","useEffect"]}
|