1/* 2Copyright 2012 Google Inc. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15 16Author: Boris Smus (smus@chromium.org) 17*/ 18 19(function(exports) { 20 21 // Define some local variables here. 22 var socket = chrome.socket || chrome.experimental.socket; 23 var dns = chrome.experimental.dns; 24 25 /** 26 * Creates an instance of the client 27 * 28 * @param {String} host The remote host to connect to 29 * @param {Number} port The port to connect to at the remote host 30 */ 31 function TcpClient(host, port, pollInterval) { 32 this.host = host; 33 this.port = port; 34 this.pollInterval = pollInterval || 15; 35 36 // Callback functions. 37 this.callbacks = { 38 connect: null, // Called when socket is connected. 39 disconnect: null, // Called when socket is disconnected. 40 recvBuffer: null, // Called (as ArrayBuffer) when client receives data from server. 41 recvString: null, // Called (as string) when client receives data from server. 42 sent: null // Called when client sends data to server. 43 }; 44 45 // Socket. 46 this.socketId = null; 47 this.isConnected = false; 48 49 log('initialized tcp client'); 50 } 51 52 /** 53 * Connects to the TCP socket, and creates an open socket. 54 * 55 * @see http://developer.chrome.com/trunk/apps/socket.html#method-create 56 * @param {Function} callback The function to call on connection 57 */ 58 TcpClient.prototype.connect = function(callback) { 59 // First resolve the hostname to an IP. 60 dns.resolve(this.host, function(result) { 61 this.addr = result.address; 62 socket.create('tcp', {}, this._onCreate.bind(this)); 63 64 // Register connect callback. 65 this.callbacks.connect = callback; 66 }.bind(this)); 67 }; 68 69 /** 70 * Sends an arraybuffer/view down the wire to the remote side 71 * 72 * @see http://developer.chrome.com/trunk/apps/socket.html#method-write 73 * @param {String} msg The arraybuffer/view to send 74 * @param {Function} callback The function to call when the message has sent 75 */ 76 TcpClient.prototype.sendBuffer = function(buf, callback) { 77 if (buf.buffer) { 78 buf = buf.buffer; 79 } 80 81 /* 82 // Debug 83 var bytes = [], u8 = new Uint8Array(buf); 84 for (var i = 0; i < u8.length; i++) { 85 bytes.push(u8[i]); 86 } 87 log("sending bytes: " + (bytes.join(','))); 88 */ 89 90 socket.write(this.socketId, buf, this._onWriteComplete.bind(this)); 91 92 // Register sent callback. 93 this.callbacks.sent = callback; 94 }; 95 96 /** 97 * Sends a string down the wire to the remote side 98 * 99 * @see http://developer.chrome.com/trunk/apps/socket.html#method-write 100 * @param {String} msg The string to send 101 * @param {Function} callback The function to call when the message has sent 102 */ 103 TcpClient.prototype.sendString = function(msg, callback) { 104 /* 105 // Debug 106 log("sending string: " + msg); 107 */ 108 109 this._stringToArrayBuffer(msg, function(arrayBuffer) { 110 socket.write(this.socketId, arrayBuffer, this._onWriteComplete.bind(this)); 111 }.bind(this)); 112 113 // Register sent callback. 114 this.callbacks.sent = callback; 115 }; 116 117 /** 118 * Sets the callback for when a message is received 119 * 120 * @param {Function} callback The function to call when a message has arrived 121 * @param {String} type The callback argument type: "arraybuffer" or "string" 122 */ 123 TcpClient.prototype.addResponseListener = function(callback, type) { 124 if (typeof type === "undefined") { 125 type = "arraybuffer"; 126 } 127 // Register received callback. 128 if (type === "string") { 129 this.callbacks.recvString = callback; 130 } else { 131 this.callbacks.recvBuffer = callback; 132 } 133 }; 134 135 /** 136 * Sets the callback for when the socket disconnects 137 * 138 * @param {Function} callback The function to call when the socket disconnects 139 * @param {String} type The callback argument type: "arraybuffer" or "string" 140 */ 141 TcpClient.prototype.addDisconnectListener = function(callback) { 142 // Register disconnect callback. 143 this.callbacks.disconnect = callback; 144 }; 145 146 /** 147 * Disconnects from the remote side 148 * 149 * @see http://developer.chrome.com/trunk/apps/socket.html#method-disconnect 150 */ 151 TcpClient.prototype.disconnect = function() { 152 if (this.isConnected) { 153 this.isConnected = false; 154 socket.disconnect(this.socketId); 155 if (this.callbacks.disconnect) { 156 this.callbacks.disconnect(); 157 } 158 log('socket disconnected'); 159 } 160 }; 161 162 /** 163 * The callback function used for when we attempt to have Chrome 164 * create a socket. If the socket is successfully created 165 * we go ahead and connect to the remote side. 166 * 167 * @private 168 * @see http://developer.chrome.com/trunk/apps/socket.html#method-connect 169 * @param {Object} createInfo The socket details 170 */ 171 TcpClient.prototype._onCreate = function(createInfo) { 172 this.socketId = createInfo.socketId; 173 if (this.socketId > 0) { 174 socket.connect(this.socketId, this.addr, this.port, this._onConnectComplete.bind(this)); 175 } else { 176 error('Unable to create socket'); 177 } 178 }; 179 180 /** 181 * The callback function used for when we attempt to have Chrome 182 * connect to the remote side. If a successful connection is 183 * made then polling starts to check for data to read 184 * 185 * @private 186 * @param {Number} resultCode Indicates whether the connection was successful 187 */ 188 TcpClient.prototype._onConnectComplete = function(resultCode) { 189 // Start polling for reads. 190 this.isConnected = true; 191 setTimeout(this._periodicallyRead.bind(this), this.pollInterval); 192 193 if (this.callbacks.connect) { 194 log('connect complete'); 195 this.callbacks.connect(); 196 } 197 log('onConnectComplete'); 198 }; 199 200 /** 201 * Checks for new data to read from the socket 202 * 203 * @see http://developer.chrome.com/trunk/apps/socket.html#method-read 204 */ 205 TcpClient.prototype._periodicallyRead = function() { 206 var that = this; 207 socket.getInfo(this.socketId, function (info) { 208 if (info.connected) { 209 setTimeout(that._periodicallyRead.bind(that), that.pollInterval); 210 socket.read(that.socketId, null, that._onDataRead.bind(that)); 211 } else if (that.isConnected) { 212 log('socket disconnect detected'); 213 that.disconnect(); 214 } 215 }); 216 }; 217 218 /** 219 * Callback function for when data has been read from the socket. 220 * Converts the array buffer that is read in to a string 221 * and sends it on for further processing by passing it to 222 * the previously assigned callback function. 223 * 224 * @private 225 * @see TcpClient.prototype.addResponseListener 226 * @param {Object} readInfo The incoming message 227 */ 228 TcpClient.prototype._onDataRead = function(readInfo) { 229 // Call received callback if there's data in the response. 230 if (readInfo.resultCode > 0) { 231 log('onDataRead'); 232 233 /* 234 // Debug 235 var bytes = [], u8 = new Uint8Array(readInfo.data); 236 for (var i = 0; i < u8.length; i++) { 237 bytes.push(u8[i]); 238 } 239 log("received bytes: " + (bytes.join(','))); 240 */ 241 242 if (this.callbacks.recvBuffer) { 243 // Return raw ArrayBuffer directly. 244 this.callbacks.recvBuffer(readInfo.data); 245 } 246 if (this.callbacks.recvString) { 247 // Convert ArrayBuffer to string. 248 this._arrayBufferToString(readInfo.data, function(str) { 249 this.callbacks.recvString(str); 250 }.bind(this)); 251 } 252 253 // Trigger another read right away 254 setTimeout(this._periodicallyRead.bind(this), 0); 255 } 256 }; 257 258 /** 259 * Callback for when data has been successfully 260 * written to the socket. 261 * 262 * @private 263 * @param {Object} writeInfo The outgoing message 264 */ 265 TcpClient.prototype._onWriteComplete = function(writeInfo) { 266 log('onWriteComplete'); 267 // Call sent callback. 268 if (this.callbacks.sent) { 269 this.callbacks.sent(writeInfo); 270 } 271 }; 272 273 /** 274 * Converts an array buffer to a string 275 * 276 * @private 277 * @param {ArrayBuffer} buf The buffer to convert 278 * @param {Function} callback The function to call when conversion is complete 279 */ 280 TcpClient.prototype._arrayBufferToString = function(buf, callback) { 281 var bb = new Blob([new Uint8Array(buf)]); 282 var f = new FileReader(); 283 f.onload = function(e) { 284 callback(e.target.result); 285 }; 286 f.readAsText(bb); 287 }; 288 289 /** 290 * Converts a string to an array buffer 291 * 292 * @private 293 * @param {String} str The string to convert 294 * @param {Function} callback The function to call when conversion is complete 295 */ 296 TcpClient.prototype._stringToArrayBuffer = function(str, callback) { 297 var bb = new Blob([str]); 298 var f = new FileReader(); 299 f.onload = function(e) { 300 callback(e.target.result); 301 }; 302 f.readAsArrayBuffer(bb); 303 }; 304 305 /** 306 * Wrapper function for logging 307 */ 308 function log(msg) { 309 console.log(msg); 310 } 311 312 /** 313 * Wrapper function for error logging 314 */ 315 function error(msg) { 316 console.error(msg); 317 } 318 319 exports.TcpClient = TcpClient; 320 321})(window); 322