UNPKG

core-outline

Version:

A React component for Core&Outline

441 lines (406 loc) 13.1 kB
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;