text: // 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_);
      });
  