import http from 'node:http'; import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { servePublicCache, bridgeScript } from './public-cache.mjs'; import { serveRadicade, backendOrigin } from './radicade-adapter.mjs'; const archiveMode = process.env.RECNET_DATA_MODE === 'archive'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../artifacts/recroom-network-mirror'); const types = {'.html':'text/html; charset=utf-8','.js':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg','.webp':'image/webp','.avif':'image/avif','.ico':'image/x-icon','.woff':'font/woff','.woff2':'font/woff2','.ttf':'font/ttf'}; http.createServer(async (req, res) => { res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; media-src 'self' blob:; form-action 'none'; frame-src 'none'; object-src 'none'; base-uri 'self'"); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Cache-Control', 'no-store'); if (!['GET','HEAD'].includes(req.method)) { res.writeHead(501, {'Content-Type':'application/json'}); res.end('{"error":"offline_preview","message":"Login and server actions are not connected in this local copy."}'); return; } try { const url = new URL(req.url, 'http://127.0.0.1'); if (!archiveMode && url.pathname.startsWith('/_radicade/v2/')) { url.pathname = url.pathname.replace('/_radicade/v2/', '/_public/'); } if (archiveMode ? await servePublicCache(url, req, res, root) : await serveRadicade(url, req, res)) return; if (url.pathname === '/static/img/NoSearchResult.png') { res.writeHead(200, {'Content-Type':'image/svg+xml'}); res.end('[ = )'); return; } const filename = path.resolve(root, '.' + url.pathname); if (filename !== root && !filename.startsWith(root + path.sep)) throw new Error('Invalid path'); let actual = filename; const stat = await fs.stat(actual); if (stat.isDirectory()) actual = path.join(actual, 'index.html'); let content = await fs.readFile(actual); const ext = path.extname(actual); const dataPrefix = archiveMode ? '/_public/' : '/_radicade/v2/'; if (ext === '.js' || ext === '.html') content = Buffer.from(content.toString() .replaceAll('https://img.recroom.network/', dataPrefix + 'img/') .replaceAll('/_public/img/', dataPrefix + 'img/')); if (ext === '.html') { const previewScript = ``; const sourceLabel=archiveMode?'Cached recroom.network data':'Radicade public data · '+backendOrigin.replace(/[&<>"']/g,''); const activeBridge = bridgeScript.replaceAll('/_public/', dataPrefix); content = Buffer.from(content.toString().replace('', '' + activeBridge + previewScript).replace('', '
Local copy · '+sourceLabel+' · Login not connected
')); } res.writeHead(200, {'Content-Type':types[ext] || 'application/octet-stream'}); res.end(req.method === 'HEAD' ? undefined : content); } catch { res.writeHead(404, {'Content-Type':'application/json'}); res.end('{"error":"not_archived","message":"This file or API is not available in the local copy."}'); } }).listen(3040, '127.0.0.1', () => console.log('Local website copy: http://127.0.0.1:3040'));