text: 'use strict';
  
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  
  var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor');
  
  var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
  
  var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
  
  var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
  
  var _deleteProperty = require('babel-runtime/core-js/reflect/delete-property');
  
  var _deleteProperty2 = _interopRequireDefault(_deleteProperty);
  
  var _assign = require('babel-runtime/core-js/object/assign');
  
  var _assign2 = _interopRequireDefault(_assign);
  
  var _keys = require('babel-runtime/core-js/object/keys');
  
  var _keys2 = _interopRequireDefault(_keys);
  
  var _promise = require('babel-runtime/core-js/promise');
  
  var _promise2 = _interopRequireDefault(_promise);
  
  var _set2 = require('lodash/set');
  
  var _set3 = _interopRequireDefault(_set2);
  
  var _get2 = require('lodash/get');
  
  var _get3 = _interopRequireDefault(_get2);
  
  var _dec, _dec2, _desc, _value, _obj; /**!
                                         *
                                         * Copyright (c) 2015-2017 Cisco Systems, Inc. See LICENSE file.
                                         */
  
  var _sparkCore = require('@ciscospark/spark-core');
  
  var _common = require('@ciscospark/common');
  
  var _string = require('string');
  
  var _string2 = _interopRequireDefault(_string);
  
  var _backoff = require('backoff');
  
  var _backoff2 = _interopRequireDefault(_backoff);
  
  var _socket = require('./socket');
  
  var _socket2 = _interopRequireDefault(_socket);
  
  var _errors = require('./errors');
  
  var _url = require('url');
  
  var _url2 = _interopRequireDefault(_url);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
    var desc = {};
    Object['ke' + 'ys'](descriptor).forEach(function (key) {
      desc[key] = descriptor[key];
    });
    desc.enumerable = !!desc.enumerable;
    desc.configurable = !!desc.configurable;
  
    if ('value' in desc || desc.initializer) {
      desc.writable = true;
    }
  
    desc = decorators.slice().reverse().reduce(function (desc, decorator) {
      return decorator(target, property, desc) || desc;
    }, desc);
  
    if (context && desc.initializer !== void 0) {
      desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
      desc.initializer = undefined;
    }
  
    if (desc.initializer === void 0) {
      Object['define' + 'Property'](target, property, desc);
      desc = null;
    }
  
    return desc;
  }
  
  var normalReconnectReasons = ['idle', 'done (forced)', 'pong not received', 'pong mismatch'];
  
  var Mercury = _sparkCore.SparkPlugin.extend((_dec = (0, _common.deprecated)('Mercury#listen(): Use Mercury#connect() instead'), _dec2 = (0, _common.deprecated)('Mercury#stopListening(): Use Mercury#disconnect() instead'), (_obj = {
    namespace: 'Mercury',
  
    session: {
      connected: {
        default: false,
        type: 'boolean'
      },
      connecting: {
        default: false,
        type: 'boolean'
      },
      socket: 'object',
      localClusterServiceUrls: 'object'
    },
  
    derived: {
      listening: {
        deps: ['connected'],
        fn: function fn() {
          return this.connected;
        }
      }
    },
  
    connect: function connect() {
      var _this = this;
  
      if (this.connected) {
        this.logger.info('mercury: already connected, will not connect again');
        return _promise2.default.resolve();
      }
  
      this.connecting = true;
      return _promise2.default.resolve(this.spark.internal.device.registered || this.spark.internal.device.register()).then(function () {
        _this.logger.info('mercury: connecting');
  
        return _this._connectWithBackoff();
      });
    },
    disconnect: function disconnect() {
      var _this2 = this;
  
      return new _promise2.default(function (resolve) {
        if (_this2.backoffCall) {
          _this2.logger.info('mercury: aborting connection');
          _this2.backoffCall.abort();
        }
  
        if (_this2.socket) {
          _this2.socket.removeAllListeners('message');
          _this2.once('offline', resolve);
          _this2.socket.close();
          return;
        }
  
        resolve();
      });
    },
    listen: function listen() {
      /* eslint no-invalid-this: [0] */
      return this.connect();
    },
    stopListening: function stopListening() {
      /* eslint no-invalid-this: [0] */
      return this.disconnect();
    },
    processRegistrationStatusEvent: function processRegistrationStatusEvent(message) {
      this.localClusterServiceUrls = message.localClusterServiceUrls;
    },
    _applyOverrides: function _applyOverrides(event) {
      if (!event.headers) {
        return;
      }
      var headerKeys = (0, _keys2.default)(event.headers);
      headerKeys.forEach(function (keyPath) {
        (0, _set3.default)(event, keyPath, event.headers[keyPath]);
      });
    },
    _prepareUrl: function _prepareUrl(webSocketUrl) {
      var _this3 = this;
  
      if (!webSocketUrl) {
        webSocketUrl = this.spark.internal.device.webSocketUrl;
      }
      return this.spark.internal.feature.getFeature('developer', 'web-shared-mercury').then(function (webSharedMercury) {
        webSocketUrl = _url2.default.parse(webSocketUrl, true);
        (0, _assign2.default)(webSocketUrl.query, {
          outboundWireFormat: 'text',
          bufferStates: true,
          aliasHttpStatus: true
        });
  
        if (webSharedMercury) {
          (0, _assign2.default)(webSocketUrl.query, {
            mercuryRegistrationStatus: true,
            isRegistrationRefreshEnabled: true
          });
          (0, _deleteProperty2.default)(webSocketUrl.query, 'bufferStates');
        }
  
        if ((0, _get3.default)(_this3, 'spark.config.device.ephemeral', false)) {
          webSocketUrl.query.multipleConnections = true;
        }
  
        return _url2.default.format(webSocketUrl);
      });
    },
    _attemptConnection: function _attemptConnection(callback) {
      var _this4 = this;
  
      var socket = new _socket2.default();
      socket.on('close', function () {
        return _this4._onclose.apply(_this4, arguments);
      });
      socket.on('message', function () {
        return _this4._onmessage.apply(_this4, arguments);
      });
      socket.on('sequence-mismatch', function () {
        for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
          args[_key] = arguments[_key];
        }
  
        return _this4._emit.apply(_this4, ['sequence-mismatch'].concat(args));
      });
  
      _promise2.default.all([this._prepareUrl(), this.spark.credentials.getUserToken()]).then(function (_ref) {
        var _ref2 = (0, _slicedToArray3.default)(_ref, 2),
            webSocketUrl = _ref2[0],
            token = _ref2[1];
  
        return socket.open(webSocketUrl, {
          forceCloseDelay: _this4.config.forceCloseDelay,
          pingInterval: _this4.config.pingInterval,
          pongTimeout: _this4.config.pongTimeout,
          token: token.toString(),
          trackingId: _this4.spark.sessionId + '_' + Date.now(),
          logger: _this4.logger
        });
      }).then(function () {
        _this4.socket = socket;
        callback();
      }).catch(function (reason) {
        // Suppress connection errors that appear to be network related. This
        // may end up suppressing metrics during outages, but we might not care
        // (especially since many of our outages happen in a way that client
        // metrics can't be trusted).
        if (reason.code !== 1006 && _this4.backoffCall && _this4.backoffCall.getNumRetries() > 0) {
          _this4._emit('connection_failed', reason, { retries: _this4.backoffCall.getNumRetries() });
        }
        _this4.logger.info('mercury: connection attempt failed', reason);
        // UnknownResponse is produced by IE for any 4XXX; treated it like a bad
        // web socket url and let WDM handle the token checking
        if (reason instanceof _errors.UnknownResponse) {
          _this4.logger.info('mercury: received unknown response code, refreshing device registration');
          return _this4.spark.internal.device.refresh().then(function () {
            return callback(reason);
          });
        }
        // NotAuthorized implies expired token
        else if (reason instanceof _errors.NotAuthorized) {
            _this4.logger.info('mercury: received authorization error, reauthorizing');
            return _this4.spark.credentials.refresh({ force: true }).then(function () {
              return callback(reason);
            });
          }
          // // NotFound implies expired web socket url
          // else if (reason instanceof NotFound) {
          //   this.logger.info(`mercury: received not found error, refreshing device registration`);
          //   return this.spark.internal.device.refresh()
          //     .then(() => callback(reason));
          // }
          // BadRequest implies current credentials are for a Service Account
          // Forbidden implies current user is not entitle for Spark
          else if (reason instanceof _errors.BadRequest || reason instanceof _errors.Forbidden) {
              _this4.logger.warn('mercury: received unrecoverable response from mercury');
              _this4.backoffCall.abort();
              return callback(reason);
            }
        return callback(reason);
      }).catch(function (reason) {
        _this4.logger.error('mercury: failed to handle connection failure', reason);
        callback(reason);
      });
    },
    _connectWithBackoff: function _connectWithBackoff() {
      var _this5 = this;
  
      return new _promise2.default(function (resolve, reject) {
        // eslint gets confused about whether or not call is actually used
        // eslint-disable-next-line prefer-const
        var call = void 0;
        var onComplete = function onComplete(err) {
          _this5.connecting = false;
  
          _this5.backoffCall = undefined;
          if (err) {
            _this5.logger.info('mercury: failed to connect after ' + call.getNumRetries() + ' retries; log statement about next retry was inaccurate');
            return reject(err);
          }
          _this5.connected = true;
          _this5._emit('online');
          return resolve();
        };
  
        // eslint-disable-next-line prefer-reflect
        call = _backoff2.default.call(function (callback) {
          _this5.logger.info('mercury: executing connection attempt ' + call.getNumRetries());
          _this5._attemptConnection(callback);
        }, onComplete);
  
        call.setStrategy(new _backoff2.default.ExponentialStrategy({
          initialDelay: _this5.config.backoffTimeReset,
          maxDelay: _this5.config.backoffTimeMax
        }));
  
        if (_this5.config.maxRetries) {
          call.failAfter(_this5.config.maxRetries);
        }
  
        call.on('abort', function () {
          _this5.logger.info('mercury: connection aborted');
          reject();
        });
  
        call.on('callback', function (err) {
          if (err) {
            var number = call.getNumRetries();
            var delay = Math.min(call.strategy_.nextBackoffDelay_, _this5.config.backoffTimeMax);
  
            _this5.logger.info('mercury: failed to connect; attempting retry ' + (number + 1) + ' in ' + delay + ' ms');
            /* istanbul ignore if */
            if (process.env.NODE_ENV === 'development') {
              _this5.logger.debug('mercury: ', err, err.stack);
            }
            return;
          }
          _this5.logger.info('mercury: connected');
        });
  
        call.start();
  
        _this5.backoffCall = call;
      });
    },
    _emit: function _emit() {
      try {
        this.trigger.apply(this, arguments);
      } catch (error) {
        this.logger.error('mercury: error occurred in event handler', error);
      }
    },
    _getEventHandlers: function _getEventHandlers(eventType) {
      var _eventType$split = eventType.split('.'),
          _eventType$split2 = (0, _slicedToArray3.default)(_eventType$split, 2),
          namespace = _eventType$split2[0],
          name = _eventType$split2[1];
  
      var handlers = [];
  
      if (!this.spark[namespace] && !this.spark.internal[namespace]) {
        return handlers;
      }
  
      var handlerName = (0, _string2.default)('process_' + name + '_event').camelize().s;
      if ((this.spark[namespace] || this.spark.internal[namespace])[handlerName]) {
        handlers.push({
          name: handlerName,
          namespace: namespace
        });
      }
      return handlers;
    },
    _onclose: function _onclose(event) {
      // I don't see any way to avoid the complexity or statement count in here.
      /* eslint complexity: [0] */
  
      try {
        var reason = event.reason && event.reason.toLowerCase();
        this.socket.removeAllListeners();
        this.unset('socket');
        this.connected = false;
        this._emit('offline', event);
  
        switch (event.code) {
          case 1003:
            // metric: disconnect
            this.logger.info('mercury: Mercury service rejected last message; will not reconnect: ' + event.reason);
            this._emit('offline.permanent', event);
            break;
          case 4000:
            // metric: disconnect
            this.logger.info('mercury: socket replaced; will not reconnect');
            this._emit('offline.replaced', event);
            break;
          case 1001:
          case 1005:
          case 1006:
          case 1011:
            this.logger.info('mercury: socket disconnected; reconnecting');
            this._emit('offline.transient', event);
            this._reconnect();
            // metric: disconnect
            // if (code == 1011 && rason !== ping error) metric: unexpected disconnect
            break;
          case 1000:
            if (normalReconnectReasons.includes(reason)) {
              this.logger.info('mercury: socket disconnected; reconnecting');
              this._emit('offline.transient', event);
              this._reconnect();
              // metric: disconnect
              // if (reason === done forced) metric: force closure
            } else {
              this.logger.info('mercury: socket disconnected; will not reconnect');
              this._emit('offline.permanent', event);
            }
            break;
          default:
            this.logger.info('mercury: socket disconnected unexpectedly; will not reconnect');
            // unexpected disconnect
            this._emit('offline.permanent', event);
        }
      } catch (error) {
        this.logger.error('mercury: error occurred in close handler', error);
      }
    },
    _onmessage: function _onmessage(event) {
      var _this6 = this;
  
      var envelope = event.data;
      if (process.env.ENABLE_MERCURY_LOGGING) {
        this.logger.debug('mercury: message envelope: ', envelope);
      }
  
      var data = envelope.data;
      this._applyOverrides(data);
      return this._getEventHandlers(data.eventType).reduce(function (promise, handler) {
        return promise.then(function () {
          var namespace = handler.namespace,
              name = handler.name;
  
          return new _promise2.default(function (resolve) {
            return resolve((_this6.spark[namespace] || _this6.spark.internal[namespace])[name](data));
          }).catch(function (reason) {
            return _this6.logger.error('mercury: error occurred in autowired event handler for ' + data.eventType, reason);
          });
        });
      }, _promise2.default.resolve()).then(function () {
        _this6._emit('event', event.data);
  
        var _data$eventType$split = data.eventType.split('.'),
            _data$eventType$split2 = (0, _slicedToArray3.default)(_data$eventType$split, 1),
            namespace = _data$eventType$split2[0];
  
        _this6._emit('event:' + namespace, envelope);
        _this6._emit('event:' + data.eventType, envelope);
      }).catch(function (reason) {
        _this6.logger.error('mercury: error occurred processing socket message', reason);
      });
    },
    _reconnect: function _reconnect() {
      this.logger.info('mercury: reconnecting');
      return this.connect();
    },
    version: '1.8.0'
  }, (_applyDecoratedDescriptor(_obj, 'connect', [_common.oneFlight], (0, _getOwnPropertyDescriptor2.default)(_obj, 'connect'), _obj), _applyDecoratedDescriptor(_obj, 'disconnect', [_common.oneFlight], (0, _getOwnPropertyDescriptor2.default)(_obj, 'disconnect'), _obj), _applyDecoratedDescriptor(_obj, 'listen', [_dec], (0, _getOwnPropertyDescriptor2.default)(_obj, 'listen'), _obj), _applyDecoratedDescriptor(_obj, 'stopListening', [_dec2], (0, _getOwnPropertyDescriptor2.default)(_obj, 'stopListening'), _obj)), _obj)));
  
  exports.default = Mercury;