@loamly/tracker 2.0.1 → 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/README.md CHANGED
@@ -110,6 +110,29 @@ loamly.init({
110
110
  });
111
111
  ```
112
112
 
113
+ ### Lightweight Mode
114
+
115
+ Disable specific features to reduce CPU/memory overhead:
116
+
117
+ ```typescript
118
+ loamly.init({
119
+ apiKey: 'your-api-key',
120
+ features: {
121
+ scroll: true, // Scroll depth tracking (default: true)
122
+ time: true, // Time on page tracking (default: true)
123
+ forms: true, // Form interaction tracking (default: true)
124
+ spa: true, // SPA navigation support (default: true)
125
+ behavioralML: false, // Behavioral ML classification (default: true) - saves ~2KB CPU
126
+ focusBlur: true, // Focus/blur paste detection (default: true)
127
+ agentic: false, // Agentic browser detection (default: true) - saves ~1.5KB CPU
128
+ eventQueue: true, // Event queue with retry (default: true)
129
+ ping: false, // Heartbeat ping service (default: false - opt-in)
130
+ }
131
+ });
132
+ ```
133
+
134
+ **Note:** All features are included in the bundle (~11KB gzipped). The `features` config only affects runtime initialization, not download size.
135
+
113
136
  ### `track(eventName, options?)`
114
137
 
115
138
  Track a custom event.
package/dist/index.cjs CHANGED
@@ -34,7 +34,7 @@ __export(index_exports, {
34
34
  module.exports = __toCommonJS(index_exports);
35
35
 
36
36
  // src/config.ts
37
- var VERSION = "2.0.1";
37
+ var VERSION = "2.1.0";
38
38
  var DEFAULT_CONFIG = {
39
39
  apiHost: "https://app.loamly.ai",
40
40
  endpoints: {
@@ -1805,16 +1805,32 @@ function init(userConfig = {}) {
1805
1805
  apiHost: userConfig.apiHost || DEFAULT_CONFIG.apiHost
1806
1806
  };
1807
1807
  debugMode = userConfig.debug ?? false;
1808
+ const features = {
1809
+ scroll: true,
1810
+ time: true,
1811
+ forms: true,
1812
+ spa: true,
1813
+ behavioralML: true,
1814
+ focusBlur: true,
1815
+ agentic: true,
1816
+ eventQueue: true,
1817
+ ping: false,
1818
+ // Opt-in only
1819
+ ...userConfig.features
1820
+ };
1808
1821
  log("Initializing Loamly Tracker v" + VERSION);
1822
+ log("Features:", features);
1809
1823
  visitorId = getVisitorId();
1810
1824
  log("Visitor ID:", visitorId);
1811
1825
  const session = getSessionId();
1812
1826
  sessionId = session.sessionId;
1813
1827
  log("Session ID:", sessionId, session.isNew ? "(new)" : "(existing)");
1814
- eventQueue = new EventQueue(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {
1815
- batchSize: DEFAULT_CONFIG.batchSize,
1816
- batchTimeout: DEFAULT_CONFIG.batchTimeout
1817
- });
1828
+ if (features.eventQueue) {
1829
+ eventQueue = new EventQueue(endpoint(DEFAULT_CONFIG.endpoints.behavioral), {
1830
+ batchSize: DEFAULT_CONFIG.batchSize,
1831
+ batchTimeout: DEFAULT_CONFIG.batchTimeout
1832
+ });
1833
+ }
1818
1834
  navigationTiming = detectNavigationType();
1819
1835
  log("Navigation timing:", navigationTiming);
1820
1836
  aiDetection = detectAIFromReferrer(document.referrer) || detectAIFromUTM(window.location.href);
@@ -1826,21 +1842,27 @@ function init(userConfig = {}) {
1826
1842
  pageview();
1827
1843
  }
1828
1844
  if (!userConfig.disableBehavioral) {
1829
- setupAdvancedBehavioralTracking();
1845
+ setupAdvancedBehavioralTracking(features);
1846
+ }
1847
+ if (features.behavioralML) {
1848
+ behavioralClassifier = new BehavioralClassifier(1e4);
1849
+ behavioralClassifier.setOnClassify(handleBehavioralClassification);
1850
+ setupBehavioralMLTracking();
1851
+ }
1852
+ if (features.focusBlur) {
1853
+ focusBlurAnalyzer = new FocusBlurAnalyzer();
1854
+ focusBlurAnalyzer.initTracking();
1855
+ setTimeout(() => {
1856
+ if (focusBlurAnalyzer) {
1857
+ handleFocusBlurAnalysis(focusBlurAnalyzer.analyze());
1858
+ }
1859
+ }, 5e3);
1830
1860
  }
1831
- behavioralClassifier = new BehavioralClassifier(1e4);
1832
- behavioralClassifier.setOnClassify(handleBehavioralClassification);
1833
- setupBehavioralMLTracking();
1834
- focusBlurAnalyzer = new FocusBlurAnalyzer();
1835
- focusBlurAnalyzer.initTracking();
1836
- setTimeout(() => {
1837
- if (focusBlurAnalyzer) {
1838
- handleFocusBlurAnalysis(focusBlurAnalyzer.analyze());
1839
- }
1840
- }, 5e3);
1841
- agenticAnalyzer = new AgenticBrowserAnalyzer();
1842
- agenticAnalyzer.init();
1843
- if (visitorId && sessionId) {
1861
+ if (features.agentic) {
1862
+ agenticAnalyzer = new AgenticBrowserAnalyzer();
1863
+ agenticAnalyzer.init();
1864
+ }
1865
+ if (features.ping && visitorId && sessionId) {
1844
1866
  pingService = new PingService(sessionId, visitorId, VERSION, {
1845
1867
  interval: DEFAULT_CONFIG.pingInterval,
1846
1868
  endpoint: endpoint(DEFAULT_CONFIG.endpoints.ping)
@@ -1852,50 +1874,66 @@ function init(userConfig = {}) {
1852
1874
  });
1853
1875
  spaRouter.start();
1854
1876
  setupUnloadHandlers();
1877
+ reportHealth("initialized");
1855
1878
  log("Initialization complete");
1856
1879
  }
1857
- function setupAdvancedBehavioralTracking() {
1858
- scrollTracker = new ScrollTracker({
1859
- chunks: [30, 60, 90, 100],
1860
- onChunkReached: (event) => {
1861
- log("Scroll chunk:", event.chunk);
1862
- queueEvent("scroll_depth", {
1863
- depth: event.depth,
1864
- chunk: event.chunk,
1865
- time_to_reach_ms: event.time_to_reach_ms
1866
- });
1867
- }
1868
- });
1869
- scrollTracker.start();
1870
- timeTracker = new TimeTracker({
1871
- updateIntervalMs: 1e4,
1872
- // Report every 10 seconds
1873
- onUpdate: (event) => {
1874
- if (event.active_time_ms >= DEFAULT_CONFIG.timeSpentThresholdMs) {
1875
- queueEvent("time_spent", {
1876
- active_time_ms: event.active_time_ms,
1877
- total_time_ms: event.total_time_ms,
1878
- idle_time_ms: event.idle_time_ms,
1879
- is_engaged: event.is_engaged
1880
+ function setupAdvancedBehavioralTracking(features) {
1881
+ if (features.scroll) {
1882
+ scrollTracker = new ScrollTracker({
1883
+ chunks: [30, 60, 90, 100],
1884
+ onChunkReached: (event) => {
1885
+ log("Scroll chunk:", event.chunk);
1886
+ queueEvent("scroll_depth", {
1887
+ depth: event.depth,
1888
+ chunk: event.chunk,
1889
+ time_to_reach_ms: event.time_to_reach_ms
1880
1890
  });
1881
1891
  }
1882
- }
1883
- });
1884
- timeTracker.start();
1885
- formTracker = new FormTracker({
1886
- onFormEvent: (event) => {
1887
- log("Form event:", event.event_type, event.form_id);
1888
- queueEvent(event.event_type, {
1889
- form_id: event.form_id,
1890
- form_type: event.form_type,
1891
- field_name: event.field_name,
1892
- field_type: event.field_type,
1893
- time_to_submit_ms: event.time_to_submit_ms,
1894
- is_conversion: event.is_conversion
1895
- });
1896
- }
1897
- });
1898
- formTracker.start();
1892
+ });
1893
+ scrollTracker.start();
1894
+ }
1895
+ if (features.time) {
1896
+ timeTracker = new TimeTracker({
1897
+ updateIntervalMs: 1e4,
1898
+ // Report every 10 seconds
1899
+ onUpdate: (event) => {
1900
+ if (event.active_time_ms >= DEFAULT_CONFIG.timeSpentThresholdMs) {
1901
+ queueEvent("time_spent", {
1902
+ active_time_ms: event.active_time_ms,
1903
+ total_time_ms: event.total_time_ms,
1904
+ idle_time_ms: event.idle_time_ms,
1905
+ is_engaged: event.is_engaged
1906
+ });
1907
+ }
1908
+ }
1909
+ });
1910
+ timeTracker.start();
1911
+ }
1912
+ if (features.forms) {
1913
+ formTracker = new FormTracker({
1914
+ onFormEvent: (event) => {
1915
+ log("Form event:", event.event_type, event.form_id);
1916
+ queueEvent(event.event_type, {
1917
+ form_id: event.form_id,
1918
+ form_type: event.form_type,
1919
+ field_name: event.field_name,
1920
+ field_type: event.field_type,
1921
+ time_to_submit_ms: event.time_to_submit_ms,
1922
+ is_conversion: event.is_conversion
1923
+ });
1924
+ }
1925
+ });
1926
+ formTracker.start();
1927
+ }
1928
+ if (features.spa) {
1929
+ spaRouter = new SPARouter({
1930
+ onNavigate: (event) => {
1931
+ log("SPA navigation:", event.navigation_type);
1932
+ pageview(event.to_url);
1933
+ }
1934
+ });
1935
+ spaRouter.start();
1936
+ }
1899
1937
  document.addEventListener("click", (e) => {
1900
1938
  const target = e.target;
1901
1939
  const link = target.closest("a");
@@ -2197,6 +2235,39 @@ function getAgenticResult() {
2197
2235
  function isTrackerInitialized() {
2198
2236
  return initialized;
2199
2237
  }
2238
+ function reportHealth(status, errorMessage) {
2239
+ if (!config.apiKey) return;
2240
+ try {
2241
+ const healthData = {
2242
+ workspace_id: config.apiKey,
2243
+ status,
2244
+ error_message: errorMessage || null,
2245
+ version: VERSION,
2246
+ url: typeof window !== "undefined" ? window.location.href : null,
2247
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : null,
2248
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2249
+ features: {
2250
+ scroll_tracker: !!scrollTracker,
2251
+ time_tracker: !!timeTracker,
2252
+ form_tracker: !!formTracker,
2253
+ spa_router: !!spaRouter,
2254
+ behavioral_ml: !!behavioralClassifier,
2255
+ focus_blur: !!focusBlurAnalyzer,
2256
+ agentic: !!agenticAnalyzer,
2257
+ ping_service: !!pingService,
2258
+ event_queue: !!eventQueue
2259
+ }
2260
+ };
2261
+ safeFetch(endpoint(DEFAULT_CONFIG.endpoints.health), {
2262
+ method: "POST",
2263
+ headers: { "Content-Type": "application/json" },
2264
+ body: JSON.stringify(healthData)
2265
+ }).catch(() => {
2266
+ });
2267
+ log("Health reported:", status);
2268
+ } catch {
2269
+ }
2270
+ }
2200
2271
  function reset() {
2201
2272
  log("Resetting tracker");
2202
2273
  pingService?.stop();
@@ -2246,7 +2317,8 @@ var loamly = {
2246
2317
  getAgentic: getAgenticResult,
2247
2318
  isInitialized: isTrackerInitialized,
2248
2319
  reset,
2249
- debug: setDebug
2320
+ debug: setDebug,
2321
+ reportHealth
2250
2322
  };
2251
2323
  // Annotate the CommonJS export names for ESM import in node:
2252
2324
  0 && (module.exports = {