summaryrefslogtreecommitdiff
path: root/ercp.js
diff options
context:
space:
mode:
Diffstat (limited to 'ercp.js')
-rw-r--r--ercp.js775
1 files changed, 422 insertions, 353 deletions
diff --git a/ercp.js b/ercp.js
index ad9d283..10ba0ae 100644
--- a/ercp.js
+++ b/ercp.js
@@ -1,10 +1,13 @@
const { WebSocketServer } = require('ws');
-const { desktopCapturer, screen } = require('electron');
+const { desktopCapturer, screen, dialog } = require('electron');
const { mouse, keyboard, Point, Key } = require("@nut-tree/nut-js");
+const axios = require('axios');
const wss = new WebSocketServer({ host: "127.0.0.1", port: 38071 });
const clients = [];
+const blocked = [];
+
keyboard.config.autoDelayMs = 0;
mouse.config.autoDelayMs = 0;
@@ -12,432 +15,498 @@ wss.on('connection', function connection(ws) {
ws._setting_latency = 1000;
ws._setting_quality = 50;
ws._setting_minimalLatency = 1000;
+ ws._setting_height = 480;
ws.screenWidth = 0;
ws.screenHeight = 0;
-
- let currentScreen = 0;
- clients.push(ws);
-
- ws.send(JSON.stringify({
- type: "command_stdout",
- value: Buffer.from("Luna version " + luna_version + "\n * Interpreter: " + require('os').userInfo().shell + "\n * Working directory: " + require('os').userInfo().homedir + "\nYou may now start entering commands.\n").toString("base64")
- }));
-
- ws.workingDirectory = require('os').userInfo().homedir;
-
- async function sendFrame() {
- if (clients.length === 0) return;
-
- let captureStart = new Date();
- let displays = screen.getAllDisplays();
- let sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: { width: 1220, height: 720 } });
- if (sources.length <= currentScreen) currentScreen = 0;
-
- ws.screenWidth = displays[currentScreen].size.width;
- ws.screenHeight = displays[currentScreen].size.height;
- let b64 = sources[currentScreen].thumbnail.toJPEG(ws._setting_quality).toString("base64");
- let captureEnd = new Date();
- let captureTime = captureEnd.getTime() - captureStart.getTime();
-
- console.log("Took " + captureTime + "ms to capture this frame");
- ws._setting_minimalLatency = captureTime * 5;
-
- let m = JSON.stringify({
- type: "image",
- time: new Date().getTime(),
- url: "data:image/jpeg;base64," + b64
- });
-
- console.log("Sent frame, packet is " + m.length + " characters");
- ws.send(m);
- }
-
- function setupClient(ws, rate) {
- if (ws._interval) clearInterval(ws._interval);
- ws._interval = setInterval(sendFrame, rate);
- }
+ ws.initiated = false;
ws.on('message', async (_data) => {
- try {
- let data = JSON.parse(_data);
+ if (ws.initiated) return;
+ ws.initiated = true;
- if (data.type === "set_latency") {
- if (data.value > ws._setting_minimalLatency) data.value = ws._setting_minimalLatency;
+ let data = JSON.parse(_data);
- console.log("Latency is now " + data.value + "ms");
- ws._setting_latency = data.value;
+ if (data._init) {
+ let address = data._init[0];
+ let name = data._init[1];
- setupClient(ws, ws._setting_latency, ws._setting_quality);
- } else if (data.type === "increase_quality") {
- if (ws._setting_quality < 90) {
- ws._setting_quality += 5;
- console.log("Quality is now " + ws._setting_quality + "%");
+ if (blocked.includes(address)) {
+ ws.send(JSON.stringify({
+ rejected: true
+ }))
+ ws.close();
+ return;
+ }
- setupClient(ws, ws._setting_latency, ws._setting_quality);
+ let ip = (await axios.get("https://api.iplocation.net/?ip=" + address)).data;
+
+ dialog.showMessageBox({
+ type: "question",
+ buttons: [
+ "Accept",
+ "Reject"
+ ],
+ title: "Incoming connection",
+ message: "Do you want to allow " + name + " to see and control your screen?",
+ detail: "This will allow " + name + " (from " + ip['country_name'] + ", " + address + ") to see all content on your screen, including personal information, and fully control your computer, including running commands in the background. Make sure you trust them before allowing them to connect.",
+ cancelId: 1,
+ checkboxLabel: "Block until computer is restarted"
+ }).then((obj) => {
+ let allow = false;
+ if (obj.response === 0 && !obj.checkboxChecked) allow = true;
+
+ if (!allow) {
+ ws.send(JSON.stringify({
+ rejected: true
+ }))
+ ws.close();
+ return;
}
- } else if (data.type === "reduce_quality") {
- if (ws._setting_quality > 10) {
- ws._setting_quality -= 5;
- console.log("Quality is now " + ws._setting_quality + "%");
- setupClient(ws, ws._setting_latency, ws._setting_quality);
+ if (obj.checkboxChecked) {
+ blocked.push(address);
}
- } else if (data.type === "move_cursor") {
- console.log("Moving cursor to X" + (data.x * ws.screenWidth) + ", Y" + (data.y * ws.screenHeight));
- await mouse.setPosition(new Point(data.x * ws.screenWidth, data.y * ws.screenHeight))
- } else if (data.type === "click") {
- console.log("Clicking button " + data.button);
-
- switch (data.button) {
- case 0:
- await mouse.click(0);
- sendFrame();
- break;
-
- case 1:
- await mouse.click(2);
- sendFrame();
- break;
-
- case 2:
- await mouse.click(1);
- sendFrame();
- break;
- }
- } else if (data.type === "start_drag") {
- console.log("Dragging started");
-
- await mouse.pressButton(0);
- } else if (data.type === "stop_drag") {
- console.log("Dragging stopped");
- await mouse.releaseButton(0);
- } else if (data.type === "keyboard") {
- if (data.key.length === 1) {
- let keys = [ data.key ];
+ setup();
+ })
+ }
+ });
- if (data.ctrl) keys.unshift(Key.LeftControl);
- if (data.alt) keys.unshift(Key.LeftAlt);
- if (data.meta) keys.unshift(Key.LeftSuper);
+ function setup() {
+ let currentScreen = 0;
+ clients.push(ws);
- if (data.shift) {
- keys[keys.length - 1] = data.key.toUpperCase();
- }
+ ws.send(JSON.stringify({
+ type: "command_stdout",
+ value: Buffer.from("Luna version " + luna_version + "\n * Interpreter: " + require('os').userInfo().shell + "\n * Working directory: " + require('os').userInfo().homedir + "\nYou may now start entering commands.\n").toString("base64")
+ }));
- await keyboard.type(...keys);
- } else {
- let keys = [];
+ ws.workingDirectory = require('os').userInfo().homedir;
- if (data.ctrl) keys.unshift(Key.LeftControl);
- if (data.alt) keys.unshift(Key.LeftAlt);
- if (data.meta) keys.unshift(Key.LeftSuper);
- if (data.shift) keys.unshift(Key.LeftShift);
+ async function sendFrame() {
+ if (clients.length === 0) return;
- switch (data.key) {
- case "Enter":
- keys.push(Key.Enter);
- await keyboard.type(...keys);
- break;
+ let captureStart = new Date();
+ let displays = screen.getAllDisplays();
+ let sources = await desktopCapturer.getSources({ types: ['screen'], thumbnailSize: { width: Math.round(ws._setting_height * (16/9)), height: ws._setting_height } });
+ if (sources.length <= currentScreen) currentScreen = 0;
- case "Tab":
- keys.push(Key.Tab);
- await keyboard.type(...keys);
- break;
+ ws.screenWidth = displays[currentScreen].size.width;
+ ws.screenHeight = displays[currentScreen].size.height;
+ let b64 = sources[currentScreen].thumbnail.toJPEG(ws._setting_quality).toString("base64");
+ let captureEnd = new Date();
+ let captureTime = captureEnd.getTime() - captureStart.getTime();
- case "Delete":
- keys.push(Key.Delete);
- await keyboard.type(...keys);
- break;
+ //console.log("Took " + captureTime + "ms to capture this frame");
+ ws._setting_minimalLatency = captureTime * 5;
- case "Insert":
- keys.push(Key.Insert);
- await keyboard.type(...keys);
- break;
+ let m = JSON.stringify({
+ type: "image",
+ time: new Date().getTime(),
+ url: "data:image/jpeg;base64," + b64
+ });
- case "Pause":
- keys.push(Key.Pause);
- await keyboard.type(...keys);
- break;
+ //console.log("Sent frame, packet is " + m.length + " characters");
+ ws.send(m);
+ }
- case "PrintScreen":
- keys.push(Key.Print);
- await keyboard.type(...keys);
- break;
+ function setupClient(ws, rate) {
+ if (ws._interval) clearInterval(ws._interval);
+ ws._interval = setInterval(sendFrame, rate);
+ }
- case "NumLock":
- keys.push(Key.NumLock);
- await keyboard.type(...keys);
- break;
+ ws.on('message', async (_data) => {
+ try {
+ let data = JSON.parse(_data);
- case "CapsLock":
- keys.push(Key.CapsLock);
- await keyboard.type(...keys);
- break;
+ if (data.type === "set_latency") {
+ if (data.value > ws._setting_minimalLatency) data.value = ws._setting_minimalLatency;
- case "ArrowLeft":
- keys.push(Key.Left);
- await keyboard.type(...keys);
- break;
-
- case "ArrowRight":
- keys.push(Key.Right);
- await keyboard.type(...keys);
- break;
+ //console.log("Latency is now " + data.value + "ms");
+ ws._setting_latency = data.value;
- case "ArrowUp":
- keys.push(Key.Up);
- await keyboard.type(...keys);
- break;
-
- case "ArrowDown":
- keys.push(Key.Down);
- await keyboard.type(...keys);
- break;
-
- case "Home":
- keys.push(Key.Home);
- await keyboard.type(...keys);
- break;
+ setupClient(ws, ws._setting_latency, ws._setting_quality);
+ } else if (data.type === "increase_quality") {
+ if (ws._setting_quality < 90) {
+ ws._setting_quality += 5;
+ //console.log("Quality is now " + ws._setting_quality + "%");
- case "End":
- keys.push(Key.End);
- await keyboard.type(...keys);
- break;
+ if (ws._setting_height < 720) {
+ ws._setting_height += 5;
+ }
- case "PageUp":
- keys.push(Key.PageUp);
- await keyboard.type(...keys);
- break;
+ setupClient(ws, ws._setting_latency, ws._setting_quality);
+ }
+ } else if (data.type === "reduce_quality") {
+ if (ws._setting_quality > 10) {
+ ws._setting_quality -= 5;
+ //console.log("Quality is now " + ws._setting_quality + "%");
- case "PageDown":
- keys.push(Key.PageDown);
- await keyboard.type(...keys);
- break;
+ if (ws._setting_height > 480) {
+ ws._setting_height -= 5;
+ }
- case "Escape":
- keys.push(Key.Escape);
- await keyboard.type(...keys);
- break;
+ setupClient(ws, ws._setting_latency, ws._setting_quality);
+ }
+ } else if (data.type === "move_cursor") {
+ //console.log("Moving cursor to X" + (data.x * ws.screenWidth) + ", Y" + (data.y * ws.screenHeight));
+ await mouse.setPosition(new Point(data.x * ws.screenWidth, data.y * ws.screenHeight))
+ } else if (data.type === "click") {
+ //console.log("Clicking button " + data.button);
- case "F1":
- keys.push(Key.F1);
- await keyboard.type(...keys);
+ switch (data.button) {
+ case 0:
+ await mouse.click(0);
+ sendFrame();
break;
- case "F2":
- keys.push(Key.F2);
- await keyboard.type(...keys);
+ case 1:
+ await mouse.click(2);
+ sendFrame();
break;
- case "F3":
- keys.push(Key.F3);
- await keyboard.type(...keys);
+ case 2:
+ await mouse.click(1);
+ sendFrame();
break;
+ }
+ } else if (data.type === "start_drag") {
+ //console.log("Dragging started");
- case "F4":
- keys.push(Key.F4);
- await keyboard.type(...keys);
- break;
+ await mouse.pressButton(0);
+ } else if (data.type === "stop_drag") {
+ //console.log("Dragging stopped");
- case "F5":
- keys.push(Key.F5);
- await keyboard.type(...keys);
- break;
+ await mouse.releaseButton(0);
+ } else if (data.type === "keyboard") {
+ if (data.key.length === 1) {
+ let keys = [ data.key ];
- case "F6":
- keys.push(Key.F6);
- await keyboard.type(...keys);
- break;
+ if (data.ctrl) keys.unshift(Key.LeftControl);
+ if (data.alt) keys.unshift(Key.LeftAlt);
+ if (data.meta) keys.unshift(Key.LeftSuper);
- case "F7":
- keys.push(Key.F7);
- await keyboard.type(...keys);
- break;
+ if (data.shift) {
+ keys[keys.length - 1] = data.key.toUpperCase();
+ }
- case "F8":
- keys.push(Key.F8);
- await keyboard.type(...keys);
- break;
+ await keyboard.type(...keys);
+ } else {
+ let keys = [];
+
+ if (data.ctrl) keys.unshift(Key.LeftControl);
+ if (data.alt) keys.unshift(Key.LeftAlt);
+ if (data.meta) keys.unshift(Key.LeftSuper);
+ if (data.shift) keys.unshift(Key.LeftShift);
+
+ switch (data.key) {
+ case "Enter":
+ keys.push(Key.Enter);
+ await keyboard.type(...keys);
+ break;
+
+ case "Tab":
+ keys.push(Key.Tab);
+ await keyboard.type(...keys);
+ break;
+
+ case "Delete":
+ keys.push(Key.Delete);
+ await keyboard.type(...keys);
+ break;
+
+ case "Insert":
+ keys.push(Key.Insert);
+ await keyboard.type(...keys);
+ break;
+
+ case "Pause":
+ keys.push(Key.Pause);
+ await keyboard.type(...keys);
+ break;
+
+ case "PrintScreen":
+ keys.push(Key.Print);
+ await keyboard.type(...keys);
+ break;
+
+ case "NumLock":
+ keys.push(Key.NumLock);
+ await keyboard.type(...keys);
+ break;
+
+ case "CapsLock":
+ keys.push(Key.CapsLock);
+ await keyboard.type(...keys);
+ break;
+
+ case "ArrowLeft":
+ keys.push(Key.Left);
+ await keyboard.type(...keys);
+ break;
+
+ case "ArrowRight":
+ keys.push(Key.Right);
+ await keyboard.type(...keys);
+ break;
+
+ case "ArrowUp":
+ keys.push(Key.Up);
+ await keyboard.type(...keys);
+ break;
+
+ case "ArrowDown":
+ keys.push(Key.Down);
+ await keyboard.type(...keys);
+ break;
+
+ case "Home":
+ keys.push(Key.Home);
+ await keyboard.type(...keys);
+ break;
+
+ case "End":
+ keys.push(Key.End);
+ await keyboard.type(...keys);
+ break;
+
+ case "PageUp":
+ keys.push(Key.PageUp);
+ await keyboard.type(...keys);
+ break;
+
+ case "PageDown":
+ keys.push(Key.PageDown);
+ await keyboard.type(...keys);
+ break;
+
+ case "Escape":
+ keys.push(Key.Escape);
+ await keyboard.type(...keys);
+ break;
+
+ case "F1":
+ keys.push(Key.F1);
+ await keyboard.type(...keys);
+ break;
+
+ case "F2":
+ keys.push(Key.F2);
+ await keyboard.type(...keys);
+ break;
+
+ case "F3":
+ keys.push(Key.F3);
+ await keyboard.type(...keys);
+ break;
+
+ case "F4":
+ keys.push(Key.F4);
+ await keyboard.type(...keys);
+ break;
+
+ case "F5":
+ keys.push(Key.F5);
+ await keyboard.type(...keys);
+ break;
+
+ case "F6":
+ keys.push(Key.F6);
+ await keyboard.type(...keys);
+ break;
+
+ case "F7":
+ keys.push(Key.F7);
+ await keyboard.type(...keys);
+ break;
+
+ case "F8":
+ keys.push(Key.F8);
+ await keyboard.type(...keys);
+ break;
+
+ case "F9":
+ keys.push(Key.F9);
+ await keyboard.type(...keys);
+ break;
+
+ case "F10":
+ keys.push(Key.F10);
+ await keyboard.type(...keys);
+ break;
+
+ case "F11":
+ keys.push(Key.F11);
+ await keyboard.type(...keys);
+ break;
+
+ case "F12":
+ keys.push(Key.F12);
+ await keyboard.type(...keys);
+ break;
+
+ case "OS":
+ keys.push(Key.LeftSuper);
+ await keyboard.type(...keys);
+ break;
+
+ case "Backspace":
+ keys.push(Key.Backspace);
+ await keyboard.type(...keys);
+ break;
+
+ default:
+ //console.log("Missing corresponding action for key " + data.key);
+ break;
+ }
+ }
+ } else if (data.type === "command") {
+ ws.send(JSON.stringify({
+ type: "command_start"
+ }));
- case "F9":
- keys.push(Key.F9);
- await keyboard.type(...keys);
- break;
+ if (Buffer.from(data.value, "base64").toString().startsWith("cd ") || Buffer.from(data.value, "base64").toString().startsWith("chdir ")) {
+ try {
+ let path;
- case "F10":
- keys.push(Key.F10);
- await keyboard.type(...keys);
- break;
+ if (Buffer.from(data.value, "base64").toString().startsWith("cd ")) {
+ path = require('path').resolve(ws.workingDirectory + "/" + Buffer.from(data.value, "base64").toString().substring(3));
+ } else {
+ path = require('path').resolve(ws.workingDirectory + "/" + Buffer.from(data.value, "base64").toString().substring(6));
+ }
- case "F11":
- keys.push(Key.F11);
- await keyboard.type(...keys);
- break;
+ if (!require('fs').existsSync(path)) {
+ ws.send(JSON.stringify({
+ type: "command_stderr",
+ value: Buffer.from(path + ": no such file or directory\n").toString("base64")
+ }), () => {
+ ws.send(JSON.stringify({
+ type: "command_stop",
+ code: 1
+ }));
+ });
+ return;
+ }
+
+ if ((!require('fs').lstatSync(path).isSymbolicLink() && !require('fs').lstatSync(path).isDirectory()) || (require('fs').lstatSync(path).isSymbolicLink() && !require('fs').lstatSync(require('fs').readlinkSync(path)).isDirectory())) {
+ ws.send(JSON.stringify({
+ type: "command_stderr",
+ value: Buffer.from(path + ": not a directory\n").toString("base64")
+ }), () => {
+ ws.send(JSON.stringify({
+ type: "command_stop",
+ code: 1
+ }));
+ });
+ return;
+ }
+
+ ws.workingDirectory = path;
- case "F12":
- keys.push(Key.F12);
- await keyboard.type(...keys);
- break;
+ ws.send(JSON.stringify({
+ type: "command_stdout",
+ value: Buffer.from(" * Working directory: " + ws.workingDirectory + "\n").toString("base64")
+ }));
- case "OS":
- keys.push(Key.LeftSuper);
- await keyboard.type(...keys);
- break;
+ ws.send(JSON.stringify({
+ type: "command_stop",
+ code: 0
+ }));
+ } catch (e) {
+ console.error(e);
- case "Backspace":
- keys.push(Key.Backspace);
- await keyboard.type(...keys);
- break;
+ ws.send(JSON.stringify({
+ type: "command_stop",
+ code: null,
+ signal: e.name
+ }));
+ }
- default:
- console.log("Missing corresponding action for key " + data.key);
- break;
+ return;
}
- }
- } else if (data.type === "command") {
- ws.send(JSON.stringify({
- type: "command_start"
- }));
-
- if (Buffer.from(data.value, "base64").toString().startsWith("cd ") || Buffer.from(data.value, "base64").toString().startsWith("chdir ")) {
- try {
- let path;
- if (Buffer.from(data.value, "base64").toString().startsWith("cd ")) {
- path = require('path').resolve(ws.workingDirectory + "/" + Buffer.from(data.value, "base64").toString().substring(3));
- } else {
- path = require('path').resolve(ws.workingDirectory + "/" + Buffer.from(data.value, "base64").toString().substring(6));
- }
-
- if (!require('fs').existsSync(path)) {
+ if (Buffer.from(data.value, "base64").toString().startsWith("pwd ") || Buffer.from(data.value, "base64").toString().trim() === "pwd") {
+ ws.send(JSON.stringify({
+ type: "command_start"
+ }), () => {
ws.send(JSON.stringify({
- type: "command_stderr",
- value: Buffer.from(path + ": no such file or directory\n").toString("base64")
+ type: "command_stdout",
+ value: Buffer.from(ws.workingDirectory + "\n").toString("base64")
}), () => {
ws.send(JSON.stringify({
type: "command_stop",
- code: 1
+ code: 0
}));
});
- return;
- }
+ });
- if ((!require('fs').lstatSync(path).isSymbolicLink() && !require('fs').lstatSync(path).isDirectory()) || (require('fs').lstatSync(path).isSymbolicLink() && !require('fs').lstatSync(require('fs').readlinkSync(path)).isDirectory())) {
- ws.send(JSON.stringify({
- type: "command_stderr",
- value: Buffer.from(path + ": not a directory\n").toString("base64")
- }), () => {
- ws.send(JSON.stringify({
- type: "command_stop",
- code: 1
- }));
- });
- return;
- }
+ return;
+ }
+
+ ws.cmd = require('child_process').exec(Buffer.from(data.value, "base64").toString(), { cwd: ws.workingDirectory });
- ws.workingDirectory = path;
+ ws.send(JSON.stringify({
+ type: "command_start"
+ }));
+
+ ws.cmd.stdout.on('data', (data) => {
+ let send;
+
+ if (data instanceof Buffer) {
+ send = data.toString("base64")
+ } else {
+ send = Buffer.from(data).toString("base64");
+ }
ws.send(JSON.stringify({
type: "command_stdout",
- value: Buffer.from(" * Working directory: " + ws.workingDirectory + "\n").toString("base64")
+ value: send
}));
+ })
- ws.send(JSON.stringify({
- type: "command_stop",
- code: 0
- }));
- } catch (e) {
- console.error(e);
+ ws.cmd.stderr.on('data', (data) => {
+ let send;
+
+ if (data instanceof Buffer) {
+ send = data.toString("base64")
+ } else {
+ send = Buffer.from(data).toString("base64");
+ }
ws.send(JSON.stringify({
- type: "command_stop",
- code: null,
- signal: e.name
+ type: "command_stderr",
+ value: send
}));
- }
+ })
- return;
- }
-
- if (Buffer.from(data.value, "base64").toString().startsWith("pwd ") || Buffer.from(data.value, "base64").toString().trim() === "pwd") {
- ws.send(JSON.stringify({
- type: "command_start"
- }), () => {
- ws.send(JSON.stringify({
- type: "command_stdout",
- value: Buffer.from(ws.workingDirectory + "\n").toString("base64")
- }), () => {
+ ws.cmd.on('close', (code, signal) => {
+ setTimeout(() => {
+ delete ws.cmd;
ws.send(JSON.stringify({
type: "command_stop",
- code: 0
+ code,
+ signal
}));
- });
- });
-
- return;
- }
-
- ws.cmd = require('child_process').exec(Buffer.from(data.value, "base64").toString(), { cwd: ws.workingDirectory });
-
- ws.send(JSON.stringify({
- type: "command_start"
- }));
-
- ws.cmd.stdout.on('data', (data) => {
- let send;
-
- if (data instanceof Buffer) {
- send = data.toString("base64")
+ }, 100);
+ })
+ } else if (data.type === "halt") {
+ if (ws.cmd) {
+ ws.cmd.kill("SIGKILL");
} else {
- send = Buffer.from(data).toString("base64");
+ console.log("Attempted to stop nonexistant command");
}
-
- ws.send(JSON.stringify({
- type: "command_stdout",
- value: send
- }));
- })
-
- ws.cmd.stderr.on('data', (data) => {
- let send;
-
- if (data instanceof Buffer) {
- send = data.toString("base64")
- } else {
- send = Buffer.from(data).toString("base64");
- }
-
- ws.send(JSON.stringify({
- type: "command_stderr",
- value: send
- }));
- })
-
- ws.cmd.on('close', (code, signal) => {
- setTimeout(() => {
- delete ws.cmd;
- ws.send(JSON.stringify({
- type: "command_stop",
- code,
- signal
- }));
- }, 100);
- })
- } else if (data.type === "halt") {
- if (ws.cmd) {
- ws.cmd.kill("SIGKILL");
}
- }
- } catch (e) {}
- });
+ } catch (e) {}
+ });
- ws.on('close', () => {
- clients.splice(clients.indexOf(ws), 1);
+ ws.on('close', () => {
+ clients.splice(clients.indexOf(ws), 1);
- if (ws.cmd) {
- ws.cmd.kill("SIGKILL");
- }
- })
+ if (ws.cmd) {
+ ws.cmd.kill("SIGKILL");
+ }
+ })
- setupClient(ws, ws._setting_latency, ws._setting_quality);
+ setupClient(ws, ws._setting_latency, ws._setting_quality);
+ }
}); \ No newline at end of file