37 lines
2.2 KiB
TypeScript
37 lines
2.2 KiB
TypeScript
/** Development relay for FIR WAV POSTs and GETs to private IPv4 devices. */
|
|
import http from 'node:http';
|
|
import type { Plugin } from 'vite';
|
|
export function firDevProxy(): Plugin {
|
|
return { name: 'fir-device-dev-proxy', apply: 'serve', configureServer(server) {
|
|
server.middlewares.use((req, res, next) => {
|
|
if (!req.url?.startsWith('/fir-device/')) return next();
|
|
const match = /^\/fir-device\/(\d+\.\d+\.\d+\.\d+)\/(left|right)$/.exec(req.url);
|
|
const octets = match?.[1].split('.').map(Number);
|
|
const privateIp = octets && octets.every(n => n >= 0 && n <= 255) &&
|
|
(octets[0] === 10 || (octets[0] === 192 && octets[1] === 168) || (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31));
|
|
if (!['GET', 'POST'].includes(req.method || '') || !match || !privateIp) { res.statusCode = 400; res.end('Invalid FIR device request'); return; }
|
|
const length = Number(req.headers['content-length']);
|
|
if (req.method === 'POST' && ![32822, 32826].includes(length)) { res.statusCode = 422; res.end('Invalid FIR payload size'); return; }
|
|
let upstreamResponse: http.IncomingMessage | undefined;
|
|
const upstream = http.request({ hostname: match[1], port: 80, method: req.method, agent: false,
|
|
path: `/dev/info.cgi?action=fir&channel=${match[2]}`,
|
|
headers: req.method === 'POST' ? { 'Content-Type': 'audio/wav', 'Content-Length': length } : {},
|
|
}, response => {
|
|
upstreamResponse = response;
|
|
res.statusCode = response.statusCode || 502;
|
|
res.setHeader('Content-Type', response.headers['content-type'] || 'text/plain');
|
|
response.pipe(res);
|
|
response.on('error', () => res.destroy());
|
|
});
|
|
upstream.setTimeout(3500, () => upstream.destroy(new Error('Device upload timed out')));
|
|
upstream.on('error', () => {
|
|
// Firmware can append bytes after Content-Length. Node reports a parse error
|
|
// for that tail even when the entire declared WAV has already arrived.
|
|
if (upstreamResponse?.complete) return;
|
|
if (!res.headersSent) { res.statusCode = 502; res.end('Device FIR upload failed'); } else res.destroy(); });
|
|
req.on('aborted', () => upstream.destroy());
|
|
req.pipe(upstream);
|
|
});
|
|
}};
|
|
}
|