@grainql/analytics-web 2.7.0 → 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.
- package/dist/cjs/debug-agent.d.ts +171 -0
- package/dist/cjs/debug-agent.d.ts.map +1 -0
- package/dist/cjs/debug-agent.js +1219 -0
- package/dist/cjs/debug-agent.js.map +1 -0
- package/dist/cjs/heatmap-tracking.d.ts.map +1 -1
- package/dist/cjs/heatmap-tracking.js +2 -1
- package/dist/cjs/heatmap-tracking.js.map +1 -1
- package/dist/cjs/index.d.ts +14 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/interaction-tracking.d.ts +6 -0
- package/dist/cjs/interaction-tracking.d.ts.map +1 -1
- package/dist/cjs/interaction-tracking.js +57 -6
- package/dist/cjs/interaction-tracking.js.map +1 -1
- package/dist/cjs/text-utils.d.ts +14 -0
- package/dist/cjs/text-utils.d.ts.map +1 -0
- package/dist/cjs/text-utils.js +49 -0
- package/dist/cjs/text-utils.js.map +1 -0
- package/dist/debug-agent.d.ts +171 -0
- package/dist/debug-agent.d.ts.map +1 -0
- package/dist/debug-agent.js +1219 -0
- package/dist/esm/debug-agent.d.ts +171 -0
- package/dist/esm/debug-agent.d.ts.map +1 -0
- package/dist/esm/debug-agent.js +1215 -0
- package/dist/esm/debug-agent.js.map +1 -0
- package/dist/esm/heatmap-tracking.d.ts.map +1 -1
- package/dist/esm/heatmap-tracking.js +2 -1
- package/dist/esm/heatmap-tracking.js.map +1 -1
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/interaction-tracking.d.ts +6 -0
- package/dist/esm/interaction-tracking.d.ts.map +1 -1
- package/dist/esm/interaction-tracking.js +57 -6
- package/dist/esm/interaction-tracking.js.map +1 -1
- package/dist/esm/text-utils.d.ts +14 -0
- package/dist/esm/text-utils.d.ts.map +1 -0
- package/dist/esm/text-utils.js +45 -0
- package/dist/esm/text-utils.js.map +1 -0
- package/dist/heatmap-tracking.d.ts.map +1 -1
- package/dist/heatmap-tracking.js +2 -1
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.dev.js +1350 -8
- package/dist/index.global.dev.js.map +4 -4
- package/dist/index.global.js +506 -2
- package/dist/index.global.js.map +4 -4
- package/dist/index.js +99 -0
- package/dist/index.mjs +99 -0
- package/dist/interaction-tracking.d.ts +6 -0
- package/dist/interaction-tracking.d.ts.map +1 -1
- package/dist/interaction-tracking.js +57 -6
- package/dist/text-utils.d.ts +14 -0
- package/dist/text-utils.d.ts.map +1 -0
- package/dist/text-utils.js +49 -0
- package/package.json +1 -1
package/dist/index.global.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Grain Analytics Web SDK v2.
|
|
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;
|
|
@@ -249,6 +249,26 @@ var Grain = (() => {
|
|
|
249
249
|
}
|
|
250
250
|
});
|
|
251
251
|
|
|
252
|
+
// src/text-utils.ts
|
|
253
|
+
function removeEmojis(text) {
|
|
254
|
+
if (!text)
|
|
255
|
+
return void 0;
|
|
256
|
+
return text.replace(/[\u{1F300}-\u{1F9FF}]/gu, "").replace(/[\u{1F600}-\u{1F64F}]/gu, "").replace(/[\u{1F680}-\u{1F6FF}]/gu, "").replace(/[\u{2600}-\u{26FF}]/gu, "").replace(/[\u{2700}-\u{27BF}]/gu, "").replace(/[\u{1F900}-\u{1F9FF}]/gu, "").replace(/[\u{1F1E0}-\u{1F1FF}]/gu, "").replace(/[\u{200D}]/gu, "").replace(/[\u{FE0F}]/gu, "").replace(/[\u{20E3}]/gu, "").trim();
|
|
257
|
+
}
|
|
258
|
+
function cleanElementText(text, maxLength = 100) {
|
|
259
|
+
if (!text)
|
|
260
|
+
return void 0;
|
|
261
|
+
const cleaned = removeEmojis(text);
|
|
262
|
+
if (!cleaned)
|
|
263
|
+
return void 0;
|
|
264
|
+
return cleaned.substring(0, maxLength) || void 0;
|
|
265
|
+
}
|
|
266
|
+
var init_text_utils = __esm({
|
|
267
|
+
"src/text-utils.ts"() {
|
|
268
|
+
"use strict";
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
252
272
|
// src/heatmap-tracking.ts
|
|
253
273
|
var heatmap_tracking_exports = {};
|
|
254
274
|
__export(heatmap_tracking_exports, {
|
|
@@ -259,6 +279,7 @@ var Grain = (() => {
|
|
|
259
279
|
"src/heatmap-tracking.ts"() {
|
|
260
280
|
"use strict";
|
|
261
281
|
init_attention_quality();
|
|
282
|
+
init_text_utils();
|
|
262
283
|
DEFAULT_OPTIONS2 = {
|
|
263
284
|
scrollDebounceDelay: 100,
|
|
264
285
|
batchDelay: 2e3,
|
|
@@ -465,7 +486,7 @@ var Grain = (() => {
|
|
|
465
486
|
const pageX = Math.round(event.pageX);
|
|
466
487
|
const pageY = Math.round(event.pageY);
|
|
467
488
|
const elementTag = element.tagName?.toLowerCase() || "unknown";
|
|
468
|
-
const elementText = element.textContent
|
|
489
|
+
const elementText = cleanElementText(element.textContent);
|
|
469
490
|
const clickData = {
|
|
470
491
|
pageUrl,
|
|
471
492
|
xpath,
|
|
@@ -721,6 +742,7 @@ var Grain = (() => {
|
|
|
721
742
|
var init_interaction_tracking = __esm({
|
|
722
743
|
"src/interaction-tracking.ts"() {
|
|
723
744
|
"use strict";
|
|
745
|
+
init_text_utils();
|
|
724
746
|
InteractionTrackingManager = class {
|
|
725
747
|
constructor(tracker, interactions, config = {}) {
|
|
726
748
|
this.isDestroyed = false;
|
|
@@ -733,19 +755,65 @@ var Grain = (() => {
|
|
|
733
755
|
this.config = {
|
|
734
756
|
debug: config.debug ?? false,
|
|
735
757
|
enableMutationObserver: config.enableMutationObserver ?? true,
|
|
736
|
-
mutationDebounceDelay: config.mutationDebounceDelay ?? 500
|
|
758
|
+
mutationDebounceDelay: config.mutationDebounceDelay ?? 500,
|
|
759
|
+
tenantId: config.tenantId,
|
|
760
|
+
apiUrl: config.apiUrl
|
|
737
761
|
};
|
|
738
762
|
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
739
|
-
if (
|
|
740
|
-
|
|
763
|
+
if (this.config.tenantId && this.config.apiUrl) {
|
|
764
|
+
this.fetchAndMergeTrackers().then(() => {
|
|
765
|
+
this.attachAllListeners();
|
|
766
|
+
});
|
|
741
767
|
} else {
|
|
742
|
-
|
|
768
|
+
if (document.readyState === "loading") {
|
|
769
|
+
document.addEventListener("DOMContentLoaded", () => this.attachAllListeners());
|
|
770
|
+
} else {
|
|
771
|
+
setTimeout(() => this.attachAllListeners(), 0);
|
|
772
|
+
}
|
|
743
773
|
}
|
|
744
774
|
if (this.config.enableMutationObserver) {
|
|
745
775
|
this.setupMutationObserver();
|
|
746
776
|
}
|
|
747
777
|
}
|
|
748
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
|
+
}
|
|
749
817
|
/**
|
|
750
818
|
* Attach listeners to all configured interactions
|
|
751
819
|
*/
|
|
@@ -799,7 +867,7 @@ var Grain = (() => {
|
|
|
799
867
|
interaction_description: interaction.description,
|
|
800
868
|
interaction_priority: interaction.priority,
|
|
801
869
|
element_tag: element.tagName?.toLowerCase(),
|
|
802
|
-
element_text: element.textContent
|
|
870
|
+
element_text: cleanElementText(element.textContent),
|
|
803
871
|
element_id: element.id || void 0,
|
|
804
872
|
element_class: element.className || void 0,
|
|
805
873
|
...isNavigationLink && { href: element.href },
|
|
@@ -1436,6 +1504,1185 @@ var Grain = (() => {
|
|
|
1436
1504
|
}
|
|
1437
1505
|
});
|
|
1438
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
|
+
|
|
1439
2686
|
// src/index.ts
|
|
1440
2687
|
var src_exports = {};
|
|
1441
2688
|
__export(src_exports, {
|
|
@@ -5282,6 +6529,9 @@ var Grain = (() => {
|
|
|
5282
6529
|
// Session tracking
|
|
5283
6530
|
this.sessionStartTime = Date.now();
|
|
5284
6531
|
this.sessionEventCount = 0;
|
|
6532
|
+
// Debug mode properties
|
|
6533
|
+
this.debugAgent = null;
|
|
6534
|
+
this.isDebugMode = false;
|
|
5285
6535
|
this.config = {
|
|
5286
6536
|
apiUrl: "https://api.grainql.com",
|
|
5287
6537
|
authStrategy: "NONE",
|
|
@@ -5336,6 +6586,7 @@ var Grain = (() => {
|
|
|
5336
6586
|
this.initializeConfigCache();
|
|
5337
6587
|
this.ephemeralSessionId = this.generateUUID();
|
|
5338
6588
|
if (typeof window !== "undefined") {
|
|
6589
|
+
this.checkAndInitializeDebugMode();
|
|
5339
6590
|
this.initializeAutomaticTracking();
|
|
5340
6591
|
this.trackSessionStart();
|
|
5341
6592
|
if (this.config.enableHeatmapTracking) {
|
|
@@ -5911,7 +7162,9 @@ var Grain = (() => {
|
|
|
5911
7162
|
{
|
|
5912
7163
|
debug: this.config.debug,
|
|
5913
7164
|
enableMutationObserver: true,
|
|
5914
|
-
mutationDebounceDelay: 500
|
|
7165
|
+
mutationDebounceDelay: 500,
|
|
7166
|
+
tenantId: this.config.tenantId,
|
|
7167
|
+
apiUrl: this.config.apiUrl
|
|
5915
7168
|
}
|
|
5916
7169
|
);
|
|
5917
7170
|
this.log("Interaction tracking initialized");
|
|
@@ -6889,6 +8142,91 @@ var Grain = (() => {
|
|
|
6889
8142
|
offConsentChange(listener) {
|
|
6890
8143
|
this.consentManager.removeListener(listener);
|
|
6891
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
|
+
}
|
|
6892
8230
|
/**
|
|
6893
8231
|
* Destroy the client and clean up resources
|
|
6894
8232
|
*/
|
|
@@ -6924,6 +8262,10 @@ var Grain = (() => {
|
|
|
6924
8262
|
this.heatmapTrackingManager.destroy();
|
|
6925
8263
|
this.heatmapTrackingManager = null;
|
|
6926
8264
|
}
|
|
8265
|
+
if (this.debugAgent) {
|
|
8266
|
+
this.debugAgent.destroy();
|
|
8267
|
+
this.debugAgent = null;
|
|
8268
|
+
}
|
|
6927
8269
|
if (this.eventQueue.length > 0) {
|
|
6928
8270
|
const eventsToSend = [...this.eventQueue];
|
|
6929
8271
|
this.eventQueue = [];
|