Files
cloudlense/backend/src/routes/lighthouse.ts
T
Dennis 50e25e3ee8 refactor: flatten monorepo structure to backend/ frontend/ devops/
Rename subdirectories for a cleaner single-repo layout:
- website-monitoring-backend/  → backend/
- website-monitoring-frontend/ → frontend/
- website-monitoring-devops/   → devops/

Update all references in package.json scripts, CI workflows,
docker-compose, pre-commit hooks, and documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-07 00:25:29 +01:00

175 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import express, { Request, Response } from "express";
import { v4 as uuidv4 } from "uuid";
const router = express.Router();
const progressClients = new Map<string, Response>();
/** 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 were 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;