websocket.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. // Copyright 2011 The Closure Library Authors. All Rights Reserved.
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Definition of the WebSocket class. A WebSocket provides a
  16. * bi-directional, full-duplex communications channel, over a single TCP socket.
  17. *
  18. * See http://dev.w3.org/html5/websockets/
  19. * for the full HTML5 WebSocket API.
  20. *
  21. * Typical usage will look like this:
  22. *
  23. * var ws = new goog.net.WebSocket();
  24. *
  25. * var handler = new goog.events.EventHandler();
  26. * handler.listen(ws, goog.net.WebSocket.EventType.OPENED, onOpen);
  27. * handler.listen(ws, goog.net.WebSocket.EventType.MESSAGE, onMessage);
  28. *
  29. * try {
  30. * ws.open('ws://127.0.0.1:4200');
  31. * } catch (e) {
  32. * ...
  33. * }
  34. *
  35. */
  36. goog.provide('goog.net.WebSocket');
  37. goog.provide('goog.net.WebSocket.ErrorEvent');
  38. goog.provide('goog.net.WebSocket.EventType');
  39. goog.provide('goog.net.WebSocket.MessageEvent');
  40. goog.require('goog.Timer');
  41. goog.require('goog.asserts');
  42. goog.require('goog.debug.entryPointRegistry');
  43. goog.require('goog.events');
  44. goog.require('goog.events.Event');
  45. goog.require('goog.events.EventTarget');
  46. goog.require('goog.log');
  47. /**
  48. * Class encapsulating the logic for using a WebSocket.
  49. *
  50. * @param {boolean=} opt_autoReconnect True if the web socket should
  51. * automatically reconnect or not. This is true by default.
  52. * @param {function(number):number=} opt_getNextReconnect A function for
  53. * obtaining the time until the next reconnect attempt. Given the reconnect
  54. * attempt count (which is a positive integer), the function should return a
  55. * positive integer representing the milliseconds to the next reconnect
  56. * attempt. The default function used is an exponential back-off. Note that
  57. * this function is never called if auto reconnect is disabled.
  58. * @constructor
  59. * @extends {goog.events.EventTarget}
  60. */
  61. goog.net.WebSocket = function(opt_autoReconnect, opt_getNextReconnect) {
  62. goog.net.WebSocket.base(this, 'constructor');
  63. /**
  64. * True if the web socket should automatically reconnect or not.
  65. * @type {boolean}
  66. * @private
  67. */
  68. this.autoReconnect_ =
  69. goog.isDef(opt_autoReconnect) ? opt_autoReconnect : true;
  70. /**
  71. * A function for obtaining the time until the next reconnect attempt.
  72. * Given the reconnect attempt count (which is a positive integer), the
  73. * function should return a positive integer representing the milliseconds to
  74. * the next reconnect attempt.
  75. * @type {function(number):number}
  76. * @private
  77. */
  78. this.getNextReconnect_ =
  79. opt_getNextReconnect || goog.net.WebSocket.EXPONENTIAL_BACKOFF_;
  80. /**
  81. * The time, in milliseconds, that must elapse before the next attempt to
  82. * reconnect.
  83. * @type {number}
  84. * @private
  85. */
  86. this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
  87. };
  88. goog.inherits(goog.net.WebSocket, goog.events.EventTarget);
  89. /**
  90. * The actual web socket that will be used to send/receive messages.
  91. * @type {WebSocket}
  92. * @private
  93. */
  94. goog.net.WebSocket.prototype.webSocket_ = null;
  95. /**
  96. * The URL to which the web socket will connect.
  97. * @type {?string}
  98. * @private
  99. */
  100. goog.net.WebSocket.prototype.url_ = null;
  101. /**
  102. * The subprotocol name used when establishing the web socket connection.
  103. * @type {string|undefined}
  104. * @private
  105. */
  106. goog.net.WebSocket.prototype.protocol_ = undefined;
  107. /**
  108. * True if a call to the close callback is expected or not.
  109. * @type {boolean}
  110. * @private
  111. */
  112. goog.net.WebSocket.prototype.closeExpected_ = false;
  113. /**
  114. * Keeps track of the number of reconnect attempts made since the last
  115. * successful connection.
  116. * @type {number}
  117. * @private
  118. */
  119. goog.net.WebSocket.prototype.reconnectAttempt_ = 0;
  120. /** @private {?number} */
  121. goog.net.WebSocket.prototype.reconnectTimer_ = null;
  122. /**
  123. * The logger for this class.
  124. * @type {goog.log.Logger}
  125. * @private
  126. */
  127. goog.net.WebSocket.prototype.logger_ = goog.log.getLogger('goog.net.WebSocket');
  128. /**
  129. * The events fired by the web socket.
  130. * @enum {string} The event types for the web socket.
  131. */
  132. goog.net.WebSocket.EventType = {
  133. /**
  134. * Fired when an attempt to open the WebSocket fails or there is a connection
  135. * failure after a successful connection has been established.
  136. */
  137. CLOSED: goog.events.getUniqueId('closed'),
  138. /**
  139. * Fired when the WebSocket encounters an error.
  140. */
  141. ERROR: goog.events.getUniqueId('error'),
  142. /**
  143. * Fired when a new message arrives from the WebSocket.
  144. */
  145. MESSAGE: goog.events.getUniqueId('message'),
  146. /**
  147. * Fired when the WebSocket connection has been established.
  148. */
  149. OPENED: goog.events.getUniqueId('opened')
  150. };
  151. /**
  152. * The various states of the web socket.
  153. * @enum {number} The states of the web socket.
  154. * @private
  155. */
  156. goog.net.WebSocket.ReadyState_ = {
  157. // This is the initial state during construction.
  158. CONNECTING: 0,
  159. // This is when the socket is actually open and ready for data.
  160. OPEN: 1,
  161. // This is when the socket is in the middle of a close handshake.
  162. // Note that this is a valid state even if the OPEN state was never achieved.
  163. CLOSING: 2,
  164. // This is when the socket is actually closed.
  165. CLOSED: 3
  166. };
  167. /**
  168. * The maximum amount of time between reconnect attempts for the exponential
  169. * back-off in milliseconds.
  170. * @type {number}
  171. * @private
  172. */
  173. goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_ = 60 * 1000;
  174. /**
  175. * Computes the next reconnect time given the number of reconnect attempts since
  176. * the last successful connection.
  177. *
  178. * @param {number} attempt The number of reconnect attempts since the last
  179. * connection.
  180. * @return {number} The time, in milliseconds, until the next reconnect attempt.
  181. * @const
  182. * @private
  183. */
  184. goog.net.WebSocket.EXPONENTIAL_BACKOFF_ = function(attempt) {
  185. var time = Math.pow(2, attempt) * 1000;
  186. return Math.min(time, goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_);
  187. };
  188. /**
  189. * Installs exception protection for all entry points introduced by
  190. * goog.net.WebSocket instances which are not protected by
  191. * {@link goog.debug.ErrorHandler#protectWindowSetTimeout},
  192. * {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or
  193. * {@link goog.events.protectBrowserEventEntryPoint}.
  194. *
  195. * @param {!goog.debug.ErrorHandler} errorHandler Error handler with which to
  196. * protect the entry points.
  197. */
  198. goog.net.WebSocket.protectEntryPoints = function(errorHandler) {
  199. goog.net.WebSocket.prototype.onOpen_ =
  200. errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onOpen_);
  201. goog.net.WebSocket.prototype.onClose_ =
  202. errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onClose_);
  203. goog.net.WebSocket.prototype.onMessage_ =
  204. errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onMessage_);
  205. goog.net.WebSocket.prototype.onError_ =
  206. errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onError_);
  207. };
  208. /**
  209. * Creates and opens the actual WebSocket. Only call this after attaching the
  210. * appropriate listeners to this object. If listeners aren't registered, then
  211. * the {@code goog.net.WebSocket.EventType.OPENED} event might be missed.
  212. *
  213. * @param {string} url The URL to which to connect.
  214. * @param {string=} opt_protocol The subprotocol to use. The connection will
  215. * only be established if the server reports that it has selected this
  216. * subprotocol. The subprotocol name must all be a non-empty ASCII string
  217. * with no control characters and no spaces in them (i.e. only characters
  218. * in the range U+0021 to U+007E).
  219. */
  220. goog.net.WebSocket.prototype.open = function(url, opt_protocol) {
  221. // Sanity check. This works only in modern browsers.
  222. goog.asserts.assert(
  223. goog.global['WebSocket'], 'This browser does not support WebSocket');
  224. // Don't do anything if the web socket is already open.
  225. goog.asserts.assert(!this.isOpen(), 'The WebSocket is already open');
  226. // Clear any pending attempts to reconnect.
  227. this.clearReconnectTimer_();
  228. // Construct the web socket.
  229. this.url_ = url;
  230. this.protocol_ = opt_protocol;
  231. // This check has to be made otherwise you get protocol mismatch exceptions
  232. // for passing undefined, null, '', or [].
  233. if (this.protocol_) {
  234. goog.log.info(
  235. this.logger_, 'Opening the WebSocket on ' + this.url_ +
  236. ' with protocol ' + this.protocol_);
  237. this.webSocket_ = new WebSocket(this.url_, this.protocol_);
  238. } else {
  239. goog.log.info(this.logger_, 'Opening the WebSocket on ' + this.url_);
  240. this.webSocket_ = new WebSocket(this.url_);
  241. }
  242. // Register the event handlers. Note that it is not possible for these
  243. // callbacks to be missed because it is registered after the web socket is
  244. // instantiated. Because of the synchronous nature of JavaScript, this code
  245. // will execute before the browser creates the resource and makes any calls
  246. // to these callbacks.
  247. this.webSocket_.onopen = goog.bind(this.onOpen_, this);
  248. this.webSocket_.onclose = goog.bind(this.onClose_, this);
  249. this.webSocket_.onmessage = goog.bind(this.onMessage_, this);
  250. this.webSocket_.onerror = goog.bind(this.onError_, this);
  251. };
  252. /**
  253. * Closes the web socket connection.
  254. */
  255. goog.net.WebSocket.prototype.close = function() {
  256. // Clear any pending attempts to reconnect.
  257. this.clearReconnectTimer_();
  258. // Attempt to close only if the web socket was created.
  259. if (this.webSocket_) {
  260. goog.log.info(this.logger_, 'Closing the WebSocket.');
  261. // Close is expected here since it was a direct call. Close is considered
  262. // unexpected when opening the connection fails or there is some other form
  263. // of connection loss after being connected.
  264. this.closeExpected_ = true;
  265. this.webSocket_.close();
  266. this.webSocket_ = null;
  267. }
  268. };
  269. /**
  270. * Sends the message over the web socket.
  271. *
  272. * @param {string|!ArrayBuffer|!ArrayBufferView} message The message to send.
  273. */
  274. goog.net.WebSocket.prototype.send = function(message) {
  275. // Make sure the socket is ready to go before sending a message.
  276. goog.asserts.assert(this.isOpen(), 'Cannot send without an open socket');
  277. // Send the message and let onError_ be called if it fails thereafter.
  278. this.webSocket_.send(message);
  279. };
  280. /**
  281. * Checks to see if the web socket is open or not.
  282. *
  283. * @return {boolean} True if the web socket is open, false otherwise.
  284. */
  285. goog.net.WebSocket.prototype.isOpen = function() {
  286. return !!this.webSocket_ &&
  287. this.webSocket_.readyState == goog.net.WebSocket.ReadyState_.OPEN;
  288. };
  289. /**
  290. * Gets the number of bytes of data that have been queued using calls to send()
  291. * but not yet transmitted to the network.
  292. *
  293. * @return {number} Number of bytes of data that have been queued.
  294. */
  295. goog.net.WebSocket.prototype.getBufferedAmount = function() {
  296. return this.webSocket_.bufferedAmount;
  297. };
  298. /**
  299. * Called when the web socket has connected.
  300. *
  301. * @private
  302. */
  303. goog.net.WebSocket.prototype.onOpen_ = function() {
  304. goog.log.info(this.logger_, 'WebSocket opened on ' + this.url_);
  305. this.dispatchEvent(goog.net.WebSocket.EventType.OPENED);
  306. // Set the next reconnect interval.
  307. this.reconnectAttempt_ = 0;
  308. this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
  309. };
  310. /**
  311. * Called when the web socket has closed.
  312. *
  313. * @param {!Event} event The close event.
  314. * @private
  315. */
  316. goog.net.WebSocket.prototype.onClose_ = function(event) {
  317. goog.log.info(this.logger_, 'The WebSocket on ' + this.url_ + ' closed.');
  318. // Firing this event allows handlers to query the URL.
  319. this.dispatchEvent(goog.net.WebSocket.EventType.CLOSED);
  320. // Always clear out the web socket on a close event.
  321. this.webSocket_ = null;
  322. // See if this is an expected call to onClose_.
  323. if (this.closeExpected_) {
  324. goog.log.info(this.logger_, 'The WebSocket closed normally.');
  325. // Only clear out the URL if this is a normal close.
  326. this.url_ = null;
  327. this.protocol_ = undefined;
  328. } else {
  329. // Unexpected, so try to reconnect.
  330. goog.log.error(
  331. this.logger_, 'The WebSocket disconnected unexpectedly: ' + event.data);
  332. // Only try to reconnect if it is enabled.
  333. if (this.autoReconnect_) {
  334. // Log the reconnect attempt.
  335. var seconds = Math.floor(this.nextReconnect_ / 1000);
  336. goog.log.info(
  337. this.logger_, 'Seconds until next reconnect attempt: ' + seconds);
  338. // Actually schedule the timer.
  339. this.reconnectTimer_ = goog.Timer.callOnce(
  340. goog.bind(this.open, this, this.url_, this.protocol_),
  341. this.nextReconnect_, this);
  342. // Set the next reconnect interval.
  343. this.reconnectAttempt_++;
  344. this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
  345. }
  346. }
  347. this.closeExpected_ = false;
  348. };
  349. /**
  350. * Called when a new message arrives from the server.
  351. *
  352. * @param {MessageEvent<string>} event The web socket message event.
  353. * @private
  354. */
  355. goog.net.WebSocket.prototype.onMessage_ = function(event) {
  356. var message = event.data;
  357. this.dispatchEvent(new goog.net.WebSocket.MessageEvent(message));
  358. };
  359. /**
  360. * Called when there is any error in communication.
  361. *
  362. * @param {Event} event The error event containing the error data.
  363. * @private
  364. */
  365. goog.net.WebSocket.prototype.onError_ = function(event) {
  366. var data = /** @type {string} */ (event.data);
  367. goog.log.error(this.logger_, 'An error occurred: ' + data);
  368. this.dispatchEvent(new goog.net.WebSocket.ErrorEvent(data));
  369. };
  370. /**
  371. * Clears the reconnect timer.
  372. *
  373. * @private
  374. */
  375. goog.net.WebSocket.prototype.clearReconnectTimer_ = function() {
  376. if (goog.isDefAndNotNull(this.reconnectTimer_)) {
  377. goog.Timer.clear(this.reconnectTimer_);
  378. }
  379. this.reconnectTimer_ = null;
  380. };
  381. /** @override */
  382. goog.net.WebSocket.prototype.disposeInternal = function() {
  383. goog.net.WebSocket.base(this, 'disposeInternal');
  384. this.close();
  385. };
  386. /**
  387. * Object representing a new incoming message event.
  388. *
  389. * @param {string} message The raw message coming from the web socket.
  390. * @extends {goog.events.Event}
  391. * @constructor
  392. * @final
  393. */
  394. goog.net.WebSocket.MessageEvent = function(message) {
  395. goog.net.WebSocket.MessageEvent.base(
  396. this, 'constructor', goog.net.WebSocket.EventType.MESSAGE);
  397. /**
  398. * The new message from the web socket.
  399. * @type {string}
  400. */
  401. this.message = message;
  402. };
  403. goog.inherits(goog.net.WebSocket.MessageEvent, goog.events.Event);
  404. /**
  405. * Object representing an error event. This is fired whenever an error occurs
  406. * on the web socket.
  407. *
  408. * @param {string} data The error data.
  409. * @extends {goog.events.Event}
  410. * @constructor
  411. * @final
  412. */
  413. goog.net.WebSocket.ErrorEvent = function(data) {
  414. goog.net.WebSocket.ErrorEvent.base(
  415. this, 'constructor', goog.net.WebSocket.EventType.ERROR);
  416. /**
  417. * The error data coming from the web socket.
  418. * @type {string}
  419. */
  420. this.data = data;
  421. };
  422. goog.inherits(goog.net.WebSocket.ErrorEvent, goog.events.Event);
  423. // Register the WebSocket as an entry point, so that it can be monitored for
  424. // exception handling, etc.
  425. goog.debug.entryPointRegistry.register(
  426. /**
  427. * @param {function(!Function): !Function} transformer The transforming
  428. * function.
  429. */
  430. function(transformer) {
  431. goog.net.WebSocket.prototype.onOpen_ =
  432. transformer(goog.net.WebSocket.prototype.onOpen_);
  433. goog.net.WebSocket.prototype.onClose_ =
  434. transformer(goog.net.WebSocket.prototype.onClose_);
  435. goog.net.WebSocket.prototype.onMessage_ =
  436. transformer(goog.net.WebSocket.prototype.onMessage_);
  437. goog.net.WebSocket.prototype.onError_ =
  438. transformer(goog.net.WebSocket.prototype.onError_);
  439. });