const express = require('express'); const { chromium } = require('playwright'); const cheerio = require('cheerio'); const cors = require('cors'); const app = express(); app.use(cors()); app.use(express.json()); const requestQueue = []; let isProcessing = false; let browserInstance = null; // Globally cached single browser instance // Initialize the master browser process on boot async function initBrowser() { try { console.log("[System Init]: Launching master Chromium process..."); browserInstance = await chromium.launch({ headless: true, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', // Vital for low memory containers '--disable-accelerated-2d-canvas', '--disable-gpu' ] }); console.log("[System Init]: Headless Chromium ready to snatch."); } catch (err) { console.error("[System Init Error]: Failed launching master browser:", err.message); } } // Queue Processing Loop async function processQueue() { if (isProcessing || requestQueue.length === 0) return; isProcessing = true; const currentTask = requestQueue.shift(); // Ensure browser didn't crash or close unexpectedly under heavy workloads if (!browserInstance) { await initBrowser(); } let context = null; let page = null; try { const { targetUrl, requestedAssets, res } = currentTask; console.log(`\n[Snatch Worker]: Processing -> ${targetUrl}`); // Create an isolated, completely fresh browser context (simulates a new device context) context = await browserInstance.newContext({ userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', viewport: { width: 1280, height: 800 }, deviceScaleFactor: 1, bypassCSP: true // Bypass Content Security Policy to pull stylesheets freely }); page = await context.newPage(); // Navigate to the page and wait until network connections completely settle down await page.goto(targetUrl, { timeout: 25000, waitUntil: 'networkidle' // Crucial: Waits for all dynamic React/JS loads to finish }); // Pull the fully compiled, javascript-rendered DOM tree structure string const renderedHtml = await page.content(); const $ = cheerio.load(renderedHtml); let extractedCss = ""; let extractedJs = ""; // 1. Asset Extractor: CSS Elements if (requestedAssets.includes('css')) { $('style').each((_, el) => { extractedCss += `/* --- Inline Style --- */\n${$(el).text()}\n\n`; }); $('link[rel="stylesheet"]').each((_, el) => { const href = $(el).attr('href'); if (href) { const absUrl = href.startsWith('http') ? href : new URL(href, targetUrl).href; extractedCss += `/* External Reference: ${absUrl} */\n`; } }); } // 2. Asset Extractor: JS Elements if (requestedAssets.includes('js')) { $('script').each((_, el) => { const text = $(el).text(); const src = $(el).attr('src'); if (text.trim()) { extractedJs += `// --- Inline Script ---\n${text.trim()}\n\n`; } else if (src) { const absUrl = src.startsWith('http') ? src : new URL(src, targetUrl).href; extractedJs += `// External Reference: ${absUrl}\n`; } }); } // 3. Asset Extractor: Clean, isolated HTML document frame body let cleanHtml = ""; if (requestedAssets.includes('html')) { $('script, style, link[rel="stylesheet"]').remove(); cleanHtml = $('body').html() || $.html(); } // Send payload back cleanly res.json({ html: requestedAssets.includes('html') ? cleanHtml.trim() : null, css: requestedAssets.includes('css') ? extractedCss.trim() : null, js: requestedAssets.includes('js') ? extractedJs.trim() : null }); console.log(`[Snatch Worker]: Successfully sent assets back to frontend client.`); } catch (error) { console.error(`[Snatch Worker Error]: Execution hit a wall:`, error.message); currentTask.res.status(500).json({ error: "Playwright Snatcher failed execution task: " + error.message }); } finally { // Clean up individual tabs to completely prevent memory leaks if (page) await page.close().catch(() => {}); if (context) await context.close().catch(() => {}); isProcessing = false; // Moderate pacing backoff time window delay setTimeout(processQueue, 150); } } // Operational Status Route Mapping app.get('/', (req, res) => { res.json({ engine: "CodeSnatch Ultimate Playwright Cluster", status: "operational", browserProcessAlive: !!browserInstance, backlogLength: requestQueue.length }); }); // Primary Scrape Ingestion Route app.get('/scrape', (req, res) => { const targetUrl = req.query.url; const requestedAssets = req.query.assets ? req.query.assets.split(',') : ['html', 'css', 'js']; if (!targetUrl) { return res.status(400).json({ error: "Missing required query parameter 'url'." }); } requestQueue.push({ targetUrl, requestedAssets, res }); console.log(`[Traffic]: Enqueued new web page target. Backlog count: ${requestQueue.length}`); processQueue(); }); // Standard system shutdown handling to cleanly close Chromium process process.on('SIGTERM', async () => { if (browserInstance) await browserInstance.close(); process.exit(0); }); const PORT = process.env.PORT || 7860; app.listen(PORT, async () => { console.log(`=== Ultimate CodeSnatch Backend Listening on Port: ${PORT} ===`); await initBrowser(); // Instantly spin up browser engine instance when web server activates });