|
|
@@ -1,5 +1,5 @@ |
|
|
|
let http = require("http"); |
|
|
|
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; |
|
|
@@ -11,84 +11,199 @@ let html = ` |
|
|
|
<meta charset="utf-8"> |
|
|
|
<title>XRemoteView</title> |
|
|
|
</head> |
|
|
|
<body> |
|
|
|
<img id="image" src="/image" style="object-fit: contain; height: 100vh; width: 100%;"> |
|
|
|
<body style="margin: 0px"> |
|
|
|
<img |
|
|
|
src="/image" |
|
|
|
style="display: block; object-fit: contain; height: 100vh; width: 100%" |
|
|
|
ondblclick="fullscreen(event.target)"> |
|
|
|
<script> |
|
|
|
let img = document.getElementById("image"); |
|
|
|
let url = img.src; |
|
|
|
|
|
|
|
function refresh() { |
|
|
|
img.onload = wait; |
|
|
|
img.onerror = () => { |
|
|
|
console.error("Load error"); |
|
|
|
setTimeout(refresh, 4000); |
|
|
|
}; |
|
|
|
|
|
|
|
let rand = Math.floor(Math.random() * 1000000); |
|
|
|
img.src = url + "?" + rand; |
|
|
|
} |
|
|
|
|
|
|
|
function wait() { |
|
|
|
fetch("/wait").then(refresh).catch(err => { |
|
|
|
console.error("Wait error:", err); |
|
|
|
wait(); |
|
|
|
}); |
|
|
|
function fullscreen(el) { |
|
|
|
if (document.fullscreenElement) { |
|
|
|
document.exitFullscreen(); |
|
|
|
} else if (el.requestFullscreen) { |
|
|
|
el.requestFullscreen(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
wait(); |
|
|
|
</script> |
|
|
|
</body> |
|
|
|
</html> |
|
|
|
`; |
|
|
|
|
|
|
|
let imgdata = Buffer.alloc(0); |
|
|
|
let boundary = "mjpeg-boundary"; |
|
|
|
|
|
|
|
let imgdata = fs.readFileSync("placeholder.jpg"); |
|
|
|
let waiters = []; |
|
|
|
|
|
|
|
net.createServer(conn => { |
|
|
|
console.log("Connection", conn.remoteAddress); |
|
|
|
class Reader { |
|
|
|
constructor(stream) { |
|
|
|
this.stream = stream; |
|
|
|
this.buf = Buffer.alloc(0); |
|
|
|
this.resolve = null; |
|
|
|
this.resolveMax = null; |
|
|
|
this.good = true; |
|
|
|
|
|
|
|
received = Buffer.alloc(0); |
|
|
|
let hasLength = false; |
|
|
|
let expectedLen = 0; |
|
|
|
conn.on("data", data => { |
|
|
|
received = Buffer.concat([received, data]); |
|
|
|
|
|
|
|
while (true) { |
|
|
|
if (!hasLength && received.length >= 4) { |
|
|
|
expectedLen = received.readUInt32BE(0); |
|
|
|
received = received.slice(4); |
|
|
|
hasLength = true; |
|
|
|
this.stream.on("close", () => { |
|
|
|
this.good = false; |
|
|
|
if (this.resolve) { |
|
|
|
let resolve = this.resolve; |
|
|
|
this.resolve = null; |
|
|
|
resolve(Buffer.alloc(0)); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
if (hasLength && received.length >= expectedLen) { |
|
|
|
imgdata = received.slice(0, expectedLen); |
|
|
|
received = received.slice(expectedLen); |
|
|
|
hasLength = false; |
|
|
|
|
|
|
|
for (let waiter of waiters) { |
|
|
|
waiter.end(); |
|
|
|
} |
|
|
|
this.stream.on("error", err => { |
|
|
|
console.log(this.stream.remoteAddress + ": " + err.code); |
|
|
|
}); |
|
|
|
|
|
|
|
waiters = []; |
|
|
|
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 { |
|
|
|
break; |
|
|
|
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); |
|
|
|
} |
|
|
|
}); |
|
|
|
}).on("error", err => console.error(err)).listen(tcpport, "0.0.0.0"); |
|
|
|
console.log(`TCP listening on 0.0.0.0:${tcpport}`); |
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
http.createServer((req, res) => { |
|
|
|
if (req.url.split("?")[0] == "/image") { |
|
|
|
res.writeHead(200, { |
|
|
|
"Content-Type": "image/jpeg", |
|
|
|
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]; |
|
|
|
}); |
|
|
|
res.end(imgdata); |
|
|
|
} else if (req.url.split("?")[0] == "/wait") { |
|
|
|
waiters.push(res); |
|
|
|
} else { |
|
|
|
res.writeHead(200); |
|
|
|
res.end(html); |
|
|
|
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); |
|
|
|
} |
|
|
|
}).listen(httpport, "0.0.0.0"); |
|
|
|
console.log(`HTTP listening on 0.0.0.0:${httpport}`); |
|
|
|
}).on("error", err => console.error(err)).listen(tcpport, "0.0.0.0"); |
|
|
|
console.log(`TCP listening on 0.0.0.0:${tcpport}`); |