core-outline 1.1.21 → 1.1.23
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 +81 -50
- package/dist/index.es.js +404 -2403
- package/dist/index.js +404 -2403
- package/docs/changelog/270626-143000_tracking_pipeline_changelog.md +44 -0
- package/package.json +2 -2
- package/src/components/CoreOutline/CoreOutline.js +138 -355
- package/src/components/CoreOutline/helpers.js +66 -0
- package/src/components/CoreOutline/ingest.js +116 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Tracking Pipeline — react-component Changes
|
|
2
|
+
Date: 2026-06-27
|
|
3
|
+
|
|
4
|
+
## Summary
|
|
5
|
+
Replaced Socket.IO with direct HTTP calls to the Hermes ingest API. Added warehouse_id-based routing, improved event schema, and removed dev artifacts.
|
|
6
|
+
|
|
7
|
+
## Breaking Changes
|
|
8
|
+
- `<CoreOutline>` now requires a `warehouse_id` prop (in addition to existing `data_source_id` + `data_source_secret`)
|
|
9
|
+
- The `data_source_secret` is now validated against Hermes's Vault store; the old `api.coreoutline.com/data-source/authorize` endpoint is no longer used
|
|
10
|
+
|
|
11
|
+
## New Files
|
|
12
|
+
- `src/components/CoreOutline/helpers.js` — browser utilities: `getAnonymousId`, `getSessionId`, `getUtmParams`, `detectDeviceType`, `detectOS`, `getBrowserName`, `getPagePath`
|
|
13
|
+
- `src/components/CoreOutline/ingest.js` — HTTP ingest client: `initIngest`, `trackEvent`, `flush`, `sendRrwebBatch`, `stopFlushInterval`
|
|
14
|
+
|
|
15
|
+
## Modified Files
|
|
16
|
+
|
|
17
|
+
### `src/components/CoreOutline/CoreOutline.js`
|
|
18
|
+
- Added `warehouse_id` prop
|
|
19
|
+
- Replaced all `socket.send()` calls with `trackEvent()` from `ingest.js`
|
|
20
|
+
- Events now include: UTM params, device type, OS, page_url, page_path, anonymous_id (persistent cross-session), session_id (per-tab via sessionStorage)
|
|
21
|
+
- Removed `getLocation()` / geolocation (replaced by server-side IP geolocation)
|
|
22
|
+
- Removed dev-only `<button>Save Events Locally</button>` from rendered output
|
|
23
|
+
- `flag_item_clicked(item_id)` and `flag_item_purchased(item_id, price)` rewritten to use `trackEvent()`; `flag_item_purchased` now accepts an optional `price` parameter mapped to `value_num`
|
|
24
|
+
- Events auto-flush every 5 seconds and on page unload/visibility change
|
|
25
|
+
- rrweb recordings uploaded via `sendRrwebBatch` at session end
|
|
26
|
+
|
|
27
|
+
### `package.json`
|
|
28
|
+
- Removed: `socket.io-client` (~100KB bundle savings)
|
|
29
|
+
- Added: `uuid` as explicit dependency (was transitive via rrweb)
|
|
30
|
+
|
|
31
|
+
## Event Types Emitted
|
|
32
|
+
| event_type | event_category | Destination table |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `session_start` | `session` | `fact_session` |
|
|
35
|
+
| `session_end` | `session` | `fact_session` (update via ReplacingMergeTree) |
|
|
36
|
+
| `pageview` | `navigation` | `fact_pageview` + `fact_event` |
|
|
37
|
+
| `item_clicked` | `interaction` | `fact_event` |
|
|
38
|
+
| `product_clicked` | `commerce` | `fact_event` |
|
|
39
|
+
| `product_purchased` | `commerce` | `fact_event` (value_num = price) |
|
|
40
|
+
|
|
41
|
+
## Data Flow
|
|
42
|
+
react-component → `POST https://api.coreoutline.com/api/ingest/authorize` (get JWT)
|
|
43
|
+
→ `POST https://api.coreoutline.com/api/ingest/events` (batch, every 5s + on unload)
|
|
44
|
+
→ `POST https://api.coreoutline.com/api/ingest/rrweb` (session end)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-outline",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.23",
|
|
4
4
|
"description": "A React component for Core&Outline",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.es.js",
|
|
@@ -47,6 +47,6 @@
|
|
|
47
47
|
"react-error-boundary": "^5.0.0",
|
|
48
48
|
"react-tracking": "^9.3.2",
|
|
49
49
|
"rrweb": "^2.0.0-alpha.4",
|
|
50
|
-
"
|
|
50
|
+
"uuid": "^9.0.0"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -1,171 +1,125 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { record } from 'rrweb';
|
|
3
|
-
import { v4 } from 'uuid';
|
|
4
|
-
import socket from './socket';
|
|
5
|
-
|
|
6
3
|
import {
|
|
7
4
|
EventType,
|
|
8
|
-
eventWithTime,
|
|
9
5
|
IncrementalSource,
|
|
10
6
|
MouseInteractions,
|
|
11
7
|
} from 'rrweb';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
import {
|
|
9
|
+
getAnonymousId,
|
|
10
|
+
getSessionId,
|
|
11
|
+
getUtmParams,
|
|
12
|
+
detectDeviceType,
|
|
13
|
+
detectOS,
|
|
14
|
+
getBrowserName,
|
|
15
|
+
getPagePath,
|
|
16
|
+
} from './helpers';
|
|
17
|
+
import {
|
|
18
|
+
initIngest,
|
|
19
|
+
trackEvent,
|
|
20
|
+
flush,
|
|
21
|
+
sendRrwebBatch,
|
|
22
|
+
stopFlushInterval,
|
|
23
|
+
} from './ingest';
|
|
24
|
+
|
|
25
|
+
const CoreOutline = ({ children, data_source_id, data_source_secret, warehouse_id }) => {
|
|
15
26
|
const [replayEvents, setReplayEvents] = useState([]);
|
|
16
27
|
const recorderActive = useRef(false);
|
|
17
28
|
const [currentPage, setCurrentPage] = useState(window.location.href);
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
29
|
+
const pageviewCountRef = useRef(0);
|
|
30
|
+
const eventCountRef = useRef(0);
|
|
31
|
+
const sessionStartRef = useRef(Date.now());
|
|
32
|
+
|
|
33
|
+
const sessionId = getSessionId();
|
|
34
|
+
const anonymousId = getAnonymousId();
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const utmParams = getUtmParams();
|
|
38
|
+
const deviceType = detectDeviceType();
|
|
39
|
+
const os = detectOS();
|
|
40
|
+
const browser = getBrowserName();
|
|
41
|
+
const referrer = document.referrer || 'direct';
|
|
42
|
+
|
|
43
|
+
const basePayload = {
|
|
44
|
+
session_id: sessionId,
|
|
45
|
+
anonymous_id: anonymousId,
|
|
46
|
+
account_id: data_source_id,
|
|
47
|
+
platform: 'web',
|
|
48
|
+
device_type: deviceType,
|
|
49
|
+
os,
|
|
50
|
+
browser,
|
|
51
|
+
referrer,
|
|
52
|
+
page_url: window.location.href,
|
|
53
|
+
page_path: getPagePath(),
|
|
54
|
+
...utmParams,
|
|
33
55
|
};
|
|
34
|
-
const response = fetch(
|
|
35
|
-
`http://api.coreoutline.com/data-source/authorize`,
|
|
36
|
-
requestOptions
|
|
37
|
-
)
|
|
38
|
-
.then((response) => response.json())
|
|
39
|
-
.then((data) => {
|
|
40
|
-
setAppToken(data.app_token);
|
|
41
|
-
return data;
|
|
42
|
-
})
|
|
43
|
-
.catch((error) => {
|
|
44
|
-
console.error('Error:', error);
|
|
45
|
-
return error;
|
|
46
|
-
});
|
|
47
|
-
return response;
|
|
48
|
-
}
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
const formattedEvents = events;
|
|
52
|
-
const rawEvents = replayEvents;
|
|
53
|
-
const data = JSON.stringify({
|
|
54
|
-
data_source_id: data_source_id,
|
|
55
|
-
sessionId: sessionId,
|
|
56
|
-
formatted_events: formattedEvents,
|
|
57
|
-
raw_events: rawEvents,
|
|
58
|
-
});
|
|
57
|
+
let initialized = false;
|
|
59
58
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
session_id: localStorage.getItem('coreOutlineSessionId'),
|
|
64
|
-
start_date: new Date().toLocaleString(),
|
|
65
|
-
latitude: location.latitude,
|
|
66
|
-
longitude: location.longitude,
|
|
67
|
-
browser: browser,
|
|
68
|
-
event_id: 'SESSION_END',
|
|
69
|
-
});
|
|
59
|
+
const init = async () => {
|
|
60
|
+
initialized = await initIngest(warehouse_id, data_source_id, data_source_secret);
|
|
61
|
+
if (!initialized) return;
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
}).catch((err) => console.error('Upload failed:', err));
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
useEffect(() => {}, [events, replayEvents]);
|
|
63
|
+
pageviewCountRef.current += 1;
|
|
64
|
+
trackEvent('pageview', { ...basePayload, event_name: 'pageview', event_category: 'navigation' });
|
|
65
|
+
trackEvent('session_start', {
|
|
66
|
+
...basePayload,
|
|
67
|
+
event_name: 'session_start',
|
|
68
|
+
event_category: 'session',
|
|
69
|
+
session_start_ts: new Date().toISOString(),
|
|
70
|
+
});
|
|
71
|
+
};
|
|
85
72
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
73
|
+
init();
|
|
74
|
+
|
|
75
|
+
const handleSessionEnd = () => {
|
|
76
|
+
if (!initialized) return;
|
|
77
|
+
const durationMs = Date.now() - sessionStartRef.current;
|
|
78
|
+
trackEvent('session_end', {
|
|
79
|
+
...basePayload,
|
|
80
|
+
event_name: 'session_end',
|
|
81
|
+
event_category: 'session',
|
|
82
|
+
session_end_ts: new Date().toISOString(),
|
|
83
|
+
pageviews: pageviewCountRef.current,
|
|
84
|
+
events_count: eventCountRef.current,
|
|
85
|
+
duration_ms: durationMs,
|
|
86
|
+
});
|
|
87
|
+
flush();
|
|
88
|
+
stopFlushInterval();
|
|
89
|
+
sendRrwebBatch(replayEvents, sessionId);
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
const handleVisibilityChange = () => {
|
|
92
|
-
if (document.visibilityState === 'hidden')
|
|
93
|
-
offLoadFunctionality();
|
|
94
|
-
}
|
|
93
|
+
if (document.visibilityState === 'hidden') handleSessionEnd();
|
|
95
94
|
};
|
|
96
|
-
|
|
95
|
+
|
|
96
|
+
window.addEventListener('beforeunload', handleSessionEnd);
|
|
97
97
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
98
|
-
await authorizeApp(data_source_id, data_source_secret);
|
|
99
|
-
localStorage.setItem('coreOutlineSessionId', sessionId);
|
|
100
|
-
localStorage.setItem('coreOutlineDataSourceIdId', data_source_id);
|
|
101
|
-
console.log('Session', sessionId);
|
|
102
|
-
socket.send({
|
|
103
|
-
topic: 'session-data',
|
|
104
|
-
data_source_id: data_source_id,
|
|
105
|
-
session_id: localStorage.getItem('coreOutlineSessionId'),
|
|
106
|
-
start_date: new Date().toLocaleString(),
|
|
107
|
-
latitude: location.latitude,
|
|
108
|
-
longitude: location.longitude,
|
|
109
|
-
browser: browser,
|
|
110
|
-
event_id: 'SESSION_START',
|
|
111
|
-
});
|
|
112
98
|
|
|
113
99
|
return () => {
|
|
114
|
-
|
|
115
|
-
window.removeEventListener('beforeunload',
|
|
100
|
+
handleSessionEnd();
|
|
101
|
+
window.removeEventListener('beforeunload', handleSessionEnd);
|
|
116
102
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
117
|
-
socket.send({
|
|
118
|
-
topic: 'session-data',
|
|
119
|
-
data_source_id: data_source_id,
|
|
120
|
-
session_id: localStorage.getItem('coreOutlineSessionId'),
|
|
121
|
-
start_date: new Date().toLocaleString(),
|
|
122
|
-
latitude: location.latitude,
|
|
123
|
-
longitude: location.longitude,
|
|
124
|
-
browser: browser,
|
|
125
|
-
event_id: 'SESSION_END',
|
|
126
|
-
});
|
|
127
103
|
};
|
|
128
104
|
}, []);
|
|
129
105
|
|
|
130
106
|
useEffect(() => {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
data: {
|
|
134
|
-
href: window.location.href,
|
|
135
|
-
width: 1536,
|
|
136
|
-
height: 730,
|
|
137
|
-
},
|
|
138
|
-
timestamp: Date.now(),
|
|
139
|
-
};
|
|
140
|
-
const startTime = performance.now();
|
|
141
|
-
const observer = new MutationObserver(() => {
|
|
142
|
-
const endTime = performance.now();
|
|
143
|
-
const loadTime = endTime - startTime;
|
|
144
|
-
navigationEvent.data.loadTime = loadTime;
|
|
145
|
-
const hours = Math.floor(loadTime / 3600000);
|
|
146
|
-
const minutes = Math.floor((loadTime % 3600000) / 60000);
|
|
147
|
-
const seconds = Math.floor((loadTime % 60000) / 1000);
|
|
148
|
-
navigationEvent.data.loadTimeFormatted = `${hours} hours ${minutes} minutes ${seconds} seconds`;
|
|
149
|
-
observer.disconnect();
|
|
150
|
-
});
|
|
151
|
-
observer.observe(document, { childList: true, subtree: true });
|
|
152
|
-
setEvents((prevEvents) => [...prevEvents, navigationEvent]);
|
|
153
|
-
localStorage.setItem(
|
|
154
|
-
'coreoutlineFormattedEvents',
|
|
155
|
-
JSON.stringify((prevEvents) => [...prevEvents, navigationEvent])
|
|
156
|
-
);
|
|
107
|
+
const utmParams = getUtmParams();
|
|
108
|
+
|
|
157
109
|
const handleNavigation = () => {
|
|
158
110
|
setCurrentPage(window.location.href);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
111
|
+
pageviewCountRef.current += 1;
|
|
112
|
+
trackEvent('pageview', {
|
|
113
|
+
session_id: sessionId,
|
|
114
|
+
anonymous_id: anonymousId,
|
|
115
|
+
account_id: data_source_id,
|
|
116
|
+
platform: 'web',
|
|
117
|
+
page_url: window.location.href,
|
|
118
|
+
page_path: getPagePath(),
|
|
119
|
+
referrer: document.referrer || 'direct',
|
|
120
|
+
event_name: 'pageview',
|
|
121
|
+
event_category: 'navigation',
|
|
122
|
+
...utmParams,
|
|
169
123
|
});
|
|
170
124
|
};
|
|
171
125
|
|
|
@@ -182,76 +136,22 @@ const CoreOutline = ({ children, data_source_id, data_source_secret }) => {
|
|
|
182
136
|
};
|
|
183
137
|
}, [currentPage]);
|
|
184
138
|
|
|
185
|
-
useEffect(() => {
|
|
186
|
-
console.log('Location', location);
|
|
187
|
-
getLocation();
|
|
188
|
-
}, []);
|
|
189
|
-
|
|
190
139
|
useEffect(() => {
|
|
191
140
|
let stopFn;
|
|
192
141
|
if (!recorderActive.current) {
|
|
193
142
|
stopFn = record({
|
|
194
143
|
emit(event) {
|
|
195
|
-
setReplayEvents((
|
|
196
|
-
localStorage.setItem(
|
|
197
|
-
'coreoutlineRawEvents',
|
|
198
|
-
JSON.stringify((prevEvents) => [...replayEvents, event])
|
|
199
|
-
);
|
|
144
|
+
setReplayEvents((prev) => [...prev, event]);
|
|
200
145
|
},
|
|
201
146
|
maskAllInputs: false,
|
|
202
147
|
maskTextSelector: null,
|
|
203
148
|
});
|
|
204
|
-
|
|
205
149
|
recorderActive.current = true;
|
|
206
150
|
}
|
|
207
151
|
|
|
208
152
|
return () => {
|
|
209
153
|
if (stopFn) {
|
|
210
154
|
stopFn();
|
|
211
|
-
|
|
212
|
-
recorderActive.current = false;
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
}, []);
|
|
216
|
-
|
|
217
|
-
useEffect(() => {
|
|
218
|
-
let stopFn;
|
|
219
|
-
if (!recorderActive.current) {
|
|
220
|
-
stopFn = record({
|
|
221
|
-
emit(event) {
|
|
222
|
-
console.log('Event:', event);
|
|
223
|
-
if (event.type == 3 && event.data.type == 2) {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (event.type == 4) {
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (event.type == 3 && event.data.source == 0) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (event.type == 3 && event.data.source == 1) {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
if (event.type == 3 && event.data.type == 3) {
|
|
236
|
-
console.log('Contextmenu Event:', event);
|
|
237
|
-
}
|
|
238
|
-
setEvents((prevEvents) => [...prevEvents, event]);
|
|
239
|
-
localStorage.setItem(
|
|
240
|
-
'coreoutlineFormattedEvents',
|
|
241
|
-
JSON.stringify((prevEvents) => [...prevEvents, event])
|
|
242
|
-
);
|
|
243
|
-
},
|
|
244
|
-
maskAllInputs: false,
|
|
245
|
-
maskTextSelector: null,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
recorderActive.current = true;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return () => {
|
|
252
|
-
if (stopFn) {
|
|
253
|
-
stopFn();
|
|
254
|
-
|
|
255
155
|
recorderActive.current = false;
|
|
256
156
|
}
|
|
257
157
|
};
|
|
@@ -259,180 +159,63 @@ const CoreOutline = ({ children, data_source_id, data_source_secret }) => {
|
|
|
259
159
|
|
|
260
160
|
useEffect(() => {
|
|
261
161
|
const handleClick = (event) => {
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
clickedElement.getAttribute('value') ||
|
|
287
|
-
clickedElement.innerText ||
|
|
288
|
-
null,
|
|
289
|
-
id:
|
|
290
|
-
clickedElement.getAttribute('id') ||
|
|
291
|
-
clickedElement.innerText ||
|
|
292
|
-
null,
|
|
293
|
-
name:
|
|
294
|
-
clickedElement.getAttribute('name') ||
|
|
295
|
-
clickedElement.innerText ||
|
|
296
|
-
null,
|
|
297
|
-
class:
|
|
298
|
-
clickedElement.getAttribute('class') ||
|
|
299
|
-
clickedElement.innerText ||
|
|
300
|
-
null,
|
|
301
|
-
tag: clickedElement.tagName,
|
|
302
|
-
html: String(clickedElement.getHTML()),
|
|
303
|
-
innerText: event.target.innerText,
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
setEvents((prevEvents) => [...prevEvents, clickEvent]);
|
|
307
|
-
localStorage.setItem(
|
|
308
|
-
'coreoutlineFormattedEvents',
|
|
309
|
-
JSON.stringify((prevEvents) => [...prevEvents, clickEvent])
|
|
310
|
-
);
|
|
311
|
-
socket.send({
|
|
312
|
-
topic: 'session-data',
|
|
313
|
-
data_source_id: data_source_id,
|
|
314
|
-
session_id: localStorage.getItem('coreOutlineSessionId'),
|
|
315
|
-
start_date: new Date().toLocaleString(),
|
|
316
|
-
latitude: location.latitude,
|
|
317
|
-
longitude: location.longitude,
|
|
318
|
-
browser: browser,
|
|
319
|
-
event_id: 'ITEM_CLICKED',
|
|
320
|
-
// item_clicked: event.target,
|
|
162
|
+
const clickedElement = document.elementFromPoint(event.clientX, event.clientY);
|
|
163
|
+
const metadata = clickedElement
|
|
164
|
+
? {
|
|
165
|
+
label: clickedElement.getAttribute('data-label') || clickedElement.innerText || null,
|
|
166
|
+
value: clickedElement.getAttribute('value') || clickedElement.innerText || null,
|
|
167
|
+
id: clickedElement.getAttribute('id') || null,
|
|
168
|
+
name: clickedElement.getAttribute('name') || null,
|
|
169
|
+
class: clickedElement.getAttribute('class') || null,
|
|
170
|
+
tag: clickedElement.tagName,
|
|
171
|
+
inner_text: event.target.innerText || null,
|
|
172
|
+
}
|
|
173
|
+
: {};
|
|
174
|
+
|
|
175
|
+
eventCountRef.current += 1;
|
|
176
|
+
trackEvent('item_clicked', {
|
|
177
|
+
session_id: sessionId,
|
|
178
|
+
anonymous_id: anonymousId,
|
|
179
|
+
account_id: data_source_id,
|
|
180
|
+
platform: 'web',
|
|
181
|
+
page_url: window.location.href,
|
|
182
|
+
page_path: getPagePath(),
|
|
183
|
+
event_name: 'item_clicked',
|
|
184
|
+
event_category: 'interaction',
|
|
185
|
+
properties_json: JSON.stringify(metadata),
|
|
321
186
|
});
|
|
322
187
|
};
|
|
323
188
|
|
|
324
189
|
document.addEventListener('click', handleClick);
|
|
325
|
-
|
|
326
|
-
return () => {
|
|
327
|
-
document.removeEventListener('click', handleClick);
|
|
328
|
-
};
|
|
190
|
+
return () => document.removeEventListener('click', handleClick);
|
|
329
191
|
}, []);
|
|
330
192
|
|
|
331
|
-
|
|
332
|
-
let blob = '';
|
|
333
|
-
if (type === 'formatted') {
|
|
334
|
-
blob = new Blob([JSON.stringify(events, null, 2)], {
|
|
335
|
-
type: 'application/json',
|
|
336
|
-
});
|
|
337
|
-
} else if (type === 'raw') {
|
|
338
|
-
blob = new Blob([JSON.stringify(replayEvents, null, 2)], {
|
|
339
|
-
type: 'application/json',
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
const url = URL.createObjectURL(blob);
|
|
343
|
-
const a = document.createElement('a');
|
|
344
|
-
a.href = url;
|
|
345
|
-
a.download = `${type}_events.json`;
|
|
346
|
-
a.click();
|
|
347
|
-
URL.revokeObjectURL(url);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function getBrowserInfo() {
|
|
351
|
-
const userAgent = navigator.userAgent;
|
|
352
|
-
let browserName = 'Unknown';
|
|
353
|
-
|
|
354
|
-
if (userAgent.indexOf('Firefox') > -1) {
|
|
355
|
-
browserName = 'Firefox';
|
|
356
|
-
} else if (
|
|
357
|
-
userAgent.indexOf('Opera') > -1 ||
|
|
358
|
-
userAgent.indexOf('OPR') > -1
|
|
359
|
-
) {
|
|
360
|
-
browserName = 'Opera';
|
|
361
|
-
} else if (userAgent.indexOf('Chrome') > -1) {
|
|
362
|
-
browserName = 'Chrome';
|
|
363
|
-
} else if (userAgent.indexOf('Safari') > -1) {
|
|
364
|
-
browserName = 'Safari';
|
|
365
|
-
} else if (
|
|
366
|
-
userAgent.indexOf('MSIE') > -1 ||
|
|
367
|
-
userAgent.indexOf('Trident/') > -1
|
|
368
|
-
) {
|
|
369
|
-
browserName = 'Internet Explorer';
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return browserName;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
function getLocation() {
|
|
376
|
-
if (navigator.geolocation) {
|
|
377
|
-
navigator.geolocation.getCurrentPosition(
|
|
378
|
-
(position) => {
|
|
379
|
-
setLocation({
|
|
380
|
-
latitude: position.coords.latitude,
|
|
381
|
-
longitude: position.coords.longitude,
|
|
382
|
-
});
|
|
383
|
-
},
|
|
384
|
-
(error) => {
|
|
385
|
-
console.error('Error getting location:', error);
|
|
386
|
-
}
|
|
387
|
-
);
|
|
388
|
-
} else {
|
|
389
|
-
console.error('Geolocation is not supported by this browser.');
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
return (
|
|
394
|
-
<div>
|
|
395
|
-
<button
|
|
396
|
-
onClick={() => {
|
|
397
|
-
saveEventsLocally('formatted');
|
|
398
|
-
saveEventsLocally('raw');
|
|
399
|
-
}}
|
|
400
|
-
>
|
|
401
|
-
Save Events Locally
|
|
402
|
-
</button>
|
|
403
|
-
{children}
|
|
404
|
-
</div>
|
|
405
|
-
);
|
|
193
|
+
return <>{children}</>;
|
|
406
194
|
};
|
|
407
195
|
|
|
408
196
|
export const flag_item_clicked = (item_id) => {
|
|
409
|
-
const sessionId =
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
start_date: new Date().toLocaleString(),
|
|
418
|
-
event_id: 'PRODUCT_CLICKED',
|
|
419
|
-
item_clicked: item_id,
|
|
197
|
+
const sessionId = sessionStorage.getItem('co_session_id') || '';
|
|
198
|
+
const anonymousId = localStorage.getItem('co_anon_id') || '';
|
|
199
|
+
trackEvent('product_clicked', {
|
|
200
|
+
session_id: sessionId,
|
|
201
|
+
anonymous_id: anonymousId,
|
|
202
|
+
event_name: 'product_clicked',
|
|
203
|
+
event_category: 'commerce',
|
|
204
|
+
feature_key: item_id,
|
|
420
205
|
});
|
|
421
206
|
return { status: 'success', message: 'Item click flagged successfully.' };
|
|
422
207
|
};
|
|
423
208
|
|
|
424
|
-
export const flag_item_purchased = (item_id) => {
|
|
425
|
-
const sessionId =
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
event_id: 'PRODUCT_PURCHASED',
|
|
435
|
-
item_clicked: item_id,
|
|
209
|
+
export const flag_item_purchased = (item_id, price = null) => {
|
|
210
|
+
const sessionId = sessionStorage.getItem('co_session_id') || '';
|
|
211
|
+
const anonymousId = localStorage.getItem('co_anon_id') || '';
|
|
212
|
+
trackEvent('product_purchased', {
|
|
213
|
+
session_id: sessionId,
|
|
214
|
+
anonymous_id: anonymousId,
|
|
215
|
+
event_name: 'product_purchased',
|
|
216
|
+
event_category: 'commerce',
|
|
217
|
+
feature_key: item_id,
|
|
218
|
+
value_num: price != null ? Number(price) : null,
|
|
436
219
|
});
|
|
437
220
|
return { status: 'success', message: 'Item purchase flagged successfully.' };
|
|
438
221
|
};
|