Added browser to device websocket relay, #4172

Ylian Saint-Hilaire committed Jun 28, 2022 at 14:20 UTC b41fdec922b8b6bb9fc6d2e7fd3c0f24b7f03e1b
1 file changed +44 -9
apprelays.js
+44 -9
@@ -229,7 +229,8 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
229
230 // Process a websocket request
231 obj.processWebSocket = function (req, ws) {
232 - console.log('processWebSocket', req.url);
232 + if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; }
233 + parent.lastOperation = obj.lastOperation = Date.now();
234
235 // Mark this tunnel as being a web socket tunnel
236 obj.isWebSocket = true;
@@ -239,10 +240,9 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
240 obj.ws._socket.pause();
241
242 // Remove the trailing '/.websocket' if needed
242 - if (req.url.endsWith('/.websocket')) { req.url = req.url.substring(0, req.url.length - 11); }
243 -
244 - if (obj.relayActive == false) { console.log("ERROR: Attempt to use an unconnected tunnel"); return false; }
245 - parent.lastOperation = obj.lastOperation = Date.now();
243 + var baseurl = req.url, i = req.url.indexOf('?');
244 + if (i > 0) { baseurl = req.url.substring(0, i); }
245 + if (baseurl.endsWith('/.websocket')) { req.url = baseurl.substring(0, baseurl.length - 11) + ((i < 1) ? '' : req.url.substring(i)); }
246
247 // Construct the HTTP request and send it out
248 var request = req.method + ' ' + req.url + ' HTTP/' + req.httpVersion + '\r\n';
@@ -251,8 +251,46 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
251 for (var i in req.headers) { if (blockedHeaders.indexOf(i) == -1) { request += i + ': ' + req.headers[i] + '\r\n'; } }
252 if (parent.webCookie != null) { request += 'cookie: ' + parent.webCookie + '\r\n' } // If we have a sessin cookie, use it.
253 request += '\r\n';
254 -
254 send(Buffer.from(request));
255 +
256 + // Hook up the websocket events
257 + obj.ws.on('message', function (data) {
258 + // Setup opcode and payload
259 + var op = 2, payload = data;
260 + if (typeof data == 'string') { op = 1; payload = Buffer.from(data, 'binary'); } // Text frame
261 +
262 + // Select a random mask
263 + const mask = parent.parent.crypto.randomBytes(4)
264 +
265 + // Setup header and mask
266 + var header = null;
267 + if (data.length < 126) {
268 + header = Buffer.alloc(6); // Header (2) + Mask (4)
269 + header[0] = 0x80 + op; // FIN + OP
270 + header[1] = 0x80 + data.length; // Mask + Length
271 + mask.copy(header, 2, 0, 4); // Copy the mask
272 + } else if (data.length <= 0xFFFF) {
273 + header = Buffer.alloc(8); // Header (2) + Length (2) + Mask (4)
274 + header[0] = 0x80 + op; // FIN + OP
275 + header[1] = 0x80 + 126; // Mask + 126
276 + header.writeInt16BE(data.length, 2); // Payload size
277 + mask.copy(header, 4, 0, 4); // Copy the mask
278 + } else {
279 + header = Buffer.alloc(14); // Header (2) + Length (8) + Mask (4)
280 + header[0] = 0x80 + op; // FIN + OP
281 + header[1] = 0x80 + 127; // Mask + 127
282 + header.writeInt32BE(data.length, 6); // Payload size
283 + mask.copy(header, 10, 0, 4); // Copy the mask
284 + }
285 +
286 + // Mask the payload
287 + for (var i = 0; i < payload.length; i++) { payload[i] = (payload[i] ^ mask[i % 4]); }
288 +
289 + // Send the frame
290 + send(Buffer.concat([header, payload]));
291 + });
292 + obj.ws.on('close', function () { obj.close(); });
293 + obj.ws.on('error', function (err) { obj.close(); });
294 }
295
296 // Disconnect
@@ -407,13 +445,10 @@ module.exports.CreateWebRelay = function (parent, db, args, domain) {
445 if (obj.socketXHeader['content-length'] != null) { obj.socketParseState = 1; }
446 if ((obj.socketXHeader['transfer-encoding'] != null) && (obj.socketXHeader['transfer-encoding'].toLowerCase() == 'chunked')) { obj.socketParseState = 1; }
447 if (obj.isWebSocket) {
410 - console.log('websocket', obj.socketXHeader);
448 if ((obj.socketXHeader['connection'] != null) && (obj.socketXHeader['connection'].toLowerCase() == 'upgrade')) {
412 - console.log('websocket pass-thru');
449 obj.socketParseState = 2; // Switch to decoding websocket frames
450 obj.ws._socket.resume(); // Resume the browser's websocket
451 } else {
416 - console.log('websocket failed');
452 obj.close(); // Failed to upgrade to websocket
453 }
454 }