Home | History | Annotate | Download | only in chrome-app
      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 (at) 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