import React, { useState, useRef, useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { toast } from 'react-toastify'; interface SessionRecordingProps { onSessionStart?: (sessionId: string) => void; onSessionEnd?: (sessionId: string, reportPath: string) => void; } export const SessionRecording: React.FC = ({ onSessionStart, onSessionEnd }) => { const [isRecording, setIsRecording] = useState(false); const [sessionId, setSessionId] = useState(null); const [recordingTime, setRecordingTime] = useState(0); const [screenshots, setScreenshots] = useState([]); const [isProcessing, setIsProcessing] = useState(false); const mediaRecorderRef = useRef(null); const streamRef = useRef(null); const recordedChunksRef = useRef([]); const timerRef = useRef(null); const navigate = useNavigate(); useEffect(() => { return () => { if (timerRef.current) { clearInterval(timerRef.current); } if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); } }; }, []); const startRecording = useCallback(async () => { try { // Coletar informações de contexto const metadata = await collectContextMetadata(); // Criar sessão no backend const response = await fetch('/api/sessions/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(metadata) }); if (!response.ok) { throw new Error('Erro ao criar sessão'); } const { sessionId: newSessionId } = await response.json(); setSessionId(newSessionId); // Solicitar captura de tela const stream = await navigator.mediaDevices.getDisplayMedia({ video: { mediaSource: 'screen' }, audio: true }); streamRef.current = stream; // Configurar MediaRecorder const mediaRecorder = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9' }); mediaRecorderRef.current = mediaRecorder; recordedChunksRef.current = []; mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { recordedChunksRef.current.push(event.data); } }; mediaRecorder.onstop = () => { handleRecordingStop(newSessionId); }; // Iniciar gravação mediaRecorder.start(1000); // Gravar em chunks de 1 segundo setIsRecording(true); setRecordingTime(0); // Iniciar timer timerRef.current = setInterval(() => { setRecordingTime(prev => prev + 1); }, 1000); // Overlay de informações na tela createScreenOverlay(metadata); onSessionStart?.(newSessionId); toast.success('Gravação de sessão iniciada!'); } catch (error) { console.error('Erro ao iniciar gravação:', error); toast.error('Erro ao iniciar gravação: ' + error.message); } }, [onSessionStart]); const stopRecording = useCallback(() => { if (mediaRecorderRef.current && isRecording) { mediaRecorderRef.current.stop(); setIsRecording(false); if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } if (streamRef.current) { streamRef.current.getTracks().forEach(track => track.stop()); } removeScreenOverlay(); } }, [isRecording]); const takeScreenshot = useCallback(async () => { if (!sessionId || !streamRef.current) { toast.error('Sessão não ativa'); return; } try { // Capturar screenshot usando Canvas const canvas = document.createElement('canvas'); const video = document.createElement('video'); video.srcObject = streamRef.current; video.play(); video.onloadedmetadata = async () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext('2d'); ctx?.drawImage(video, 0, 0); // Converter para base64 const screenshotData = canvas.toDataURL('image/png'); // Enviar para o backend const response = await fetch(`/api/sessions/${sessionId}/screenshot`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ screenshot: screenshotData }) }); if (response.ok) { const { filename } = await response.json(); setScreenshots(prev => [...prev, filename]); toast.success('Screenshot capturado!'); } }; } catch (error) { console.error('Erro ao capturar screenshot:', error); toast.error('Erro ao capturar screenshot'); } }, [sessionId]); const handleRecordingStop = async (sessionId: string) => { try { setIsProcessing(true); // Combinar chunks do vídeo const blob = new Blob(recordedChunksRef.current, { type: 'video/webm' }); // Enviar vídeo para o backend const formData = new FormData(); formData.append('video', blob, `session_${sessionId}.webm`); const uploadResponse = await fetch(`/api/sessions/${sessionId}/video`, { method: 'POST', body: formData }); if (!uploadResponse.ok) { throw new Error('Erro ao enviar vídeo'); } // Finalizar sessão e gerar relatório const finalizeResponse = await fetch(`/api/sessions/${sessionId}/finalize`, { method: 'POST' }); if (!finalizeResponse.ok) { throw new Error('Erro ao finalizar sessão'); } const { reportPath } = await finalizeResponse.json(); onSessionEnd?.(sessionId, reportPath); toast.success('Sessão finalizada e relatório gerado!'); // Navegar para página de resultados navigate(`/sessions/report/${sessionId}`); } catch (error) { console.error('Erro ao processar gravação:', error); toast.error('Erro ao processar gravação: ' + error.message); } finally { setIsProcessing(false); } }; const formatTime = (seconds: number): string => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); const secs = seconds % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; return (

Gravação de Sessão de Coleta de Prova

{/* Status da gravação */}
{isRecording ? 'Gravando' : isProcessing ? 'Processando...' : 'Pronto para gravar'} {isRecording && ( {formatTime(recordingTime)} )}
{sessionId && ( Sessão: {sessionId} )}
{/* Controles */}
{!isRecording && !isProcessing && ( )} {isRecording && ( <> )}
{/* Informações da sessão */} {(isRecording || screenshots.length > 0) && (

Informações da Sessão

Screenshots capturados: {screenshots.length}
Tempo de gravação: {formatTime(recordingTime)}
)} {/* Lista de screenshots */} {screenshots.length > 0 && (

Screenshots Capturados

{screenshots.map((screenshot, index) => (
{index + 1}. {screenshot}
))}
)} {isProcessing && (

Processando gravação e gerando relatório...

)}
); }; // Função para coletar metadados de contexto async function collectContextMetadata() { const getLocation = (): Promise => { return new Promise((resolve) => { if (!navigator.geolocation) { resolve(null); return; } navigator.geolocation.getCurrentPosition( (position) => resolve(position), () => resolve(null), { timeout: 5000 } ); }); }; const location = await getLocation(); return { userAgent: navigator.userAgent, language: navigator.language, platform: navigator.platform, cookieEnabled: navigator.cookieEnabled, onlineStatus: navigator.onLine, screenResolution: `${screen.width}x${screen.height}`, viewportSize: `${window.innerWidth}x${window.innerHeight}`, timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, geolocation: location ? { latitude: location.coords.latitude, longitude: location.coords.longitude, accuracy: location.coords.accuracy } : null, timestamp: new Date().toISOString(), url: window.location.href, referrer: document.referrer }; } // Função para criar overlay na tela function createScreenOverlay(metadata: any) { const overlay = document.createElement('div'); overlay.id = 'recording-overlay'; overlay.style.cssText = ` position: fixed; top: 10px; right: 10px; background: rgba(0, 0, 0, 0.8); color: white; padding: 10px; border-radius: 5px; font-family: monospace; font-size: 12px; z-index: 10000; max-width: 300px; `; overlay.innerHTML = `
🔴 GRAVAÇÃO ATIVA
IP: ${metadata.ipAddress || 'Coletando...'}
Timestamp: ${new Date().toLocaleString()}
User-Agent: ${metadata.userAgent.substring(0, 50)}...
`; document.body.appendChild(overlay); } // Função para remover overlay function removeScreenOverlay() { const overlay = document.getElementById('recording-overlay'); if (overlay) { overlay.remove(); } } export default SessionRecording;