@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/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.2";
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)
@@ -1855,48 +1877,63 @@ function init(userConfig = {}) {
1855
1877
  reportHealth("initialized");
1856
1878
  log("Initialization complete");
1857
1879
  }
1858
- function setupAdvancedBehavioralTracking() {
1859
- scrollTracker = new ScrollTracker({
1860
- chunks: [30, 60, 90, 100],
1861
- onChunkReached: (event) => {
1862
- log("Scroll chunk:", event.chunk);
1863
- queueEvent("scroll_depth", {
1864
- depth: event.depth,
1865
- chunk: event.chunk,
1866
- time_to_reach_ms: event.time_to_reach_ms
1867
- });
1868
- }
1869
- });
1870
- scrollTracker.start();
1871
- timeTracker = new TimeTracker({
1872
- updateIntervalMs: 1e4,
1873
- // Report every 10 seconds
1874
- onUpdate: (event) => {
1875
- if (event.active_time_ms >= DEFAULT_CONFIG.timeSpentThresholdMs) {
1876
- queueEvent("time_spent", {
1877
- active_time_ms: event.active_time_ms,
1878
- total_time_ms: event.total_time_ms,
1879
- idle_time_ms: event.idle_time_ms,
1880
- 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
1881
1890
  });
1882
1891
  }
1883
- }
1884
- });
1885
- timeTracker.start();
1886
- formTracker = new FormTracker({
1887
- onFormEvent: (event) => {
1888
- log("Form event:", event.event_type, event.form_id);
1889
- queueEvent(event.event_type, {
1890
- form_id: event.form_id,
1891
- form_type: event.form_type,
1892
- field_name: event.field_name,
1893
- field_type: event.field_type,
1894
- time_to_submit_ms: event.time_to_submit_ms,
1895
- is_conversion: event.is_conversion
1896
- });
1897
- }
1898
- });
1899
- 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
+ }
1900
1937
  document.addEventListener("click", (e) => {
1901
1938
  const target = e.target;
1902
1939
  const link = target.closest("a");