@loamly/tracker 2.0.2 → 2.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loamly/tracker",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "See every AI bot that visits your website. ChatGPT, Claude, Perplexity, Gemini — know when they crawl or refer traffic.",
5
5
  "author": "Loamly <hello@loamly.ai>",
6
6
  "license": "MIT",
package/src/config.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * @see https://github.com/loamly/loamly
7
7
  */
8
8
 
9
- export const VERSION = '2.0.2'
9
+ export const VERSION = '2.1.0'
10
10
 
11
11
  export const DEFAULT_CONFIG = {
12
12
  apiHost: 'https://app.loamly.ai',
package/src/core.ts CHANGED
@@ -117,7 +117,22 @@ function init(userConfig: LoamlyConfig = {}): void {
117
117
 
118
118
  debugMode = userConfig.debug ?? false
119
119
 
120
+ // Feature flags with defaults (all enabled except ping)
121
+ const features = {
122
+ scroll: true,
123
+ time: true,
124
+ forms: true,
125
+ spa: true,
126
+ behavioralML: true,
127
+ focusBlur: true,
128
+ agentic: true,
129
+ eventQueue: true,
130
+ ping: false, // Opt-in only
131
+ ...userConfig.features,
132
+ }
133
+
120
134
  log('Initializing Loamly Tracker v' + VERSION)
135
+ log('Features:', features)
121
136
 
122
137
  // Get/create visitor ID
123
138
  visitorId = getVisitorId()
@@ -128,17 +143,19 @@ function init(userConfig: LoamlyConfig = {}): void {
128
143
  sessionId = session.sessionId
129
144
  log('Session ID:', sessionId, session.isNew ? '(new)' : '(existing)')
130
145
 
131
- // Initialize event queue with batching
132
- eventQueue = new EventQueue(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {
133
- batchSize: DEFAULT_CONFIG.batchSize,
134
- batchTimeout: DEFAULT_CONFIG.batchTimeout,
135
- })
146
+ // Initialize event queue with batching (if enabled)
147
+ if (features.eventQueue) {
148
+ eventQueue = new EventQueue(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {
149
+ batchSize: DEFAULT_CONFIG.batchSize,
150
+ batchTimeout: DEFAULT_CONFIG.batchTimeout,
151
+ })
152
+ }
136
153
 
137
- // Detect navigation timing (paste vs click)
154
+ // Detect navigation timing (paste vs click) - always lightweight
138
155
  navigationTiming = detectNavigationType()
139
156
  log('Navigation timing:', navigationTiming)
140
157
 
141
- // Detect AI from referrer/UTM
158
+ // Detect AI from referrer/UTM - always lightweight
142
159
  aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href)
143
160
  if (aiDetection) {
144
161
  log('AI detected:', aiDetection)
@@ -151,33 +168,39 @@ function init(userConfig: LoamlyConfig = {}): void {
151
168
  pageview()
152
169
  }
153
170
 
154
- // Set up behavioral tracking unless disabled
171
+ // Set up behavioral tracking (scroll, time, forms) unless disabled
155
172
  if (!userConfig.disableBehavioral) {
156
- setupAdvancedBehavioralTracking()
173
+ setupAdvancedBehavioralTracking(features)
157
174
  }
158
175
 
159
- // Initialize behavioral ML classifier (LOA-180)
160
- behavioralClassifier = new BehavioralClassifier(10000) // 10s min session
161
- behavioralClassifier.setOnClassify(handleBehavioralClassification)
162
- setupBehavioralMLTracking()
163
-
164
- // Initialize focus/blur analyzer (LOA-182)
165
- focusBlurAnalyzer = new FocusBlurAnalyzer()
166
- focusBlurAnalyzer.initTracking()
176
+ // Initialize behavioral ML classifier (LOA-180) - if enabled
177
+ if (features.behavioralML) {
178
+ behavioralClassifier = new BehavioralClassifier(10000) // 10s min session
179
+ behavioralClassifier.setOnClassify(handleBehavioralClassification)
180
+ setupBehavioralMLTracking()
181
+ }
167
182
 
168
- // Analyze focus/blur after 5 seconds
169
- setTimeout(() => {
170
- if (focusBlurAnalyzer) {
171
- handleFocusBlurAnalysis(focusBlurAnalyzer.analyze())
172
- }
173
- }, 5000)
183
+ // Initialize focus/blur analyzer (LOA-182) - if enabled
184
+ if (features.focusBlur) {
185
+ focusBlurAnalyzer = new FocusBlurAnalyzer()
186
+ focusBlurAnalyzer.initTracking()
187
+
188
+ // Analyze focus/blur after 5 seconds
189
+ setTimeout(() => {
190
+ if (focusBlurAnalyzer) {
191
+ handleFocusBlurAnalysis(focusBlurAnalyzer.analyze())
192
+ }
193
+ }, 5000)
194
+ }
174
195
 
175
- // Initialize agentic browser detection (LOA-187)
176
- agenticAnalyzer = new AgenticBrowserAnalyzer()
177
- agenticAnalyzer.init()
196
+ // Initialize agentic browser detection (LOA-187) - if enabled
197
+ if (features.agentic) {
198
+ agenticAnalyzer = new AgenticBrowserAnalyzer()
199
+ agenticAnalyzer.init()
200
+ }
178
201
 
179
- // Set up ping service
180
- if (visitorId && sessionId) {
202
+ // Set up ping service - if enabled (opt-in)
203
+ if (features.ping && visitorId && sessionId) {
181
204
  pingService = new PingService(sessionId, visitorId, VERSION, {
182
205
  interval: DEFAULT_CONFIG.pingInterval,
183
206
  endpoint: endpoint(DEFAULT_CONFIG.endpoints.ping),
@@ -203,54 +226,83 @@ function init(userConfig: LoamlyConfig = {}): void {
203
226
  /**
204
227
  * Set up advanced behavioral tracking with new modules
205
228
  */
206
- function setupAdvancedBehavioralTracking(): void {
207
- // Scroll tracker with 30% chunks
208
- scrollTracker = new ScrollTracker({
209
- chunks: [30, 60, 90, 100],
210
- onChunkReached: (event: ScrollEvent) => {
211
- log('Scroll chunk:', event.chunk)
212
- queueEvent('scroll_depth', {
213
- depth: event.depth,
214
- chunk: event.chunk,
215
- time_to_reach_ms: event.time_to_reach_ms,
216
- })
217
- },
218
- })
219
- scrollTracker.start()
229
+ interface FeatureFlags {
230
+ scroll?: boolean
231
+ time?: boolean
232
+ forms?: boolean
233
+ spa?: boolean
234
+ behavioralML?: boolean
235
+ focusBlur?: boolean
236
+ agentic?: boolean
237
+ eventQueue?: boolean
238
+ ping?: boolean
239
+ }
240
+
241
+ function setupAdvancedBehavioralTracking(features: FeatureFlags): void {
242
+ // Scroll tracker with 30% chunks (if enabled)
243
+ if (features.scroll) {
244
+ scrollTracker = new ScrollTracker({
245
+ chunks: [30, 60, 90, 100],
246
+ onChunkReached: (event: ScrollEvent) => {
247
+ log('Scroll chunk:', event.chunk)
248
+ queueEvent('scroll_depth', {
249
+ depth: event.depth,
250
+ chunk: event.chunk,
251
+ time_to_reach_ms: event.time_to_reach_ms,
252
+ })
253
+ },
254
+ })
255
+ scrollTracker.start()
256
+ }
220
257
 
221
- // Time tracker
222
- timeTracker = new TimeTracker({
223
- updateIntervalMs: 10000, // Report every 10 seconds
224
- onUpdate: (event: TimeEvent) => {
225
- if (event.active_time_ms >= DEFAULT_CONFIG.timeSpentThresholdMs) {
226
- queueEvent('time_spent', {
227
- active_time_ms: event.active_time_ms,
228
- total_time_ms: event.total_time_ms,
229
- idle_time_ms: event.idle_time_ms,
230
- is_engaged: event.is_engaged,
258
+ // Time tracker (if enabled)
259
+ if (features.time) {
260
+ timeTracker = new TimeTracker({
261
+ updateIntervalMs: 10000, // Report every 10 seconds
262
+ onUpdate: (event: TimeEvent) => {
263
+ if (event.active_time_ms >= DEFAULT_CONFIG.timeSpentThresholdMs) {
264
+ queueEvent('time_spent', {
265
+ active_time_ms: event.active_time_ms,
266
+ total_time_ms: event.total_time_ms,
267
+ idle_time_ms: event.idle_time_ms,
268
+ is_engaged: event.is_engaged,
269
+ })
270
+ }
271
+ },
272
+ })
273
+ timeTracker.start()
274
+ }
275
+
276
+ // Form tracker with universal support (if enabled)
277
+ if (features.forms) {
278
+ formTracker = new FormTracker({
279
+ onFormEvent: (event: FormEvent) => {
280
+ log('Form event:', event.event_type, event.form_id)
281
+ queueEvent(event.event_type, {
282
+ form_id: event.form_id,
283
+ form_type: event.form_type,
284
+ field_name: event.field_name,
285
+ field_type: event.field_type,
286
+ time_to_submit_ms: event.time_to_submit_ms,
287
+ is_conversion: event.is_conversion,
231
288
  })
232
- }
233
- },
234
- })
235
- timeTracker.start()
236
-
237
- // Form tracker with universal support
238
- formTracker = new FormTracker({
239
- onFormEvent: (event: FormEvent) => {
240
- log('Form event:', event.event_type, event.form_id)
241
- queueEvent(event.event_type, {
242
- form_id: event.form_id,
243
- form_type: event.form_type,
244
- field_name: event.field_name,
245
- field_type: event.field_type,
246
- time_to_submit_ms: event.time_to_submit_ms,
247
- is_conversion: event.is_conversion,
248
- })
249
- },
250
- })
251
- formTracker.start()
289
+ },
290
+ })
291
+ formTracker.start()
292
+ }
293
+
294
+ // SPA router (if enabled)
295
+ if (features.spa) {
296
+ spaRouter = new SPARouter({
297
+ onNavigate: (event: NavigationEvent) => {
298
+ log('SPA navigation:', event.navigation_type)
299
+ pageview(event.to_url)
300
+ },
301
+ })
302
+ spaRouter.start()
303
+ }
252
304
 
253
- // Click tracking for links (basic)
305
+ // Click tracking for links (always enabled, lightweight)
254
306
  document.addEventListener('click', (e) => {
255
307
  const target = e.target as HTMLElement
256
308
  const link = target.closest('a')
package/src/types.ts CHANGED
@@ -21,6 +21,31 @@ export interface LoamlyConfig {
21
21
 
22
22
  /** Custom session timeout in milliseconds (default: 30 minutes) */
23
23
  sessionTimeout?: number
24
+
25
+ /**
26
+ * Feature flags for lightweight mode
27
+ * Set to false to reduce initialization overhead
28
+ */
29
+ features?: {
30
+ /** Scroll depth tracking (default: true) */
31
+ scroll?: boolean
32
+ /** Time on page tracking (default: true) */
33
+ time?: boolean
34
+ /** Form interaction tracking (default: true) */
35
+ forms?: boolean
36
+ /** SPA navigation support (default: true) */
37
+ spa?: boolean
38
+ /** Behavioral ML classification (default: true) */
39
+ behavioralML?: boolean
40
+ /** Focus/blur paste detection (default: true) */
41
+ focusBlur?: boolean
42
+ /** Agentic browser detection (default: true) */
43
+ agentic?: boolean
44
+ /** Event queue with retry (default: true) */
45
+ eventQueue?: boolean
46
+ /** Heartbeat ping service (default: false - opt-in) */
47
+ ping?: boolean
48
+ }
24
49
  }
25
50
 
26
51
  export interface TrackEventOptions {