123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- // Copyright 2011 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Definition of the WebSocket class. A WebSocket provides a
- * bi-directional, full-duplex communications channel, over a single TCP socket.
- *
- * See http://dev.w3.org/html5/websockets/
- * for the full HTML5 WebSocket API.
- *
- * Typical usage will look like this:
- *
- * var ws = new goog.net.WebSocket();
- *
- * var handler = new goog.events.EventHandler();
- * handler.listen(ws, goog.net.WebSocket.EventType.OPENED, onOpen);
- * handler.listen(ws, goog.net.WebSocket.EventType.MESSAGE, onMessage);
- *
- * try {
- * ws.open('ws://127.0.0.1:4200');
- * } catch (e) {
- * ...
- * }
- *
- */
- goog.provide('goog.net.WebSocket');
- goog.provide('goog.net.WebSocket.ErrorEvent');
- goog.provide('goog.net.WebSocket.EventType');
- goog.provide('goog.net.WebSocket.MessageEvent');
- goog.require('goog.Timer');
- goog.require('goog.asserts');
- goog.require('goog.debug.entryPointRegistry');
- goog.require('goog.events');
- goog.require('goog.events.Event');
- goog.require('goog.events.EventTarget');
- goog.require('goog.log');
- /**
- * Class encapsulating the logic for using a WebSocket.
- *
- * @param {boolean=} opt_autoReconnect True if the web socket should
- * automatically reconnect or not. This is true by default.
- * @param {function(number):number=} opt_getNextReconnect A function for
- * obtaining the time until the next reconnect attempt. Given the reconnect
- * attempt count (which is a positive integer), the function should return a
- * positive integer representing the milliseconds to the next reconnect
- * attempt. The default function used is an exponential back-off. Note that
- * this function is never called if auto reconnect is disabled.
- * @constructor
- * @extends {goog.events.EventTarget}
- */
- goog.net.WebSocket = function(opt_autoReconnect, opt_getNextReconnect) {
- goog.net.WebSocket.base(this, 'constructor');
- /**
- * True if the web socket should automatically reconnect or not.
- * @type {boolean}
- * @private
- */
- this.autoReconnect_ =
- goog.isDef(opt_autoReconnect) ? opt_autoReconnect : true;
- /**
- * A function for obtaining the time until the next reconnect attempt.
- * Given the reconnect attempt count (which is a positive integer), the
- * function should return a positive integer representing the milliseconds to
- * the next reconnect attempt.
- * @type {function(number):number}
- * @private
- */
- this.getNextReconnect_ =
- opt_getNextReconnect || goog.net.WebSocket.EXPONENTIAL_BACKOFF_;
- /**
- * The time, in milliseconds, that must elapse before the next attempt to
- * reconnect.
- * @type {number}
- * @private
- */
- this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
- };
- goog.inherits(goog.net.WebSocket, goog.events.EventTarget);
- /**
- * The actual web socket that will be used to send/receive messages.
- * @type {WebSocket}
- * @private
- */
- goog.net.WebSocket.prototype.webSocket_ = null;
- /**
- * The URL to which the web socket will connect.
- * @type {?string}
- * @private
- */
- goog.net.WebSocket.prototype.url_ = null;
- /**
- * The subprotocol name used when establishing the web socket connection.
- * @type {string|undefined}
- * @private
- */
- goog.net.WebSocket.prototype.protocol_ = undefined;
- /**
- * True if a call to the close callback is expected or not.
- * @type {boolean}
- * @private
- */
- goog.net.WebSocket.prototype.closeExpected_ = false;
- /**
- * Keeps track of the number of reconnect attempts made since the last
- * successful connection.
- * @type {number}
- * @private
- */
- goog.net.WebSocket.prototype.reconnectAttempt_ = 0;
- /** @private {?number} */
- goog.net.WebSocket.prototype.reconnectTimer_ = null;
- /**
- * The logger for this class.
- * @type {goog.log.Logger}
- * @private
- */
- goog.net.WebSocket.prototype.logger_ = goog.log.getLogger('goog.net.WebSocket');
- /**
- * The events fired by the web socket.
- * @enum {string} The event types for the web socket.
- */
- goog.net.WebSocket.EventType = {
- /**
- * Fired when an attempt to open the WebSocket fails or there is a connection
- * failure after a successful connection has been established.
- */
- CLOSED: goog.events.getUniqueId('closed'),
- /**
- * Fired when the WebSocket encounters an error.
- */
- ERROR: goog.events.getUniqueId('error'),
- /**
- * Fired when a new message arrives from the WebSocket.
- */
- MESSAGE: goog.events.getUniqueId('message'),
- /**
- * Fired when the WebSocket connection has been established.
- */
- OPENED: goog.events.getUniqueId('opened')
- };
- /**
- * The various states of the web socket.
- * @enum {number} The states of the web socket.
- * @private
- */
- goog.net.WebSocket.ReadyState_ = {
- // This is the initial state during construction.
- CONNECTING: 0,
- // This is when the socket is actually open and ready for data.
- OPEN: 1,
- // This is when the socket is in the middle of a close handshake.
- // Note that this is a valid state even if the OPEN state was never achieved.
- CLOSING: 2,
- // This is when the socket is actually closed.
- CLOSED: 3
- };
- /**
- * The maximum amount of time between reconnect attempts for the exponential
- * back-off in milliseconds.
- * @type {number}
- * @private
- */
- goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_ = 60 * 1000;
- /**
- * Computes the next reconnect time given the number of reconnect attempts since
- * the last successful connection.
- *
- * @param {number} attempt The number of reconnect attempts since the last
- * connection.
- * @return {number} The time, in milliseconds, until the next reconnect attempt.
- * @const
- * @private
- */
- goog.net.WebSocket.EXPONENTIAL_BACKOFF_ = function(attempt) {
- var time = Math.pow(2, attempt) * 1000;
- return Math.min(time, goog.net.WebSocket.EXPONENTIAL_BACKOFF_CEILING_);
- };
- /**
- * Installs exception protection for all entry points introduced by
- * goog.net.WebSocket instances which are not protected by
- * {@link goog.debug.ErrorHandler#protectWindowSetTimeout},
- * {@link goog.debug.ErrorHandler#protectWindowSetInterval}, or
- * {@link goog.events.protectBrowserEventEntryPoint}.
- *
- * @param {!goog.debug.ErrorHandler} errorHandler Error handler with which to
- * protect the entry points.
- */
- goog.net.WebSocket.protectEntryPoints = function(errorHandler) {
- goog.net.WebSocket.prototype.onOpen_ =
- errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onOpen_);
- goog.net.WebSocket.prototype.onClose_ =
- errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onClose_);
- goog.net.WebSocket.prototype.onMessage_ =
- errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onMessage_);
- goog.net.WebSocket.prototype.onError_ =
- errorHandler.protectEntryPoint(goog.net.WebSocket.prototype.onError_);
- };
- /**
- * Creates and opens the actual WebSocket. Only call this after attaching the
- * appropriate listeners to this object. If listeners aren't registered, then
- * the {@code goog.net.WebSocket.EventType.OPENED} event might be missed.
- *
- * @param {string} url The URL to which to connect.
- * @param {string=} opt_protocol The subprotocol to use. The connection will
- * only be established if the server reports that it has selected this
- * subprotocol. The subprotocol name must all be a non-empty ASCII string
- * with no control characters and no spaces in them (i.e. only characters
- * in the range U+0021 to U+007E).
- */
- goog.net.WebSocket.prototype.open = function(url, opt_protocol) {
- // Sanity check. This works only in modern browsers.
- goog.asserts.assert(
- goog.global['WebSocket'], 'This browser does not support WebSocket');
- // Don't do anything if the web socket is already open.
- goog.asserts.assert(!this.isOpen(), 'The WebSocket is already open');
- // Clear any pending attempts to reconnect.
- this.clearReconnectTimer_();
- // Construct the web socket.
- this.url_ = url;
- this.protocol_ = opt_protocol;
- // This check has to be made otherwise you get protocol mismatch exceptions
- // for passing undefined, null, '', or [].
- if (this.protocol_) {
- goog.log.info(
- this.logger_, 'Opening the WebSocket on ' + this.url_ +
- ' with protocol ' + this.protocol_);
- this.webSocket_ = new WebSocket(this.url_, this.protocol_);
- } else {
- goog.log.info(this.logger_, 'Opening the WebSocket on ' + this.url_);
- this.webSocket_ = new WebSocket(this.url_);
- }
- // Register the event handlers. Note that it is not possible for these
- // callbacks to be missed because it is registered after the web socket is
- // instantiated. Because of the synchronous nature of JavaScript, this code
- // will execute before the browser creates the resource and makes any calls
- // to these callbacks.
- this.webSocket_.onopen = goog.bind(this.onOpen_, this);
- this.webSocket_.onclose = goog.bind(this.onClose_, this);
- this.webSocket_.onmessage = goog.bind(this.onMessage_, this);
- this.webSocket_.onerror = goog.bind(this.onError_, this);
- };
- /**
- * Closes the web socket connection.
- */
- goog.net.WebSocket.prototype.close = function() {
- // Clear any pending attempts to reconnect.
- this.clearReconnectTimer_();
- // Attempt to close only if the web socket was created.
- if (this.webSocket_) {
- goog.log.info(this.logger_, 'Closing the WebSocket.');
- // Close is expected here since it was a direct call. Close is considered
- // unexpected when opening the connection fails or there is some other form
- // of connection loss after being connected.
- this.closeExpected_ = true;
- this.webSocket_.close();
- this.webSocket_ = null;
- }
- };
- /**
- * Sends the message over the web socket.
- *
- * @param {string|!ArrayBuffer|!ArrayBufferView} message The message to send.
- */
- goog.net.WebSocket.prototype.send = function(message) {
- // Make sure the socket is ready to go before sending a message.
- goog.asserts.assert(this.isOpen(), 'Cannot send without an open socket');
- // Send the message and let onError_ be called if it fails thereafter.
- this.webSocket_.send(message);
- };
- /**
- * Checks to see if the web socket is open or not.
- *
- * @return {boolean} True if the web socket is open, false otherwise.
- */
- goog.net.WebSocket.prototype.isOpen = function() {
- return !!this.webSocket_ &&
- this.webSocket_.readyState == goog.net.WebSocket.ReadyState_.OPEN;
- };
- /**
- * Gets the number of bytes of data that have been queued using calls to send()
- * but not yet transmitted to the network.
- *
- * @return {number} Number of bytes of data that have been queued.
- */
- goog.net.WebSocket.prototype.getBufferedAmount = function() {
- return this.webSocket_.bufferedAmount;
- };
- /**
- * Called when the web socket has connected.
- *
- * @private
- */
- goog.net.WebSocket.prototype.onOpen_ = function() {
- goog.log.info(this.logger_, 'WebSocket opened on ' + this.url_);
- this.dispatchEvent(goog.net.WebSocket.EventType.OPENED);
- // Set the next reconnect interval.
- this.reconnectAttempt_ = 0;
- this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
- };
- /**
- * Called when the web socket has closed.
- *
- * @param {!Event} event The close event.
- * @private
- */
- goog.net.WebSocket.prototype.onClose_ = function(event) {
- goog.log.info(this.logger_, 'The WebSocket on ' + this.url_ + ' closed.');
- // Firing this event allows handlers to query the URL.
- this.dispatchEvent(goog.net.WebSocket.EventType.CLOSED);
- // Always clear out the web socket on a close event.
- this.webSocket_ = null;
- // See if this is an expected call to onClose_.
- if (this.closeExpected_) {
- goog.log.info(this.logger_, 'The WebSocket closed normally.');
- // Only clear out the URL if this is a normal close.
- this.url_ = null;
- this.protocol_ = undefined;
- } else {
- // Unexpected, so try to reconnect.
- goog.log.error(
- this.logger_, 'The WebSocket disconnected unexpectedly: ' + event.data);
- // Only try to reconnect if it is enabled.
- if (this.autoReconnect_) {
- // Log the reconnect attempt.
- var seconds = Math.floor(this.nextReconnect_ / 1000);
- goog.log.info(
- this.logger_, 'Seconds until next reconnect attempt: ' + seconds);
- // Actually schedule the timer.
- this.reconnectTimer_ = goog.Timer.callOnce(
- goog.bind(this.open, this, this.url_, this.protocol_),
- this.nextReconnect_, this);
- // Set the next reconnect interval.
- this.reconnectAttempt_++;
- this.nextReconnect_ = this.getNextReconnect_(this.reconnectAttempt_);
- }
- }
- this.closeExpected_ = false;
- };
- /**
- * Called when a new message arrives from the server.
- *
- * @param {MessageEvent<string>} event The web socket message event.
- * @private
- */
- goog.net.WebSocket.prototype.onMessage_ = function(event) {
- var message = event.data;
- this.dispatchEvent(new goog.net.WebSocket.MessageEvent(message));
- };
- /**
- * Called when there is any error in communication.
- *
- * @param {Event} event The error event containing the error data.
- * @private
- */
- goog.net.WebSocket.prototype.onError_ = function(event) {
- var data = /** @type {string} */ (event.data);
- goog.log.error(this.logger_, 'An error occurred: ' + data);
- this.dispatchEvent(new goog.net.WebSocket.ErrorEvent(data));
- };
- /**
- * Clears the reconnect timer.
- *
- * @private
- */
- goog.net.WebSocket.prototype.clearReconnectTimer_ = function() {
- if (goog.isDefAndNotNull(this.reconnectTimer_)) {
- goog.Timer.clear(this.reconnectTimer_);
- }
- this.reconnectTimer_ = null;
- };
- /** @override */
- goog.net.WebSocket.prototype.disposeInternal = function() {
- goog.net.WebSocket.base(this, 'disposeInternal');
- this.close();
- };
- /**
- * Object representing a new incoming message event.
- *
- * @param {string} message The raw message coming from the web socket.
- * @extends {goog.events.Event}
- * @constructor
- * @final
- */
- goog.net.WebSocket.MessageEvent = function(message) {
- goog.net.WebSocket.MessageEvent.base(
- this, 'constructor', goog.net.WebSocket.EventType.MESSAGE);
- /**
- * The new message from the web socket.
- * @type {string}
- */
- this.message = message;
- };
- goog.inherits(goog.net.WebSocket.MessageEvent, goog.events.Event);
- /**
- * Object representing an error event. This is fired whenever an error occurs
- * on the web socket.
- *
- * @param {string} data The error data.
- * @extends {goog.events.Event}
- * @constructor
- * @final
- */
- goog.net.WebSocket.ErrorEvent = function(data) {
- goog.net.WebSocket.ErrorEvent.base(
- this, 'constructor', goog.net.WebSocket.EventType.ERROR);
- /**
- * The error data coming from the web socket.
- * @type {string}
- */
- this.data = data;
- };
- goog.inherits(goog.net.WebSocket.ErrorEvent, goog.events.Event);
- // Register the WebSocket as an entry point, so that it can be monitored for
- // exception handling, etc.
- goog.debug.entryPointRegistry.register(
- /**
- * @param {function(!Function): !Function} transformer The transforming
- * function.
- */
- function(transformer) {
- goog.net.WebSocket.prototype.onOpen_ =
- transformer(goog.net.WebSocket.prototype.onOpen_);
- goog.net.WebSocket.prototype.onClose_ =
- transformer(goog.net.WebSocket.prototype.onClose_);
- goog.net.WebSocket.prototype.onMessage_ =
- transformer(goog.net.WebSocket.prototype.onMessage_);
- goog.net.WebSocket.prototype.onError_ =
- transformer(goog.net.WebSocket.prototype.onError_);
- });
|