@grainql/analytics-web 2.7.1 → 2.8.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 (37) hide show
  1. package/dist/cjs/debug-agent.d.ts +171 -0
  2. package/dist/cjs/debug-agent.d.ts.map +1 -0
  3. package/dist/cjs/debug-agent.js +1219 -0
  4. package/dist/cjs/debug-agent.js.map +1 -0
  5. package/dist/cjs/index.d.ts +14 -0
  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 +6 -0
  9. package/dist/cjs/interaction-tracking.d.ts.map +1 -1
  10. package/dist/cjs/interaction-tracking.js +55 -5
  11. package/dist/cjs/interaction-tracking.js.map +1 -1
  12. package/dist/debug-agent.d.ts +171 -0
  13. package/dist/debug-agent.d.ts.map +1 -0
  14. package/dist/debug-agent.js +1219 -0
  15. package/dist/esm/debug-agent.d.ts +171 -0
  16. package/dist/esm/debug-agent.d.ts.map +1 -0
  17. package/dist/esm/debug-agent.js +1215 -0
  18. package/dist/esm/debug-agent.js.map +1 -0
  19. package/dist/esm/index.d.ts +14 -0
  20. package/dist/esm/index.d.ts.map +1 -1
  21. package/dist/esm/index.js.map +1 -1
  22. package/dist/esm/interaction-tracking.d.ts +6 -0
  23. package/dist/esm/interaction-tracking.d.ts.map +1 -1
  24. package/dist/esm/interaction-tracking.js +55 -5
  25. package/dist/esm/interaction-tracking.js.map +1 -1
  26. package/dist/index.d.ts +14 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.global.dev.js +1326 -6
  29. package/dist/index.global.dev.js.map +4 -4
  30. package/dist/index.global.js +506 -2
  31. package/dist/index.global.js.map +4 -4
  32. package/dist/index.js +99 -0
  33. package/dist/index.mjs +99 -0
  34. package/dist/interaction-tracking.d.ts +6 -0
  35. package/dist/interaction-tracking.d.ts.map +1 -1
  36. package/dist/interaction-tracking.js +55 -5
  37. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- /* Grain Analytics Web SDK v2.7.1 | MIT License | Development Build */
1
+ /* Grain Analytics Web SDK v2.8.0 | MIT License | Development Build */
2
2
  "use strict";
3
3
  var Grain = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -755,19 +755,65 @@ var Grain = (() => {
755
755
  this.config = {
756
756
  debug: config.debug ?? false,
757
757
  enableMutationObserver: config.enableMutationObserver ?? true,
758
- mutationDebounceDelay: config.mutationDebounceDelay ?? 500
758
+ mutationDebounceDelay: config.mutationDebounceDelay ?? 500,
759
+ tenantId: config.tenantId,
760
+ apiUrl: config.apiUrl
759
761
  };
760
762
  if (typeof window !== "undefined" && typeof document !== "undefined") {
761
- if (document.readyState === "loading") {
762
- document.addEventListener("DOMContentLoaded", () => this.attachAllListeners());
763
+ if (this.config.tenantId && this.config.apiUrl) {
764
+ this.fetchAndMergeTrackers().then(() => {
765
+ this.attachAllListeners();
766
+ });
763
767
  } else {
764
- setTimeout(() => this.attachAllListeners(), 0);
768
+ if (document.readyState === "loading") {
769
+ document.addEventListener("DOMContentLoaded", () => this.attachAllListeners());
770
+ } else {
771
+ setTimeout(() => this.attachAllListeners(), 0);
772
+ }
765
773
  }
766
774
  if (this.config.enableMutationObserver) {
767
775
  this.setupMutationObserver();
768
776
  }
769
777
  }
770
778
  }
779
+ /**
780
+ * Fetch trackers from API and merge with existing interactions
781
+ */
782
+ async fetchAndMergeTrackers() {
783
+ if (!this.config.tenantId || !this.config.apiUrl)
784
+ return;
785
+ try {
786
+ const currentUrl = typeof window !== "undefined" ? window.location.href : "";
787
+ const url = `${this.config.apiUrl}/v1/client/${encodeURIComponent(this.config.tenantId)}/trackers?url=${encodeURIComponent(currentUrl)}`;
788
+ this.log("Fetching trackers from:", url);
789
+ const response = await fetch(url, {
790
+ method: "GET",
791
+ headers: {
792
+ "Content-Type": "application/json"
793
+ }
794
+ });
795
+ if (!response.ok) {
796
+ this.log("Failed to fetch trackers:", response.status);
797
+ return;
798
+ }
799
+ const result = await response.json();
800
+ if (result.trackers && Array.isArray(result.trackers)) {
801
+ this.log("Fetched", result.trackers.length, "trackers");
802
+ const trackerInteractions = result.trackers.map((tracker) => ({
803
+ eventName: tracker.eventName,
804
+ selector: tracker.selector,
805
+ priority: 5,
806
+ // High priority for manually created trackers
807
+ label: tracker.eventName,
808
+ description: `Tracker: ${tracker.eventName}`
809
+ }));
810
+ this.interactions = [...trackerInteractions, ...this.interactions];
811
+ this.log("Merged trackers, total interactions:", this.interactions.length);
812
+ }
813
+ } catch (error) {
814
+ this.log("Error fetching trackers:", error);
815
+ }
816
+ }
771
817
  /**
772
818
  * Attach listeners to all configured interactions
773
819
  */
@@ -1458,6 +1504,1185 @@ var Grain = (() => {
1458
1504
  }
1459
1505
  });
1460
1506
 
1507
+ // src/debug-agent.ts
1508
+ var debug_agent_exports = {};
1509
+ __export(debug_agent_exports, {
1510
+ DebugAgent: () => DebugAgent
1511
+ });
1512
+ var DebugAgent;
1513
+ var init_debug_agent = __esm({
1514
+ "src/debug-agent.ts"() {
1515
+ "use strict";
1516
+ DebugAgent = class {
1517
+ constructor(tracker, sessionId, tenantId, apiUrl, config = {}) {
1518
+ this.isDestroyed = false;
1519
+ // UI state
1520
+ this.isInspectMode = false;
1521
+ this.showTrackers = false;
1522
+ this.selectedElement = null;
1523
+ this.toolbarElement = null;
1524
+ this.panelElement = null;
1525
+ this.highlightElement = null;
1526
+ this.existingTrackers = [];
1527
+ this.trackerHighlights = [];
1528
+ // Dragging state
1529
+ this.isDragging = false;
1530
+ this.dragStartX = 0;
1531
+ this.dragStartY = 0;
1532
+ this.toolbarStartX = 0;
1533
+ this.toolbarStartY = 0;
1534
+ // Event listeners
1535
+ this.mouseMoveListener = null;
1536
+ this.clickListener = null;
1537
+ this.dragMoveListener = null;
1538
+ this.dragEndListener = null;
1539
+ /**
1540
+ * Handle ESC key to exit inspect mode
1541
+ */
1542
+ this.handleEscapeKey = (e) => {
1543
+ if (e.key === "Escape" && this.isInspectMode) {
1544
+ this.disableInspectMode();
1545
+ }
1546
+ };
1547
+ this.tracker = tracker;
1548
+ this.sessionId = sessionId;
1549
+ this.tenantId = tenantId;
1550
+ this.apiUrl = apiUrl;
1551
+ this.config = {
1552
+ debug: config.debug ?? false
1553
+ };
1554
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
1555
+ this.initialize();
1556
+ }
1557
+ }
1558
+ /**
1559
+ * Initialize the debug agent
1560
+ */
1561
+ async initialize() {
1562
+ this.log("Initializing debug agent");
1563
+ await this.loadExistingTrackers();
1564
+ this.showToolbar();
1565
+ this.createHighlightElement();
1566
+ this.showTrackers = true;
1567
+ this.showTrackerHighlights();
1568
+ this.showTrackersList();
1569
+ }
1570
+ /**
1571
+ * Load existing trackers from API
1572
+ */
1573
+ async loadExistingTrackers() {
1574
+ try {
1575
+ const url = `${this.apiUrl}/v1/tenant/${encodeURIComponent(this.tenantId)}/trackers`;
1576
+ const response = await fetch(url);
1577
+ if (response.ok) {
1578
+ this.existingTrackers = await response.json();
1579
+ this.log("Loaded trackers:", this.existingTrackers);
1580
+ }
1581
+ } catch (error) {
1582
+ this.log("Failed to load trackers:", error);
1583
+ this.existingTrackers = [];
1584
+ }
1585
+ }
1586
+ /**
1587
+ * Show the debug toolbar
1588
+ */
1589
+ showToolbar() {
1590
+ if (this.toolbarElement)
1591
+ return;
1592
+ const toolbar = document.createElement("div");
1593
+ toolbar.id = "grain-debug-toolbar";
1594
+ toolbar.innerHTML = `
1595
+ <style>
1596
+ #grain-debug-toolbar {
1597
+ position: fixed;
1598
+ bottom: 20px;
1599
+ right: 20px;
1600
+ background: repeating-linear-gradient(
1601
+ 45deg,
1602
+ #fbbf24,
1603
+ #fbbf24 10px,
1604
+ #1e293b 10px,
1605
+ #1e293b 20px
1606
+ );
1607
+ border: 2px solid #1e293b;
1608
+ border-radius: 12px;
1609
+ padding: 6px;
1610
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2), 0 2px 8px rgba(0, 0, 0, 0.1);
1611
+ z-index: 999999;
1612
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
1613
+ font-size: 13px;
1614
+ }
1615
+
1616
+ .grain-toolbar-inner {
1617
+ display: flex;
1618
+ align-items: center;
1619
+ gap: 12px;
1620
+ background: white;
1621
+ border-radius: 6px;
1622
+ padding: 8px 12px;
1623
+ }
1624
+
1625
+ #grain-debug-toolbar.dragging {
1626
+ cursor: move;
1627
+ user-select: none;
1628
+ }
1629
+
1630
+ .grain-toolbar-header {
1631
+ display: flex;
1632
+ align-items: center;
1633
+ cursor: move;
1634
+ user-select: none;
1635
+ padding: 6px 10px;
1636
+ background: #1e293b;
1637
+ border-radius: 4px;
1638
+ margin-right: 4px;
1639
+ }
1640
+
1641
+ .grain-toolbar-title {
1642
+ font-size: 11px;
1643
+ font-weight: 700;
1644
+ letter-spacing: 1.2px;
1645
+ text-transform: uppercase;
1646
+ color: #fbbf24;
1647
+ }
1648
+
1649
+ .grain-toolbar-body {
1650
+ display: flex;
1651
+ align-items: center;
1652
+ gap: 10px;
1653
+ flex: 1;
1654
+ }
1655
+
1656
+ .grain-toolbar-stats {
1657
+ display: flex;
1658
+ gap: 12px;
1659
+ padding: 6px 10px;
1660
+ background: #f8fafc;
1661
+ border-radius: 4px;
1662
+ margin-right: 4px;
1663
+ }
1664
+
1665
+ .grain-stat {
1666
+ display: flex;
1667
+ align-items: baseline;
1668
+ gap: 6px;
1669
+ }
1670
+
1671
+ .grain-stat-value {
1672
+ font-size: 18px;
1673
+ font-weight: 700;
1674
+ color: #1e293b;
1675
+ }
1676
+
1677
+ .grain-stat-label {
1678
+ font-size: 11px;
1679
+ color: #64748b;
1680
+ font-weight: 500;
1681
+ }
1682
+
1683
+ .grain-toolbar-actions {
1684
+ display: flex;
1685
+ gap: 8px;
1686
+ align-items: center;
1687
+ }
1688
+
1689
+ #grain-debug-toolbar button {
1690
+ background: white;
1691
+ border: 1.5px solid #e2e8f0;
1692
+ color: #475569;
1693
+ padding: 8px 14px;
1694
+ border-radius: 8px;
1695
+ cursor: pointer;
1696
+ font-size: 12px;
1697
+ font-weight: 600;
1698
+ transition: all 0.2s;
1699
+ display: flex;
1700
+ align-items: center;
1701
+ justify-content: center;
1702
+ gap: 6px;
1703
+ white-space: nowrap;
1704
+ }
1705
+
1706
+ #grain-debug-toolbar button:hover {
1707
+ background: #f8fafc;
1708
+ border-color: #cbd5e1;
1709
+ transform: translateY(-1px);
1710
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
1711
+ }
1712
+
1713
+ #grain-debug-toolbar button.active {
1714
+ background: #fbbf24;
1715
+ color: #1e293b;
1716
+ border-color: #fbbf24;
1717
+ box-shadow: 0 2px 8px rgba(251, 191, 36, 0.3);
1718
+ }
1719
+
1720
+ #grain-debug-toolbar button.danger {
1721
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
1722
+ border-color: #ef4444;
1723
+ color: white;
1724
+ }
1725
+
1726
+ #grain-debug-toolbar button.danger:hover {
1727
+ transform: translateY(-1px);
1728
+ box-shadow: 0 2px 8px rgba(239, 68, 68, 0.25);
1729
+ }
1730
+
1731
+ .grain-debug-highlight {
1732
+ position: absolute;
1733
+ pointer-events: none;
1734
+ border: 2px solid #10b981;
1735
+ background: rgba(16, 185, 129, 0.1);
1736
+ z-index: 999998;
1737
+ transition: all 0.1s;
1738
+ }
1739
+
1740
+ .grain-tracker-highlight {
1741
+ position: absolute;
1742
+ pointer-events: none;
1743
+ border: 2px solid #6366f1;
1744
+ background: rgba(99, 102, 241, 0.08);
1745
+ z-index: 999997;
1746
+ transition: opacity 0.2s;
1747
+ }
1748
+
1749
+ .grain-tracker-label {
1750
+ position: absolute;
1751
+ top: -28px;
1752
+ left: 0;
1753
+ background: #6366f1;
1754
+ color: white;
1755
+ padding: 4px 10px;
1756
+ border-radius: 6px;
1757
+ font-size: 11px;
1758
+ font-weight: 600;
1759
+ white-space: nowrap;
1760
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
1761
+ pointer-events: none;
1762
+ }
1763
+
1764
+ .grain-tracker-label::after {
1765
+ content: '';
1766
+ position: absolute;
1767
+ bottom: -4px;
1768
+ left: 10px;
1769
+ width: 0;
1770
+ height: 0;
1771
+ border-left: 4px solid transparent;
1772
+ border-right: 4px solid transparent;
1773
+ border-top: 4px solid #6366f1;
1774
+ }
1775
+
1776
+ .grain-trackers-list {
1777
+ position: fixed;
1778
+ bottom: 80px;
1779
+ right: 20px;
1780
+ background: white;
1781
+ border: 1.5px solid #e2e8f0;
1782
+ border-radius: 10px;
1783
+ padding: 12px;
1784
+ max-height: 400px;
1785
+ width: 320px;
1786
+ overflow-y: auto;
1787
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1), 0 2px 8px rgba(0, 0, 0, 0.06);
1788
+ z-index: 999998;
1789
+ }
1790
+
1791
+ .grain-tracker-item {
1792
+ padding: 10px;
1793
+ background: #f8fafc;
1794
+ border-radius: 8px;
1795
+ margin-bottom: 8px;
1796
+ cursor: pointer;
1797
+ transition: all 0.2s;
1798
+ }
1799
+
1800
+ .grain-tracker-item:hover {
1801
+ background: #f1f5f9;
1802
+ transform: translateX(4px);
1803
+ }
1804
+
1805
+ .grain-tracker-item:last-child {
1806
+ margin-bottom: 0;
1807
+ }
1808
+
1809
+ .grain-tracker-name {
1810
+ font-weight: 600;
1811
+ color: #1e293b;
1812
+ font-size: 13px;
1813
+ margin-bottom: 4px;
1814
+ }
1815
+
1816
+ .grain-tracker-details {
1817
+ font-size: 11px;
1818
+ color: #64748b;
1819
+ display: flex;
1820
+ gap: 8px;
1821
+ align-items: center;
1822
+ }
1823
+
1824
+ .grain-tracker-type {
1825
+ background: #dbeafe;
1826
+ color: #1e40af;
1827
+ padding: 2px 6px;
1828
+ border-radius: 4px;
1829
+ font-weight: 600;
1830
+ text-transform: uppercase;
1831
+ font-size: 9px;
1832
+ letter-spacing: 0.5px;
1833
+ }
1834
+ </style>
1835
+ <div class="grain-toolbar-inner">
1836
+ <div class="grain-toolbar-header" id="grain-toolbar-handle">
1837
+ <div class="grain-toolbar-title">Grain Debug</div>
1838
+ </div>
1839
+ <div class="grain-toolbar-body">
1840
+ <div class="grain-toolbar-stats">
1841
+ <div class="grain-stat">
1842
+ <div class="grain-stat-value">${this.existingTrackers.length}</div>
1843
+ <div class="grain-stat-label">trackers</div>
1844
+ </div>
1845
+ <div class="grain-stat">
1846
+ <div class="grain-stat-value">${this.existingTrackers.filter((t) => t.isEnabled).length}</div>
1847
+ <div class="grain-stat-label">active</div>
1848
+ </div>
1849
+ </div>
1850
+ <div class="grain-toolbar-actions">
1851
+ <button id="grain-debug-inspect" type="button">
1852
+ + New
1853
+ </button>
1854
+ <button id="grain-debug-trackers" class="active" type="button">
1855
+ Hide
1856
+ </button>
1857
+ <button id="grain-debug-end" class="danger" type="button">
1858
+ End Session
1859
+ </button>
1860
+ </div>
1861
+ </div>
1862
+ </div>
1863
+ <div id="grain-trackers-list-container"></div>
1864
+ `;
1865
+ document.body.appendChild(toolbar);
1866
+ this.toolbarElement = toolbar;
1867
+ const handle = toolbar.querySelector("#grain-toolbar-handle");
1868
+ if (handle) {
1869
+ handle.addEventListener("mousedown", (e) => this.startDrag(e));
1870
+ }
1871
+ const inspectBtn = toolbar.querySelector("#grain-debug-inspect");
1872
+ const trackersBtn = toolbar.querySelector("#grain-debug-trackers");
1873
+ const endBtn = toolbar.querySelector("#grain-debug-end");
1874
+ if (inspectBtn) {
1875
+ inspectBtn.addEventListener("click", () => this.toggleInspectMode());
1876
+ }
1877
+ if (trackersBtn) {
1878
+ trackersBtn.addEventListener("click", () => this.toggleTrackerView());
1879
+ }
1880
+ if (endBtn) {
1881
+ endBtn.addEventListener("click", () => this.endDebug());
1882
+ }
1883
+ this.log("Toolbar shown");
1884
+ }
1885
+ /**
1886
+ * Create highlight element for hovering
1887
+ */
1888
+ createHighlightElement() {
1889
+ if (this.highlightElement)
1890
+ return;
1891
+ const highlight = document.createElement("div");
1892
+ highlight.className = "grain-debug-highlight";
1893
+ highlight.style.display = "none";
1894
+ document.body.appendChild(highlight);
1895
+ this.highlightElement = highlight;
1896
+ }
1897
+ /**
1898
+ * Start dragging the toolbar
1899
+ */
1900
+ startDrag(e) {
1901
+ if (!this.toolbarElement)
1902
+ return;
1903
+ this.isDragging = true;
1904
+ this.dragStartX = e.clientX;
1905
+ this.dragStartY = e.clientY;
1906
+ const rect = this.toolbarElement.getBoundingClientRect();
1907
+ this.toolbarStartX = rect.left;
1908
+ this.toolbarStartY = rect.top;
1909
+ this.toolbarElement.classList.add("dragging");
1910
+ this.dragMoveListener = (e2) => this.onDrag(e2);
1911
+ this.dragEndListener = () => this.endDrag();
1912
+ document.addEventListener("mousemove", this.dragMoveListener);
1913
+ document.addEventListener("mouseup", this.dragEndListener);
1914
+ }
1915
+ /**
1916
+ * Handle drag movement
1917
+ */
1918
+ onDrag(e) {
1919
+ if (!this.isDragging || !this.toolbarElement)
1920
+ return;
1921
+ const deltaX = e.clientX - this.dragStartX;
1922
+ const deltaY = e.clientY - this.dragStartY;
1923
+ const newX = this.toolbarStartX + deltaX;
1924
+ const newY = this.toolbarStartY + deltaY;
1925
+ const maxX = window.innerWidth - this.toolbarElement.offsetWidth;
1926
+ const maxY = window.innerHeight - this.toolbarElement.offsetHeight;
1927
+ const clampedX = Math.max(0, Math.min(newX, maxX));
1928
+ const clampedY = Math.max(0, Math.min(newY, maxY));
1929
+ this.toolbarElement.style.left = `${clampedX}px`;
1930
+ this.toolbarElement.style.top = `${clampedY}px`;
1931
+ this.toolbarElement.style.right = "auto";
1932
+ this.toolbarElement.style.bottom = "auto";
1933
+ }
1934
+ /**
1935
+ * End dragging
1936
+ */
1937
+ endDrag() {
1938
+ if (!this.isDragging)
1939
+ return;
1940
+ this.isDragging = false;
1941
+ if (this.toolbarElement) {
1942
+ this.toolbarElement.classList.remove("dragging");
1943
+ }
1944
+ if (this.dragMoveListener) {
1945
+ document.removeEventListener("mousemove", this.dragMoveListener);
1946
+ this.dragMoveListener = null;
1947
+ }
1948
+ if (this.dragEndListener) {
1949
+ document.removeEventListener("mouseup", this.dragEndListener);
1950
+ this.dragEndListener = null;
1951
+ }
1952
+ }
1953
+ /**
1954
+ * Toggle tracker view
1955
+ */
1956
+ toggleTrackerView() {
1957
+ this.showTrackers = !this.showTrackers;
1958
+ const trackersBtn = document.querySelector("#grain-debug-trackers");
1959
+ if (trackersBtn) {
1960
+ trackersBtn.textContent = this.showTrackers ? "Hide" : "View";
1961
+ trackersBtn.classList.toggle("active", this.showTrackers);
1962
+ }
1963
+ if (this.showTrackers) {
1964
+ this.showTrackerHighlights();
1965
+ this.showTrackersList();
1966
+ } else {
1967
+ this.hideTrackerHighlights();
1968
+ this.hideTrackersList();
1969
+ }
1970
+ }
1971
+ /**
1972
+ * Show tracker highlights on page
1973
+ */
1974
+ showTrackerHighlights() {
1975
+ this.hideTrackerHighlights();
1976
+ for (const tracker of this.existingTrackers) {
1977
+ if (!tracker.isEnabled)
1978
+ continue;
1979
+ try {
1980
+ const element = this.findElementBySelector(tracker.selector);
1981
+ if (!element)
1982
+ continue;
1983
+ const rect = element.getBoundingClientRect();
1984
+ const highlight = document.createElement("div");
1985
+ highlight.className = "grain-tracker-highlight";
1986
+ const label = document.createElement("div");
1987
+ label.className = "grain-tracker-label";
1988
+ label.textContent = tracker.name;
1989
+ highlight.style.top = `${rect.top + window.scrollY}px`;
1990
+ highlight.style.left = `${rect.left + window.scrollX}px`;
1991
+ highlight.style.width = `${rect.width}px`;
1992
+ highlight.style.height = `${rect.height}px`;
1993
+ highlight.appendChild(label);
1994
+ document.body.appendChild(highlight);
1995
+ this.trackerHighlights.push(highlight);
1996
+ } catch (error) {
1997
+ this.log("Failed to highlight tracker:", tracker.name, error);
1998
+ }
1999
+ }
2000
+ }
2001
+ /**
2002
+ * Hide tracker highlights
2003
+ */
2004
+ hideTrackerHighlights() {
2005
+ for (const highlight of this.trackerHighlights) {
2006
+ highlight.remove();
2007
+ }
2008
+ this.trackerHighlights = [];
2009
+ }
2010
+ /**
2011
+ * Show trackers list
2012
+ */
2013
+ showTrackersList() {
2014
+ let list = document.querySelector(".grain-trackers-list");
2015
+ if (this.existingTrackers.length === 0) {
2016
+ if (list)
2017
+ list.remove();
2018
+ return;
2019
+ }
2020
+ if (!list) {
2021
+ list = document.createElement("div");
2022
+ list.className = "grain-trackers-list";
2023
+ document.body.appendChild(list);
2024
+ }
2025
+ list.innerHTML = `
2026
+ ${this.existingTrackers.map((tracker) => `
2027
+ <div class="grain-tracker-item" data-tracker-id="${tracker.trackerId}">
2028
+ <div class="grain-tracker-name">${tracker.name}</div>
2029
+ <div class="grain-tracker-details">
2030
+ <span class="grain-tracker-type">${tracker.type}</span>
2031
+ <span>${tracker.urlScope}</span>
2032
+ ${!tracker.isEnabled ? '<span style="color: #ef4444;">\u2022 Disabled</span>' : ""}
2033
+ </div>
2034
+ </div>
2035
+ `).join("")}
2036
+ `;
2037
+ list.querySelectorAll(".grain-tracker-item").forEach((item) => {
2038
+ item.addEventListener("click", () => {
2039
+ const trackerId = item.getAttribute("data-tracker-id");
2040
+ const tracker = this.existingTrackers.find((t) => t.trackerId === trackerId);
2041
+ if (tracker) {
2042
+ this.scrollToTracker(tracker);
2043
+ }
2044
+ });
2045
+ });
2046
+ }
2047
+ /**
2048
+ * Hide trackers list
2049
+ */
2050
+ hideTrackersList() {
2051
+ const list = document.querySelector(".grain-trackers-list");
2052
+ if (list) {
2053
+ list.remove();
2054
+ }
2055
+ }
2056
+ /**
2057
+ * Scroll to and highlight a tracker element
2058
+ */
2059
+ scrollToTracker(tracker) {
2060
+ try {
2061
+ const element = this.findElementBySelector(tracker.selector);
2062
+ if (element) {
2063
+ element.scrollIntoView({ behavior: "smooth", block: "center" });
2064
+ const highlight = this.trackerHighlights.find((h) => {
2065
+ const rect = element.getBoundingClientRect();
2066
+ const hRect = h.getBoundingClientRect();
2067
+ return Math.abs(hRect.top - rect.top) < 5;
2068
+ });
2069
+ if (highlight) {
2070
+ highlight.style.opacity = "0";
2071
+ setTimeout(() => {
2072
+ highlight.style.opacity = "1";
2073
+ }, 100);
2074
+ setTimeout(() => {
2075
+ highlight.style.opacity = "0";
2076
+ }, 300);
2077
+ setTimeout(() => {
2078
+ highlight.style.opacity = "1";
2079
+ }, 500);
2080
+ }
2081
+ }
2082
+ } catch (error) {
2083
+ this.log("Failed to scroll to tracker:", error);
2084
+ }
2085
+ }
2086
+ /**
2087
+ * Find element by XPath selector
2088
+ */
2089
+ findElementBySelector(selector) {
2090
+ try {
2091
+ const result = document.evaluate(
2092
+ selector,
2093
+ document,
2094
+ null,
2095
+ XPathResult.FIRST_ORDERED_NODE_TYPE,
2096
+ null
2097
+ );
2098
+ return result.singleNodeValue;
2099
+ } catch (error) {
2100
+ this.log("Failed to find element:", error);
2101
+ return null;
2102
+ }
2103
+ }
2104
+ /**
2105
+ * Toggle inspect mode
2106
+ */
2107
+ toggleInspectMode() {
2108
+ if (this.isInspectMode) {
2109
+ this.disableInspectMode();
2110
+ } else {
2111
+ this.enableInspectMode();
2112
+ }
2113
+ }
2114
+ /**
2115
+ * Enable element inspection mode
2116
+ */
2117
+ enableInspectMode() {
2118
+ if (this.isInspectMode)
2119
+ return;
2120
+ this.log("Enabling inspect mode");
2121
+ this.isInspectMode = true;
2122
+ const inspectBtn = document.querySelector("#grain-debug-inspect");
2123
+ if (inspectBtn) {
2124
+ inspectBtn.classList.add("active");
2125
+ inspectBtn.textContent = "Click Element";
2126
+ }
2127
+ this.mouseMoveListener = (e) => this.handleMouseMove(e);
2128
+ this.clickListener = (e) => this.handleElementClick(e);
2129
+ document.addEventListener("mousemove", this.mouseMoveListener, true);
2130
+ document.addEventListener("click", this.clickListener, true);
2131
+ document.addEventListener("keydown", this.handleEscapeKey);
2132
+ }
2133
+ /**
2134
+ * Disable element inspection mode
2135
+ */
2136
+ disableInspectMode() {
2137
+ if (!this.isInspectMode)
2138
+ return;
2139
+ this.log("Disabling inspect mode");
2140
+ this.isInspectMode = false;
2141
+ const inspectBtn = document.querySelector("#grain-debug-inspect");
2142
+ if (inspectBtn) {
2143
+ inspectBtn.classList.remove("active");
2144
+ inspectBtn.textContent = "+ New";
2145
+ }
2146
+ if (this.mouseMoveListener) {
2147
+ document.removeEventListener("mousemove", this.mouseMoveListener, true);
2148
+ this.mouseMoveListener = null;
2149
+ }
2150
+ if (this.clickListener) {
2151
+ document.removeEventListener("click", this.clickListener, true);
2152
+ this.clickListener = null;
2153
+ }
2154
+ document.removeEventListener("keydown", this.handleEscapeKey);
2155
+ if (this.highlightElement) {
2156
+ this.highlightElement.style.display = "none";
2157
+ }
2158
+ }
2159
+ /**
2160
+ * Handle mouse move to highlight hovered element
2161
+ */
2162
+ handleMouseMove(e) {
2163
+ if (!this.isInspectMode || !this.highlightElement)
2164
+ return;
2165
+ const target = e.target;
2166
+ if (target.closest("#grain-debug-toolbar") || target.closest("#grain-debug-panel") || target.closest(".grain-trackers-list")) {
2167
+ this.highlightElement.style.display = "none";
2168
+ return;
2169
+ }
2170
+ const element = e.target;
2171
+ const rect = element.getBoundingClientRect();
2172
+ this.highlightElement.style.display = "block";
2173
+ this.highlightElement.style.top = `${rect.top + window.scrollY}px`;
2174
+ this.highlightElement.style.left = `${rect.left + window.scrollX}px`;
2175
+ this.highlightElement.style.width = `${rect.width}px`;
2176
+ this.highlightElement.style.height = `${rect.height}px`;
2177
+ }
2178
+ /**
2179
+ * Handle element click to show creation panel
2180
+ */
2181
+ handleElementClick(e) {
2182
+ if (!this.isInspectMode)
2183
+ return;
2184
+ const target = e.target;
2185
+ if (target.closest("#grain-debug-toolbar") || target.closest(".grain-trackers-list")) {
2186
+ this.disableInspectMode();
2187
+ return;
2188
+ }
2189
+ if (target.closest("#grain-debug-panel")) {
2190
+ e.preventDefault();
2191
+ e.stopPropagation();
2192
+ return;
2193
+ }
2194
+ e.preventDefault();
2195
+ e.stopPropagation();
2196
+ this.selectedElement = target;
2197
+ this.disableInspectMode();
2198
+ this.showCreationPanel(target);
2199
+ }
2200
+ /**
2201
+ * Show tracker creation panel
2202
+ */
2203
+ showCreationPanel(element) {
2204
+ if (this.panelElement) {
2205
+ this.panelElement.remove();
2206
+ }
2207
+ const panel = document.createElement("div");
2208
+ panel.id = "grain-debug-panel";
2209
+ const tagName = element.tagName.toLowerCase();
2210
+ const elementId = element.id;
2211
+ const elementText = element.textContent?.trim().substring(0, 50) || "";
2212
+ const xpath = this.getXPathForElement(element);
2213
+ panel.innerHTML = `
2214
+ <style>
2215
+ #grain-debug-panel {
2216
+ position: fixed;
2217
+ top: 50%;
2218
+ left: 50%;
2219
+ transform: translate(-50%, -50%);
2220
+ background: repeating-linear-gradient(
2221
+ 45deg,
2222
+ #fbbf24,
2223
+ #fbbf24 10px,
2224
+ #1e293b 10px,
2225
+ #1e293b 20px
2226
+ );
2227
+ border: 2px solid #1e293b;
2228
+ border-radius: 16px;
2229
+ padding: 6px;
2230
+ width: 420px;
2231
+ max-width: 90vw;
2232
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25), 0 8px 24px rgba(0, 0, 0, 0.15);
2233
+ z-index: 1000000;
2234
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2235
+ }
2236
+
2237
+ .grain-panel-inner {
2238
+ background: white;
2239
+ border-radius: 10px;
2240
+ overflow: hidden;
2241
+ }
2242
+
2243
+ .grain-panel-header {
2244
+ background: #1e293b;
2245
+ padding: 14px 18px;
2246
+ color: white;
2247
+ }
2248
+
2249
+ .grain-panel-header h3 {
2250
+ margin: 0 0 4px 0;
2251
+ font-size: 16px;
2252
+ font-weight: 700;
2253
+ letter-spacing: 0.5px;
2254
+ color: #fbbf24;
2255
+ text-transform: uppercase;
2256
+ }
2257
+
2258
+ .grain-panel-header p {
2259
+ margin: 0;
2260
+ font-size: 12px;
2261
+ opacity: 0.85;
2262
+ color: white;
2263
+ }
2264
+
2265
+ .grain-panel-body {
2266
+ padding: 18px;
2267
+ }
2268
+
2269
+ #grain-debug-panel .element-preview {
2270
+ background: #f8fafc;
2271
+ border: 1.5px solid #e2e8f0;
2272
+ border-radius: 8px;
2273
+ padding: 10px 12px;
2274
+ margin-bottom: 16px;
2275
+ font-size: 11px;
2276
+ color: #475569;
2277
+ }
2278
+
2279
+ #grain-debug-panel .element-preview div {
2280
+ margin-bottom: 4px;
2281
+ }
2282
+
2283
+ #grain-debug-panel .element-preview div:last-child {
2284
+ margin-bottom: 0;
2285
+ }
2286
+
2287
+ #grain-debug-panel .element-preview strong {
2288
+ color: #1e293b;
2289
+ font-weight: 600;
2290
+ }
2291
+
2292
+ #grain-debug-panel label {
2293
+ display: block;
2294
+ color: #1e293b;
2295
+ font-size: 12px;
2296
+ font-weight: 600;
2297
+ margin-bottom: 6px;
2298
+ }
2299
+
2300
+ #grain-debug-panel input,
2301
+ #grain-debug-panel select {
2302
+ width: 100%;
2303
+ background: white;
2304
+ border: 1.5px solid #e2e8f0;
2305
+ border-radius: 8px;
2306
+ padding: 9px 12px;
2307
+ color: #1e293b;
2308
+ font-size: 13px;
2309
+ margin-bottom: 14px;
2310
+ box-sizing: border-box;
2311
+ transition: all 0.2s;
2312
+ }
2313
+
2314
+ #grain-debug-panel input:focus,
2315
+ #grain-debug-panel select:focus {
2316
+ outline: none;
2317
+ border-color: #fbbf24;
2318
+ box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.1);
2319
+ }
2320
+
2321
+ #grain-debug-panel .button-group {
2322
+ display: flex;
2323
+ gap: 8px;
2324
+ margin-top: 18px;
2325
+ }
2326
+
2327
+ #grain-debug-panel button {
2328
+ flex: 1;
2329
+ padding: 10px 16px;
2330
+ border: none;
2331
+ border-radius: 8px;
2332
+ font-size: 13px;
2333
+ font-weight: 600;
2334
+ cursor: pointer;
2335
+ transition: all 0.2s;
2336
+ }
2337
+
2338
+ #grain-debug-panel button.primary {
2339
+ background: #fbbf24;
2340
+ color: #1e293b;
2341
+ box-shadow: 0 2px 8px rgba(251, 191, 36, 0.3);
2342
+ }
2343
+
2344
+ #grain-debug-panel button.primary:hover {
2345
+ background: #f59e0b;
2346
+ transform: translateY(-1px);
2347
+ box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4);
2348
+ }
2349
+
2350
+ #grain-debug-panel button.secondary {
2351
+ background: white;
2352
+ border: 1.5px solid #e2e8f0;
2353
+ color: #475569;
2354
+ }
2355
+
2356
+ #grain-debug-panel button.secondary:hover {
2357
+ background: #f8fafc;
2358
+ border-color: #cbd5e1;
2359
+ }
2360
+
2361
+ #grain-debug-panel .url-pattern-input {
2362
+ display: none;
2363
+ }
2364
+
2365
+ #grain-debug-panel .url-pattern-input.visible {
2366
+ display: block;
2367
+ }
2368
+ </style>
2369
+ <div class="grain-panel-inner">
2370
+ <div class="grain-panel-header">
2371
+ <h3>Create Tracker</h3>
2372
+ <p>Set up automatic tracking for this element</p>
2373
+ </div>
2374
+ <div class="grain-panel-body">
2375
+ <div class="element-preview">
2376
+ <div><strong>Element:</strong> ${tagName}${elementId ? `#${elementId}` : ""}</div>
2377
+ ${elementText ? `<div><strong>Text:</strong> ${elementText}</div>` : ""}
2378
+ </div>
2379
+ <div>
2380
+ <label>Event Name</label>
2381
+ <input type="text" id="grain-event-name" placeholder="e.g., signup_button_click" value="" />
2382
+ </div>
2383
+ <div>
2384
+ <label>Type</label>
2385
+ <select id="grain-event-type">
2386
+ <option value="metric">Metric</option>
2387
+ <option value="conversion">Conversion</option>
2388
+ </select>
2389
+ </div>
2390
+ <div>
2391
+ <label>URL Scope</label>
2392
+ <select id="grain-url-scope">
2393
+ <option value="all">All Pages</option>
2394
+ <option value="contains" selected>This Page</option>
2395
+ <option value="equals">Exact URL</option>
2396
+ </select>
2397
+ </div>
2398
+ <div class="url-pattern-input visible" id="grain-url-pattern-container">
2399
+ <label>URL Pattern</label>
2400
+ <input type="text" id="grain-url-pattern" placeholder="e.g., /pricing" value="${window.location.pathname}" />
2401
+ </div>
2402
+ <div class="button-group">
2403
+ <button type="button" class="secondary" id="grain-cancel">Cancel</button>
2404
+ <button type="button" class="primary" id="grain-create">\u2713 Create</button>
2405
+ </div>
2406
+ </div>
2407
+ </div>
2408
+ `;
2409
+ document.body.appendChild(panel);
2410
+ this.panelElement = panel;
2411
+ const eventNameInput = panel.querySelector("#grain-event-name");
2412
+ if (eventNameInput) {
2413
+ const suggestedName = this.generateEventName(element);
2414
+ eventNameInput.value = suggestedName;
2415
+ eventNameInput.select();
2416
+ }
2417
+ const urlScopeSelect = panel.querySelector("#grain-url-scope");
2418
+ const urlPatternContainer = panel.querySelector("#grain-url-pattern-container");
2419
+ const urlPatternInput = panel.querySelector("#grain-url-pattern");
2420
+ if (urlScopeSelect && urlPatternContainer) {
2421
+ urlScopeSelect.addEventListener("change", () => {
2422
+ if (urlScopeSelect.value === "all") {
2423
+ urlPatternContainer.classList.remove("visible");
2424
+ } else {
2425
+ urlPatternContainer.classList.add("visible");
2426
+ if (urlPatternInput && !urlPatternInput.value) {
2427
+ urlPatternInput.value = window.location.pathname;
2428
+ }
2429
+ }
2430
+ });
2431
+ }
2432
+ const cancelBtn = panel.querySelector("#grain-cancel");
2433
+ const createBtn = panel.querySelector("#grain-create");
2434
+ if (cancelBtn) {
2435
+ cancelBtn.addEventListener("click", () => this.hideCreationPanel());
2436
+ }
2437
+ if (createBtn) {
2438
+ createBtn.addEventListener("click", () => this.handleCreateTracker(xpath));
2439
+ }
2440
+ }
2441
+ /**
2442
+ * Generate suggested event name from element
2443
+ */
2444
+ generateEventName(element) {
2445
+ const tagName = element.tagName.toLowerCase();
2446
+ const elementId = element.id;
2447
+ const elementText = element.textContent?.trim().toLowerCase().replace(/\s+/g, "_").substring(0, 30) || "";
2448
+ if (elementId) {
2449
+ return `${elementId}_click`;
2450
+ } else if (elementText) {
2451
+ return `${elementText}_click`;
2452
+ } else if (tagName === "button") {
2453
+ return "button_click";
2454
+ } else if (tagName === "a") {
2455
+ return "link_click";
2456
+ } else {
2457
+ return `${tagName}_click`;
2458
+ }
2459
+ }
2460
+ /**
2461
+ * Handle tracker creation
2462
+ */
2463
+ async handleCreateTracker(selector) {
2464
+ if (!this.panelElement)
2465
+ return;
2466
+ const eventNameInput = this.panelElement.querySelector("#grain-event-name");
2467
+ const eventTypeSelect = this.panelElement.querySelector("#grain-event-type");
2468
+ const urlScopeSelect = this.panelElement.querySelector("#grain-url-scope");
2469
+ const urlPatternInput = this.panelElement.querySelector("#grain-url-pattern");
2470
+ if (!eventNameInput || !eventTypeSelect || !urlScopeSelect)
2471
+ return;
2472
+ const eventName = eventNameInput.value.trim();
2473
+ const eventType = eventTypeSelect.value;
2474
+ const urlScope = urlScopeSelect.value;
2475
+ const urlPattern = urlPatternInput?.value.trim() || void 0;
2476
+ if (!eventName) {
2477
+ alert("Please enter an event name");
2478
+ return;
2479
+ }
2480
+ if (!eventName.match(/^[a-zA-Z0-9_-]+$/)) {
2481
+ alert("Event name can only contain letters, numbers, underscores, and hyphens");
2482
+ return;
2483
+ }
2484
+ if ((urlScope === "contains" || urlScope === "equals") && !urlPattern) {
2485
+ alert("Please enter a URL pattern");
2486
+ return;
2487
+ }
2488
+ try {
2489
+ const createBtn = this.panelElement.querySelector("#grain-create");
2490
+ if (createBtn) {
2491
+ createBtn.textContent = "Creating...";
2492
+ createBtn.disabled = true;
2493
+ }
2494
+ await this.createTracker(eventName, eventType, selector, urlScope, urlPattern);
2495
+ this.hideCreationPanel();
2496
+ this.showSuccessMessage(`Tracker "${eventName}" created successfully!`);
2497
+ await this.loadExistingTrackers();
2498
+ this.updateToolbarStats();
2499
+ if (this.showTrackers) {
2500
+ this.showTrackerHighlights();
2501
+ this.showTrackersList();
2502
+ }
2503
+ this.log("Tracker created:", eventName);
2504
+ } catch (error) {
2505
+ alert("Failed to create tracker. Please try again.");
2506
+ this.log("Failed to create tracker:", error);
2507
+ const createBtn = this.panelElement.querySelector("#grain-create");
2508
+ if (createBtn) {
2509
+ createBtn.textContent = "Create Tracker";
2510
+ createBtn.disabled = false;
2511
+ }
2512
+ }
2513
+ }
2514
+ /**
2515
+ * Create tracker via API
2516
+ */
2517
+ async createTracker(name, type, selector, urlScope, urlPattern) {
2518
+ const url = `${this.apiUrl}/v1/tenant/${encodeURIComponent(this.tenantId)}/debug-sessions/${this.sessionId}/trackers`;
2519
+ const response = await fetch(url, {
2520
+ method: "POST",
2521
+ headers: {
2522
+ "Content-Type": "application/json"
2523
+ },
2524
+ body: JSON.stringify({
2525
+ name,
2526
+ type,
2527
+ selector,
2528
+ urlScope,
2529
+ urlPattern
2530
+ })
2531
+ });
2532
+ if (!response.ok) {
2533
+ throw new Error(`Failed to create tracker: ${response.status}`);
2534
+ }
2535
+ }
2536
+ /**
2537
+ * Hide creation panel
2538
+ */
2539
+ hideCreationPanel() {
2540
+ if (this.panelElement) {
2541
+ this.panelElement.remove();
2542
+ this.panelElement = null;
2543
+ }
2544
+ this.selectedElement = null;
2545
+ }
2546
+ /**
2547
+ * Show success message
2548
+ */
2549
+ showSuccessMessage(message) {
2550
+ const toast = document.createElement("div");
2551
+ toast.style.cssText = `
2552
+ position: fixed;
2553
+ top: 20px;
2554
+ left: 50%;
2555
+ transform: translateX(-50%) translateY(-20px);
2556
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
2557
+ color: white;
2558
+ padding: 14px 24px;
2559
+ border-radius: 12px;
2560
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2561
+ font-size: 14px;
2562
+ font-weight: 600;
2563
+ box-shadow: 0 12px 32px rgba(16, 185, 129, 0.3), 0 4px 12px rgba(0, 0, 0, 0.15);
2564
+ z-index: 1000000;
2565
+ display: flex;
2566
+ align-items: center;
2567
+ gap: 10px;
2568
+ animation: slideDown 0.3s ease-out forwards;
2569
+ `;
2570
+ toast.innerHTML = `
2571
+ <style>
2572
+ @keyframes slideDown {
2573
+ to {
2574
+ transform: translateX(-50%) translateY(0);
2575
+ }
2576
+ }
2577
+ </style>
2578
+ <span style="font-size: 18px;">\u2713</span>
2579
+ <span>${message}</span>
2580
+ `;
2581
+ document.body.appendChild(toast);
2582
+ setTimeout(() => {
2583
+ toast.style.animation = "slideDown 0.3s ease-in reverse";
2584
+ setTimeout(() => {
2585
+ toast.remove();
2586
+ }, 300);
2587
+ }, 2700);
2588
+ }
2589
+ /**
2590
+ * End debug session
2591
+ */
2592
+ async endDebug() {
2593
+ try {
2594
+ const url2 = `${this.apiUrl}/v1/tenant/${encodeURIComponent(this.tenantId)}/debug-sessions/${this.sessionId}/end`;
2595
+ await fetch(url2, {
2596
+ method: "POST",
2597
+ headers: {
2598
+ "Content-Type": "application/json"
2599
+ }
2600
+ });
2601
+ } catch (error) {
2602
+ this.log("Failed to end debug session:", error);
2603
+ }
2604
+ this.destroy();
2605
+ const url = new URL(window.location.href);
2606
+ url.searchParams.delete("grain_debug");
2607
+ url.searchParams.delete("grain_session");
2608
+ window.location.href = url.toString();
2609
+ }
2610
+ /**
2611
+ * Get XPath for element
2612
+ */
2613
+ getXPathForElement(element) {
2614
+ if (element.id) {
2615
+ return `//*[@id="${element.id}"]`;
2616
+ }
2617
+ const parts = [];
2618
+ let current = element;
2619
+ while (current && current.nodeType === Node.ELEMENT_NODE) {
2620
+ let index = 0;
2621
+ let sibling = current;
2622
+ while (sibling) {
2623
+ if (sibling.nodeType === Node.ELEMENT_NODE && sibling.tagName === current.tagName) {
2624
+ index++;
2625
+ }
2626
+ sibling = sibling.previousElementSibling;
2627
+ }
2628
+ const tagName = current.tagName.toLowerCase();
2629
+ const pathIndex = index > 1 ? `[${index}]` : "";
2630
+ parts.unshift(`${tagName}${pathIndex}`);
2631
+ current = current.parentElement;
2632
+ }
2633
+ return parts.length ? `/${parts.join("/")}` : "";
2634
+ }
2635
+ /**
2636
+ * Log debug messages
2637
+ */
2638
+ log(...args) {
2639
+ if (this.config.debug) {
2640
+ console.log("[DebugAgent]", ...args);
2641
+ }
2642
+ }
2643
+ /**
2644
+ * Update toolbar stats
2645
+ */
2646
+ updateToolbarStats() {
2647
+ if (!this.toolbarElement)
2648
+ return;
2649
+ const totalStat = this.toolbarElement.querySelector(".grain-stat:nth-child(1) .grain-stat-value");
2650
+ const activeStat = this.toolbarElement.querySelector(".grain-stat:nth-child(2) .grain-stat-value");
2651
+ if (totalStat) {
2652
+ totalStat.textContent = String(this.existingTrackers.length);
2653
+ }
2654
+ if (activeStat) {
2655
+ activeStat.textContent = String(this.existingTrackers.filter((t) => t.isEnabled).length);
2656
+ }
2657
+ }
2658
+ /**
2659
+ * Destroy the debug agent
2660
+ */
2661
+ destroy() {
2662
+ if (this.isDestroyed)
2663
+ return;
2664
+ this.log("Destroying debug agent");
2665
+ this.isDestroyed = true;
2666
+ this.disableInspectMode();
2667
+ this.hideTrackerHighlights();
2668
+ this.endDrag();
2669
+ if (this.toolbarElement) {
2670
+ this.toolbarElement.remove();
2671
+ this.toolbarElement = null;
2672
+ }
2673
+ if (this.panelElement) {
2674
+ this.panelElement.remove();
2675
+ this.panelElement = null;
2676
+ }
2677
+ if (this.highlightElement) {
2678
+ this.highlightElement.remove();
2679
+ this.highlightElement = null;
2680
+ }
2681
+ }
2682
+ };
2683
+ }
2684
+ });
2685
+
1461
2686
  // src/index.ts
1462
2687
  var src_exports = {};
1463
2688
  __export(src_exports, {
@@ -5304,6 +6529,9 @@ var Grain = (() => {
5304
6529
  // Session tracking
5305
6530
  this.sessionStartTime = Date.now();
5306
6531
  this.sessionEventCount = 0;
6532
+ // Debug mode properties
6533
+ this.debugAgent = null;
6534
+ this.isDebugMode = false;
5307
6535
  this.config = {
5308
6536
  apiUrl: "https://api.grainql.com",
5309
6537
  authStrategy: "NONE",
@@ -5358,6 +6586,7 @@ var Grain = (() => {
5358
6586
  this.initializeConfigCache();
5359
6587
  this.ephemeralSessionId = this.generateUUID();
5360
6588
  if (typeof window !== "undefined") {
6589
+ this.checkAndInitializeDebugMode();
5361
6590
  this.initializeAutomaticTracking();
5362
6591
  this.trackSessionStart();
5363
6592
  if (this.config.enableHeatmapTracking) {
@@ -5933,7 +7162,9 @@ var Grain = (() => {
5933
7162
  {
5934
7163
  debug: this.config.debug,
5935
7164
  enableMutationObserver: true,
5936
- mutationDebounceDelay: 500
7165
+ mutationDebounceDelay: 500,
7166
+ tenantId: this.config.tenantId,
7167
+ apiUrl: this.config.apiUrl
5937
7168
  }
5938
7169
  );
5939
7170
  this.log("Interaction tracking initialized");
@@ -6911,6 +8142,91 @@ var Grain = (() => {
6911
8142
  offConsentChange(listener) {
6912
8143
  this.consentManager.removeListener(listener);
6913
8144
  }
8145
+ /**
8146
+ * Check for debug mode parameters and initialize debug agent if valid
8147
+ */
8148
+ checkAndInitializeDebugMode() {
8149
+ if (typeof window === "undefined")
8150
+ return;
8151
+ try {
8152
+ const urlParams = new URLSearchParams(window.location.search);
8153
+ const isDebug = urlParams.get("grain_debug") === "1";
8154
+ const sessionId = urlParams.get("grain_session");
8155
+ if (!isDebug || !sessionId) {
8156
+ return;
8157
+ }
8158
+ this.log("Debug mode detected, verifying session:", sessionId);
8159
+ this.verifyDebugSession(sessionId, window.location.hostname).then((valid) => {
8160
+ if (valid) {
8161
+ this.log("Debug session verified, initializing debug agent");
8162
+ this.isDebugMode = true;
8163
+ this.initializeDebugAgent(sessionId);
8164
+ } else {
8165
+ this.log("Debug session verification failed");
8166
+ }
8167
+ }).catch((error) => {
8168
+ this.log("Failed to verify debug session:", error);
8169
+ });
8170
+ } catch (error) {
8171
+ this.log("Error checking debug mode:", error);
8172
+ }
8173
+ }
8174
+ /**
8175
+ * Verify debug session with API
8176
+ */
8177
+ async verifyDebugSession(sessionId, domain) {
8178
+ try {
8179
+ const url = `${this.config.apiUrl}/v1/tenant/${encodeURIComponent(this.config.tenantId)}/debug-sessions/verify`;
8180
+ const response = await fetch(url, {
8181
+ method: "POST",
8182
+ headers: {
8183
+ "Content-Type": "application/json"
8184
+ },
8185
+ body: JSON.stringify({
8186
+ sessionId,
8187
+ domain
8188
+ })
8189
+ });
8190
+ if (!response.ok) {
8191
+ return false;
8192
+ }
8193
+ const result = await response.json();
8194
+ return result.valid === true;
8195
+ } catch (error) {
8196
+ this.log("Debug session verification error:", error);
8197
+ return false;
8198
+ }
8199
+ }
8200
+ /**
8201
+ * Initialize debug agent
8202
+ */
8203
+ initializeDebugAgent(sessionId) {
8204
+ if (typeof window === "undefined")
8205
+ return;
8206
+ try {
8207
+ this.log("Loading debug agent module");
8208
+ Promise.resolve().then(() => (init_debug_agent(), debug_agent_exports)).then(({ DebugAgent: DebugAgent2 }) => {
8209
+ try {
8210
+ this.debugAgent = new DebugAgent2(
8211
+ this,
8212
+ sessionId,
8213
+ this.config.tenantId,
8214
+ this.config.apiUrl,
8215
+ {
8216
+ debug: this.config.debug
8217
+ }
8218
+ );
8219
+ this.log("Debug agent initialized");
8220
+ } catch (error) {
8221
+ this.log("Failed to initialize debug agent:", error);
8222
+ }
8223
+ }).catch((error) => {
8224
+ this.log("Failed to load debug agent module:", error);
8225
+ });
8226
+ } catch (error) {
8227
+ this.log("Error initializing debug agent:", error);
8228
+ }
8229
+ }
6914
8230
  /**
6915
8231
  * Destroy the client and clean up resources
6916
8232
  */
@@ -6946,6 +8262,10 @@ var Grain = (() => {
6946
8262
  this.heatmapTrackingManager.destroy();
6947
8263
  this.heatmapTrackingManager = null;
6948
8264
  }
8265
+ if (this.debugAgent) {
8266
+ this.debugAgent.destroy();
8267
+ this.debugAgent = null;
8268
+ }
6949
8269
  if (this.eventQueue.length > 0) {
6950
8270
  const eventsToSend = [...this.eventQueue];
6951
8271
  this.eventQueue = [];