core-outline
Version:
A React component for Core&Outline
441 lines (406 loc) • 13.1 kB
JavaScript
import React, { use, useEffect, useRef, useState } from 'react';
import { record } from 'rrweb';
import { v4 } from 'uuid';
import socket from './socket';
import {
EventType,
eventWithTime,
IncrementalSource,
MouseInteractions,
} from 'rrweb';
const CoreOutline = ({ children, data_source_id, data_source_secret }) => {
const [events, setEvents] = useState([]);
const [replayEvents, setReplayEvents] = useState([]);
const recorderActive = useRef(false);
const [currentPage, setCurrentPage] = useState(window.location.href);
const [location, setLocation] = useState({ latitude: null, longitude: null });
const [browser, setBrowser] = useState(getBrowserInfo());
const [appToken, setAppToken] = useState('');
const [sessionId, setSessionId] = useState(v4());
async function authorizeApp(data_source_id, data_source_secret) {
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
data_source_id: data_source_id,
data_source_secret: data_source_secret,
}),
};
const response = fetch(
`http://api.coreoutline.com/data-source/authorize`,
requestOptions
)
.then((response) => response.json())
.then((data) => {
setAppToken(data.app_token);
return data;
})
.catch((error) => {
console.error('Error:', error);
return error;
});
return response;
}
const offLoadFunctionality = () => {
const formattedEvents = events;
const rawEvents = replayEvents;
const data = JSON.stringify({
data_source_id: data_source_id,
sessionId: sessionId,
formatted_events: formattedEvents,
raw_events: rawEvents,
});
socket.send({
topic: 'session-data',
data_source_id: data_source_id,
session_id: localStorage.getItem('coreOutlineSessionId'),
start_date: new Date().toLocaleString(),
latitude: location.latitude,
longitude: location.longitude,
browser: browser,
event_id: 'SESSION_END',
});
if (navigator.sendBeacon) {
navigator.sendBeacon(`http://data.coreoutline.com/upload-events`, data);
} else {
fetch(`http://data.coreoutline.com/upload-events`, {
method: 'POST',
body: data,
keepalive: true,
headers: {
'Content-Type': 'application/json',
},
}).catch((err) => console.error('Upload failed:', err));
}
};
useEffect(() => {}, [events, replayEvents]);
useEffect(async () => {
const handleBeforeUnload = () => {
offLoadFunctionality();
};
const handleVisibilityChange = () => {
if (document.visibilityState === 'hidden') {
offLoadFunctionality();
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
document.addEventListener('visibilitychange', handleVisibilityChange);
await authorizeApp(data_source_id, data_source_secret);
localStorage.setItem('coreOutlineSessionId', sessionId);
localStorage.setItem('coreOutlineDataSourceIdId', data_source_id);
console.log('Session', sessionId);
socket.send({
topic: 'session-data',
data_source_id: data_source_id,
session_id: localStorage.getItem('coreOutlineSessionId'),
start_date: new Date().toLocaleString(),
latitude: location.latitude,
longitude: location.longitude,
browser: browser,
event_id: 'SESSION_START',
});
return () => {
offLoadFunctionality();
window.removeEventListener('beforeunload', handleBeforeUnload);
document.removeEventListener('visibilitychange', handleVisibilityChange);
socket.send({
topic: 'session-data',
data_source_id: data_source_id,
session_id: localStorage.getItem('coreOutlineSessionId'),
start_date: new Date().toLocaleString(),
latitude: location.latitude,
longitude: location.longitude,
browser: browser,
event_id: 'SESSION_END',
});
};
}, []);
useEffect(() => {
const navigationEvent = {
type: 4,
data: {
href: window.location.href,
width: 1536,
height: 730,
},
timestamp: Date.now(),
};
const startTime = performance.now();
const observer = new MutationObserver(() => {
const endTime = performance.now();
const loadTime = endTime - startTime;
navigationEvent.data.loadTime = loadTime;
const hours = Math.floor(loadTime / 3600000);
const minutes = Math.floor((loadTime % 3600000) / 60000);
const seconds = Math.floor((loadTime % 60000) / 1000);
navigationEvent.data.loadTimeFormatted = `${hours} hours ${minutes} minutes ${seconds} seconds`;
observer.disconnect();
});
observer.observe(document, { childList: true, subtree: true });
setEvents((prevEvents) => [...prevEvents, navigationEvent]);
localStorage.setItem(
'coreoutlineFormattedEvents',
JSON.stringify((prevEvents) => [...prevEvents, navigationEvent])
);
const handleNavigation = () => {
setCurrentPage(window.location.href);
socket.send({
topic: 'session-data',
data_source_id: data_source_id,
session_id: localStorage.getItem('coreOutlineSessionId'),
start_date: new Date().toLocaleString(),
latitude: location.latitude,
longitude: location.longitude,
browser: browser,
page_name: window.location.href,
event_id: 'PAGE_NAVIGATION',
});
};
window.addEventListener('popstate', handleNavigation);
const originalPushState = window.history.pushState;
window.history.pushState = function (...args) {
originalPushState.apply(this, args);
handleNavigation();
};
return () => {
window.removeEventListener('popstate', handleNavigation);
window.history.pushState = originalPushState;
};
}, [currentPage]);
useEffect(() => {
console.log('Location', location);
getLocation();
}, []);
useEffect(() => {
let stopFn;
if (!recorderActive.current) {
stopFn = record({
emit(event) {
setReplayEvents((prevEvents) => [...prevEvents, event]);
localStorage.setItem(
'coreoutlineRawEvents',
JSON.stringify((prevEvents) => [...replayEvents, event])
);
},
maskAllInputs: false,
maskTextSelector: null,
});
recorderActive.current = true;
}
return () => {
if (stopFn) {
stopFn();
recorderActive.current = false;
}
};
}, []);
useEffect(() => {
let stopFn;
if (!recorderActive.current) {
stopFn = record({
emit(event) {
console.log('Event:', event);
if (event.type == 3 && event.data.type == 2) {
return;
}
if (event.type == 4) {
return;
}
if (event.type == 3 && event.data.source == 0) {
return;
}
if (event.type == 3 && event.data.source == 1) {
return;
}
if (event.type == 3 && event.data.type == 3) {
console.log('Contextmenu Event:', event);
}
setEvents((prevEvents) => [...prevEvents, event]);
localStorage.setItem(
'coreoutlineFormattedEvents',
JSON.stringify((prevEvents) => [...prevEvents, event])
);
},
maskAllInputs: false,
maskTextSelector: null,
});
recorderActive.current = true;
}
return () => {
if (stopFn) {
stopFn();
recorderActive.current = false;
}
};
}, []);
useEffect(() => {
const handleClick = (event) => {
console.log('Inner text:', event.target.innerText);
const clickEvent = {
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.MouseInteraction,
type: MouseInteractions.Click,
id: event.target.id,
x: event.clientX,
y: event.clientY,
},
timestamp: Date.now(),
};
const clickedElement = document.elementFromPoint(
clickEvent.data.x,
clickEvent.data.y
);
if (clickedElement) {
clickEvent.data.metadata = {
label:
clickedElement.getAttribute('data-label') ||
clickedElement.innerText ||
null,
value:
clickedElement.getAttribute('value') ||
clickedElement.innerText ||
null,
id:
clickedElement.getAttribute('id') ||
clickedElement.innerText ||
null,
name:
clickedElement.getAttribute('name') ||
clickedElement.innerText ||
null,
class:
clickedElement.getAttribute('class') ||
clickedElement.innerText ||
null,
tag: clickedElement.tagName,
html: String(clickedElement.getHTML()),
innerText: event.target.innerText,
};
}
setEvents((prevEvents) => [...prevEvents, clickEvent]);
localStorage.setItem(
'coreoutlineFormattedEvents',
JSON.stringify((prevEvents) => [...prevEvents, clickEvent])
);
socket.send({
topic: 'session-data',
data_source_id: data_source_id,
session_id: localStorage.getItem('coreOutlineSessionId'),
start_date: new Date().toLocaleString(),
latitude: location.latitude,
longitude: location.longitude,
browser: browser,
event_id: 'ITEM_CLICKED',
// item_clicked: event.target,
});
};
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
}, []);
function saveEventsLocally(type = 'formatted' || 'raw') {
let blob = '';
if (type === 'formatted') {
blob = new Blob([JSON.stringify(events, null, 2)], {
type: 'application/json',
});
} else if (type === 'raw') {
blob = new Blob([JSON.stringify(replayEvents, null, 2)], {
type: 'application/json',
});
}
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${type}_events.json`;
a.click();
URL.revokeObjectURL(url);
}
function getBrowserInfo() {
const userAgent = navigator.userAgent;
let browserName = 'Unknown';
if (userAgent.indexOf('Firefox') > -1) {
browserName = 'Firefox';
} else if (
userAgent.indexOf('Opera') > -1 ||
userAgent.indexOf('OPR') > -1
) {
browserName = 'Opera';
} else if (userAgent.indexOf('Chrome') > -1) {
browserName = 'Chrome';
} else if (userAgent.indexOf('Safari') > -1) {
browserName = 'Safari';
} else if (
userAgent.indexOf('MSIE') > -1 ||
userAgent.indexOf('Trident/') > -1
) {
browserName = 'Internet Explorer';
}
return browserName;
}
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
});
},
(error) => {
console.error('Error getting location:', error);
}
);
} else {
console.error('Geolocation is not supported by this browser.');
}
}
return (
<div>
<button
onClick={() => {
saveEventsLocally('formatted');
saveEventsLocally('raw');
}}
>
Save Events Locally
</button>
{children}
</div>
);
};
export const flag_item_clicked = (item_id) => {
const sessionId = localStorage.getItem('coreOutlineSessionId');
const dataSourceId = localStorage.getItem('coreOutlineDataSourceIdId');
console.log('Session', sessionId);
console.log('Data Source', dataSourceId);
socket.send({
topic: 'session-data',
data_source_id: dataSourceId,
session_id: localStorage.getItem('coreOutlineSessionId'),
start_date: new Date().toLocaleString(),
event_id: 'PRODUCT_CLICKED',
item_clicked: item_id,
});
return { status: 'success', message: 'Item click flagged successfully.' };
};
export const flag_item_purchased = (item_id) => {
const sessionId = localStorage.getItem('coreOutlineSessionId');
const dataSourceId = localStorage.getItem('coreOutlineDataSourceIdId');
console.log('Session', sessionId);
console.log('Data Source', dataSourceId);
socket.send({
topic: 'session-data',
data_source_id: dataSourceId,
session_id: localStorage.getItem('coreOutlineSessionId'),
start_date: new Date().toLocaleString(),
event_id: 'PRODUCT_PURCHASED',
item_clicked: item_id,
});
return { status: 'success', message: 'Item purchase flagged successfully.' };
};
export default CoreOutline;