@aranova/tracking-next 0.2.3 → 0.4.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 +499 -5
- package/dist/index.d.ts +499 -5
- package/dist/index.js +604 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +609 -30
- package/dist/index.mjs.map +1 -1
- package/dist/middleware.js +112 -0
- package/dist/middleware.js.map +1 -1
- package/dist/middleware.mjs +112 -0
- package/dist/middleware.mjs.map +1 -1
- package/dist/server.d.mts +5 -1
- package/dist/server.d.ts +5 -1
- package/dist/server.js +112 -0
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +112 -0
- package/dist/server.mjs.map +1 -1
- package/dist/{server-BMTa7njW.d.mts → types-Gp0ioRiQ.d.mts} +3 -3
- package/dist/{server-BMTa7njW.d.ts → types-Gp0ioRiQ.d.ts} +3 -3
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -34,12 +34,11 @@ __export(src_exports, {
|
|
|
34
34
|
GoogleAdsTracking: () => GoogleAdsTracking,
|
|
35
35
|
TRACKING_PARAM_KEYS: () => TRACKING_PARAM_KEYS,
|
|
36
36
|
captureTrackingParamsFromLocation: () => captureTrackingParamsFromLocation,
|
|
37
|
+
createTracking: () => createTracking,
|
|
37
38
|
createTrackingClientContext: () => createTrackingClientContext,
|
|
38
39
|
createTrackingEventCreatePayload: () => createTrackingEventCreatePayload,
|
|
39
|
-
createTrackingMiddleware: () => createTrackingMiddleware,
|
|
40
40
|
createTrackingSessionUpsertPayload: () => createTrackingSessionUpsertPayload,
|
|
41
41
|
getConsentState: () => getConsentState,
|
|
42
|
-
getTrackingParamsServer: () => getTrackingParamsServer,
|
|
43
42
|
setConsentState: () => setConsentState,
|
|
44
43
|
useConsentState: () => useConsentState,
|
|
45
44
|
useGclid: () => useGclid,
|
|
@@ -89,6 +88,7 @@ function createTrackingClientContext(surface, input = {}) {
|
|
|
89
88
|
function createTrackingSessionUpsertPayload(trackingParams, input, context) {
|
|
90
89
|
return {
|
|
91
90
|
session_id: input.sessionId,
|
|
91
|
+
visitor_id: input.visitorId ?? null,
|
|
92
92
|
gclid: trackingParams.gclid,
|
|
93
93
|
fbclid: trackingParams.fbclid,
|
|
94
94
|
utm_source: trackingParams.utm_source,
|
|
@@ -194,8 +194,8 @@ function getTrackingQueryValues(searchParams) {
|
|
|
194
194
|
function getCookieValueFromDocument(key) {
|
|
195
195
|
if (typeof document === "undefined")
|
|
196
196
|
return null;
|
|
197
|
-
const
|
|
198
|
-
const match =
|
|
197
|
+
const cookies = document.cookie ? document.cookie.split("; ") : [];
|
|
198
|
+
const match = cookies.find((cookie) => cookie.startsWith(`${key}=`));
|
|
199
199
|
if (!match)
|
|
200
200
|
return null;
|
|
201
201
|
const [, rawValue = ""] = match.split("=");
|
|
@@ -220,6 +220,515 @@ function captureTrackingParamsFromLocation(url = typeof window === "undefined" ?
|
|
|
220
220
|
return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
// ../tracking-core/src/session.ts
|
|
224
|
+
var VISITOR_STORAGE_KEY = "aranova_tracking_visitor";
|
|
225
|
+
var SESSION_STORAGE_KEY = "aranova_tracking_session";
|
|
226
|
+
var SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
227
|
+
function safeUuid() {
|
|
228
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function")
|
|
229
|
+
return crypto.randomUUID();
|
|
230
|
+
return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;
|
|
231
|
+
}
|
|
232
|
+
function readLocalStorage(key) {
|
|
233
|
+
try {
|
|
234
|
+
return window.localStorage.getItem(key);
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function writeLocalStorage(key, value) {
|
|
240
|
+
try {
|
|
241
|
+
window.localStorage.setItem(key, value);
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
function getVisitorId() {
|
|
246
|
+
if (typeof window === "undefined")
|
|
247
|
+
return safeUuid();
|
|
248
|
+
const existing = readLocalStorage(VISITOR_STORAGE_KEY);
|
|
249
|
+
if (existing && existing.length > 0)
|
|
250
|
+
return existing;
|
|
251
|
+
const fresh = safeUuid();
|
|
252
|
+
writeLocalStorage(VISITOR_STORAGE_KEY, fresh);
|
|
253
|
+
return fresh;
|
|
254
|
+
}
|
|
255
|
+
function getOrRotateSessionId(now = Date.now()) {
|
|
256
|
+
if (typeof window === "undefined")
|
|
257
|
+
return safeUuid();
|
|
258
|
+
const raw = readLocalStorage(SESSION_STORAGE_KEY);
|
|
259
|
+
if (raw) {
|
|
260
|
+
try {
|
|
261
|
+
const parsed = JSON.parse(raw);
|
|
262
|
+
if (typeof parsed.id === "string" && typeof parsed.last_event_at === "number") {
|
|
263
|
+
if (now - parsed.last_event_at <= SESSION_IDLE_MS) {
|
|
264
|
+
const refreshed = { id: parsed.id, last_event_at: now };
|
|
265
|
+
writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(refreshed));
|
|
266
|
+
return parsed.id;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const fresh = { id: safeUuid(), last_event_at: now };
|
|
273
|
+
writeLocalStorage(SESSION_STORAGE_KEY, JSON.stringify(fresh));
|
|
274
|
+
return fresh.id;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ../tracking-core/src/events/page-view.ts
|
|
278
|
+
var import_zod = require("zod");
|
|
279
|
+
var pageViewMetadataSchema = import_zod.z.object({
|
|
280
|
+
page: import_zod.z.object({
|
|
281
|
+
title: import_zod.z.string().nullable(),
|
|
282
|
+
path: import_zod.z.string(),
|
|
283
|
+
search: import_zod.z.string(),
|
|
284
|
+
hash: import_zod.z.string()
|
|
285
|
+
}),
|
|
286
|
+
referrer: import_zod.z.string().nullable(),
|
|
287
|
+
// `.nullable().optional()` — absent (undefined) OR explicit null OR a
|
|
288
|
+
// real viewport object. Mirrors Pydantic's `_Viewport | None = None`
|
|
289
|
+
// on the backend side so the drift test stays clean.
|
|
290
|
+
viewport: import_zod.z.object({
|
|
291
|
+
w: import_zod.z.number(),
|
|
292
|
+
h: import_zod.z.number()
|
|
293
|
+
}).nullable().optional()
|
|
294
|
+
}).strict();
|
|
295
|
+
var pageViewConfigSchema = import_zod.z.object({}).strict();
|
|
296
|
+
|
|
297
|
+
// ../tracking-core/src/page-view.ts
|
|
298
|
+
var LAST_FIRED_URL_STORAGE_KEY = "aranova_tracking_last_fired_url";
|
|
299
|
+
var lastFiredUrl = null;
|
|
300
|
+
var lastFiredUrlHydrated = false;
|
|
301
|
+
function readSessionStorage(key) {
|
|
302
|
+
try {
|
|
303
|
+
if (typeof window === "undefined") return null;
|
|
304
|
+
return window.sessionStorage.getItem(key);
|
|
305
|
+
} catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function writeSessionStorage(key, value) {
|
|
310
|
+
try {
|
|
311
|
+
if (typeof window === "undefined") return;
|
|
312
|
+
window.sessionStorage.setItem(key, value);
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function getLastFiredUrl() {
|
|
317
|
+
if (!lastFiredUrlHydrated) {
|
|
318
|
+
lastFiredUrlHydrated = true;
|
|
319
|
+
const stored = readSessionStorage(LAST_FIRED_URL_STORAGE_KEY);
|
|
320
|
+
if (stored !== null) lastFiredUrl = stored;
|
|
321
|
+
}
|
|
322
|
+
return lastFiredUrl;
|
|
323
|
+
}
|
|
324
|
+
function setLastFiredUrl(url) {
|
|
325
|
+
lastFiredUrl = url;
|
|
326
|
+
lastFiredUrlHydrated = true;
|
|
327
|
+
writeSessionStorage(LAST_FIRED_URL_STORAGE_KEY, url);
|
|
328
|
+
}
|
|
329
|
+
function buildPageViewMetadata(referrerOverride) {
|
|
330
|
+
if (typeof window === "undefined" || typeof document === "undefined")
|
|
331
|
+
return null;
|
|
332
|
+
return pageViewMetadataSchema.parse({
|
|
333
|
+
page: {
|
|
334
|
+
title: document.title || null,
|
|
335
|
+
path: window.location.pathname,
|
|
336
|
+
search: window.location.search,
|
|
337
|
+
hash: window.location.hash
|
|
338
|
+
},
|
|
339
|
+
referrer: referrerOverride !== void 0 ? referrerOverride : document.referrer || null,
|
|
340
|
+
viewport: { w: window.innerWidth, h: window.innerHeight }
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
function fireManualPageView(client) {
|
|
344
|
+
if (typeof window === "undefined")
|
|
345
|
+
return;
|
|
346
|
+
const currentHref = window.location.href;
|
|
347
|
+
const previousFiredUrl = getLastFiredUrl();
|
|
348
|
+
const internalReferrer = previousFiredUrl !== null && previousFiredUrl !== currentHref ? previousFiredUrl : null;
|
|
349
|
+
const externalReferrer = typeof document !== "undefined" ? document.referrer || null : null;
|
|
350
|
+
const referrer = internalReferrer ?? externalReferrer;
|
|
351
|
+
const metadata = buildPageViewMetadata(referrer);
|
|
352
|
+
client.trackEvent({
|
|
353
|
+
eventType: "page_view",
|
|
354
|
+
pageUrl: currentHref,
|
|
355
|
+
metadata
|
|
356
|
+
});
|
|
357
|
+
if (currentHref !== previousFiredUrl) {
|
|
358
|
+
setLastFiredUrl(currentHref);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function attachBfcacheRestore(client) {
|
|
362
|
+
if (typeof window === "undefined") return () => {
|
|
363
|
+
};
|
|
364
|
+
function handlePageShow(event) {
|
|
365
|
+
if (!event.persisted) return;
|
|
366
|
+
fireManualPageView(client);
|
|
367
|
+
}
|
|
368
|
+
window.addEventListener("pageshow", handlePageShow);
|
|
369
|
+
return () => {
|
|
370
|
+
window.removeEventListener("pageshow", handlePageShow);
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ../tracking-core/src/ingest.ts
|
|
375
|
+
var DEFAULT_FLUSH_INTERVAL_MS = 2e3;
|
|
376
|
+
var DEFAULT_MAX_QUEUE_SIZE = 10;
|
|
377
|
+
var HARD_MAX_BATCH = 50;
|
|
378
|
+
var API_KEY_HEADER = "X-Aranova-Api-Key";
|
|
379
|
+
function buildContext(surface, sdkVersion, packageName) {
|
|
380
|
+
return {
|
|
381
|
+
surface,
|
|
382
|
+
sdk_version: sdkVersion,
|
|
383
|
+
package_name: packageName,
|
|
384
|
+
site_origin: typeof window === "undefined" ? null : window.location.origin,
|
|
385
|
+
page_title: typeof document === "undefined" ? null : document.title || null,
|
|
386
|
+
referrer: typeof document === "undefined" ? null : document.referrer || null
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function readTrackingParams() {
|
|
390
|
+
if (typeof window === "undefined")
|
|
391
|
+
return createEmptyTrackingParams();
|
|
392
|
+
try {
|
|
393
|
+
captureTrackingParamsFromLocation();
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
return getTrackingParamsFromCookieReader(getCookieValueFromDocument);
|
|
397
|
+
}
|
|
398
|
+
function consentSnapshot() {
|
|
399
|
+
try {
|
|
400
|
+
return { state: getConsentState() };
|
|
401
|
+
} catch {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
async function postWithFetch(url, body, apiKey, keepalive) {
|
|
406
|
+
if (typeof fetch !== "function")
|
|
407
|
+
return;
|
|
408
|
+
try {
|
|
409
|
+
await fetch(url, {
|
|
410
|
+
method: "POST",
|
|
411
|
+
headers: {
|
|
412
|
+
"Content-Type": "application/json",
|
|
413
|
+
[API_KEY_HEADER]: apiKey
|
|
414
|
+
},
|
|
415
|
+
body,
|
|
416
|
+
keepalive,
|
|
417
|
+
// CORS is open on the tracking endpoint; never send cookies.
|
|
418
|
+
credentials: "omit",
|
|
419
|
+
mode: "cors"
|
|
420
|
+
});
|
|
421
|
+
} catch {
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
var globalClient = null;
|
|
425
|
+
var globalClientKey = null;
|
|
426
|
+
function clientConfigKey(config) {
|
|
427
|
+
return `${config.apiKey}@${config.endpoint}#${config.surface}`;
|
|
428
|
+
}
|
|
429
|
+
function getOrCreateTrackingClient(config) {
|
|
430
|
+
const key = clientConfigKey(config);
|
|
431
|
+
if (globalClient !== null && globalClientKey === key) {
|
|
432
|
+
return globalClient;
|
|
433
|
+
}
|
|
434
|
+
if (globalClient !== null) {
|
|
435
|
+
globalClient.destroy();
|
|
436
|
+
}
|
|
437
|
+
globalClient = createTrackingClient(config);
|
|
438
|
+
globalClientKey = key;
|
|
439
|
+
return globalClient;
|
|
440
|
+
}
|
|
441
|
+
function createTrackingClient(config) {
|
|
442
|
+
const flushIntervalMs = config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS;
|
|
443
|
+
const maxQueueSize = Math.min(config.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE, HARD_MAX_BATCH);
|
|
444
|
+
const sdkVersion = config.sdkVersion ?? null;
|
|
445
|
+
const packageName = config.packageName ?? null;
|
|
446
|
+
const endpointBase = config.endpoint.replace(/\/$/, "");
|
|
447
|
+
const eventsUrl = `${endpointBase}/events`;
|
|
448
|
+
let queue = [];
|
|
449
|
+
let flushTimer = null;
|
|
450
|
+
let firstPage = null;
|
|
451
|
+
let destroyed = false;
|
|
452
|
+
const visitorId = getVisitorId();
|
|
453
|
+
let sessionId = getOrRotateSessionId();
|
|
454
|
+
if (typeof window !== "undefined")
|
|
455
|
+
firstPage = window.location.href;
|
|
456
|
+
function buildSessionPayload() {
|
|
457
|
+
sessionId = getOrRotateSessionId();
|
|
458
|
+
const params = readTrackingParams();
|
|
459
|
+
const context = buildContext(config.surface, sdkVersion, packageName);
|
|
460
|
+
return {
|
|
461
|
+
session_id: sessionId,
|
|
462
|
+
visitor_id: visitorId,
|
|
463
|
+
gclid: params.gclid,
|
|
464
|
+
fbclid: params.fbclid,
|
|
465
|
+
utm_source: params.utm_source,
|
|
466
|
+
utm_medium: params.utm_medium,
|
|
467
|
+
utm_campaign: params.utm_campaign,
|
|
468
|
+
utm_term: params.utm_term,
|
|
469
|
+
utm_content: params.utm_content,
|
|
470
|
+
first_page: firstPage,
|
|
471
|
+
consent_state: consentSnapshot(),
|
|
472
|
+
context
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function scheduleFlush() {
|
|
476
|
+
if (flushTimer !== null || destroyed)
|
|
477
|
+
return;
|
|
478
|
+
flushTimer = setTimeout(() => {
|
|
479
|
+
flushTimer = null;
|
|
480
|
+
void flush();
|
|
481
|
+
}, flushIntervalMs);
|
|
482
|
+
}
|
|
483
|
+
function clearScheduledFlush() {
|
|
484
|
+
if (flushTimer !== null) {
|
|
485
|
+
clearTimeout(flushTimer);
|
|
486
|
+
flushTimer = null;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function flush() {
|
|
490
|
+
if (queue.length === 0)
|
|
491
|
+
return;
|
|
492
|
+
const events = queue.slice(0, HARD_MAX_BATCH);
|
|
493
|
+
queue = queue.slice(events.length);
|
|
494
|
+
clearScheduledFlush();
|
|
495
|
+
const body = {
|
|
496
|
+
session: buildSessionPayload(),
|
|
497
|
+
events
|
|
498
|
+
};
|
|
499
|
+
const serialized = JSON.stringify(body);
|
|
500
|
+
await postWithFetch(eventsUrl, serialized, config.apiKey, false);
|
|
501
|
+
}
|
|
502
|
+
function trackEvent(input) {
|
|
503
|
+
if (destroyed)
|
|
504
|
+
return;
|
|
505
|
+
if (!input || typeof input.eventType !== "string" || input.eventType.length === 0)
|
|
506
|
+
return;
|
|
507
|
+
const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : typeof input.occurredAt === "string" ? input.occurredAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
508
|
+
queue.push({
|
|
509
|
+
event_type: input.eventType,
|
|
510
|
+
page_url: input.pageUrl ?? (typeof window === "undefined" ? null : window.location.href),
|
|
511
|
+
metadata: input.metadata ?? null,
|
|
512
|
+
occurred_at: occurredAt
|
|
513
|
+
});
|
|
514
|
+
if (queue.length >= maxQueueSize) {
|
|
515
|
+
void flush();
|
|
516
|
+
} else {
|
|
517
|
+
scheduleFlush();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function flushOnUnload() {
|
|
521
|
+
if (queue.length === 0)
|
|
522
|
+
return;
|
|
523
|
+
const events = queue.slice(0, HARD_MAX_BATCH);
|
|
524
|
+
queue = queue.slice(events.length);
|
|
525
|
+
clearScheduledFlush();
|
|
526
|
+
const body = {
|
|
527
|
+
session: buildSessionPayload(),
|
|
528
|
+
events
|
|
529
|
+
};
|
|
530
|
+
const serialized = JSON.stringify(body);
|
|
531
|
+
void postWithFetch(eventsUrl, serialized, config.apiKey, true);
|
|
532
|
+
}
|
|
533
|
+
if (typeof window !== "undefined") {
|
|
534
|
+
window.addEventListener("pagehide", flushOnUnload);
|
|
535
|
+
window.addEventListener("visibilitychange", () => {
|
|
536
|
+
if (document.visibilityState === "hidden") flushOnUnload();
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
trackEvent,
|
|
541
|
+
flush,
|
|
542
|
+
getSessionId: () => sessionId,
|
|
543
|
+
getVisitorId: () => visitorId,
|
|
544
|
+
destroy: () => {
|
|
545
|
+
destroyed = true;
|
|
546
|
+
if (queue.length > 0) {
|
|
547
|
+
flushOnUnload();
|
|
548
|
+
}
|
|
549
|
+
clearScheduledFlush();
|
|
550
|
+
queue = [];
|
|
551
|
+
if (typeof window !== "undefined") {
|
|
552
|
+
window.removeEventListener("pagehide", flushOnUnload);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ../tracking-core/src/events/contact-page-visit.ts
|
|
559
|
+
var import_zod2 = require("zod");
|
|
560
|
+
var contactPageVisitMetadataSchema = import_zod2.z.object({
|
|
561
|
+
page: import_zod2.z.object({
|
|
562
|
+
path: import_zod2.z.string()
|
|
563
|
+
})
|
|
564
|
+
}).strict();
|
|
565
|
+
var contactPageVisitConfigSchema = import_zod2.z.object({
|
|
566
|
+
// RegExp is runtime-only so we wrap it via z.custom. Consumers pass a
|
|
567
|
+
// real regex at createTracking() time; the factory validates with this
|
|
568
|
+
// schema and then keeps the live RegExp reference for runtime matching.
|
|
569
|
+
pathPattern: import_zod2.z.custom(
|
|
570
|
+
(value) => value instanceof RegExp,
|
|
571
|
+
{ message: "pathPattern must be a RegExp" }
|
|
572
|
+
)
|
|
573
|
+
}).strict();
|
|
574
|
+
|
|
575
|
+
// ../tracking-core/src/events/form-submit.ts
|
|
576
|
+
var import_zod3 = require("zod");
|
|
577
|
+
var formSubmitMetadataSchema = import_zod3.z.object({
|
|
578
|
+
form: import_zod3.z.object({
|
|
579
|
+
id: import_zod3.z.string(),
|
|
580
|
+
action: import_zod3.z.string().nullable()
|
|
581
|
+
}),
|
|
582
|
+
page: import_zod3.z.object({
|
|
583
|
+
path: import_zod3.z.string()
|
|
584
|
+
})
|
|
585
|
+
}).strict();
|
|
586
|
+
var formSubmitConfigSchema = import_zod3.z.object({}).strict();
|
|
587
|
+
|
|
588
|
+
// ../tracking-core/src/events/phone-click.ts
|
|
589
|
+
var import_zod4 = require("zod");
|
|
590
|
+
var phoneClickMetadataSchema = import_zod4.z.object({
|
|
591
|
+
element: import_zod4.z.object({
|
|
592
|
+
tag: import_zod4.z.string(),
|
|
593
|
+
text: import_zod4.z.string().nullable(),
|
|
594
|
+
href: import_zod4.z.string()
|
|
595
|
+
}),
|
|
596
|
+
page: import_zod4.z.object({
|
|
597
|
+
path: import_zod4.z.string()
|
|
598
|
+
})
|
|
599
|
+
}).strict();
|
|
600
|
+
var phoneClickConfigSchema = import_zod4.z.object({}).strict();
|
|
601
|
+
|
|
602
|
+
// ../tracking-core/src/events/time-on-site.ts
|
|
603
|
+
var import_zod5 = require("zod");
|
|
604
|
+
var timeOnSiteMetadataSchema = import_zod5.z.object({
|
|
605
|
+
duration_ms: import_zod5.z.number().int().nonnegative(),
|
|
606
|
+
page: import_zod5.z.object({
|
|
607
|
+
path: import_zod5.z.string()
|
|
608
|
+
})
|
|
609
|
+
}).strict();
|
|
610
|
+
var timeOnSiteConfigSchema = import_zod5.z.object({
|
|
611
|
+
thresholdSeconds: import_zod5.z.number().int().positive()
|
|
612
|
+
}).strict();
|
|
613
|
+
|
|
614
|
+
// ../tracking-core/src/events/registry.ts
|
|
615
|
+
var EVENT_REGISTRY = {
|
|
616
|
+
// --- automatic triggers ---
|
|
617
|
+
page_view: {
|
|
618
|
+
kind: "automatic",
|
|
619
|
+
metadataSchema: pageViewMetadataSchema,
|
|
620
|
+
configSchema: pageViewConfigSchema
|
|
621
|
+
},
|
|
622
|
+
time_on_site: {
|
|
623
|
+
kind: "automatic",
|
|
624
|
+
metadataSchema: timeOnSiteMetadataSchema,
|
|
625
|
+
configSchema: timeOnSiteConfigSchema
|
|
626
|
+
},
|
|
627
|
+
contact_page_visit: {
|
|
628
|
+
kind: "automatic",
|
|
629
|
+
metadataSchema: contactPageVisitMetadataSchema,
|
|
630
|
+
configSchema: contactPageVisitConfigSchema
|
|
631
|
+
},
|
|
632
|
+
// --- manual triggers ---
|
|
633
|
+
form_submit: {
|
|
634
|
+
kind: "manual",
|
|
635
|
+
metadataSchema: formSubmitMetadataSchema,
|
|
636
|
+
configSchema: formSubmitConfigSchema
|
|
637
|
+
},
|
|
638
|
+
phone_click: {
|
|
639
|
+
kind: "manual",
|
|
640
|
+
metadataSchema: phoneClickMetadataSchema,
|
|
641
|
+
configSchema: phoneClickConfigSchema
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
var ALL_AUTOMATIC_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "automatic").map(([name]) => name);
|
|
645
|
+
var ALL_MANUAL_EVENT_NAMES = Object.entries(EVENT_REGISTRY).filter(([, def]) => def.kind === "manual").map(([name]) => name);
|
|
646
|
+
function getEventDefinition(name) {
|
|
647
|
+
return EVENT_REGISTRY[name];
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ../tracking-core/src/ingest-typed.ts
|
|
651
|
+
function createTypedClient(raw, _registry, options = {}) {
|
|
652
|
+
const debug = options.debug ?? false;
|
|
653
|
+
return {
|
|
654
|
+
trackEvent(eventType, metadata, opts) {
|
|
655
|
+
if (debug) {
|
|
656
|
+
const def = getEventDefinition(eventType);
|
|
657
|
+
def.metadataSchema.parse(metadata);
|
|
658
|
+
}
|
|
659
|
+
raw.trackEvent({
|
|
660
|
+
eventType,
|
|
661
|
+
metadata,
|
|
662
|
+
pageUrl: opts?.pageUrl ?? null,
|
|
663
|
+
occurredAt: opts?.occurredAt ?? null
|
|
664
|
+
});
|
|
665
|
+
},
|
|
666
|
+
flush: raw.flush.bind(raw),
|
|
667
|
+
getSessionId: raw.getSessionId.bind(raw),
|
|
668
|
+
getVisitorId: raw.getVisitorId.bind(raw)
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// ../tracking-core/src/triggers/time-on-site.ts
|
|
673
|
+
function attachTimeOnSite(client, config) {
|
|
674
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
675
|
+
return () => {
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const thresholdMs = config.thresholdSeconds * 1e3;
|
|
679
|
+
let accumulatedMs = 0;
|
|
680
|
+
let activeSince = document.visibilityState === "visible" ? Date.now() : null;
|
|
681
|
+
let timer = null;
|
|
682
|
+
let fired = false;
|
|
683
|
+
function fire() {
|
|
684
|
+
if (fired) return;
|
|
685
|
+
fired = true;
|
|
686
|
+
client.trackEvent({
|
|
687
|
+
eventType: "time_on_site",
|
|
688
|
+
metadata: {
|
|
689
|
+
duration_ms: thresholdMs,
|
|
690
|
+
page: { path: window.location.pathname }
|
|
691
|
+
},
|
|
692
|
+
pageUrl: window.location.href,
|
|
693
|
+
occurredAt: null
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function scheduleNext() {
|
|
697
|
+
if (fired || activeSince === null) return;
|
|
698
|
+
const remaining = thresholdMs - accumulatedMs;
|
|
699
|
+
if (remaining <= 0) {
|
|
700
|
+
fire();
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
timer = setTimeout(fire, remaining);
|
|
704
|
+
}
|
|
705
|
+
function clearTimer() {
|
|
706
|
+
if (timer !== null) {
|
|
707
|
+
clearTimeout(timer);
|
|
708
|
+
timer = null;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function onVisibilityChange() {
|
|
712
|
+
if (fired) return;
|
|
713
|
+
if (document.visibilityState === "hidden") {
|
|
714
|
+
if (activeSince !== null) {
|
|
715
|
+
accumulatedMs += Date.now() - activeSince;
|
|
716
|
+
activeSince = null;
|
|
717
|
+
}
|
|
718
|
+
clearTimer();
|
|
719
|
+
} else {
|
|
720
|
+
activeSince = Date.now();
|
|
721
|
+
scheduleNext();
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
725
|
+
scheduleNext();
|
|
726
|
+
return () => {
|
|
727
|
+
clearTimer();
|
|
728
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
223
732
|
// src/ConsentBanner.tsx
|
|
224
733
|
var import_react = require("react");
|
|
225
734
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -315,33 +824,97 @@ function GoogleAdsTracking({ gtagId }) {
|
|
|
315
824
|
] });
|
|
316
825
|
}
|
|
317
826
|
|
|
318
|
-
// src/
|
|
319
|
-
var
|
|
320
|
-
var
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
827
|
+
// src/factory.tsx
|
|
828
|
+
var import_navigation = require("next/navigation");
|
|
829
|
+
var import_react3 = require("react");
|
|
830
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
831
|
+
function createTracking(options) {
|
|
832
|
+
const { apiKey, endpoint, triggers, debug } = options;
|
|
833
|
+
if (!apiKey) throw new Error("createTracking: apiKey is required");
|
|
834
|
+
if (!endpoint) throw new Error("createTracking: endpoint is required");
|
|
835
|
+
const TrackingContext = (0, import_react3.createContext)(null);
|
|
836
|
+
const contactPageVisitConfig = triggers.automatic.contact_page_visit ?? null;
|
|
837
|
+
const timeOnSiteConfig = triggers.automatic.time_on_site ?? null;
|
|
838
|
+
function TrackingProvider({ children }) {
|
|
839
|
+
const pathname = (0, import_navigation.usePathname)();
|
|
840
|
+
const searchParams = (0, import_navigation.useSearchParams)();
|
|
841
|
+
const client = (0, import_react3.useMemo)(
|
|
842
|
+
() => createTypedClient(
|
|
843
|
+
getOrCreateTrackingClient({
|
|
844
|
+
apiKey,
|
|
845
|
+
endpoint,
|
|
846
|
+
surface: "next",
|
|
847
|
+
packageName: "@aranova/tracking-next",
|
|
848
|
+
debug
|
|
849
|
+
}),
|
|
850
|
+
triggers,
|
|
851
|
+
{ debug }
|
|
852
|
+
),
|
|
853
|
+
[]
|
|
854
|
+
);
|
|
855
|
+
(0, import_react3.useEffect)(() => {
|
|
856
|
+
const rawClient = getOrCreateTrackingClient({
|
|
857
|
+
apiKey,
|
|
858
|
+
endpoint,
|
|
859
|
+
surface: "next",
|
|
860
|
+
packageName: "@aranova/tracking-next",
|
|
861
|
+
debug
|
|
330
862
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
863
|
+
fireManualPageView(rawClient);
|
|
864
|
+
}, [pathname, searchParams]);
|
|
865
|
+
const lastContactPathRef = (0, import_react3.useRef)(null);
|
|
866
|
+
(0, import_react3.useEffect)(() => {
|
|
867
|
+
if (!contactPageVisitConfig || pathname === null) return;
|
|
868
|
+
if (!contactPageVisitConfig.pathPattern.test(pathname)) return;
|
|
869
|
+
if (lastContactPathRef.current === pathname) return;
|
|
870
|
+
lastContactPathRef.current = pathname;
|
|
871
|
+
const rawClient = getOrCreateTrackingClient({
|
|
872
|
+
apiKey,
|
|
873
|
+
endpoint,
|
|
874
|
+
surface: "next",
|
|
875
|
+
packageName: "@aranova/tracking-next",
|
|
876
|
+
debug
|
|
877
|
+
});
|
|
878
|
+
rawClient.trackEvent({
|
|
879
|
+
eventType: "contact_page_visit",
|
|
880
|
+
metadata: { page: { path: pathname } },
|
|
881
|
+
pageUrl: null,
|
|
882
|
+
occurredAt: null
|
|
883
|
+
});
|
|
884
|
+
}, [pathname]);
|
|
885
|
+
(0, import_react3.useEffect)(() => {
|
|
886
|
+
const rawClient = getOrCreateTrackingClient({
|
|
887
|
+
apiKey,
|
|
888
|
+
endpoint,
|
|
889
|
+
surface: "next",
|
|
890
|
+
packageName: "@aranova/tracking-next",
|
|
891
|
+
debug
|
|
892
|
+
});
|
|
893
|
+
return attachBfcacheRestore(rawClient);
|
|
894
|
+
}, []);
|
|
895
|
+
(0, import_react3.useEffect)(() => {
|
|
896
|
+
if (!timeOnSiteConfig) return;
|
|
897
|
+
const rawClient = getOrCreateTrackingClient({
|
|
898
|
+
apiKey,
|
|
899
|
+
endpoint,
|
|
900
|
+
surface: "next",
|
|
901
|
+
packageName: "@aranova/tracking-next",
|
|
902
|
+
debug
|
|
903
|
+
});
|
|
904
|
+
return attachTimeOnSite(rawClient, timeOnSiteConfig);
|
|
905
|
+
}, []);
|
|
906
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TrackingContext.Provider, { value: client, children });
|
|
907
|
+
}
|
|
908
|
+
function useTracking() {
|
|
909
|
+
const client = (0, import_react3.useContext)(TrackingContext);
|
|
910
|
+
if (client === null) {
|
|
911
|
+
throw new Error(
|
|
912
|
+
"useTracking must be called inside a <TrackingProvider> returned by createTracking()"
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
return client;
|
|
916
|
+
}
|
|
917
|
+
return { TrackingProvider, useTracking };
|
|
345
918
|
}
|
|
346
919
|
// Annotate the CommonJS export names for ESM import in node:
|
|
347
920
|
0 && (module.exports = {
|
|
@@ -349,12 +922,11 @@ function getTrackingParamsServer() {
|
|
|
349
922
|
GoogleAdsTracking,
|
|
350
923
|
TRACKING_PARAM_KEYS,
|
|
351
924
|
captureTrackingParamsFromLocation,
|
|
925
|
+
createTracking,
|
|
352
926
|
createTrackingClientContext,
|
|
353
927
|
createTrackingEventCreatePayload,
|
|
354
|
-
createTrackingMiddleware,
|
|
355
928
|
createTrackingSessionUpsertPayload,
|
|
356
929
|
getConsentState,
|
|
357
|
-
getTrackingParamsServer,
|
|
358
930
|
setConsentState,
|
|
359
931
|
useConsentState,
|
|
360
932
|
useGclid,
|