Repository navigation
Expand file tree
/
Copy pathlocal-preview.cjs
More file actions
20 lines (20 loc) · 1.76 KB
/
Copy pathlocal-preview.cjs
File metadata and controls
20 lines (20 loc) · 1.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Local-only static preview. No dependencies, AI requests or credentials.
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const root = __dirname;
const types = {'.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'text/javascript; charset=utf-8','.json':'application/json; charset=utf-8','.svg':'image/svg+xml','.png':'image/png','.webp':'image/webp','.jpg':'image/jpeg','.jpeg':'image/jpeg','.mp4':'video/mp4','.txt':'text/plain; charset=utf-8'};
const server = http.createServer((req,res)=>{
if(!['GET','HEAD'].includes(req.method)){res.writeHead(405);res.end();return;}
let pathname;try{pathname=decodeURIComponent(new URL(req.url,'http://localhost').pathname);}catch{res.writeHead(400);res.end();return;}
if(pathname.includes('\0')){res.writeHead(400);res.end();return;}
if(pathname.split(/[\\/]/).some(part=>part.startsWith('.'))){res.writeHead(403);res.end();return;}
const file=path.resolve(root,'.'+(pathname==='/'?'/index.html':pathname));
if(!file.startsWith(root+path.sep)){res.writeHead(403);res.end();return;}
fs.stat(file,(error,stat)=>{if(error || !stat.isFile()){res.writeHead(404);res.end('Not found');return;}
res.writeHead(200,{'Content-Type':types[path.extname(file).toLowerCase()] || 'application/octet-stream','Content-Length':stat.size,'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'});
if(req.method==='HEAD'){res.end();return;}const stream=fs.createReadStream(file);stream.on('error',()=>res.destroy());stream.pipe(res);
});
});
server.on('error',error=>{console.error(error.message);process.exit(1);});
server.listen(Number(process.env.MIRA_PREVIEW_PORT || 4173),'127.0.0.1',()=>console.log(`MIRA local preview: http://127.0.0.1:${server.address().port}`));