• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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