| 1 | /* |
| 2 | Copyright 2012 Google Inc. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | |
| 16 | Author: Boris Smus (smus@chromium.org) |
| 17 | */ |
| 18 | |
| 19 | (function(exports) { |
| 20 | |
| 21 | // Define some local variables here. |
| 22 | var socket = chrome.socket; |
| 23 | |
| 24 | /** |
| 25 | * Creates an instance of the client |
| 26 | * |
| 27 | * @param {String} host The remote host to connect to |
| 28 | * @param {Number} port The port to connect to at the remote host |
| 29 | */ |
| 30 | function TcpClient(host, port) { |
| 31 | this.host = host; |
| 32 | this.port = port; |
| 33 | |
| 34 | // Callback functions. |
| 35 | this.callbacks = { |
| 36 | connect: null, // Called when socket is connected. |
| 37 | disconnect: null, // Called when socket is disconnected. |
| 38 | recv: null, // Called when client receives data from server. |
| 39 | sent: null // Called when client sends data to server. |
| 40 | }; |
| 41 | |
| 42 | // Socket. |
| 43 | this.socketId = null; |
| 44 | this.isConnected = false; |
| 45 | this.pollerId = null; |
| 46 | |
| 47 | log('initialized tcp client'); |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * Connects to the TCP socket, and creates an open socket. |
| 52 | * |
| 53 | * @see http://developer.chrome.com/apps/socket.html#method-create |
| 54 | * @param {Function} callback The function to call on connection |
| 55 | */ |
| 56 | TcpClient.prototype.connect = function(callback) { |
| 57 | socket.create('tcp', {}, this._onCreate.bind(this)); |
| 58 | |
| 59 | // Register connect callback. |
| 60 | this.callbacks.connect = callback; |
| 61 | }; |
| 62 | |
| 63 | /** |
| 64 | * Sends a message down the wire to the remote side |
| 65 | * |
| 66 | * @see http://developer.chrome.com/apps/socket.html#method-write |
| 67 | * @param {String} msg The message to send |
| 68 | * @param {Function} callback The function to call when the message has sent |
| 69 | */ |
| 70 | TcpClient.prototype.sendMessage = function(msg, callback) { |
| 71 | this._stringToArrayBuffer(msg + '\n', function(arrayBuffer) { |
| 72 | socket.write(this.socketId, arrayBuffer, this._onWriteComplete.bind(this)); |
| 73 | }.bind(this)); |
| 74 | |
| 75 | // Register sent callback. |
| 76 | this.callbacks.sent = callback; |
| 77 | }; |
| 78 | |
| 79 | /** |
| 80 | * Sets the callback for when a message is received |
| 81 | * |
| 82 | * @param {Function} callback The function to call when a message has arrived |
| 83 | */ |
| 84 | TcpClient.prototype.addResponseListener = function(callback) { |
| 85 | // Register received callback. |
| 86 | this.callbacks.recv = callback; |
| 87 | }; |
| 88 | |
| 89 | /** |
| 90 | * Disconnects from the remote side |
| 91 | * |
| 92 | * @see http://developer.chrome.com/apps/socket.html#method-disconnect |
| 93 | */ |
| 94 | TcpClient.prototype.disconnect = function() { |
| 95 | socket.disconnect(this.socketId); |
| 96 | if (this.isConnected) { |
| 97 | clearInterval(this.pollerId); |
| 98 | this.pollerId = null; |
| 99 | this.isConnected = false; |
| 100 | } |
| 101 | }; |
| 102 | |
| 103 | /** |
| 104 | * The callback function used for when we attempt to have Chrome |
| 105 | * create a socket. If the socket is successfully created |
| 106 | * we go ahead and connect to the remote side. |
| 107 | * |
| 108 | * @private |
| 109 | * @see http://developer.chrome.com/apps/socket.html#method-connect |
| 110 | * @param {Object} createInfo The socket details |
| 111 | */ |
| 112 | TcpClient.prototype._onCreate = function(createInfo) { |
| 113 | if (this.socketId !== null) { |
| 114 | socket.destroy(this.socketId); |
| 115 | } |
| 116 | this.socketId = createInfo.socketId; |
| 117 | if (this.socketId > 0) { |
| 118 | socket.connect(this.socketId, this.host, this.port, this._onConnectComplete.bind(this)); |
| 119 | } else { |
| 120 | error('Unable to create socket'); |
| 121 | } |
| 122 | }; |
| 123 | |
| 124 | /** |
| 125 | * The callback function used for when we attempt to have Chrome |
| 126 | * connect to the remote side. If a successful connection is |
| 127 | * made then polling starts to check for data to read |
| 128 | * |
| 129 | * @private |
| 130 | * @param {Number} resultCode Indicates whether the connection was successful |
| 131 | */ |
| 132 | TcpClient.prototype._onConnectComplete = function(resultCode) { |
| 133 | log('resultCode: ' + resultCode); |
| 134 | |
| 135 | // XXX: Can this ever be positive ? |
| 136 | this.isConnected = (resultCode >= 0); |
| 137 | |
| 138 | // Start polling for reads. |
| 139 | clearInterval(this.pollerId); |
| 140 | if (this.isConnected) { |
| 141 | this.pollerId = setInterval(this.poll.bind(this), 500); |
| 142 | } |
| 143 | |
| 144 | if (this.callbacks.connect) { |
| 145 | log('connect complete'); |
| 146 | this.callbacks.connect(resultCode); |
| 147 | } |
| 148 | log('onConnectComplete'); |
| 149 | }; |
| 150 | |
| 151 | /** |
| 152 | * Checks for new data to read from the socket |
| 153 | * |
| 154 | * @see http://developer.chrome.com/apps/socket.html#method-read |
| 155 | */ |
| 156 | TcpClient.prototype.poll = function() { |
| 157 | socket.read(this.socketId, null, this._onDataRead.bind(this)); |
| 158 | }; |
| 159 | |
| 160 | /** |
| 161 | * Callback function for when data has been read from the socket. |
| 162 | * Converts the array buffer that is read in to a string |
| 163 | * and sends it on for further processing by passing it to |
| 164 | * the previously assigned callback function. |
| 165 | * |
| 166 | * @private |
| 167 | * @see TcpClient.prototype.addResponseListener |
| 168 | * @param {Object} readInfo The incoming message |
| 169 | */ |
| 170 | TcpClient.prototype._onDataRead = function(readInfo) { |
| 171 | // Call received callback if there's data in the response. |
| 172 | if (readInfo.resultCode > 0 && this.callbacks.recv) { |
| 173 | log('onDataRead'); |
| 174 | // Convert ArrayBuffer to string. |
| 175 | this._arrayBufferToString(readInfo.data, function(str) { |
| 176 | this.callbacks.recv(str); |
| 177 | }.bind(this)); |
| 178 | } |
| 179 | }; |
| 180 | |
| 181 | /** |
| 182 | * Callback for when data has been successfully |
| 183 | * written to the socket. |
| 184 | * |
| 185 | * @private |
| 186 | * @param {Object} writeInfo The outgoing message |
| 187 | */ |
| 188 | TcpClient.prototype._onWriteComplete = function(writeInfo) { |
| 189 | log('onWriteComplete'); |
| 190 | // Call sent callback. |
| 191 | if (this.callbacks.sent) { |
| 192 | this.callbacks.sent(writeInfo); |
| 193 | } |
| 194 | }; |
| 195 | |
| 196 | /** |
| 197 | * Converts an array buffer to a string |
| 198 | * |
| 199 | * @private |
| 200 | * @param {ArrayBuffer} buf The buffer to convert |
| 201 | * @param {Function} callback The function to call when conversion is complete |
| 202 | */ |
| 203 | TcpClient.prototype._arrayBufferToString = function(buf, callback) { |
| 204 | var bb = new Blob([new Uint8Array(buf)]); |
| 205 | var f = new FileReader(); |
| 206 | f.onload = function(e) { |
| 207 | callback(e.target.result); |
| 208 | }; |
| 209 | f.readAsText(bb); |
| 210 | }; |
| 211 | |
| 212 | /** |
| 213 | * Converts a string to an array buffer |
| 214 | * |
| 215 | * @private |
| 216 | * @param {String} str The string to convert |
| 217 | * @param {Function} callback The function to call when conversion is complete |
| 218 | */ |
| 219 | TcpClient.prototype._stringToArrayBuffer = function(str, callback) { |
| 220 | var bb = new Blob([str]); |
| 221 | var f = new FileReader(); |
| 222 | f.onload = function(e) { |
| 223 | callback(e.target.result); |
| 224 | }; |
| 225 | f.readAsArrayBuffer(bb); |
| 226 | }; |
| 227 | |
| 228 | /** |
| 229 | * Wrapper function for logging |
| 230 | */ |
| 231 | function log(msg) { |
| 232 | //console.log('tcp-client:', msg); |
| 233 | } |
| 234 | |
| 235 | /** |
| 236 | * Wrapper function for error logging |
| 237 | */ |
| 238 | function error(msg) { |
| 239 | console.error('tcp-client:', msg); |
| 240 | } |
| 241 | |
| 242 | exports.TcpClient = TcpClient; |
| 243 | |
| 244 | })(window); |