Files
Radicade/Tools/recroom-mirror/serve.mjs
T
2026-10-16 23:33:13 -07:00

47 lines
4.2 KiB
JavaScript

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('<svg xmlns="http://www.w3.org/2000/svg" width="160" height="120"><text x="80" y="70" fill="#aaa" font-size="42" text-anchor="middle">[ = )</text></svg>');
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 = `<script>document.addEventListener('click',function(e){const a=e.target.closest('a');const b=e.target.closest('button');if(b&&/^log\\s?in$/i.test(b.textContent.trim())){e.preventDefault();e.stopImmediatePropagation();alert('Login is not connected in this local preview.');return;}if(a){const u=new URL(a.href,location.href);if(u.origin===location.origin&&!u.hash){e.preventDefault();e.stopImmediatePropagation();location.assign(u.href);}}},true);</script>`;
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('<head>', '<head>' + activeBridge + previewScript).replace('</body>', '<div style="position:fixed;bottom:12px;left:12px;z-index:2147483647;background:#202020;color:#fff;padding:10px 16px;border:1px solid #888;border-radius:8px;font:13px Arial;max-width:90vw">Local copy · '+sourceLabel+' · Login not connected</div></body>'));
}
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'));