import express, { Request, Response } from "express"; import { v4 as uuidv4 } from "uuid"; const router = express.Router(); const progressClients = new Map(); /** Send SSE progress data to the browser */ function sendProgress(clientId: string, data: any) { const client = progressClients.get(clientId); if (client) { client.write(`data: ${JSON.stringify(data)}\n\n`); } } // SSE endpoint for progress router.get("/status/:id", (req: Request, res: Response) => { res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.setHeader("Access-Control-Allow-Origin", "*"); const clientId = req.params.id; progressClients.set(clientId, res); // Send an initial event sendProgress(clientId, { status: "Connected", progress: 0, stage: "setup", }); req.on("close", () => { progressClients.delete(clientId); }); }); router.post("/", async (req: Request, res: Response) => { const { url } = req.body; if (!url) { return res.status(400).json({ error: "Missing URL" }); } const clientId = uuidv4(); // Generate a UUID console.log(`New client ID: ${clientId}`); let currentProgress = 0; let auditCount = 0; let totalAudits = 0; try { // Immediately tell the frontend we’re starting (0%) sendProgress(clientId, { status: "Starting analysis...", progress: 0, stage: "setup", }); // Return clientId so the frontend can open the SSE channel res.status(200).json({ clientId }); // Dynamically import chrome-launcher and lighthouse const { launch } = await import("chrome-launcher"); const lighthouse = (await import("lighthouse")).default; const log = (await import("lighthouse-logger")).default; // Launch Chrome const chrome = await launch({ chromeFlags: ["--headless", "--no-sandbox", "--disable-dev-shm-usage"], }); // Update progress to 10% after launching Chrome currentProgress = 10; sendProgress(clientId, { status: "Chrome launched", progress: currentProgress, stage: "setup", }); // Turn on Lighthouse logs at "info" level log.setLevel("info"); // Listen for Lighthouse status events log.events.addListener("status", (lhStatus) => { let msg = lhStatus[1].toLowerCase(); let newProgress = currentProgress; let newStage = "analyzing"; if (msg.includes("initialize config")) { newProgress = Math.max(newProgress, 15); newStage = "setup"; } else if (msg.includes("resolve artifact definitions")) { newProgress = Math.max(newProgress, 20); newStage = "setup"; } else if (msg.includes("gather phase")) { newProgress = Math.max(newProgress, 25); newStage = "analyzing"; } else if (msg.includes("connecting to browser")) { newProgress = Math.max(newProgress, 28); newStage = "analyzing"; } else if (msg.includes("navigating to")) { newProgress = Math.max(newProgress, 30); newStage = "analyzing"; } else if (msg.includes("benchmarking machine")) { newProgress = Math.max(newProgress, 35); newStage = "analyzing"; } else if (msg.includes("getting artifact")) { newProgress = Math.max(newProgress, 40); newStage = "analyzing"; } else if (msg.includes("computing artifact")) { newProgress = Math.max(newProgress, 50); newStage = "analyzing"; } else if (msg.includes("audit phase")) { newProgress = Math.max(newProgress, 60); newStage = "auditing"; } else if (msg.includes("auditing:")) { auditCount++; totalAudits = Math.max(totalAudits, auditCount); newProgress = Math.max( newProgress, 60 + (auditCount / totalAudits) * 30, ); newStage = "auditing"; } else if (msg.includes("generating results")) { newProgress = Math.max(newProgress, 95); newStage = "finishing"; } // Add some randomness to the progress increments newProgress += Math.random() * 2; // Only send progress if it actually increased if (newProgress > currentProgress) { currentProgress = newProgress; sendProgress(clientId, { status: lhStatus.message || "Processing...", progress: currentProgress, stage: newStage, }); } }); // Run Lighthouse const runnerResult = await lighthouse(url, { port: chrome.port, output: "json", logLevel: "info", onlyCategories: ["performance", "seo", "accessibility", "best-practices"], }); if (!runnerResult) { throw new Error("Lighthouse returned no result"); } // Finish progress sendProgress(clientId, { status: "Analysis complete", progress: 100, stage: "complete", report: runnerResult.lhr, }); await chrome.kill(); } catch (error) { console.error("Lighthouse error:", error); sendProgress(clientId, { status: "Error occurred", progress: 0, stage: "error", error: error instanceof Error ? error.message : "An unknown error occurred", }); } }); export default router;