@grainql/analytics-web 2.5.4 → 2.7.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.
Files changed (99) hide show
  1. package/README.md +3 -1
  2. package/dist/activity.js +1 -1
  3. package/dist/attention-quality.d.ts +128 -0
  4. package/dist/attention-quality.d.ts.map +1 -0
  5. package/dist/attention-quality.js +246 -0
  6. package/dist/cjs/activity.js +1 -1
  7. package/dist/cjs/activity.js.map +1 -1
  8. package/dist/cjs/attention-quality.d.ts +128 -0
  9. package/dist/cjs/attention-quality.d.ts.map +1 -0
  10. package/dist/cjs/attention-quality.js +246 -0
  11. package/dist/cjs/attention-quality.js.map +1 -0
  12. package/dist/cjs/consent.js +4 -4
  13. package/dist/cjs/consent.js.map +1 -1
  14. package/dist/cjs/heartbeat.d.ts.map +1 -1
  15. package/dist/cjs/heartbeat.js +0 -6
  16. package/dist/cjs/heartbeat.js.map +1 -1
  17. package/dist/cjs/heatmap-tracking.d.ts +93 -0
  18. package/dist/cjs/heatmap-tracking.d.ts.map +1 -0
  19. package/dist/cjs/heatmap-tracking.js +499 -0
  20. package/dist/cjs/heatmap-tracking.js.map +1 -0
  21. package/dist/cjs/index.d.ts +11 -0
  22. package/dist/cjs/index.d.ts.map +1 -1
  23. package/dist/cjs/index.js.map +1 -1
  24. package/dist/cjs/interaction-tracking.d.ts.map +1 -1
  25. package/dist/cjs/interaction-tracking.js +9 -18
  26. package/dist/cjs/interaction-tracking.js.map +1 -1
  27. package/dist/cjs/page-tracking.d.ts.map +1 -1
  28. package/dist/cjs/page-tracking.js +0 -9
  29. package/dist/cjs/page-tracking.js.map +1 -1
  30. package/dist/cjs/section-tracking.d.ts +3 -0
  31. package/dist/cjs/section-tracking.d.ts.map +1 -1
  32. package/dist/cjs/section-tracking.js +30 -7
  33. package/dist/cjs/section-tracking.js.map +1 -1
  34. package/dist/cjs/types/auto-tracking.d.ts +3 -0
  35. package/dist/cjs/types/auto-tracking.d.ts.map +1 -1
  36. package/dist/cjs/types/heatmap-tracking.d.ts +44 -0
  37. package/dist/cjs/types/heatmap-tracking.d.ts.map +1 -0
  38. package/dist/cjs/types/heatmap-tracking.js +6 -0
  39. package/dist/cjs/types/heatmap-tracking.js.map +1 -0
  40. package/dist/consent.js +4 -4
  41. package/dist/esm/activity.js +1 -1
  42. package/dist/esm/activity.js.map +1 -1
  43. package/dist/esm/attention-quality.d.ts +128 -0
  44. package/dist/esm/attention-quality.d.ts.map +1 -0
  45. package/dist/esm/attention-quality.js +242 -0
  46. package/dist/esm/attention-quality.js.map +1 -0
  47. package/dist/esm/consent.js +4 -4
  48. package/dist/esm/consent.js.map +1 -1
  49. package/dist/esm/heartbeat.d.ts.map +1 -1
  50. package/dist/esm/heartbeat.js +0 -6
  51. package/dist/esm/heartbeat.js.map +1 -1
  52. package/dist/esm/heatmap-tracking.d.ts +93 -0
  53. package/dist/esm/heatmap-tracking.d.ts.map +1 -0
  54. package/dist/esm/heatmap-tracking.js +495 -0
  55. package/dist/esm/heatmap-tracking.js.map +1 -0
  56. package/dist/esm/index.d.ts +11 -0
  57. package/dist/esm/index.d.ts.map +1 -1
  58. package/dist/esm/index.js.map +1 -1
  59. package/dist/esm/interaction-tracking.d.ts.map +1 -1
  60. package/dist/esm/interaction-tracking.js +9 -18
  61. package/dist/esm/interaction-tracking.js.map +1 -1
  62. package/dist/esm/page-tracking.d.ts.map +1 -1
  63. package/dist/esm/page-tracking.js +0 -9
  64. package/dist/esm/page-tracking.js.map +1 -1
  65. package/dist/esm/section-tracking.d.ts +3 -0
  66. package/dist/esm/section-tracking.d.ts.map +1 -1
  67. package/dist/esm/section-tracking.js +30 -7
  68. package/dist/esm/section-tracking.js.map +1 -1
  69. package/dist/esm/types/auto-tracking.d.ts +3 -0
  70. package/dist/esm/types/auto-tracking.d.ts.map +1 -1
  71. package/dist/esm/types/heatmap-tracking.d.ts +44 -0
  72. package/dist/esm/types/heatmap-tracking.d.ts.map +1 -0
  73. package/dist/esm/types/heatmap-tracking.js +5 -0
  74. package/dist/esm/types/heatmap-tracking.js.map +1 -0
  75. package/dist/heartbeat.d.ts.map +1 -1
  76. package/dist/heartbeat.js +0 -6
  77. package/dist/heatmap-tracking.d.ts +93 -0
  78. package/dist/heatmap-tracking.d.ts.map +1 -0
  79. package/dist/heatmap-tracking.js +499 -0
  80. package/dist/index.d.ts +11 -0
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.global.dev.js +801 -80
  83. package/dist/index.global.dev.js.map +4 -4
  84. package/dist/index.global.js +2 -2
  85. package/dist/index.global.js.map +4 -4
  86. package/dist/index.js +70 -38
  87. package/dist/index.mjs +70 -38
  88. package/dist/interaction-tracking.d.ts.map +1 -1
  89. package/dist/interaction-tracking.js +9 -18
  90. package/dist/page-tracking.d.ts.map +1 -1
  91. package/dist/page-tracking.js +0 -9
  92. package/dist/section-tracking.d.ts +3 -0
  93. package/dist/section-tracking.d.ts.map +1 -1
  94. package/dist/section-tracking.js +30 -7
  95. package/dist/types/auto-tracking.d.ts +3 -0
  96. package/dist/types/auto-tracking.d.ts.map +1 -1
  97. package/dist/types/heatmap-tracking.d.ts +44 -0
  98. package/dist/types/heatmap-tracking.d.ts.map +1 -0
  99. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -75,6 +75,7 @@ class GrainAnalytics {
75
75
  // Auto-tracking properties
76
76
  this.interactionTrackingManager = null;
77
77
  this.sectionTrackingManager = null;
78
+ this.heatmapTrackingManager = null;
78
79
  // Session tracking
79
80
  this.sessionStartTime = Date.now();
80
81
  this.sessionEventCount = 0;
@@ -104,6 +105,8 @@ class GrainAnalytics {
104
105
  heartbeatInactiveInterval: 300000, // 5 minutes
105
106
  enableAutoPageView: true,
106
107
  stripQueryParams: true,
108
+ // Heatmap Tracking defaults
109
+ enableHeatmapTracking: true,
107
110
  ...config,
108
111
  tenantId: config.tenantId,
109
112
  };
@@ -132,6 +135,10 @@ class GrainAnalytics {
132
135
  this.initializeAutomaticTracking();
133
136
  // Track session start
134
137
  this.trackSessionStart();
138
+ // Initialize heatmap tracking if enabled
139
+ if (this.config.enableHeatmapTracking) {
140
+ this.initializeHeatmapTracking();
141
+ }
135
142
  }
136
143
  // Set up consent change listener to flush waiting events and handle consent upgrade
137
144
  this.consentManager.addListener((state) => {
@@ -381,6 +388,9 @@ class GrainAnalytics {
381
388
  * Log formatted error gracefully
382
389
  */
383
390
  logError(formattedError) {
391
+ // Only log errors in debug mode to reduce noise in production
392
+ if (!this.config.debug)
393
+ return;
384
394
  const { code, message, digest, timestamp, context } = formattedError;
385
395
  const errorOutput = {
386
396
  '🚨 Grain Analytics Error': {
@@ -398,10 +408,7 @@ class GrainAnalytics {
398
408
  }
399
409
  };
400
410
  console.error('🚨 Grain Analytics Error:', errorOutput);
401
- // Also log in a more compact format for debugging
402
- if (this.config.debug) {
403
- console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
404
- }
411
+ console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
405
412
  }
406
413
  /**
407
414
  * Safely execute a function with error handling
@@ -509,7 +516,6 @@ class GrainAnalytics {
509
516
  try {
510
517
  const headers = await this.getAuthHeaders();
511
518
  const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/multi`;
512
- this.log(`Sending ${events.length} events to ${url} (attempt ${attempt + 1})`);
513
519
  const response = await fetch(url, {
514
520
  method: 'POST',
515
521
  headers,
@@ -582,7 +588,6 @@ class GrainAnalytics {
582
588
  body,
583
589
  keepalive: true,
584
590
  });
585
- this.log(`Successfully sent ${events.length} events via fetch (keepalive)`);
586
591
  }
587
592
  catch (error) {
588
593
  // Log error gracefully for beacon failures (page unload scenarios)
@@ -654,7 +659,6 @@ class GrainAnalytics {
654
659
  inactiveInterval: this.config.heartbeatInactiveInterval,
655
660
  debug: this.config.debug,
656
661
  });
657
- this.log('Heartbeat tracking initialized');
658
662
  }
659
663
  catch (error) {
660
664
  this.log('Failed to initialize heartbeat tracking:', error);
@@ -667,7 +671,6 @@ class GrainAnalytics {
667
671
  debug: this.config.debug,
668
672
  tenantId: this.config.tenantId,
669
673
  });
670
- this.log('Auto page view tracking initialized');
671
674
  }
672
675
  catch (error) {
673
676
  this.log('Failed to initialize page view tracking:', error);
@@ -676,12 +679,41 @@ class GrainAnalytics {
676
679
  // Initialize auto-tracking when config is available
677
680
  this.initializeAutoTracking();
678
681
  }
682
+ /**
683
+ * Initialize heatmap tracking
684
+ */
685
+ initializeHeatmapTracking() {
686
+ if (typeof window === 'undefined')
687
+ return;
688
+ try {
689
+ this.log('Initializing heatmap tracking');
690
+ Promise.resolve().then(() => __importStar(require('./heatmap-tracking'))).then(({ HeatmapTrackingManager }) => {
691
+ try {
692
+ this.heatmapTrackingManager = new HeatmapTrackingManager(this, {
693
+ scrollDebounceDelay: 100,
694
+ batchDelay: 2000,
695
+ maxBatchSize: 20,
696
+ debug: this.config.debug,
697
+ });
698
+ this.log('Heatmap tracking initialized');
699
+ }
700
+ catch (error) {
701
+ this.log('Failed to initialize heatmap tracking:', error);
702
+ }
703
+ }).catch((error) => {
704
+ this.log('Failed to load heatmap tracking module:', error);
705
+ });
706
+ }
707
+ catch (error) {
708
+ this.log('Failed to initialize heatmap tracking:', error);
709
+ }
710
+ }
679
711
  /**
680
712
  * Initialize auto-tracking (interactions and sections)
681
713
  */
682
714
  async initializeAutoTracking() {
683
715
  try {
684
- this.log('Initializing auto-tracking...');
716
+ this.log('Initializing auto-tracking');
685
717
  // Fetch remote config to get auto-tracking configuration
686
718
  const userId = this.globalUserId || this.persistentAnonymousUserId || this.generateUUID();
687
719
  const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
@@ -694,25 +726,20 @@ class GrainAnalytics {
694
726
  };
695
727
  const headers = await this.getAuthHeaders();
696
728
  const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
697
- this.log('Fetching auto-tracking config from:', url);
698
729
  const response = await fetch(url, {
699
730
  method: 'POST',
700
731
  headers,
701
732
  body: JSON.stringify(request),
702
733
  });
703
734
  if (!response.ok) {
704
- this.log('Failed to fetch auto-tracking config:', response.status, response.statusText);
735
+ this.log('Failed to fetch auto-tracking config:', response.status);
705
736
  return;
706
737
  }
707
738
  const configResponse = await response.json();
708
- this.log('Received config response:', configResponse);
709
739
  if (configResponse.autoTrackingConfig) {
710
- this.log('Auto-tracking config found:', configResponse.autoTrackingConfig);
740
+ this.log('Auto-tracking config loaded');
711
741
  this.setupAutoTrackingManagers(configResponse.autoTrackingConfig);
712
742
  }
713
- else {
714
- this.log('No auto-tracking config in response');
715
- }
716
743
  }
717
744
  catch (error) {
718
745
  this.log('Failed to initialize auto-tracking:', error);
@@ -723,10 +750,10 @@ class GrainAnalytics {
723
750
  * Setup auto-tracking managers
724
751
  */
725
752
  setupAutoTrackingManagers(config) {
726
- this.log('Setting up auto-tracking managers...', config);
753
+ this.log('Setting up auto-tracking managers');
727
754
  // Lazy load the managers to avoid bundling them if not needed
728
755
  if (config.interactions && config.interactions.length > 0) {
729
- this.log('Loading interaction tracking module for', config.interactions.length, 'interactions');
756
+ this.log('Loading interaction tracking:', config.interactions.length, 'interactions');
730
757
  Promise.resolve().then(() => __importStar(require('./interaction-tracking'))).then(({ InteractionTrackingManager }) => {
731
758
  try {
732
759
  this.interactionTrackingManager = new InteractionTrackingManager(this, config.interactions, {
@@ -734,20 +761,17 @@ class GrainAnalytics {
734
761
  enableMutationObserver: true,
735
762
  mutationDebounceDelay: 500,
736
763
  });
737
- this.log('Interaction tracking initialized successfully with', config.interactions.length, 'interactions');
764
+ this.log('Interaction tracking initialized');
738
765
  }
739
766
  catch (error) {
740
- this.log('Failed to initialize interaction tracking:', error);
767
+ this.log('Failed to initialize interaction tracking:', error);
741
768
  }
742
769
  }).catch((error) => {
743
- this.log('Failed to load interaction tracking module:', error);
770
+ this.log('Failed to load interaction tracking module:', error);
744
771
  });
745
772
  }
746
- else {
747
- this.log('No interactions configured for auto-tracking');
748
- }
749
773
  if (config.sections && config.sections.length > 0) {
750
- this.log('Loading section tracking module for', config.sections.length, 'sections');
774
+ this.log('Loading section tracking:', config.sections.length, 'sections');
751
775
  Promise.resolve().then(() => __importStar(require('./section-tracking'))).then(({ SectionTrackingManager }) => {
752
776
  try {
753
777
  this.sectionTrackingManager = new SectionTrackingManager(this, config.sections, {
@@ -758,18 +782,15 @@ class GrainAnalytics {
758
782
  batchDelay: 2000,
759
783
  debug: this.config.debug,
760
784
  });
761
- this.log('Section tracking initialized successfully with', config.sections.length, 'sections');
785
+ this.log('Section tracking initialized');
762
786
  }
763
787
  catch (error) {
764
- this.log('Failed to initialize section tracking:', error);
788
+ this.log('Failed to initialize section tracking:', error);
765
789
  }
766
790
  }).catch((error) => {
767
- this.log('Failed to load section tracking module:', error);
791
+ this.log('Failed to load section tracking module:', error);
768
792
  });
769
793
  }
770
- else {
771
- this.log('No sections configured for auto-tracking');
772
- }
773
794
  }
774
795
  /**
775
796
  * Track session start event
@@ -827,7 +848,7 @@ class GrainAnalytics {
827
848
  properties.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
828
849
  }
829
850
  this.trackSystemEvent('_grain_session_start', properties);
830
- this.log('Session started:', properties);
851
+ this.log('Session started');
831
852
  }
832
853
  /**
833
854
  * Track session end event
@@ -850,7 +871,7 @@ class GrainAnalytics {
850
871
  properties.page_count = pageCount; // Keep for backward compatibility
851
872
  }
852
873
  this.trackSystemEvent('_grain_session_end', properties);
853
- this.log('Session ended:', properties);
874
+ this.log('Session ended');
854
875
  }
855
876
  /**
856
877
  * Detect browser name
@@ -954,7 +975,7 @@ class GrainAnalytics {
954
975
  // Bypass consent check for necessary system events
955
976
  this.eventQueue.push(event);
956
977
  this.eventCountSinceLastHeartbeat++;
957
- this.log(`Queued system event: ${eventName}`, properties);
978
+ this.log(`Queued system event: ${eventName}`);
958
979
  // Consider flushing
959
980
  if (this.eventQueue.length >= this.config.batchSize) {
960
981
  this.flush().catch((error) => {
@@ -990,6 +1011,15 @@ class GrainAnalytics {
990
1011
  resetEventCountSinceLastHeartbeat() {
991
1012
  this.eventCountSinceLastHeartbeat = 0;
992
1013
  }
1014
+ /**
1015
+ * Get the activity detector (for internal use by tracking managers)
1016
+ */
1017
+ getActivityDetector() {
1018
+ if (!this.activityDetector) {
1019
+ throw new Error('Activity detector not initialized');
1020
+ }
1021
+ return this.activityDetector;
1022
+ }
993
1023
  /**
994
1024
  * Get the effective user ID (public method)
995
1025
  */
@@ -1049,7 +1079,7 @@ class GrainAnalytics {
1049
1079
  this.eventQueue.push(formattedEvent);
1050
1080
  this.eventCountSinceLastHeartbeat++;
1051
1081
  this.sessionEventCount++;
1052
- this.log(`Queued event: ${event.eventName}`, event.properties);
1082
+ this.log(`Queued event: ${event.eventName}`);
1053
1083
  // Check if we should flush immediately
1054
1084
  if (opts.flush || this.eventQueue.length >= this.config.batchSize) {
1055
1085
  await this.flush();
@@ -1275,7 +1305,6 @@ class GrainAnalytics {
1275
1305
  try {
1276
1306
  const headers = await this.getAuthHeaders();
1277
1307
  const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/properties`;
1278
- this.log(`Setting properties for user ${payload.userId} (attempt ${attempt + 1})`);
1279
1308
  const response = await fetch(url, {
1280
1309
  method: 'POST',
1281
1310
  headers,
@@ -1520,7 +1549,6 @@ class GrainAnalytics {
1520
1549
  try {
1521
1550
  const headers = await this.getAuthHeaders();
1522
1551
  const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
1523
- this.log(`Fetching configurations for user ${userId} (attempt ${attempt + 1})`);
1524
1552
  const response = await fetch(url, {
1525
1553
  method: 'POST',
1526
1554
  headers,
@@ -1549,7 +1577,7 @@ class GrainAnalytics {
1549
1577
  if (configResponse.configurations) {
1550
1578
  this.updateConfigCache(configResponse, userId);
1551
1579
  }
1552
- this.log(`Successfully fetched configurations for user ${userId}:`, configResponse);
1580
+ this.log('Successfully fetched configurations');
1553
1581
  return configResponse;
1554
1582
  }
1555
1583
  catch (error) {
@@ -1825,6 +1853,10 @@ class GrainAnalytics {
1825
1853
  this.sectionTrackingManager.destroy();
1826
1854
  this.sectionTrackingManager = null;
1827
1855
  }
1856
+ if (this.heatmapTrackingManager) {
1857
+ this.heatmapTrackingManager.destroy();
1858
+ this.heatmapTrackingManager = null;
1859
+ }
1828
1860
  // Send any remaining events (in chunks if necessary)
1829
1861
  if (this.eventQueue.length > 0) {
1830
1862
  const eventsToSend = [...this.eventQueue];
package/dist/index.mjs CHANGED
@@ -34,6 +34,7 @@ export class GrainAnalytics {
34
34
  // Auto-tracking properties
35
35
  this.interactionTrackingManager = null;
36
36
  this.sectionTrackingManager = null;
37
+ this.heatmapTrackingManager = null;
37
38
  // Session tracking
38
39
  this.sessionStartTime = Date.now();
39
40
  this.sessionEventCount = 0;
@@ -63,6 +64,8 @@ export class GrainAnalytics {
63
64
  heartbeatInactiveInterval: 300000, // 5 minutes
64
65
  enableAutoPageView: true,
65
66
  stripQueryParams: true,
67
+ // Heatmap Tracking defaults
68
+ enableHeatmapTracking: true,
66
69
  ...config,
67
70
  tenantId: config.tenantId,
68
71
  };
@@ -91,6 +94,10 @@ export class GrainAnalytics {
91
94
  this.initializeAutomaticTracking();
92
95
  // Track session start
93
96
  this.trackSessionStart();
97
+ // Initialize heatmap tracking if enabled
98
+ if (this.config.enableHeatmapTracking) {
99
+ this.initializeHeatmapTracking();
100
+ }
94
101
  }
95
102
  // Set up consent change listener to flush waiting events and handle consent upgrade
96
103
  this.consentManager.addListener((state) => {
@@ -340,6 +347,9 @@ export class GrainAnalytics {
340
347
  * Log formatted error gracefully
341
348
  */
342
349
  logError(formattedError) {
350
+ // Only log errors in debug mode to reduce noise in production
351
+ if (!this.config.debug)
352
+ return;
343
353
  const { code, message, digest, timestamp, context } = formattedError;
344
354
  const errorOutput = {
345
355
  '🚨 Grain Analytics Error': {
@@ -357,10 +367,7 @@ export class GrainAnalytics {
357
367
  }
358
368
  };
359
369
  console.error('🚨 Grain Analytics Error:', errorOutput);
360
- // Also log in a more compact format for debugging
361
- if (this.config.debug) {
362
- console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
363
- }
370
+ console.error(`[Grain Analytics] ${code}: ${message} (${context}) - Events: ${digest.eventCount}, Props: ${digest.totalProperties}, Size: ${digest.totalSize}B`);
364
371
  }
365
372
  /**
366
373
  * Safely execute a function with error handling
@@ -468,7 +475,6 @@ export class GrainAnalytics {
468
475
  try {
469
476
  const headers = await this.getAuthHeaders();
470
477
  const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/multi`;
471
- this.log(`Sending ${events.length} events to ${url} (attempt ${attempt + 1})`);
472
478
  const response = await fetch(url, {
473
479
  method: 'POST',
474
480
  headers,
@@ -541,7 +547,6 @@ export class GrainAnalytics {
541
547
  body,
542
548
  keepalive: true,
543
549
  });
544
- this.log(`Successfully sent ${events.length} events via fetch (keepalive)`);
545
550
  }
546
551
  catch (error) {
547
552
  // Log error gracefully for beacon failures (page unload scenarios)
@@ -613,7 +618,6 @@ export class GrainAnalytics {
613
618
  inactiveInterval: this.config.heartbeatInactiveInterval,
614
619
  debug: this.config.debug,
615
620
  });
616
- this.log('Heartbeat tracking initialized');
617
621
  }
618
622
  catch (error) {
619
623
  this.log('Failed to initialize heartbeat tracking:', error);
@@ -626,7 +630,6 @@ export class GrainAnalytics {
626
630
  debug: this.config.debug,
627
631
  tenantId: this.config.tenantId,
628
632
  });
629
- this.log('Auto page view tracking initialized');
630
633
  }
631
634
  catch (error) {
632
635
  this.log('Failed to initialize page view tracking:', error);
@@ -635,12 +638,41 @@ export class GrainAnalytics {
635
638
  // Initialize auto-tracking when config is available
636
639
  this.initializeAutoTracking();
637
640
  }
641
+ /**
642
+ * Initialize heatmap tracking
643
+ */
644
+ initializeHeatmapTracking() {
645
+ if (typeof window === 'undefined')
646
+ return;
647
+ try {
648
+ this.log('Initializing heatmap tracking');
649
+ import('./heatmap-tracking').then(({ HeatmapTrackingManager }) => {
650
+ try {
651
+ this.heatmapTrackingManager = new HeatmapTrackingManager(this, {
652
+ scrollDebounceDelay: 100,
653
+ batchDelay: 2000,
654
+ maxBatchSize: 20,
655
+ debug: this.config.debug,
656
+ });
657
+ this.log('Heatmap tracking initialized');
658
+ }
659
+ catch (error) {
660
+ this.log('Failed to initialize heatmap tracking:', error);
661
+ }
662
+ }).catch((error) => {
663
+ this.log('Failed to load heatmap tracking module:', error);
664
+ });
665
+ }
666
+ catch (error) {
667
+ this.log('Failed to initialize heatmap tracking:', error);
668
+ }
669
+ }
638
670
  /**
639
671
  * Initialize auto-tracking (interactions and sections)
640
672
  */
641
673
  async initializeAutoTracking() {
642
674
  try {
643
- this.log('Initializing auto-tracking...');
675
+ this.log('Initializing auto-tracking');
644
676
  // Fetch remote config to get auto-tracking configuration
645
677
  const userId = this.globalUserId || this.persistentAnonymousUserId || this.generateUUID();
646
678
  const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
@@ -653,25 +685,20 @@ export class GrainAnalytics {
653
685
  };
654
686
  const headers = await this.getAuthHeaders();
655
687
  const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
656
- this.log('Fetching auto-tracking config from:', url);
657
688
  const response = await fetch(url, {
658
689
  method: 'POST',
659
690
  headers,
660
691
  body: JSON.stringify(request),
661
692
  });
662
693
  if (!response.ok) {
663
- this.log('Failed to fetch auto-tracking config:', response.status, response.statusText);
694
+ this.log('Failed to fetch auto-tracking config:', response.status);
664
695
  return;
665
696
  }
666
697
  const configResponse = await response.json();
667
- this.log('Received config response:', configResponse);
668
698
  if (configResponse.autoTrackingConfig) {
669
- this.log('Auto-tracking config found:', configResponse.autoTrackingConfig);
699
+ this.log('Auto-tracking config loaded');
670
700
  this.setupAutoTrackingManagers(configResponse.autoTrackingConfig);
671
701
  }
672
- else {
673
- this.log('No auto-tracking config in response');
674
- }
675
702
  }
676
703
  catch (error) {
677
704
  this.log('Failed to initialize auto-tracking:', error);
@@ -682,10 +709,10 @@ export class GrainAnalytics {
682
709
  * Setup auto-tracking managers
683
710
  */
684
711
  setupAutoTrackingManagers(config) {
685
- this.log('Setting up auto-tracking managers...', config);
712
+ this.log('Setting up auto-tracking managers');
686
713
  // Lazy load the managers to avoid bundling them if not needed
687
714
  if (config.interactions && config.interactions.length > 0) {
688
- this.log('Loading interaction tracking module for', config.interactions.length, 'interactions');
715
+ this.log('Loading interaction tracking:', config.interactions.length, 'interactions');
689
716
  import('./interaction-tracking').then(({ InteractionTrackingManager }) => {
690
717
  try {
691
718
  this.interactionTrackingManager = new InteractionTrackingManager(this, config.interactions, {
@@ -693,20 +720,17 @@ export class GrainAnalytics {
693
720
  enableMutationObserver: true,
694
721
  mutationDebounceDelay: 500,
695
722
  });
696
- this.log('Interaction tracking initialized successfully with', config.interactions.length, 'interactions');
723
+ this.log('Interaction tracking initialized');
697
724
  }
698
725
  catch (error) {
699
- this.log('Failed to initialize interaction tracking:', error);
726
+ this.log('Failed to initialize interaction tracking:', error);
700
727
  }
701
728
  }).catch((error) => {
702
- this.log('Failed to load interaction tracking module:', error);
729
+ this.log('Failed to load interaction tracking module:', error);
703
730
  });
704
731
  }
705
- else {
706
- this.log('No interactions configured for auto-tracking');
707
- }
708
732
  if (config.sections && config.sections.length > 0) {
709
- this.log('Loading section tracking module for', config.sections.length, 'sections');
733
+ this.log('Loading section tracking:', config.sections.length, 'sections');
710
734
  import('./section-tracking').then(({ SectionTrackingManager }) => {
711
735
  try {
712
736
  this.sectionTrackingManager = new SectionTrackingManager(this, config.sections, {
@@ -717,18 +741,15 @@ export class GrainAnalytics {
717
741
  batchDelay: 2000,
718
742
  debug: this.config.debug,
719
743
  });
720
- this.log('Section tracking initialized successfully with', config.sections.length, 'sections');
744
+ this.log('Section tracking initialized');
721
745
  }
722
746
  catch (error) {
723
- this.log('Failed to initialize section tracking:', error);
747
+ this.log('Failed to initialize section tracking:', error);
724
748
  }
725
749
  }).catch((error) => {
726
- this.log('Failed to load section tracking module:', error);
750
+ this.log('Failed to load section tracking module:', error);
727
751
  });
728
752
  }
729
- else {
730
- this.log('No sections configured for auto-tracking');
731
- }
732
753
  }
733
754
  /**
734
755
  * Track session start event
@@ -786,7 +807,7 @@ export class GrainAnalytics {
786
807
  properties.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
787
808
  }
788
809
  this.trackSystemEvent('_grain_session_start', properties);
789
- this.log('Session started:', properties);
810
+ this.log('Session started');
790
811
  }
791
812
  /**
792
813
  * Track session end event
@@ -809,7 +830,7 @@ export class GrainAnalytics {
809
830
  properties.page_count = pageCount; // Keep for backward compatibility
810
831
  }
811
832
  this.trackSystemEvent('_grain_session_end', properties);
812
- this.log('Session ended:', properties);
833
+ this.log('Session ended');
813
834
  }
814
835
  /**
815
836
  * Detect browser name
@@ -913,7 +934,7 @@ export class GrainAnalytics {
913
934
  // Bypass consent check for necessary system events
914
935
  this.eventQueue.push(event);
915
936
  this.eventCountSinceLastHeartbeat++;
916
- this.log(`Queued system event: ${eventName}`, properties);
937
+ this.log(`Queued system event: ${eventName}`);
917
938
  // Consider flushing
918
939
  if (this.eventQueue.length >= this.config.batchSize) {
919
940
  this.flush().catch((error) => {
@@ -949,6 +970,15 @@ export class GrainAnalytics {
949
970
  resetEventCountSinceLastHeartbeat() {
950
971
  this.eventCountSinceLastHeartbeat = 0;
951
972
  }
973
+ /**
974
+ * Get the activity detector (for internal use by tracking managers)
975
+ */
976
+ getActivityDetector() {
977
+ if (!this.activityDetector) {
978
+ throw new Error('Activity detector not initialized');
979
+ }
980
+ return this.activityDetector;
981
+ }
952
982
  /**
953
983
  * Get the effective user ID (public method)
954
984
  */
@@ -1008,7 +1038,7 @@ export class GrainAnalytics {
1008
1038
  this.eventQueue.push(formattedEvent);
1009
1039
  this.eventCountSinceLastHeartbeat++;
1010
1040
  this.sessionEventCount++;
1011
- this.log(`Queued event: ${event.eventName}`, event.properties);
1041
+ this.log(`Queued event: ${event.eventName}`);
1012
1042
  // Check if we should flush immediately
1013
1043
  if (opts.flush || this.eventQueue.length >= this.config.batchSize) {
1014
1044
  await this.flush();
@@ -1234,7 +1264,6 @@ export class GrainAnalytics {
1234
1264
  try {
1235
1265
  const headers = await this.getAuthHeaders();
1236
1266
  const url = `${this.config.apiUrl}/v1/events/${encodeURIComponent(this.config.tenantId)}/properties`;
1237
- this.log(`Setting properties for user ${payload.userId} (attempt ${attempt + 1})`);
1238
1267
  const response = await fetch(url, {
1239
1268
  method: 'POST',
1240
1269
  headers,
@@ -1479,7 +1508,6 @@ export class GrainAnalytics {
1479
1508
  try {
1480
1509
  const headers = await this.getAuthHeaders();
1481
1510
  const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
1482
- this.log(`Fetching configurations for user ${userId} (attempt ${attempt + 1})`);
1483
1511
  const response = await fetch(url, {
1484
1512
  method: 'POST',
1485
1513
  headers,
@@ -1508,7 +1536,7 @@ export class GrainAnalytics {
1508
1536
  if (configResponse.configurations) {
1509
1537
  this.updateConfigCache(configResponse, userId);
1510
1538
  }
1511
- this.log(`Successfully fetched configurations for user ${userId}:`, configResponse);
1539
+ this.log('Successfully fetched configurations');
1512
1540
  return configResponse;
1513
1541
  }
1514
1542
  catch (error) {
@@ -1784,6 +1812,10 @@ export class GrainAnalytics {
1784
1812
  this.sectionTrackingManager.destroy();
1785
1813
  this.sectionTrackingManager = null;
1786
1814
  }
1815
+ if (this.heatmapTrackingManager) {
1816
+ this.heatmapTrackingManager.destroy();
1817
+ this.heatmapTrackingManager = null;
1818
+ }
1787
1819
  // Send any remaining events (in chunks if necessary)
1788
1820
  if (this.eventQueue.length > 0) {
1789
1821
  const eventsToSend = [...this.eventQueue];
@@ -1 +1 @@
1
- {"version":3,"file":"interaction-tracking.d.ts","sourceRoot":"","sources":["../src/interaction-tracking.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjH,UAAU,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAC;IACxE,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,qBAAa,0BAA0B;IACrC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAA6E;IACtG,OAAO,CAAC,UAAU,CAA0C;IAC5D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,qBAAqB,CAAuB;gBAGlD,OAAO,EAAE,kBAAkB,EAC3B,YAAY,EAAE,iBAAiB,EAAE,EACjC,MAAM,GAAE,yBAA8B;IA0BxC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAkCjC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAuC9B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAoB9B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAyC1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0B7B;;OAEG;IACH,OAAO,CAAC,eAAe;IA+BvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;OAEG;IACH,OAAO,CAAC,GAAG;IAMX;;OAEG;IACH,kBAAkB,CAAC,YAAY,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAoB3D;;OAEG;IACH,OAAO,IAAI,IAAI;CA4BhB"}
1
+ {"version":3,"file":"interaction-tracking.d.ts","sourceRoot":"","sources":["../src/interaction-tracking.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjH,UAAU,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,GAAG,YAAY,GAAG,OAAO,CAAC;IACxE,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,qBAAa,0BAA0B;IACrC,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAA6E;IACtG,OAAO,CAAC,UAAU,CAA0C;IAC5D,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,qBAAqB,CAAuB;gBAGlD,OAAO,EAAE,kBAAkB,EAC3B,YAAY,EAAE,iBAAiB,EAAE,EACjC,MAAM,GAAE,yBAA8B;IA0BxC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAU1B;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAiCjC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAiC9B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAkB9B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAyC1B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0B7B;;OAEG;IACH,OAAO,CAAC,eAAe;IA+BvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACH,OAAO,CAAC,GAAG;IAMX;;OAEG;IACH,kBAAkB,CAAC,YAAY,EAAE,iBAAiB,EAAE,GAAG,IAAI;IAoB3D;;OAEG;IACH,OAAO,IAAI,IAAI;CA4BhB"}
@@ -40,7 +40,7 @@ class InteractionTrackingManager {
40
40
  attachAllListeners() {
41
41
  if (this.isDestroyed)
42
42
  return;
43
- this.log('Attaching interaction listeners for', this.interactions.length, 'interactions');
43
+ this.log('Attaching interaction listeners');
44
44
  for (const interaction of this.interactions) {
45
45
  this.attachInteractionListener(interaction);
46
46
  }
@@ -73,7 +73,6 @@ class InteractionTrackingManager {
73
73
  handlers.push({ event: 'focus', handler: focusHandler });
74
74
  }
75
75
  this.attachedListeners.set(element, handlers);
76
- this.log('Attached listeners to element for:', interaction.eventName);
77
76
  }
78
77
  /**
79
78
  * Handle click event on interaction
@@ -98,21 +97,15 @@ class InteractionTrackingManager {
98
97
  ...(isNavigationLink && { href: element.href }),
99
98
  timestamp: Date.now(),
100
99
  };
101
- // If it's a navigation link, flush immediately to ensure event is sent before navigation
102
- if (isNavigationLink) {
103
- // Use flush option to send immediately - handle promise if returned
104
- const result = this.tracker.track(interaction.eventName, eventProperties, { flush: true });
105
- if (result instanceof Promise) {
106
- result.catch((error) => {
107
- // Log error but don't block navigation
108
- this.log('Failed to track navigation click:', error);
109
- });
110
- }
111
- }
112
- else {
113
- this.tracker.track(interaction.eventName, eventProperties);
100
+ // Always use flush for auto-tracked clicks to ensure delivery
101
+ // This is especially important for navigation links and quick interactions
102
+ const result = this.tracker.track(interaction.eventName, eventProperties, { flush: true });
103
+ if (result instanceof Promise) {
104
+ result.catch((error) => {
105
+ // Log error but don't block interaction
106
+ this.log('Failed to track click:', error);
107
+ });
114
108
  }
115
- this.log('Tracked click interaction:', interaction.eventName);
116
109
  }
117
110
  /**
118
111
  * Handle focus event on interaction (for form fields)
@@ -133,7 +126,6 @@ class InteractionTrackingManager {
133
126
  element_class: element.className || undefined,
134
127
  timestamp: Date.now(),
135
128
  });
136
- this.log('Tracked focus interaction:', interaction.eventName);
137
129
  }
138
130
  /**
139
131
  * Find element by XPath selector
@@ -233,7 +225,6 @@ class InteractionTrackingManager {
233
225
  element.removeEventListener(event, handler);
234
226
  });
235
227
  this.attachedListeners.delete(element);
236
- this.log('Detached listeners from element');
237
228
  }
238
229
  /**
239
230
  * Log debug messages
@@ -1 +1 @@
1
- {"version":3,"file":"page-tracking.d.ts","sourceRoot":"","sources":["../src/page-tracking.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/E,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,kBAAkB,IAAI,MAAM,CAAC;IAC7B,qBAAqB,IAAI,MAAM,CAAC;IAChC,YAAY,IAAI,MAAM,CAAC;CACxB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,oBAAoB,CAA4C;IACxE,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,aAAa,CAAK;gBAEd,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB;IAY5D;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAK/B;;OAEG;IACH,OAAO,CAAC,cAAc,CAGpB;IAEF;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAGtB;IAEF;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAgHxB;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACH,OAAO,CAAC,KAAK;IAUb;;OAEG;IACH,OAAO,CAAC,aAAa;IAwBrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAmBnB;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAyCnE;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,OAAO,IAAI,IAAI;CAyBhB"}
1
+ {"version":3,"file":"page-tracking.d.ts","sourceRoot":"","sources":["../src/page-tracking.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,MAAM,WAAW,kBAAkB;IACjC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/E,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,kBAAkB,IAAI,MAAM,CAAC;IAC7B,qBAAqB,IAAI,MAAM,CAAC;IAChC,YAAY,IAAI,MAAM,CAAC;CACxB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,oBAAoB,CAA4C;IACxE,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,aAAa,CAAK;gBAEd,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB;IAY5D;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAqB7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAK/B;;OAEG;IACH,OAAO,CAAC,cAAc,CAGpB;IAEF;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAGtB;IAEF;;OAEG;IACH,OAAO,CAAC,gBAAgB;IA4GxB;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACH,OAAO,CAAC,KAAK;IAUb;;OAEG;IACH,OAAO,CAAC,aAAa;IAwBrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAmBnB;;OAEG;IACH,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAqCnE;;OAEG;IACH,gBAAgB,IAAI,MAAM;IAI1B;;OAEG;IACH,OAAO,IAAI,IAAI;CAqBhB"}