@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/README.md +82 -0
- package/dist/index.cjs +584 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.mjs +551 -0
- package/dist/index.mjs.map +1 -0
- package/dist/loamly.iife.global.js +594 -0
- package/dist/loamly.iife.global.js.map +1 -0
- package/dist/loamly.iife.min.global.js +2 -0
- package/dist/loamly.iife.min.global.js.map +1 -0
- package/package.json +68 -0
- package/src/browser.ts +81 -0
- package/src/config.ts +59 -0
- package/src/core.ts +428 -0
- package/src/detection/index.ts +13 -0
- package/src/detection/navigation-timing.ts +117 -0
- package/src/detection/referrer.ts +98 -0
- package/src/index.ts +31 -0
- package/src/types.ts +100 -0
- package/src/utils.ts +130 -0
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
|
+
|