import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../artifacts/recroom-network-mirror'); const origin = 'https://recroom.network'; const allowed = new Set(['recroom.network', 'cdn.recroom.network']); const queue = ['/', '/rooms', '/events', '/news', '/creator', '/shop', '/download'].map(p => origin + p); const seen = new Set(); const manifest = []; await fs.mkdir(root, { recursive: true }); function localPath(url) { return (url.hostname === 'cdn.recroom.network' ? '/mirror-cdn' : '') + url.pathname; } function rewrite(text) { return text.replaceAll('https://cdn.recroom.network', '/mirror-cdn') .replaceAll('https://recroom.network', ''); } function discover(text, base) { const add = raw => { try { const value = raw.replaceAll('&', '&').replaceAll('\\/', '/'); const url = new URL(value, base); if (!allowed.has(url.hostname) || url.protocol !== 'https:') return; if (!/\.(?:js|css|woff2?|ttf|png|jpe?g|webp|svg|ico|avif)(?:$|\?)/i.test(url.pathname + url.search)) return; url.hash = ''; if (!seen.has(url.href)) queue.push(url.href); } catch {} }; for (const m of text.matchAll(/(?:src|href|poster)=["']([^"']+)["']/g)) add(m[1]); for (const m of text.matchAll(/url\(["']?([^\s)'";]+)["']?\)/g)) add(m[1]); for (const m of text.matchAll(/["'`](https:\/\/[^"'`\s<>]+|\/(?:_next|static|logos|images)\/[^"'`\s<>]+)["'`]/g)) add(m[1]); for (const m of text.matchAll(/["'](static\/(?:chunks|media)\/[^"']+)["']/g)) add('/_next/' + m[1]); } while (queue.length && seen.size < 350) { const source = queue.shift(); if (seen.has(source)) continue; seen.add(source); try { const response = await fetch(source, { signal: AbortSignal.timeout(25000) }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const type = response.headers.get('content-type') || ''; const url = new URL(source); let output = localPath(url); if (type.includes('text/html')) output = output.replace(/\/$/, '') + '/index.html'; const filename = path.resolve(root, '.' + output); if (!filename.startsWith(root + path.sep)) throw new Error('Invalid output path'); const bytes = Buffer.from(await response.arrayBuffer()); await fs.mkdir(path.dirname(filename), { recursive: true }); if (/javascript|text\/css|text\/html/.test(type)) { const sourceText = bytes.toString('utf8'); discover(sourceText, source); await fs.writeFile(filename, rewrite(sourceText)); } else await fs.writeFile(filename, bytes); manifest.push({ source, file: output, type, bytes: bytes.length }); console.log(`${manifest.length}: ${output} (${bytes.length})`); } catch (error) { manifest.push({ source, error: error.message }); console.log(`FAILED ${source}: ${error.message}`); } } await fs.writeFile(path.join(root, 'manifest.json'), JSON.stringify({ capturedAt: new Date().toISOString(), source: origin, files: manifest }, null, 2)); console.log(`Saved ${manifest.filter(x => !x.error).length} files; ${manifest.filter(x => x.error).length} failed; ${queue.length} queued. ${root}`);