@kylelogue/eventfoundry-tracker 0.2.0 → 0.3.1
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 +283 -10
- package/dist/tracker.min.js +1 -22
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Embedded JavaScript tracking script for EventFoundry - enables custom GA4 event tracking with a visual editor.
|
|
4
4
|
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [How It Works](#how-it-works)
|
|
10
|
+
- [Features](#features)
|
|
11
|
+
- [Configuration](#configuration)
|
|
12
|
+
- [Development Mode](#development-mode)
|
|
13
|
+
- [Editor Integration](#editor-integration)
|
|
14
|
+
- [Architecture](#architecture)
|
|
15
|
+
- [Browser Compatibility](#browser-compatibility)
|
|
16
|
+
- [Development](#development)
|
|
17
|
+
- [PostMessage API Reference](#postmessage-api-reference)
|
|
18
|
+
- [License](#license)
|
|
19
|
+
|
|
5
20
|
## Installation
|
|
6
21
|
|
|
7
22
|
### Via unpkg CDN (Recommended)
|
|
@@ -19,7 +34,7 @@ Or pin to a specific version for production stability:
|
|
|
19
34
|
|
|
20
35
|
```html
|
|
21
36
|
<!-- Pinned version -->
|
|
22
|
-
<script src="https://unpkg.com/@kylelogue/eventfoundry-tracker@0.
|
|
37
|
+
<script src="https://unpkg.com/@kylelogue/eventfoundry-tracker@0.3.0/dist/tracker.min.js"
|
|
23
38
|
data-site-key="YOUR_SITE_KEY_HERE"
|
|
24
39
|
async></script>
|
|
25
40
|
```
|
|
@@ -65,40 +80,298 @@ If you already have GA4 installed, EventFoundry will detect and use it:
|
|
|
65
80
|
|
|
66
81
|
## How It Works
|
|
67
82
|
|
|
68
|
-
EventFoundry Tracker
|
|
83
|
+
EventFoundry Tracker operates in two distinct modes:
|
|
84
|
+
|
|
85
|
+
### 1. Tracking Mode (Production)
|
|
86
|
+
|
|
87
|
+
This is the default mode when the script is embedded on your website:
|
|
69
88
|
|
|
70
|
-
1. **
|
|
89
|
+
1. **Initialization**: On page load, the tracker reads configuration from script attributes
|
|
90
|
+
2. **GA4 Setup**: Either detects existing GA4 installation or auto-injects it based on `data-measurement-id`
|
|
91
|
+
3. **Event Fetching**: Retrieves event definitions from EventFoundry API using your site key
|
|
92
|
+
4. **Caching**: Stores event definitions in localStorage with 1-hour cache duration
|
|
93
|
+
5. **Click Tracking**: Attaches click listeners to elements matching CSS selectors from your event definitions
|
|
94
|
+
6. **Event Sending**: When tracked elements are clicked, sends events to GA4 using the Beacon API for reliable delivery (even during page navigation)
|
|
71
95
|
|
|
72
|
-
2.
|
|
96
|
+
### 2. Editor Mode (EventFoundry App)
|
|
97
|
+
|
|
98
|
+
When loaded inside the EventFoundry application's iframe:
|
|
99
|
+
|
|
100
|
+
1. **Detection**: Automatically detects iframe environment and initializes editor mode
|
|
101
|
+
2. **Visual Overlay**: Displays an interactive overlay for selecting elements on your page
|
|
102
|
+
3. **Element Selection**: Click any element to select it and generate an optimized CSS selector
|
|
103
|
+
4. **Selector Generation**: Uses a priority-based algorithm to create the most reliable selector
|
|
104
|
+
5. **Communication**: Sends element data back to the parent app via secure PostMessage
|
|
105
|
+
6. **Preview Testing**: Allows testing events in preview mode with real-time logging
|
|
73
106
|
|
|
74
107
|
## Features
|
|
75
108
|
|
|
76
|
-
- **Lightweight**:
|
|
109
|
+
- **Lightweight**: ~3.2 KB gzipped (8.2 KB minified)
|
|
77
110
|
- **Zero dependencies**: Pure vanilla JavaScript
|
|
78
111
|
- **Fast**: 1-hour localStorage caching of event definitions
|
|
79
112
|
- **Non-blocking**: Async loading won't slow down your site
|
|
80
113
|
- **Browser support**: Chrome, Firefox, Safari, Edge (ES2015+)
|
|
114
|
+
- **Reliable delivery**: Uses Beacon API to ensure events complete even during navigation
|
|
115
|
+
- **Development mode**: Auto-detects localhost and logs events without sending to GA4
|
|
116
|
+
- **Visual editor**: Interactive element selection overlay for defining events
|
|
117
|
+
- **Smart selectors**: Priority-based CSS selector generation algorithm
|
|
118
|
+
- **Secure communication**: Origin validation for iframe PostMessage API
|
|
81
119
|
|
|
82
120
|
## Configuration
|
|
83
121
|
|
|
84
|
-
The script accepts the following attributes:
|
|
122
|
+
The script accepts the following data attributes:
|
|
123
|
+
|
|
124
|
+
### Required
|
|
125
|
+
|
|
126
|
+
- **`data-site-key`**: Your EventFoundry site key (obtained from your EventFoundry account)
|
|
127
|
+
|
|
128
|
+
### Optional
|
|
129
|
+
|
|
130
|
+
- **`data-measurement-id`**: Your GA4 measurement ID (e.g., `G-XXXXXXXXXX`)
|
|
131
|
+
- Only required if you want EventFoundry to auto-inject GA4
|
|
132
|
+
- Omit this if you already have GA4 installed on your site
|
|
133
|
+
|
|
134
|
+
- **`data-dev-mode`**: Control development mode behavior
|
|
135
|
+
- `"true"`: Force development mode on (logs events, doesn't send to GA4)
|
|
136
|
+
- `"false"`: Force production mode on
|
|
137
|
+
- Not set: Auto-detects (dev mode on localhost, production mode elsewhere)
|
|
138
|
+
|
|
139
|
+
## Development Mode
|
|
140
|
+
|
|
141
|
+
Development mode helps you test EventFoundry tracking without sending events to GA4.
|
|
142
|
+
|
|
143
|
+
### Auto-Detection
|
|
144
|
+
|
|
145
|
+
By default, the tracker automatically enables development mode when running on `localhost`:
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// On localhost - dev mode is automatically enabled
|
|
149
|
+
// Events are logged to console but NOT sent to GA4
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Manual Control
|
|
153
|
+
|
|
154
|
+
You can override the auto-detection:
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<!-- Force dev mode on production domain (for testing) -->
|
|
158
|
+
<script src="https://unpkg.com/@kylelogue/eventfoundry-tracker@latest/dist/tracker.min.js"
|
|
159
|
+
data-site-key="YOUR_SITE_KEY_HERE"
|
|
160
|
+
data-measurement-id="G-XXXXXXXXXX"
|
|
161
|
+
data-dev-mode="true"
|
|
162
|
+
async></script>
|
|
163
|
+
|
|
164
|
+
<!-- Force production mode on localhost (to test real GA4 events) -->
|
|
165
|
+
<script src="https://unpkg.com/@kylelogue/eventfoundry-tracker@latest/dist/tracker.min.js"
|
|
166
|
+
data-site-key="YOUR_SITE_KEY_HERE"
|
|
167
|
+
data-measurement-id="G-XXXXXXXXXX"
|
|
168
|
+
data-dev-mode="false"
|
|
169
|
+
async></script>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Behavior in Dev Mode
|
|
173
|
+
|
|
174
|
+
When development mode is active:
|
|
175
|
+
- Events are logged to the browser console with full details
|
|
176
|
+
- No events are sent to Google Analytics
|
|
177
|
+
- A console message indicates dev mode is active
|
|
178
|
+
- All tracking logic still runs (useful for testing selectors and event configuration)
|
|
179
|
+
|
|
180
|
+
## Editor Integration
|
|
181
|
+
|
|
182
|
+
EventFoundry Tracker includes a sophisticated visual editor for defining events directly on your website.
|
|
183
|
+
|
|
184
|
+
### How Editor Mode Works
|
|
185
|
+
|
|
186
|
+
When your site is loaded inside the EventFoundry application:
|
|
187
|
+
|
|
188
|
+
1. The tracker detects the iframe environment
|
|
189
|
+
2. Sends an `IFRAME_READY` message to the parent app
|
|
190
|
+
3. Activates the visual overlay system
|
|
191
|
+
4. Waits for element selection requests from the parent app
|
|
192
|
+
|
|
193
|
+
### Visual Element Selection
|
|
194
|
+
|
|
195
|
+
The editor overlay provides:
|
|
196
|
+
- **Click-to-select**: Click any element to select it
|
|
197
|
+
- **Visual highlighting**: Selected elements are highlighted in the overlay
|
|
198
|
+
- **Parent traversal**: Navigate up the DOM tree to select parent elements
|
|
199
|
+
- **Selector preview**: See the generated CSS selector in real-time
|
|
200
|
+
|
|
201
|
+
### CSS Selector Generation
|
|
202
|
+
|
|
203
|
+
The tracker uses a priority-based algorithm to generate reliable CSS selectors:
|
|
204
|
+
|
|
205
|
+
1. **ID selectors** (highest priority): `#unique-element-id`
|
|
206
|
+
2. **Unique attributes**: `[data-track="value"]`, `[name="value"]`
|
|
207
|
+
3. **Class-based selectors**: `.class-name`, `.multiple.classes`
|
|
208
|
+
4. **Tag + nth-child**: `div:nth-child(3)`, with parent context for specificity
|
|
209
|
+
5. **Fallback combinations**: Combines multiple strategies for robustness
|
|
210
|
+
|
|
211
|
+
The algorithm ensures selectors are:
|
|
212
|
+
- Specific enough to target the correct element
|
|
213
|
+
- Resilient to minor page changes
|
|
214
|
+
- Human-readable when possible
|
|
215
|
+
|
|
216
|
+
### Preview Mode
|
|
85
217
|
|
|
86
|
-
|
|
87
|
-
-
|
|
218
|
+
Test your events before deploying:
|
|
219
|
+
- Preview mode logs events to the parent app instead of GA4
|
|
220
|
+
- See real-time feedback when clicking tracked elements
|
|
221
|
+
- Verify event parameters and tracking accuracy
|
|
222
|
+
- No impact on your actual GA4 data
|
|
223
|
+
|
|
224
|
+
## Architecture
|
|
225
|
+
|
|
226
|
+
EventFoundry Tracker is built with a modular architecture:
|
|
227
|
+
|
|
228
|
+
### Source Structure
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
src/
|
|
232
|
+
├── index.js - Main entry point, config parsing, GA4 setup, caching
|
|
233
|
+
├── tracker.js - Event tracking logic, click listeners, Beacon API
|
|
234
|
+
├── editor-mode.js - Editor mode detection and PostMessage communication
|
|
235
|
+
└── editor-overlay.js - Visual overlay, element selection, selector generation
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Module Responsibilities
|
|
239
|
+
|
|
240
|
+
- **index.js**: Initialization, configuration parsing, GA4 detection/injection, event caching
|
|
241
|
+
- **tracker.js**: Click event handling, event data collection, GA4 event sending
|
|
242
|
+
- **editor-mode.js**: Iframe detection, parent communication, mode switching
|
|
243
|
+
- **editor-overlay.js**: DOM overlay rendering, element selection UI, CSS selector algorithm
|
|
244
|
+
|
|
245
|
+
### Build Output
|
|
246
|
+
|
|
247
|
+
- **Format**: IIFE (Immediately Invoked Function Expression)
|
|
248
|
+
- **Target**: ES2015 for broad browser compatibility
|
|
249
|
+
- **Global**: `window.EventFoundry` (for programmatic access if needed)
|
|
250
|
+
- **Minification**: Template minification and console statement stripping in production
|
|
251
|
+
|
|
252
|
+
## Browser Compatibility
|
|
253
|
+
|
|
254
|
+
EventFoundry Tracker is built with ES2015+ JavaScript and supports all modern browsers:
|
|
255
|
+
|
|
256
|
+
- **Chrome**: 51+
|
|
257
|
+
- **Firefox**: 54+
|
|
258
|
+
- **Safari**: 10+
|
|
259
|
+
- **Edge**: 15+
|
|
260
|
+
|
|
261
|
+
### Required Browser Features
|
|
262
|
+
|
|
263
|
+
- ES2015 syntax (arrow functions, const/let, template literals)
|
|
264
|
+
- `localStorage` API
|
|
265
|
+
- `fetch` API
|
|
266
|
+
- `navigator.sendBeacon` API (for reliable event delivery)
|
|
267
|
+
- `postMessage` API (for editor mode communication)
|
|
268
|
+
|
|
269
|
+
All these features are widely supported in browsers from 2016 onwards.
|
|
88
270
|
|
|
89
271
|
## Development
|
|
90
272
|
|
|
273
|
+
### Setup
|
|
274
|
+
|
|
91
275
|
```bash
|
|
92
276
|
# Install dependencies
|
|
93
277
|
npm install
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Building
|
|
94
281
|
|
|
95
|
-
|
|
282
|
+
```bash
|
|
283
|
+
# Production build (outputs to dist/tracker.min.js)
|
|
96
284
|
npm run build
|
|
97
285
|
|
|
98
|
-
# Development watch mode
|
|
286
|
+
# Development watch mode (outputs to ../eventfoundry-app/frontend/public/tracker.min.js)
|
|
99
287
|
npm run dev
|
|
100
288
|
```
|
|
101
289
|
|
|
290
|
+
### Build System
|
|
291
|
+
|
|
292
|
+
The tracker uses **esbuild** for fast, efficient bundling:
|
|
293
|
+
|
|
294
|
+
- **Template minification**: CSS and HTML strings are minified via `esbuild-minify-templates`
|
|
295
|
+
- **Console stripping**: All `console.*` statements are removed from production builds
|
|
296
|
+
- **Sourcemaps**: Generated in development mode for debugging
|
|
297
|
+
- **Watch mode**: Automatically rebuilds on file changes and outputs to sibling project
|
|
298
|
+
|
|
299
|
+
### Build Configuration
|
|
300
|
+
|
|
301
|
+
- Production builds optimize for size and performance
|
|
302
|
+
- Development builds include sourcemaps and preserve console logs
|
|
303
|
+
- Both modes produce IIFE bundles targeting ES2015
|
|
304
|
+
|
|
305
|
+
## PostMessage API Reference
|
|
306
|
+
|
|
307
|
+
When running in editor mode (inside EventFoundry app iframe), the tracker communicates with the parent window using the following PostMessage events:
|
|
308
|
+
|
|
309
|
+
### Messages Sent by Tracker (to Parent)
|
|
310
|
+
|
|
311
|
+
#### `IFRAME_READY`
|
|
312
|
+
Sent when the tracker initializes in editor mode.
|
|
313
|
+
|
|
314
|
+
```javascript
|
|
315
|
+
{
|
|
316
|
+
type: 'IFRAME_READY'
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### `ELEMENT_SELECTED`
|
|
321
|
+
Sent when a user selects an element in the visual editor.
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
{
|
|
325
|
+
type: 'ELEMENT_SELECTED',
|
|
326
|
+
selector: 'button.primary', // Generated CSS selector
|
|
327
|
+
tagName: 'BUTTON', // Element tag name
|
|
328
|
+
text: 'Click me', // Element text content
|
|
329
|
+
id: 'submit-btn', // Element ID (if present)
|
|
330
|
+
classes: ['primary', 'btn'], // Array of classes
|
|
331
|
+
attributes: { // All element attributes
|
|
332
|
+
type: 'submit',
|
|
333
|
+
'data-track': 'signup'
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
#### `EVENT_FIRED`
|
|
339
|
+
Sent in preview mode when a tracked event is triggered.
|
|
340
|
+
|
|
341
|
+
```javascript
|
|
342
|
+
{
|
|
343
|
+
type: 'EVENT_FIRED',
|
|
344
|
+
eventName: 'button_click',
|
|
345
|
+
eventParams: {
|
|
346
|
+
// Event parameters that would be sent to GA4
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Messages Received by Tracker (from Parent)
|
|
352
|
+
|
|
353
|
+
#### `ENABLE_PREVIEW_MODE`
|
|
354
|
+
Activates preview mode for testing events.
|
|
355
|
+
|
|
356
|
+
```javascript
|
|
357
|
+
{
|
|
358
|
+
type: 'ENABLE_PREVIEW_MODE'
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### `DISABLE_PREVIEW_MODE`
|
|
363
|
+
Deactivates preview mode and returns to normal editor mode.
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
{
|
|
367
|
+
type: 'DISABLE_PREVIEW_MODE'
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Security
|
|
372
|
+
|
|
373
|
+
All PostMessage communication includes origin validation to ensure messages are only accepted from authorized EventFoundry domains. This prevents malicious sites from interfering with the tracker's operation.
|
|
374
|
+
|
|
102
375
|
## License
|
|
103
376
|
|
|
104
377
|
MIT © Kyle Logue
|
package/dist/tracker.min.js
CHANGED
|
@@ -1,22 +1 @@
|
|
|
1
|
-
var EventFoundry=(()=>{var
|
|
2
|
-
.eventfoundry-highlight {
|
|
3
|
-
outline: 2px solid #3498db !important;
|
|
4
|
-
outline-offset: 2px;
|
|
5
|
-
cursor: pointer !important;
|
|
6
|
-
}
|
|
7
|
-
.eventfoundry-test-highlight {
|
|
8
|
-
outline: 3px solid #e74c3c !important;
|
|
9
|
-
outline-offset: 3px;
|
|
10
|
-
background-color: rgba(231, 76, 60, 0.1) !important;
|
|
11
|
-
}
|
|
12
|
-
.eventfoundry-overlay {
|
|
13
|
-
position: fixed;
|
|
14
|
-
top: 0;
|
|
15
|
-
left: 0;
|
|
16
|
-
right: 0;
|
|
17
|
-
bottom: 0;
|
|
18
|
-
z-index: 999998;
|
|
19
|
-
pointer-events: none;
|
|
20
|
-
cursor: crosshair;
|
|
21
|
-
}
|
|
22
|
-
`,document.head.appendChild(t);let n=document.createElement("div");n.className="eventfoundry-overlay",document.body.appendChild(n),document.body.addEventListener("mouseover",e=>{e.target.classList.contains("eventfoundry-overlay")||(f&&f.classList.remove("eventfoundry-highlight"),e.target.classList.add("eventfoundry-highlight"),f=e.target)},!0),document.body.addEventListener("mouseout",e=>{e.target.classList.contains("eventfoundry-overlay")||e.target.classList.remove("eventfoundry-highlight")},!0),document.body.addEventListener("click",e=>{if(e.target.classList.contains("eventfoundry-overlay"))return;e.preventDefault(),e.stopPropagation();let o=e.target,a="";o.className&&typeof o.className=="string"&&(a=o.className.trim().split(/\s+/).filter(r=>!r.startsWith("eventfoundry-")).join(" "));let i={tagName:o.tagName.toLowerCase(),text:o.textContent.trim().substring(0,50),id:o.id,className:a,selector:x(o)};window.parent.postMessage({type:"ELEMENT_SELECTED",element:i},"*"),console.log("\u2192 ELEMENT_SELECTED sent to parent")},!0),window.addEventListener("message",e=>{if(M(e.origin)&&(e.data.type==="HIGHLIGHT_SELECTOR"&&(console.log("\u2190 HIGHLIGHT_SELECTOR received:",e.data.selector),_(e.data.selector)),e.data.type==="COUNT_MATCHES")){let o=e.data.selector,a=0;try{a=document.querySelectorAll(o).length}catch(i){a=0}window.parent.postMessage({type:"MATCH_COUNT_RESULT",selector:o,count:a},"*")}})}function g(t){return t?t.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g,"\\$1"):""}function F(t){return t?!!(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i.test(t)||/\d{13}/.test(t)||/^[a-f0-9]{16,}$/i.test(t)||/-\d{4,}$/.test(t)):!1}function d(t,n){try{let e=document.querySelectorAll(n);return e.length===1&&e[0]===t}catch(e){return!1}}function x(t){let n=[];if(t.id&&!F(t.id)){let r=`#${g(t.id)}`;d(t,r)&&n.push({selector:r,priority:100})}let e=Array.from(t.attributes).filter(r=>r.name.startsWith("data-")&&r.value);for(let r of e){let s=`[${r.name}="${g(r.value)}"]`;if(d(t,s)){n.push({selector:s,priority:90});break}}if(t.className&&typeof t.className=="string"){let r=t.className.trim().split(/\s+/).filter(s=>!s.startsWith("eventfoundry-"));for(let s of r){let c=`.${g(s)}`;if(d(t,c)){n.push({selector:c,priority:80});break}}}let o=t.tagName.toLowerCase();t instanceof SVGElement&&(o=t.tagName.toLowerCase());let a=o,i=t.parentElement;if(i){let s=Array.from(i.children).indexOf(t)+1;a+=`:nth-child(${s})`}return d(t,a)&&n.push({selector:a,priority:50}),n.length>0?(n.sort((r,s)=>s.priority-r.priority),n[0].selector):o}function _(t){document.querySelectorAll(".eventfoundry-test-highlight").forEach(n=>{n.classList.remove("eventfoundry-test-highlight")});try{let n=document.querySelectorAll(t);n.forEach(e=>{e.classList.add("eventfoundry-test-highlight")}),setTimeout(()=>{n.forEach(e=>{e.classList.remove("eventfoundry-test-highlight")})},3e3)}catch(n){console.error("EventFoundry: Invalid selector",t,n)}}function A(t,n=!1){!t||t.length===0||document.addEventListener("click",e=>{if(w())return;let o=t.filter(r=>{try{return e.target.closest(r.selector)!==null}catch(s){return console.warn("EventFoundry: Invalid selector",r.selector,s.message),!1}});if(o.length===0)return;let a=o[0],i=E({event_category:"EventFoundry",event_label:a.name},a.gaEventParams);n?console.log("EventFoundry [DEV]: Tracked event (not sent to GA4)",a.gaEventName,i):(console.log("EventFoundry: Tracked event",a.gaEventName,i),window.gtag("event",a.gaEventName,i)),o.length>1&&console.warn("EventFoundry: Multiple events matched, only firing first:",o.map(r=>r.name))},!0)}function k(){return["http://localhost","https://eventfoundry.app"]}function O(t){return k().some(e=>e==="http://localhost"?t.startsWith("http://localhost"):t===e)}function h(){try{return window.self!==window.top}catch(t){return!0}}function C(){h()&&(window.addEventListener("message",t=>{if(!O(t.origin)){console.warn("EventFoundry: Rejected postMessage from untrusted origin:",t.origin);return}t.data.type==="ENTER_EDIT_MODE"&&(console.log("\u2190 ENTER_EDIT_MODE received"),G())}),setTimeout(()=>{window.parent&&(window.parent.postMessage({type:"IFRAME_READY",timestamp:Date.now()},"*"),console.log("\u2192 IFRAME_READY sent to parent"))},100))}function G(){L()}var S=window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1",P=S?"http://localhost/api/tracking/events":"https://eventfoundry.app/api/tracking/events",p="eventfoundry_definitions",$=3600*1e3;function H(){let t=document.currentScript||document.querySelector("script[data-site-key]");if(!t)return console.error("EventFoundry: Could not find script tag"),null;let n=t.getAttribute("data-dev-mode"),e=n==="false"?!1:S||n==="true";return{siteKey:t.getAttribute("data-site-key"),measurementId:t.getAttribute("data-measurement-id"),devMode:e}}function j(t){return l(this,null,function*(){if(window.gtag&&typeof window.gtag=="function")return{hasGtag:!0,wasInjected:!1};if(!t)return console.warn("EventFoundry: No GA4 detected and no measurement ID provided. Events will not be tracked."),{hasGtag:!1,wasInjected:!1};let n=document.createElement("script");n.async=!0,n.src=`https://www.googletagmanager.com/gtag/js?id=${t}`,document.head.appendChild(n),window.dataLayer=window.dataLayer||[];function e(){dataLayer.push(arguments)}return window.gtag=e,e("js",new Date),e("config",t),yield new Promise(o=>{n.onload=o,setTimeout(o,5e3)}),{hasGtag:!0,wasInjected:!0}})}function N(){return l(this,null,function*(){let t=H();if(!t||!t.siteKey){console.error("EventFoundry: No site key provided");return}let{siteKey:n,measurementId:e,devMode:o}=t;console.log("\u2713 EventFoundry tracker loaded"),h()&&C();let{hasGtag:a,wasInjected:i}=yield j(e);if(!a){console.error("EventFoundry: Cannot track events without GA4");return}console.log(i?"\u2713 GA4 injected successfully":"\u2713 GA4 detected"),console.log(o?"\u26A0 Dev mode: Events disabled":"\u2713 Production mode: Events enabled");try{let r=yield R(n),s=r.events||r;A(s,o)}catch(r){console.error("EventFoundry: Failed to initialize:",r)}})}function R(t){return l(this,null,function*(){let n=q();if(n)return n;let e=yield fetch(`${P}?siteKey=${t}`);if(!e.ok)throw new Error(`API returned ${e.status}`);let o=yield e.json();return K(o),o})}function q(){try{let t=localStorage.getItem(p);if(!t)return null;let{data:n,timestamp:e}=JSON.parse(t);return Date.now()-e>$?(localStorage.removeItem(p),null):n}catch(t){return console.warn("EventFoundry: Cache read failed:",t),null}}function K(t){try{let n={data:t,timestamp:Date.now()};localStorage.setItem(p,JSON.stringify(n))}catch(n){console.warn("EventFoundry: Cache write failed:",n)}}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",N):N();})();
|
|
1
|
+
var EventFoundry=(()=>{var p=Object.defineProperty;var j=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames,N=Object.getOwnPropertySymbols;var F=Object.prototype.hasOwnProperty,V=Object.prototype.propertyIsEnumerable;var A=(e,t,n)=>t in e?p(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,I=(e,t)=>{for(var n in t||(t={}))F.call(t,n)&&A(e,n,t[n]);if(N)for(var n of N(t))V.call(t,n)&&A(e,n,t[n]);return e};var K=(e,t)=>{for(var n in t)p(e,n,{get:t[n],enumerable:!0})},W=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of H(t))!F.call(e,r)&&r!==n&&p(e,r,{get:()=>t[r],enumerable:!(o=j(t,r))||o.enumerable});return e};var q=e=>W(p({},"__esModule",{value:!0}),e);var g=(e,t,n)=>new Promise((o,r)=>{var a=d=>{try{l(n.next(d))}catch(m){r(m)}},s=d=>{try{l(n.throw(d))}catch(m){r(m)}},l=d=>d.done?o(d.value):Promise.resolve(d.value).then(a,s);l((n=n.apply(e,t)).next())});var ve={};K(ve,{reloadEventDefinitions:()=>b});var u=null,v=!1,y=null,f=!1;function P(){return v}function E(){return f}function B(){return["http://localhost","https://eventfoundry.app"]}function U(e){return B().includes(e)}function D(){if(v)return;v=!0;let e=document.createElement("style");e.textContent=` .eventfoundry-highlight { outline: 2px solid #3498db !important; outline-offset: 2px; cursor: pointer !important; } .eventfoundry-test-highlight { outline: 3px solid #e74c3c !important; outline-offset: 3px; background-color: rgba(231, 76, 60, 0.1) !important; } .eventfoundry-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 999998; pointer-events: none; cursor: crosshair; } body.eventfoundry-edit-mode, body.eventfoundry-edit-mode * { cursor: crosshair !important; } `,document.head.appendChild(e);let t=document.createElement("div");t.className="eventfoundry-overlay",document.body.appendChild(t),y=t,document.body.classList.add("eventfoundry-edit-mode"),document.body.addEventListener("mouseover",n=>{f||n.target.classList.contains("eventfoundry-overlay")||(u&&u.classList.remove("eventfoundry-highlight"),n.target.classList.add("eventfoundry-highlight"),u=n.target)},!0),document.body.addEventListener("mouseout",n=>{f||n.target.classList.contains("eventfoundry-overlay")||n.target.classList.remove("eventfoundry-highlight")},!0),document.body.addEventListener("click",n=>{if(n.target.classList.contains("eventfoundry-overlay")||f)return;n.preventDefault(),n.stopPropagation();let o=n.target,r="";o.className&&typeof o.className=="string"&&(r=o.className.trim().split(/\s+/).filter(l=>!l.startsWith("eventfoundry-")).join(" "));let a=window.location.pathname+window.location.search+window.location.hash,s={tagName:o.tagName.toLowerCase(),text:o.textContent.trim().substring(0,50),id:o.id,className:r,selector:ne(o),page_path:a};window.parent.postMessage({type:"ELEMENT_SELECTED",element:s},"*")},!0),window.addEventListener("message",n=>{if(U(n.origin)&&(n.data.type==="HIGHLIGHT_SELECTOR"&&oe(n.data.selector),n.data.type==="COUNT_MATCHES")){let o=n.data.selector,r=0;try{r=document.querySelectorAll(o).length}catch(a){r=0}window.parent.postMessage({type:"MATCH_COUNT_RESULT",selector:o,count:r},"*")}})}function i(e){return e?e.replace(/([!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~])/g,"\\$1"):""}function z(e){return e?!!(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i.test(e)||/\d{13}/.test(e)||/^[a-f0-9]{16,}$/i.test(e)||/-\d{4,}$/.test(e)):!1}function J(e,t){try{let n=document.querySelectorAll(t);return n.length===1&&n[0]===e}catch(n){return!1}}function w(e){return!e.className||typeof e.className!="string"?[]:e.className.trim().split(/\s+/).filter(t=>t&&!t.startsWith("eventfoundry-"))}function $(e,t=!0){let n=e.parentElement;if(!n)return null;let o=w(n);return t&&o.length>0?`.${i(o[0])}`:n.tagName.toLowerCase()}function c(e,t,n,o){return J(o,t)?(e.push({selector:t,priority:n}),!0):!1}function Y(e){let t=[];if(e.id&&!z(e.id)){let n=`#${i(e.id)}`;c(t,n,100,e)}return t}function Q(e){let t=[],n=Array.from(e.attributes).filter(o=>o.name.startsWith("data-")&&o.value);for(let o of n){let r=`[${o.name}="${i(o.value)}"]`;if(c(t,r,90,e))break}return t}function X(e){let t=[],n=w(e);if(n.length===0)return t;let o=e.tagName.toLowerCase();for(let r of n){let a=`${o}.${i(r)}`;if(c(t,a,85,e))break}if(n.length>=2){let r=`.${i(n[0])}.${i(n[1])}`;if(c(t,r,82,e),n.length>=3){let a=`.${i(n[0])}.${i(n[1])}.${i(n[2])}`;c(t,a,82,e)}}for(let r of n){let a=`.${i(r)}`;if(c(t,a,80,e))break}return t}function Z(e){let t=[],n=e.tagName.toLowerCase(),o=["type","name","href","role","aria-label"];for(let r of o){let a=e.getAttribute(r);if(a){let s=`${n}[${r}="${i(a)}"]`;if(c(t,s,70,e))break}}return t}function ee(e){let t=[],n=$(e,!0);if(!n)return t;let o=e.tagName.toLowerCase(),r=w(e);if(r.length>0){let s=`${n} .${i(r[0])}`;c(t,s,75,e)}if(r.length>0){let s=`${n} ${o}.${i(r[0])}`;c(t,s,72,e)}let a=`${n} ${o}`;return c(t,a,65,e),t}function te(e){let t=[],n=e.tagName.toLowerCase(),o=e.parentElement;if(!o)return[{selector:n,priority:10}];let r=$(e,!0),s=Array.from(o.children).indexOf(e)+1,l=`${r} ${n}:nth-child(${s})`;return c(t,l,50,e),t}function ne(e){let t=[];return t.push(...Y(e)),t.push(...Q(e)),t.push(...X(e)),t.push(...Z(e)),t.push(...ee(e)),t.push(...te(e)),t.length>0?(t.sort((n,o)=>o.priority-n.priority),t[0].selector):e.tagName.toLowerCase()}function oe(e){document.querySelectorAll(".eventfoundry-test-highlight").forEach(t=>{t.classList.remove("eventfoundry-test-highlight")});try{let t=document.querySelectorAll(e);t.forEach(n=>{n.classList.add("eventfoundry-test-highlight")}),setTimeout(()=>{t.forEach(n=>{n.classList.remove("eventfoundry-test-highlight")})},3e3)}catch(t){}}function M(){v&&(f=!0,y&&(y.style.display="none"),document.body.classList.remove("eventfoundry-edit-mode"),u&&(u.classList.remove("eventfoundry-highlight"),u=null))}function T(){v&&(f=!1,y&&(y.style.display=""),document.body.classList.add("eventfoundry-edit-mode"))}var _=console.log;function h(e,...t){e&&_&&_(...t)}function L(e,t=!1){!e||e.length===0||document.addEventListener("click",n=>{if(P()&&!E())return;let o=e.filter(s=>{try{return n.target.closest(s.selector)!==null}catch(l){return!1}});if(o.length===0)return;let r=o[0],a=I({event_category:"EventFoundry",event_label:r.name,transport_type:"beacon"},r.gaEventParams);if(h(t,"EventFoundry [DEV]: Tracked event (not sent to GA4)",r.gaEventName,a),t||window.gtag("event",r.gaEventName,a),E())try{window.parent.postMessage({type:"EVENT_FIRED",timestamp:Date.now(),eventName:r.name,gaEventName:r.gaEventName,selector:r.selector,params:a,pagePath:window.location.pathname},"*")}catch(s){}o.length>1},!0)}function re(){return["http://localhost","https://eventfoundry.app"]}function ae(e){return re().some(n=>n==="http://localhost"?e.startsWith("http://localhost"):e===n)}function S(){try{return window.self!==window.top}catch(e){return!0}}function k(){S()&&(window.addEventListener("message",e=>{if(ae(e.origin))switch(e.data.type){case"ENTER_EDIT_MODE":se();break;case"ENABLE_PREVIEW":ie();break;case"DISABLE_PREVIEW":ce();break;case"RELOAD_CONFIG":b();break}}),setTimeout(()=>{window.parent&&window.parent.postMessage({type:"IFRAME_READY"},"*")},100))}function se(){D()}function ie(){M()}function ce(){T()}var O=window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1",le=O?"http://localhost/api/tracking/events":"https://eventfoundry.app/api/tracking/events",de=3600*1e3;function C(e){return`eventfoundry_definitions_${e}`}function R(){let e=document.currentScript||document.querySelector("script[data-site-key]");if(!e)return null;let t=e.getAttribute("data-dev-mode"),n=t==="false"?!1:O||t==="true";return{siteKey:e.getAttribute("data-site-key"),measurementId:e.getAttribute("data-measurement-id"),devMode:n}}function ue(e){return g(this,null,function*(){if(window.gtag&&typeof window.gtag=="function")return{hasGtag:!0,wasInjected:!1};if(!e)return{hasGtag:!1,wasInjected:!1};let t=document.createElement("script");t.async=!0,t.src=`https://www.googletagmanager.com/gtag/js?id=${e}`,document.head.appendChild(t),window.dataLayer=window.dataLayer||[];function n(){dataLayer.push(arguments)}return window.gtag=n,n("js",new Date),n("config",e),yield new Promise(o=>{t.onload=o,setTimeout(o,5e3)}),{hasGtag:!0,wasInjected:!0}})}function x(){return g(this,null,function*(){let e=R();if(!e||!e.siteKey)return;let{siteKey:t,measurementId:n,devMode:o}=e;S()&&k();let{hasGtag:r,wasInjected:a}=yield ue(n);if(r){h(o,"\u26A0 Dev mode: Events disabled"),h(!o,"\u2713 Production mode: Events enabled");try{let l=(yield G(t)).events;L(l,o)}catch(s){}}})}function G(e){return g(this,null,function*(){let t=fe(e);if(t)return t;let n=yield fetch(`${le}?siteKey=${e}`);if(!n.ok)throw new Error(`API returned ${n.status}`);let o=yield n.json();return ge(e,o),o})}function fe(e){try{let t=C(e),n=localStorage.getItem(t);if(!n)return null;let{data:o,timestamp:r}=JSON.parse(n);return Date.now()-r>de?(localStorage.removeItem(t),null):o}catch(t){return null}}function ge(e,t){try{let n=C(e),o={data:t,timestamp:Date.now()};localStorage.setItem(n,JSON.stringify(o))}catch(n){}}function b(){return g(this,null,function*(){let e=R();if(!e||!e.siteKey)return;let{siteKey:t,devMode:n}=e;try{let o=C(t);localStorage.removeItem(o)}catch(o){}try{let r=(yield G(t)).events;L(r,n)}catch(o){}})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",x):x();return q(ve);})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kylelogue/eventfoundry-tracker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "EventFoundry tracking script for custom GA4 event tracking",
|
|
5
5
|
"main": "dist/tracker.min.js",
|
|
6
6
|
"files": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"author": "Kyle Logue",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"esbuild": "^0.27.0"
|
|
33
|
+
"esbuild": "^0.27.0",
|
|
34
|
+
"esbuild-minify-templates": "^0.13.1"
|
|
34
35
|
}
|
|
35
36
|
}
|