import express, { Request, Response } from "express"; import { launch } from "chrome-launcher"; import lighthouse from "lighthouse"; import cors from "cors"; const app = express(); app.use(cors()); app.use(express.json()); // Health check endpoint app.get("/health", (req: Request, res: Response) => { res.json({ status: "ok", timestamp: new Date().toISOString() }); }); // Enhanced lighthouse endpoint app.post("/lighthouse", async (req: Request, res: Response) => { const { url } = req.body; if (!url) return res.status(400).json({ error: "Missing URL" }); console.log(`Starting Lighthouse scan for: ${url}`); try { // Validate URL format try { new URL(url); } catch (urlError) { return res.status(400).json({ error: "Invalid URL format" }); } const chrome = await launch({ chromeFlags: [ "--headless", "--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--no-first-run", "--disable-extensions", "--disable-background-timer-throttling", "--disable-backgrounding-occluded-windows", "--disable-renderer-backgrounding", "--disable-features=TranslateUI", "--disable-ipc-flooding-protection", ], }); console.log(`Chrome launched on port: ${chrome.port}`); const runnerResult = await lighthouse(url, { port: chrome.port, output: "json", logLevel: "info", onlyCategories: ["performance", "seo", "accessibility", "best-practices"], formFactor: "desktop", screenEmulation: { mobile: false, width: 1350, height: 940, deviceScaleFactor: 1, disabled: false, }, throttling: { rttMs: 40, throughputKbps: 10240, cpuSlowdownMultiplier: 1, requestLatencyMs: 0, downloadThroughputKbps: 0, uploadThroughputKbps: 0, }, }); await chrome.kill(); if (!runnerResult) throw new Error("Lighthouse returned no result"); const { categories, audits } = runnerResult.lhr; // Extract key metrics for easier access const metrics = { performance: categories.performance?.score ? Math.round(categories.performance.score * 100) : null, seo: categories.seo?.score ? Math.round(categories.seo.score * 100) : null, accessibility: categories.accessibility?.score ? Math.round(categories.accessibility.score * 100) : null, bestPractices: categories["best-practices"]?.score ? Math.round(categories["best-practices"].score * 100) : null, firstContentfulPaint: audits["first-contentful-paint"]?.numericValue || null, largestContentfulPaint: audits["largest-contentful-paint"]?.numericValue || null, cumulativeLayoutShift: audits["cumulative-layout-shift"]?.numericValue || null, totalBlockingTime: audits["total-blocking-time"]?.numericValue || null, speedIndex: audits["speed-index"]?.numericValue || null, }; console.log( `Lighthouse scan completed for: ${url} - Performance: ${metrics.performance}%`, ); res.json({ categories, audits, metrics, raw: runnerResult.lhr, timestamp: new Date().toISOString(), url: url, }); } catch (error) { console.error(`Lighthouse scan failed for ${url}:`, error); res.status(500).json({ error: error instanceof Error ? error.message : String(error), url: url, timestamp: new Date().toISOString(), }); } }); // Batch lighthouse endpoint for multiple URLs app.post("/lighthouse/batch", async (req: Request, res: Response) => { const { urls } = req.body; if (!urls || !Array.isArray(urls) || urls.length === 0) { return res.status(400).json({ error: "Missing or empty URLs array" }); } if (urls.length > 10) { return res.status(400).json({ error: "Maximum 10 URLs allowed per batch" }); } console.log(`Starting batch Lighthouse scan for ${urls.length} URLs`); const results = []; for (const url of urls) { try { // Make internal request to single lighthouse endpoint const response = await fetch(`http://localhost:${PORT}/lighthouse`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ url }), }); const result = await response.json(); results.push({ url, success: response.ok, data: result }); } catch (error) { results.push({ url, success: false, error: error instanceof Error ? error.message : String(error), }); } } res.json({ results, total: urls.length, successful: results.filter((r) => r.success).length, failed: results.filter((r) => !r.success).length, timestamp: new Date().toISOString(), }); }); const PORT = process.env.PORT || 5001; app.listen(PORT, () => { console.log(`Lighthouse worker listening on http://localhost:${PORT}`); console.log(`Health check available at http://localhost:${PORT}/health`); });