@grainql/analytics-web 2.7.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/dist/cjs/consent.d.ts +38 -7
- package/dist/cjs/consent.d.ts.map +1 -1
- package/dist/cjs/consent.js +82 -23
- package/dist/cjs/consent.js.map +1 -1
- 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/id-manager.d.ts +66 -0
- package/dist/cjs/id-manager.d.ts.map +1 -0
- package/dist/cjs/id-manager.js +212 -0
- package/dist/cjs/id-manager.js.map +1 -0
- package/dist/cjs/index.d.ts +26 -8
- 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 +55 -5
- package/dist/cjs/interaction-tracking.js.map +1 -1
- package/dist/cjs/page-tracking.d.ts +6 -0
- package/dist/cjs/page-tracking.d.ts.map +1 -1
- package/dist/cjs/page-tracking.js +23 -2
- package/dist/cjs/page-tracking.js.map +1 -1
- package/dist/cjs/react/hooks/useConsent.d.ts +18 -2
- package/dist/cjs/react/hooks/useConsent.d.ts.map +1 -1
- package/dist/cjs/react/hooks/useConsent.js +52 -1
- package/dist/cjs/react/hooks/useConsent.js.map +1 -1
- package/dist/consent.d.ts +38 -7
- package/dist/consent.d.ts.map +1 -1
- package/dist/consent.js +82 -23
- 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/consent.d.ts +38 -7
- package/dist/esm/consent.d.ts.map +1 -1
- package/dist/esm/consent.js +82 -23
- package/dist/esm/consent.js.map +1 -1
- 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/id-manager.d.ts +66 -0
- package/dist/esm/id-manager.d.ts.map +1 -0
- package/dist/esm/id-manager.js +208 -0
- package/dist/esm/id-manager.js.map +1 -0
- package/dist/esm/index.d.ts +26 -8
- 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 +55 -5
- package/dist/esm/interaction-tracking.js.map +1 -1
- package/dist/esm/page-tracking.d.ts +6 -0
- package/dist/esm/page-tracking.d.ts.map +1 -1
- package/dist/esm/page-tracking.js +23 -2
- package/dist/esm/page-tracking.js.map +1 -1
- package/dist/esm/react/hooks/useConsent.d.ts +18 -2
- package/dist/esm/react/hooks/useConsent.d.ts.map +1 -1
- package/dist/esm/react/hooks/useConsent.js +49 -1
- package/dist/esm/react/hooks/useConsent.js.map +1 -1
- package/dist/id-manager.d.ts +66 -0
- package/dist/id-manager.d.ts.map +1 -0
- package/dist/id-manager.js +212 -0
- package/dist/index.d.ts +26 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.global.dev.js +1635 -86
- 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 +171 -44
- package/dist/index.mjs +172 -45
- package/dist/interaction-tracking.d.ts +6 -0
- package/dist/interaction-tracking.d.ts.map +1 -1
- package/dist/interaction-tracking.js +55 -5
- package/dist/page-tracking.d.ts +6 -0
- package/dist/page-tracking.d.ts.map +1 -1
- package/dist/page-tracking.js +23 -2
- package/dist/react/hooks/useConsent.d.ts +18 -2
- package/dist/react/hooks/useConsent.d.ts.map +1 -1
- package/dist/react/hooks/useConsent.js +52 -1
- package/dist/react/hooks/useConsent.mjs +49 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Debug Agent for Visual Event Tracking
|
|
4
|
+
* Provides a toolbar and element inspection mode for creating event trackers
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.DebugAgent = void 0;
|
|
8
|
+
class DebugAgent {
|
|
9
|
+
constructor(tracker, sessionId, tenantId, apiUrl, config = {}) {
|
|
10
|
+
this.isDestroyed = false;
|
|
11
|
+
// UI state
|
|
12
|
+
this.isInspectMode = false;
|
|
13
|
+
this.showTrackers = false;
|
|
14
|
+
this.selectedElement = null;
|
|
15
|
+
this.toolbarElement = null;
|
|
16
|
+
this.panelElement = null;
|
|
17
|
+
this.highlightElement = null;
|
|
18
|
+
this.existingTrackers = [];
|
|
19
|
+
this.trackerHighlights = [];
|
|
20
|
+
// Dragging state
|
|
21
|
+
this.isDragging = false;
|
|
22
|
+
this.dragStartX = 0;
|
|
23
|
+
this.dragStartY = 0;
|
|
24
|
+
this.toolbarStartX = 0;
|
|
25
|
+
this.toolbarStartY = 0;
|
|
26
|
+
// Event listeners
|
|
27
|
+
this.mouseMoveListener = null;
|
|
28
|
+
this.clickListener = null;
|
|
29
|
+
this.dragMoveListener = null;
|
|
30
|
+
this.dragEndListener = null;
|
|
31
|
+
/**
|
|
32
|
+
* Handle ESC key to exit inspect mode
|
|
33
|
+
*/
|
|
34
|
+
this.handleEscapeKey = (e) => {
|
|
35
|
+
if (e.key === 'Escape' && this.isInspectMode) {
|
|
36
|
+
this.disableInspectMode();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
this.tracker = tracker;
|
|
40
|
+
this.sessionId = sessionId;
|
|
41
|
+
this.tenantId = tenantId;
|
|
42
|
+
this.apiUrl = apiUrl;
|
|
43
|
+
this.config = {
|
|
44
|
+
debug: config.debug ?? false,
|
|
45
|
+
};
|
|
46
|
+
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
47
|
+
this.initialize();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Initialize the debug agent
|
|
52
|
+
*/
|
|
53
|
+
async initialize() {
|
|
54
|
+
this.log('Initializing debug agent');
|
|
55
|
+
await this.loadExistingTrackers();
|
|
56
|
+
this.showToolbar();
|
|
57
|
+
this.createHighlightElement();
|
|
58
|
+
// Show trackers by default
|
|
59
|
+
this.showTrackers = true;
|
|
60
|
+
this.showTrackerHighlights();
|
|
61
|
+
this.showTrackersList();
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load existing trackers from API
|
|
65
|
+
*/
|
|
66
|
+
async loadExistingTrackers() {
|
|
67
|
+
try {
|
|
68
|
+
const url = `${this.apiUrl}/v1/tenant/${encodeURIComponent(this.tenantId)}/trackers`;
|
|
69
|
+
const response = await fetch(url);
|
|
70
|
+
if (response.ok) {
|
|
71
|
+
this.existingTrackers = await response.json();
|
|
72
|
+
this.log('Loaded trackers:', this.existingTrackers);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
this.log('Failed to load trackers:', error);
|
|
77
|
+
this.existingTrackers = [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Show the debug toolbar
|
|
82
|
+
*/
|
|
83
|
+
showToolbar() {
|
|
84
|
+
if (this.toolbarElement)
|
|
85
|
+
return;
|
|
86
|
+
const toolbar = document.createElement('div');
|
|
87
|
+
toolbar.id = 'grain-debug-toolbar';
|
|
88
|
+
toolbar.innerHTML = `
|
|
89
|
+
<style>
|
|
90
|
+
#grain-debug-toolbar {
|
|
91
|
+
position: fixed;
|
|
92
|
+
bottom: 20px;
|
|
93
|
+
right: 20px;
|
|
94
|
+
background: repeating-linear-gradient(
|
|
95
|
+
45deg,
|
|
96
|
+
#fbbf24,
|
|
97
|
+
#fbbf24 10px,
|
|
98
|
+
#1e293b 10px,
|
|
99
|
+
#1e293b 20px
|
|
100
|
+
);
|
|
101
|
+
border: 2px solid #1e293b;
|
|
102
|
+
border-radius: 12px;
|
|
103
|
+
padding: 6px;
|
|
104
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2), 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
105
|
+
z-index: 999999;
|
|
106
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
107
|
+
font-size: 13px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.grain-toolbar-inner {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: 12px;
|
|
114
|
+
background: white;
|
|
115
|
+
border-radius: 6px;
|
|
116
|
+
padding: 8px 12px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#grain-debug-toolbar.dragging {
|
|
120
|
+
cursor: move;
|
|
121
|
+
user-select: none;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.grain-toolbar-header {
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
cursor: move;
|
|
128
|
+
user-select: none;
|
|
129
|
+
padding: 6px 10px;
|
|
130
|
+
background: #1e293b;
|
|
131
|
+
border-radius: 4px;
|
|
132
|
+
margin-right: 4px;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.grain-toolbar-title {
|
|
136
|
+
font-size: 11px;
|
|
137
|
+
font-weight: 700;
|
|
138
|
+
letter-spacing: 1.2px;
|
|
139
|
+
text-transform: uppercase;
|
|
140
|
+
color: #fbbf24;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.grain-toolbar-body {
|
|
144
|
+
display: flex;
|
|
145
|
+
align-items: center;
|
|
146
|
+
gap: 10px;
|
|
147
|
+
flex: 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.grain-toolbar-stats {
|
|
151
|
+
display: flex;
|
|
152
|
+
gap: 12px;
|
|
153
|
+
padding: 6px 10px;
|
|
154
|
+
background: #f8fafc;
|
|
155
|
+
border-radius: 4px;
|
|
156
|
+
margin-right: 4px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.grain-stat {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: baseline;
|
|
162
|
+
gap: 6px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.grain-stat-value {
|
|
166
|
+
font-size: 18px;
|
|
167
|
+
font-weight: 700;
|
|
168
|
+
color: #1e293b;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.grain-stat-label {
|
|
172
|
+
font-size: 11px;
|
|
173
|
+
color: #64748b;
|
|
174
|
+
font-weight: 500;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.grain-toolbar-actions {
|
|
178
|
+
display: flex;
|
|
179
|
+
gap: 8px;
|
|
180
|
+
align-items: center;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#grain-debug-toolbar button {
|
|
184
|
+
background: white;
|
|
185
|
+
border: 1.5px solid #e2e8f0;
|
|
186
|
+
color: #475569;
|
|
187
|
+
padding: 8px 14px;
|
|
188
|
+
border-radius: 8px;
|
|
189
|
+
cursor: pointer;
|
|
190
|
+
font-size: 12px;
|
|
191
|
+
font-weight: 600;
|
|
192
|
+
transition: all 0.2s;
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
justify-content: center;
|
|
196
|
+
gap: 6px;
|
|
197
|
+
white-space: nowrap;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
#grain-debug-toolbar button:hover {
|
|
201
|
+
background: #f8fafc;
|
|
202
|
+
border-color: #cbd5e1;
|
|
203
|
+
transform: translateY(-1px);
|
|
204
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#grain-debug-toolbar button.active {
|
|
208
|
+
background: #fbbf24;
|
|
209
|
+
color: #1e293b;
|
|
210
|
+
border-color: #fbbf24;
|
|
211
|
+
box-shadow: 0 2px 8px rgba(251, 191, 36, 0.3);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
#grain-debug-toolbar button.danger {
|
|
215
|
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
|
216
|
+
border-color: #ef4444;
|
|
217
|
+
color: white;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#grain-debug-toolbar button.danger:hover {
|
|
221
|
+
transform: translateY(-1px);
|
|
222
|
+
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.25);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.grain-debug-highlight {
|
|
226
|
+
position: absolute;
|
|
227
|
+
pointer-events: none;
|
|
228
|
+
border: 2px solid #10b981;
|
|
229
|
+
background: rgba(16, 185, 129, 0.1);
|
|
230
|
+
z-index: 999998;
|
|
231
|
+
transition: all 0.1s;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.grain-tracker-highlight {
|
|
235
|
+
position: absolute;
|
|
236
|
+
pointer-events: none;
|
|
237
|
+
border: 2px solid #6366f1;
|
|
238
|
+
background: rgba(99, 102, 241, 0.08);
|
|
239
|
+
z-index: 999997;
|
|
240
|
+
transition: opacity 0.2s;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.grain-tracker-label {
|
|
244
|
+
position: absolute;
|
|
245
|
+
top: -28px;
|
|
246
|
+
left: 0;
|
|
247
|
+
background: #6366f1;
|
|
248
|
+
color: white;
|
|
249
|
+
padding: 4px 10px;
|
|
250
|
+
border-radius: 6px;
|
|
251
|
+
font-size: 11px;
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
white-space: nowrap;
|
|
254
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
255
|
+
pointer-events: none;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.grain-tracker-label::after {
|
|
259
|
+
content: '';
|
|
260
|
+
position: absolute;
|
|
261
|
+
bottom: -4px;
|
|
262
|
+
left: 10px;
|
|
263
|
+
width: 0;
|
|
264
|
+
height: 0;
|
|
265
|
+
border-left: 4px solid transparent;
|
|
266
|
+
border-right: 4px solid transparent;
|
|
267
|
+
border-top: 4px solid #6366f1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.grain-trackers-list {
|
|
271
|
+
position: fixed;
|
|
272
|
+
bottom: 80px;
|
|
273
|
+
right: 20px;
|
|
274
|
+
background: white;
|
|
275
|
+
border: 1.5px solid #e2e8f0;
|
|
276
|
+
border-radius: 10px;
|
|
277
|
+
padding: 12px;
|
|
278
|
+
max-height: 400px;
|
|
279
|
+
width: 320px;
|
|
280
|
+
overflow-y: auto;
|
|
281
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1), 0 2px 8px rgba(0, 0, 0, 0.06);
|
|
282
|
+
z-index: 999998;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.grain-tracker-item {
|
|
286
|
+
padding: 10px;
|
|
287
|
+
background: #f8fafc;
|
|
288
|
+
border-radius: 8px;
|
|
289
|
+
margin-bottom: 8px;
|
|
290
|
+
cursor: pointer;
|
|
291
|
+
transition: all 0.2s;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.grain-tracker-item:hover {
|
|
295
|
+
background: #f1f5f9;
|
|
296
|
+
transform: translateX(4px);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.grain-tracker-item:last-child {
|
|
300
|
+
margin-bottom: 0;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.grain-tracker-name {
|
|
304
|
+
font-weight: 600;
|
|
305
|
+
color: #1e293b;
|
|
306
|
+
font-size: 13px;
|
|
307
|
+
margin-bottom: 4px;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.grain-tracker-details {
|
|
311
|
+
font-size: 11px;
|
|
312
|
+
color: #64748b;
|
|
313
|
+
display: flex;
|
|
314
|
+
gap: 8px;
|
|
315
|
+
align-items: center;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.grain-tracker-type {
|
|
319
|
+
background: #dbeafe;
|
|
320
|
+
color: #1e40af;
|
|
321
|
+
padding: 2px 6px;
|
|
322
|
+
border-radius: 4px;
|
|
323
|
+
font-weight: 600;
|
|
324
|
+
text-transform: uppercase;
|
|
325
|
+
font-size: 9px;
|
|
326
|
+
letter-spacing: 0.5px;
|
|
327
|
+
}
|
|
328
|
+
</style>
|
|
329
|
+
<div class="grain-toolbar-inner">
|
|
330
|
+
<div class="grain-toolbar-header" id="grain-toolbar-handle">
|
|
331
|
+
<div class="grain-toolbar-title">Grain Debug</div>
|
|
332
|
+
</div>
|
|
333
|
+
<div class="grain-toolbar-body">
|
|
334
|
+
<div class="grain-toolbar-stats">
|
|
335
|
+
<div class="grain-stat">
|
|
336
|
+
<div class="grain-stat-value">${this.existingTrackers.length}</div>
|
|
337
|
+
<div class="grain-stat-label">trackers</div>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="grain-stat">
|
|
340
|
+
<div class="grain-stat-value">${this.existingTrackers.filter(t => t.isEnabled).length}</div>
|
|
341
|
+
<div class="grain-stat-label">active</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
<div class="grain-toolbar-actions">
|
|
345
|
+
<button id="grain-debug-inspect" type="button">
|
|
346
|
+
+ New
|
|
347
|
+
</button>
|
|
348
|
+
<button id="grain-debug-trackers" class="active" type="button">
|
|
349
|
+
Hide
|
|
350
|
+
</button>
|
|
351
|
+
<button id="grain-debug-end" class="danger" type="button">
|
|
352
|
+
End Session
|
|
353
|
+
</button>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
<div id="grain-trackers-list-container"></div>
|
|
358
|
+
`;
|
|
359
|
+
document.body.appendChild(toolbar);
|
|
360
|
+
this.toolbarElement = toolbar;
|
|
361
|
+
// Setup dragging
|
|
362
|
+
const handle = toolbar.querySelector('#grain-toolbar-handle');
|
|
363
|
+
if (handle) {
|
|
364
|
+
handle.addEventListener('mousedown', (e) => this.startDrag(e));
|
|
365
|
+
}
|
|
366
|
+
// Add event listeners
|
|
367
|
+
const inspectBtn = toolbar.querySelector('#grain-debug-inspect');
|
|
368
|
+
const trackersBtn = toolbar.querySelector('#grain-debug-trackers');
|
|
369
|
+
const endBtn = toolbar.querySelector('#grain-debug-end');
|
|
370
|
+
if (inspectBtn) {
|
|
371
|
+
inspectBtn.addEventListener('click', () => this.toggleInspectMode());
|
|
372
|
+
}
|
|
373
|
+
if (trackersBtn) {
|
|
374
|
+
trackersBtn.addEventListener('click', () => this.toggleTrackerView());
|
|
375
|
+
}
|
|
376
|
+
if (endBtn) {
|
|
377
|
+
endBtn.addEventListener('click', () => this.endDebug());
|
|
378
|
+
}
|
|
379
|
+
this.log('Toolbar shown');
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Create highlight element for hovering
|
|
383
|
+
*/
|
|
384
|
+
createHighlightElement() {
|
|
385
|
+
if (this.highlightElement)
|
|
386
|
+
return;
|
|
387
|
+
const highlight = document.createElement('div');
|
|
388
|
+
highlight.className = 'grain-debug-highlight';
|
|
389
|
+
highlight.style.display = 'none';
|
|
390
|
+
document.body.appendChild(highlight);
|
|
391
|
+
this.highlightElement = highlight;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Start dragging the toolbar
|
|
395
|
+
*/
|
|
396
|
+
startDrag(e) {
|
|
397
|
+
if (!this.toolbarElement)
|
|
398
|
+
return;
|
|
399
|
+
this.isDragging = true;
|
|
400
|
+
this.dragStartX = e.clientX;
|
|
401
|
+
this.dragStartY = e.clientY;
|
|
402
|
+
const rect = this.toolbarElement.getBoundingClientRect();
|
|
403
|
+
this.toolbarStartX = rect.left;
|
|
404
|
+
this.toolbarStartY = rect.top;
|
|
405
|
+
this.toolbarElement.classList.add('dragging');
|
|
406
|
+
this.dragMoveListener = (e) => this.onDrag(e);
|
|
407
|
+
this.dragEndListener = () => this.endDrag();
|
|
408
|
+
document.addEventListener('mousemove', this.dragMoveListener);
|
|
409
|
+
document.addEventListener('mouseup', this.dragEndListener);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Handle drag movement
|
|
413
|
+
*/
|
|
414
|
+
onDrag(e) {
|
|
415
|
+
if (!this.isDragging || !this.toolbarElement)
|
|
416
|
+
return;
|
|
417
|
+
const deltaX = e.clientX - this.dragStartX;
|
|
418
|
+
const deltaY = e.clientY - this.dragStartY;
|
|
419
|
+
const newX = this.toolbarStartX + deltaX;
|
|
420
|
+
const newY = this.toolbarStartY + deltaY;
|
|
421
|
+
// Keep toolbar within viewport
|
|
422
|
+
const maxX = window.innerWidth - this.toolbarElement.offsetWidth;
|
|
423
|
+
const maxY = window.innerHeight - this.toolbarElement.offsetHeight;
|
|
424
|
+
const clampedX = Math.max(0, Math.min(newX, maxX));
|
|
425
|
+
const clampedY = Math.max(0, Math.min(newY, maxY));
|
|
426
|
+
this.toolbarElement.style.left = `${clampedX}px`;
|
|
427
|
+
this.toolbarElement.style.top = `${clampedY}px`;
|
|
428
|
+
this.toolbarElement.style.right = 'auto';
|
|
429
|
+
this.toolbarElement.style.bottom = 'auto';
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* End dragging
|
|
433
|
+
*/
|
|
434
|
+
endDrag() {
|
|
435
|
+
if (!this.isDragging)
|
|
436
|
+
return;
|
|
437
|
+
this.isDragging = false;
|
|
438
|
+
if (this.toolbarElement) {
|
|
439
|
+
this.toolbarElement.classList.remove('dragging');
|
|
440
|
+
}
|
|
441
|
+
if (this.dragMoveListener) {
|
|
442
|
+
document.removeEventListener('mousemove', this.dragMoveListener);
|
|
443
|
+
this.dragMoveListener = null;
|
|
444
|
+
}
|
|
445
|
+
if (this.dragEndListener) {
|
|
446
|
+
document.removeEventListener('mouseup', this.dragEndListener);
|
|
447
|
+
this.dragEndListener = null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Toggle tracker view
|
|
452
|
+
*/
|
|
453
|
+
toggleTrackerView() {
|
|
454
|
+
this.showTrackers = !this.showTrackers;
|
|
455
|
+
const trackersBtn = document.querySelector('#grain-debug-trackers');
|
|
456
|
+
if (trackersBtn) {
|
|
457
|
+
trackersBtn.textContent = this.showTrackers ? 'Hide' : 'View';
|
|
458
|
+
trackersBtn.classList.toggle('active', this.showTrackers);
|
|
459
|
+
}
|
|
460
|
+
if (this.showTrackers) {
|
|
461
|
+
this.showTrackerHighlights();
|
|
462
|
+
this.showTrackersList();
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
this.hideTrackerHighlights();
|
|
466
|
+
this.hideTrackersList();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Show tracker highlights on page
|
|
471
|
+
*/
|
|
472
|
+
showTrackerHighlights() {
|
|
473
|
+
this.hideTrackerHighlights();
|
|
474
|
+
for (const tracker of this.existingTrackers) {
|
|
475
|
+
if (!tracker.isEnabled)
|
|
476
|
+
continue;
|
|
477
|
+
try {
|
|
478
|
+
const element = this.findElementBySelector(tracker.selector);
|
|
479
|
+
if (!element)
|
|
480
|
+
continue;
|
|
481
|
+
const rect = element.getBoundingClientRect();
|
|
482
|
+
const highlight = document.createElement('div');
|
|
483
|
+
highlight.className = 'grain-tracker-highlight';
|
|
484
|
+
const label = document.createElement('div');
|
|
485
|
+
label.className = 'grain-tracker-label';
|
|
486
|
+
label.textContent = tracker.name;
|
|
487
|
+
highlight.style.top = `${rect.top + window.scrollY}px`;
|
|
488
|
+
highlight.style.left = `${rect.left + window.scrollX}px`;
|
|
489
|
+
highlight.style.width = `${rect.width}px`;
|
|
490
|
+
highlight.style.height = `${rect.height}px`;
|
|
491
|
+
highlight.appendChild(label);
|
|
492
|
+
document.body.appendChild(highlight);
|
|
493
|
+
this.trackerHighlights.push(highlight);
|
|
494
|
+
}
|
|
495
|
+
catch (error) {
|
|
496
|
+
this.log('Failed to highlight tracker:', tracker.name, error);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Hide tracker highlights
|
|
502
|
+
*/
|
|
503
|
+
hideTrackerHighlights() {
|
|
504
|
+
for (const highlight of this.trackerHighlights) {
|
|
505
|
+
highlight.remove();
|
|
506
|
+
}
|
|
507
|
+
this.trackerHighlights = [];
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Show trackers list
|
|
511
|
+
*/
|
|
512
|
+
showTrackersList() {
|
|
513
|
+
// Check if list already exists
|
|
514
|
+
let list = document.querySelector('.grain-trackers-list');
|
|
515
|
+
if (this.existingTrackers.length === 0) {
|
|
516
|
+
if (list)
|
|
517
|
+
list.remove();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (!list) {
|
|
521
|
+
list = document.createElement('div');
|
|
522
|
+
list.className = 'grain-trackers-list';
|
|
523
|
+
document.body.appendChild(list);
|
|
524
|
+
}
|
|
525
|
+
list.innerHTML = `
|
|
526
|
+
${this.existingTrackers.map(tracker => `
|
|
527
|
+
<div class="grain-tracker-item" data-tracker-id="${tracker.trackerId}">
|
|
528
|
+
<div class="grain-tracker-name">${tracker.name}</div>
|
|
529
|
+
<div class="grain-tracker-details">
|
|
530
|
+
<span class="grain-tracker-type">${tracker.type}</span>
|
|
531
|
+
<span>${tracker.urlScope}</span>
|
|
532
|
+
${!tracker.isEnabled ? '<span style="color: #ef4444;">• Disabled</span>' : ''}
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
`).join('')}
|
|
536
|
+
`;
|
|
537
|
+
// Add click handlers to scroll to elements
|
|
538
|
+
list.querySelectorAll('.grain-tracker-item').forEach(item => {
|
|
539
|
+
item.addEventListener('click', () => {
|
|
540
|
+
const trackerId = item.getAttribute('data-tracker-id');
|
|
541
|
+
const tracker = this.existingTrackers.find(t => t.trackerId === trackerId);
|
|
542
|
+
if (tracker) {
|
|
543
|
+
this.scrollToTracker(tracker);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Hide trackers list
|
|
550
|
+
*/
|
|
551
|
+
hideTrackersList() {
|
|
552
|
+
const list = document.querySelector('.grain-trackers-list');
|
|
553
|
+
if (list) {
|
|
554
|
+
list.remove();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Scroll to and highlight a tracker element
|
|
559
|
+
*/
|
|
560
|
+
scrollToTracker(tracker) {
|
|
561
|
+
try {
|
|
562
|
+
const element = this.findElementBySelector(tracker.selector);
|
|
563
|
+
if (element) {
|
|
564
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
565
|
+
// Flash the highlight
|
|
566
|
+
const highlight = this.trackerHighlights.find(h => {
|
|
567
|
+
const rect = element.getBoundingClientRect();
|
|
568
|
+
const hRect = h.getBoundingClientRect();
|
|
569
|
+
return Math.abs(hRect.top - rect.top) < 5;
|
|
570
|
+
});
|
|
571
|
+
if (highlight) {
|
|
572
|
+
highlight.style.opacity = '0';
|
|
573
|
+
setTimeout(() => {
|
|
574
|
+
highlight.style.opacity = '1';
|
|
575
|
+
}, 100);
|
|
576
|
+
setTimeout(() => {
|
|
577
|
+
highlight.style.opacity = '0';
|
|
578
|
+
}, 300);
|
|
579
|
+
setTimeout(() => {
|
|
580
|
+
highlight.style.opacity = '1';
|
|
581
|
+
}, 500);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
this.log('Failed to scroll to tracker:', error);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Find element by XPath selector
|
|
591
|
+
*/
|
|
592
|
+
findElementBySelector(selector) {
|
|
593
|
+
try {
|
|
594
|
+
const result = document.evaluate(selector, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
595
|
+
return result.singleNodeValue;
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
this.log('Failed to find element:', error);
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Toggle inspect mode
|
|
604
|
+
*/
|
|
605
|
+
toggleInspectMode() {
|
|
606
|
+
if (this.isInspectMode) {
|
|
607
|
+
this.disableInspectMode();
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
this.enableInspectMode();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Enable element inspection mode
|
|
615
|
+
*/
|
|
616
|
+
enableInspectMode() {
|
|
617
|
+
if (this.isInspectMode)
|
|
618
|
+
return;
|
|
619
|
+
this.log('Enabling inspect mode');
|
|
620
|
+
this.isInspectMode = true;
|
|
621
|
+
// Update button state
|
|
622
|
+
const inspectBtn = document.querySelector('#grain-debug-inspect');
|
|
623
|
+
if (inspectBtn) {
|
|
624
|
+
inspectBtn.classList.add('active');
|
|
625
|
+
inspectBtn.textContent = 'Click Element';
|
|
626
|
+
}
|
|
627
|
+
// Add event listeners
|
|
628
|
+
this.mouseMoveListener = (e) => this.handleMouseMove(e);
|
|
629
|
+
this.clickListener = (e) => this.handleElementClick(e);
|
|
630
|
+
document.addEventListener('mousemove', this.mouseMoveListener, true);
|
|
631
|
+
document.addEventListener('click', this.clickListener, true);
|
|
632
|
+
// Add ESC key listener to exit inspect mode
|
|
633
|
+
document.addEventListener('keydown', this.handleEscapeKey);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Disable element inspection mode
|
|
637
|
+
*/
|
|
638
|
+
disableInspectMode() {
|
|
639
|
+
if (!this.isInspectMode)
|
|
640
|
+
return;
|
|
641
|
+
this.log('Disabling inspect mode');
|
|
642
|
+
this.isInspectMode = false;
|
|
643
|
+
// Update button state
|
|
644
|
+
const inspectBtn = document.querySelector('#grain-debug-inspect');
|
|
645
|
+
if (inspectBtn) {
|
|
646
|
+
inspectBtn.classList.remove('active');
|
|
647
|
+
inspectBtn.textContent = '+ New';
|
|
648
|
+
}
|
|
649
|
+
// Remove event listeners
|
|
650
|
+
if (this.mouseMoveListener) {
|
|
651
|
+
document.removeEventListener('mousemove', this.mouseMoveListener, true);
|
|
652
|
+
this.mouseMoveListener = null;
|
|
653
|
+
}
|
|
654
|
+
if (this.clickListener) {
|
|
655
|
+
document.removeEventListener('click', this.clickListener, true);
|
|
656
|
+
this.clickListener = null;
|
|
657
|
+
}
|
|
658
|
+
document.removeEventListener('keydown', this.handleEscapeKey);
|
|
659
|
+
// Hide highlight
|
|
660
|
+
if (this.highlightElement) {
|
|
661
|
+
this.highlightElement.style.display = 'none';
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Handle mouse move to highlight hovered element
|
|
666
|
+
*/
|
|
667
|
+
handleMouseMove(e) {
|
|
668
|
+
if (!this.isInspectMode || !this.highlightElement)
|
|
669
|
+
return;
|
|
670
|
+
// Ignore if hovering over toolbar, panel, or tracker list
|
|
671
|
+
const target = e.target;
|
|
672
|
+
if (target.closest('#grain-debug-toolbar') ||
|
|
673
|
+
target.closest('#grain-debug-panel') ||
|
|
674
|
+
target.closest('.grain-trackers-list')) {
|
|
675
|
+
this.highlightElement.style.display = 'none';
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
// Get element rect
|
|
679
|
+
const element = e.target;
|
|
680
|
+
const rect = element.getBoundingClientRect();
|
|
681
|
+
// Position highlight
|
|
682
|
+
this.highlightElement.style.display = 'block';
|
|
683
|
+
this.highlightElement.style.top = `${rect.top + window.scrollY}px`;
|
|
684
|
+
this.highlightElement.style.left = `${rect.left + window.scrollX}px`;
|
|
685
|
+
this.highlightElement.style.width = `${rect.width}px`;
|
|
686
|
+
this.highlightElement.style.height = `${rect.height}px`;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Handle element click to show creation panel
|
|
690
|
+
*/
|
|
691
|
+
handleElementClick(e) {
|
|
692
|
+
if (!this.isInspectMode)
|
|
693
|
+
return;
|
|
694
|
+
const target = e.target;
|
|
695
|
+
// If clicking toolbar or tracker list, exit inspect mode and allow normal click
|
|
696
|
+
if (target.closest('#grain-debug-toolbar') || target.closest('.grain-trackers-list')) {
|
|
697
|
+
this.disableInspectMode();
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
// If clicking panel, prevent default but don't do anything
|
|
701
|
+
if (target.closest('#grain-debug-panel')) {
|
|
702
|
+
e.preventDefault();
|
|
703
|
+
e.stopPropagation();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
// Otherwise, select the element
|
|
707
|
+
e.preventDefault();
|
|
708
|
+
e.stopPropagation();
|
|
709
|
+
this.selectedElement = target;
|
|
710
|
+
this.disableInspectMode();
|
|
711
|
+
this.showCreationPanel(target);
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Show tracker creation panel
|
|
715
|
+
*/
|
|
716
|
+
showCreationPanel(element) {
|
|
717
|
+
// Remove existing panel if any
|
|
718
|
+
if (this.panelElement) {
|
|
719
|
+
this.panelElement.remove();
|
|
720
|
+
}
|
|
721
|
+
const panel = document.createElement('div');
|
|
722
|
+
panel.id = 'grain-debug-panel';
|
|
723
|
+
// Extract element info
|
|
724
|
+
const tagName = element.tagName.toLowerCase();
|
|
725
|
+
const elementId = element.id;
|
|
726
|
+
const elementText = element.textContent?.trim().substring(0, 50) || '';
|
|
727
|
+
const xpath = this.getXPathForElement(element);
|
|
728
|
+
panel.innerHTML = `
|
|
729
|
+
<style>
|
|
730
|
+
#grain-debug-panel {
|
|
731
|
+
position: fixed;
|
|
732
|
+
top: 50%;
|
|
733
|
+
left: 50%;
|
|
734
|
+
transform: translate(-50%, -50%);
|
|
735
|
+
background: repeating-linear-gradient(
|
|
736
|
+
45deg,
|
|
737
|
+
#fbbf24,
|
|
738
|
+
#fbbf24 10px,
|
|
739
|
+
#1e293b 10px,
|
|
740
|
+
#1e293b 20px
|
|
741
|
+
);
|
|
742
|
+
border: 2px solid #1e293b;
|
|
743
|
+
border-radius: 16px;
|
|
744
|
+
padding: 6px;
|
|
745
|
+
width: 420px;
|
|
746
|
+
max-width: 90vw;
|
|
747
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25), 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
748
|
+
z-index: 1000000;
|
|
749
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.grain-panel-inner {
|
|
753
|
+
background: white;
|
|
754
|
+
border-radius: 10px;
|
|
755
|
+
overflow: hidden;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
.grain-panel-header {
|
|
759
|
+
background: #1e293b;
|
|
760
|
+
padding: 14px 18px;
|
|
761
|
+
color: white;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.grain-panel-header h3 {
|
|
765
|
+
margin: 0 0 4px 0;
|
|
766
|
+
font-size: 16px;
|
|
767
|
+
font-weight: 700;
|
|
768
|
+
letter-spacing: 0.5px;
|
|
769
|
+
color: #fbbf24;
|
|
770
|
+
text-transform: uppercase;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.grain-panel-header p {
|
|
774
|
+
margin: 0;
|
|
775
|
+
font-size: 12px;
|
|
776
|
+
opacity: 0.85;
|
|
777
|
+
color: white;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.grain-panel-body {
|
|
781
|
+
padding: 18px;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
#grain-debug-panel .element-preview {
|
|
785
|
+
background: #f8fafc;
|
|
786
|
+
border: 1.5px solid #e2e8f0;
|
|
787
|
+
border-radius: 8px;
|
|
788
|
+
padding: 10px 12px;
|
|
789
|
+
margin-bottom: 16px;
|
|
790
|
+
font-size: 11px;
|
|
791
|
+
color: #475569;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
#grain-debug-panel .element-preview div {
|
|
795
|
+
margin-bottom: 4px;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
#grain-debug-panel .element-preview div:last-child {
|
|
799
|
+
margin-bottom: 0;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
#grain-debug-panel .element-preview strong {
|
|
803
|
+
color: #1e293b;
|
|
804
|
+
font-weight: 600;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
#grain-debug-panel label {
|
|
808
|
+
display: block;
|
|
809
|
+
color: #1e293b;
|
|
810
|
+
font-size: 12px;
|
|
811
|
+
font-weight: 600;
|
|
812
|
+
margin-bottom: 6px;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
#grain-debug-panel input,
|
|
816
|
+
#grain-debug-panel select {
|
|
817
|
+
width: 100%;
|
|
818
|
+
background: white;
|
|
819
|
+
border: 1.5px solid #e2e8f0;
|
|
820
|
+
border-radius: 8px;
|
|
821
|
+
padding: 9px 12px;
|
|
822
|
+
color: #1e293b;
|
|
823
|
+
font-size: 13px;
|
|
824
|
+
margin-bottom: 14px;
|
|
825
|
+
box-sizing: border-box;
|
|
826
|
+
transition: all 0.2s;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
#grain-debug-panel input:focus,
|
|
830
|
+
#grain-debug-panel select:focus {
|
|
831
|
+
outline: none;
|
|
832
|
+
border-color: #fbbf24;
|
|
833
|
+
box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.1);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
#grain-debug-panel .button-group {
|
|
837
|
+
display: flex;
|
|
838
|
+
gap: 8px;
|
|
839
|
+
margin-top: 18px;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
#grain-debug-panel button {
|
|
843
|
+
flex: 1;
|
|
844
|
+
padding: 10px 16px;
|
|
845
|
+
border: none;
|
|
846
|
+
border-radius: 8px;
|
|
847
|
+
font-size: 13px;
|
|
848
|
+
font-weight: 600;
|
|
849
|
+
cursor: pointer;
|
|
850
|
+
transition: all 0.2s;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
#grain-debug-panel button.primary {
|
|
854
|
+
background: #fbbf24;
|
|
855
|
+
color: #1e293b;
|
|
856
|
+
box-shadow: 0 2px 8px rgba(251, 191, 36, 0.3);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
#grain-debug-panel button.primary:hover {
|
|
860
|
+
background: #f59e0b;
|
|
861
|
+
transform: translateY(-1px);
|
|
862
|
+
box-shadow: 0 4px 12px rgba(251, 191, 36, 0.4);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
#grain-debug-panel button.secondary {
|
|
866
|
+
background: white;
|
|
867
|
+
border: 1.5px solid #e2e8f0;
|
|
868
|
+
color: #475569;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
#grain-debug-panel button.secondary:hover {
|
|
872
|
+
background: #f8fafc;
|
|
873
|
+
border-color: #cbd5e1;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
#grain-debug-panel .url-pattern-input {
|
|
877
|
+
display: none;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
#grain-debug-panel .url-pattern-input.visible {
|
|
881
|
+
display: block;
|
|
882
|
+
}
|
|
883
|
+
</style>
|
|
884
|
+
<div class="grain-panel-inner">
|
|
885
|
+
<div class="grain-panel-header">
|
|
886
|
+
<h3>Create Tracker</h3>
|
|
887
|
+
<p>Set up automatic tracking for this element</p>
|
|
888
|
+
</div>
|
|
889
|
+
<div class="grain-panel-body">
|
|
890
|
+
<div class="element-preview">
|
|
891
|
+
<div><strong>Element:</strong> ${tagName}${elementId ? `#${elementId}` : ''}</div>
|
|
892
|
+
${elementText ? `<div><strong>Text:</strong> ${elementText}</div>` : ''}
|
|
893
|
+
</div>
|
|
894
|
+
<div>
|
|
895
|
+
<label>Event Name</label>
|
|
896
|
+
<input type="text" id="grain-event-name" placeholder="e.g., signup_button_click" value="" />
|
|
897
|
+
</div>
|
|
898
|
+
<div>
|
|
899
|
+
<label>Type</label>
|
|
900
|
+
<select id="grain-event-type">
|
|
901
|
+
<option value="metric">Metric</option>
|
|
902
|
+
<option value="conversion">Conversion</option>
|
|
903
|
+
</select>
|
|
904
|
+
</div>
|
|
905
|
+
<div>
|
|
906
|
+
<label>URL Scope</label>
|
|
907
|
+
<select id="grain-url-scope">
|
|
908
|
+
<option value="all">All Pages</option>
|
|
909
|
+
<option value="contains" selected>This Page</option>
|
|
910
|
+
<option value="equals">Exact URL</option>
|
|
911
|
+
</select>
|
|
912
|
+
</div>
|
|
913
|
+
<div class="url-pattern-input visible" id="grain-url-pattern-container">
|
|
914
|
+
<label>URL Pattern</label>
|
|
915
|
+
<input type="text" id="grain-url-pattern" placeholder="e.g., /pricing" value="${window.location.pathname}" />
|
|
916
|
+
</div>
|
|
917
|
+
<div class="button-group">
|
|
918
|
+
<button type="button" class="secondary" id="grain-cancel">Cancel</button>
|
|
919
|
+
<button type="button" class="primary" id="grain-create">✓ Create</button>
|
|
920
|
+
</div>
|
|
921
|
+
</div>
|
|
922
|
+
</div>
|
|
923
|
+
`;
|
|
924
|
+
document.body.appendChild(panel);
|
|
925
|
+
this.panelElement = panel;
|
|
926
|
+
// Auto-generate event name suggestion
|
|
927
|
+
const eventNameInput = panel.querySelector('#grain-event-name');
|
|
928
|
+
if (eventNameInput) {
|
|
929
|
+
const suggestedName = this.generateEventName(element);
|
|
930
|
+
eventNameInput.value = suggestedName;
|
|
931
|
+
eventNameInput.select();
|
|
932
|
+
}
|
|
933
|
+
// Handle URL scope change
|
|
934
|
+
const urlScopeSelect = panel.querySelector('#grain-url-scope');
|
|
935
|
+
const urlPatternContainer = panel.querySelector('#grain-url-pattern-container');
|
|
936
|
+
const urlPatternInput = panel.querySelector('#grain-url-pattern');
|
|
937
|
+
if (urlScopeSelect && urlPatternContainer) {
|
|
938
|
+
urlScopeSelect.addEventListener('change', () => {
|
|
939
|
+
if (urlScopeSelect.value === 'all') {
|
|
940
|
+
urlPatternContainer.classList.remove('visible');
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
urlPatternContainer.classList.add('visible');
|
|
944
|
+
if (urlPatternInput && !urlPatternInput.value) {
|
|
945
|
+
urlPatternInput.value = window.location.pathname;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
// Handle buttons
|
|
951
|
+
const cancelBtn = panel.querySelector('#grain-cancel');
|
|
952
|
+
const createBtn = panel.querySelector('#grain-create');
|
|
953
|
+
if (cancelBtn) {
|
|
954
|
+
cancelBtn.addEventListener('click', () => this.hideCreationPanel());
|
|
955
|
+
}
|
|
956
|
+
if (createBtn) {
|
|
957
|
+
createBtn.addEventListener('click', () => this.handleCreateTracker(xpath));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Generate suggested event name from element
|
|
962
|
+
*/
|
|
963
|
+
generateEventName(element) {
|
|
964
|
+
const tagName = element.tagName.toLowerCase();
|
|
965
|
+
const elementId = element.id;
|
|
966
|
+
const elementText = element.textContent?.trim().toLowerCase().replace(/\s+/g, '_').substring(0, 30) || '';
|
|
967
|
+
// Try to generate meaningful name
|
|
968
|
+
if (elementId) {
|
|
969
|
+
return `${elementId}_click`;
|
|
970
|
+
}
|
|
971
|
+
else if (elementText) {
|
|
972
|
+
return `${elementText}_click`;
|
|
973
|
+
}
|
|
974
|
+
else if (tagName === 'button') {
|
|
975
|
+
return 'button_click';
|
|
976
|
+
}
|
|
977
|
+
else if (tagName === 'a') {
|
|
978
|
+
return 'link_click';
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
return `${tagName}_click`;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Handle tracker creation
|
|
986
|
+
*/
|
|
987
|
+
async handleCreateTracker(selector) {
|
|
988
|
+
if (!this.panelElement)
|
|
989
|
+
return;
|
|
990
|
+
const eventNameInput = this.panelElement.querySelector('#grain-event-name');
|
|
991
|
+
const eventTypeSelect = this.panelElement.querySelector('#grain-event-type');
|
|
992
|
+
const urlScopeSelect = this.panelElement.querySelector('#grain-url-scope');
|
|
993
|
+
const urlPatternInput = this.panelElement.querySelector('#grain-url-pattern');
|
|
994
|
+
if (!eventNameInput || !eventTypeSelect || !urlScopeSelect)
|
|
995
|
+
return;
|
|
996
|
+
const eventName = eventNameInput.value.trim();
|
|
997
|
+
const eventType = eventTypeSelect.value;
|
|
998
|
+
const urlScope = urlScopeSelect.value;
|
|
999
|
+
const urlPattern = urlPatternInput?.value.trim() || undefined;
|
|
1000
|
+
// Validate
|
|
1001
|
+
if (!eventName) {
|
|
1002
|
+
alert('Please enter an event name');
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
if (!eventName.match(/^[a-zA-Z0-9_-]+$/)) {
|
|
1006
|
+
alert('Event name can only contain letters, numbers, underscores, and hyphens');
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
if ((urlScope === 'contains' || urlScope === 'equals') && !urlPattern) {
|
|
1010
|
+
alert('Please enter a URL pattern');
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
// Show loading state
|
|
1015
|
+
const createBtn = this.panelElement.querySelector('#grain-create');
|
|
1016
|
+
if (createBtn) {
|
|
1017
|
+
createBtn.textContent = 'Creating...';
|
|
1018
|
+
createBtn.disabled = true;
|
|
1019
|
+
}
|
|
1020
|
+
// Create tracker
|
|
1021
|
+
await this.createTracker(eventName, eventType, selector, urlScope, urlPattern);
|
|
1022
|
+
// Success
|
|
1023
|
+
this.hideCreationPanel();
|
|
1024
|
+
this.showSuccessMessage(`Tracker "${eventName}" created successfully!`);
|
|
1025
|
+
// Reload trackers and update UI
|
|
1026
|
+
await this.loadExistingTrackers();
|
|
1027
|
+
this.updateToolbarStats();
|
|
1028
|
+
if (this.showTrackers) {
|
|
1029
|
+
this.showTrackerHighlights();
|
|
1030
|
+
this.showTrackersList();
|
|
1031
|
+
}
|
|
1032
|
+
this.log('Tracker created:', eventName);
|
|
1033
|
+
}
|
|
1034
|
+
catch (error) {
|
|
1035
|
+
alert('Failed to create tracker. Please try again.');
|
|
1036
|
+
this.log('Failed to create tracker:', error);
|
|
1037
|
+
// Reset button
|
|
1038
|
+
const createBtn = this.panelElement.querySelector('#grain-create');
|
|
1039
|
+
if (createBtn) {
|
|
1040
|
+
createBtn.textContent = 'Create Tracker';
|
|
1041
|
+
createBtn.disabled = false;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Create tracker via API
|
|
1047
|
+
*/
|
|
1048
|
+
async createTracker(name, type, selector, urlScope, urlPattern) {
|
|
1049
|
+
const url = `${this.apiUrl}/v1/tenant/${encodeURIComponent(this.tenantId)}/debug-sessions/${this.sessionId}/trackers`;
|
|
1050
|
+
const response = await fetch(url, {
|
|
1051
|
+
method: 'POST',
|
|
1052
|
+
headers: {
|
|
1053
|
+
'Content-Type': 'application/json',
|
|
1054
|
+
},
|
|
1055
|
+
body: JSON.stringify({
|
|
1056
|
+
name,
|
|
1057
|
+
type,
|
|
1058
|
+
selector,
|
|
1059
|
+
urlScope,
|
|
1060
|
+
urlPattern,
|
|
1061
|
+
}),
|
|
1062
|
+
});
|
|
1063
|
+
if (!response.ok) {
|
|
1064
|
+
throw new Error(`Failed to create tracker: ${response.status}`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Hide creation panel
|
|
1069
|
+
*/
|
|
1070
|
+
hideCreationPanel() {
|
|
1071
|
+
if (this.panelElement) {
|
|
1072
|
+
this.panelElement.remove();
|
|
1073
|
+
this.panelElement = null;
|
|
1074
|
+
}
|
|
1075
|
+
this.selectedElement = null;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Show success message
|
|
1079
|
+
*/
|
|
1080
|
+
showSuccessMessage(message) {
|
|
1081
|
+
const toast = document.createElement('div');
|
|
1082
|
+
toast.style.cssText = `
|
|
1083
|
+
position: fixed;
|
|
1084
|
+
top: 20px;
|
|
1085
|
+
left: 50%;
|
|
1086
|
+
transform: translateX(-50%) translateY(-20px);
|
|
1087
|
+
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
|
1088
|
+
color: white;
|
|
1089
|
+
padding: 14px 24px;
|
|
1090
|
+
border-radius: 12px;
|
|
1091
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1092
|
+
font-size: 14px;
|
|
1093
|
+
font-weight: 600;
|
|
1094
|
+
box-shadow: 0 12px 32px rgba(16, 185, 129, 0.3), 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
1095
|
+
z-index: 1000000;
|
|
1096
|
+
display: flex;
|
|
1097
|
+
align-items: center;
|
|
1098
|
+
gap: 10px;
|
|
1099
|
+
animation: slideDown 0.3s ease-out forwards;
|
|
1100
|
+
`;
|
|
1101
|
+
toast.innerHTML = `
|
|
1102
|
+
<style>
|
|
1103
|
+
@keyframes slideDown {
|
|
1104
|
+
to {
|
|
1105
|
+
transform: translateX(-50%) translateY(0);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
</style>
|
|
1109
|
+
<span style="font-size: 18px;">✓</span>
|
|
1110
|
+
<span>${message}</span>
|
|
1111
|
+
`;
|
|
1112
|
+
document.body.appendChild(toast);
|
|
1113
|
+
setTimeout(() => {
|
|
1114
|
+
toast.style.animation = 'slideDown 0.3s ease-in reverse';
|
|
1115
|
+
setTimeout(() => {
|
|
1116
|
+
toast.remove();
|
|
1117
|
+
}, 300);
|
|
1118
|
+
}, 2700);
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* End debug session
|
|
1122
|
+
*/
|
|
1123
|
+
async endDebug() {
|
|
1124
|
+
try {
|
|
1125
|
+
// Call end session API
|
|
1126
|
+
const url = `${this.apiUrl}/v1/tenant/${encodeURIComponent(this.tenantId)}/debug-sessions/${this.sessionId}/end`;
|
|
1127
|
+
await fetch(url, {
|
|
1128
|
+
method: 'POST',
|
|
1129
|
+
headers: {
|
|
1130
|
+
'Content-Type': 'application/json',
|
|
1131
|
+
},
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
catch (error) {
|
|
1135
|
+
this.log('Failed to end debug session:', error);
|
|
1136
|
+
}
|
|
1137
|
+
// Clean up and reload page
|
|
1138
|
+
this.destroy();
|
|
1139
|
+
// Remove debug params from URL and reload
|
|
1140
|
+
const url = new URL(window.location.href);
|
|
1141
|
+
url.searchParams.delete('grain_debug');
|
|
1142
|
+
url.searchParams.delete('grain_session');
|
|
1143
|
+
window.location.href = url.toString();
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* Get XPath for element
|
|
1147
|
+
*/
|
|
1148
|
+
getXPathForElement(element) {
|
|
1149
|
+
if (element.id) {
|
|
1150
|
+
return `//*[@id="${element.id}"]`;
|
|
1151
|
+
}
|
|
1152
|
+
const parts = [];
|
|
1153
|
+
let current = element;
|
|
1154
|
+
while (current && current.nodeType === Node.ELEMENT_NODE) {
|
|
1155
|
+
let index = 0;
|
|
1156
|
+
let sibling = current;
|
|
1157
|
+
while (sibling) {
|
|
1158
|
+
if (sibling.nodeType === Node.ELEMENT_NODE && sibling.tagName === current.tagName) {
|
|
1159
|
+
index++;
|
|
1160
|
+
}
|
|
1161
|
+
sibling = sibling.previousElementSibling;
|
|
1162
|
+
}
|
|
1163
|
+
const tagName = current.tagName.toLowerCase();
|
|
1164
|
+
const pathIndex = index > 1 ? `[${index}]` : '';
|
|
1165
|
+
parts.unshift(`${tagName}${pathIndex}`);
|
|
1166
|
+
current = current.parentElement;
|
|
1167
|
+
}
|
|
1168
|
+
return parts.length ? `/${parts.join('/')}` : '';
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Log debug messages
|
|
1172
|
+
*/
|
|
1173
|
+
log(...args) {
|
|
1174
|
+
if (this.config.debug) {
|
|
1175
|
+
console.log('[DebugAgent]', ...args);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Update toolbar stats
|
|
1180
|
+
*/
|
|
1181
|
+
updateToolbarStats() {
|
|
1182
|
+
if (!this.toolbarElement)
|
|
1183
|
+
return;
|
|
1184
|
+
const totalStat = this.toolbarElement.querySelector('.grain-stat:nth-child(1) .grain-stat-value');
|
|
1185
|
+
const activeStat = this.toolbarElement.querySelector('.grain-stat:nth-child(2) .grain-stat-value');
|
|
1186
|
+
if (totalStat) {
|
|
1187
|
+
totalStat.textContent = String(this.existingTrackers.length);
|
|
1188
|
+
}
|
|
1189
|
+
if (activeStat) {
|
|
1190
|
+
activeStat.textContent = String(this.existingTrackers.filter(t => t.isEnabled).length);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Destroy the debug agent
|
|
1195
|
+
*/
|
|
1196
|
+
destroy() {
|
|
1197
|
+
if (this.isDestroyed)
|
|
1198
|
+
return;
|
|
1199
|
+
this.log('Destroying debug agent');
|
|
1200
|
+
this.isDestroyed = true;
|
|
1201
|
+
this.disableInspectMode();
|
|
1202
|
+
this.hideTrackerHighlights();
|
|
1203
|
+
this.endDrag();
|
|
1204
|
+
if (this.toolbarElement) {
|
|
1205
|
+
this.toolbarElement.remove();
|
|
1206
|
+
this.toolbarElement = null;
|
|
1207
|
+
}
|
|
1208
|
+
if (this.panelElement) {
|
|
1209
|
+
this.panelElement.remove();
|
|
1210
|
+
this.panelElement = null;
|
|
1211
|
+
}
|
|
1212
|
+
if (this.highlightElement) {
|
|
1213
|
+
this.highlightElement.remove();
|
|
1214
|
+
this.highlightElement = null;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
exports.DebugAgent = DebugAgent;
|
|
1219
|
+
//# sourceMappingURL=debug-agent.js.map
|