core-outline 1.1.3 → 1.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-outline",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "A React component for Core&Outline",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.es.js",
@@ -46,6 +46,7 @@
46
46
  "dependencies": {
47
47
  "react-error-boundary": "^5.0.0",
48
48
  "react-tracking": "^9.3.2",
49
- "rrweb": "^2.0.0-alpha.4"
49
+ "rrweb": "^2.0.0-alpha.4",
50
+ "socket.io-client": "^4.8.1"
50
51
  }
51
52
  }
@@ -1,15 +1,118 @@
1
1
  import React, { useEffect, useRef, useState } from 'react';
2
2
  import { record } from 'rrweb';
3
+ import { v4 } from 'uuid';
4
+ import socket from './socket';
3
5
 
4
- const CoreOutline = ({ children }) => {
6
+ import {
7
+ EventType,
8
+ eventWithTime,
9
+ IncrementalSource,
10
+ MouseInteractions,
11
+ } from 'rrweb';
12
+
13
+ const CoreOutline = ({ children, data_source_id, data_source_secret }) => {
5
14
  const [events, setEvents] = useState([]);
15
+ const [replayEvents, setReplayEvents] = useState([]);
6
16
  const recorderActive = useRef(false);
7
17
  const [currentPage, setCurrentPage] = useState(window.location.href);
18
+ const [location, setLocation] = useState({ latitude: null, longitude: null });
19
+ const [browser, setBrowser] = useState(getBrowserInfo());
20
+ const [appToken, setAppToken] = useState('');
21
+ const [sessionId, setSessionId] = useState('');
22
+
23
+ async function authorizeApp(data_source_id, data_source_secret) {
24
+ const requestOptions = {
25
+ method: 'POST',
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ },
29
+ body: JSON.stringify({
30
+ data_source_id: data_source_id,
31
+ data_source_secret: data_source_secret,
32
+ }),
33
+ };
34
+ const response = fetch(
35
+ `http://localhost:4000/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
+ useEffect(async () => {
50
+ await authorizeApp(data_source_id, data_source_secret);
51
+ setSessionId(v4());
52
+ localStorage.setItem('coreOutlineSessionId', sessionId);
53
+ localStorage.setItem('coreOutlineDataSourceIdId', data_source_id);
54
+ console.log('Session', sessionId);
55
+ socket.send({
56
+ topic: 'session-data',
57
+ data_source_id: data_source_id,
58
+ session_id: sessionId,
59
+ start_date: new Date().toLocaleString(),
60
+ latitude: location.latitude,
61
+ longitude: location.longitude,
62
+ browser: browser,
63
+ event_id: 'SESSION_START',
64
+ });
65
+
66
+ return () => {
67
+ socket.send({
68
+ topic: 'session-data',
69
+ data_source_id: data_source_id,
70
+ session_id: sessionId,
71
+ start_date: new Date().toLocaleString(),
72
+ latitude: location.latitude,
73
+ longitude: location.longitude,
74
+ browser: browser,
75
+ event_id: 'SESSION_END',
76
+ });
77
+ };
78
+ }, []);
8
79
 
9
80
  useEffect(() => {
10
- console.log(currentPage);
81
+ const navigationEvent = {
82
+ type: 4,
83
+ data: {
84
+ href: window.location.href,
85
+ width: 1536,
86
+ height: 730,
87
+ },
88
+ timestamp: Date.now(),
89
+ };
90
+ const startTime = performance.now();
91
+ const observer = new MutationObserver(() => {
92
+ const endTime = performance.now();
93
+ const loadTime = endTime - startTime;
94
+ navigationEvent.data.loadTime = loadTime;
95
+ const hours = Math.floor(loadTime / 3600000);
96
+ const minutes = Math.floor((loadTime % 3600000) / 60000);
97
+ const seconds = Math.floor((loadTime % 60000) / 1000);
98
+ navigationEvent.data.loadTimeFormatted = `${hours} hours ${minutes} minutes ${seconds} seconds`;
99
+ observer.disconnect();
100
+ });
101
+ observer.observe(document, { childList: true, subtree: true });
102
+ setEvents((prevEvents) => [...prevEvents, navigationEvent]);
11
103
  const handleNavigation = () => {
12
104
  setCurrentPage(window.location.href);
105
+ socket.send({
106
+ topic: 'session-data',
107
+ data_source_id: data_source_id,
108
+ session_id: sessionId,
109
+ start_date: new Date().toLocaleString(),
110
+ latitude: location.latitude,
111
+ longitude: location.longitude,
112
+ browser: browser,
113
+ page_name: window.location.href,
114
+ event_id: 'PAGE_NAVIGATION',
115
+ });
13
116
  saveEvents(events);
14
117
  };
15
118
 
@@ -26,13 +129,60 @@ const CoreOutline = ({ children }) => {
26
129
  };
27
130
  }, [currentPage]);
28
131
 
132
+ useEffect(() => {
133
+ console.log('Location', location);
134
+ getLocation();
135
+ }, []);
136
+
29
137
  useEffect(() => {
30
138
  let stopFn;
31
139
  if (!recorderActive.current) {
32
140
  stopFn = record({
33
141
  emit(event) {
142
+ setReplayEvents((prevEvents) => [...prevEvents, event]);
143
+ },
144
+ maskAllInputs: false,
145
+ maskTextSelector: null,
146
+ });
147
+
148
+ recorderActive.current = true;
149
+ }
150
+
151
+ return () => {
152
+ if (stopFn) {
153
+ stopFn();
154
+ saveEvents(events);
155
+
156
+ recorderActive.current = false;
157
+ }
158
+ };
159
+ }, []);
160
+
161
+ useEffect(() => {
162
+ let stopFn;
163
+ if (!recorderActive.current) {
164
+ stopFn = record({
165
+ emit(event) {
166
+ console.log('Event:', event);
167
+ if (event.type == 3 && event.data.type == 2) {
168
+ return;
169
+ }
170
+ if (event.type == 4) {
171
+ return;
172
+ }
173
+ if (event.type == 3 && event.data.source == 0) {
174
+ return;
175
+ }
176
+ if (event.type == 3 && event.data.source == 1) {
177
+ return;
178
+ }
179
+ if (event.type == 3 && event.data.type == 3) {
180
+ console.log('Contextmenu Event:', event);
181
+ }
34
182
  setEvents((prevEvents) => [...prevEvents, event]);
35
183
  },
184
+ maskAllInputs: false,
185
+ maskTextSelector: null,
36
186
  });
37
187
 
38
188
  recorderActive.current = true;
@@ -48,6 +198,74 @@ const CoreOutline = ({ children }) => {
48
198
  };
49
199
  }, []);
50
200
 
201
+ useEffect(() => {
202
+ const handleClick = (event) => {
203
+ console.log('Inner text:', event.target.innerText);
204
+ const clickEvent = {
205
+ type: EventType.IncrementalSnapshot,
206
+ data: {
207
+ source: IncrementalSource.MouseInteraction,
208
+ type: MouseInteractions.Click,
209
+ id: event.target.id,
210
+ x: event.clientX,
211
+ y: event.clientY,
212
+ },
213
+ timestamp: Date.now(),
214
+ };
215
+ const clickedElement = document.elementFromPoint(
216
+ clickEvent.data.x,
217
+ clickEvent.data.y
218
+ );
219
+
220
+ if (clickedElement) {
221
+ clickEvent.data.metadata = {
222
+ label:
223
+ clickedElement.getAttribute('data-label') ||
224
+ clickedElement.innerText ||
225
+ null,
226
+ value:
227
+ clickedElement.getAttribute('value') ||
228
+ clickedElement.innerText ||
229
+ null,
230
+ id:
231
+ clickedElement.getAttribute('id') ||
232
+ clickedElement.innerText ||
233
+ null,
234
+ name:
235
+ clickedElement.getAttribute('name') ||
236
+ clickedElement.innerText ||
237
+ null,
238
+ class:
239
+ clickedElement.getAttribute('class') ||
240
+ clickedElement.innerText ||
241
+ null,
242
+ tag: clickedElement.tagName,
243
+ html: String(clickedElement.getHTML()),
244
+ innerText: event.target.innerText,
245
+ };
246
+ }
247
+ setEvents((prevEvents) => [...prevEvents, clickEvent]);
248
+
249
+ socket.send({
250
+ topic: 'session-data',
251
+ data_source_id: data_source_id,
252
+ session_id: sessionId,
253
+ start_date: new Date().toLocaleString(),
254
+ latitude: location.latitude,
255
+ longitude: location.longitude,
256
+ browser: browser,
257
+ event_id: 'ITEM_CLICKED',
258
+ // item_clicked: event.target,
259
+ });
260
+ };
261
+
262
+ document.addEventListener('click', handleClick);
263
+
264
+ return () => {
265
+ document.removeEventListener('click', handleClick);
266
+ };
267
+ }, []);
268
+
51
269
  function saveEvents(events) {
52
270
  const requestOptions = {
53
271
  method: 'PUT',
@@ -56,8 +274,9 @@ const CoreOutline = ({ children }) => {
56
274
  },
57
275
  body: JSON.stringify(events),
58
276
  };
59
-
60
- fetch('http://localhost:8000/generate-s3-url')
277
+ fetch(
278
+ `http://localhost:5000/generate-s3-url/${data_source_id}/${sessionId}`
279
+ )
61
280
  .then((response) => response.json())
62
281
  .then((data) => {
63
282
  const uploadURL = data.signed_url;
@@ -65,7 +284,113 @@ const CoreOutline = ({ children }) => {
65
284
  });
66
285
  }
67
286
 
68
- return <div>{children}</div>;
287
+ function saveEventsLocally(type = 'formatted' || 'raw') {
288
+ let blob = '';
289
+ if (type === 'formatted') {
290
+ blob = new Blob([JSON.stringify(events, null, 2)], {
291
+ type: 'application/json',
292
+ });
293
+ } else if (type === 'raw') {
294
+ blob = new Blob([JSON.stringify(replayEvents, null, 2)], {
295
+ type: 'application/json',
296
+ });
297
+ }
298
+ const url = URL.createObjectURL(blob);
299
+ const a = document.createElement('a');
300
+ a.href = url;
301
+ a.download = `${type}_events.json`;
302
+ a.click();
303
+ URL.revokeObjectURL(url);
304
+ }
305
+
306
+ function getBrowserInfo() {
307
+ const userAgent = navigator.userAgent;
308
+ let browserName = 'Unknown';
309
+
310
+ if (userAgent.indexOf('Firefox') > -1) {
311
+ browserName = 'Firefox';
312
+ } else if (
313
+ userAgent.indexOf('Opera') > -1 ||
314
+ userAgent.indexOf('OPR') > -1
315
+ ) {
316
+ browserName = 'Opera';
317
+ } else if (userAgent.indexOf('Chrome') > -1) {
318
+ browserName = 'Chrome';
319
+ } else if (userAgent.indexOf('Safari') > -1) {
320
+ browserName = 'Safari';
321
+ } else if (
322
+ userAgent.indexOf('MSIE') > -1 ||
323
+ userAgent.indexOf('Trident/') > -1
324
+ ) {
325
+ browserName = 'Internet Explorer';
326
+ }
327
+
328
+ return browserName;
329
+ }
330
+
331
+ function getLocation() {
332
+ if (navigator.geolocation) {
333
+ navigator.geolocation.getCurrentPosition(
334
+ (position) => {
335
+ setLocation({
336
+ latitude: position.coords.latitude,
337
+ longitude: position.coords.longitude,
338
+ });
339
+ },
340
+ (error) => {
341
+ console.error('Error getting location:', error);
342
+ }
343
+ );
344
+ } else {
345
+ console.error('Geolocation is not supported by this browser.');
346
+ }
347
+ }
348
+
349
+ return (
350
+ <div>
351
+ <button
352
+ onClick={() => {
353
+ saveEventsLocally('formatted');
354
+ saveEventsLocally('raw');
355
+ }}
356
+ >
357
+ Save Events Locally
358
+ </button>
359
+ {children}
360
+ </div>
361
+ );
362
+ };
363
+
364
+ export const flag_item_clicked = (item_id) => {
365
+ const sessionId = localStorage.getItem('coreOutlineSessionId');
366
+ const dataSourceId = localStorage.getItem('coreOutlineDataSourceIdId');
367
+ console.log('Session', sessionId);
368
+ console.log('Data Source', dataSourceId);
369
+ socket.send({
370
+ topic: 'session-data',
371
+ data_source_id: dataSourceId,
372
+ session_id: sessionId,
373
+ start_date: new Date().toLocaleString(),
374
+ event_id: 'PRODUCT_CLICKED',
375
+ item_clicked: item_id,
376
+ });
377
+ return { status: 'success', message: 'Item click flagged successfully.' };
378
+ };
379
+
380
+ export const flag_item_purchased = (item_id) => {
381
+ const sessionId = localStorage.getItem('coreOutlineSessionId');
382
+ const dataSourceId = localStorage.getItem('coreOutlineDataSourceIdId');
383
+ console.log('Session', sessionId);
384
+ console.log('Data Source', dataSourceId);
385
+ socket.send({
386
+ topic: 'session-data',
387
+ data_source_id: dataSourceId,
388
+ session_id: sessionId,
389
+ start_date: new Date().toLocaleString(),
390
+ event_id: 'PRODUCT_PURCHASED',
391
+ item_clicked: item_id,
392
+ });
393
+ return { status: 'success', message: 'Item purchase flagged successfully.' };
69
394
  };
70
395
 
71
396
  export default CoreOutline;
@@ -0,0 +1,3 @@
1
+ import { io } from 'socket.io-client';
2
+ const socket = io('http://localhost:8000');
3
+ export default socket;