add webproof system
Signed-off-by: Rodrigo B. de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
1 parent
a63aaeb655
commit
8e75018a35
6 files changed
+665
No files matched your search
@@ -0,0 +1,106 @@
|
||||
// Adicionar ao schema.prisma existente (não substitui)
|
||||
model SessaoNavegacao {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
titulo String
|
||||
descricao String?
|
||||
status StatusSessao @default(INICIADA)
|
||||
usuarioId String @db.Uuid
|
||||
usuario Usuario @relation(fields: [usuarioId], references: [id])
|
||||
|
||||
// Dados da sessão
|
||||
sessionKey String @unique
|
||||
browserFingerprint Json
|
||||
inicioSessao DateTime @default(now())
|
||||
fimSessao DateTime?
|
||||
duracaoSegundos Int?
|
||||
|
||||
// Arquivos gerados
|
||||
videoPath String?
|
||||
screenshotsPaths String[]
|
||||
metadataPath String?
|
||||
relatorioPdfPath String?
|
||||
|
||||
// Hashes e validação
|
||||
checksumVideo String?
|
||||
checksumMetadata String?
|
||||
timestampProof String?
|
||||
|
||||
// Navegação registrada
|
||||
acoes AcaoNavegacao[]
|
||||
capturas CapturaNavegacao[]
|
||||
|
||||
criadoEm DateTime @default(now())
|
||||
atualizadoEm DateTime @updatedAt
|
||||
|
||||
@@map("sessoes_navegacao")
|
||||
}
|
||||
|
||||
model AcaoNavegacao {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
sessaoId String @db.Uuid
|
||||
sessao SessaoNavegacao @relation(fields: [sessaoId], references: [id], onDelete: Cascade)
|
||||
|
||||
timestamp DateTime
|
||||
tipo TipoAcao
|
||||
url String?
|
||||
elemento String?
|
||||
coordenadas Json?
|
||||
valor String?
|
||||
screenshot String?
|
||||
|
||||
metadados Json
|
||||
|
||||
criadoEm DateTime @default(now())
|
||||
|
||||
@@map("acoes_navegacao")
|
||||
}
|
||||
|
||||
model CapturaNavegacao {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
sessaoId String @db.Uuid
|
||||
sessao SessaoNavegacao @relation(fields: [sessaoId], references: [id], onDelete: Cascade)
|
||||
|
||||
timestamp DateTime
|
||||
url String
|
||||
titulo String?
|
||||
screenshotPath String
|
||||
htmlContent String?
|
||||
checksumHtml String?
|
||||
checksumScreenshot String
|
||||
|
||||
// Metadados da página
|
||||
networkRequests Json[]
|
||||
cookies Json[]
|
||||
localStorage Json?
|
||||
sessionStorage Json?
|
||||
|
||||
criadoEm DateTime @default(now())
|
||||
|
||||
@@map("capturas_navegacao")
|
||||
}
|
||||
|
||||
enum StatusSessao {
|
||||
INICIADA
|
||||
EM_ANDAMENTO
|
||||
FINALIZANDO
|
||||
CONCLUIDA
|
||||
ERRO
|
||||
CANCELADA
|
||||
}
|
||||
|
||||
enum TipoAcao {
|
||||
NAVEGACAO
|
||||
CLIQUE
|
||||
DIGITACAO
|
||||
SCROLL
|
||||
HOVER
|
||||
DOWNLOAD
|
||||
FORMULARIO
|
||||
POPUP
|
||||
REDIRECT
|
||||
}
|
||||
|
||||
// Adicionar relação ao modelo Usuario existente
|
||||
// Usuario {
|
||||
// sessoesNavegacao SessaoNavegacao[]
|
||||
// }
|
||||
@@ -49,6 +49,7 @@ import { NovoCalculo } from './components/calculos/NovoCalculo';
|
||||
import { DetalhesCalculo } from './components/calculos/DetalhesCalculo';
|
||||
import { NovoPensaoAlimenticia } from './components/calculos/NovoPensaoAlimenticia';
|
||||
import { DetalhePensaoAlimenticia } from './components/calculos/DetalhePensaoAlimenticia';
|
||||
import { IniciarNavegacao } from './components/navegacao/IniciarNavegacao';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@@ -126,6 +127,11 @@ const router = createBrowserRouter(
|
||||
<DetalhePensaoAlimenticia />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
<Route path="/navegacao/iniciar" element={
|
||||
<ProtectedRoute>
|
||||
<IniciarNavegacao />
|
||||
</ProtectedRoute>
|
||||
} />
|
||||
</Route>
|
||||
</Route>
|
||||
<Route path="*" element={<Navigate to="/login" />} />
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
Container,
|
||||
Card,
|
||||
CardContent,
|
||||
Typography,
|
||||
TextField,
|
||||
Button,
|
||||
Box,
|
||||
Alert,
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
|
||||
export const IniciarNavegacao: React.FC = () => {
|
||||
const [titulo, setTitulo] = useState('');
|
||||
const [descricao, setDescricao] = useState('');
|
||||
const [iniciando, setIniciando] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleIniciar = async () => {
|
||||
if (!titulo.trim()) {
|
||||
toast.error('Título é obrigatório');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIniciando(true);
|
||||
|
||||
const response = await fetch('/api/navegacao/iniciar', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({ titulo, descricao })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erro ao iniciar sessão');
|
||||
}
|
||||
|
||||
const resultado = await response.json();
|
||||
|
||||
toast.success('Sessão de navegação iniciada!');
|
||||
navigate(`/navegacao/sessao/${resultado.sessaoId}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erro:', error);
|
||||
toast.error('Erro ao iniciar sessão de navegação');
|
||||
} finally {
|
||||
setIniciando(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="md">
|
||||
<Box mt={4}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Nova Sessão de Coleta de Provas
|
||||
</Typography>
|
||||
|
||||
<Alert severity="info" sx={{ mb: 3 }}>
|
||||
<Typography variant="body2">
|
||||
Este sistema criará uma sessão controlada de navegação onde todas as ações
|
||||
serão gravadas e registradas para fins de coleta de provas digitais.
|
||||
</Typography>
|
||||
</Alert>
|
||||
|
||||
<Box component="form" sx={{ mt: 2 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Título da Sessão"
|
||||
value={titulo}
|
||||
onChange={(e) => setTitulo(e.target.value)}
|
||||
margin="normal"
|
||||
required
|
||||
helperText="Descreva brevemente o objetivo desta coleta"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Descrição (opcional)"
|
||||
value={descricao}
|
||||
onChange={(e) => setDescricao(e.target.value)}
|
||||
margin="normal"
|
||||
multiline
|
||||
rows={3}
|
||||
helperText="Informações adicionais sobre o que será coletado"
|
||||
/>
|
||||
|
||||
<Box mt={3} display="flex" gap={2}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleIniciar}
|
||||
disabled={iniciando || !titulo.trim()}
|
||||
startIcon={iniciando ? <CircularProgress size={20} /> : null}
|
||||
>
|
||||
{iniciando ? 'Iniciando...' : 'Iniciar Coleta'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => navigate('/navegacao')}
|
||||
>
|
||||
Cancelar
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
@@ -27,6 +27,7 @@ import indicesMonetariosRoutes from './routes/indicesMonetarios';
|
||||
import pensaoAlimenticiaRouter from './routes/pensaoAlimenticia'; // Importar router de pensão alimentícia
|
||||
import financeiroPainelRoutes from './routes/financeiroPainel'; // Importar rotas do financeiro painel
|
||||
import audienciasRoutes from './routes/audiencias'; // Importar rotas de audiências
|
||||
import navegacaoRoutes from './routes/navegacao'; // Adicionar esta importação
|
||||
|
||||
|
||||
const app = express();
|
||||
@@ -1482,3 +1483,4 @@ app.use('/api/indices', indicesMonetariosRoutes);
|
||||
app.use('/api/pensao-alimenticia', pensaoAlimenticiaRouter);
|
||||
app.use('/api/financeiro-painel', financeiroPainelRoutes); // Adicionar uso das rotas do financeiro painel
|
||||
app.use('/api/audiencias', audienciasRoutes); // Adicionar uso das rotas de audiências
|
||||
app.use('/api/navegacao', navegacaoRoutes); // Adicionar esta linha junto com as outras rotas
|
||||
@@ -0,0 +1,168 @@
|
||||
import { Router } from 'express';
|
||||
import { NavegacaoService } from '../services/NavegacaoService';
|
||||
import { authMiddleware } from '../../middleware/auth';
|
||||
import { prisma } from '../prismaClient';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Aplicar middleware de autenticação
|
||||
router.use(authMiddleware);
|
||||
|
||||
// Iniciar nova sessão de navegação
|
||||
router.post('/iniciar', async (req, res) => {
|
||||
try {
|
||||
const { titulo, descricao } = req.body;
|
||||
|
||||
if (!req.user?.id) {
|
||||
return res.status(401).json({ error: 'Usuário não autenticado' });
|
||||
}
|
||||
|
||||
const navegacaoService = new NavegacaoService();
|
||||
const resultado = await navegacaoService.iniciarSessao(
|
||||
req.user.id,
|
||||
titulo,
|
||||
descricao
|
||||
);
|
||||
|
||||
res.json(resultado);
|
||||
} catch (error) {
|
||||
console.error('Erro ao iniciar navegação:', error);
|
||||
res.status(500).json({
|
||||
error: 'Erro ao iniciar sessão de navegação',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Navegar para URL específica
|
||||
router.post('/:sessaoId/navegar', async (req, res) => {
|
||||
try {
|
||||
const { sessaoId } = req.params;
|
||||
const { url } = req.body;
|
||||
|
||||
// Verificar se a sessão pertence ao usuário
|
||||
const sessao = await prisma.sessaoNavegacao.findFirst({
|
||||
where: {
|
||||
id: sessaoId,
|
||||
usuarioId: req.user!.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!sessao) {
|
||||
return res.status(404).json({ error: 'Sessão não encontrada' });
|
||||
}
|
||||
|
||||
const navegacaoService = new NavegacaoService();
|
||||
await navegacaoService.navegarPara(url);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Erro na navegação:', error);
|
||||
res.status(500).json({ error: 'Erro ao navegar' });
|
||||
}
|
||||
});
|
||||
|
||||
// Finalizar sessão
|
||||
router.post('/:sessaoId/finalizar', async (req, res) => {
|
||||
try {
|
||||
const { sessaoId } = req.params;
|
||||
|
||||
const sessao = await prisma.sessaoNavegacao.findFirst({
|
||||
where: {
|
||||
id: sessaoId,
|
||||
usuarioId: req.user!.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!sessao) {
|
||||
return res.status(404).json({ error: 'Sessão não encontrada' });
|
||||
}
|
||||
|
||||
const navegacaoService = new NavegacaoService();
|
||||
await navegacaoService.finalizarSessao();
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Erro ao finalizar sessão:', error);
|
||||
res.status(500).json({ error: 'Erro ao finalizar sessão' });
|
||||
}
|
||||
});
|
||||
|
||||
// Listar sessões do usuário
|
||||
router.get('/sessoes', async (req, res) => {
|
||||
try {
|
||||
const sessoes = await prisma.sessaoNavegacao.findMany({
|
||||
where: { usuarioId: req.user!.id },
|
||||
include: {
|
||||
acoes: {
|
||||
select: { id: true, timestamp: true, tipo: true }
|
||||
},
|
||||
capturas: {
|
||||
select: { id: true, timestamp: true, url: true }
|
||||
}
|
||||
},
|
||||
orderBy: { criadoEm: 'desc' }
|
||||
});
|
||||
|
||||
res.json(sessoes);
|
||||
} catch (error) {
|
||||
console.error('Erro ao listar sessões:', error);
|
||||
res.status(500).json({ error: 'Erro ao listar sessões' });
|
||||
}
|
||||
});
|
||||
|
||||
// Detalhes de uma sessão
|
||||
router.get('/sessoes/:sessaoId', async (req, res) => {
|
||||
try {
|
||||
const { sessaoId } = req.params;
|
||||
|
||||
const sessao = await prisma.sessaoNavegacao.findFirst({
|
||||
where: {
|
||||
id: sessaoId,
|
||||
usuarioId: req.user!.id
|
||||
},
|
||||
include: {
|
||||
acoes: {
|
||||
orderBy: { timestamp: 'asc' }
|
||||
},
|
||||
capturas: {
|
||||
orderBy: { timestamp: 'asc' }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!sessao) {
|
||||
return res.status(404).json({ error: 'Sessão não encontrada' });
|
||||
}
|
||||
|
||||
res.json(sessao);
|
||||
} catch (error) {
|
||||
console.error('Erro ao buscar sessão:', error);
|
||||
res.status(500).json({ error: 'Erro ao buscar detalhes da sessão' });
|
||||
}
|
||||
});
|
||||
|
||||
// Download do relatório
|
||||
router.get('/sessoes/:sessaoId/relatorio', async (req, res) => {
|
||||
try {
|
||||
const { sessaoId } = req.params;
|
||||
|
||||
const sessao = await prisma.sessaoNavegacao.findFirst({
|
||||
where: {
|
||||
id: sessaoId,
|
||||
usuarioId: req.user!.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!sessao || !sessao.relatorioPdfPath) {
|
||||
return res.status(404).json({ error: 'Relatório não encontrado' });
|
||||
}
|
||||
|
||||
res.download(sessao.relatorioPdfPath);
|
||||
} catch (error) {
|
||||
console.error('Erro ao baixar relatório:', error);
|
||||
res.status(500).json({ error: 'Erro ao baixar relatório' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,264 @@
|
||||
import { prisma } from '../config/database';
|
||||
import puppeteer from 'puppeteer-extra';
|
||||
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
||||
import { createHash } from 'crypto';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs/promises';
|
||||
import { Browser, Page } from 'puppeteer';
|
||||
import * as OpenTimestamps from 'opentimestamps';
|
||||
|
||||
puppeteer.use(StealthPlugin());
|
||||
|
||||
export class NavegacaoService {
|
||||
private browser: Browser | null = null;
|
||||
private page: Page | null = null;
|
||||
private sessaoId: string | null = null;
|
||||
private recording: boolean = false;
|
||||
private recordingInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
async iniciarSessao(usuarioId: string, titulo: string, descricao?: string) {
|
||||
try {
|
||||
// Criar sessão no banco
|
||||
const sessionKey = this.generateSessionKey();
|
||||
|
||||
const sessao = await prisma.sessaoNavegacao.create({
|
||||
data: {
|
||||
titulo,
|
||||
descricao,
|
||||
usuarioId,
|
||||
sessionKey,
|
||||
status: 'INICIADA',
|
||||
browserFingerprint: await this.getBrowserFingerprint()
|
||||
}
|
||||
});
|
||||
|
||||
this.sessaoId = sessao.id;
|
||||
|
||||
// Inicializar browser controlado
|
||||
await this.setupBrowser();
|
||||
|
||||
return {
|
||||
sessaoId: sessao.id,
|
||||
sessionKey: sessao.sessionKey,
|
||||
browserWsEndpoint: this.browser?.wsEndpoint()
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Erro ao iniciar sessão:', error);
|
||||
throw new Error('Falha ao iniciar sessão de navegação');
|
||||
}
|
||||
}
|
||||
|
||||
private async setupBrowser() {
|
||||
this.browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
devtools: true,
|
||||
defaultViewport: null,
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
'--enable-webrtc-logs',
|
||||
'--use-fake-ui-for-media-stream',
|
||||
'--autoplay-policy=no-user-gesture-required'
|
||||
]
|
||||
});
|
||||
|
||||
this.page = await this.browser.newPage();
|
||||
|
||||
// Configurar interceptadores
|
||||
await this.setupPageInterceptors();
|
||||
}
|
||||
|
||||
private async setupPageInterceptors() {
|
||||
if (!this.page) return;
|
||||
|
||||
// Interceptar requests de rede
|
||||
await this.page.setRequestInterception(true);
|
||||
|
||||
this.page.on('request', async (request) => {
|
||||
await this.registrarAcao('NAVEGACAO', {
|
||||
url: request.url(),
|
||||
method: request.method(),
|
||||
headers: request.headers(),
|
||||
postData: request.postData()
|
||||
});
|
||||
request.continue();
|
||||
});
|
||||
|
||||
// Interceptar cliques
|
||||
await this.page.evaluateOnNewDocument(() => {
|
||||
document.addEventListener('click', (event) => {
|
||||
window.postMessage({
|
||||
type: 'NAVEGACAO_ACTION',
|
||||
action: 'CLIQUE',
|
||||
data: {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
target: event.target?.tagName,
|
||||
targetId: event.target?.id,
|
||||
targetClass: event.target?.className
|
||||
}
|
||||
}, '*');
|
||||
});
|
||||
});
|
||||
|
||||
// Escutar mensagens do frontend
|
||||
this.page.on('console', async (msg) => {
|
||||
if (msg.text().includes('NAVEGACAO_ACTION')) {
|
||||
const data = JSON.parse(msg.text().replace('NAVEGACAO_ACTION:', ''));
|
||||
await this.registrarAcao(data.action, data.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async navegarPara(url: string) {
|
||||
if (!this.page || !this.sessaoId) {
|
||||
throw new Error('Sessão não iniciada');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.page.goto(url, { waitUntil: 'networkidle2' });
|
||||
|
||||
// Capturar estado atual
|
||||
await this.capturarEstadoAtual();
|
||||
|
||||
// Atualizar status
|
||||
await prisma.sessaoNavegacao.update({
|
||||
where: { id: this.sessaoId },
|
||||
data: { status: 'EM_ANDAMENTO' }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erro na navegação:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async capturarEstadoAtual() {
|
||||
if (!this.page || !this.sessaoId) return;
|
||||
|
||||
const url = this.page.url();
|
||||
const titulo = await this.page.title();
|
||||
const timestamp = new Date();
|
||||
|
||||
// Screenshot
|
||||
const screenshotBuffer = await this.page.screenshot({ fullPage: true });
|
||||
const screenshotPath = await this.salvarArquivo(
|
||||
screenshotBuffer,
|
||||
`screenshot_${Date.now()}.png`
|
||||
);
|
||||
|
||||
// HTML content
|
||||
const htmlContent = await this.page.content();
|
||||
const checksumHtml = createHash('sha256').update(htmlContent).digest('hex');
|
||||
const checksumScreenshot = createHash('sha256').update(screenshotBuffer).digest('hex');
|
||||
|
||||
// Cookies e storage
|
||||
const cookies = await this.page.cookies();
|
||||
const localStorage = await this.page.evaluate(() =>
|
||||
JSON.stringify(window.localStorage)
|
||||
);
|
||||
const sessionStorage = await this.page.evaluate(() =>
|
||||
JSON.stringify(window.sessionStorage)
|
||||
);
|
||||
|
||||
// Salvar captura
|
||||
await prisma.capturaNavegacao.create({
|
||||
data: {
|
||||
sessaoId: this.sessaoId,
|
||||
timestamp,
|
||||
url,
|
||||
titulo,
|
||||
screenshotPath,
|
||||
htmlContent,
|
||||
checksumHtml,
|
||||
checksumScreenshot,
|
||||
networkRequests: [], // Será preenchido pelos interceptadores
|
||||
cookies,
|
||||
localStorage: localStorage ? JSON.parse(localStorage) : null,
|
||||
sessionStorage: sessionStorage ? JSON.parse(sessionStorage) : null
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async registrarAcao(tipo: string, dados: any) {
|
||||
if (!this.sessaoId) return;
|
||||
|
||||
const screenshot = this.page ? await this.page.screenshot({ encoding: 'base64' }) : null;
|
||||
|
||||
await prisma.acaoNavegacao.create({
|
||||
data: {
|
||||
sessaoId: this.sessaoId,
|
||||
timestamp: new Date(),
|
||||
tipo: tipo as any,
|
||||
url: this.page?.url(),
|
||||
elemento: dados.target,
|
||||
coordenadas: dados.x && dados.y ? { x: dados.x, y: dados.y } : null,
|
||||
valor: dados.value,
|
||||
screenshot: screenshot ? `data:image/png;base64,${screenshot}` : null,
|
||||
metadados: dados
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async finalizarSessao() {
|
||||
if (!this.sessaoId) return;
|
||||
|
||||
try {
|
||||
await prisma.sessaoNavegacao.update({
|
||||
where: { id: this.sessaoId },
|
||||
data: {
|
||||
status: 'FINALIZANDO',
|
||||
fimSessao: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
// Gerar relatório final
|
||||
await this.gerarRelatorioFinal();
|
||||
|
||||
if (this.browser) {
|
||||
await this.browser.close();
|
||||
}
|
||||
|
||||
await prisma.sessaoNavegacao.update({
|
||||
where: { id: this.sessaoId },
|
||||
data: { status: 'CONCLUIDA' }
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erro ao finalizar sessão:', error);
|
||||
await prisma.sessaoNavegacao.update({
|
||||
where: { id: this.sessaoId },
|
||||
data: { status: 'ERRO' }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async gerarRelatorioFinal() {
|
||||
// Implementar geração do relatório PDF com todas as evidências
|
||||
// Compilar screenshots, metadados, timestamps, etc.
|
||||
}
|
||||
|
||||
private generateSessionKey(): string {
|
||||
return createHash('sha256')
|
||||
.update(`${Date.now()}_${Math.random()}_${process.pid}`)
|
||||
.digest('hex');
|
||||
}
|
||||
|
||||
private async getBrowserFingerprint() {
|
||||
return {
|
||||
userAgent: 'Controlled Browser',
|
||||
timestamp: new Date().toISOString(),
|
||||
nodeVersion: process.version
|
||||
};
|
||||
}
|
||||
|
||||
private async salvarArquivo(buffer: Buffer, filename: string): Promise<string> {
|
||||
const dir = path.join(process.cwd(), 'storage', 'navegacao', this.sessaoId!);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
|
||||
const filepath = path.join(dir, filename);
|
||||
await fs.writeFile(filepath, buffer);
|
||||
|
||||
return filepath;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user