overcentric
Version:
A lightweight, privacy-focused toolkit for modern SaaS web applications
122 lines (121 loc) • 4.45 kB
JavaScript
import { record } from '@rrweb/record';
import { CONFIG, log } from ".";
import { getSessionId } from "./session";
// Default maximum recording duration in milliseconds (1 hour)
const DEFAULT_MAX_RECORDING_DURATION = 60 * 60 * 1000;
/**
* Initialize the session recording feature.
*/
async function initRecording(maxDuration = DEFAULT_MAX_RECORDING_DURATION) {
log.debug('Starting recording');
const sessionId = getSessionId();
const events = [];
const BATCH_SIZE = 100;
const recordingStartTime = Date.now();
let isRecordingStopped = false;
// Register the recording session
try {
const response = await fetch(`${CONFIG.basePath}/recordings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
session_id: sessionId,
project_id: window.overcentricProjectId,
context: window.overcentricContext,
hostname: window.location.hostname,
pathname: window.location.pathname
})
});
if (!response.ok) {
throw new Error(`Failed to register recording: ${response.status}`);
}
log.debug('Recording session registered');
}
catch (error) {
log.error('Failed to register recording session', error);
return; // Don't proceed with recording if registration fails
}
// Function to send recording chunks to backend
const sendRecordingEvents = async (eventsToSend) => {
if (!eventsToSend.length)
return;
try {
const response = await fetch(`${CONFIG.basePath}/recordings/${sessionId}/chunks`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
project_id: window.overcentricProjectId,
events: eventsToSend,
timestamp: Date.now()
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
log.debug(`Sent ${eventsToSend.length} recording events`);
}
catch (error) {
log.error('Failed to send recording events', error);
}
};
// Set up periodic flush of events
const flushInterval = setInterval(() => {
// Check if max recording duration has been reached
if (!isRecordingStopped && Date.now() - recordingStartTime >= maxDuration) {
log.debug(`Maximum recording duration of ${maxDuration / 1000} seconds reached, stopping recording`);
isRecordingStopped = true;
}
if (events.length > 0 && !isRecordingStopped) {
sendRecordingEvents(events.splice(0));
}
}, 5000); // Flush every 5 seconds
// Clean up on page unload
window.addEventListener('beforeunload', () => {
clearInterval(flushInterval);
if (events.length > 0 && !isRecordingStopped) {
sendRecordingEvents(events.splice(0));
}
});
// Function to manually stop recording
const stopRecording = () => {
if (!isRecordingStopped) {
isRecordingStopped = true;
clearInterval(flushInterval);
if (events.length > 0) {
sendRecordingEvents(events.splice(0));
}
if (stopFn)
stopFn();
log.debug('Recording stopped manually');
}
};
// Automatically stop recording after maxDuration
if (maxDuration > 0) {
setTimeout(stopRecording, maxDuration);
}
// Add stopRecording function to window for external access if needed
window.overcentricStopRecording = stopRecording;
const stopFn = record({
emit(event) {
// Only add events if we haven't reached the maximum duration
if (!isRecordingStopped) {
events.push(event);
// Send events when batch size is reached
if (events.length >= BATCH_SIZE) {
sendRecordingEvents(events.splice(0));
}
}
},
maskAllInputs: true,
sampling: {
mousemove: true,
scroll: 150,
input: 'last'
}
});
}
export { initRecording };