Files
short-video-viral-replicator/agent-studio-mini/scripts/share-preview.mjs
T

54 lines
2.9 KiB
JavaScript

import http from 'node:http';
import { randomBytes, timingSafeEqual } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
// Expose only built assets and the application API, with a temporary password.
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../apps/web/dist');
const password = process.env.PREVIEW_PASSWORD || randomBytes(12).toString('base64url');
const credential = Buffer.from('Basic ' + Buffer.from(`beryl:${password}`).toString('base64'));
const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.png': 'image/png', '.woff2': 'font/woff2' };
http.createServer(async (req, res) => {
const supplied = Buffer.from(req.headers.authorization || '');
if (supplied.length !== credential.length || !timingSafeEqual(supplied, credential)) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Beryl preview"', 'Cache-Control': 'no-store' });
return res.end('Authentication required');
}
if (req.headers.origin && req.headers.origin !== `https://${req.headers.host}` && req.headers.origin !== `http://${req.headers.host}`) {
res.writeHead(403); return res.end('Origin denied');
}
let pathname;
try { pathname = decodeURIComponent(new URL(req.url, 'http://localhost').pathname); }
catch { res.writeHead(400); return res.end(); }
if (pathname.startsWith('/api/') || pathname === '/health') {
const headers = { ...req.headers, host: 'localhost:3001' };
delete headers.authorization;
const upstream = http.request({ hostname: '127.0.0.1', port: 3001, path: req.url, method: req.method, headers }, response => {
const safeHeaders = { ...response.headers, 'cache-control': 'no-store' };
delete safeHeaders['access-control-allow-origin'];
res.writeHead(response.statusCode, safeHeaders);
response.pipe(res);
});
upstream.on('error', () => { if (!res.headersSent) res.writeHead(502); res.end('Backend unavailable'); });
res.on('close', () => upstream.destroy());
req.pipe(upstream);
return;
}
if (!['GET', 'HEAD'].includes(req.method)) { res.writeHead(405); return res.end(); }
const relative = pathname === '/' ? 'index.html' : pathname.slice(1);
const file = path.resolve(root, relative);
if (!file.startsWith(root + path.sep) || relative.split('/').some(p => p.startsWith('.'))) {
res.writeHead(404); return res.end();
}
try {
const data = await readFile(file);
res.writeHead(200, { 'Content-Type': types[path.extname(file)] || 'application/octet-stream', 'X-Content-Type-Options': 'nosniff', 'Cache-Control': 'no-store' });
res.end(req.method === 'HEAD' ? undefined : data);
} catch { res.writeHead(404); res.end('Not found'); }
}).listen(4173, '127.0.0.1', () => {
console.log('Preview listening on 127.0.0.1:4173');
console.log('Username: beryl');
console.log('Password: ' + password);
});