core-outline 1.1.4 → 1.1.6

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.
@@ -1,48 +1,109 @@
1
- import React, { useEffect, useRef, useState } from "react";
2
- import { record } from "rrweb";
3
- import { v4 } from "uuid";
4
- import io from "socket.io-client";
5
- const socket = io("http://localhost:4000");
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { record } from 'rrweb';
3
+ import { v4 } from 'uuid';
4
+ import socket from './socket';
6
5
 
7
- const CoreOutline = ({ children, data_source_id }) => {
6
+ import {
7
+ EventType,
8
+ eventWithTime,
9
+ IncrementalSource,
10
+ MouseInteractions,
11
+ } from 'rrweb';
12
+
13
+ const CoreOutline = ({ children, data_source_id, data_source_secret }) => {
8
14
  const [events, setEvents] = useState([]);
15
+ const [replayEvents, setReplayEvents] = useState([]);
9
16
  const recorderActive = useRef(false);
10
17
  const [currentPage, setCurrentPage] = useState(window.location.href);
11
18
  const [location, setLocation] = useState({ latitude: null, longitude: null });
12
19
  const [browser, setBrowser] = useState(getBrowserInfo());
13
- const sessionId = v4();
20
+ const [appToken, setAppToken] = useState('');
21
+ const [sessionId, setSessionId] = useState('');
14
22
 
15
- useEffect(() => {
16
- socket.emit("dataEvent", {
17
- topic: "session-data",
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',
18
57
  data_source_id: data_source_id,
19
58
  session_id: sessionId,
20
59
  start_date: new Date().toLocaleString(),
21
60
  latitude: location.latitude,
22
61
  longitude: location.longitude,
23
62
  browser: browser,
24
- event_id: "SESSION_START",
63
+ event_id: 'SESSION_START',
25
64
  });
26
65
 
27
66
  return () => {
28
- socket.emit("dataEvent", {
29
- topic: "session-data",
67
+ socket.send({
68
+ topic: 'session-data',
30
69
  data_source_id: data_source_id,
31
70
  session_id: sessionId,
32
71
  start_date: new Date().toLocaleString(),
33
72
  latitude: location.latitude,
34
73
  longitude: location.longitude,
35
74
  browser: browser,
36
- event_id: "SESSION_END",
75
+ event_id: 'SESSION_END',
37
76
  });
38
77
  };
39
78
  }, []);
40
79
 
41
80
  useEffect(() => {
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]);
42
103
  const handleNavigation = () => {
43
104
  setCurrentPage(window.location.href);
44
- socket.emit("dataEvent", {
45
- topic: "session-data",
105
+ socket.send({
106
+ topic: 'session-data',
46
107
  data_source_id: data_source_id,
47
108
  session_id: sessionId,
48
109
  start_date: new Date().toLocaleString(),
@@ -50,12 +111,12 @@ const CoreOutline = ({ children, data_source_id }) => {
50
111
  longitude: location.longitude,
51
112
  browser: browser,
52
113
  page_name: window.location.href,
53
- event_id: "PAGE_NAVIGATION",
114
+ event_id: 'PAGE_NAVIGATION',
54
115
  });
55
116
  saveEvents(events);
56
117
  };
57
118
 
58
- window.addEventListener("popstate", handleNavigation);
119
+ window.addEventListener('popstate', handleNavigation);
59
120
  const originalPushState = window.history.pushState;
60
121
  window.history.pushState = function (...args) {
61
122
  originalPushState.apply(this, args);
@@ -63,13 +124,13 @@ const CoreOutline = ({ children, data_source_id }) => {
63
124
  };
64
125
 
65
126
  return () => {
66
- window.removeEventListener("popstate", handleNavigation);
127
+ window.removeEventListener('popstate', handleNavigation);
67
128
  window.history.pushState = originalPushState;
68
129
  };
69
130
  }, [currentPage]);
70
131
 
71
132
  useEffect(() => {
72
- console.log("Location", location);
133
+ console.log('Location', location);
73
134
  getLocation();
74
135
  }, []);
75
136
 
@@ -78,8 +139,50 @@ const CoreOutline = ({ children, data_source_id }) => {
78
139
  if (!recorderActive.current) {
79
140
  stopFn = record({
80
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
+ }
81
182
  setEvents((prevEvents) => [...prevEvents, event]);
82
183
  },
184
+ maskAllInputs: false,
185
+ maskTextSelector: null,
83
186
  });
84
187
 
85
188
  recorderActive.current = true;
@@ -97,38 +200,83 @@ const CoreOutline = ({ children, data_source_id }) => {
97
200
 
98
201
  useEffect(() => {
99
202
  const handleClick = (event) => {
100
- console.log("Button clicked:", event.target.id);
101
- // const itemId = event.target.id;
102
- socket.emit("dataEvent", {
103
- topic: "session-data",
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',
104
251
  data_source_id: data_source_id,
105
252
  session_id: sessionId,
106
253
  start_date: new Date().toLocaleString(),
107
254
  latitude: location.latitude,
108
255
  longitude: location.longitude,
109
256
  browser: browser,
110
- event_id: "ITEM_CLICKED",
111
- item_clicked: event.target,
257
+ event_id: 'ITEM_CLICKED',
258
+ // item_clicked: event.target,
112
259
  });
113
260
  };
114
261
 
115
- document.addEventListener("click", handleClick);
262
+ document.addEventListener('click', handleClick);
116
263
 
117
264
  return () => {
118
- document.removeEventListener("click", handleClick);
265
+ document.removeEventListener('click', handleClick);
119
266
  };
120
267
  }, []);
121
268
 
122
269
  function saveEvents(events) {
123
270
  const requestOptions = {
124
- method: "PUT",
271
+ method: 'PUT',
125
272
  headers: {
126
- "Content-Type": "application/json",
273
+ 'Content-Type': 'application/json',
127
274
  },
128
275
  body: JSON.stringify(events),
129
276
  };
130
-
131
- fetch(`http://localhost:8000/generate-s3-url/${data_source_id}/${sessionId}`)
277
+ fetch(
278
+ `http://localhost:5000/generate-s3-url/${data_source_id}/${sessionId}`
279
+ )
132
280
  .then((response) => response.json())
133
281
  .then((data) => {
134
282
  const uploadURL = data.signed_url;
@@ -136,31 +284,50 @@ const CoreOutline = ({ children, data_source_id }) => {
136
284
  });
137
285
  }
138
286
 
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
+
139
306
  function getBrowserInfo() {
140
307
  const userAgent = navigator.userAgent;
141
- let browserName = "Unknown";
142
-
143
- if (userAgent.indexOf("Firefox") > -1) {
144
- browserName = "Firefox";
145
- } else if (userAgent.indexOf("Opera") > -1 || userAgent.indexOf("OPR") > -1) {
146
- browserName = "Opera";
147
- } else if (userAgent.indexOf("Chrome") > -1) {
148
- browserName = "Chrome";
149
- } else if (userAgent.indexOf("Safari") > -1) {
150
- browserName = "Safari";
151
- } else if (userAgent.indexOf("MSIE") > -1 || userAgent.indexOf("Trident/") > -1) {
152
- browserName = "Internet Explorer";
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';
153
326
  }
154
327
 
155
328
  return browserName;
156
329
  }
157
330
 
158
- useEffect(() => {
159
- if (events.length > 0) {
160
- socket.emit("record-event", { sessionId, events });
161
- }
162
- }, [events]);
163
-
164
331
  function getLocation() {
165
332
  if (navigator.geolocation) {
166
333
  navigator.geolocation.getCurrentPosition(
@@ -171,15 +338,59 @@ const CoreOutline = ({ children, data_source_id }) => {
171
338
  });
172
339
  },
173
340
  (error) => {
174
- console.error("Error getting location:", error);
341
+ console.error('Error getting location:', error);
175
342
  }
176
343
  );
177
344
  } else {
178
- console.error("Geolocation is not supported by this browser.");
345
+ console.error('Geolocation is not supported by this browser.');
179
346
  }
180
347
  }
181
348
 
182
- return <div>{children}</div>;
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.' };
183
394
  };
184
395
 
185
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;