tcp-client.js 9.2 KB

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