@grainql/analytics-web 2.3.0 → 2.5.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 (66) hide show
  1. package/dist/cjs/countries.d.ts +13 -0
  2. package/dist/cjs/countries.d.ts.map +1 -0
  3. package/dist/cjs/countries.js +2907 -0
  4. package/dist/cjs/countries.js.map +1 -0
  5. package/dist/cjs/index.d.ts +29 -1
  6. package/dist/cjs/index.d.ts.map +1 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/interaction-tracking.d.ts +71 -0
  9. package/dist/cjs/interaction-tracking.d.ts.map +1 -0
  10. package/dist/cjs/interaction-tracking.js +270 -0
  11. package/dist/cjs/interaction-tracking.js.map +1 -0
  12. package/dist/cjs/page-tracking.d.ts.map +1 -1
  13. package/dist/cjs/page-tracking.js +5 -1
  14. package/dist/cjs/page-tracking.js.map +1 -1
  15. package/dist/cjs/section-tracking.d.ts +91 -0
  16. package/dist/cjs/section-tracking.d.ts.map +1 -0
  17. package/dist/cjs/section-tracking.js +373 -0
  18. package/dist/cjs/section-tracking.js.map +1 -0
  19. package/dist/cjs/types/auto-tracking.d.ts +55 -0
  20. package/dist/cjs/types/auto-tracking.d.ts.map +1 -0
  21. package/dist/cjs/types/auto-tracking.js +6 -0
  22. package/dist/cjs/types/auto-tracking.js.map +1 -0
  23. package/dist/countries.d.ts +13 -0
  24. package/dist/countries.d.ts.map +1 -0
  25. package/dist/countries.js +2907 -0
  26. package/dist/esm/countries.d.ts +13 -0
  27. package/dist/esm/countries.d.ts.map +1 -0
  28. package/dist/esm/countries.js +2902 -0
  29. package/dist/esm/countries.js.map +1 -0
  30. package/dist/esm/index.d.ts +29 -1
  31. package/dist/esm/index.d.ts.map +1 -1
  32. package/dist/esm/index.js.map +1 -1
  33. package/dist/esm/interaction-tracking.d.ts +71 -0
  34. package/dist/esm/interaction-tracking.d.ts.map +1 -0
  35. package/dist/esm/interaction-tracking.js +266 -0
  36. package/dist/esm/interaction-tracking.js.map +1 -0
  37. package/dist/esm/page-tracking.d.ts.map +1 -1
  38. package/dist/esm/page-tracking.js +5 -1
  39. package/dist/esm/page-tracking.js.map +1 -1
  40. package/dist/esm/section-tracking.d.ts +91 -0
  41. package/dist/esm/section-tracking.d.ts.map +1 -0
  42. package/dist/esm/section-tracking.js +369 -0
  43. package/dist/esm/section-tracking.js.map +1 -0
  44. package/dist/esm/types/auto-tracking.d.ts +55 -0
  45. package/dist/esm/types/auto-tracking.d.ts.map +1 -0
  46. package/dist/esm/types/auto-tracking.js +5 -0
  47. package/dist/esm/types/auto-tracking.js.map +1 -0
  48. package/dist/index.d.ts +29 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.global.dev.js +3609 -2
  51. package/dist/index.global.dev.js.map +4 -4
  52. package/dist/index.global.js +2 -2
  53. package/dist/index.global.js.map +4 -4
  54. package/dist/index.js +130 -1
  55. package/dist/index.mjs +93 -0
  56. package/dist/interaction-tracking.d.ts +71 -0
  57. package/dist/interaction-tracking.d.ts.map +1 -0
  58. package/dist/interaction-tracking.js +270 -0
  59. package/dist/page-tracking.d.ts.map +1 -1
  60. package/dist/page-tracking.js +5 -1
  61. package/dist/section-tracking.d.ts +91 -0
  62. package/dist/section-tracking.d.ts.map +1 -0
  63. package/dist/section-tracking.js +373 -0
  64. package/dist/types/auto-tracking.d.ts +55 -0
  65. package/dist/types/auto-tracking.d.ts.map +1 -0
  66. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,8 +3,41 @@
3
3
  * Grain Analytics Web SDK
4
4
  * A lightweight, dependency-free TypeScript SDK for sending analytics events to Grain's REST API
5
5
  */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
6
39
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.GrainAnalytics = exports.parseUTMParameters = exports.categorizeReferrer = void 0;
40
+ exports.GrainAnalytics = exports.getState = exports.getCountryCodeFromTimezone = exports.getCountry = exports.parseUTMParameters = exports.categorizeReferrer = void 0;
8
41
  exports.createGrainAnalytics = createGrainAnalytics;
9
42
  const consent_1 = require("./consent");
10
43
  const cookies_1 = require("./cookies");
@@ -14,6 +47,11 @@ const page_tracking_1 = require("./page-tracking");
14
47
  const attribution_1 = require("./attribution");
15
48
  Object.defineProperty(exports, "categorizeReferrer", { enumerable: true, get: function () { return attribution_1.categorizeReferrer; } });
16
49
  Object.defineProperty(exports, "parseUTMParameters", { enumerable: true, get: function () { return attribution_1.parseUTMParameters; } });
50
+ // Re-export timezone-country utilities
51
+ var countries_1 = require("./countries");
52
+ Object.defineProperty(exports, "getCountry", { enumerable: true, get: function () { return countries_1.getCountry; } });
53
+ Object.defineProperty(exports, "getCountryCodeFromTimezone", { enumerable: true, get: function () { return countries_1.getCountryCodeFromTimezone; } });
54
+ Object.defineProperty(exports, "getState", { enumerable: true, get: function () { return countries_1.getState; } });
17
55
  class GrainAnalytics {
18
56
  constructor(config) {
19
57
  this.eventQueue = [];
@@ -34,6 +72,9 @@ class GrainAnalytics {
34
72
  this.pageTrackingManager = null;
35
73
  this.ephemeralSessionId = null;
36
74
  this.eventCountSinceLastHeartbeat = 0;
75
+ // Auto-tracking properties
76
+ this.interactionTrackingManager = null;
77
+ this.sectionTrackingManager = null;
37
78
  // Session tracking
38
79
  this.sessionStartTime = Date.now();
39
80
  this.sessionEventCount = 0;
@@ -593,6 +634,85 @@ class GrainAnalytics {
593
634
  this.log('Failed to initialize page view tracking:', error);
594
635
  }
595
636
  }
637
+ // Initialize auto-tracking when config is available
638
+ this.initializeAutoTracking();
639
+ }
640
+ /**
641
+ * Initialize auto-tracking (interactions and sections)
642
+ */
643
+ async initializeAutoTracking() {
644
+ try {
645
+ // Fetch remote config to get auto-tracking configuration
646
+ const userId = this.globalUserId || this.persistentAnonymousUserId || this.generateUUID();
647
+ const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
648
+ // Fetch config with currentUrl
649
+ const request = {
650
+ userId,
651
+ immediateKeys: [],
652
+ properties: {},
653
+ currentUrl, // Add current URL to request
654
+ };
655
+ const headers = await this.getAuthHeaders();
656
+ const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
657
+ const response = await fetch(url, {
658
+ method: 'POST',
659
+ headers,
660
+ body: JSON.stringify(request),
661
+ });
662
+ if (response.ok) {
663
+ const configResponse = await response.json();
664
+ if (configResponse.autoTrackingConfig) {
665
+ this.setupAutoTrackingManagers(configResponse.autoTrackingConfig);
666
+ }
667
+ }
668
+ }
669
+ catch (error) {
670
+ this.log('Failed to initialize auto-tracking:', error);
671
+ // Fail silently - auto-tracking is optional
672
+ }
673
+ }
674
+ /**
675
+ * Setup auto-tracking managers
676
+ */
677
+ setupAutoTrackingManagers(config) {
678
+ // Lazy load the managers to avoid bundling them if not needed
679
+ Promise.resolve().then(() => __importStar(require('./interaction-tracking'))).then(({ InteractionTrackingManager }) => {
680
+ try {
681
+ if (config.interactions && config.interactions.length > 0) {
682
+ this.interactionTrackingManager = new InteractionTrackingManager(this, config.interactions, {
683
+ debug: this.config.debug,
684
+ enableMutationObserver: true,
685
+ mutationDebounceDelay: 500,
686
+ });
687
+ this.log('Interaction tracking initialized with', config.interactions.length, 'interactions');
688
+ }
689
+ }
690
+ catch (error) {
691
+ this.log('Failed to initialize interaction tracking:', error);
692
+ }
693
+ }).catch((error) => {
694
+ this.log('Failed to load interaction tracking module:', error);
695
+ });
696
+ Promise.resolve().then(() => __importStar(require('./section-tracking'))).then(({ SectionTrackingManager }) => {
697
+ try {
698
+ if (config.sections && config.sections.length > 0) {
699
+ this.sectionTrackingManager = new SectionTrackingManager(this, config.sections, {
700
+ minDwellTime: 1000,
701
+ scrollVelocityThreshold: 500,
702
+ intersectionThreshold: 0.1,
703
+ debounceDelay: 100,
704
+ batchDelay: 2000,
705
+ debug: this.config.debug,
706
+ });
707
+ this.log('Section tracking initialized with', config.sections.length, 'sections');
708
+ }
709
+ }
710
+ catch (error) {
711
+ this.log('Failed to initialize section tracking:', error);
712
+ }
713
+ }).catch((error) => {
714
+ this.log('Failed to load section tracking module:', error);
715
+ });
596
716
  }
597
717
  /**
598
718
  * Track session start event
@@ -1639,6 +1759,15 @@ class GrainAnalytics {
1639
1759
  this.activityDetector.destroy();
1640
1760
  this.activityDetector = null;
1641
1761
  }
1762
+ // Destroy auto-tracking managers
1763
+ if (this.interactionTrackingManager) {
1764
+ this.interactionTrackingManager.destroy();
1765
+ this.interactionTrackingManager = null;
1766
+ }
1767
+ if (this.sectionTrackingManager) {
1768
+ this.sectionTrackingManager.destroy();
1769
+ this.sectionTrackingManager = null;
1770
+ }
1642
1771
  // Send any remaining events (in chunks if necessary)
1643
1772
  if (this.eventQueue.length > 0) {
1644
1773
  const eventsToSend = [...this.eventQueue];
package/dist/index.mjs CHANGED
@@ -9,6 +9,8 @@ import { HeartbeatManager } from './heartbeat.js';
9
9
  import { PageTrackingManager } from './page-tracking.js';
10
10
  import { categorizeReferrer, parseUTMParameters, getOrCreateFirstTouchAttribution, getSessionUTMParameters, } from './attribution.js';
11
11
  export { categorizeReferrer, parseUTMParameters };
12
+ // Re-export timezone-country utilities
13
+ export { getCountry, getCountryCodeFromTimezone, getState } from './countries.js';
12
14
  export class GrainAnalytics {
13
15
  constructor(config) {
14
16
  this.eventQueue = [];
@@ -29,6 +31,9 @@ export class GrainAnalytics {
29
31
  this.pageTrackingManager = null;
30
32
  this.ephemeralSessionId = null;
31
33
  this.eventCountSinceLastHeartbeat = 0;
34
+ // Auto-tracking properties
35
+ this.interactionTrackingManager = null;
36
+ this.sectionTrackingManager = null;
32
37
  // Session tracking
33
38
  this.sessionStartTime = Date.now();
34
39
  this.sessionEventCount = 0;
@@ -588,6 +593,85 @@ export class GrainAnalytics {
588
593
  this.log('Failed to initialize page view tracking:', error);
589
594
  }
590
595
  }
596
+ // Initialize auto-tracking when config is available
597
+ this.initializeAutoTracking();
598
+ }
599
+ /**
600
+ * Initialize auto-tracking (interactions and sections)
601
+ */
602
+ async initializeAutoTracking() {
603
+ try {
604
+ // Fetch remote config to get auto-tracking configuration
605
+ const userId = this.globalUserId || this.persistentAnonymousUserId || this.generateUUID();
606
+ const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
607
+ // Fetch config with currentUrl
608
+ const request = {
609
+ userId,
610
+ immediateKeys: [],
611
+ properties: {},
612
+ currentUrl, // Add current URL to request
613
+ };
614
+ const headers = await this.getAuthHeaders();
615
+ const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/config/configurations`;
616
+ const response = await fetch(url, {
617
+ method: 'POST',
618
+ headers,
619
+ body: JSON.stringify(request),
620
+ });
621
+ if (response.ok) {
622
+ const configResponse = await response.json();
623
+ if (configResponse.autoTrackingConfig) {
624
+ this.setupAutoTrackingManagers(configResponse.autoTrackingConfig);
625
+ }
626
+ }
627
+ }
628
+ catch (error) {
629
+ this.log('Failed to initialize auto-tracking:', error);
630
+ // Fail silently - auto-tracking is optional
631
+ }
632
+ }
633
+ /**
634
+ * Setup auto-tracking managers
635
+ */
636
+ setupAutoTrackingManagers(config) {
637
+ // Lazy load the managers to avoid bundling them if not needed
638
+ import('./interaction-tracking').then(({ InteractionTrackingManager }) => {
639
+ try {
640
+ if (config.interactions && config.interactions.length > 0) {
641
+ this.interactionTrackingManager = new InteractionTrackingManager(this, config.interactions, {
642
+ debug: this.config.debug,
643
+ enableMutationObserver: true,
644
+ mutationDebounceDelay: 500,
645
+ });
646
+ this.log('Interaction tracking initialized with', config.interactions.length, 'interactions');
647
+ }
648
+ }
649
+ catch (error) {
650
+ this.log('Failed to initialize interaction tracking:', error);
651
+ }
652
+ }).catch((error) => {
653
+ this.log('Failed to load interaction tracking module:', error);
654
+ });
655
+ import('./section-tracking').then(({ SectionTrackingManager }) => {
656
+ try {
657
+ if (config.sections && config.sections.length > 0) {
658
+ this.sectionTrackingManager = new SectionTrackingManager(this, config.sections, {
659
+ minDwellTime: 1000,
660
+ scrollVelocityThreshold: 500,
661
+ intersectionThreshold: 0.1,
662
+ debounceDelay: 100,
663
+ batchDelay: 2000,
664
+ debug: this.config.debug,
665
+ });
666
+ this.log('Section tracking initialized with', config.sections.length, 'sections');
667
+ }
668
+ }
669
+ catch (error) {
670
+ this.log('Failed to initialize section tracking:', error);
671
+ }
672
+ }).catch((error) => {
673
+ this.log('Failed to load section tracking module:', error);
674
+ });
591
675
  }
592
676
  /**
593
677
  * Track session start event
@@ -1634,6 +1718,15 @@ export class GrainAnalytics {
1634
1718
  this.activityDetector.destroy();
1635
1719
  this.activityDetector = null;
1636
1720
  }
1721
+ // Destroy auto-tracking managers
1722
+ if (this.interactionTrackingManager) {
1723
+ this.interactionTrackingManager.destroy();
1724
+ this.interactionTrackingManager = null;
1725
+ }
1726
+ if (this.sectionTrackingManager) {
1727
+ this.sectionTrackingManager.destroy();
1728
+ this.sectionTrackingManager = null;
1729
+ }
1637
1730
  // Send any remaining events (in chunks if necessary)
1638
1731
  if (this.eventQueue.length > 0) {
1639
1732
  const eventsToSend = [...this.eventQueue];
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Interaction Tracking Manager for Grain Analytics
3
+ * Automatically attaches click and focus listeners to detected interactive elements
4
+ */
5
+ import type { InteractionConfig } from './types/auto-tracking';
6
+ export interface InteractionTracker {
7
+ track(eventName: string, properties?: Record<string, unknown>): void | Promise<void>;
8
+ hasConsent(category: 'analytics' | 'marketing' | 'functional'): boolean;
9
+ log(...args: unknown[]): void;
10
+ }
11
+ export interface InteractionTrackingConfig {
12
+ debug?: boolean;
13
+ enableMutationObserver?: boolean;
14
+ mutationDebounceDelay?: number;
15
+ }
16
+ export declare class InteractionTrackingManager {
17
+ private tracker;
18
+ private interactions;
19
+ private config;
20
+ private isDestroyed;
21
+ private attachedListeners;
22
+ private xpathCache;
23
+ private mutationObserver;
24
+ private mutationDebounceTimer;
25
+ constructor(tracker: InteractionTracker, interactions: InteractionConfig[], config?: InteractionTrackingConfig);
26
+ /**
27
+ * Attach listeners to all configured interactions
28
+ */
29
+ private attachAllListeners;
30
+ /**
31
+ * Attach listener to a specific interaction
32
+ */
33
+ private attachInteractionListener;
34
+ /**
35
+ * Handle click event on interaction
36
+ */
37
+ private handleInteractionClick;
38
+ /**
39
+ * Handle focus event on interaction (for form fields)
40
+ */
41
+ private handleInteractionFocus;
42
+ /**
43
+ * Find element by XPath selector
44
+ */
45
+ private findElementByXPath;
46
+ /**
47
+ * Setup mutation observer to handle dynamic content
48
+ */
49
+ private setupMutationObserver;
50
+ /**
51
+ * Handle DOM mutations
52
+ */
53
+ private handleMutations;
54
+ /**
55
+ * Detach listeners from an element
56
+ */
57
+ private detachListeners;
58
+ /**
59
+ * Log debug messages
60
+ */
61
+ private log;
62
+ /**
63
+ * Update interactions configuration
64
+ */
65
+ updateInteractions(interactions: InteractionConfig[]): void;
66
+ /**
67
+ * Cleanup and destroy
68
+ */
69
+ destroy(): void;
70
+ }
71
+ //# sourceMappingURL=interaction-tracking.d.ts.map
@@ -0,0 +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,kBAAkB;IACjC,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrF,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;IAqB9B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAoB9B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAmC1B;;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"}
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ /**
3
+ * Interaction Tracking Manager for Grain Analytics
4
+ * Automatically attaches click and focus listeners to detected interactive elements
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.InteractionTrackingManager = void 0;
8
+ class InteractionTrackingManager {
9
+ constructor(tracker, interactions, config = {}) {
10
+ this.isDestroyed = false;
11
+ this.attachedListeners = new Map();
12
+ this.xpathCache = new Map();
13
+ this.mutationObserver = null;
14
+ this.mutationDebounceTimer = null;
15
+ this.tracker = tracker;
16
+ this.interactions = interactions;
17
+ this.config = {
18
+ debug: config.debug ?? false,
19
+ enableMutationObserver: config.enableMutationObserver ?? true,
20
+ mutationDebounceDelay: config.mutationDebounceDelay ?? 500,
21
+ };
22
+ if (typeof window !== 'undefined' && typeof document !== 'undefined') {
23
+ // Attach listeners after DOM is ready
24
+ if (document.readyState === 'loading') {
25
+ document.addEventListener('DOMContentLoaded', () => this.attachAllListeners());
26
+ }
27
+ else {
28
+ // DOM already loaded
29
+ setTimeout(() => this.attachAllListeners(), 0);
30
+ }
31
+ // Setup mutation observer for dynamic content
32
+ if (this.config.enableMutationObserver) {
33
+ this.setupMutationObserver();
34
+ }
35
+ }
36
+ }
37
+ /**
38
+ * Attach listeners to all configured interactions
39
+ */
40
+ attachAllListeners() {
41
+ if (this.isDestroyed)
42
+ return;
43
+ this.log('Attaching interaction listeners for', this.interactions.length, 'interactions');
44
+ for (const interaction of this.interactions) {
45
+ this.attachInteractionListener(interaction);
46
+ }
47
+ }
48
+ /**
49
+ * Attach listener to a specific interaction
50
+ */
51
+ attachInteractionListener(interaction) {
52
+ if (this.isDestroyed)
53
+ return;
54
+ const element = this.findElementByXPath(interaction.selector);
55
+ if (!element) {
56
+ this.log('Element not found for interaction:', interaction.eventName, 'selector:', interaction.selector);
57
+ return;
58
+ }
59
+ // Check if we already attached listeners to this element for this interaction
60
+ if (this.attachedListeners.has(element)) {
61
+ this.log('Listeners already attached for element:', element);
62
+ return;
63
+ }
64
+ const handlers = [];
65
+ // Click handler
66
+ const clickHandler = (event) => this.handleInteractionClick(interaction, event);
67
+ element.addEventListener('click', clickHandler, { passive: true });
68
+ handlers.push({ event: 'click', handler: clickHandler });
69
+ // Focus handler (for form inputs)
70
+ if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement) {
71
+ const focusHandler = (event) => this.handleInteractionFocus(interaction, event);
72
+ element.addEventListener('focus', focusHandler, { passive: true });
73
+ handlers.push({ event: 'focus', handler: focusHandler });
74
+ }
75
+ this.attachedListeners.set(element, handlers);
76
+ this.log('Attached listeners to element for:', interaction.eventName);
77
+ }
78
+ /**
79
+ * Handle click event on interaction
80
+ */
81
+ handleInteractionClick(interaction, event) {
82
+ if (this.isDestroyed)
83
+ return;
84
+ if (!this.tracker.hasConsent('analytics'))
85
+ return;
86
+ const element = event.target;
87
+ this.tracker.track(interaction.eventName, {
88
+ interaction_type: 'click',
89
+ interaction_label: interaction.label,
90
+ interaction_description: interaction.description,
91
+ interaction_priority: interaction.priority,
92
+ element_tag: element.tagName?.toLowerCase(),
93
+ element_text: element.textContent?.trim().substring(0, 100),
94
+ element_id: element.id || undefined,
95
+ element_class: element.className || undefined,
96
+ timestamp: Date.now(),
97
+ });
98
+ this.log('Tracked click interaction:', interaction.eventName);
99
+ }
100
+ /**
101
+ * Handle focus event on interaction (for form fields)
102
+ */
103
+ handleInteractionFocus(interaction, event) {
104
+ if (this.isDestroyed)
105
+ return;
106
+ if (!this.tracker.hasConsent('analytics'))
107
+ return;
108
+ const element = event.target;
109
+ this.tracker.track(interaction.eventName, {
110
+ interaction_type: 'focus',
111
+ interaction_label: interaction.label,
112
+ interaction_description: interaction.description,
113
+ interaction_priority: interaction.priority,
114
+ element_tag: element.tagName?.toLowerCase(),
115
+ element_id: element.id || undefined,
116
+ element_class: element.className || undefined,
117
+ timestamp: Date.now(),
118
+ });
119
+ this.log('Tracked focus interaction:', interaction.eventName);
120
+ }
121
+ /**
122
+ * Find element by XPath selector
123
+ */
124
+ findElementByXPath(xpath) {
125
+ // Check cache first
126
+ if (this.xpathCache.has(xpath)) {
127
+ const cached = this.xpathCache.get(xpath);
128
+ // Verify element is still in DOM
129
+ if (cached && document.contains(cached)) {
130
+ return cached;
131
+ }
132
+ // Clear invalid cache entry
133
+ this.xpathCache.delete(xpath);
134
+ }
135
+ try {
136
+ const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
137
+ const element = result.singleNodeValue;
138
+ // Cache the result
139
+ if (element) {
140
+ this.xpathCache.set(xpath, element);
141
+ }
142
+ return element;
143
+ }
144
+ catch (error) {
145
+ this.log('Error evaluating XPath:', xpath, error);
146
+ return null;
147
+ }
148
+ }
149
+ /**
150
+ * Setup mutation observer to handle dynamic content
151
+ */
152
+ setupMutationObserver() {
153
+ if (typeof MutationObserver === 'undefined') {
154
+ this.log('MutationObserver not supported');
155
+ return;
156
+ }
157
+ this.mutationObserver = new MutationObserver((mutations) => {
158
+ // Debounce the re-attachment
159
+ if (this.mutationDebounceTimer !== null) {
160
+ clearTimeout(this.mutationDebounceTimer);
161
+ }
162
+ this.mutationDebounceTimer = window.setTimeout(() => {
163
+ this.handleMutations(mutations);
164
+ this.mutationDebounceTimer = null;
165
+ }, this.config.mutationDebounceDelay);
166
+ });
167
+ this.mutationObserver.observe(document.body, {
168
+ childList: true,
169
+ subtree: true,
170
+ });
171
+ this.log('Mutation observer setup');
172
+ }
173
+ /**
174
+ * Handle DOM mutations
175
+ */
176
+ handleMutations(mutations) {
177
+ if (this.isDestroyed)
178
+ return;
179
+ // Clear XPath cache on mutations
180
+ this.xpathCache.clear();
181
+ // Check if any of our tracked elements were removed
182
+ const removedElements = new Set();
183
+ for (const mutation of mutations) {
184
+ mutation.removedNodes.forEach((node) => {
185
+ if (node instanceof Element) {
186
+ removedElements.add(node);
187
+ // Also check for child elements we were tracking
188
+ this.attachedListeners.forEach((handlers, element) => {
189
+ if (node.contains(element)) {
190
+ removedElements.add(element);
191
+ }
192
+ });
193
+ }
194
+ });
195
+ }
196
+ // Clean up removed elements
197
+ removedElements.forEach((element) => {
198
+ this.detachListeners(element);
199
+ });
200
+ // Try to re-attach listeners for any interactions that might now be available
201
+ this.attachAllListeners();
202
+ }
203
+ /**
204
+ * Detach listeners from an element
205
+ */
206
+ detachListeners(element) {
207
+ const handlers = this.attachedListeners.get(element);
208
+ if (!handlers)
209
+ return;
210
+ handlers.forEach(({ event, handler }) => {
211
+ element.removeEventListener(event, handler);
212
+ });
213
+ this.attachedListeners.delete(element);
214
+ this.log('Detached listeners from element');
215
+ }
216
+ /**
217
+ * Log debug messages
218
+ */
219
+ log(...args) {
220
+ if (this.config.debug) {
221
+ console.log('[InteractionTracking]', ...args);
222
+ }
223
+ }
224
+ /**
225
+ * Update interactions configuration
226
+ */
227
+ updateInteractions(interactions) {
228
+ if (this.isDestroyed)
229
+ return;
230
+ this.log('Updating interactions configuration');
231
+ // Detach all existing listeners
232
+ this.attachedListeners.forEach((handlers, element) => {
233
+ this.detachListeners(element);
234
+ });
235
+ // Clear cache
236
+ this.xpathCache.clear();
237
+ // Update configuration
238
+ this.interactions = interactions;
239
+ // Reattach listeners
240
+ this.attachAllListeners();
241
+ }
242
+ /**
243
+ * Cleanup and destroy
244
+ */
245
+ destroy() {
246
+ if (this.isDestroyed)
247
+ return;
248
+ this.log('Destroying interaction tracking manager');
249
+ this.isDestroyed = true;
250
+ // Clear debounce timer
251
+ if (this.mutationDebounceTimer !== null) {
252
+ clearTimeout(this.mutationDebounceTimer);
253
+ this.mutationDebounceTimer = null;
254
+ }
255
+ // Disconnect mutation observer
256
+ if (this.mutationObserver) {
257
+ this.mutationObserver.disconnect();
258
+ this.mutationObserver = null;
259
+ }
260
+ // Detach all listeners
261
+ this.attachedListeners.forEach((handlers, element) => {
262
+ this.detachListeners(element);
263
+ });
264
+ // Clear caches
265
+ this.attachedListeners.clear();
266
+ this.xpathCache.clear();
267
+ }
268
+ }
269
+ exports.InteractionTrackingManager = InteractionTrackingManager;
270
+ //# sourceMappingURL=interaction-tracking.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"page-tracking.d.ts","sourceRoot":"","sources":["../src/page-tracking.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,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;IA2GxB;;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;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"}
@@ -6,6 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.PageTrackingManager = void 0;
8
8
  const attribution_1 = require("./attribution");
9
+ const countries_1 = require("./countries");
9
10
  class PageTrackingManager {
10
11
  constructor(tracker, config) {
11
12
  this.isDestroyed = false;
@@ -149,7 +150,10 @@ class PageTrackingManager {
149
150
  properties.browser = this.getBrowser();
150
151
  properties.os = this.getOS();
151
152
  properties.language = navigator.language || '';
152
- properties.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
153
+ // Timezone and country (privacy-friendly: derived from timezone, no IP tracking)
154
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
155
+ properties.timezone = timezone;
156
+ properties.country = (0, countries_1.getCountryCodeFromTimezone)();
153
157
  properties.screen_resolution = `${screen.width}x${screen.height}`;
154
158
  properties.viewport = `${window.innerWidth}x${window.innerHeight}`;
155
159
  }