diff --git a/src/server.ts b/src/server.ts index 583941d..bb558ec 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,3 +1,173 @@ +/** + * ╔══════════════════════════════════════════════════════════════════════╗ + * ║ SAMSUNG TV PROXY — Bun + Puppeteer SSR Server ║ + * ╚══════════════════════════════════════════════════════════════════════╝ + * + * Renders web pages to SSR HTML (with full CSS/resource inlining) or MJPEG + * screenshots, served to Samsung TVs (Tizen browser) and other limited + * clients. Multi-browser page-pool, render-cache, URL-rotation views, + * WebSocket debug dashboard, IP ban management, and device tracking. + * + * ───────────────────────────────────────────────────────────────────────── + * QUICK-SEARCH INDEX — jump by section name or semantic topic: + * ───────────────────────────────────────────────────────────────────────── + * + * SEC | ~LINE | FLAG / TOPIC | WHAT IT DOES + * ────┼───────┼───────────────────────────────────┼─────────────────────── + * 01 | 35 | CONFIG | All tunables (pool, timeouts, intervals) + * 02 | 57 | TYPES / interfaces | View, URLItem, CachedRender, DeviceEntry… + * 03 | 115 | DATABASE / SQLite / schema | views + banned_ips tables, migrations + * 04 | 158 | DB HELPERS / dbLoad / dbInsert | CRUD: dbLoadAll, dbInsert, dbUpdate, dbDelete + * 05 | 202 | BAN SYSTEM / dbBanIP / dbIsBanned | IP ban persistence layer + * 06 | 219 | VIEW CACHE / viewCache | In-memory mirror of DB views table + * 07 | 230 | DEVICE TRACKING / devices | Per-IP tracking, ban gate, warmup trigger + * 08 | 252 | DEBUG DATA / getDebugData | Aggregates server+views+devices for API/WS + * 09 | 285 | PAGE POOL / PagePool / acquire | Multi-browser page lifecycle (acquire/release/reset) + * 10 | 383 | WS DEBUG / debugClients | Real-time debug-over-WebSocket push + * 11 | 393 | RENDER CACHE / renderCache | URL→HTML cache with TTL, inFlight tracking + * 12 | 417 | WARMUP / warmupView / warmupCache | Pre-cache active-device views on interval + * 13 | 461 | CACHE INVALIDATION / invalidate | Purge all cache entries for a view + * 14 | 475 | RENDER QUEUE / renderQueue | FIFO queue with timeout; drains into PagePool + * 15 | 506 | RENDER ENGINE / renderViewToCache | Core: SSR or MJPEG render → cache write + * 16 | 549 | GET-OR-RENDER / getOrRender | Cache-hit → inFlight → queue → render decision tree + * 17 | 590 | CACHE STATS / cacheStats | Telemetry: per-cache-entry TTL/URL info + * 18 | 620 | ROTATION / getCurrentURLIndex | Time-based URL cycling (+ remainingSec) + * 19 | 645 | SSR INLINING / injectMetaRefresh | Meta-refresh injection, script stripping + * 20 | 663 | CSS IMPORT RESOLVER / @import | Recursive @import resolver (max 3 levels) + * 21 | 694 | COMPUTED STYLE BAKING / bake | Inline computed styles for Tizen compat + * 22 | 777 | RESOURCE INLINING / inlineAll | CSS/images/favicons/bg→data: URIs in-page + * 23 | 887 | SSR RENDER / renderSSR | Full SSR pipeline: acquire→goto→inline→strip + * 24 | 932 | SCREENSHOT / renderScreenshotFrame| JPEG screenshot renderer (MJPEG mode) + * 25 | 961 | SCREENSHOT HTML / screenshotHTML | Wrap JPEG base64 in refresh-page HTML + * 26 | 981 | ERROR PAGE / errorPage | Styled error HTML with auto-retry meta-refresh + * 27 | 1002 | VIEW SERVING / serveView | /v/:id → rotation→render→response pipeline + * 28 | 1032 | API / handleAPI | REST: CRUD views, ban/unban, disconnect, refresh + * 29 | 1181 | STATIC FILES / serveStatic | Serves ../public (SPA fallback to index.html) + * 30 | 1198 | HTTP SERVER / Bun.serve | Main fetch handler: /ws, /v/:id, /api, static + * 31 | 1293 | INIT+SHUTDOWN / init / shutdown | Bootstrap + graceful drain + signal handlers + * + * ───────────────────────────────────────────────────────────────────────── + * DATA-FLOW MAP (how a GET /v/:id request travels): + * ───────────────────────────────────────────────────────────────────────── + * + * Client GET /v/:id + * │ + * ▼ + * Bun.serve.fetch() [ SEC 30 ] + * │ + * ├─ deviceTrack(ip…) [ SEC 07 ] ← blocks banned IPs + * │ + * ▼ + * serveView(view) [ SEC 27 ] + * │ + * ├─ getCurrentURLIndex(view) [ SEC 18 ] ← time-based rotation + * │ + * ▼ + * getOrRender(view, idx) [ SEC 16 ] + * │ + * ├── cache HIT? ──▶ return cached body + * ├── inFlight? ──▶ await existing promise + * ├── pool free? ──▶ renderViewToCache() [ SEC 15 ] + * │ │ + * │ ├─ renderSSR() [ SEC 23 ] + * │ │ ├─ pagePool.acquire() [ SEC 09 ] + * │ │ ├─ page.goto(url) + * │ │ ├─ inlineAllResources() [ SEC 22 ] + * │ │ │ └─ resolveCSSImports() [ SEC 20 ] + * │ │ ├─ [opt] bakeComputedStyles() [ SEC 21 ] + * │ │ ├─ stripScriptTags() + * │ │ └─ pagePool.release() + * │ │ + * │ └─ renderScreenshotFrame() [ SEC 24 ] + * │ └─ screenshotHTML() [ SEC 25 ] + * │ + * └── pool full? ──▶ enqueue → drainRenderQueue() [ SEC 14 ] + * + * ───────────────────────────────────────────────────────────────────────── + * KEY DATA STRUCTURES (grep these to find type definitions & usage): + * ───────────────────────────────────────────────────────────────────────── + * + * View — id, name, urls(URLItem[]), method(ssr|mjpeg), cacheTtl, viewport + * CachedRender — body(string), urlIndex, createdAt, expiresAt + * ActiveRender — promise(Promise), urlIndex (in-flight dedup) + * QueuedRender — resolve/reject callbacks, key, queuedAt (FIFO overflow) + * DeviceEntry — ip, viewId, userAgent, lastSeen, requestCount + * BrowserSlot — browser, available(Page[]), inUse, max + * + * ───────────────────────────────────────────────────────────────────────── + * HOW TO SEARCH THIS FILE (for humans & AIs): + * ───────────────────────────────────────────────────────────────────────── + * + * There is no need to read the file line-by-line. Use grep / text-search + * on any of the keywords below to jump directly to what you need: + * + * "CONFIG" → tunables (port, timeouts, pool sizing) + * "interface View" → View type definition + * "CREATE TABLE" → DB schema + * "dbLoadAll|dbInsert" → DB CRUD operations + * "dbBanIP|banned" → IP banning + * "viewCache" → in-memory view mirror + * "deviceTrack" → device tracking & ban gate + * "class PagePool" → browser/page pool management + * "acquire|release" → page lifecycle + * "renderCache|CachedRender" → render cache + * "getOrRender" → cache/inflight/queue decision + * "renderQueue|drainRenderQueue" → overflow queue + * "renderViewToCache" → actual render execution + * "warmupView|warmupCache" → pre-caching + * "cacheInvalidate" → cache purging + * "getCurrentURLIndex" → URL rotation logic + * "renderSSR" → SSR rendering + * "renderScreenshotFrame" → MJPEG rendering + * "inlineAllResources" → CSS/image inlining + * "bakeComputedStyles" → style baking for Tizen + * "resolveCSSImports" → @import recursion + * "serveView" → /v/:id response builder + * "handleAPI" → REST API handler + * "Bun.serve" → HTTP server entry + * "shutdown" → graceful shutdown + * "getDebugData" → debug telemetry + * "WebSocket|ws" → debug WS + * "errorPage" → error HTML template + * "screenshotHTML" → MJPEG wrapper HTML + * + * ───────────────────────────────────────────────────────────────────────── + * MODIFICATION GUIDE (what to touch when adding/changing features): + * ───────────────────────────────────────────────────────────────────────── + * + * To add a new View field: + * 1. Add to `interface View` [ SEC 02 ] + * 2. Add column migration [ SEC 03 ] + * 3. Update `rowToView()` [ SEC 03 ] + * 4. Update `dbInsert()` / `dbUpdate()` [ SEC 04 ] + * 5. Update `CreateViewBody` [ SEC 02 ] + * 6. Update API handler [ SEC 28 ] + * 7. Update `getDebugData()` [ SEC 08 ] (if visible in debug) + * + * To add a new API endpoint: + * 1. Add route in `handleAPI()` [ SEC 28 ] + * 2. If real-time: add to `getDebugData()` [ SEC 08 ] + * 3. If admin: consider ban/auth gate [ SEC 07 ] + * + * To change rendering behavior: + * - SSR pipeline: `renderSSR()` [ SEC 23 ] + * - MJPEG pipeline: `renderScreenshotFrame()` [ SEC 24 ] + * - Inlining logic: `inlineAllResources()` [ SEC 22 ] + * - Style baking: `bakeComputedStyles()` [ SEC 21 ] + * - Script removal: `stripScriptTags()` [ SEC 19 ] + * + * ───────────────────────────────────────────────────────────────────────── + * EXTERNAL DEPENDENCIES: + * ───────────────────────────────────────────────────────────────────────── + * + * bun:sqlite — embedded SQLite (views.db, banned_ips.db) + * puppeteer — headless Chromium (launched via /usr/bin/chromium) + * Bun — runtime: Bun.file(), Bun.serve(), WebSocket upgrade + * crypto — randomUUID() for view IDs + * + * ═══════════════════════════════════════════════════════════════════════ + */ + import { Database } from "bun:sqlite"; import type { Browser, Page } from "puppeteer"; import puppeteer from "puppeteer"; @@ -26,6 +196,7 @@ const CONFIG = { interface URLItem { url: string; durationSec: number; + waitBeforeCaptureMs?: number; forceDarkMode?: boolean; bakeStyles?: boolean; } @@ -867,189 +1038,57 @@ async function resolveCSSImports(cssText: string, baseUrl: string, depth = 0): P } // ═══════════════════════════════════════════════════════════════ -// COMPUTED STYLE BAKING +// CSS COLLECTION (simplified — all CSS into single style tag) // ═══════════════════════════════════════════════════════════════ /** - * Bake computed styles into inline style attributes. - * This ensures styles work on browsers with limited CSS support (e.g., Samsung TV). - * Compares each element against a same-tagname fresh element so tag-specific defaults - * (e.g. block display on div, inline on span, bold on h1) are accounted for. + * Collect all CSS from loaded stylesheets and inject into a single