import http from 'node:http'; import { readFile } from 'node:fs/promises'; import { timingSafeEqual } from 'node:crypto'; import { createDesk } from './desk.mjs'; function body(request) { return new Promise((resolve, reject) => { let size = 0, large = false; const chunks = []; request.on('data', (chunk) => { size += chunk.length; if (size > 1024) large = true; else chunks.push(chunk); }); request.on('error', reject); request.on('end', () => { if (large) return reject(Object.assign(new Error('Body too large'), { status: 413 })); try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8'))); } catch { reject(Object.assign(new Error('Invalid JSON'), { status: 400 })); } }); }); } export function createServer({ token, desk = createDesk() }) { if (typeof token !== 'string' || !/^[a-f0-9]{64}$/.test(token)) throw new Error('Supply an ephemeral 32-byte hexadecimal write token'); const expected = Buffer.from('Bearer ' + token); const server = http.createServer((request, response) => { const send = (status, value) => { response.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store', }); response.end(JSON.stringify(value)); }; (async () => { const url = new URL(request.url, 'http://desk.invalid'); if (request.method === 'GET' && url.pathname === '/') { const html = await readFile( new URL('./dashboard.html', import.meta.url), 'utf8', ); response.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'content-security-policy': "default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; base-uri 'none'; frame-ancestors 'none'", }); response.end(html); return; } if (request.method === 'GET' && url.pathname === '/dashboard.mjs') { response.writeHead(200, { 'content-type': 'application/javascript' }); response.end( await readFile(new URL('./dashboard.mjs', import.meta.url), 'utf8'), ); return; } if (request.method === 'GET' && url.pathname === '/api/kits') return send(200, { items: desk.inventory(), persistent: false }); if (request.method === 'POST' && url.pathname === '/api/checkouts') { const supplied = Buffer.from(request.headers.authorization ?? ''); if ( supplied.length !== expected.length || !timingSafeEqual(supplied, expected) ) { request.resume(); return send(401, { error: 'Unauthorized' }); } if ( (request.headers['content-type'] ?? '').split(';')[0] !== 'application/json' ) { request.resume(); return send(415, { error: 'Use JSON' }); } return send(200, desk.checkout(await body(request))); } request.resume(); return send(404, { error: 'Not found' }); })().catch((error) => { if (response.headersSent) return response.destroy(); send(error.status ?? 500, { error: error.status ? error.message : 'Internal failure', }); }); }); server.requestTimeout = 2000; server.headersTimeout = 2000; return server; }