(function ()()

in src/api.js [6:2237]


(function () {
  var global = this;
  connect = global.connect || {};
  global.connect = connect;
  global.lily = connect;

  /*----------------------------------------------------------------
   * enum AgentStateType
   */
  connect.AgentStateType = connect.makeEnum([
    'init',
    'routable',
    'not_routable',
    'offline'
  ]);
  connect.AgentStatusType = connect.AgentStateType;

  /**
   * enum AgentAvailStates
   */
  connect.AgentAvailStates = connect.makeEnum([
    'Init',
    'Busy',
    'AfterCallWork',
    'CallingCustomer',
    'Dialing',
    'Joining',
    'PendingAvailable',
    'PendingBusy'
  ]);

  /**
   * enum AgentErrorStates
   */
  connect.AgentErrorStates = connect.makeEnum([
    'Error',
    'AgentHungUp',
    'BadAddressAgent',
    'BadAddressCustomer',
    'Default',
    'FailedConnectAgent',
    'FailedConnectCustomer',
    'InvalidLocale',
    'LineEngagedAgent',
    'LineEngagedCustomer',
    'MissedCallAgent',
    'MissedCallCustomer',
    'MultipleCcpWindows',
    'RealtimeCommunicationError'
  ]);

  /*----------------------------------------------------------------
   * enum AddressType
   */
  connect.EndpointType = connect.makeEnum([
    'phone_number',
    'agent',
    'queue'
  ]);
  connect.AddressType = connect.EndpointType;

  /*----------------------------------------------------------------
   * enum ConnectionType
   */
  connect.ConnectionType = connect.makeEnum([
    'agent',
    'inbound',
    'outbound',
    'monitoring'
  ]);

  /*----------------------------------------------------------------
   * enum ConnectionStateType
   */
  connect.ConnectionStateType = connect.makeEnum([
    'init',
    'connecting',
    'connected',
    'hold',
    'disconnected'
  ]);
  connect.ConnectionStatusType = connect.ConnectionStateType;

  connect.CONNECTION_ACTIVE_STATES = connect.set([
    connect.ConnectionStateType.CONNECTING,
    connect.ConnectionStateType.CONNECTED,
    connect.ConnectionStateType.HOLD
  ]);

  /*----------------------------------------------------------------
   * enum ContactStateType
   */
  connect.ContactStateType = connect.makeEnum([
    'init',
    'incoming',
    'pending',
    'connecting',
    'connected',
    'missed',
    'error',
    'ended'
  ]);
  connect.ContactStatusType = connect.ContactStateType;

  connect.CONTACT_ACTIVE_STATES = connect.makeEnum([
    'incoming',
    'pending',
    'connecting',
    'connected'
  ]);

  /*----------------------------------------------------------------
   * enum ContactType
   */
  connect.ContactType = connect.makeEnum([
    'voice',
    'queue_callback',
    'chat',
    'task'
  ]);

  /*----------------------------------------------------------------
   * enum ContactInitiationMethod
   */
  connect.ContactInitiationMethod = connect.makeEnum([
    'inbound',
    'outbound',
    'transfer',
    'queue_transfer',
    'callback',
    'api',
    'disconnect'
  ]);

  /*----------------------------------------------------------------
  * enum ChannelType
  */
  connect.ChannelType = connect.makeEnum([
    'VOICE',
    'CHAT',
    'TASK'
  ]);

  /*----------------------------------------------------------------
  * enum MediaType
  */
  connect.MediaType = connect.makeEnum([
    'softphone',
    'chat',
    'task'
  ]);

  /*----------------------------------------------------------------
   * enum SoftphoneCallType
   */
  connect.SoftphoneCallType = connect.makeEnum([
    'audio_video',
    'video_only',
    'audio_only',
    'none'
  ]);

  /*----------------------------------------------------------------
   * enum for SoftphoneErrorTypes
   */
  connect.SoftphoneErrorTypes = connect.makeEnum([
    'unsupported_browser',
    'microphone_not_shared',
    'signalling_handshake_failure',
    'signalling_connection_failure',
    'ice_collection_timeout',
    'user_busy_error',
    'webrtc_error',
    'realtime_communication_error',
    'other'
  ]);

  /*----------------------------------------------------------------
   * enum for VoiceIdErrorTypes
   */
  connect.VoiceIdErrorTypes = connect.makeEnum([
    'no_speaker_id_found',
    'speaker_id_not_enrolled',
    'get_speaker_id_failed',
    'get_speaker_status_failed',
    'opt_out_speaker_failed',
    'opt_out_speaker_in_lcms_failed',
    'delete_speaker_failed',
    'start_session_failed',
    'evaluate_speaker_failed',
    'session_not_exists',
    'describe_session_failed',
    'enroll_speaker_failed',
    'update_speaker_id_failed',
    'update_speaker_id_in_lcms_failed',
    'not_supported_on_conference_calls',
    'enroll_speaker_timeout',
    'evaluate_speaker_timeout',
    'get_domain_id_failed',
    'no_domain_id_found'
  ]);

  /*----------------------------------------------------------------
   * enum for CTI exceptions
   */
  connect.CTIExceptions = connect.makeEnum([
    "AccessDeniedException",
    "InvalidStateException",
    "BadEndpointException",
    "InvalidAgentARNException",
    "InvalidConfigurationException",
    "InvalidContactTypeException",
    "PaginationException",
    "RefreshTokenExpiredException",
    "SendDataFailedException",
    "UnauthorizedException",
    "QuotaExceededException"
  ]);
  /*----------------------------------------------------------------
   * enum for VoiceId streaming status
   */
  connect.VoiceIdStreamingStatus = connect.makeEnum([
    "ONGOING",
    "ENDED",
    "PENDING_CONFIGURATION"
  ]);

  /*----------------------------------------------------------------
   * enum for VoiceId authentication decision
   */
  connect.VoiceIdAuthenticationDecision = connect.makeEnum([
    "ACCEPT",
    "REJECT",
    "NOT_ENOUGH_SPEECH",
    "SPEAKER_NOT_ENROLLED",
    "SPEAKER_OPTED_OUT",
    "SPEAKER_ID_NOT_PROVIDED",
    "SPEAKER_EXPIRED"
  ]);

  /*----------------------------------------------------------------
   * enum for VoiceId fraud detection decision
   */
  connect.VoiceIdFraudDetectionDecision = connect.makeEnum([
    "NOT_ENOUGH_SPEECH",
    "HIGH_RISK",
    "LOW_RISK"
  ]);

  /*----------------------------------------------------------------
   * enum for contact flow authentication decision 
   */
  connect.ContactFlowAuthenticationDecision = connect.makeEnum([
    "Authenticated",
    "NotAuthenticated",
    "Inconclusive",
    "NotEnrolled",
    "OptedOut",
    "NotEnabled",
    "Error"
  ]);

  /*----------------------------------------------------------------
   * enum for contact flow  fraud detection decision
   */
  connect.ContactFlowFraudDetectionDecision = connect.makeEnum([
    "HighRisk",
    "LowRisk",
    "Inconclusive",
    "NotEnabled",
    "Error"
  ]);

  /*----------------------------------------------------------------
   * enum for VoiceId EnrollmentRequest Status 
   */
  connect.VoiceIdEnrollmentRequestStatus = connect.makeEnum([
    "NOT_ENOUGH_SPEECH",
    "IN_PROGRESS",
    "COMPLETED",
    "FAILED"
  ]);

  /*----------------------------------------------------------------
   * enum for VoiceId Speaker status
   */
  connect.VoiceIdSpeakerStatus = connect.makeEnum([
    "OPTED_OUT",
    "ENROLLED",
    "PENDING"
  ]);

  connect.VoiceIdConstants = {
    EVALUATE_SESSION_DELAY: 10000,
    EVALUATION_MAX_POLL_TIMES: 24, // EvaluateSpeaker is Polling for maximum 2 mins.
    EVALUATION_POLLING_INTERVAL: 5000,
    ENROLLMENT_MAX_POLL_TIMES: 120, // EnrollmentSpeaker is Polling for maximum 10 mins.
    ENROLLMENT_POLLING_INTERVAL: 5000,
    START_SESSION_DELAY: 8000
  }

  /*----------------------------------------------------------------
   * constants for AgentPermissions
   */
  connect.AgentPermissions = {
    OUTBOUND_CALL: 'outboundCall',
    VOICE_ID: 'voiceId'
  };

  /*----------------------------------------------------------------
   * class Agent
   */
  var Agent = function () {
    if (!connect.agent.initialized) {
      throw new connect.StateError("The agent is not yet initialized!");
    }
  };

  Agent.prototype._getData = function () {
    return connect.core.getAgentDataProvider().getAgentData();
  };

  Agent.prototype._createContactAPI = function (contactData) {
    return new connect.Contact(contactData.contactId);
  };

  Agent.prototype.onRefresh = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.REFRESH, f);
  };

  Agent.prototype.onRoutable = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.ROUTABLE, f);
  };

  Agent.prototype.onNotRoutable = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.NOT_ROUTABLE, f);
  };

  Agent.prototype.onOffline = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.OFFLINE, f);
  };

  Agent.prototype.onError = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.ERROR, f);
  };

  Agent.prototype.onSoftphoneError = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.SOFTPHONE_ERROR, f);
  };

  Agent.prototype.onWebSocketConnectionLost = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.WEBSOCKET_CONNECTION_LOST, f);
  }

  Agent.prototype.onWebSocketConnectionGained = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.WEBSOCKET_CONNECTION_GAINED, f);
  }

  Agent.prototype.onAfterCallWork = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.ACW, f);
  };

  Agent.prototype.onStateChange = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.STATE_CHANGE, f);
  };

  Agent.prototype.onMuteToggle = function (f) {
    connect.core.getUpstream().onUpstream(connect.AgentEvents.MUTE_TOGGLE, f);
  };

  Agent.prototype.onLocalMediaStreamCreated = function (f) {
    connect.core.getUpstream().onUpstream(connect.AgentEvents.LOCAL_MEDIA_STREAM_CREATED, f);
  };

  Agent.prototype.onSpeakerDeviceChanged = function(f){
    connect.core.getUpstream().onUpstream(connect.ConfigurationEvents.SPEAKER_DEVICE_CHANGED, f);
  }

  Agent.prototype.onMicrophoneDeviceChanged = function(f){
    connect.core.getUpstream().onUpstream(connect.ConfigurationEvents.MICROPHONE_DEVICE_CHANGED, f);
  }

  Agent.prototype.onRingerDeviceChanged = function(f){
    connect.core.getUpstream().onUpstream(connect.ConfigurationEvents.RINGER_DEVICE_CHANGED, f);
  }

  Agent.prototype.mute = function () {
    connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST,
      {
        event: connect.EventType.MUTE,
        data: { mute: true }
      });
  };

  Agent.prototype.unmute = function () {
    connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST,
      {
        event: connect.EventType.MUTE,
        data: { mute: false }
      });
  };

  Agent.prototype.setSpeakerDevice = function (deviceId) {
    connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
      event: connect.ConfigurationEvents.SET_SPEAKER_DEVICE,
      data: { deviceId: deviceId }
    });
  };

  Agent.prototype.setMicrophoneDevice = function (deviceId) {
    connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
      event: connect.ConfigurationEvents.SET_MICROPHONE_DEVICE,
      data: { deviceId: deviceId }
    });
  };

  Agent.prototype.setRingerDevice = function (deviceId) {
    connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
      event: connect.ConfigurationEvents.SET_RINGER_DEVICE,
      data: { deviceId: deviceId }
    });
  };

  Agent.prototype.getState = function () {
    return this._getData().snapshot.state;
  };

  Agent.prototype.getNextState = function () {
    return this._getData().snapshot.nextState;
  };

  Agent.prototype.getAvailabilityState = function () {
    return this._getData().snapshot.agentAvailabilityState;
  };

  Agent.prototype.getStatus = Agent.prototype.getState;

  Agent.prototype.getStateDuration = function () {
    return connect.now() - this._getData().snapshot.state.startTimestamp.getTime() + connect.core.getSkew();
  };

  Agent.prototype.getStatusDuration = Agent.prototype.getStateDuration;

  Agent.prototype.getPermissions = function () {
    return this.getConfiguration().permissions;
  };

  Agent.prototype.getContacts = function (contactTypeFilter) {
    var self = this;
    return this._getData().snapshot.contacts.map(function (contactData) {
      return self._createContactAPI(contactData);
    }).filter(function (contact) {
      return (!contactTypeFilter) || contact.getType() === contactTypeFilter;
    });
  };

  Agent.prototype.getConfiguration = function () {
    return this._getData().configuration;
  };

  Agent.prototype.getAgentStates = function () {
    return this.getConfiguration().agentStates;
  };

  Agent.prototype.getRoutingProfile = function () {
    return this.getConfiguration().routingProfile;
  };

  Agent.prototype.getChannelConcurrency = function (channel) {
    var channelConcurrencyMap = this.getRoutingProfile().channelConcurrencyMap;
    if (!channelConcurrencyMap) {
      channelConcurrencyMap = Object.keys(connect.ChannelType).reduce(function (acc, key) {
        // Exclude TASK from default concurrency.
        if (key !== 'TASK') {
          acc[connect.ChannelType[key]] = 1;
        }
        return acc;
      }, {});
    }
    return channel
      ? (channelConcurrencyMap[channel] || 0)
      : channelConcurrencyMap;
  };

  Agent.prototype.getName = function () {
    return this.getConfiguration().name;
  };

  Agent.prototype.getExtension = function () {
    return this.getConfiguration().extension;
  };

  Agent.prototype.getDialableCountries = function () {
    return this.getConfiguration().dialableCountries;
  };

  Agent.prototype.isSoftphoneEnabled = function () {
    return this.getConfiguration().softphoneEnabled;
  };

  Agent.prototype.setConfiguration = function (configuration, callbacks) {
    var client = connect.core.getClient();
    if (configuration && configuration.agentPreferences && !connect.isValidLocale(configuration.agentPreferences.locale)) {
      if (callbacks && callbacks.failure) {
        callbacks.failure(connect.AgentErrorStates.INVALID_LOCALE);
      }
    } else {
      client.call(connect.ClientMethods.UPDATE_AGENT_CONFIGURATION, {
        configuration: connect.assertNotNull(configuration, 'configuration')
      }, {
          success: function (data) {
            // We need to ask the shared worker to reload agent config
            // once we change it so every tab has accurate config.
            var conduit = connect.core.getUpstream();
            conduit.sendUpstream(connect.EventType.RELOAD_AGENT_CONFIGURATION);

            if (callbacks.success) {
              callbacks.success(data);
            }
          },
          failure: callbacks && callbacks.failure
      });
    }
  };

  Agent.prototype.setState = function (state, callbacks, options) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.PUT_AGENT_STATE, {
      state: connect.assertNotNull(state, 'state'),
      enqueueNextState: options && !!options.enqueueNextState
    }, callbacks);
  };
  Agent.prototype.onEnqueuedNextState = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(connect.AgentEvents.ENQUEUED_NEXT_STATE, f);
  };

  Agent.prototype.setStatus = Agent.prototype.setState;

  Agent.prototype.connect = function (endpointIn, params) {
    var client = connect.core.getClient();
    var endpoint = new connect.Endpoint(endpointIn);
    // Have to remove the endpointId field or AWS JS SDK gets mad.
    delete endpoint.endpointId;

    client.call(connect.ClientMethods.CREATE_OUTBOUND_CONTACT, {
      endpoint: connect.assertNotNull(endpoint, 'endpoint'),
      queueARN: (params && (params.queueARN || params.queueId)) || this.getRoutingProfile().defaultOutboundQueue.queueARN
    }, params && {
        success: params.success,
        failure: params.failure
      });
  };

  Agent.prototype.getAllQueueARNs = function () {
    return this.getConfiguration().routingProfile.queues.map(function (queue) {
      return queue.queueARN;
    });
  };

  Agent.prototype.getEndpoints = function (queueARNs, callbacks, pageInfoIn) {
    var self = this;
    var client = connect.core.getClient();
    connect.assertNotNull(callbacks, "callbacks");
    connect.assertNotNull(callbacks.success, "callbacks.success");
    var pageInfo = pageInfoIn || { };

    pageInfo.endpoints = pageInfo.endpoints || [];
    pageInfo.maxResults = pageInfo.maxResults || connect.DEFAULT_BATCH_SIZE;

    // Backwards compatibility allowing a single queueARN to be specified
    // instead of an array.
    if (!connect.isArray(queueARNs)) {
      queueARNs = [queueARNs];
    }

    client.call(connect.ClientMethods.GET_ENDPOINTS, {
      queueARNs: queueARNs,
      nextToken: pageInfo.nextToken || null,
      maxResults: pageInfo.maxResults
    }, {
        success: function (data) {
          if (data.nextToken) {
            self.getEndpoints(queueARNs, callbacks, {
              nextToken: data.nextToken,
              maxResults: pageInfo.maxResults,
              endpoints: pageInfo.endpoints.concat(data.endpoints)
            });
          } else {
            pageInfo.endpoints = pageInfo.endpoints.concat(data.endpoints);
            var endpoints = pageInfo.endpoints.map(function (endpoint) {
              return new connect.Endpoint(endpoint);
            });

            callbacks.success({
              endpoints: endpoints,
              addresses: endpoints
            });
          }
        },
        failure: callbacks.failure
      });
  };

  Agent.prototype.getAddresses = Agent.prototype.getEndpoints;

  //Internal identifier.
  Agent.prototype._getResourceId = function() {
    queueArns = this.getAllQueueARNs();
    for (let queueArn of queueArns) {
      const agentIdMatch = queueArn.match(/\/agent\/([^/]+)/);
      
      if (agentIdMatch) {
        return agentIdMatch[1];
      }
    }
    return new Error("Agent.prototype._getResourceId: queueArns did not contain agentResourceId: ", queueArns);
  }

  Agent.prototype.toSnapshot = function () {
    return new connect.AgentSnapshot(this._getData());
  };

  /*----------------------------------------------------------------
   * class AgentSnapshot
   */
  var AgentSnapshot = function (agentData) {
    connect.Agent.call(this);
    this.agentData = agentData;
  };
  AgentSnapshot.prototype = Object.create(Agent.prototype);
  AgentSnapshot.prototype.constructor = AgentSnapshot;

  AgentSnapshot.prototype._getData = function () {
    return this.agentData;
  };

  AgentSnapshot.prototype._createContactAPI = function (contactData) {
    return new connect.ContactSnapshot(contactData);
  };

  /*----------------------------------------------------------------
   * class Contact
   */
  var Contact = function (contactId) {
    this.contactId = contactId;
  };

  Contact.prototype._getData = function () {
    return connect.core.getAgentDataProvider().getContactData(this.getContactId());
  };

  Contact.prototype._createConnectionAPI = function (connectionData) {
    if (this.getType() === connect.ContactType.CHAT) {
      return new connect.ChatConnection(this.contactId, connectionData.connectionId);
    } else if (this.getType() === connect.ContactType.TASK) {
      return new connect.TaskConnection(this.contactId, connectionData.connectionId);
    } else {
      return new connect.VoiceConnection(this.contactId, connectionData.connectionId);
    }
  };

  Contact.prototype.getEventName = function (eventName) {
    return connect.core.getContactEventName(eventName, this.getContactId());
  };

  Contact.prototype.onRefresh = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.REFRESH), f);
  };

  Contact.prototype.onIncoming = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.INCOMING), f);
  };

  Contact.prototype.onConnecting = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.CONNECTING), f);
  };

  Contact.prototype.onPending = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.PENDING), f);
  };

  Contact.prototype.onAccepted = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.ACCEPTED), f);
  };

  Contact.prototype.onMissed = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.MISSED), f);
  };

  Contact.prototype.onEnded = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.ENDED), f);
    bus.subscribe(this.getEventName(connect.ContactEvents.DESTROYED), f);
  };

  Contact.prototype.onDestroy = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.DESTROYED), f);
  };

  Contact.prototype.onACW = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.ACW), f);
  };

  Contact.prototype.onConnected = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.CONNECTED), f);
  };

  Contact.prototype.onError = function (f) {
    var bus = connect.core.getEventBus();
    bus.subscribe(this.getEventName(connect.ContactEvents.ERROR), f);
  }

  Contact.prototype.getContactId = function () {
    return this.contactId;
  };

  Contact.prototype.getOriginalContactId = function () {
    return this._getData().initialContactId;
  };
  Contact.prototype.getInitialContactId = Contact.prototype.getOriginalContactId;

  Contact.prototype.getType = function () {
    return this._getData().type;
  };

  Contact.prototype.getContactDuration = function() {
    return this._getData().contactDuration;
  }

  Contact.prototype.getState = function () {
    return this._getData().state;
  };

  Contact.prototype.getStatus = Contact.prototype.getState;

  Contact.prototype.getStateDuration = function () {
    return connect.now() - this._getData().state.timestamp.getTime() + connect.core.getSkew();
  };

  Contact.prototype.getStatusDuration = Contact.prototype.getStateDuration;

  Contact.prototype.getQueue = function () {
    return this._getData().queue;
  };

  Contact.prototype.getQueueTimestamp = function () {
    return this._getData().queueTimestamp;
  };

  Contact.prototype.getConnections = function () {
    var self = this;
    return this._getData().connections.map(function (connData) {
      if (self.getType() === connect.ContactType.CHAT) {
        return new connect.ChatConnection(self.contactId, connData.connectionId);
      } else if (self.getType() === connect.ContactType.TASK) {
        return new connect.TaskConnection(self.contactId, connData.connectionId);
      } else {
        return new connect.VoiceConnection(self.contactId, connData.connectionId);
      }
    });
  };

  Contact.prototype.getInitialConnection = function () {
    return connect.find(this.getConnections(), function (conn) {
      return conn.isInitialConnection();
    }) || null;
  };

  Contact.prototype.getActiveInitialConnection = function () {
    var initialConn = this.getInitialConnection();
    if (initialConn != null && initialConn.isActive()) {
      return initialConn;
    } else {
      return null;
    }
  };

  Contact.prototype.getThirdPartyConnections = function () {
    return this.getConnections().filter(function (conn) {
      return !conn.isInitialConnection() && conn.getType() !== connect.ConnectionType.AGENT;
    });
  };

  Contact.prototype.getSingleActiveThirdPartyConnection = function () {
    return this.getThirdPartyConnections().filter(function (conn) {
      return conn.isActive();
    })[0] || null;
  };

  Contact.prototype.getAgentConnection = function () {
    return connect.find(this.getConnections(), function (conn) {
      var connType = conn.getType();
      return connType === connect.ConnectionType.AGENT || connType === connect.ConnectionType.MONITORING;
    });
  };

  Contact.prototype.getName = function () {
    return this._getData().name;
  };

  Contact.prototype.getContactMetadata = function () {
    return this._getData().contactMetadata;
  }

  Contact.prototype.getDescription = function () {
    return this._getData().description;
  };

  Contact.prototype.getReferences = function () {
    return this._getData().references;
  };

  Contact.prototype.getAttributes = function () {
    return this._getData().attributes;
  };

  Contact.prototype.getContactFeatures = function () {
    return this._getData().contactFeatures;
  };

  Contact.prototype.isSoftphoneCall = function () {
    return connect.find(this.getConnections(), function (conn) {
      return conn.getSoftphoneMediaInfo() != null;
    }) != null;
  };

  Contact.prototype._isInbound = function () {
    var initiationMethod = this._getData().initiationMethod;
    return (initiationMethod === connect.ContactInitiationMethod.OUTBOUND) ? false : true;
  }

  Contact.prototype.isInbound = function () {
    var conn = this.getInitialConnection();

    // We will gradually change checking inbound by relying on contact initiationMethod
    if (conn.getMediaType() === connect.MediaType.TASK) {
      return this._isInbound();
    }

    return conn ? conn.getType() === connect.ConnectionType.INBOUND : false;
  };

  Contact.prototype.isConnected = function () {
    return this.getStatus().type === connect.ContactStateType.CONNECTED;
  };

  Contact.prototype.accept = function (callbacks) {
    var client = connect.core.getClient();
    var self = this;
    var contactId = this.getContactId();
    client.call(connect.ClientMethods.ACCEPT_CONTACT, {
      contactId: contactId
    }, {
        success: function (data) {
          var conduit = connect.core.getUpstream();
          conduit.sendUpstream(connect.EventType.BROADCAST, {
            event: connect.ContactEvents.ACCEPTED,
            data: new connect.Contact(contactId)
          });
          conduit.sendUpstream(connect.EventType.BROADCAST, {
            event: connect.core.getContactEventName(connect.ContactEvents.ACCEPTED, self.getContactId()),
            data: new connect.Contact(contactId)
          });

          // In Firefox, there's a browser restriction that an unfocused browser tab is not allowed to access the user's microphone.
          // The problem is that the restriction could cause a webrtc session creation timeout error when you get an incoming call while you are not on the primary tab.
          // It was hard to workaround the issue especially when you have multiple tabs open because you needed to find the right tab and accept the contact before the timeout.
          // To avoid the error, when multiple tabs are open in Firefox, a webrtc session is not immediately created as an incoming softphone contact is detected.
          // Instead, it waits until contact.accept() is called on a tab and lets the tab become the new primary tab and start the web rtc session there
          // because the tab should be focused at the moment and have access to the user's microphone.
          var contact = new connect.Contact(contactId);
          if (connect.isFirefoxBrowser() && contact.isSoftphoneCall()) {
            connect.core.triggerReadyToStartSessionEvent();
          }

          if (callbacks && callbacks.success) {
            callbacks.success(data);
          }
        },
        failure: callbacks ? callbacks.failure : null
      });
  };

  Contact.prototype.destroy = function () {
    connect.getLog().warn("contact.destroy() has been deprecated.");
  };

  Contact.prototype.reject = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.REJECT_CONTACT, {
      contactId: this.getContactId()
    }, callbacks);
  };

  Contact.prototype.complete = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.COMPLETE_CONTACT, {
      contactId: this.getContactId()
    }, callbacks);
  };

  Contact.prototype.clear = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.CLEAR_CONTACT, {
      contactId: this.getContactId()
    }, callbacks);
  };

  Contact.prototype.notifyIssue = function (issueCode, description, callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.NOTIFY_CONTACT_ISSUE, {
      contactId: this.getContactId(),
      issueCode: issueCode,
      description: description
    }, callbacks);
  };

  Contact.prototype.addConnection = function (endpointIn, callbacks) {
    var client = connect.core.getClient();
    var endpoint = new connect.Endpoint(endpointIn);
    // Have to remove the endpointId field or AWS JS SDK gets mad.
    delete endpoint.endpointId;

    client.call(connect.ClientMethods.CREATE_ADDITIONAL_CONNECTION, {
      contactId: this.getContactId(),
      endpoint: endpoint
    }, callbacks);
  };

  Contact.prototype.toggleActiveConnections = function (callbacks) {
    var client = connect.core.getClient();
    var connectionId = null;
    var holdingConn = connect.find(this.getConnections(), function (conn) {
      return conn.getStatus().type === connect.ConnectionStateType.HOLD;
    });

    if (holdingConn != null) {
      connectionId = holdingConn.getConnectionId();

    } else {
      var activeConns = this.getConnections().filter(function (conn) {
        return conn.isActive();
      });
      if (activeConns.length > 0) {
        connectionId = activeConns[0].getConnectionId();
      }
    }

    client.call(connect.ClientMethods.TOGGLE_ACTIVE_CONNECTIONS, {
      contactId: this.getContactId(),
      connectionId: connectionId
    }, callbacks);
  };

  Contact.prototype.sendSoftphoneMetrics = function (softphoneStreamStatistics, callbacks) {
    var client = connect.core.getClient();

    client.call(connect.ClientMethods.SEND_SOFTPHONE_CALL_METRICS, {
      contactId: this.getContactId(),
      ccpVersion: global.ccpVersion,
      softphoneStreamStatistics: softphoneStreamStatistics
    }, callbacks);

    connect.publishSoftphoneStats({
      contactId: this.getContactId(),
      ccpVersion: global.ccpVersion,
      stats: softphoneStreamStatistics
    });
  };

  Contact.prototype.sendSoftphoneReport = function (report, callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.SEND_SOFTPHONE_CALL_REPORT, {
      contactId: this.getContactId(),
      ccpVersion: global.ccpVersion,
      report: report
    }, callbacks);

    connect.publishSoftphoneReport({
      contactId: this.getContactId(),
      ccpVersion: global.ccpVersion,
      report: report
    });
  };

  Contact.prototype.conferenceConnections = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.CONFERENCE_CONNECTIONS, {
      contactId: this.getContactId()
    }, callbacks);
  };

  Contact.prototype.toSnapshot = function () {
    return new connect.ContactSnapshot(this._getData());
  };

  /*----------------------------------------------------------------
   * class ContactSnapshot
   */
  var ContactSnapshot = function (contactData) {
    connect.Contact.call(this, contactData.contactId);
    this.contactData = contactData;
  };
  ContactSnapshot.prototype = Object.create(Contact.prototype);
  ContactSnapshot.prototype.constructor = ContactSnapshot;

  ContactSnapshot.prototype._getData = function () {
    return this.contactData;
  };

  ContactSnapshot.prototype._createConnectionAPI = function (connectionData) {
    return new connect.ConnectionSnapshot(connectionData);
  };

  /*----------------------------------------------------------------
   * class Connection
   */
  var Connection = function (contactId, connectionId) {
    this.contactId = contactId;
    this.connectionId = connectionId;
    this._initMediaController();
  };

  Connection.prototype._getData = function () {
    return connect.core.getAgentDataProvider().getConnectionData(
      this.getContactId(), this.getConnectionId());
  };

  Connection.prototype.getContactId = function () {
    return this.contactId;
  };

  Connection.prototype.getConnectionId = function () {
    return this.connectionId;
  };

  Connection.prototype.getEndpoint = function () {
    return new connect.Endpoint(this._getData().endpoint);
  };

  Connection.prototype.getAddress = Connection.prototype.getEndpoint;

  Connection.prototype.getState = function () {
    return this._getData().state;
  };

  Connection.prototype.getStatus = Connection.prototype.getState;

  Connection.prototype.getStateDuration = function () {
    return connect.now() - this._getData().state.timestamp.getTime() + connect.core.getSkew();
  };

  Connection.prototype.getStatusDuration = Connection.prototype.getStateDuration;

  Connection.prototype.getType = function () {
    return this._getData().type;
  };

  Connection.prototype.isInitialConnection = function () {
    return this._getData().initial;
  };

  Connection.prototype.isActive = function () {
    return connect.contains(connect.CONNECTION_ACTIVE_STATES, this.getStatus().type);
  };

  Connection.prototype.isConnected = function () {
    return this.getStatus().type === connect.ConnectionStateType.CONNECTED;
  };

  Connection.prototype.isConnecting = function () {
    return this.getStatus().type === connect.ConnectionStateType.CONNECTING;
  };

  Connection.prototype.isOnHold = function () {
    return this.getStatus().type === connect.ConnectionStateType.HOLD;
  };

  Connection.prototype.getSoftphoneMediaInfo = function () {
    return this._getData().softphoneMediaInfo;
  };

  /**
   * Gets the currently monitored contact info, Returns null if does not exists.
   * @return {{agentName:string, customerName:string, joinTime:Date}}
   */
  Connection.prototype.getMonitorInfo = function () {
    return this._getData().monitoringInfo;
  };

  Connection.prototype.destroy = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.DESTROY_CONNECTION, {
      contactId: this.getContactId(),
      connectionId: this.getConnectionId()
    }, callbacks);
  };

  Connection.prototype.sendDigits = function (digits, callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.SEND_DIGITS, {
      contactId: this.getContactId(),
      connectionId: this.getConnectionId(),
      digits: digits
    }, callbacks);
  };

  Connection.prototype.hold = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.HOLD_CONNECTION, {
      contactId: this.getContactId(),
      connectionId: this.getConnectionId()
    }, callbacks);
  };

  Connection.prototype.resume = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.RESUME_CONNECTION, {
      contactId: this.getContactId(),
      connectionId: this.getConnectionId()
    }, callbacks);
  };

  Connection.prototype.toSnapshot = function () {
    return new connect.ConnectionSnapshot(this._getData());
  };

  Connection.prototype._initMediaController = function () {
    if (this.getMediaInfo()) {
      connect.core.mediaFactory.get(this).catch(function () { });
    }
  }

  // Method for checking whether this connection is an agent-side connection 
  // (type AGENT or MONITORING)
  Connection.prototype._isAgentConnectionType = function () {
    var connectionType = this.getType();
    return connectionType === connect.ConnectionType.AGENT 
      || connectionType === connect.ConnectionType.MONITORING;
  }

  /**
   * Utility method for checking whether this connection is an agent-side connection 
   * (type AGENT or MONITORING)
   * @return {boolean} True if this connection is an agent-side connection. False otherwise.
   */
  Connection.prototype._isAgentConnectionType = function () {
    var connectionType = this.getType();
    return connectionType === connect.ConnectionType.AGENT 
      || connectionType === connect.ConnectionType.MONITORING;
  }

  /*----------------------------------------------------------------
  * Voice authenticator VoiceId
  */
 
  var VoiceId = function (contactId) {
    this.contactId = contactId;
  };

  VoiceId.prototype.getSpeakerId = function () {
    var self = this;
    self.checkConferenceCall();
    var client = connect.core.getClient();
    return new Promise(function (resolve, reject) {
      client.call(connect.AgentAppClientMethods.GET_CONTACT, {
        "contactId": self.contactId,
        "instanceId": connect.core.getAgentDataProvider().getInstanceId(),
        "awsAccountId": connect.core.getAgentDataProvider().getAWSAccountId()
        }, {
          success: function (data) {
            if(data.contactData.customerId) {
              var obj = {
                speakerId: data.contactData.customerId
              }
              connect.getLog().info("getSpeakerId succeeded").withObject(data).sendInternalLogToServer();
              resolve(obj);
            } else {
              var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.NO_SPEAKER_ID_FOUND, "No speakerId assotiated with this call");
              reject(error);
            }
            
          },
          failure: function (err) {
            connect.getLog().error("Get SpeakerId failed")
              .withObject({
                err: err
              }).sendInternalLogToServer();
            var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.GET_SPEAKER_ID_FAILED, "Get SpeakerId failed", err);
            reject(error);
          }
        });
    });
  };

  VoiceId.prototype.getSpeakerStatus = function () {
    var self = this;
    self.checkConferenceCall();
    var client = connect.core.getClient();
    return new Promise(function (resolve, reject) {
      self.getSpeakerId().then(function(data){
        self.getDomainId().then(function(domainId) {
          client.call(connect.AgentAppClientMethods.DESCRIBE_SPEAKER, {
            "SpeakerId": connect.assertNotNull(data.speakerId, 'speakerId'),
            "DomainId" : domainId
            }, {
              success: function (data) {
                connect.getLog().info("getSpeakerStatus succeeded").withObject(data).sendInternalLogToServer();
                resolve(data);
              },
              failure: function (err) {
                var error;
                var parsedErr = JSON.parse(err);
                switch(parsedErr.status) {
                  case 400:
                  case 404:
                    var data = parsedErr;
                    data.type = data.type ? data.type : connect.VoiceIdErrorTypes.SPEAKER_ID_NOT_ENROLLED;
                    connect.getLog().info("Speaker is not enrolled.").sendInternalLogToServer();
                    resolve(data);
                    break;
                  default:
                    connect.getLog().error("getSpeakerStatus failed")
                    .withObject({
                      err: err
                    }).sendInternalLogToServer();
                    var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.GET_SPEAKER_STATUS_FAILED, "Get SpeakerStatus failed", err);
                    reject(error);
                }
              }
            });
        }).catch(function(err) {
          reject(err);
        });
      }).catch(function(err){
        reject(err);
      });
    });
  };

  // internal only
  VoiceId.prototype._optOutSpeakerInLcms = function (speakerId) {
    var self = this;
    var client = connect.core.getClient();
    return new Promise(function (resolve, reject) {
      client.call(connect.AgentAppClientMethods.UPDATE_VOICE_ID_DATA, {
        "ContactId": self.contactId,
        "InstanceId": connect.core.getAgentDataProvider().getInstanceId(),
        "AWSAccountId": connect.core.getAgentDataProvider().getAWSAccountId(),
        "CustomerId": connect.assertNotNull(speakerId, 'speakerId'),
        "VoiceIdResult": {
          "SpeakerOptedOut": true
        }
        }, {
          success: function (data) {
            connect.getLog().info("optOutSpeakerInLcms succeeded").withObject(data).sendInternalLogToServer();
            resolve(data);
          },
          failure: function (err) {
            connect.getLog().error("optOutSpeakerInLcms failed")
              .withObject({
                err: err,
              }).sendInternalLogToServer();
              var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.OPT_OUT_SPEAKER_IN_LCMS_FAILED, "optOutSpeakerInLcms failed", err);
              reject(error);
          }
        });
    });
  };

  VoiceId.prototype.optOutSpeaker = function () {
    var self = this;
    self.checkConferenceCall();
    var client = connect.core.getClient();
    return new Promise(function (resolve, reject) {
      self.getSpeakerId().then(function(data){
        self.getDomainId().then(function(domainId) {
          var speakerId = data.speakerId;
          client.call(connect.AgentAppClientMethods.OPT_OUT_SPEAKER, {
            "SpeakerId": connect.assertNotNull(speakerId, 'speakerId'),
            "DomainId" : domainId
            }, {
              success: function (data) {
                self._optOutSpeakerInLcms(speakerId).catch(function(){});
                connect.getLog().info("optOutSpeaker succeeded").withObject(data).sendInternalLogToServer();
                resolve(data);
              },
              failure: function (err) {
                connect.getLog().error("optOutSpeaker failed")
                  .withObject({
                    err: err,
                  }).sendInternalLogToServer();
                var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.OPT_OUT_SPEAKER_FAILED, "optOutSpeaker failed.", err);
                reject(error);
              }
            });
        }).catch(function(err) {
          reject(err);
        });
      }).catch(function(err){
        reject(err);
      });
    });
  };

  VoiceId.prototype.deleteSpeaker = function () {
    var self = this;
    self.checkConferenceCall();
    var client = connect.core.getClient();
    return new Promise(function (resolve, reject) {
      self.getSpeakerId().then(function(data){
        self.getDomainId().then(function(domainId) {
          client.call(connect.AgentAppClientMethods.DELETE_SPEAKER, {
            "SpeakerId": connect.assertNotNull(data.speakerId, 'speakerId'),
            "DomainId" : domainId
            }, {
              success: function (data) {
                connect.getLog().info("deleteSpeaker succeeded").withObject(data).sendInternalLogToServer();
                resolve(data);
              },
              failure: function (err) {
                connect.getLog().error("deleteSpeaker failed")
                  .withObject({
                    err: err,
                  }).sendInternalLogToServer();
                var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.DELETE_SPEAKER_FAILED, "deleteSpeaker failed.", err);
                reject(error);
              }
            });
        }).catch(function(err) {
          reject(err);
        });
      }).catch(function(err){
        reject(err);
      });
    });
  };

  VoiceId.prototype.startSession = function () {
    var self = this;
    self.checkConferenceCall();
    var client = connect.core.getClient();
    return new Promise(function (resolve, reject) {
      self.getDomainId().then(function(domainId) {
        client.call(connect.AgentAppClientMethods.START_VOICE_ID_SESSION, {
          "contactId": self.contactId,
          "instanceId": connect.core.getAgentDataProvider().getInstanceId(),
          "customerAccountId": connect.core.getAgentDataProvider().getAWSAccountId(),
          "clientToken": AWS.util.uuid.v4(),
          "domainId" : domainId
          }, {
            success: function (data) {
              if(data.sessionId) {
                resolve(data);
              } else {
                connect.getLog().error("startVoiceIdSession failed, no session id returned")
                  .withObject({
                    data: data
                  }).sendInternalLogToServer();
                var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.START_SESSION_FAILED, "No session id returned from start session api");
                reject(error);
              }
            },
            failure: function (err) {
              connect.getLog().error("startVoiceIdSession failed")
                .withObject({
                  err: err
                }).sendInternalLogToServer();
              var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.START_SESSION_FAILED, "startVoiceIdSession failed", err);
              reject(error);
            }
          });
      }).catch(function(err) {
        reject(err);
      });
    });
  };

  VoiceId.prototype.evaluateSpeaker = function (startNew) {
    var self = this;
    self.checkConferenceCall();
    var client = connect.core.getClient();
    var contactData = connect.core.getAgentDataProvider().getContactData(this.contactId);
    var pollTimes = 0; 
    return new Promise(function (resolve, reject) {
      function evaluate() {
        self.getDomainId().then(function(domainId) {
          client.call(connect.AgentAppClientMethods.EVALUATE_SESSION, {
            "SessionNameOrId": contactData.initialContactId || this.contactId,
            "DomainId" : domainId
          }, {
            success: function (data) {
              if(++pollTimes < connect.VoiceIdConstants.EVALUATION_MAX_POLL_TIMES) {
                if(data.StreamingStatus === connect.VoiceIdStreamingStatus.PENDING_CONFIGURATION) {
                  setTimeout(evaluate, connect.VoiceIdConstants.EVALUATION_POLLING_INTERVAL);
                } else {
                  if(!data.AuthenticationResult) {
                    data.AuthenticationResult = {};
                    data.AuthenticationResult.Decision = connect.ContactFlowAuthenticationDecision.NOT_ENABLED;
                  }

                  if(!data.FraudDetectionResult) {
                    data.FraudDetectionResult = {};
                    data.FraudDetectionResult.Decision = connect.ContactFlowFraudDetectionDecision.NOT_ENABLED;
                  }

                  // Resolve if both authentication and fraud detection are not enabled.
                  if(!self.isAuthEnabled(data.AuthenticationResult.Decision) && 
                    !self.isFraudEnabled(data.FraudDetectionResult.Decision)) {
                      connect.getLog().info("evaluateSpeaker succeeded").withObject(data).sendInternalLogToServer();
                      resolve(data);
                      return;
                  }

                  if(data.StreamingStatus === connect.VoiceIdStreamingStatus.ENDED) {
                    if(self.isAuthResultNotEnoughSpeech(data.AuthenticationResult.Decision)) {
                      data.AuthenticationResult.Decision = connect.ContactFlowAuthenticationDecision.INCONCLUSIVE;
                    }
                    if(self.isFraudResultNotEnoughSpeech(data.FraudDetectionResult.Decision)) {
                      data.FraudDetectionResult.Decision = connect.ContactFlowFraudDetectionDecision.INCONCLUSIVE;
                    }
                  }
                  // Voice print is not long enough for both authentication and fraud detection
                  if(self.isAuthResultInconclusive(data.AuthenticationResult.Decision) &&
                    self.isFraudResultInconclusive(data.FraudDetectionResult.Decision)) {
                      connect.getLog().info("evaluateSpeaker succeeded").withObject(data).sendInternalLogToServer();
                      resolve(data);
                      return;
                  }

                  if(!self.isAuthResultNotEnoughSpeech(data.AuthenticationResult.Decision) && 
                    self.isAuthEnabled(data.AuthenticationResult.Decision)) {
                    switch (data.AuthenticationResult.Decision) {
                      case connect.VoiceIdAuthenticationDecision.ACCEPT:
                        data.AuthenticationResult.Decision = connect.ContactFlowAuthenticationDecision.AUTHENTICATED;
                        break;
                      case connect.VoiceIdAuthenticationDecision.REJECT:
                        data.AuthenticationResult.Decision = connect.ContactFlowAuthenticationDecision.NOT_AUTHENTICATED;
                        break;
                      case connect.VoiceIdAuthenticationDecision.SPEAKER_OPTED_OUT:
                        data.AuthenticationResult.Decision = connect.ContactFlowAuthenticationDecision.OPTED_OUT;
                        break;
                      case connect.VoiceIdAuthenticationDecision.SPEAKER_NOT_ENROLLED:
                        data.AuthenticationResult.Decision = connect.ContactFlowAuthenticationDecision.NOT_ENROLLED;
                        break;
                      default:
                        data.AuthenticationResult.Decision = connect.ContactFlowAuthenticationDecision.ERROR;
                    }
                  }

                  if(!self.isFraudResultNotEnoughSpeech(data.FraudDetectionResult.Decision) && 
                    self.isFraudEnabled(data.FraudDetectionResult.Decision)) {
                    switch (data.FraudDetectionResult.Decision) {
                      case connect.VoiceIdFraudDetectionDecision.HIGH_RISK:
                        data.FraudDetectionResult.Decision = connect.ContactFlowFraudDetectionDecision.HIGH_RISK;
                        break;
                      case connect.VoiceIdFraudDetectionDecision.LOW_RISK:
                        data.FraudDetectionResult.Decision = connect.ContactFlowFraudDetectionDecision.LOW_RISK;
                        break;
                      default:
                        data.FraudDetectionResult.Decision = connect.ContactFlowFraudDetectionDecision.ERROR;
                    }
                  }

                  if(!self.isAuthResultNotEnoughSpeech(data.AuthenticationResult.Decision) &&
                    !self.isFraudResultNotEnoughSpeech(data.FraudDetectionResult.Decision)) {
                      // Resolve only when both authentication and fraud detection have results. Otherwise, keep polling.
                      connect.getLog().info("evaluateSpeaker succeeded").withObject(data).sendInternalLogToServer();
                      resolve(data);
                      return;
                  } else {
                    setTimeout(evaluate, connect.VoiceIdConstants.EVALUATION_POLLING_INTERVAL);
                  }
                }
              } else {
                connect.getLog().error("evaluateSpeaker timeout").sendInternalLogToServer();
                var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.EVALUATE_SPEAKER_TIMEOUT, "evaluateSpeaker timeout");
                reject(error);
              }
            },
            failure: function (err) {
              var error;
              var parsedErr = JSON.parse(err);
              switch(parsedErr.status) {
                case 400:
                case 404:
                  error = connect.VoiceIdError(connect.VoiceIdErrorTypes.SESSION_NOT_EXISTS, "evaluateSpeaker failed, session not exists", err);
                  connect.getLog().error("evaluateSpeaker failed, session not exists").withObject({ err: err }).sendInternalLogToServer();
                  break;
                default:
                  error = connect.VoiceIdError(connect.VoiceIdErrorTypes.EVALUATE_SPEAKER_FAILED, "evaluateSpeaker failed", err);
                  connect.getLog().error("evaluateSpeaker failed").withObject({ err: err }).sendInternalLogToServer();    
              }
              reject(error);
            }
          })
        }).catch(function(err) {
          reject(err);
        });
      }
      
      if(!startNew) {
        self.syncSpeakerId().then(function () {
          evaluate();
        }).catch(function (err) {
          connect.getLog().error("syncSpeakerId failed when session startNew=false")
                .withObject({err: err}).sendInternalLogToServer();
          reject(err);
        })
      } else { 
        self.startSession().then(function(data) {
          self.syncSpeakerId().then(function(data) {
            setTimeout(evaluate, connect.VoiceIdConstants.EVALUATE_SESSION_DELAY);
          }).catch(function (err) {
              connect.getLog().error("syncSpeakerId failed when session startNew=true")
                .withObject({err: err}).sendInternalLogToServer();
              reject(err);
            });
          }).catch(function(err){
            connect.getLog().error("startSession failed when session startNew=true")
              .withObject({err: err}).sendInternalLogToServer();
          reject(err)
        });
      }
    });
  };

  VoiceId.prototype.describeSession = function () {
    var self = this;
    var client = connect.core.getClient();
    var contactData = connect.core.getAgentDataProvider().getContactData(this.contactId);
    return new Promise(function (resolve, reject) {
      self.getDomainId().then(function(domainId) {
        client.call(connect.AgentAppClientMethods.DESCRIBE_SESSION, {
          "SessionNameOrId": contactData.initialContactId || this.contactId,
          "DomainId" : domainId
        }, {
          success: function (data) {
            resolve(data)
          },
          failure: function (err) {
            connect.getLog().error("describeSession failed")
              .withObject({
                err: err
              }).sendInternalLogToServer();
            var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.DESCRIBE_SESSION_FAILED, "describeSession failed", err);
            reject(error);
          }
        })
      }).catch(function(err) {
        reject(err);
      });
    });
  };

  VoiceId.prototype.checkEnrollmentStatus = function () {
    var self = this;
    var pollingTimes = 0;

    return new Promise(function (resolve, reject) {
      function describe () {
        if(++pollingTimes < connect.VoiceIdConstants.ENROLLMENT_MAX_POLL_TIMES) {
          self.describeSession().then(function(data){
            switch(data.Session.EnrollmentRequestDetails.Status) {
              case connect.VoiceIdEnrollmentRequestStatus.COMPLETED:
                resolve(data);
                break;
              case connect.VoiceIdEnrollmentRequestStatus.IN_PROGRESS:
                setTimeout(describe, connect.VoiceIdConstants.ENROLLMENT_POLLING_INTERVAL);
                break;
              case connect.VoiceIdEnrollmentRequestStatus.NOT_ENOUGH_SPEECH:
                if(data.Session.StreamingStatus !== connect.VoiceIdStreamingStatus.ENDED) {
                  setTimeout(describe,connect.VoiceIdConstants.ENROLLMENT_POLLING_INTERVAL);
                } else {
                  setTimeout(function(){
                    self.startSession().then(function(data) {
                      describe();
                    }).catch(function(err, data){
                      reject(err);
                    });
                  }, connect.VoiceIdConstants.START_SESSION_DELAY);
                }
                break;
              default:
                var message = data.Session.EnrollmentRequestDetails.Message ? data.Session.EnrollmentRequestDetails.Message : "enrollSpeaker failed. Unknown enrollment status has been received";
                connect.getLog().error(message).sendInternalLogToServer();
  		          var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.ENROLL_SPEAKER_FAILED, message, data.Session.EnrollmentRequestDetails.Status);
  		          reject(error);
            }
          });
        } else {
          connect.getLog().error("enrollSpeaker timeout").sendInternalLogToServer();
          var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.ENROLL_SPEAKER_TIMEOUT, "enrollSpeaker timeout");
          reject(error);
        }
      }
      describe();
    });
  };

  VoiceId.prototype.enrollSpeaker = function () {
    var self = this;
    self.checkConferenceCall();
    return new Promise(function(resolve, reject) {
      self.syncSpeakerId().then(function() {
        self.getSpeakerStatus().then(function(data) {
          if(data.Speaker && data.Speaker.Status == connect.VoiceIdSpeakerStatus.OPTED_OUT) {
            self.deleteSpeaker().then(function() {
              self.enrollSpeakerHelper(resolve, reject);
            }).catch(function(err) {
              reject(err);
            });
          } else {
            self.enrollSpeakerHelper(resolve, reject);
          }
        }).catch(function(err) {
          reject(err);
        })
      }).catch(function(err) {
        reject(err)
      })
    })
  }

  VoiceId.prototype.enrollSpeakerHelper = function (resolve, reject) {
    var self = this;
    var client = connect.core.getClient();
    var contactData = connect.core.getAgentDataProvider().getContactData(this.contactId);
    self.getDomainId().then(function(domainId) {
      client.call(connect.AgentAppClientMethods.ENROLL_BY_SESSION, {
        "SessionNameOrId": contactData.initialContactId || this.contactId,
        "DomainId" : domainId
        }, {
          success: function (data) {
            if(data.Status === connect.VoiceIdEnrollmentRequestStatus.COMPLETED) {
              connect.getLog().info("enrollSpeaker succeeded").withObject(data).sendInternalLogToServer();
              resolve(data);
            } else {
              self.checkEnrollmentStatus().then(function(data){
                connect.getLog().info("enrollSpeaker succeeded").withObject(data).sendInternalLogToServer();
                resolve(data);
              }).catch(function(err){
                reject(err);
              })
            }
          },
          failure: function (err) {
            connect.getLog().error("enrollSpeaker failed")
              .withObject({
                err: err
              }).sendInternalLogToServer();
            var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.ENROLL_SPEAKER_FAILED, "enrollSpeaker failed", err);
            reject(error);
          }
        });
    }).catch(function(err) {
      reject(err);
    });
  };

  // internal only
  VoiceId.prototype._updateSpeakerIdInLcms = function (speakerId) {
    var self = this;
    var client = connect.core.getClient();
    return new Promise(function (resolve, reject) {
      client.call(connect.AgentAppClientMethods.UPDATE_VOICE_ID_DATA, {
        "ContactId": self.contactId,
        "InstanceId": connect.core.getAgentDataProvider().getInstanceId(),
        "AWSAccountId": connect.core.getAgentDataProvider().getAWSAccountId(),
        "CustomerId": connect.assertNotNull(speakerId, 'speakerId'),
        "VoiceIdResult": {
          "generatedSpeakerId": speakerId
        }
      }, {
        success: function (data) {
          connect.getLog().info("updateSpeakerIdInLcms succeeded").withObject(data).sendInternalLogToServer();
          resolve(data);
        },
        failure: function (err) {
          connect.getLog().error("updateSpeakerIdInLcms failed")
            .withObject({
              err: err,
            }).sendInternalLogToServer();
            var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.UPDATE_SPEAKER_ID_IN_LCMS_FAILED, "updateSpeakerIdInLcms failed", err);
            reject(error);
        }
      });
    });
  };

  VoiceId.prototype.updateSpeakerIdInVoiceId = function (speakerId) {
    var self = this;
    self.checkConferenceCall();
    var client = connect.core.getClient();
    var contactData = connect.core.getAgentDataProvider().getContactData(this.contactId);
    return new Promise(function (resolve, reject) {
      self.getDomainId().then(function(domainId) {
        client.call(connect.AgentAppClientMethods.UPDATE_SESSION, {
          "SessionNameOrId": contactData.initialContactId || this.contactId,
          "SpeakerId": connect.assertNotNull(speakerId, 'speakerId'),
          "DomainId" : domainId
          }, {
            success: function (data) {
              connect.getLog().info("updateSpeakerIdInVoiceId succeeded").withObject(data).sendInternalLogToServer();
              self._updateSpeakerIdInLcms(speakerId)
                .then(function() {
                  resolve(data);
                })
                .catch(function(err) {
                  reject(err);
                });
            },
            failure: function (err) {
              var error;
              var parsedErr = JSON.parse(err);
              switch(parsedErr.status) {
                case 400:
                case 404:
                  error = connect.VoiceIdError(connect.VoiceIdErrorTypes.SESSION_NOT_EXISTS, "updateSpeakerIdInVoiceId failed, session not exists", err);
                  connect.getLog().error("updateSpeakerIdInVoiceId failed, session not exists").withObject({ err: err }).sendInternalLogToServer();
                  break;
                default:
                  error = connect.VoiceIdError(connect.VoiceIdErrorTypes.UPDATE_SPEAKER_ID_FAILED, "updateSpeakerIdInVoiceId failed", err);
                  connect.getLog().error("updateSpeakerIdInVoiceId failed").withObject({ err: err }).sendInternalLogToServer();    
              }
              reject(error);
            }
          });
      }).catch(function(err) {
        reject(err);
      });
    });
  };

  VoiceId.prototype.syncSpeakerId = function () {
    var self = this;
    return new Promise(function (resolve, reject) {
      self.getSpeakerId().then(function(data){
        self.updateSpeakerIdInVoiceId(data.speakerId).then(function(data){
          resolve(data);
        }).catch(function(err) {
          reject(err);
        })
      }).catch(function(err){
        reject(err);
      });
    })
  }

  VoiceId.prototype.getDomainId = function() {
    return new Promise(function (resolve, reject) {
      const agent = new connect.Agent();
      if (!agent.getPermissions().includes(connect.AgentPermissions.VOICE_ID)) {
        reject(new Error("Agent doesn't have the permission for Voice ID"));
      } else if (connect.core.voiceIdDomainId) {
        resolve(connect.core.voiceIdDomainId);
      } else {
        var client = connect.core.getClient();
        client.call(connect.AgentAppClientMethods.LIST_INTEGRATION_ASSOCIATIONS, {
          "InstanceId": connect.core.getAgentDataProvider().getInstanceId(),
          "IntegrationType": "VOICE_ID"
        }, {
          success: function (data) {
            try {
              var domainId;
              if (data.IntegrationAssociationSummaryList.length >= 1) {
                var integrationArn = data.IntegrationAssociationSummaryList[0].IntegrationArn;
                domainId = integrationArn.replace(/^.*domain\//i, '');
              }
              if (!domainId) {
                connect.getLog().info("getDomainId: no domainId found").sendInternalLogToServer();
                var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.NO_DOMAIN_ID_FOUND);
                reject(error);
                return;
              }
              connect.getLog().info("getDomainId succeeded").withObject(data).sendInternalLogToServer();
              connect.core.getUpstream().sendUpstream(connect.EventType.BROADCAST, {
                event: connect.VoiceIdEvents.UPDATE_DOMAIN_ID,
                data: { domainId: domainId }
              });
              resolve(domainId);
            } catch(err) {
              connect.getLog().error("getDomainId failed").withObject({ err: err }).sendInternalLogToServer();
              var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.GET_DOMAIN_ID_FAILED, "getDomainId failed", err);
              reject(error);
            }
          },
          failure: function (err) {
            connect.getLog().error("getDomainId failed").withObject({ err: err }).sendInternalLogToServer();
            var error = connect.VoiceIdError(connect.VoiceIdErrorTypes.GET_DOMAIN_ID_FAILED, "getDomainId failed", err);
            reject(error);
          }
        });
      }
    });
  }

  VoiceId.prototype.checkConferenceCall = function(){
    var self = this;
    var isConferenceCall = connect.core.getAgentDataProvider().getContactData(self.contactId).connections.filter(function (conn) {
      return connect.contains(connect.CONNECTION_ACTIVE_STATES, conn.state.type);
    }).length > 2;
    if(isConferenceCall){
      throw new connect.NotImplementedError("VoiceId is not supported for conference calls");
    }
  }

  VoiceId.prototype.isAuthEnabled = function(authResult) {
    return authResult !== connect.ContactFlowAuthenticationDecision.NOT_ENABLED;
  }

  VoiceId.prototype.isAuthResultNotEnoughSpeech = function(authResult) {
    return authResult === connect.VoiceIdAuthenticationDecision.NOT_ENOUGH_SPEECH;
  }

  VoiceId.prototype.isAuthResultInconclusive = function(authResult) {
    return authResult === connect.ContactFlowAuthenticationDecision.INCONCLUSIVE;
  }

  VoiceId.prototype.isFraudEnabled = function(fraudResult) {
    return fraudResult !== connect.ContactFlowFraudDetectionDecision.NOT_ENABLED;
  }

  VoiceId.prototype.isFraudResultNotEnoughSpeech = function(fraudResult) {
    return fraudResult === connect.VoiceIdFraudDetectionDecision.NOT_ENOUGH_SPEECH;
  }

  VoiceId.prototype.isFraudResultInconclusive = function(fraudResult) {
    return fraudResult === connect.ContactFlowFraudDetectionDecision.INCONCLUSIVE;
  }

  /**
   * @class VoiceConnection
   * @param {number} contactId 
   * @param {number} connectionId 
   * @description - Provides voice media specific operations
   */
  var VoiceConnection = function (contactId, connectionId) {
    this._speakerAuthenticator = new VoiceId(contactId);
    Connection.call(this, contactId, connectionId);
  };

  VoiceConnection.prototype = Object.create(Connection.prototype);
  VoiceConnection.prototype.constructor = VoiceConnection;

  /**
  * @deprecated
  * Please use getMediaInfo 
  */
  VoiceConnection.prototype.getSoftphoneMediaInfo = function () {
    return this._getData().softphoneMediaInfo;
  };

  VoiceConnection.prototype.getMediaInfo = function () {
    return this._getData().softphoneMediaInfo;
  };

  VoiceConnection.prototype.getMediaType = function () {
    return connect.MediaType.SOFTPHONE;
  };

  VoiceConnection.prototype.getMediaController = function () {
    return connect.core.mediaFactory.get(this);
  }

  VoiceConnection.prototype.getVoiceIdSpeakerId = function() {
    return this._speakerAuthenticator.getSpeakerId();
  }

  VoiceConnection.prototype.getVoiceIdSpeakerStatus = function() {
    return this._speakerAuthenticator.getSpeakerStatus();
  }

  VoiceConnection.prototype.optOutVoiceIdSpeaker = function() {
    
    return this._speakerAuthenticator.optOutSpeaker();
  }

  VoiceConnection.prototype.deleteVoiceIdSpeaker = function() {
    return this._speakerAuthenticator.deleteSpeaker();
  }

  VoiceConnection.prototype.evaluateSpeakerWithVoiceId = function(startNew) {
    return this._speakerAuthenticator.evaluateSpeaker(startNew);
  }

  VoiceConnection.prototype.enrollSpeakerInVoiceId = function() {
    return this._speakerAuthenticator.enrollSpeaker();
  }

  VoiceConnection.prototype.updateVoiceIdSpeakerId = function(speakerId) {
    return this._speakerAuthenticator.updateSpeakerIdInVoiceId(speakerId);
  }

  VoiceConnection.prototype.getQuickConnectName = function () {
    return this._getData().quickConnectName;
  };

  VoiceConnection.prototype.isMute = function () {
    return this._getData().mute;
  };

  VoiceConnection.prototype.muteParticipant = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.MUTE_PARTICIPANT, {
      contactId: this.getContactId(),
      connectionId: this.getConnectionId()
    }, callbacks);
  };

  VoiceConnection.prototype.unmuteParticipant = function (callbacks) {
    var client = connect.core.getClient();
    client.call(connect.ClientMethods.UNMUTE_PARTICIPANT, {
      contactId: this.getContactId(),
      connectionId: this.getConnectionId()
    }, callbacks);
  };


  /**
   * @class ChatConnection
   * @param {*} contactId 
   * @param {*} connectionId 
   * @description adds the chat media specific functionality
   */
  var ChatConnection = function (contactId, connectionId) {
    Connection.call(this, contactId, connectionId);
  };

  ChatConnection.prototype = Object.create(Connection.prototype);
  ChatConnection.prototype.constructor = ChatConnection;

  ChatConnection.prototype.getMediaInfo = function () {
    var data = this._getData().chatMediaInfo;
    if (!data) {
      return null;
    } else {
      var contactData = connect.core.getAgentDataProvider().getContactData(this.contactId);
      var mediaObject = {
        contactId: this.contactId,
        initialContactId: contactData.initialContactId || this.contactId,
        participantId: this.connectionId,
        getConnectionToken: connect.hitch(this, this.getConnectionToken)
      };
      if (data.connectionData) {
        try {
          mediaObject.participantToken = JSON.parse(data.connectionData).ConnectionAuthenticationToken;
        } catch (e) {
          connect.getLog().error(connect.LogComponent.CHAT, "Connection data is invalid")
            .withObject(data)
            .withException(e)
            .sendInternalLogToServer();
          mediaObject.participantToken = null;
        }
      }
      mediaObject.participantToken = mediaObject.participantToken || null;
      /** Just to keep the data accessible */
      mediaObject.originalInfo = this._getData().chatMediaInfo;
      return mediaObject;
    }
  };

  /**
  * Provides the chat connectionToken through the create_transport API for a specific contact and participant Id. 
  * @returns a promise which, upon success, returns the response from the createTransport API.
  * Usage:
  * connection.getConnectionToken()
  *  .then(response => {})
  *  .catch(error => {})
  */
  ChatConnection.prototype.getConnectionToken = function () {
    client = connect.core.getClient();
    var contactData = connect.core.getAgentDataProvider().getContactData(this.contactId);
    var transportDetails = {
      transportType: connect.TRANSPORT_TYPES.CHAT_TOKEN,
      participantId: this.connectionId,
      contactId: contactData.initialContactId || this.contactId
    };
    return new Promise(function (resolve, reject) {
      client.call(connect.ClientMethods.CREATE_TRANSPORT, transportDetails, {
        success: function (data) {
          connect.getLog().info("getConnectionToken succeeded").sendInternalLogToServer();
          resolve(data);
        },
        failure: function (err, data) {
          connect.getLog().error("getConnectionToken failed").sendInternalLogToServer()
            .withObject({
              err: err,
              data: data
            });
          reject(Error("getConnectionToken failed"));
        }
      });
    });
  };

  ChatConnection.prototype.getMediaType = function () {
    return connect.MediaType.CHAT;
  };

  ChatConnection.prototype.getMediaController = function () {
    return connect.core.mediaFactory.get(this);
  };

  ChatConnection.prototype._initMediaController = function () {
    // Note that a chat media controller only needs to be produced for agent type connections.
    if (this._isAgentConnectionType()) {
      connect.core.mediaFactory.get(this).catch(function () { });
    }
  }

  /**
   * @class TaskConnection
   * @param {*} contactId 
   * @param {*} connectionId 
   * @description adds the task media specific functionality
   */
  var TaskConnection = function (contactId, connectionId) {
    Connection.call(this, contactId, connectionId);
  };
  TaskConnection.prototype = Object.create(Connection.prototype);
  TaskConnection.prototype.constructor = TaskConnection;

  TaskConnection.prototype.getMediaType = function () {
    return connect.MediaType.TASK;
  }

  TaskConnection.prototype.getMediaInfo = function () {
      var contactData = connect.core.getAgentDataProvider().getContactData(this.contactId);
      var mediaObject = {
        contactId: this.contactId,
        initialContactId: contactData.initialContactId || this.contactId,
      };
      return mediaObject;
  };

  TaskConnection.prototype.getMediaController = function () {
    return connect.core.mediaFactory.get(this);
  };

  /*----------------------------------------------------------------
   * class ConnectionSnapshot
   */
  var ConnectionSnapshot = function (connectionData) {
    connect.Connection.call(this, connectionData.contactId, connectionData.connectionId);
    this.connectionData = connectionData;
  };
  ConnectionSnapshot.prototype = Object.create(Connection.prototype);
  ConnectionSnapshot.prototype.constructor = ConnectionSnapshot;

  ConnectionSnapshot.prototype._getData = function () {
    return this.connectionData;
  };

  ConnectionSnapshot.prototype._initMediaController = function () { };

  var Endpoint = function (paramsIn) {
    var params = paramsIn || {};
    this.endpointARN = params.endpointId || params.endpointARN || null;
    this.endpointId = this.endpointARN;
    this.type = params.type || null;
    this.name = params.name || null;
    this.phoneNumber = params.phoneNumber || null;
    this.agentLogin = params.agentLogin || null;
    this.queue = params.queue || null;
  };

  /**
   * Strip the SIP endpoint components from the phoneNumber field.
   */
  Endpoint.prototype.stripPhoneNumber = function () {
    return this.phoneNumber ? this.phoneNumber.replace(/sip:([^@]*)@.*/, "$1") : "";
  };

  /**
   * Create an Endpoint object from the given phone number and name.
   */
  Endpoint.byPhoneNumber = function (number, name) {
    return new Endpoint({
      type: connect.EndpointType.PHONE_NUMBER,
      phoneNumber: number,
      name: name || null
    });
  };

  /*----------------------------------------------------------------
   * class SoftphoneError
   */
  var SoftphoneError = function (errorType, errorMessage, endPointUrl) {
    this.errorType = errorType;
    this.errorMessage = errorMessage;
    this.endPointUrl = endPointUrl;
  };
  SoftphoneError.prototype.getErrorType = function () {
    return this.errorType;
  };
  SoftphoneError.prototype.getErrorMessage = function () {
    return this.errorMessage;
  };
  SoftphoneError.prototype.getEndPointUrl = function () {
    return this.endPointUrl;
  };

  /*----------------------------------------------------------------
   * Root Subscription APIs.
   */
  connect.agent = function (f) {
    var bus = connect.core.getEventBus();
    var sub = bus.subscribe(connect.AgentEvents.INIT, f);
    if (connect.agent.initialized) {
      f(new connect.Agent());
    }
    return sub;
  };
  connect.agent.initialized = false;

  connect.contact = function (f) {
    var bus = connect.core.getEventBus();
    return bus.subscribe(connect.ContactEvents.INIT, f);
  };

  connect.onWebsocketInitFailure = function (f) {
    var bus = connect.core.getEventBus();
    var sub = bus.subscribe(connect.WebSocketEvents.INIT_FAILURE, f);
    if (connect.webSocketInitFailed) {
      f();
    }
    return sub;
  };

  /**
   * Starts the given function asynchronously only if the shared worker
   * says we are the master for the given topic.  If there is no master for
   * the given topic, we become the master and start the function unless
   * shouldNotBecomeMasterIfNone is true.
   *
   * @param topic The master topic we are concerned about.
   * @param f_true The callback to be invoked if we are the master.
   * @param f_else [optional] A callback to be invoked if we are not the master.
   * @param shouldNotBecomeMasterIfNone [optional] if true, this tab won't become master.
   */
  connect.ifMaster = function (topic, f_true, f_else, shouldNotBecomeMasterIfNone) {
    connect.assertNotNull(topic, "A topic must be provided.");
    connect.assertNotNull(f_true, "A true callback must be provided.");

    if (!connect.core.masterClient) {
      // We can't be the master because there is no master client!
      connect.getLog().warn("We can't be the master for topic '%s' because there is no master client!", topic).sendInternalLogToServer();
      if (f_else) {
        f_else();
      }
      return;
    }

    var masterClient = connect.core.getMasterClient();
    masterClient.call(connect.MasterMethods.CHECK_MASTER, {
      topic: topic,
      shouldNotBecomeMasterIfNone: shouldNotBecomeMasterIfNone
    }, {
        success: function (data) {
          if (data.isMaster) {
            f_true();

          } else if (f_else) {
            f_else();
          }
        }
      });
  };

  /**
   * Notify the shared worker and other CCP tabs that we are now the master for the given topic.
   */
  connect.becomeMaster = function (topic, successCallback, failureCallback) {
    connect.assertNotNull(topic, "A topic must be provided.");

    if (!connect.core.masterClient) {
      // We can't be the master because there is no master client!
      connect.getLog().warn("We can't be the master for topic '%s' because there is no master client!", topic);
      if (failureCallback) {
        failureCallback();
      }
    } else {
      var masterClient = connect.core.getMasterClient();
      masterClient.call(connect.MasterMethods.BECOME_MASTER, {
        topic: topic
      }, {
        success: function () {
          if (successCallback) {
            successCallback();
          }
        }
      });
    }
  };

  connect.Agent = Agent;
  connect.AgentSnapshot = AgentSnapshot;
  connect.Contact = Contact;
  connect.ContactSnapshot = ContactSnapshot;
  /** Default will get the Voice connection */
  connect.Connection = VoiceConnection;
  connect.BaseConnection = Connection;
  connect.VoiceConnection = VoiceConnection;
  connect.ChatConnection = ChatConnection;
  connect.TaskConnection = TaskConnection;
  connect.ConnectionSnapshot = ConnectionSnapshot;
  connect.Endpoint = Endpoint;
  connect.Address = Endpoint;
  connect.SoftphoneError = SoftphoneError;
  connect.VoiceId = VoiceId;
})();