import fs from 'node:fs/promises'; import path from 'node:path'; import { createHash } from 'node:crypto'; export function upstreamFor(url) { const routes = { '/_public/rooms/featuredrooms/current': 'https://rooms.recroom.network/featuredrooms/current', '/_public/rooms/rooms/bulk': 'https://rooms.recroom.network/rooms/bulk', '/_public/accounts/account/bulk': 'https://accounts.recroom.network/account/bulk', '/_public/apim/apis/api/images/v3/feed/global': 'https://apim.recroom.network/apis/api/images/v3/feed/global', }; if (routes[url.pathname]) { const target = new URL(routes[url.pathname]); for (const [key,value] of url.searchParams) { if (!['id','ids','skip','take','include'].includes(key) || !/^[0-9,]{1,2000}$/.test(value)) return null; target.searchParams.append(key,value); } return target; } const image = url.pathname.match(/^\/_public\/img\/([a-zA-Z0-9_.%-]{1,180})$/); if (image) { return new URL('https://img.recroom.network/' + image[1]); } return null; } export async function servePublicCache(url, req, res, root) { if (!url.pathname.startsWith('/_public/')) return false; const upstream = upstreamFor(url); if (!upstream) { res.writeHead(404); res.end(); return true; } const key = createHash('sha256').update(upstream.href).digest('hex'); const dir = path.join(root, 'public-cache'); try { let bytes, type; try { bytes = await fs.readFile(path.join(dir,key)); type = await fs.readFile(path.join(dir,key+'.type'),'utf8'); } catch { const response = await fetch(upstream, {redirect:'error', signal:AbortSignal.timeout(20000)}); if (response.status === 404 && upstream.hostname === 'img.recroom.network') { res.writeHead(200, {'Content-Type':'image/svg+xml','X-Mirror-Placeholder':'source-image-missing'}); res.end('Image unavailable in source archive'); return true; } if (!response.ok) throw new Error(`Public source returned ${response.status}`); type = response.headers.get('content-type') || 'application/octet-stream'; bytes = Buffer.from(await response.arrayBuffer()); if (bytes.length > 15 * 1024 * 1024) throw new Error('Response too large'); await fs.mkdir(dir,{recursive:true}); await fs.writeFile(path.join(dir,key),bytes); await fs.writeFile(path.join(dir,key+'.type'),type); } res.writeHead(200, {'Content-Type':type,'Cache-Control':'public, max-age=3600'}); res.end(req.method === 'HEAD' ? undefined : bytes); } catch(error) { console.error('Public cache:', upstream.href, error.message); res.writeHead(502, {'Content-Type':'application/json'}); res.end(JSON.stringify({error:'public_snapshot_unavailable'})); } return true; } export const bridgeScript = ``;