let net = require("net"); let fs = require("fs"); let tcpport = process.env["TCPPORT"] ? parseInt(process.env["TCPPORT"]) : 8099; let httpport = process.env["HTTPPORT"] ? parseInt(process.env["HTTPPORT"]) : 8098; let html = ` XRemoteView `; let boundary = "mjpeg-boundary"; let imgdata = fs.readFileSync("placeholder.jpg"); let waiters = []; class Reader { constructor(stream) { this.stream = stream; this.buf = Buffer.alloc(0); this.resolve = null; this.resolveMax = null; this.good = true; this.stream.on("close", () => { this.good = false; if (this.resolve) { let resolve = this.resolve; this.resolve = null; resolve(Buffer.alloc(0)); } }); this.stream.on("error", err => { console.log(this.stream.remoteAddress + ": " + err.code); }); this.stream.on("data", data => { let resolve = this.resolve; let max = this.resolveMax; if (resolve && data.length <= this.resolveMax) { this.resolve = null; resolve(data); } else if (this.resolve) { this.resolve = null; this.buf = data.slice(max); resolve(data.slice(0, max)); } else { this.buf = Buffer.concat([this.buf, data]); } }); } read(max) { if (this.buf.length > max) { let buf = this.buf.slice(0, max); this.buf = this.buf.slice(max); return Promise.resolve(buf); } else if (this.buf.length != 0) { let buf = this.buf; this.buf = Buffer.alloc(0); return buf; } else if (this.good) { this.resolveMax = max; return new Promise((resolve, reject) => { this.resolve = resolve; }); } else { return Buffer.alloc(0); } } async readAll(length) { if (!this.good) { return Buffer.alloc(0); } let buf = Buffer.alloc(0); while (buf.length < length && this.good) { let b = await this.read(length - buf.length); buf = Buffer.concat([buf, b]); } return buf; } }; async function respondToHttp(reader, conn) { let path = ""; while (true) { let ch = (await reader.readAll(1)).toString(); if (ch == " ") { break; } path += ch; } // We frankly don't care about anything else the client has to say if (path == "/image") { conn.write( "HTTP/1.1 200 OK\r\n" + "Content-Type: multipart/x-mixed-replace;boundary=" + boundary + "\r\n" + "\r\n"); conn.write( "--" + boundary + "\r\n" + "Content-Type: image/jpeg\r\n" + "\r\n"); conn.write(imgdata); conn.write( "\r\n" + "--" + boundary + "\r\n"); let idx = waiters.length; waiters.push({ w: conn, fresh: true }); conn.on("close", () => { delete waiters[idx]; }); } else { conn.write( "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Content-Type: text/html\r\n" + "\r\n"); conn.write(html); conn.end(); } } net.createServer(async conn => { console.log("Connection", conn.remoteAddress); let reader = new Reader(conn); let magic = await reader.readAll(4); if (magic.equals(Buffer.from("GET "))) { return respondToHttp(reader, conn); } let length = magic.readUInt32BE(0); let received = Buffer.alloc(0); while (reader.good) { for (let waiter of waiters) { if (waiter == null) continue; waiter.fresh = false; waiter.w.write( "Content-Type: image/jpeg\r\n" + "\r\n"); } while (length > 0) { let buf = await reader.read(length); if (buf.length == 0) { console.log(conn.remoteAddress, "disconnected."); return; } for (let waiter of waiters) { if (waiter && !waiter.fresh) { waiter.w.write(buf); } } received = Buffer.concat([received, buf]); length -= buf.length; } for (let waiter of waiters) { if (waiter && !waiter.fresh) { waiter.w.write( "\r\n" + "--" + boundary + "\r\n"); } } imgdata = received; received = await reader.readAll(4); if (received.length < 4) { console.log(conn.remoteAddress, "disconnected."); return; } length = received.readUInt32BE(0); received = Buffer.alloc(0); } }).on("error", err => console.error(err)).listen(tcpport, "0.0.0.0"); console.log(`TCP listening on 0.0.0.0:${tcpport}`);