87 lines
2.9 KiB
JavaScript
87 lines
2.9 KiB
JavaScript
/**
|
|
* Local Zapier interface simulator + trace console.
|
|
* Default http://0.0.0.0:3847
|
|
*/
|
|
import http from 'node:http';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { ACTIONS, Simulator } from './pipeline.js';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const PUBLIC = path.join(__dirname, '..', 'public');
|
|
const PORT = Number(process.env.SIM_PORT || 3847);
|
|
const BIND = process.env.SIM_BIND || '0.0.0.0';
|
|
const world = new Simulator();
|
|
|
|
const mime = {
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.js': 'text/javascript; charset=utf-8',
|
|
'.css': 'text/css; charset=utf-8',
|
|
'.json': 'application/json; charset=utf-8',
|
|
'.svg': 'image/svg+xml',
|
|
};
|
|
|
|
function send(res, code, body, type = 'application/json; charset=utf-8') {
|
|
const buf = typeof body === 'string' ? body : JSON.stringify(body, null, 2);
|
|
res.writeHead(code, { 'content-type': type, 'cache-control': 'no-store' });
|
|
res.end(buf);
|
|
}
|
|
|
|
function readBody(req) {
|
|
return new Promise((resolve, reject) => {
|
|
const chunks = [];
|
|
req.on('data', (c) => chunks.push(c));
|
|
req.on('end', () => {
|
|
const raw = Buffer.concat(chunks).toString('utf8');
|
|
if (!raw) return resolve({});
|
|
try {
|
|
resolve(JSON.parse(raw));
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
});
|
|
req.on('error', reject);
|
|
});
|
|
}
|
|
|
|
const server = http.createServer(async (req, res) => {
|
|
const url = new URL(req.url, `http://127.0.0.1:${PORT}`);
|
|
try {
|
|
if (req.method === 'GET' && url.pathname === '/health') {
|
|
return send(res, 200, { ok: true, port: PORT });
|
|
}
|
|
if (req.method === 'GET' && url.pathname === '/api/actions') {
|
|
return send(res, 200, { actions: ACTIONS, state: world.snapshot() });
|
|
}
|
|
if (req.method === 'GET' && url.pathname === '/api/state') {
|
|
return send(res, 200, world.snapshot());
|
|
}
|
|
if (req.method === 'POST' && url.pathname === '/api/reset') {
|
|
world.reset();
|
|
return send(res, 200, { ok: true, state: world.snapshot() });
|
|
}
|
|
if (req.method === 'POST' && url.pathname === '/api/run') {
|
|
const body = await readBody(req);
|
|
const result = await world.run(body);
|
|
return send(res, 200, result);
|
|
}
|
|
let file = url.pathname === '/' ? '/index.html' : url.pathname;
|
|
const dest = path.normalize(path.join(PUBLIC, file));
|
|
if (!dest.startsWith(PUBLIC)) {
|
|
return send(res, 403, { error: 'forbidden' });
|
|
}
|
|
if (!fs.existsSync(dest) || fs.statSync(dest).isDirectory()) {
|
|
return send(res, 404, { error: 'not found' });
|
|
}
|
|
const ext = path.extname(dest);
|
|
res.writeHead(200, { 'content-type': mime[ext] || 'application/octet-stream' });
|
|
fs.createReadStream(dest).pipe(res);
|
|
} catch (err) {
|
|
send(res, 500, { error: err.message });
|
|
}
|
|
});
|
|
|
|
server.listen(PORT, BIND, () => {
|
|
process.stdout.write(`Zapier simulator http://${BIND}:${PORT}/\n`);
|
|
});
|