GuacdClient.js (5693B)
1const Net = require('net'); 2 3class GuacdClient { 4 5 /** 6 * 7 * @param {Server} server 8 * @param {ClientConnection} clientConnection 9 */ 10 constructor(server, clientConnection) { 11 this.STATE_OPENING = 0; 12 this.STATE_OPEN = 1; 13 this.STATE_CLOSED = 2; 14 15 this.state = this.STATE_OPENING; 16 17 this.server = server; 18 this.clientConnection = clientConnection; 19 this.handshakeReplySent = false; 20 this.receivedBuffer = ''; 21 this.lastActivity = Date.now(); 22 23 this.guacdConnection = Net.connect(server.guacdOptions.port, server.guacdOptions.host); 24 25 this.guacdConnection.on('connect', this.processConnectionOpen.bind(this)); 26 this.guacdConnection.on('data', this.processReceivedData.bind(this)); 27 this.guacdConnection.on('close', this.clientConnection.close.bind(this.clientConnection)); 28 this.guacdConnection.on('error', this.clientConnection.error.bind(this.clientConnection)); 29 30 this.activityCheckInterval = setInterval(this.checkActivity.bind(this), 1000); 31 } 32 33 checkActivity() { 34 if (Date.now() > (this.lastActivity + 10000)) { 35 this.clientConnection.close(new Error('guacd was inactive for too long')); 36 } 37 } 38 39 close(error) { 40 if (this.state == this.STATE_CLOSED) { 41 return; 42 } 43 44 if (error) { 45 this.clientConnection.log(this.server.LOGLEVEL.ERRORS, error); 46 } 47 48 this.log(this.server.LOGLEVEL.VERBOSE, 'Closing guacd connection'); 49 clearInterval(this.activityCheckInterval); 50 51 this.guacdConnection.removeAllListeners('close'); 52 this.guacdConnection.end(); 53 this.guacdConnection.destroy(); 54 55 this.state = this.STATE_CLOSED; 56 this.server.emit('close', this.clientConnection); 57 } 58 59 send(data) { 60 if (this.state == this.STATE_CLOSED) { 61 return; 62 } 63 64 this.log(this.server.LOGLEVEL.DEBUG, '<<<W2G< ' + data + '***'); 65 this.guacdConnection.write(data); 66 } 67 68 log(level, ...args) { 69 this.clientConnection.log(level, ...args); 70 } 71 72 processConnectionOpen() { 73 this.log(this.server.LOGLEVEL.VERBOSE, 'guacd connection open'); 74 75 this.log(this.server.LOGLEVEL.VERBOSE, 'Selecting connection type: ' + this.clientConnection.connectionType); 76 this.sendOpCode(['select', this.clientConnection.connectionType]); 77 } 78 79 sendHandshakeReply() { 80 this.sendOpCode([ 81 'size', 82 this.clientConnection.connectionSettings.connection.width, 83 this.clientConnection.connectionSettings.connection.height, 84 this.clientConnection.connectionSettings.connection.dpi 85 ]); 86 this.sendOpCode(['audio'].concat(this.clientConnection.query.GUAC_AUDIO || [])); 87 this.sendOpCode(['video'].concat(this.clientConnection.query.GUAC_VIDEO || [])); 88 this.sendOpCode(['image']); 89 90 let serverHandshake = this.getFirstOpCodeFromBuffer(); 91 92 this.log(this.server.LOGLEVEL.VERBOSE, 'Server sent handshake: ' + serverHandshake); 93 94 serverHandshake = serverHandshake.split(','); 95 let connectionOptions = []; 96 97 serverHandshake.forEach((attribute) => { 98 connectionOptions.push(this.getConnectionOption(attribute)); 99 }); 100 101 this.sendOpCode(connectionOptions); 102 103 this.handshakeReplySent = true; 104 105 if (this.state != this.STATE_OPEN) { 106 this.state = this.STATE_OPEN; 107 this.server.emit('open', this.clientConnection); 108 } 109 } 110 111 getConnectionOption(optionName) { 112 return this.clientConnection.connectionSettings.connection[this.constructor.parseOpCodeAttribute(optionName)] || null 113 } 114 115 getFirstOpCodeFromBuffer() { 116 let delimiterPos = this.receivedBuffer.indexOf(';'); 117 let opCode = this.receivedBuffer.substring(0, delimiterPos); 118 119 this.receivedBuffer = this.receivedBuffer.substring(delimiterPos + 1, this.receivedBuffer.length); 120 121 return opCode; 122 } 123 124 sendOpCode(opCode) { 125 opCode = this.constructor.formatOpCode(opCode); 126 this.log(this.server.LOGLEVEL.VERBOSE, 'Sending opCode: ' + opCode); 127 this.send(opCode); 128 } 129 130 static formatOpCode(opCodeParts) { 131 opCodeParts.forEach((part, index, opCodeParts) => { 132 part = this.stringifyOpCodePart(part); 133 opCodeParts[index] = part.length + '.' + part; 134 }); 135 136 return opCodeParts.join(',') + ';'; 137 } 138 139 static stringifyOpCodePart(part) { 140 if (part === null) { 141 part = ''; 142 } 143 144 return String(part); 145 } 146 147 static parseOpCodeAttribute(opCodeAttribute) { 148 return opCodeAttribute.substring(opCodeAttribute.indexOf('.') + 1, opCodeAttribute.length); 149 } 150 151 processReceivedData(data) { 152 this.receivedBuffer += data; 153 this.lastActivity = Date.now(); 154 155 if (!this.handshakeReplySent) { 156 if (this.receivedBuffer.indexOf(';') === -1) { 157 return; // incomplete handshake received from guacd. Will wait for the next part 158 } else { 159 this.sendHandshakeReply(); 160 } 161 } 162 163 this.sendBufferToWebSocket(); 164 } 165 166 sendBufferToWebSocket() { 167 const delimiterPos = this.receivedBuffer.lastIndexOf(';'); 168 const bufferPartToSend = this.receivedBuffer.substring(0, delimiterPos + 1); 169 170 if (bufferPartToSend) { 171 this.receivedBuffer = this.receivedBuffer.substring(delimiterPos + 1, this.receivedBuffer.length); 172 this.clientConnection.send(bufferPartToSend); 173 } 174 } 175 176 177} 178 179module.exports = GuacdClient;