@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/src/types.ts ADDED
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Loamly Tracker Types
3
+ * @module @loamly/tracker
4
+ */
5
+
6
+ export interface LoamlyConfig {
7
+ /** Your Loamly API key (found in dashboard) */
8
+ apiKey?: string
9
+
10
+ /** Custom API host (default: https://app.loamly.ai) */
11
+ apiHost?: string
12
+
13
+ /** Enable debug mode for console logging */
14
+ debug?: boolean
15
+
16
+ /** Disable automatic page view tracking */
17
+ disableAutoPageview?: boolean
18
+
19
+ /** Disable behavioral tracking (scroll, time, forms) */
20
+ disableBehavioral?: boolean
21
+
22
+ /** Custom session timeout in milliseconds (default: 30 minutes) */
23
+ sessionTimeout?: number
24
+ }
25
+
26
+ export interface TrackEventOptions {
27
+ /** Custom properties to attach to the event */
28
+ properties?: Record<string, unknown>
29
+
30
+ /** Revenue amount for conversion events */
31
+ revenue?: number
32
+
33
+ /** Currency code (default: USD) */
34
+ currency?: string
35
+ }
36
+
37
+ export interface NavigationTiming {
38
+ /** Navigation type: 'likely_paste' or 'likely_click' */
39
+ nav_type: 'likely_paste' | 'likely_click' | 'unknown'
40
+
41
+ /** Confidence score (0-1) */
42
+ confidence: number
43
+
44
+ /** Detection signals used */
45
+ signals: string[]
46
+ }
47
+
48
+ export interface AIDetectionResult {
49
+ /** Whether AI was detected as the source */
50
+ isAI: boolean
51
+
52
+ /** AI platform name if detected */
53
+ platform?: string
54
+
55
+ /** Confidence score (0-1) */
56
+ confidence: number
57
+
58
+ /** Detection method used */
59
+ method: 'referrer' | 'timing' | 'behavioral' | 'temporal' | 'unknown'
60
+ }
61
+
62
+ export interface LoamlyTracker {
63
+ /** Initialize the tracker with configuration */
64
+ init: (config: LoamlyConfig) => void
65
+
66
+ /** Track a page view (called automatically unless disabled) */
67
+ pageview: (url?: string) => void
68
+
69
+ /** Track a custom event */
70
+ track: (eventName: string, options?: TrackEventOptions) => void
71
+
72
+ /** Track a conversion/revenue event */
73
+ conversion: (eventName: string, revenue: number, currency?: string) => void
74
+
75
+ /** Identify a user (for linking sessions) */
76
+ identify: (userId: string, traits?: Record<string, unknown>) => void
77
+
78
+ /** Get the current session ID */
79
+ getSessionId: () => string | null
80
+
81
+ /** Get the current visitor ID */
82
+ getVisitorId: () => string | null
83
+
84
+ /** Get AI detection result for current page */
85
+ getAIDetection: () => AIDetectionResult | null
86
+
87
+ /** Get navigation timing analysis */
88
+ getNavigationTiming: () => NavigationTiming | null
89
+
90
+ /** Check if tracker is initialized */
91
+ isInitialized: () => boolean
92
+
93
+ /** Reset the tracker (clears session) */
94
+ reset: () => void
95
+
96
+ /** Enable debug mode */
97
+ debug: (enabled: boolean) => void
98
+ }
99
+
100
+
package/src/utils.ts ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Utility functions for Loamly Tracker
3
+ * @module @loamly/tracker
4
+ */
5
+
6
+ /**
7
+ * Generate a UUID v4
8
+ */
9
+ export function generateUUID(): string {
10
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
11
+ return crypto.randomUUID()
12
+ }
13
+
14
+ // Fallback for older browsers
15
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
16
+ const r = (Math.random() * 16) | 0
17
+ const v = c === 'x' ? r : (r & 0x3) | 0x8
18
+ return v.toString(16)
19
+ })
20
+ }
21
+
22
+ /**
23
+ * Get or create a persistent visitor ID
24
+ * (Privacy-respecting, no cookies)
25
+ */
26
+ export function getVisitorId(): string {
27
+ // Try to get from localStorage first
28
+ try {
29
+ const stored = localStorage.getItem('_loamly_vid')
30
+ if (stored) return stored
31
+
32
+ const newId = generateUUID()
33
+ localStorage.setItem('_loamly_vid', newId)
34
+ return newId
35
+ } catch {
36
+ // localStorage not available, generate ephemeral ID
37
+ return generateUUID()
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Get or create a session ID using sessionStorage
43
+ * (Cookie-free session tracking)
44
+ */
45
+ export function getSessionId(): { sessionId: string; isNew: boolean } {
46
+ try {
47
+ const storedSession = sessionStorage.getItem('loamly_session')
48
+ const storedStart = sessionStorage.getItem('loamly_start')
49
+
50
+ if (storedSession && storedStart) {
51
+ return { sessionId: storedSession, isNew: false }
52
+ }
53
+
54
+ const newSession = generateUUID()
55
+ const startTime = Date.now().toString()
56
+
57
+ sessionStorage.setItem('loamly_session', newSession)
58
+ sessionStorage.setItem('loamly_start', startTime)
59
+
60
+ return { sessionId: newSession, isNew: true }
61
+ } catch {
62
+ // sessionStorage not available
63
+ return { sessionId: generateUUID(), isNew: true }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Extract UTM parameters from URL
69
+ */
70
+ export function extractUTMParams(url: string): Record<string, string> {
71
+ const params: Record<string, string> = {}
72
+
73
+ try {
74
+ const searchParams = new URL(url).searchParams
75
+ const utmKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']
76
+
77
+ for (const key of utmKeys) {
78
+ const value = searchParams.get(key)
79
+ if (value) params[key] = value
80
+ }
81
+ } catch {
82
+ // Invalid URL
83
+ }
84
+
85
+ return params
86
+ }
87
+
88
+ /**
89
+ * Truncate text to max length
90
+ */
91
+ export function truncateText(text: string, maxLength: number): string {
92
+ if (text.length <= maxLength) return text
93
+ return text.substring(0, maxLength - 3) + '...'
94
+ }
95
+
96
+ /**
97
+ * Safe fetch with timeout
98
+ */
99
+ export async function safeFetch(
100
+ url: string,
101
+ options: RequestInit,
102
+ timeout = 10000
103
+ ): Promise<Response | null> {
104
+ try {
105
+ const controller = new AbortController()
106
+ const timeoutId = setTimeout(() => controller.abort(), timeout)
107
+
108
+ const response = await fetch(url, {
109
+ ...options,
110
+ signal: controller.signal,
111
+ })
112
+
113
+ clearTimeout(timeoutId)
114
+ return response
115
+ } catch {
116
+ return null
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Send beacon (for unload events)
122
+ */
123
+ export function sendBeacon(url: string, data: unknown): boolean {
124
+ if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
125
+ return navigator.sendBeacon(url, JSON.stringify(data))
126
+ }
127
+ return false
128
+ }
129
+
130
+