@loamly/tracker 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,551 @@
1
+ // src/config.ts
2
+ var VERSION = "1.6.0";
3
+ var DEFAULT_CONFIG = {
4
+ apiHost: "https://app.loamly.ai",
5
+ endpoints: {
6
+ visit: "/api/ingest/visit",
7
+ behavioral: "/api/ingest/behavioral",
8
+ session: "/api/ingest/session",
9
+ resolve: "/api/tracker/resolve",
10
+ health: "/api/tracker/health",
11
+ ping: "/api/tracker/ping"
12
+ },
13
+ pingInterval: 3e4,
14
+ // 30 seconds
15
+ batchSize: 10,
16
+ batchTimeout: 5e3,
17
+ sessionTimeout: 18e5,
18
+ // 30 minutes
19
+ maxTextLength: 100,
20
+ timeSpentThresholdMs: 5e3
21
+ // Only send time_spent when delta >= 5 seconds
22
+ };
23
+ var AI_PLATFORMS = {
24
+ "chatgpt.com": "chatgpt",
25
+ "chat.openai.com": "chatgpt",
26
+ "claude.ai": "claude",
27
+ "perplexity.ai": "perplexity",
28
+ "bard.google.com": "bard",
29
+ "gemini.google.com": "gemini",
30
+ "copilot.microsoft.com": "copilot",
31
+ "github.com/copilot": "github-copilot",
32
+ "you.com": "you",
33
+ "phind.com": "phind",
34
+ "poe.com": "poe"
35
+ };
36
+ var AI_BOT_PATTERNS = [
37
+ "GPTBot",
38
+ "ChatGPT-User",
39
+ "ClaudeBot",
40
+ "Claude-Web",
41
+ "PerplexityBot",
42
+ "Amazonbot",
43
+ "Google-Extended",
44
+ "CCBot",
45
+ "anthropic-ai",
46
+ "cohere-ai"
47
+ ];
48
+
49
+ // src/detection/navigation-timing.ts
50
+ function detectNavigationType() {
51
+ try {
52
+ const entries = performance.getEntriesByType("navigation");
53
+ if (!entries || entries.length === 0) {
54
+ return { nav_type: "unknown", confidence: 0, signals: ["no_timing_data"] };
55
+ }
56
+ const nav = entries[0];
57
+ const signals = [];
58
+ let pasteScore = 0;
59
+ const fetchStartDelta = nav.fetchStart - nav.startTime;
60
+ if (fetchStartDelta < 5) {
61
+ pasteScore += 0.25;
62
+ signals.push("instant_fetch_start");
63
+ } else if (fetchStartDelta < 20) {
64
+ pasteScore += 0.15;
65
+ signals.push("fast_fetch_start");
66
+ }
67
+ const dnsTime = nav.domainLookupEnd - nav.domainLookupStart;
68
+ if (dnsTime === 0) {
69
+ pasteScore += 0.15;
70
+ signals.push("no_dns_lookup");
71
+ }
72
+ const connectTime = nav.connectEnd - nav.connectStart;
73
+ if (connectTime === 0) {
74
+ pasteScore += 0.15;
75
+ signals.push("no_tcp_connect");
76
+ }
77
+ if (nav.redirectCount === 0) {
78
+ pasteScore += 0.1;
79
+ signals.push("no_redirects");
80
+ }
81
+ const timingVariance = calculateTimingVariance(nav);
82
+ if (timingVariance < 10) {
83
+ pasteScore += 0.15;
84
+ signals.push("uniform_timing");
85
+ }
86
+ if (!document.referrer || document.referrer === "") {
87
+ pasteScore += 0.1;
88
+ signals.push("no_referrer");
89
+ }
90
+ const confidence = Math.min(pasteScore, 1);
91
+ const nav_type = pasteScore >= 0.5 ? "likely_paste" : "likely_click";
92
+ return {
93
+ nav_type,
94
+ confidence: Math.round(confidence * 1e3) / 1e3,
95
+ signals
96
+ };
97
+ } catch {
98
+ return { nav_type: "unknown", confidence: 0, signals: ["detection_error"] };
99
+ }
100
+ }
101
+ function calculateTimingVariance(nav) {
102
+ const timings = [
103
+ nav.fetchStart - nav.startTime,
104
+ nav.domainLookupEnd - nav.domainLookupStart,
105
+ nav.connectEnd - nav.connectStart,
106
+ nav.responseStart - nav.requestStart
107
+ ].filter((t) => t >= 0);
108
+ if (timings.length === 0) return 100;
109
+ const mean = timings.reduce((a, b) => a + b, 0) / timings.length;
110
+ const variance = timings.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / timings.length;
111
+ return Math.sqrt(variance);
112
+ }
113
+
114
+ // src/detection/referrer.ts
115
+ function detectAIFromReferrer(referrer) {
116
+ if (!referrer) {
117
+ return null;
118
+ }
119
+ try {
120
+ const url = new URL(referrer);
121
+ const hostname = url.hostname.toLowerCase();
122
+ for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {
123
+ if (hostname.includes(pattern) || referrer.includes(pattern)) {
124
+ return {
125
+ isAI: true,
126
+ platform,
127
+ confidence: 0.95,
128
+ // High confidence when referrer matches
129
+ method: "referrer"
130
+ };
131
+ }
132
+ }
133
+ return null;
134
+ } catch {
135
+ for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {
136
+ if (referrer.toLowerCase().includes(pattern.toLowerCase())) {
137
+ return {
138
+ isAI: true,
139
+ platform,
140
+ confidence: 0.85,
141
+ method: "referrer"
142
+ };
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+ }
148
+ function detectAIFromUTM(url) {
149
+ try {
150
+ const params = new URL(url).searchParams;
151
+ const utmSource = params.get("utm_source")?.toLowerCase();
152
+ if (utmSource) {
153
+ for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {
154
+ if (utmSource.includes(pattern.split(".")[0])) {
155
+ return {
156
+ isAI: true,
157
+ platform,
158
+ confidence: 0.99,
159
+ // Very high confidence from explicit UTM
160
+ method: "referrer"
161
+ };
162
+ }
163
+ }
164
+ if (utmSource.includes("ai") || utmSource.includes("llm") || utmSource.includes("chatbot")) {
165
+ return {
166
+ isAI: true,
167
+ platform: utmSource,
168
+ confidence: 0.9,
169
+ method: "referrer"
170
+ };
171
+ }
172
+ }
173
+ return null;
174
+ } catch {
175
+ return null;
176
+ }
177
+ }
178
+
179
+ // src/utils.ts
180
+ function generateUUID() {
181
+ if (typeof crypto !== "undefined" && crypto.randomUUID) {
182
+ return crypto.randomUUID();
183
+ }
184
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
185
+ const r = Math.random() * 16 | 0;
186
+ const v = c === "x" ? r : r & 3 | 8;
187
+ return v.toString(16);
188
+ });
189
+ }
190
+ function getVisitorId() {
191
+ try {
192
+ const stored = localStorage.getItem("_loamly_vid");
193
+ if (stored) return stored;
194
+ const newId = generateUUID();
195
+ localStorage.setItem("_loamly_vid", newId);
196
+ return newId;
197
+ } catch {
198
+ return generateUUID();
199
+ }
200
+ }
201
+ function getSessionId() {
202
+ try {
203
+ const storedSession = sessionStorage.getItem("loamly_session");
204
+ const storedStart = sessionStorage.getItem("loamly_start");
205
+ if (storedSession && storedStart) {
206
+ return { sessionId: storedSession, isNew: false };
207
+ }
208
+ const newSession = generateUUID();
209
+ const startTime = Date.now().toString();
210
+ sessionStorage.setItem("loamly_session", newSession);
211
+ sessionStorage.setItem("loamly_start", startTime);
212
+ return { sessionId: newSession, isNew: true };
213
+ } catch {
214
+ return { sessionId: generateUUID(), isNew: true };
215
+ }
216
+ }
217
+ function extractUTMParams(url) {
218
+ const params = {};
219
+ try {
220
+ const searchParams = new URL(url).searchParams;
221
+ const utmKeys = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"];
222
+ for (const key of utmKeys) {
223
+ const value = searchParams.get(key);
224
+ if (value) params[key] = value;
225
+ }
226
+ } catch {
227
+ }
228
+ return params;
229
+ }
230
+ function truncateText(text, maxLength) {
231
+ if (text.length <= maxLength) return text;
232
+ return text.substring(0, maxLength - 3) + "...";
233
+ }
234
+ async function safeFetch(url, options, timeout = 1e4) {
235
+ try {
236
+ const controller = new AbortController();
237
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
238
+ const response = await fetch(url, {
239
+ ...options,
240
+ signal: controller.signal
241
+ });
242
+ clearTimeout(timeoutId);
243
+ return response;
244
+ } catch {
245
+ return null;
246
+ }
247
+ }
248
+ function sendBeacon(url, data) {
249
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
250
+ return navigator.sendBeacon(url, JSON.stringify(data));
251
+ }
252
+ return false;
253
+ }
254
+
255
+ // src/core.ts
256
+ var config = { apiHost: DEFAULT_CONFIG.apiHost };
257
+ var initialized = false;
258
+ var debugMode = false;
259
+ var visitorId = null;
260
+ var sessionId = null;
261
+ var sessionStartTime = null;
262
+ var navigationTiming = null;
263
+ var aiDetection = null;
264
+ function log(...args) {
265
+ if (debugMode) {
266
+ console.log("[Loamly]", ...args);
267
+ }
268
+ }
269
+ function endpoint(path) {
270
+ return `${config.apiHost}${path}`;
271
+ }
272
+ function init(userConfig = {}) {
273
+ if (initialized) {
274
+ log("Already initialized");
275
+ return;
276
+ }
277
+ config = {
278
+ ...config,
279
+ ...userConfig,
280
+ apiHost: userConfig.apiHost || DEFAULT_CONFIG.apiHost
281
+ };
282
+ debugMode = userConfig.debug ?? false;
283
+ log("Initializing Loamly Tracker v" + VERSION);
284
+ visitorId = getVisitorId();
285
+ log("Visitor ID:", visitorId);
286
+ const session = getSessionId();
287
+ sessionId = session.sessionId;
288
+ sessionStartTime = Date.now();
289
+ log("Session ID:", sessionId, session.isNew ? "(new)" : "(existing)");
290
+ navigationTiming = detectNavigationType();
291
+ log("Navigation timing:", navigationTiming);
292
+ aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href);
293
+ if (aiDetection) {
294
+ log("AI detected:", aiDetection);
295
+ }
296
+ initialized = true;
297
+ if (!userConfig.disableAutoPageview) {
298
+ pageview();
299
+ }
300
+ if (!userConfig.disableBehavioral) {
301
+ setupBehavioralTracking();
302
+ }
303
+ log("Initialization complete");
304
+ }
305
+ function pageview(customUrl) {
306
+ if (!initialized) {
307
+ log("Not initialized, call init() first");
308
+ return;
309
+ }
310
+ const url = customUrl || window.location.href;
311
+ const payload = {
312
+ visitor_id: visitorId,
313
+ session_id: sessionId,
314
+ url,
315
+ referrer: document.referrer || null,
316
+ title: document.title || null,
317
+ utm_source: extractUTMParams(url).utm_source || null,
318
+ utm_medium: extractUTMParams(url).utm_medium || null,
319
+ utm_campaign: extractUTMParams(url).utm_campaign || null,
320
+ user_agent: navigator.userAgent,
321
+ screen_width: window.screen?.width,
322
+ screen_height: window.screen?.height,
323
+ language: navigator.language,
324
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
325
+ tracker_version: VERSION,
326
+ navigation_timing: navigationTiming,
327
+ ai_platform: aiDetection?.platform || null,
328
+ is_ai_referrer: aiDetection?.isAI || false
329
+ };
330
+ log("Pageview:", payload);
331
+ safeFetch(endpoint(DEFAULT_CONFIG.endpoints.visit), {
332
+ method: "POST",
333
+ headers: { "Content-Type": "application/json" },
334
+ body: JSON.stringify(payload)
335
+ });
336
+ }
337
+ function track(eventName, options = {}) {
338
+ if (!initialized) {
339
+ log("Not initialized, call init() first");
340
+ return;
341
+ }
342
+ const payload = {
343
+ visitor_id: visitorId,
344
+ session_id: sessionId,
345
+ event_name: eventName,
346
+ event_type: "custom",
347
+ properties: options.properties || {},
348
+ revenue: options.revenue,
349
+ currency: options.currency || "USD",
350
+ url: window.location.href,
351
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
352
+ tracker_version: VERSION
353
+ };
354
+ log("Event:", eventName, payload);
355
+ safeFetch(endpoint("/api/ingest/event"), {
356
+ method: "POST",
357
+ headers: { "Content-Type": "application/json" },
358
+ body: JSON.stringify(payload)
359
+ });
360
+ }
361
+ function conversion(eventName, revenue, currency = "USD") {
362
+ track(eventName, { revenue, currency, properties: { type: "conversion" } });
363
+ }
364
+ function identify(userId, traits = {}) {
365
+ if (!initialized) {
366
+ log("Not initialized, call init() first");
367
+ return;
368
+ }
369
+ log("Identify:", userId, traits);
370
+ const payload = {
371
+ visitor_id: visitorId,
372
+ session_id: sessionId,
373
+ user_id: userId,
374
+ traits,
375
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
376
+ };
377
+ safeFetch(endpoint("/api/ingest/identify"), {
378
+ method: "POST",
379
+ headers: { "Content-Type": "application/json" },
380
+ body: JSON.stringify(payload)
381
+ });
382
+ }
383
+ function setupBehavioralTracking() {
384
+ let maxScrollDepth = 0;
385
+ let lastScrollUpdate = 0;
386
+ let lastTimeUpdate = Date.now();
387
+ let scrollTicking = false;
388
+ window.addEventListener("scroll", () => {
389
+ if (!scrollTicking) {
390
+ requestAnimationFrame(() => {
391
+ const scrollPercent = Math.round(
392
+ (window.scrollY + window.innerHeight) / document.documentElement.scrollHeight * 100
393
+ );
394
+ if (scrollPercent > maxScrollDepth) {
395
+ maxScrollDepth = scrollPercent;
396
+ const milestones = [25, 50, 75, 100];
397
+ for (const milestone of milestones) {
398
+ if (scrollPercent >= milestone && lastScrollUpdate < milestone) {
399
+ lastScrollUpdate = milestone;
400
+ sendBehavioralEvent("scroll_depth", { depth: milestone });
401
+ }
402
+ }
403
+ }
404
+ scrollTicking = false;
405
+ });
406
+ scrollTicking = true;
407
+ }
408
+ });
409
+ const trackTimeSpent = () => {
410
+ const now = Date.now();
411
+ const delta = now - lastTimeUpdate;
412
+ if (delta >= DEFAULT_CONFIG.timeSpentThresholdMs) {
413
+ lastTimeUpdate = now;
414
+ sendBehavioralEvent("time_spent", {
415
+ seconds: Math.round(delta / 1e3),
416
+ total_seconds: Math.round((now - (sessionStartTime || now)) / 1e3)
417
+ });
418
+ }
419
+ };
420
+ document.addEventListener("visibilitychange", () => {
421
+ if (document.visibilityState === "hidden") {
422
+ trackTimeSpent();
423
+ }
424
+ });
425
+ window.addEventListener("beforeunload", () => {
426
+ trackTimeSpent();
427
+ if (maxScrollDepth > 0) {
428
+ sendBeacon(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {
429
+ visitor_id: visitorId,
430
+ session_id: sessionId,
431
+ event_type: "scroll_depth_final",
432
+ data: { depth: maxScrollDepth },
433
+ url: window.location.href
434
+ });
435
+ }
436
+ });
437
+ document.addEventListener("focusin", (e) => {
438
+ const target = e.target;
439
+ if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT") {
440
+ sendBehavioralEvent("form_focus", {
441
+ field_type: target.tagName.toLowerCase(),
442
+ field_name: target.name || target.id || "unknown"
443
+ });
444
+ }
445
+ });
446
+ document.addEventListener("submit", (e) => {
447
+ const form = e.target;
448
+ sendBehavioralEvent("form_submit", {
449
+ form_id: form.id || form.name || "unknown",
450
+ form_action: form.action ? new URL(form.action).pathname : "unknown"
451
+ });
452
+ });
453
+ document.addEventListener("click", (e) => {
454
+ const target = e.target;
455
+ const link = target.closest("a");
456
+ if (link && link.href) {
457
+ const isExternal = link.hostname !== window.location.hostname;
458
+ sendBehavioralEvent("click", {
459
+ element: "link",
460
+ href: truncateText(link.href, 200),
461
+ text: truncateText(link.textContent || "", 100),
462
+ is_external: isExternal
463
+ });
464
+ }
465
+ });
466
+ }
467
+ function sendBehavioralEvent(eventType, data) {
468
+ const payload = {
469
+ visitor_id: visitorId,
470
+ session_id: sessionId,
471
+ event_type: eventType,
472
+ data,
473
+ url: window.location.href,
474
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
475
+ tracker_version: VERSION
476
+ };
477
+ log("Behavioral:", eventType, data);
478
+ safeFetch(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {
479
+ method: "POST",
480
+ headers: { "Content-Type": "application/json" },
481
+ body: JSON.stringify(payload)
482
+ });
483
+ }
484
+ function getCurrentSessionId() {
485
+ return sessionId;
486
+ }
487
+ function getCurrentVisitorId() {
488
+ return visitorId;
489
+ }
490
+ function getAIDetectionResult() {
491
+ return aiDetection;
492
+ }
493
+ function getNavigationTimingResult() {
494
+ return navigationTiming;
495
+ }
496
+ function isTrackerInitialized() {
497
+ return initialized;
498
+ }
499
+ function reset() {
500
+ log("Resetting tracker");
501
+ initialized = false;
502
+ visitorId = null;
503
+ sessionId = null;
504
+ sessionStartTime = null;
505
+ navigationTiming = null;
506
+ aiDetection = null;
507
+ try {
508
+ sessionStorage.removeItem("loamly_session");
509
+ sessionStorage.removeItem("loamly_start");
510
+ } catch {
511
+ }
512
+ }
513
+ function setDebug(enabled) {
514
+ debugMode = enabled;
515
+ log("Debug mode:", enabled ? "enabled" : "disabled");
516
+ }
517
+ var loamly = {
518
+ init,
519
+ pageview,
520
+ track,
521
+ conversion,
522
+ identify,
523
+ getSessionId: getCurrentSessionId,
524
+ getVisitorId: getCurrentVisitorId,
525
+ getAIDetection: getAIDetectionResult,
526
+ getNavigationTiming: getNavigationTimingResult,
527
+ isInitialized: isTrackerInitialized,
528
+ reset,
529
+ debug: setDebug
530
+ };
531
+ export {
532
+ AI_BOT_PATTERNS,
533
+ AI_PLATFORMS,
534
+ VERSION,
535
+ loamly as default,
536
+ detectAIFromReferrer,
537
+ detectAIFromUTM,
538
+ detectNavigationType,
539
+ loamly
540
+ };
541
+ /**
542
+ * Loamly Tracker
543
+ *
544
+ * Open-source AI traffic detection for websites.
545
+ * See what AI tells your customers — and track when they click.
546
+ *
547
+ * @module @loamly/tracker
548
+ * @license MIT
549
+ * @see https://loamly.ai
550
+ */
551
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts","../src/detection/navigation-timing.ts","../src/detection/referrer.ts","../src/utils.ts","../src/core.ts"],"sourcesContent":["/**\n * Loamly Tracker Configuration\n * @module @loamly/tracker\n */\n\nexport const VERSION = '1.6.0'\n\nexport const DEFAULT_CONFIG = {\n apiHost: 'https://app.loamly.ai',\n endpoints: {\n visit: '/api/ingest/visit',\n behavioral: '/api/ingest/behavioral',\n session: '/api/ingest/session',\n resolve: '/api/tracker/resolve',\n health: '/api/tracker/health',\n ping: '/api/tracker/ping',\n },\n pingInterval: 30000, // 30 seconds\n batchSize: 10,\n batchTimeout: 5000,\n sessionTimeout: 1800000, // 30 minutes\n maxTextLength: 100,\n timeSpentThresholdMs: 5000, // Only send time_spent when delta >= 5 seconds\n} as const\n\n/**\n * Known AI platforms for referrer detection\n */\nexport const AI_PLATFORMS: Record<string, string> = {\n 'chatgpt.com': 'chatgpt',\n 'chat.openai.com': 'chatgpt',\n 'claude.ai': 'claude',\n 'perplexity.ai': 'perplexity',\n 'bard.google.com': 'bard',\n 'gemini.google.com': 'gemini',\n 'copilot.microsoft.com': 'copilot',\n 'github.com/copilot': 'github-copilot',\n 'you.com': 'you',\n 'phind.com': 'phind',\n 'poe.com': 'poe',\n}\n\n/**\n * User agents of known AI crawlers\n */\nexport const AI_BOT_PATTERNS = [\n 'GPTBot',\n 'ChatGPT-User',\n 'ClaudeBot',\n 'Claude-Web',\n 'PerplexityBot',\n 'Amazonbot',\n 'Google-Extended',\n 'CCBot',\n 'anthropic-ai',\n 'cohere-ai',\n]\n\n\n","/**\n * Navigation Timing API Detection\n * \n * Detects whether the user arrived via paste (from AI chat) vs click\n * by analyzing Navigation Timing API patterns.\n * \n * @module @loamly/tracker/detection\n */\n\nimport type { NavigationTiming } from '../types'\n\n/**\n * Analyze Navigation Timing API to detect paste vs click navigation\n * \n * When users paste a URL (common after copying from AI chat), \n * the timing patterns are distinctive:\n * 1. fetchStart is virtually immediate after navigationStart\n * 2. DNS/connect times are often 0 (cached or direct)\n * 3. No redirect chain\n * 4. Uniform timing patterns\n * \n * @returns NavigationTiming result with type and confidence\n */\nexport function detectNavigationType(): NavigationTiming {\n try {\n const entries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[]\n \n if (!entries || entries.length === 0) {\n return { nav_type: 'unknown', confidence: 0, signals: ['no_timing_data'] }\n }\n\n const nav = entries[0]\n const signals: string[] = []\n let pasteScore = 0\n\n // Signal 1: fetchStart delta from navigationStart\n // Paste navigation typically has very small delta (< 5ms)\n const fetchStartDelta = nav.fetchStart - nav.startTime\n if (fetchStartDelta < 5) {\n pasteScore += 0.25\n signals.push('instant_fetch_start')\n } else if (fetchStartDelta < 20) {\n pasteScore += 0.15\n signals.push('fast_fetch_start')\n }\n\n // Signal 2: DNS lookup time\n // Paste = likely 0 (direct URL entry, no link warmup)\n const dnsTime = nav.domainLookupEnd - nav.domainLookupStart\n if (dnsTime === 0) {\n pasteScore += 0.15\n signals.push('no_dns_lookup')\n }\n\n // Signal 3: TCP connect time\n // Paste = likely 0 (no preconnect from previous page)\n const connectTime = nav.connectEnd - nav.connectStart\n if (connectTime === 0) {\n pasteScore += 0.15\n signals.push('no_tcp_connect')\n }\n\n // Signal 4: No redirects\n // Paste URLs are typically direct, no redirects\n if (nav.redirectCount === 0) {\n pasteScore += 0.1\n signals.push('no_redirects')\n }\n\n // Signal 5: Timing uniformity check\n // Paste navigation tends to have more uniform patterns\n const timingVariance = calculateTimingVariance(nav)\n if (timingVariance < 10) {\n pasteScore += 0.15\n signals.push('uniform_timing')\n }\n\n // Signal 6: No referrer (common for paste)\n if (!document.referrer || document.referrer === '') {\n pasteScore += 0.1\n signals.push('no_referrer')\n }\n\n // Determine navigation type based on score\n const confidence = Math.min(pasteScore, 1)\n const nav_type = pasteScore >= 0.5 ? 'likely_paste' : 'likely_click'\n\n return {\n nav_type,\n confidence: Math.round(confidence * 1000) / 1000,\n signals,\n }\n } catch {\n return { nav_type: 'unknown', confidence: 0, signals: ['detection_error'] }\n }\n}\n\n/**\n * Calculate timing variance to detect paste patterns\n */\nfunction calculateTimingVariance(nav: PerformanceNavigationTiming): number {\n const timings = [\n nav.fetchStart - nav.startTime,\n nav.domainLookupEnd - nav.domainLookupStart,\n nav.connectEnd - nav.connectStart,\n nav.responseStart - nav.requestStart,\n ].filter((t) => t >= 0)\n\n if (timings.length === 0) return 100\n\n const mean = timings.reduce((a, b) => a + b, 0) / timings.length\n const variance = timings.reduce((sum, t) => sum + Math.pow(t - mean, 2), 0) / timings.length\n \n return Math.sqrt(variance)\n}\n\n\n","/**\n * Referrer-based AI Detection\n * \n * Detects when users arrive from known AI platforms\n * based on the document.referrer header.\n * \n * @module @loamly/tracker/detection\n */\n\nimport { AI_PLATFORMS } from '../config'\nimport type { AIDetectionResult } from '../types'\n\n/**\n * Detect AI platform from referrer URL\n * \n * @param referrer - The document.referrer value\n * @returns AI detection result or null if no AI detected\n */\nexport function detectAIFromReferrer(referrer: string): AIDetectionResult | null {\n if (!referrer) {\n return null\n }\n\n try {\n const url = new URL(referrer)\n const hostname = url.hostname.toLowerCase()\n\n // Check against known AI platforms\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (hostname.includes(pattern) || referrer.includes(pattern)) {\n return {\n isAI: true,\n platform,\n confidence: 0.95, // High confidence when referrer matches\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n // Invalid URL, try pattern matching on raw string\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (referrer.toLowerCase().includes(pattern.toLowerCase())) {\n return {\n isAI: true,\n platform,\n confidence: 0.85,\n method: 'referrer',\n }\n }\n }\n return null\n }\n}\n\n/**\n * Extract AI platform from UTM parameters\n * \n * @param url - The current page URL\n * @returns AI platform name or null\n */\nexport function detectAIFromUTM(url: string): AIDetectionResult | null {\n try {\n const params = new URL(url).searchParams\n \n // Check utm_source for AI platforms\n const utmSource = params.get('utm_source')?.toLowerCase()\n if (utmSource) {\n for (const [pattern, platform] of Object.entries(AI_PLATFORMS)) {\n if (utmSource.includes(pattern.split('.')[0])) {\n return {\n isAI: true,\n platform,\n confidence: 0.99, // Very high confidence from explicit UTM\n method: 'referrer',\n }\n }\n }\n \n // Generic AI source patterns\n if (utmSource.includes('ai') || utmSource.includes('llm') || utmSource.includes('chatbot')) {\n return {\n isAI: true,\n platform: utmSource,\n confidence: 0.9,\n method: 'referrer',\n }\n }\n }\n\n return null\n } catch {\n return null\n }\n}\n\n\n","/**\n * Utility functions for Loamly Tracker\n * @module @loamly/tracker\n */\n\n/**\n * Generate a UUID v4\n */\nexport function generateUUID(): string {\n if (typeof crypto !== 'undefined' && crypto.randomUUID) {\n return crypto.randomUUID()\n }\n \n // Fallback for older browsers\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === 'x' ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\n/**\n * Get or create a persistent visitor ID\n * (Privacy-respecting, no cookies)\n */\nexport function getVisitorId(): string {\n // Try to get from localStorage first\n try {\n const stored = localStorage.getItem('_loamly_vid')\n if (stored) return stored\n \n const newId = generateUUID()\n localStorage.setItem('_loamly_vid', newId)\n return newId\n } catch {\n // localStorage not available, generate ephemeral ID\n return generateUUID()\n }\n}\n\n/**\n * Get or create a session ID using sessionStorage\n * (Cookie-free session tracking)\n */\nexport function getSessionId(): { sessionId: string; isNew: boolean } {\n try {\n const storedSession = sessionStorage.getItem('loamly_session')\n const storedStart = sessionStorage.getItem('loamly_start')\n \n if (storedSession && storedStart) {\n return { sessionId: storedSession, isNew: false }\n }\n \n const newSession = generateUUID()\n const startTime = Date.now().toString()\n \n sessionStorage.setItem('loamly_session', newSession)\n sessionStorage.setItem('loamly_start', startTime)\n \n return { sessionId: newSession, isNew: true }\n } catch {\n // sessionStorage not available\n return { sessionId: generateUUID(), isNew: true }\n }\n}\n\n/**\n * Extract UTM parameters from URL\n */\nexport function extractUTMParams(url: string): Record<string, string> {\n const params: Record<string, string> = {}\n \n try {\n const searchParams = new URL(url).searchParams\n const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']\n \n for (const key of utmKeys) {\n const value = searchParams.get(key)\n if (value) params[key] = value\n }\n } catch {\n // Invalid URL\n }\n \n return params\n}\n\n/**\n * Truncate text to max length\n */\nexport function truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text\n return text.substring(0, maxLength - 3) + '...'\n}\n\n/**\n * Safe fetch with timeout\n */\nexport async function safeFetch(\n url: string,\n options: RequestInit,\n timeout = 10000\n): Promise<Response | null> {\n try {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n \n const response = await fetch(url, {\n ...options,\n signal: controller.signal,\n })\n \n clearTimeout(timeoutId)\n return response\n } catch {\n return null\n }\n}\n\n/**\n * Send beacon (for unload events)\n */\nexport function sendBeacon(url: string, data: unknown): boolean {\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n return navigator.sendBeacon(url, JSON.stringify(data))\n }\n return false\n}\n\n\n","/**\n * Loamly Tracker Core\n * \n * Cookie-free, privacy-first analytics with AI traffic detection.\n * \n * @module @loamly/tracker\n */\n\nimport { VERSION, DEFAULT_CONFIG } from './config'\nimport { detectNavigationType } from './detection/navigation-timing'\nimport { detectAIFromReferrer, detectAIFromUTM } from './detection/referrer'\nimport { \n getVisitorId, \n getSessionId, \n extractUTMParams, \n truncateText,\n safeFetch,\n sendBeacon \n} from './utils'\nimport type { \n LoamlyConfig, \n LoamlyTracker, \n TrackEventOptions, \n NavigationTiming,\n AIDetectionResult \n} from './types'\n\n// State\nlet config: LoamlyConfig & { apiHost: string } = { apiHost: DEFAULT_CONFIG.apiHost }\nlet initialized = false\nlet debugMode = false\nlet visitorId: string | null = null\nlet sessionId: string | null = null\nlet sessionStartTime: number | null = null\nlet navigationTiming: NavigationTiming | null = null\nlet aiDetection: AIDetectionResult | null = null\n\n/**\n * Debug logger\n */\nfunction log(...args: unknown[]): void {\n if (debugMode) {\n console.log('[Loamly]', ...args)\n }\n}\n\n/**\n * Build API endpoint URL\n */\nfunction endpoint(path: string): string {\n return `${config.apiHost}${path}`\n}\n\n/**\n * Initialize the tracker\n */\nfunction init(userConfig: LoamlyConfig = {}): void {\n if (initialized) {\n log('Already initialized')\n return\n }\n\n config = {\n ...config,\n ...userConfig,\n apiHost: userConfig.apiHost || DEFAULT_CONFIG.apiHost,\n }\n \n debugMode = userConfig.debug ?? false\n \n log('Initializing Loamly Tracker v' + VERSION)\n \n // Get/create visitor ID\n visitorId = getVisitorId()\n log('Visitor ID:', visitorId)\n \n // Get/create session\n const session = getSessionId()\n sessionId = session.sessionId\n sessionStartTime = Date.now()\n log('Session ID:', sessionId, session.isNew ? '(new)' : '(existing)')\n \n // Detect navigation timing (paste vs click)\n navigationTiming = detectNavigationType()\n log('Navigation timing:', navigationTiming)\n \n // Detect AI from referrer\n aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href)\n if (aiDetection) {\n log('AI detected:', aiDetection)\n }\n \n initialized = true\n \n // Auto pageview unless disabled\n if (!userConfig.disableAutoPageview) {\n pageview()\n }\n \n // Set up behavioral tracking unless disabled\n if (!userConfig.disableBehavioral) {\n setupBehavioralTracking()\n }\n \n log('Initialization complete')\n}\n\n/**\n * Track a page view\n */\nfunction pageview(customUrl?: string): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const url = customUrl || window.location.href\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n url,\n referrer: document.referrer || null,\n title: document.title || null,\n utm_source: extractUTMParams(url).utm_source || null,\n utm_medium: extractUTMParams(url).utm_medium || null,\n utm_campaign: extractUTMParams(url).utm_campaign || null,\n user_agent: navigator.userAgent,\n screen_width: window.screen?.width,\n screen_height: window.screen?.height,\n language: navigator.language,\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\n tracker_version: VERSION,\n navigation_timing: navigationTiming,\n ai_platform: aiDetection?.platform || null,\n is_ai_referrer: aiDetection?.isAI || false,\n }\n\n log('Pageview:', payload)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.visit), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a custom event\n */\nfunction track(eventName: string, options: TrackEventOptions = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_name: eventName,\n event_type: 'custom',\n properties: options.properties || {},\n revenue: options.revenue,\n currency: options.currency || 'USD',\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Event:', eventName, payload)\n\n safeFetch(endpoint('/api/ingest/event'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Track a conversion/revenue event\n */\nfunction conversion(eventName: string, revenue: number, currency = 'USD'): void {\n track(eventName, { revenue, currency, properties: { type: 'conversion' } })\n}\n\n/**\n * Identify a user\n */\nfunction identify(userId: string, traits: Record<string, unknown> = {}): void {\n if (!initialized) {\n log('Not initialized, call init() first')\n return\n }\n\n log('Identify:', userId, traits)\n\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n user_id: userId,\n traits,\n timestamp: new Date().toISOString(),\n }\n\n safeFetch(endpoint('/api/ingest/identify'), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Set up behavioral tracking (scroll, time spent, etc.)\n */\nfunction setupBehavioralTracking(): void {\n let maxScrollDepth = 0\n let lastScrollUpdate = 0\n let lastTimeUpdate = Date.now()\n\n // Scroll tracking with requestAnimationFrame throttling\n let scrollTicking = false\n \n window.addEventListener('scroll', () => {\n if (!scrollTicking) {\n requestAnimationFrame(() => {\n const scrollPercent = Math.round(\n ((window.scrollY + window.innerHeight) / document.documentElement.scrollHeight) * 100\n )\n \n if (scrollPercent > maxScrollDepth) {\n maxScrollDepth = scrollPercent\n \n // Report at milestones (25%, 50%, 75%, 100%)\n const milestones = [25, 50, 75, 100]\n for (const milestone of milestones) {\n if (scrollPercent >= milestone && lastScrollUpdate < milestone) {\n lastScrollUpdate = milestone\n sendBehavioralEvent('scroll_depth', { depth: milestone })\n }\n }\n }\n \n scrollTicking = false\n })\n scrollTicking = true\n }\n })\n\n // Time spent tracking (every 5 seconds minimum)\n const trackTimeSpent = (): void => {\n const now = Date.now()\n const delta = now - lastTimeUpdate\n \n if (delta >= DEFAULT_CONFIG.timeSpentThresholdMs) {\n lastTimeUpdate = now\n sendBehavioralEvent('time_spent', { \n seconds: Math.round(delta / 1000),\n total_seconds: Math.round((now - (sessionStartTime || now)) / 1000)\n })\n }\n }\n\n // Track on visibility change\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n trackTimeSpent()\n }\n })\n\n // Track on page unload\n window.addEventListener('beforeunload', () => {\n trackTimeSpent()\n \n // Send final scroll depth\n if (maxScrollDepth > 0) {\n sendBeacon(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: 'scroll_depth_final',\n data: { depth: maxScrollDepth },\n url: window.location.href,\n })\n }\n })\n\n // Form interaction tracking\n document.addEventListener('focusin', (e) => {\n const target = e.target as HTMLElement\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {\n sendBehavioralEvent('form_focus', {\n field_type: target.tagName.toLowerCase(),\n field_name: (target as HTMLInputElement).name || (target as HTMLInputElement).id || 'unknown',\n })\n }\n })\n\n // Form submit tracking\n document.addEventListener('submit', (e) => {\n const form = e.target as HTMLFormElement\n sendBehavioralEvent('form_submit', {\n form_id: form.id || form.name || 'unknown',\n form_action: form.action ? new URL(form.action).pathname : 'unknown',\n })\n })\n\n // Click tracking for links\n document.addEventListener('click', (e) => {\n const target = e.target as HTMLElement\n const link = target.closest('a')\n \n if (link && link.href) {\n const isExternal = link.hostname !== window.location.hostname\n sendBehavioralEvent('click', {\n element: 'link',\n href: truncateText(link.href, 200),\n text: truncateText(link.textContent || '', 100),\n is_external: isExternal,\n })\n }\n })\n}\n\n/**\n * Send a behavioral event\n */\nfunction sendBehavioralEvent(eventType: string, data: Record<string, unknown>): void {\n const payload = {\n visitor_id: visitorId,\n session_id: sessionId,\n event_type: eventType,\n data,\n url: window.location.href,\n timestamp: new Date().toISOString(),\n tracker_version: VERSION,\n }\n\n log('Behavioral:', eventType, data)\n\n safeFetch(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n}\n\n/**\n * Get current session ID\n */\nfunction getCurrentSessionId(): string | null {\n return sessionId\n}\n\n/**\n * Get current visitor ID\n */\nfunction getCurrentVisitorId(): string | null {\n return visitorId\n}\n\n/**\n * Get AI detection result\n */\nfunction getAIDetectionResult(): AIDetectionResult | null {\n return aiDetection\n}\n\n/**\n * Get navigation timing result\n */\nfunction getNavigationTimingResult(): NavigationTiming | null {\n return navigationTiming\n}\n\n/**\n * Check if initialized\n */\nfunction isTrackerInitialized(): boolean {\n return initialized\n}\n\n/**\n * Reset the tracker\n */\nfunction reset(): void {\n log('Resetting tracker')\n initialized = false\n visitorId = null\n sessionId = null\n sessionStartTime = null\n navigationTiming = null\n aiDetection = null\n \n try {\n sessionStorage.removeItem('loamly_session')\n sessionStorage.removeItem('loamly_start')\n } catch {\n // Ignore\n }\n}\n\n/**\n * Enable/disable debug mode\n */\nfunction setDebug(enabled: boolean): void {\n debugMode = enabled\n log('Debug mode:', enabled ? 'enabled' : 'disabled')\n}\n\n/**\n * The Loamly Tracker instance\n */\nexport const loamly: LoamlyTracker = {\n init,\n pageview,\n track,\n conversion,\n identify,\n getSessionId: getCurrentSessionId,\n getVisitorId: getCurrentVisitorId,\n getAIDetection: getAIDetectionResult,\n getNavigationTiming: getNavigationTimingResult,\n isInitialized: isTrackerInitialized,\n reset,\n debug: setDebug,\n}\n\nexport default loamly\n\n\n"],"mappings":"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","names":[]}