import type { Observations, SinaisBrowser } from '../core/types.js'; import { coletarLighthouse } from './lighthouse.js'; import { carregarDataset } from '../detection/dataset.js'; function chavesJsDoDataset(): string[] { const chaves = new Set(); for (const fp of Object.values(carregarDataset().tecnologias)) { for (const k of Object.keys(fp.js ?? {})) chaves.add(k); } return [...chaves]; } export interface OpcoesBrowser { lighthouse: boolean; progresso: (etapa: string) => void; } export async function coletarBrowser(obs: Observations, opcoes: OpcoesBrowser): Promise { const { chromium } = await import('playwright'); const sinais: SinaisBrowser = { requisicoes: [], apis: [], websockets: [], serviceWorkers: [], globaisJs: [], errosConsole: [], dominiosTerceiros: [], }; const browser = await chromium.launch({ headless: true }); const contexto = await browser.newContext({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0 Safari/537.36', }); const pagina = await contexto.newPage(); let hostAlvo = ''; try { hostAlvo = new URL(obs.urlFinal || obs.urlSolicitada).hostname.replace(/^www\./, ''); } catch { /* ignore */ } const terceiros = new Set(); pagina.on('request', (req) => { const tipo = req.resourceType(); sinais.requisicoes.push({ url: req.url(), tipo }); if (tipo === 'xhr' || tipo === 'fetch') sinais.apis.push(req.url()); try { const h = new URL(req.url()).hostname.replace(/^www\./, ''); if (h && h !== hostAlvo && !h.endsWith('.' + hostAlvo)) terceiros.add(h); } catch { /* ignore */ } }); pagina.on('websocket', (ws) => sinais.websockets.push(ws.url())); pagina.on('console', (msg) => { if (msg.type() === 'error') sinais.errosConsole.push(msg.text().slice(0, 200)); }); try { const resp = await pagina.goto(obs.urlFinal || obs.urlSolicitada, { waitUntil: 'networkidle', timeout: 30000, }); if (resp) obs.statusFinal = resp.status(); obs.html = await pagina.content(); const scriptsCarregados = sinais.requisicoes .filter((r) => r.tipo === 'script') .map((r) => r.url); obs.scriptSrc = Array.from(new Set([...obs.scriptSrc, ...scriptsCarregados])); sinais.globaisJs = await pagina.evaluate((chaves: string[]) => { const resolvidas: string[] = []; for (const chave of chaves) { try { let alvo: unknown = globalThis; let achou = true; for (const parte of chave.split('.')) { if (alvo == null) { achou = false; break; } alvo = (alvo as Record)[parte]; } if (achou && alvo !== undefined) resolvidas.push(chave); } catch { /* propriedade inacessível */ } } return resolvidas; }, chavesJsDoDataset()); obs.globaisJs = sinais.globaisJs; sinais.serviceWorkers = contexto.serviceWorkers().map((w) => w.url()); sinais.dominiosTerceiros = [...terceiros].sort(); obs.browser = sinais; if (opcoes.lighthouse) { opcoes.progresso('auditando com Lighthouse'); await coletarLighthouse(obs); } } catch (erro) { obs.avisos.push(`Falha no modo completo: ${(erro as Error).message}`); } finally { await browser.close(); } }