threshold / preview-server.mjs
citispire
Publish verified THRESHOLD studio with GitHub source snapshot
9b132d8
Raw History Blame Contribute Delete
1.93 kB
import { createReadStream } from 'node:fs';
import { stat } from 'node:fs/promises';
import { createServer } from 'node:http';
import { extname, resolve, relative, isAbsolute } from 'node:path';
import { fileURLToPath } from 'node:url';
const root=fileURLToPath(new URL('./build/',import.meta.url));
const mime={'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8',
'.js':'text/javascript; charset=utf-8','.mjs':'text/javascript; charset=utf-8',
'.svg':'image/svg+xml','.png':'image/png'};
const port=Number(process.env.THRESHOLD_PREVIEW_PORT||4176);
createServer(async(request,response)=>{
try {
const pathname=decodeURIComponent(new URL(request.url,'http://localhost').pathname);
const file=resolve(root,pathname==='/'?'index.html':pathname.replace(/^[/\\]+/,''));
const within=relative(root,file);
if(within.startsWith('..')||isAbsolute(within)) { response.writeHead(404); response.end('Not found'); return; }
if(!['GET','HEAD'].includes(request.method)){response.writeHead(405,{'allow':'GET, HEAD'});response.end();return;}
if(!(await stat(file)).isFile()){response.writeHead(404);response.end('Not found');return;}
response.writeHead(200,{'content-type':mime[extname(file)]||'application/octet-stream',
'cache-control':'no-store','x-content-type-options':'nosniff',
'content-security-policy':"default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"});
if(request.method==='HEAD') response.end();
else createReadStream(file).on('error',()=>response.destroy()).pipe(response);
} catch(error) {
response.writeHead(error instanceof URIError?400:404);
response.end(error instanceof URIError?'Invalid URL':'Not found');
}
}).listen(port,process.env.THRESHOLD_PREVIEW_HOST||'127.0.0.1',()=>console.log('THRESHOLD preview: http://127.0.0.1:'+port));