Source: IdmAuthFlows.js

/**
 * Copyright (c) 2017, Oracle and/or its affiliates.
 * The Universal Permissive License (UPL), Version 1.0
 */
/* jshint esversion: 6 */

/**
 * @file {@link https://github.com/oracle/cordova-plugin-oracle-idm-auth|cordova-plugin-oracle-idm-auth}
 * is a plugin that provides authentication and authorization functionality for
 * cordova based mobile applications, supporting standard protocols for remote authentication
 * such as Basic Auth, OAuth, OpenID Connect and webSSO / Federated auth.
 * The plugin also supports local device level authentication such as PIN and biometric based.
 * The plugin abstracts all aspects of authentication and authorization and enforces security best practices for mobile application developers.
 * Typically an app will use a single remote authentication and possibly local authentication.
 * But the plugin can handle multiple authentication flows in parallel, be it remote or local.
 * The APIs of this plugin are exposed through an object <code>IdmAuthFlows</code> in <code>cordova.plugins</code> namespace.
 * All objects in this documentation be it in "Global" or "Classes" are part of this object.
 * They have to be accessed as <code>cordova.plugins.IdmAuthFlows.&lt;Class&gt;</code>
 * or <code>cordova.plugins.IdmAuthFlows.&lt;Object&gt;</code>.
 * <p>Usage of this plugin:</p>
 * <p>Create authentication properties object for the type of authentication to be performed.
 * Use one of the builders for this:
 * <ul style="list-style: none;">
 *  <li>{@link HttpBasicAuthPropertiesBuilder}</li>
 *  <li>{@link FedAuthPropertiesBuilder}</li>
 *  <li>{@link OAuthPropertiesBuilder}</li>
 *  <li>{@link OpenIDConnectPropertiesBuilder}</li>
 *  <li>{@link LocalAuthPropertiesBuilder}</li>
 * </ul>
 * </p>
 * <p>{@link init|Init} the authentication flow using properties. The promise resolves with an object which is subclass of {@link AuthenticationFlow}.
 *    Preserve this object for further operations.</p>
 * <p>{@link Authentication flow|AuthenticationFlow} can be used for performing operations such as
 *    {@link AuthenticationFlow#login|login}, {@link AuthenticationFlow#logout|logout} etc.
 *  Depending on the type of authentication used, the object returned can be of these types. These support operations specific to the authentication used.
 *  <ul style="list-style: none;">
 *    <li>{@link RemoteAuthenticationFlow}</li>
 *    <li>{@link HttpBasicAuthenticationFlow}</li>
 *    <li>{@link LocalAuthenticationFlow}</li>
 *  </ul>
 * </p>
 * <p> Sample usage:</p>
 * <pre>
 *  // Preserve this authentication flow object to interact with the particular flow.
 *  var authFlow;
 *
 *  // The plugin will be available in onDeviceReady or an equivalent callback
 *  // which is executed after the application is loaded by the device.
 *  document.addEventListener("deviceready", onDeviceReady);
 *  function onDeviceReady() {
 *    // Create the authentication properties
 *    var authProperties = cordova.plugins.IdmAuthFlows.newHttpBasicAuthPropertiesBuilder(...).build();
 *
 *    var authPromise = cordova.plugins.IdmAuthFlows.init(authProperties);
 *    authPromise.then(function(flow) {
 *      authFlow = flow;
 *    });
 *  }
 *
 *  // Do login.
 *  var loginPromise = authFlow.login();
 *  loginPromise.then(function(resp) {
 *    // Perform after login tasks.
 *  })
 *
 *  // Retrieve headers - If applicable for the auth type.
 *  var getHeadersPromise = authFlow.getHeaders(options);
 *  getHeadersPromise.then(function(headers) {
 *    // Use headers for setting appropriate headers for performing an XHR request.
 *  });
 *
 *  // Find our use's authentication status.
 *  var isAuthenticatedPromise = authFlow.isAuthenticated(options);
 *  isAuthenticatedPromise.then(function(authenticated) {
 *    // Use headers for setting appropriate headers for performing an XHR request.
 *  });
 *
 *  // Logout from a particular authentication flow.
 *  var logoutPromise = authFlow.logout();
 *  logoutPromise.then(function(resp) {
 *    // Do after logout tasks
 *  });
 * </pre>
 */
var exec = require('cordova/exec');

var IdmAuthFlows = function() {
  var TAG = 'IdmAuthFlows';
  var AuthFlowKey = 'AuthFlowKey';
  var IsAuthenticatedKey = 'isAuthenticated';

  /**
   * Validation error codes for JS layer.
   */
  var errorCodes = {
    NoLocalAuthEnabled: 'P1013',
    UnknownLocalAuthenticatorType: 'P1014',
    DisablePinWhenBiometricEnabled: 'P1017',
    EnableBiometricWhenPinDisabled: 'P1016',
    UserCancelledAuthentication: '10029', // Reuse existing code from IDM SDK
    ChangePinWhenPinNotEnabled: 'P1020',
    GetEnabledAuthsError: 'P1021',
    OngoingTask: 'P1015'
  };

  /**
   * Keys to be used for passing authentication properties.
   */
  var authPropertyKeys = {
    // Common for all auth types
    // IdmAuthFlows.AuthServerTypes - enum values
    AuthServerType:'AuthServerType',
    // string - Name of the application
    ApplicationName:'ApplicationName',
    // IdmAuthFlows.ConnectivityMode - enum values
    ConnectivityMode:'ConnectivityMode',
    // number - in seconds
    SessionTimeOutValue:'SessionTimeOutValue',
    // number - in seconds
    IdleTimeOutValue:'IdleTimeOutValue',
    // number - [0 - 100]
    PercentageToIdleTimeout:'PercentageToIdleTimeout',
    // boolean
    OfflineAuthAllowed:'OfflineAuthAllowed',
    // number - in seconds
    LogoutTimeOutValue:'LogoutTimeOutValue',

    // HttpBasicAuthentication specific.
    // app should not set this - defaulted to AES.
    CryptoScheme: 'CryptoScheme',
    // boolean
    RememberUsernameAllowed:'RememberUsernameAllowed',
    // boolean
    RememberCredentialsAllowed:'RememberCredentialsAllowed',
    // boolean
    RememberUsernameDefault:'RememberUsernameDefault',
    // boolean
    RememberCredentialDefault:'RememberCredentialDefault',
    // boolean
    AutoLoginDefault:'AutoLoginDefault',
    // boolean
    AutoLoginAllowed:'AutoLoginAllowed',
    // number
    MaxLoginAttempts:'MaxLoginAttempts',
    // object containing custom headers: {key: 'value', ...}
    CustomAuthHeaders:'CustomAuthHeaders',
    CustomHeadersForMobileAgent:'CustomHeadersForMobileAgent',
    // function to be invoked when there is a challenge for user credentials or PIN
    ChallengeCallback: "ChallengeCallback",
    // function to be invoked when there is an idle or session timeout.
    TimeoutCallback: 'TimeoutCallback',
    // boolean
    CollectIdentityDomain:'CollectIdentityDomain',
    // boolean
    IdentityDomainNameInHeader: 'IdentityDomainNameInHeader',
    // string - header name
    IdentityDomainHeaderName:'IdentityDomainHeaderName',
    // Shared by HttpBasicAuthentication and FederatedAuthentication.
    // string - url
    LoginURL:'LoginURL',
    // string - url
    LogoutURL:'LogoutURL',

    // Shared by HttpBasicAuthentication and OAuthAuthentication
    SendAuthorizationHeaderInLogout:'SendAuthorizationHeaderInLogout',
    SendCustomAuthHeadersInLogout: 'SendCustomAuthHeadersInLogout',

    // FederatedAuthentication specific.
    // string - url
    LoginSuccessURL:'LoginSuccessURL',
    // string - url
    LoginFailureURL:'LoginFailureURL',
    // boolean
    ParseTokenRelayResponse: 'ParseTokenRelayResponse',
    // string - url
    LogoutSuccessURL:'LogoutSuccessURL',
    // string - url
    LogoutFailureURL:'LogoutFailureURL',
    //boolean
    ConfirmLogoutAutomatically:'ConfirmLogoutAutomatically',
    // string - Logout button ID on the confirmation page.
    ConfirmLogoutButtonId:'ConfirmLogoutButtonId',

    // OAuthAuthentication specific.
    // string - url
    OAuthTokenEndpoint:'OAuthTokenEndpoint',
    // string - url
    OAuthAuthorizationEndpoint:'OAuthAuthorizationEndpoint',
    // string - url
    OAuthRedirectEndpoint:'OAuthRedirectEndpoint',
    // IdmAuthFlows.OAuthAuthorizationGrantType enum values
    OAuthAuthorizationGrantType:'OAuthAuthorizationGrantType',
    // array of scope strings - ['scope1', 'scope2', ...]
    OAuthScope:'OAuthScope',
    // string
    OAuthClientID:'OAuthClientID',
    // string
    OAuthClientSecret:'OAuthClientSecret',
    // string - url
    // Only used for OpenId OAuth flows.
    OpenIDConnectDiscoveryURL:'OpenIDConnectDiscoveryURL',
    // boolean - to save the refresh token
    SessionActiveOnRestart:'SessionActiveOnRestart',

    // Shared by OAuth2 3-legged and FederatedAuthentication only for iOS
    // boolean
    EnableWkWebView: 'enablewkwebview',
    // IdmAuthFlows.BrowserMode enum values
    BrowserMode: 'BrowserMode',

    // Only for OpenID
    OAuthEnablePKCE: 'OAuthEnablePKCE',
    // Local Auth specific
    // string - unique id
    LocalAuthFlowId: 'LocalAuthFlowId',
    // function to be invoked when there is a challenge for user credentials or PIN
    PinChallengeCallback: "PinChallengeCallback",
    // Object containing localized strings for biometric prompt.
    Translations: "Translations"
 };

  /**
   * Enum values for authPropertyKeys.AuthServerType.
   */
  var authServerTypes = {
    HttpBasicAuthentication:'HTTPBasicAuthentication',
    FederatedAuthentication:'FederatedAuthentication',
    OAuthAuthentication:'OAuthAuthentication',
    OpenIDConnect:'OpenIDConnect10',
    LocalAuthenticator: 'LocalAuthenticator'
  };

  /**
   * Utility method to validate number is greater than or equal to zero.
   */
  var assertPositiveOrZero = function(input, field)
  {
    if (typeof input !== 'number') {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. A valid number should be passed.');
    }
    if (input < 0) {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. Value should be greater than or equal to zero.');
    }
  };

  /**
   * Utility method to validate number is greater than zero.
   */
  var assertPositive = function(input, field)
  {
    if (typeof input !== 'number') {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. A valid number should be passed.');
    }
    if (input <= 0) {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. Value should be greater than zero.');
    }
  };

  /**
   * Utility method to validate boolean.
   */
  var assertBoolean = function(input, field)
  {
    if (typeof input !== 'boolean') {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. A valid boolean should be passed.');
    }
  };

  /**
   * Utility method to validate object.
   */
  var assertObject = function(input, field)
  {
    if (input === null || typeof input !== 'object') {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. A valid object should be passed.');
    }
  };

  /**
   * Utility method to validate string
   */
  var assertString = function(input, field)
  {
    if (typeof input !== 'string' || input === '') {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. A valid string should be passed.');
    }
  };

  /**
   * Utility method to validate url.
   */
  var assertUrl = function(input, field)
  {
    assertString(input, field);
    // iOS webView does not have startsWith method. Therefore using indexOf.
    // App scheme can come in here. Use minimal check.
    if (input.indexOf(':/') > -1) {
      return;
    }
    throw new Error('Invalid value ' + input + ' passed for ' + field + '. A valid URL should be passed.');
  };

  /**
   * Utility method to validate string
   */
  var assertFunction = function(input, field)
  {
    if (typeof input !== 'function') {
      throw new Error('Invalid value ' + input + ' passed for ' + field + '. A valid function should be passed.');
    }
  };

  var initializeRemoteFlow = function(authProps, timeoutCb, resolve, reject) {
    exec(function(resp) {
      // If there is a timeoutCallback, register that before calling the successCallback.
      var authFlowKey = resp[AuthFlowKey];
      if (typeof timeoutCb === "function") {
        exec(timeoutCb, reject,  TAG, 'addTimeoutCallback', [authFlowKey]);
      }
      if (authProps[authPropertyKeys.AuthServerType] === authServerTypes.HttpBasicAuthentication) {
        resolve(new HttpBasicAuthenticationFlow(authFlowKey, authProps));
        return;
      }
      resolve(new RemoteAuthenticationFlow(authFlowKey, authProps));
    }, reject, TAG, 'setup', [authProps]);
  };

  var getError = function(errCode) {
    var error = {};
    error.errorCode = errCode;
    error.errorSource = IdmAuthFlows.ErrorSource.Plugin;
    error.translatedErrorMessage = "";
    return error;
  };

  var isTypeOf = function(type, instance) {
    for (var el in type)
      if (type.hasOwnProperty(el) && type[el] === instance)
        return true;

    return false;
  };
  // End: Util methods

  // Builders
  /**
   * @classdesc This class is the base builder for all auth types.
   * Use one of the subclasses to instantiate:
   * <ul style="list-style: none;">
   *  <li>{@link HttpBasicAuthPropertiesBuilder}</li>
   *  <li>{@link FedAuthPropertiesBuilder}</li>
   *  <li>{@link OAuthPropertiesBuilder}</li>
   *  <li>{@link OpenIDConnectPropertiesBuilder}</li>
   *  <li>{@link LocalAuthPropertiesBuilder}</li>
   * </ul>
   * @class PropertiesBuilder
   * @abstract
   */
  var PropertiesBuilder = function() {
    /**
     * Bag of all properties.
     * @memberof PropertiesBuilder.prototype
     */
    this.props = {};

    /**
     * Convenience method to add key value pairs of auth settings to properties.
     * @function put
     * @memberof PropertiesBuilder.prototype
     * @param {string} key - Authentication property key
     * @param {string} value - Authentication property value
     * @return {Builder}
     */
    this.put = function(key, value)
    {
      this.props[key] = value;
      return this;
    };
  };

  PropertiesBuilder.prototype = Object.create(Object.prototype, {
    /**
     * @function build
     * @memberof PropertiesBuilder.prototype
     * @return {Object} validate and return properties collected.
     */
    build: {
      value: function() {
        return this.props;
      }
    }
  });

  /**
   * @class RemoteAuthPropertiesBuilder
   * @classdesc This class is the base builder for all remote auth types.
   * Use one of the subclasses to instantiate:
   * <ul style="list-style: none;">
   *  <li>{@link HttpBasicAuthPropertiesBuilder}</li>
   *  <li>{@link FedAuthPropertiesBuilder}</li>
   *  <li>{@link OAuthPropertiesBuilder}</li>
   *  <li>{@link OpenIDConnectPropertiesBuilder}</li>
   * </ul>
   * @extends PropertiesBuilder
   * @param {string} appName - Application name
   * @abstract
   */
  var RemoteAuthPropertiesBuilder = function(appName)
  {
    PropertiesBuilder.call(this);

    /**
     * Authentication cancel callback. Used to cancel the challenge.
     * @callback RemoteAuthPropertiesBuilder~challengeCancelCallback
     */
    /**
     * Remote authentication challenge completion callback
     * @callback RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback
     * @param {RemoteAuthPropertiesBuilder~AuthChallenge} challenge - information collected from the user.
     */
    /**
     * Remote authentication challenge handler
     * @typedef {Object} RemoteAuthPropertiesBuilder~RemoteAuthChallengeHandler
     * @property {RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback} submit - App invokes this passing user input to proceed with the current challenge.
     * @property {RemoteAuthPropertiesBuilder~challengeCancelCallback} cancel - App invokes this to cancel the current challenge.
     */
    /**
     * This is the remote authentication challenge callback.
     * This callback is invoked when there is a challenge to collect credentials from the user in some authentications such as
     * {@link HttpBasicAuthPropertiesBuilder|HTTP basic auth} and {@link OAuthPropertiesBuilder|2 legged OAuth}.
     * This callback should be implemented by app and set to the respective builder as the {@link HttpBasicAuthPropertiesBuilder#challengeCallback|challenge callback}.
     * <p>In this callback app has to do the following:</p>
     * <p>On receiving this challenge, app should show a login screen to the user.
     * {@link RemoteAuthPropertiesBuilder~AuthChallenge|Challenge object} passed to this callback will contain any saved information,
     * such as saved username or password or saved preferences for remember user, remember credentials, auto login etc.
     * App should populate the login screen based on this information as explained in the {@link RemoteAuthPropertiesBuilder~AuthChallenge|challenge object documentation}.
     * Once user has provided the input, app should update the challenge object
     * and invoke {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|completion callback}.
     * If user wishes to cancel the login then app should invoke {@link RemoteAuthPropertiesBuilder~challengeCancelCallback|cancel callback}.
     * Both callbacks are passed to this as part of {@link RemoteAuthPropertiesBuilder~RemoteAuthChallengeHandler|challenge handler}</p>
     * @callback RemoteAuthPropertiesBuilder~remoteAuthChallengeCallback
     * @param {RemoteAuthPropertiesBuilder~AuthChallenge} challenge - Saved data if any and information to be collected from the user.
     * @param {RemoteAuthPropertiesBuilder~RemoteAuthChallengeHandler} challengeHandler - To be used by app to either submit or cancel the challenge.
     */
    /**
     * This callback represents remote authentication timeout callback. This is invoked when a timeout occurs in certain type of authentications.
     * <p>For {@link HttpBasicAuthPropertiesBuilder|HTTP basic auth} this callback will be invoked in following conditions:
     * <ul>
     * <li>{@link RemoteAuthPropertiesBuilder.TimeoutType|Idle timeout} with non zero {@link RemoteAuthPropertiesBuilder~TimeoutResponse|time left to timeout}
     * - This happens when {@link HttpBasicAuthPropertiesBuilder#percentageToIdleTimeout|percentage to idle timeout} is set to a non zero value.
     * In this case, the timeout can be extended by invoking {@link HttpBasicAuthenticationFlow#resetIdleTimeout}.</li>
     * <li>{@link RemoteAuthPropertiesBuilder.TimeoutType|Idle timeout} with zero {@link RemoteAuthPropertiesBuilder~TimeoutResponse|time left to timeout}
     * - User has to be re-authenticated. The app has to invoke {@link HttpBasicAuthenticationFlow#login}.
     * If {@link HttpBasicAuthPropertiesBuilder#autoLoginAllowed|auto login is allowed} and {@link RemoteAuthPropertiesBuilder~AuthChallenge|enabled} by user, login happens without challenge.</li>
     * <li>{@link RemoteAuthPropertiesBuilder.TimeoutType|Session timeout} ({@link RemoteAuthPropertiesBuilder~TimeoutResponse|time left to timeout} will always be zero for this)
     * - User has to be re-authenticated. The app has to invoke {@link AuthenticationFlow#login}. Auto login does not help here.</li>
     * </ul></p>
     * <p> For {@link FedAuthPropertiesBuilder|Federated auth}, there is only session timeout.
     * User has to be re-authenticated. The app has to invoke {@link AuthenticationFlow#login}.</p>
     * @callback RemoteAuthPropertiesBuilder~timeoutCallback
     * @param {RemoteAuthPropertiesBuilder~TimeoutResponse} timeoutResponse
     */
    /**
     * Object passed in {@link RemoteAuthPropertiesBuilder~timeoutCallback|timeout callback}
     * @typedef RemoteAuthPropertiesBuilder~TimeoutResponse
     * @property {RemoteAuthPropertiesBuilder.TimeoutType} TimeoutType - Type of timeout.
     * @property {number} TimeLeftToTimeout - Time in seconds after which timeout will happen.
     */
    /**
     * Challenge object passed in {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCallback|remote challenge callback}
     * @typedef RemoteAuthPropertiesBuilder~AuthChallenge
     * @property {String} username_key - <p>This is the username.</p> <p><i>When challenged:</i> Populated with saved username if any. App should use this value to display on the login screen.
     * Typically this is a text field on the UI.</p>
     * <p><i>When submitting:</i> Should contain user input when submitting the challenge using {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|submit callback}.</p>
     * @property {String} password_key - <p>This is the password.</p> <p><i>When challenged:</i> Populated with saved password if any. App should use this value to display on the login screen.
     * Typically this is a password field on the UI.</p>
     * <p><i>When submitting:</i> Should contain user input when submitting the challenge using {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|submit callback}.
     * @property {boolean} CollectIdentityDomain - <p>This is a read only property. This will indicate if app should collect identity domain from user or not.
     * @property {String=} iddomain_key - <p>This is the identity domain. <p><i>When challenged:</i> Populated with saved identity domain if any.
     * This is an optional field. App should use this value to display on the login screen.
     * Typically this is a text field on the UI. App should not showing this field if this key is missing in the challenge object.</p>
     * <p><i>When submitting:</i> Should contain user input when submitting the challenge using {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|submit callback}.
     * @property {AuthError=} error - <p>This is an error object.</p> <p><i>When challenged:</i> Populated with any error associated with previous login attempt.
     * This is available only when previous login attempt failed the user is re-challenged.
     * Typically used to display error message to the user.
     * <p><i>When submitting:</i> This need not be set when submitting the challenge using {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|submit callback}.</p>
     * @property {boolean} RememberUsernameAllowed - <p>This is a read only property. This will indicate if remember username feature is enabled or not.
     * App can decide to show this option to the user on the login screen depending on the value.
     * @property {boolean=} remember_username_ui_preference_key - <p>This is a user preference to say if the user wants app to remember the username or not.</p>
     * <p><i>When challenged:</i> Populated with saved preference if any.
     * This field is available only if {@link HttpBasicAuthPropertiesBuilder#rememberUsernameAllowed|allow remember user} is set to true in the configuration.
     * The value of this will be driven by {@link HttpBasicAuthPropertiesBuilder#rememberUsernameDefault|remember user default value}.
     * Typically collected using a checkbox. App should not showing this field if this key is missing in the challenge object.
     * It means {@link HttpBasicAuthPropertiesBuilder#rememberUsernameAllowed|allow remember user} is set to false in the configuration. </p>
     * <p><i>When submitting:</i> User can modify this value and modified value should be part of the challenge when
     * submitting using {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|submit callback}.
     * User provided value will have precedence over {@link HttpBasicAuthPropertiesBuilder#rememberUsernameDefault|remember user  default value} initially.
     * @property {boolean} RememberCredentialsAllowed - <p>This is a read only property. This will indicate if remember credentials feature is enabled or not.
     * App can decide to show this option to the user on the login screen depending on the value.
     * @property {boolean=} remember_credentials_ui_preference_key - <p>This is a user preference to say if the user wants app to remember credentials or not.</p>
     * <p><i>When challenged:</i> Populated with saved preference if any.
     * This field is available only if {@link HttpBasicAuthPropertiesBuilder#rememberCredentialsAllowed| allow remember credentials} is true in the configuration.
     * The value of this will be driven by {@link HttpBasicAuthPropertiesBuilder#rememberCredentialDefault|remember credentials default value} initially.
     * Typically collected using a checkbox. App should not showing this field if this key is missing in the challenge object.
     * It means {@link HttpBasicAuthPropertiesBuilder#rememberCredentialsAllowed|allow remember credentials} is set to false in the configuration. </p>
     * <p><i>When submitting:</i> User can modify this value and modified value should be part of the challenge when
     * submitting using {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|submit callback}.
     * User provided value will have precedence over {@link HttpBasicAuthPropertiesBuilder#rememberCredentialDefault|remember credentials default value}.
     * @property {boolean} AutoLoginAllowed - <p>This is a read only property. This will indicate if auto login is enabled or not.
     * App can decide to show this option to the user on the login screen depending on the value.
     * @property {boolean=} autoLogin_ui_preference_key - <p>This is a user preference to say if the user wants app to automatically login the user or not.</p>
     * <p><i>When challenged:</i> Populated with saved preference if any.
     * This field is available only if {@link HttpBasicAuthPropertiesBuilder#autoLoginAllowed| allow auto login} is set to true in the configuration.
     * The value of this will be driven by {@link HttpBasicAuthPropertiesBuilder#autoLoginDefault|auto login default value} initially.
     * Typically collected using a checkbox. App should not showing this field if this key is missing in the challenge object.
     * It means {@link HttpBasicAuthPropertiesBuilder#autoLoginAllowed|allow auto login} is set to false in the configuration.</p>
     * <p><i>When submitting:</i> User can modify this value and modified value should be part of the challenge when submitting using {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCompletionCallback|submit callback}.
     * User provided value will have precedence over {@link HttpBasicAuthPropertiesBuilder#autoLoginDefault|auto login default value}.</p>
     */

    /**
     * @function appName
     * @memberof RemoteAuthPropertiesBuilder.prototype
     * @param {String} appName - Application name
     * @return {RemoteAuthPropertiesBuilder}
     */
    this.appName = function(name) {
      assertString(name, authPropertyKeys.ApplicationName);
      this.put(authPropertyKeys.ApplicationName, name);
      return this;
    };

    /**
     * Deprecated: This is not applicable for all remote authentications. Use method available in builder subclasses where applicable.
     * @function idleTimeOutInSeconds
     * @memberof RemoteAuthPropertiesBuilder.prototype
     * @param {number} timeout - seconds after which which idle timeout should kick in when user is idle.
     * @return {RemoteAuthPropertiesBuilder}
     * @deprecated This is not applicable for all remote authentications. Use method available in builder subclasses where applicable.
     */
    this.idleTimeOutInSeconds = function(timeout)
    {
      assertPositiveOrZero(timeout, authPropertyKeys.IdleTimeOutValue);
      this.put(authPropertyKeys.IdleTimeOutValue, timeout);
      return this;
    };
    /**
     * Deprecated: This is not applicable for all remote authentications. Use method available in builder subclasses where applicable.
     * @function percentageToIdleTimeout
     * @memberof RemoteAuthPropertiesBuilder.prototype
     * @param {number} percentage - percentage of idle timeout before which timeout callback should be invoked.
     * @return {RemoteAuthPropertiesBuilder}
     * @deprecated This is not applicable for all remote authentications. Use method available in builder subclasses where applicable.
     */
    this.percentageToIdleTimeout = function(percentage)
    {
      assertPositiveOrZero(percentage, authPropertyKeys.PercentageToIdleTimeout);
      if (percentage < 0 || percentage > 100) {
        throw new Error(authPropertyKeys.PercentageToIdleTimeout + ' should be between [0 - 100].');
      }
      this.put(authPropertyKeys.PercentageToIdleTimeout, percentage);
      return this;
    };
    /**
     * Deprecated: This is not applicable for all remote authentications. Use method available in builder subclasses where applicable.
     * @function sessionTimeOutInSeconds
     * @memberof RemoteAuthPropertiesBuilder.prototype
     * @param {number} timeout - seconds after which which session timeout should kick in.
     * @return {RemoteAuthPropertiesBuilder}
     * @deprecated This is not applicable for all remote authentications. Use method available in builder subclasses where applicable.
     */
    this.sessionTimeOutInSeconds = function(timeout)
    {
      assertPositiveOrZero(timeout, authPropertyKeys.SessionTimeOutValue);
      this.put(authPropertyKeys.SessionTimeOutValue, timeout);
      return this;
    };
    /**
     * @function logoutTimeOutInSeconds
     * @memberof RemoteAuthPropertiesBuilder.prototype
     * @param {number} timeout - specifiy timeout before which ongoing logout attempt will be aborted.
     * @return {RemoteAuthPropertiesBuilder}
     */
    this.logoutTimeOutInSeconds = function(timeout)
    {
      assertPositiveOrZero(timeout, authPropertyKeys.LogoutTimeOutValue);
      this.put(authPropertyKeys.LogoutTimeOutValue, timeout);
      return this;
    };
    /**
     * @function customAuthHeaders
     * @memberof RemoteAuthPropertiesBuilder.prototype
     * @param {Object.<string, string>} headers - any custom headers. These are returned along with other authentication headers in RemoteAuthenticationFlow#getHeaders()
     * @return {RemoteAuthPropertiesBuilder}
     */
    this.customAuthHeaders = function(headers)
    {
      assertObject(headers, authPropertyKeys.CustomAuthHeaders);
      this.put(authPropertyKeys.CustomAuthHeaders, headers);
      this.put(authPropertyKeys.CustomHeadersForMobileAgent, headers);
      return this;
    };

    if (appName)
      this.appName(appName);
  };

  RemoteAuthPropertiesBuilder.prototype = Object.create(PropertiesBuilder.prototype, {
    /**
     * @function build
     * @memberof RemoteAuthPropertiesBuilder.prototype
     * @return {Object} validate and return properties collected.
     */
    build: {
      value: function() {
        if (!this.props[authPropertyKeys.ApplicationName])
          throw new Error('Mandatory parameter appName not set.');
        return Object.getPrototypeOf(RemoteAuthPropertiesBuilder.prototype).build.call(this);
      }
    }
  });
  RemoteAuthPropertiesBuilder.prototype.constructor = RemoteAuthPropertiesBuilder;
  /**
   * Possible values for {@link RemoteAuthPropertiesBuilder~TimeoutResponse|RemoteAuthPropertiesBuilder.TimeoutResponse.TimeoutType}
   * @memberof RemoteAuthPropertiesBuilder
   * @enum
   * @readonly
   */
  RemoteAuthPropertiesBuilder.TimeoutType = {
    /**
     * Timeout type is session timeout
     * @type {string}
     */
   SessionTimeout:'SESSION_TIMEOUT',
    /**
     * Timeout type is idle timeout
     * @type {string}
     */
   IdleTimeout:'IDLE_TIMEOUT'
  };


  /**
   * @class HttpBasicAuthPropertiesBuilder
   * @classdesc This class is the builder for HTTP Basic Authentication.
   * Using this builder to {@link init} an authentication flow will return {@link HttpBasicAuthenticationFlow} in the init promise.
   * <p>In this type of authentication, while logging in, user will be challenged to provide credentials and preferences.
   * At this time {@link HttpBasicAuthPropertiesBuilder#challengeCallback|challenge callback} provided by app will get invoked.
   * App should collect these from the user and pass it back.
   * See {@link RemoteAuthPropertiesBuilder~remoteAuthChallengeCallback|remote authentication challenge callback documentation} for details.
   * In addition, app can attach {@link RemoteAuthPropertiesBuilder~timeoutCallback|timeout callback} to handle authentication timeouts.</p>
   * @extends RemoteAuthPropertiesBuilder
   * @param {string} appName - Application name
   * @param {string} loginUrl - Basic auth login end point.
   * @param {string} logoutUrl - Basic auth logout end point.
   */
  var HttpBasicAuthPropertiesBuilder = function(appName, loginUrl, logoutUrl) {
    RemoteAuthPropertiesBuilder.call(this, appName);
    this.put(authPropertyKeys.AuthServerType, authServerTypes.HttpBasicAuthentication);
    // AES is the only crypto scheme that the plugin will support.
    // This is because plugin needs to be able to retrieve unencrypted credentials for headers and AES is only way to do that.
    this.put(authPropertyKeys.CryptoScheme, 'AES');
    // Always set headers for logout.
    this.put(authPropertyKeys.SendAuthorizationHeaderInLogout, true);
    this.put(authPropertyKeys.SendCustomAuthHeadersInLogout, true);

    /**
     * @function loginUrl
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {string} url - Basic auth login end point.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.loginUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LoginURL);
      this.put(authPropertyKeys.LoginURL, url);
      return this;
    };
    /**
     * @function logoutUrl
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {string} url - Basic auth logout end point.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.logoutUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LogoutURL);
      this.put(authPropertyKeys.LogoutURL, url);
      return this;
    };
    /**
     * This has effect only when {@link HttpBasicAuthPropertiesBuilder#offlineAuthAllowed} is set to 'true'.
     * When set to 'false' will result in an {@link ConnectivityMode|Online} login always.
     * @function connectivityMode
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {HttpBasicAuthPropertiesBuilder.ConnectivityMode} mode - connectivity mode to be used
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.connectivityMode = function(mode)
    {
      assertString(mode, authPropertyKeys.ConnectivityMode);
      if (!HttpBasicAuthPropertiesBuilder.ConnectivityMode.hasOwnProperty(mode)) {
        throw new Error(authPropertyKeys.ConnectivityMode + ' should be one from HttpBasicAuthPropertiesBuilder.ConnectivityMode.');
      }
      this.put(authPropertyKeys.ConnectivityMode, mode);
      return this;
    };

    /**
     * Defaults to true. In general, apps want to retrieve headers for secured resource access.
     * Apps can set this to false if they do not want the functionality.
     * <p>
     * Setting this to true results in credentials being stored offline in secured storage.
     * App will be authenticated against this, when {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode|Offline} login applies.</p>
     * <p> No matter what {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode} is set, first time login will always be ONLINE.
     * Offline credentials will be cleared if user exceeds the {@link HttpBasicAuthPropertiesBuilder#maxLoginAttempts} while logging in.</p>
     * <ul>
     * <li>When set to false results in ONLINE login always, no matter what {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode} is set.</li>
     * <li>When set to true and {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode} is set to ONLINE. This results in an ONLINE login always.</li>
     * <li>When set to true and {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode} is set to OFFLINE.
     * This results in OFFLINE login for subsequent attempts. In the case of wrong credentials,
     * an ONLINE login will be attempted after maxLoginAttempts is exceeded.</li>
     * <li>When set to true and {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode} is set to AUTO.
     * Cookie validity determines the type if subsequent login performed.
     * If cookies are valid, it will be an OFFLINE login, otherwise it will be ONLINE login.</li>
     * </ul></p>
     * <p> Note: {@link RemoteAuthenticationFlow#getHeaders} API depends on this to be able to retrieve the Authorization headers.</p>
     * @function offlineAuthAllowed
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} allowed - if offline auth is allowed or not. Defaults to 'true'.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.offlineAuthAllowed = function(allowed)
    {
      assertBoolean(allowed, authPropertyKeys.OfflineAuthAllowed);
      this.put(authPropertyKeys.OfflineAuthAllowed, allowed);
      return this;
    };

    /**
     * @function maxLoginAttempts
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {number} attempts - maximum login attempts
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.maxLoginAttempts = function(attempts)
    {
      assertPositiveOrZero(attempts, authPropertyKeys.MaxLoginAttempts);
      this.put(authPropertyKeys.MaxLoginAttempts, attempts);
      return this;
    };

    /**
     * This method is for specifying if user is allowed to configure remember user preference or not.
     * This allows app to control what the user can do in the login screen. If this is set to 'true'
     * value set in {@link HttpBasicAuthPropertiesBuilder#rememberUsernameDefault} will be returned in the
     * {@link RemoteAuthPropertiesBuilder~AuthChallenge| challenge} for remember user preference.
     * @function rememberUsernameAllowed
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} allowed - if user can be allowed to change the preference for remembering user name .
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.rememberUsernameAllowed = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.RememberUsernameAllowed);
      this.put(authPropertyKeys.RememberUsernameAllowed, bool);
      return this;
    };

    /**
     * This method is for specifying if user is allowed to configure remember credentials preference or not.
     * This allows app to control what the user can do in the login screen. If this is set to 'true'
     * value set in {@link HttpBasicAuthPropertiesBuilder#rememberCredentialDefault} will be returned in the
     * {@link RemoteAuthPropertiesBuilder~AuthChallenge| challenge} for remember password preference.
     * @function rememberCredentialsAllowed
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} allowed - if user can be allowed to change the preference for remembering credential.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.rememberCredentialsAllowed = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.RememberCredentialsAllowed);
      this.put(authPropertyKeys.RememberCredentialsAllowed, bool);
      return this;
    };

    /**
     * This method is for specifying if user is allowed to configure automatic login preference or not.
     * This allows app to control what the user can do in the login screen. If this is set to 'true'
     * value set in {@link HttpBasicAuthPropertiesBuilder#autoLoginDefault} will be returned in the
     * {@link RemoteAuthPropertiesBuilder~AuthChallenge| challenge} for auto login preference.
     * This feature enables user to login without challenge, after first successful login, until session times out or user logs out.
     * @function autoLoginAllowed
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} allowed - if user can be allowed to change the preference for auto login.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.autoLoginAllowed = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.AutoLoginAllowed);
      this.put(authPropertyKeys.AutoLoginAllowed, bool);
      return this;
    };

    /**
     * This method sets the default value for remember user preference.
     * This preference is applicable only when {HttpBasicAuthPropertiesBuilder#rememberUsernameAllowed} is true
     * @function rememberUsernameDefault
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} allowed - default value for remember username preference.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.rememberUsernameDefault = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.RememberUsernameDefault);
      this.put(authPropertyKeys.RememberUsernameDefault, bool);
      return this;
    };

    /**
     * This method sets the default value for remember credentials preference.
     * This preference is applicable only when {HttpBasicAuthPropertiesBuilder#rememberCredentialsAllowed} is true
     * @function rememberCredentialDefault
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} allowed - default value for remember credential preference.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.rememberCredentialDefault = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.RememberCredentialDefault);
      this.put(authPropertyKeys.RememberCredentialDefault, bool);
      return this;
    };

    /**
     * This method sets the default value for automatic login preference.
     * This preference is applicable only when {HttpBasicAuthPropertiesBuilder#autoLoginAllowed} is true
     * @function autoLoginDefault
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} allowed - default value for auto login preference.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.autoLoginDefault = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.AutoLoginDefault);
      this.put(authPropertyKeys.AutoLoginDefault, bool);
      return this;
    };

    /**
     * @function idleTimeOutInSeconds
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {number} timeout - seconds after which which idle timeout should kick in when user is idle.
     * After these many seconds {@link RemoteAuthPropertiesBuilder~timeoutCallback|timeout callback} will be invoked.
     * Note: 'Idle' is not tied to app usage yet. It is based on IDM isValid API invocation.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.idleTimeOutInSeconds = function(timeout)
    {
      assertPositiveOrZero(timeout, authPropertyKeys.IdleTimeOutValue);
      this.put(authPropertyKeys.IdleTimeOutValue, timeout);
      return this;
    };

    /**
     * @function percentageToIdleTimeout
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {number} percentage - percentage of idle timeout before which timeout callback should be invoked.
     * This can be used to alerted user about the upcoming idle timeout in {@link RemoteAuthPropertiesBuilder~timeoutCallback|timeout callback}
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.percentageToIdleTimeout = function(percentage)
    {
      assertPositiveOrZero(percentage, authPropertyKeys.PercentageToIdleTimeout);
      if (percentage < 0 || percentage > 100) {
        throw new Error(authPropertyKeys.PercentageToIdleTimeout + ' should be between [0 - 100].');
      }
      this.put(authPropertyKeys.PercentageToIdleTimeout, percentage);
      return this;
    };

    /**
     * @function sessionTimeOutInSeconds
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {number} timeout - seconds after which which session timeout should kick in.
     * After these many seconds {@link RemoteAuthPropertiesBuilder~timeoutCallback|timeout callback} will be invoked.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.sessionTimeOutInSeconds = function(timeout)
    {
      assertPositiveOrZero(timeout, authPropertyKeys.SessionTimeOutValue);
      this.put(authPropertyKeys.SessionTimeOutValue, timeout);
      return this;
    };

    /**
     * @function challengeCallback
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {RemoteAuthPropertiesBuilder~remoteAuthChallengeCallback} callback - Callback to handle credential challenge.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.challengeCallback = function(callback)
    {
      assertFunction(callback, authPropertyKeys.ChallengeCallback);
      this.put(authPropertyKeys.ChallengeCallback, callback);
      return this;
    };

    /**
     * @function timeoutCallback
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {RemoteAuthPropertiesBuilder~timeoutCallback} callback - Callback to handle timeout notifications.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.timeoutCallback = function(callback)
    {
      assertFunction(callback, authPropertyKeys.TimeoutCallback);
      this.put(authPropertyKeys.TimeoutCallback, callback);
      return this;
    };

    /**
     * This is to specify if the user challenge should contain identity domain or not. Defaults to false.
     * @function collectIdentityDomain
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} collect - true to collect identity domain when user is challenged.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.collectIdentityDomain = function(collect) {
      assertBoolean(collect, authPropertyKeys.CollectIdentityDomain);
      this.put(authPropertyKeys.CollectIdentityDomain, collect);
      return this;
    };

    /**
     * This is to specify whether identity domain is sent as header value as
     * per {@link HttpBasicAuthPropertiesBuilder#identityDomainHeaderName}, when set to true,
     * or should be prepended with the user name as identity_domain_name.user_name,
     * when set to false.
     * Default value is false.
     * @function passIdentityDomainNameInHeader
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {boolean} pass - true to pass identity domain in header
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.passIdentityDomainNameInHeader = function(pass) {
      assertBoolean(pass, authPropertyKeys.IdentityDomainNameInHeader);
      this.put(authPropertyKeys.IdentityDomainNameInHeader, pass);
      return this;
    };

    /**
     * Works only when {@link HttpBasicAuthPropertiesBuilder#passIdentityDomainNameInHeader} is set to true.
     * Default value is X-USER-IDENTITY-DOMAIN-NAME.
     * @function identityDomainHeaderName
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @param {string} headerName - header name to be used.
     * @return {HttpBasicAuthPropertiesBuilder}
     */
    this.identityDomainHeaderName = function(headerName) {
      assertString(headerName, authPropertyKeys.IdentityDomainHeaderName);
      this.put(authPropertyKeys.IdentityDomainHeaderName, headerName);
      return this;
    };

    if (loginUrl)
      this.loginUrl(loginUrl);
    if (logoutUrl)
      this.logoutUrl(logoutUrl);

    this.offlineAuthAllowed(true);
  };

  HttpBasicAuthPropertiesBuilder.prototype = Object.create(RemoteAuthPropertiesBuilder.prototype, {
    /**
     * @function build
     * @memberof HttpBasicAuthPropertiesBuilder.prototype
     * @return {Object} validate and return properties collected.
     */
    build: {
      value: function() {
        if (!this.props[authPropertyKeys.LoginURL])
          throw new Error('Mandatory parameter loginUrl not set.');
        if (!this.props[authPropertyKeys.LogoutURL])
          throw new Error('Mandatory parameter logoutUrl not set.');
        return Object.getPrototypeOf(HttpBasicAuthPropertiesBuilder.prototype).build.call(this);
      }
    }
  });
  HttpBasicAuthPropertiesBuilder.prototype.constructor = HttpBasicAuthPropertiesBuilder;
  /**
   * Enum values for {@link HttpBasicAuthPropertiesBuilder#connectivityMode}
   * @memberof HttpBasicAuthPropertiesBuilder
   * @enum
   * @readonly
   */
  HttpBasicAuthPropertiesBuilder.ConnectivityMode = {
    /**
     * Connectivity mode is online
     * @type {string}
     */
    Online:'Online',
    /**
     * Connectivity mode is offline
     * @type {string}
     */
    Offline:'Offline',
    /**
     * Connectivity mode is auto
     * @type {string}
     */
    Auto:'Auto'
  };

  /**
   * @class FedAuthPropertiesBuilder
   * @classdesc This is the builder for federated authentication / web SSO.
   * Using this builder to {@link init} an authentication flow will return {@link RemoteAuthenticationFlow} in the init promise.
   * <p>In this type of authentication, while logging in, the plugin brings up a WebView and loads the {@link FedAuthPropertiesBuilder#loginUrl|login page} provided in the configuration.
   * User has credentials on this page. When login is successful, the WebView will be removed and user will be redirected back to the app.
   * While showing the WebView, plugin provides basic operations such as "Forward", "Back", "Reload" and "Cancel" to deal with any issues the user faces on this page.
   * For example, user may accidentally click a link on the login page. In this case, user can use "Back" button to come back to the login page.
   * For example, if the login page is not loaded correctly, user may want to try reloading the page, before cancelling the login.
   * If user cancels the login, the promise returned by {@link AuthenticationFlow#login} will be rejected.
   * For iOS, {@link https://developer.apple.com/documentation/webkit/wkwebview|WKWebView} will
   * be used by default.
   * </p>
   * <p>
   * While logging out, the plugin brings up a WebView and loads the {@link FedAuthPropertiesBuilder#logoutUrl|logout page} provided in the configuration.
   * Typically this step does not have any user interaction. The logout page loads with a confirmation and then is dismissed.
   * However, some federated authentication servers provide a logout confirmation screen where the user is expected to provide his consent for logout.
   * This feature is introduced by certain federated auth servers as they wanted the user to be fully aware that they are logging out and does not do so accidentally.
   * There are two ways to handle this situation. First by having the confirmation screen dismissed automatically, without user interaction.
   * This can be achieved by setting {@link FedAuthPropertiesBuilder#confirmLogoutAutomatically} to true and providing
   * {@link FedAuthPropertiesBuilder#confirmLogoutButtonId} if needed. The other way is to wait for user to provide his consent.
   * This can be done by specifying {@link FedAuthPropertiesBuilder#logoutSuccessUrl} and {@link FedAuthPropertiesBuilder#logoutFailureUrl}.
   * Note: Irrespective of whether user cancels the logout or accepts the logout in the confirmation screen, the user is logged out.</p>
   * @extends RemoteAuthPropertiesBuilder
   * @param {string} appName - Application name
   * @param {string} loginUrl - Fed auth login end point.
   * @param {string} logoutUrl - Fed auth logout end point.
   * @param {string} loginSuccessUrl - End point to which server redirects after successful login.
   * @param {string} loginFailureUrl - End point to which server redirects after unsuccessful login.
   */
   var FedAuthPropertiesBuilder = function(appName, loginUrl, logoutUrl, loginSuccessUrl, loginFailureUrl) {
    RemoteAuthPropertiesBuilder.call(this, appName);
    this.put(authPropertyKeys.AuthServerType, authServerTypes.FederatedAuthentication);
    // There is a feature to remember the user name for fed auth cases.
    // Android has this support inbuilt in the OS level. So setting this property does not make a difference for Android.
    // iOS SDK needs this flag to remember the user name. So to make things consistent across platforms, this is set by default.
    // In android, user has to long press the user name field to get access to the remembered username.
    // In iOS, it is pre-populated.
    this.put(authPropertyKeys.RememberUsernameAllowed, true);

    /**
     * @function loginUrl
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {string} url - Fed auth login end point.
     * @return {FedAuthPropertiesBuilder}
     */
    this.loginUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LoginURL);
      this.put(authPropertyKeys.LoginURL, url);
      return this;
    };
    /**
     * @function logoutUrl
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {string} url - Fed auth logout end point.
     * @return {FedAuthPropertiesBuilder}
     */
    this.logoutUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LogoutURL);
      this.put(authPropertyKeys.LogoutURL, url);
      return this;
    };
    /**
     * @function loginSuccessUrl
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {string} url - End point to which server redirects after successful login.
     * @return {FedAuthPropertiesBuilder}
     */
    this.loginSuccessUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LoginSuccessURL);
      this.put(authPropertyKeys.LoginSuccessURL, url);
      return this;
    };
    /**
     * @function loginFailureUrl
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {string} url - End point to which server redirects after unsuccessful login.
     * @return {FedAuthPropertiesBuilder}
     */
    this.loginFailureUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LoginFailureURL);
      this.put(authPropertyKeys.LoginFailureURL, url);
      return this;
    };
    /**
     * @function parseTokenRelayResponse
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {boolean} allow - if relay response token needs to be parsed. Used in case of SAML flows.
     * @return {FedAuthPropertiesBuilder}
     */
    this.parseTokenRelayResponse = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.ParseTokenRelayResponse);
      this.put(authPropertyKeys.ParseTokenRelayResponse, bool);
      return this;
    };

    /**
     * @function enableWkWebView
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {boolean} enable - if WKWebView should be enabled. Applicable only for iOS.
     * Note: App should install cordova-plugin-wkwebview-engine when using this.
     * @return {FedAuthPropertiesBuilder}
     */
    this.enableWkWebView = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.EnableWkWebView);
      this.put(authPropertyKeys.EnableWkWebView, bool);
      return this;
    };

    /**
     * @function confirmLogoutAutomatically
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {boolean} automatic - whether to confirm the logout automatically when presented with a logout confirmation screen from the server.
     * @return {FedAuthPropertiesBuilder}
     */
    this.confirmLogoutAutomatically = function(bool)
    {
      assertBoolean(bool, authPropertyKeys.ConfirmLogoutAutomatically);
      this.put(authPropertyKeys.ConfirmLogoutAutomatically, bool);
      return this;
    };

    /**
     * @function confirmLogoutButtonId
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {string} buttonId - DOM id of the logout confirmation button. Used when FedAuthPropertiesBuilder#confirmLogoutAutomatically is turned on.
     * @return {FedAuthPropertiesBuilder}
     */
    this.confirmLogoutButtonId = function(buttonId)
    {
      assertString(buttonId, authPropertyKeys.ConfirmLogoutButtonId);
      this.put(authPropertyKeys.ConfirmLogoutButtonId, buttonId);
      return this;
    };

    /**
     * @function logoutSuccessUrl
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {string} url - End point to which server redirects after successful logout. Used along with logout confirmation screen usecase.
     * @return {FedAuthPropertiesBuilder}
     */
    this.logoutSuccessUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LogoutSuccessURL);
      this.put(authPropertyKeys.LogoutSuccessURL, url);
      return this;
    };

    /**
     * @function logoutFailureUrl
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {string} url - End point to which server redirects after unsuccessful logout. Used along with logout confirmation screen usecase.
     * @return {FedAuthPropertiesBuilder}
     */
    this.logoutFailureUrl = function(url)
    {
      assertUrl(url, authPropertyKeys.LogoutFailureURL);
      this.put(authPropertyKeys.LogoutFailureURL, url);
      return this;
    };

    /**
     * @function sessionTimeOutInSeconds
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {number} timeout - set to the same value as configured in the fed auth server.
     * After these many seconds {@link RemoteAuthPropertiesBuilder~timeoutCallback|timeout callback} will be invoked.
     * Note: This does not trigger any session timeout on the server. That configuration is controlled by the server.
     * The effect of setting this is only to remove cookies after such time when server session would have time out.
     * @return {FedAuthPropertiesBuilder}
     */
    this.sessionTimeOutInSeconds = function(timeout)
    {
      assertPositiveOrZero(timeout, authPropertyKeys.SessionTimeOutValue);
      this.put(authPropertyKeys.SessionTimeOutValue, timeout);
      return this;
    };

    /**
     * @function timeoutCallback
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {RemoteAuthPropertiesBuilder~timeoutCallback} callback - Callback to handle timeout notifications.
     * @return {FedAuthPropertiesBuilder}
     */
    this.timeoutCallback = function(callback)
    {
      assertFunction(callback, authPropertyKeys.TimeoutCallback);
      this.put(authPropertyKeys.TimeoutCallback, callback);
      return this;
    };

    /**
     * This does not work for normal FedAuth cases. This is applicable for SAML kind of cases where
     * {@link FedAuthPropertiesBuilder#parseTokenRelayResponse} is turned on.
     * In this context, this parameter can be used for turning on access token reuse over app restarts.
     * With this turned on, when app is restarted, a user trying to login won't be prompted for
     * credentials, if there is a valid JWT token.
     * @function sessionActiveOnRestart
     * @memberof FedAuthPropertiesBuilder.prototype
     * @param {boolean} active - Whether to preserve login across restarts.
     * @return {FedAuthPropertiesBuilder}
     */
    this.sessionActiveOnRestart = function(active) {
      assertBoolean(active, authPropertyKeys.SessionActiveOnRestart);
      this.put(authPropertyKeys.SessionActiveOnRestart, active);
      return this;
    }

    if (loginUrl)
      this.loginUrl(loginUrl);
    if (logoutUrl)
      this.logoutUrl(logoutUrl);
    if (loginSuccessUrl)
      this.loginSuccessUrl(loginSuccessUrl);
    if (loginFailureUrl)
      this.loginFailureUrl(loginFailureUrl);
  };

  FedAuthPropertiesBuilder.prototype = Object.create(RemoteAuthPropertiesBuilder.prototype, {
    /**
     * @function build
     * @memberof FedAuthPropertiesBuilder.prototype
     * @return {Object} validate and return properties collected.
     */
    build: {
      value: function() {
        if (!this.props[authPropertyKeys.LoginURL])
          throw new Error('Mandatory parameter loginUrl not set.');
        if (!this.props[authPropertyKeys.LogoutURL])
          throw new Error('Mandatory parameter logoutUrl not set.');
        if (!this.props[authPropertyKeys.LoginSuccessURL])
          throw new Error('Mandatory parameter loginSuccessUrl not set.');
        if (!this.props[authPropertyKeys.LoginFailureURL])
          throw new Error('Mandatory parameter loginFailureUrl not set.');

        // As per IDM SDK, when SessionActiveOnRestart is set, session timeout and idle timeout should not be set.
        if (this.props[authPropertyKeys.SessionActiveOnRestart]) {
          delete this.props[authPropertyKeys.SessionTimeOutValue];
          delete this.props[authPropertyKeys.IdleTimeOutValue];
        }

        return Object.getPrototypeOf(FedAuthPropertiesBuilder.prototype).build.call(this);
      }
    }
  });
  FedAuthPropertiesBuilder.prototype.constructor = FedAuthPropertiesBuilder;

  /**
   * @class OAuthPropertiesBuilder
   * @classdesc This is the builder for OAuth.
   * Using this builder to {@link init} an authentication flow will return {@link RemoteAuthenticationFlow} in the init promise.
   * <p>In this type of authentication login can be configured to use an embedded WebView or an external browser.
   * This can be {@link OAuthPropertiesBuilder#browserMode|configured}.
   * <ul style="list-style:none">
   * <li>{@link OAuthPropertiesBuilder.BrowserMode|Using Embedded WebView}: Plugin brings up a WebView where OAuth login webpage is loaded.
   * User has to provide credentials on this page and login. When login is successful, the WebView will be removed and user will be redirected back to the app.
   * While showing the WebView, plugin provides basic operations such as "Forward", "Back", "Reload" and "Cancel" to deal with any issues the user faces on this page.
   * For example, user may accidentally click a link on the login page. In this case, user can use "Back" button to come back to the login page.
   * For example, if the login page is not loaded correctly, user may want to try reloading the page, before cancelling the login.
   * If user cancels the login, the promise returned by {@link AuthenticationFlow#login} will be rejected.</li>
   * <li>{@link OAuthPropertiesBuilder.BrowserMode|Using External Browser}: Plugin redirects user to the default browser in the device and loads the OAuth login webpage.
   * User has to provide credentials on this page and login. When login is successful, user will be redirected back to the app.
   *
   * For using this feature there are three prerequisites:
   * <ul>
   * <li>App needs to configure a {@link https://www.npmjs.com/package/cordova-plugin-customurlscheme|custom URL scheme}.
   * It has to be noted that once the external browser is launched and login page is loaded, app does not have any control.
   * Custom URL scheme is the way for any redirects back to the app.</li>
   * <li>OAuth server should have the capability to use the same custom URL scheme and redirect the user back to app after successful login or logout.
   * This is typically part of OAuth server configuration.
   * For example, "Redirect URL" should be configured from the admin console to point to app's custom URL scheme in case of IDCS server.</li>
   * <li>{@link OAuthPropertiesBuilder#oAuthRedirectEndpoint|Redirect end point} in configuration should be provided as the URL scheme.</li>
   * </ul>
   * The advantage of using external browser is that it makes this login as a single sign on for all the apps in the device.
   * For example, this is advantageous for Google OAuth usecases.
   * </li>
   * </ul>
   * </p>
   * <p>
   * In case of OAuth, typically user does not logout when using external browser as it defeats the purpose.
   * User will be challenged to login only if the OAuth token expires.
   * Still, if the app wants to provide a logout option to the user, it can be done by calling {@link AuthenticationFlow#logout}.
   * This step does not have any user interaction.
   * OAuth logout page is loaded either in WebView or external browser as configured and then is dismissed once logout is complete.</p
   * <p>
   * Some OAuth servers supports refresh token.
   * Auth tokens generally are short lived and refresh tokens are long lived.
   * When the auth token expires, the refresh token can be used to obtain a new auth token and there is no need to challenge the user.
   * For obtaining the refresh token, some servers requires special scope to be passed.
   * For e.g: IDCS needs "offline_access" scope to be used.
   * App needs to pass the relevant scope, if needed by the server, for this feature to work</p>
   *
   * @extends RemoteAuthPropertiesBuilder
   * @param {string} appName - Application name
   * @param {OAuthPropertiesBuilder.OAuthAuthorizationGrantType} oAuthAuthorizationGrantType - OAuth authorization grant type.
   * @param {string} oAuthTokenEndpoint - OAuth token end point.
   * @param {string} oAuthClientID - OAuth client id.
   */
  var OAuthPropertiesBuilder = function(appName, grantType, tokenEndpoint, clientId) {
    RemoteAuthPropertiesBuilder.call(this, appName);
    this.put(authPropertyKeys.AuthServerType, authServerTypes.OAuthAuthentication);
    this.put(authPropertyKeys.SessionActiveOnRestart, true);

    /**
     * @function oAuthAuthorizationGrantType
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {OAuthPropertiesBuilder.OAuthAuthorizationGrantType} grantType - authorization end point.
     * @return {OAuthPropertiesBuilder}
     */
    this.oAuthAuthorizationGrantType = function(grantType)
    {
      assertString(grantType, authPropertyKeys.OAuthAuthorizationGrantType);
      if (!OAuthPropertiesBuilder.OAuthAuthorizationGrantType.hasOwnProperty(grantType)) {
        throw new Error(authPropertyKeys.OAuthAuthorizationGrantType + ' should be one from OAuthPropertiesBuilder.OAuthAuthorizationGrantType.');
      }
      this.put(authPropertyKeys.OAuthAuthorizationGrantType, grantType);
      return this;
    };

    /**
     * @function oAuthTokenEndpoint
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {string} url - OAuth token end point.
     * @return {OAuthPropertiesBuilder}
     */
    this.oAuthTokenEndpoint = function(url)
    {
      assertUrl(url, authPropertyKeys.OAuthTokenEndpoint);
      this.put(authPropertyKeys.OAuthTokenEndpoint, url);
      return this;
    };
    /**
     * @function oAuthClientID
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {string} clientId - OAuth client id.
     * @return {OAuthPropertiesBuilder}
     */
    this.oAuthClientID = function(clientId)
    {
      assertString(clientId, authPropertyKeys.OAuthClientID);
      this.put(authPropertyKeys.OAuthClientID, clientId);
      return this;
    };
    /**
     * @function oAuthAuthorizationEndpoint
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {string} url - authorization end point.
     * @return {OAuthPropertiesBuilder}
     */
    this.oAuthAuthorizationEndpoint = function(url)
    {
      assertUrl(url, authPropertyKeys.OAuthAuthorizationEndpoint);
      this.put(authPropertyKeys.OAuthAuthorizationEndpoint, url);
      return this;
    };

    /**
     * @function oAuthRedirectEndpoint
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {string} url - End point to redirect after successful authentication. Typically this is app's URL scheme.
     * @return {OAuthPropertiesBuilder}
     */
    this.oAuthRedirectEndpoint = function(url)
    {
      assertUrl(url, authPropertyKeys.OAuthRedirectEndpoint);
      this.put(authPropertyKeys.OAuthRedirectEndpoint, url);
      return this;
    };

    /**
     * @function oAuthClientSecret
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {string} secret - client secret.
     * @return {OAuthPropertiesBuilder}
     */
    this.oAuthClientSecret = function(secret)
    {
      assertString(secret, authPropertyKeys.OAuthClientSecret);
      this.put(authPropertyKeys.OAuthClientSecret, secret);
      return this;
    };

    /**
     * @function oAuthScope
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {Array.<string>} scopes - OAuth scopes.
     * @return {OAuthPropertiesBuilder}
     */
    this.oAuthScope = function(scopes)
    {
      assertObject(scopes, authPropertyKeys.OAuthScope);
      this.put(authPropertyKeys.OAuthScope, scopes);
      return this;
    };

    /**
     * @function logoutURL
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {string} url - OAuth logout URL.
     * @return {OAuthPropertiesBuilder}
     */
    this.logoutURL = function(url)
    {
      assertUrl(url, authPropertyKeys.LogoutURL);
      this.put(authPropertyKeys.LogoutURL, url);
      return this;
    };

    /**
     * @function browserMode
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {OAuthPropertiesBuilder.BrowserMode} mode - Browser mode to be used.
     * @return {OAuthPropertiesBuilder}
     */
    this.browserMode = function(mode)
    {
      assertString(mode, authPropertyKeys.BrowserMode);
      if (!OAuthPropertiesBuilder.BrowserMode.hasOwnProperty(mode)) {
        throw new Error(authPropertyKeys.BrowserMode + ' should be one from OAuthPropertiesBuilder.BrowserMode.');
      }
      this.put(authPropertyKeys.BrowserMode, mode);
      return this;
    };

    /**
     * Applicable for 2 legged OAuth flows when user is challenge to collect user credentials.
     * @function challengeCallback
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {RemoteAuthPropertiesBuilder~remoteAuthChallengeCallback} callback - Callback to handle credential challenge.
     * @return {OAuthPropertiesBuilder}
     */
    this.challengeCallback = function(callback)
    {
      assertFunction(callback, authPropertyKeys.ChallengeCallback);
      this.put(authPropertyKeys.ChallengeCallback, callback);
      return this;
    };

    /**
     * This method can be used to enable PKCE for OAuth. PKCE is more secured way of using OAUTH for mobile apps.
     * See {@link http://www.ateam-oracle.com/identity-cloud-service-mobile-clients-and-pkce-support|this blog}
     * @function enablePKCE
     * @memberof OAuthPropertiesBuilder.prototype
     * @param {boolean} enable - whether to enable PKCE or not.
     * @return {OAuthPropertiesBuilder}
     */
    this.enablePKCE = function(enable) {
      assertBoolean(enable, authPropertyKeys.EnablePKCE);
      this.put(authPropertyKeys.OAuthEnablePKCE, enable);
      return this;
    };

    if (grantType)
      this.oAuthAuthorizationGrantType(grantType);
    if (tokenEndpoint)
      this.oAuthTokenEndpoint(tokenEndpoint);
    if (clientId)
      this.oAuthClientID(clientId);
  };

  OAuthPropertiesBuilder.prototype = Object.create(RemoteAuthPropertiesBuilder.prototype, {
    /**
     * @function build
     * @memberof OAuthPropertiesBuilder.prototype
     * @return {Object} validate and return properties collected.
     */
    build: {
      value: function() {
        if (!this.props[authPropertyKeys.OAuthAuthorizationGrantType])
          throw new Error('Mandatory parameter oAuthAuthorizationGrantType not set.');
        if (!this.props[authPropertyKeys.OAuthTokenEndpoint] && Object.getPrototypeOf(this) === OAuthPropertiesBuilder.prototype)
          throw new Error('Mandatory parameter oAuthTokenEndpoint not set.');
        if (!this.props[authPropertyKeys.OAuthClientID])
          throw new Error('Mandatory parameter oAuthClientID not set.');
        return Object.getPrototypeOf(OAuthPropertiesBuilder.prototype).build.call(this);
      }
    }
  });
  OAuthPropertiesBuilder.prototype.constructor = OAuthPropertiesBuilder;
  /**
   * Enum values for {@link OAuthPropertiesBuilder#browserMode}
   * @memberof OAuthPropertiesBuilder
   * @enum
   * @readonly
   */
  OAuthPropertiesBuilder.BrowserMode = {
   /**
    * Browser mode is external. An external browser will be opened.
    * @type {string}
    */
   External: 'External',
   /**
    * Browser mode is embedded. An embedded browser will be opened within the app.
    * @type {string}
    */
   Embedded: 'Embedded'
  };

  /**
   * Enum values for {@link OAuthPropertiesBuilder#oAuthAuthorizationGrantType}
   * @memberof OAuthPropertiesBuilder
   * @enum
   * @readonly
   */
  OAuthPropertiesBuilder.OAuthAuthorizationGrantType = {
    /**
     * OAuth grant type is implicit
     * @type {string}
     */
    OAuthImplicit:'OAuthImplicit',
    /**
     * OAuth grant type is authorization code
     * @type {string}
     */
    OAuthAuthorizationCode:'OAuthAuthorizationCode',
    /**
     * OAuth grant type is resource owner
     * @type {string}
     */
    OAuthResourceOwner:'OAuthResourceOwner',
    /**
     * OAuth grant type is client credentials
     * @type {string}
     */
    OAuthClientCredentials:'OAuthClientCredentials'
  };
  /**
   * @class OpenIDConnectPropertiesBuilder
   * @classdesc This is the builer for OpenIDConnect. Specifics of this type of authentication is similar to
   * {@link OAuthPropertiesBuilder|OAuth}.
   * @extends OAuthPropertiesBuilder
   * @param {string} appName - Application name
   * @param {OAuthPropertiesBuilder.OAuthAuthorizationGrantType} oAuthAuthorizationGrantType - OAuth grant type to be used.
   * @param {string} discoveryEndpoint - OpenId discovery end point.
   * @param {string} clientId - client id.
   */
  var OpenIDConnectPropertiesBuilder = function(appName, oAuthAuthorizationGrantType, discoveryEndpoint, clientId) {
    OAuthPropertiesBuilder.call(this, appName, oAuthAuthorizationGrantType, undefined, clientId);
    this.put(authPropertyKeys.AuthServerType, authServerTypes.OpenIDConnect);

    /**
     * @function discoveryEndpoint
     * @memberof OpenIDConnectPropertiesBuilder.prototype
     * @param {string} url - OAuth token end point.
     * @return {OpenIDConnectPropertiesBuilder}
     */
    this.discoveryEndpoint = function(url)
    {
      assertUrl(url, authPropertyKeys.OpenIDConnectDiscoveryURL);
      this.put(authPropertyKeys.OpenIDConnectDiscoveryURL, url);
      return this;
    };

    if (discoveryEndpoint)
      this.discoveryEndpoint(discoveryEndpoint);

  };

  OpenIDConnectPropertiesBuilder.prototype = Object.create(OAuthPropertiesBuilder.prototype, {
    /**
     * @function build
     * @memberof OpenIDConnectPropertiesBuilder.prototype
     * @return {Object} validate and return properties collected.
     */
    build: {
      value: function() {
        if (!this.props[authPropertyKeys.OpenIDConnectDiscoveryURL])
          throw new Error('Mandatory parameter discoveryEndpoint not set.');
        return Object.getPrototypeOf(OpenIDConnectPropertiesBuilder.prototype).build.call(this);
      }
    }
  });
  OpenIDConnectPropertiesBuilder.prototype.constructor = OpenIDConnectPropertiesBuilder;


  /**
   * @class LocalAuthPropertiesBuilder
   * @classdesc This is the builder for Local auth. This can be used for for device level authentication.
   * Using this builder to {@link init} an authentication flow will return {@link LocalAuthenticationFlow} in the init promise.
   * Multiple types of {@link LocalAuthPropertiesBuilder.LocalAuthenticatorType|local authentications} are supported.
   * <p>For biometric based local auth, biometric is collected by the device using native UI. This UI is provided by each device OS.
   * Device OS allows certain customizations to this UI, such as labels and titles.
   * App can optionally set
   * {@link LocalAuthPropertiesBuilder~Translations| localized strings for biometric prompt}
   * using {@link LocalAuthPropertiesBuilder#translations} method. If these strings are not provided,
   * default, english strings will be shown to the user.
   * In addition, app can control the look and feel of the biometric dialog by providing appropriate theme for the MainActivity in AndroidManifest.xml.
   * </p>
   * <p>For PIN based local authentication, the UI for collecting PIN from the user should be provided by the app.
   * App provides this through {@link LocalAuthPropertiesBuilder#pinChallengeCallback}.
   * The callback implementation details are explained in {@link LocalAuthPropertiesBuilder~localAuthPinChallengeCallback| pin challenge callback} documentation.
   * </p>
   * @extends PropertiesBuilder
   * @param {String} id - Unique id for the local authentication flow.
   * @param {LocalAuthPropertiesBuilder~localAuthPinChallengeCallback} pinChallengeCallback - Callback for handling PIN challenges.
   */
  var LocalAuthPropertiesBuilder = function(id, pinChallengeCallback){
    PropertiesBuilder.call(this);
    this.put(authPropertyKeys.AuthServerType, authServerTypes.LocalAuthenticator);
    this.put(authPropertyKeys.MaxLoginAttempts, 1);

    /**
     * Local authentication challenge handler
     * @typedef {Object} LocalAuthPropertiesBuilder~LocalAuthPinChallengeHandler
     * @property {LocalAuthPropertiesBuilder~localAuthCompletionCallback} submit - App invokes this passing pin collector to proceed with the current challenge.
     * @property {RemoteAuthPropertiesBuilder~challengeCancelCallback} cancel - App invokes this to cancel the current challenge.
     */

    /**
     * Local authentication completion callback. Used to complete a PIN challenge by submitting the PIN collected from the user.
     * @callback LocalAuthPropertiesBuilder~localAuthCompletionCallback
     * @param {number=} currentPin - Existing PIN collected from user. To be passed when
     *                  {@link LocalAuthPropertiesBuilder.PinChallengeReason|LocalAuthPropertiesBuilder.PinChallengeReason.Login} is requested.
     *                  Since {@link LocalAuthenticationFlowManager#changePin|changePin} now does a login upfront,
     *                  there is no need to pass this parameter for
     *                  {@link LocalAuthPropertiesBuilder.PinChallengeReason|LocalAuthPropertiesBuilder.PinChallengeReason.ChangePin} requests.
     * @param {number=} newPin - New PIN to be set, collected from user. To be passed when
     *                  {@link LocalAuthPropertiesBuilder.PinChallengeReason|LocalAuthPropertiesBuilder.PinChallengeReason.SetPin}
     *                  or {@link LocalAuthPropertiesBuilder.PinChallengeReason|LocalAuthPropertiesBuilder.PinChallengeReason.ChangePin} is requested.
     */

    /**
     * Local authentication PIN challenge callback
     * This callback is invoked when there is a requirement to collect PIN from the user.
     * App should show a UI for collecting PIN from the user.
     * Depending on the {@link LocalAuthPropertiesBuilder.PinChallengeReason|challenge reason} passed to this callback,
     * app should collect existing PIN and / or new PIN from the user.
     * App should then use the {@link LocalAuthPropertiesBuilder~localAuthCompletionCallback|completion callback} to submit the PIN.
     * If user wishes to cancel the login attempt, app should invoke {@link RemoteAuthPropertiesBuilder~challengeCancelCallback|cancel callback}.
     * Both callbacks are provided as part of {@link LocalAuthPropertiesBuilder~LocalAuthPinChallengeHandler|challenge handler} passed to this callback.
     *
     * @callback LocalAuthPropertiesBuilder~localAuthPinChallengeCallback
     * @param {LocalAuthPropertiesBuilder.PinChallengeReason} challengeReason
     * @param {LocalAuthPropertiesBuilder~LocalAuthPinChallengeHandler} challengeHandler - To be used by the app to either submit or cancel the current challenge.
     * @param {Error=} error - Error (if any) with the previous attempt to login using PIN and maxLoginAttemptsForPIN has not reached.
     * @param {LocalAuthPropertiesBuilder~PinChallengeOptions=} options - Object containing additional information (if any) passed to the PIN challenge callback.
     */

    /**
     * @typedef {Object} LocalAuthPropertiesBuilder~PinChallengeOptions
     * @property {number} loginAttemptCount - Current login attempt. When maxLoginAttemptsForPIN is set, this will track the current retry attempt.
     */

    /**
     * Localized strings for biometric authentication dialog prompt.
     * @typedef {Object} LocalAuthPropertiesBuilder~Translations
     * @property {String=} promptMessage - Text to be shown to the user on the dialog.
     * @property {String=} pinFallbackButtonLabel - Label for the PIN fallback button in the dialog.
     * @property {String=} cancelButtonLabel - Label for the cancel button in the dialog.
     * @property {String=} successMessage - Text to be shown when biometric authentication is successful. Applicable to Android only.
     * @property {String=} errorMessage - Text to be shown when biometric authentication is unsuccessful. Applicable to Android only.
     * @property {String=} promptTitle - Title for the dialog. Applicable to Android only.
     * @property {String=} hintText - Hint text to be shown to the user. Applicable to Android only.
     */

    /**
     * @function id
     * @param {string} id - Unique id for the local authentication flow.
     * @memberof LocalAuthPropertiesBuilder.prototype
     * @return {LocalAuthPropertiesBuilder}
     */
    this.id = function (id) {
      assertString(id, authPropertyKeys.LocalAuthFlowId);
      this.put(authPropertyKeys.LocalAuthFlowId, id);
      return this;
    };
    /**
     * @function pinChallengeCallback
     * @param {LocalAuthPropertiesBuilder~localAuthPinChallengeCallback} pinCallback - Callback for handling PIN challenges.
     * @memberof LocalAuthPropertiesBuilder.prototype
     * @return {LocalAuthPropertiesBuilder}
     */
    this.pinChallengeCallback = function(pinCallback) {
      assertFunction(pinCallback, authPropertyKeys.PinChallengeCallback);
      this.put(authPropertyKeys.PinChallengeCallback, pinCallback);
      return this;
    };

    /**
     * @function translations
     * @param {LocalAuthPropertiesBuilder~Translations} translations
     * @param {boolean} override - Whether to override the existing translations or not. Defaults to true.
     * @memberof LocalAuthPropertiesBuilder.prototype
     * @return {LocalAuthPropertiesBuilder}
     */
    this.translations = function(translations, override) {
      if (typeof override === "undefined" || override === null)
        override = true;

      assertObject(translations, authPropertyKeys.Translations);
      var target, existing = this.props[authPropertyKeys.Translations];

      if (!existing) {
       target = translations;
      } else {
        target = {};
        var firstPreference, secondPreference;

        if (override) {
          firstPreference = translations;
          secondPreference = existing;
        } else {
          firstPreference = existing;
          secondPreference = translations;
        }

        for (var key in secondPreference)
          target[key] = secondPreference[key];
        for (var key in firstPreference)
          target[key] = firstPreference[key];
      }

      this.put(authPropertyKeys.Translations, target);
      return this;
    };

    /**
     * This property comes into play whenever user is challenged to login using PIN.
     * Typically this happens with {@link LocalAuthenticationFlow#login} is invoked.
     * This also happens when {@link LocalAuthenticationFlowManager#disable} or {@link LocalAuthenticationFlowManager#changePin}
     * is called, because as a first step for those actions, user has to login using PIN.
     * When this property is set to a number greater than 1, user has those many attempts to login, if a wrong PIN was provided.
     * Once max attempts are reached, the respective action's promise will be rejected.
     * @function maxLoginAttemptsForPIN
     * @memberof LocalAuthPropertiesBuilder.prototype
     * @param {number} attempts - maximum login attempts, should be greater than zero. Defaults to 1.
     * @return {LocalAuthPropertiesBuilder}
     */
    this.maxLoginAttemptsForPIN = function(maxAttempts) {
      assertPositive(maxAttempts, authPropertyKeys.MaxLoginAttempts);
      this.put(authPropertyKeys.MaxLoginAttempts, maxAttempts);
      return this;
    }

    if (id)
      this.id(id);
    if (pinChallengeCallback)
      this.pinChallengeCallback(pinChallengeCallback);
  };

  LocalAuthPropertiesBuilder.prototype = Object.create(PropertiesBuilder.prototype, {
    /**
     * @function build
     * @memberof LocalAuthPropertiesBuilder.prototype
     * @return {Object} validate and return properties collected.
     */
    build: {
      value: function() {
        if (!this.props[authPropertyKeys.LocalAuthFlowId])
          throw new Error('Mandatory parameter id not set.');
        if (!this.props[authPropertyKeys.PinChallengeCallback])
          throw new Error('Mandatory parameter pinChallengeCallback not set.');
        return Object.getPrototypeOf(LocalAuthPropertiesBuilder.prototype).build.call(this);
      }
    }
  });
  LocalAuthPropertiesBuilder.prototype.constructor = LocalAuthPropertiesBuilder;

  /**
   * Enum values for challenge reason in {@link LocalAuthPropertiesBuilder~localAuthPinChallengeCallback|pin challenge callback}.
   * @enum
   * @readonly
   * @memberof LocalAuthPropertiesBuilder
   */
  LocalAuthPropertiesBuilder.PinChallengeReason = {
    /**
     * Reason for pin challenge is to login.
     * In this case current pin has to be collected from the user and passed to {@link LocalAuthPropertiesBuilder~localAuthCompletionCallback|completion callback}
     * @type {string}
     */
    Login: 'Login',
    /**
     * Reason for pin challenge is to set a new pin.
     * In this case a new pin has to be collected from the user and passed to {@link LocalAuthPropertiesBuilder~localAuthCompletionCallback|completion callback}
     * @type {string}
     */
    SetPin: 'SetPin',
    /**
     * Reason for pin challenge is to change existing pin.
     * In this case both current and new pin has to be collected from the user and passed to {@link LocalAuthPropertiesBuilder~localAuthCompletionCallback|completion callback}
     * @type {string}
     */
    ChangePin: 'ChangePin'
  };
  /**
   * Enum values for types of local authenticators supported. Used in {@link LocalAuthenticationFlow} and {@link LocalAuthenticationFlowManager}
   * @enum
   * @readonly
   * @memberof LocalAuthPropertiesBuilder
   */
  LocalAuthPropertiesBuilder.LocalAuthenticatorType = {
    /**
     * Local authentication type is PIN.
     * @type {string}
     */
    PIN: 'cordova.plugins.IdmAuthFlows.PIN',
    /**
     * Local authentication type is Fingerprint.
     * @type {string}
     */
    Fingerprint: 'cordova.plugins.IdmAuthFlows.Fingerprint',
    /**
     * Local authentication type can be any Biometric.
     * Currently supports:
     * FaceID OR TouchID in iOS
     * Fingerprint in Android
     * @type {string}
     */
    Biometric: 'cordova.plugins.IdmAuthFlows.Biometric'
  };
  // End: Builders


  // AuthenticationFlows
  /**
   * @class AuthenticationFlow
   * @classdesc This is the base class for authentication flow object that represents a single authentication.
   * This class implements basic functionality for any authentication flow such as login, logout, isAuthenticated.
   * This class should not instantiated directly.
   * Instance of its sub class can be obtained when the promise returned from {@link init} resolves.
   * @abstract
   * @hideconstructor
   * @param {string} authFlowKey - Unique key for identifying an auth flow.
   * @param {Object} authProps - properties object obtained from {@link Builder#build}
   * @see RemoteAuthenticationFlow
   * @see LocalAuthenticationFlow
   */
  var AuthenticationFlow = function(authFlowKey, authProps)
  {
    var self = this;
    // Insist for an authFlowKey to create AuthenticationFlow object.
    if (!authFlowKey)
    {
      throw new Error('Invalid flow key passed while creating AuthenticationFlow.');
    }

    /**
     * Option object to be used with {@link AuthenticationFlow#isAuthenticated}
     * @typedef {Object} AuthenticationFlow~IsAuthenticatedOptions
     * @property {Array.<String>} OAuthScope - OAuth scopes for which isAuthenticated should be checked. Applicable only for OAuth. Default is empty array.
     * @property {boolean} refreshExpiredTokens - Whether to refresh token or not. Applicable only for for OAuth. Defaults to true.
     */
    /**
     * @abstract
     * @function login
     * @memberof AuthenticationFlow.prototype
     * @return {Promise.<AuthenticationFlow>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.login = function() {};

    /**
     * @abstract
     * @function isAuthenticated
     * @param {AuthenticationFlow~IsAuthenticatedOptions} options - options to be used
     * @memberof AuthenticationFlow.prototype
     * @return {Promise.<boolean>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.isAuthenticated = function(options) {};

    /**
     * @abstract
     * @function logout
     * @memberof AuthenticationFlow.prototype
     * @param {boolean} purgeSettings - pass true to reset all saved information for this auth.
     * @return {Promise.<AuthenticationFlow>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.logout = function(purgeSettings) {};
  };


  /**
   * @class RemoteAuthenticationFlow
   * @classdesc Remote authentication flow object.
   * This class is not directly instantiated.
   * Instance of this class can be obtained when the promise returned from {@link init} resolves.
   * Auth properties passed to {@link init} should be from one of:
   * <ul style="list-style: none;">
   * <li>{@link FedAuthPropertiesBuilder}</li>
   * <li>{@link OAuthPropertiesBuilder}</li>
   * <li>{@link OpenIDConnectPropertiesBuilder}</li>
   * </ul>
   * Setting up and using these authentication flows are explained in the respective builder documentations.
   * @hideconstructor
   * @extends AuthenticationFlow
   * @param {string} authFlowKey - Unique key for identifying an auth flow.
   * @param {Object} authProps - properties object obtained from {@link Builder#build}
   * @see HttpBasicAuthenticationFlow
   */
  var RemoteAuthenticationFlow = function(authFlowKey, authProps) {
    AuthenticationFlow.call(this, authFlowKey, authProps);
    var self = this;

    /**
     * This method is used to login. <p>The promise is resolved when login succeeds. Once login is successful,
     * if this authentication is for accessing the app, user can be allowed to do so.
     * If it is to access data secured resources this operation can now be performed.
     * It may also be required to obtain certain headers for accessing the secured resource.
     * In this case {@link RemoteAuthenticationFlow#getHeaders} can be used.</p>
     * <p>The promise gets rejected with an {@link AuthError} object which contains information on the reason of failure.
     * This information can be used to shown the reason why login did not succeed.
     * App can keep track of these failures and implement specific policies related to maximum attempts and steps to do after multiple failures here.</p>
     * <p>Sample usage:</p><pre>
     * cordova.plugins.IdmAuthFlows.init(authProps).then(
     *   function(authenticationFlow) {
     *     var loginPromise = authenticationFlow.login();
     *     loginPromise.then(...);
     *     loginPromise.catch(...);
     *   }
     * );</pre>
     * @function login
     * @memberof RemoteAuthenticationFlow.prototype
     * @return {Promise.<AuthenticationFlow>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.login = function() {
      // Backwards compatibility
      if (typeof arguments[0] === "function") {
        console.warn("Parameter challengeCallback in AuthenticationFlow.login is deprecated. Use builder.challengeCallback().");
      }

      var challengeCallback = arguments[0];
      if (typeof authProps[authPropertyKeys.ChallengeCallback] === "function")
        challengeCallback = authProps[authPropertyKeys.ChallengeCallback];
      // End: Backwards compatibility

      return new Promise(function (resolve, reject) {
        exec(function(resp) {
          var isAuth = resp[IsAuthenticatedKey];

          if (isAuth)
          {
            resolve(self);
          }
          else
          {
            var onSuccess = function me(resp) {
              if (resp.challengeFields && typeof challengeCallback === 'function')
              {
                var executed = false;
                var callback = function(challengeFields) {
                  if (!executed) {
                    executed = true;
                    exec(me, reject, TAG, 'finishLogin', [authFlowKey, challengeFields]);
                  }
                };
                callback.submit = function(fields) {
                  callback(fields);
                };
                callback.cancel = function() {
                  exec(function() {
                    reject(getError(errorCodes.UserCancelledAuthentication));
                  }, reject, TAG, 'cancelLogin', [authFlowKey]);
                };
                challengeCallback(resp.challengeFields, callback);
              }
              else
              {
                resolve(self);
              }
            };
            exec(onSuccess, reject, TAG, 'startLogin', [authFlowKey]);
          }
        }, reject, TAG, 'isAuthenticated', [authFlowKey]);
      });
    };

    /**
     * This method is used to find out if the user is authenticated.
     * @function isAuthenticated
     * @param {AuthenticationFlow~IsAuthenticatedOptions} options - options to be used
     * @memberof RemoteAuthenticationFlow.prototype
     * @return {Promise.<boolean>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.isAuthenticated = function(options) {
      return new Promise(function (resolve, reject) {
        exec(function(resp) {
          resolve(resp[IsAuthenticatedKey]);
        }, reject, TAG, 'isAuthenticated', [authFlowKey, options]);
      });
    };

    /**
     * This method is used to logout.
     * <p>Once the promise is resolved, the user can be shown the login page to re-login or a way to attempt for the same.
     * {@link AuthenticationFlow#login} can be invoked on the same {@link AuthenticationFlow} object.
     * There is no need to create a new one, unless there is some change in the authentication properties such as server details.</p>
     * <p>User is essentially logged out even if logout promise is rejected.
     * App can decide not to show the logout error to the end user as there is no action associated with it.
     * There is one special case when device is offline. In this case, logout will throw an error because logout URL loading will fail.
     * But device local logout will be successful. Application should handle this error, check for the device status (offline / online)
     * and then decide to show the error message to the user.</p>
     * The following table describes what is cleared on logout with different values for purgeSettings:
     *
     * |     purgeSettings       |  false                       | true                |
     * | :---------------------- | :-------------------------   | :------------------ |
     * | HttpBasicAuthentication | Clear remembered credentials | Clears offline, remembered credentials, user preferences |
     * | FederatedAuthentication | Clear Cookies by loading logout URL | |
     * | OAuth, OpenID           | Clear access token           | Invalidate session maintained by the browser by loading logout URL. |
     *
     * @function logout
     * @memberof RemoteAuthenticationFlow.prototype
     * @param {boolean} purgeSettings - pass true to reset all saved information for this auth. Falls back to 'false' if non boolean is passed.
     * @return {Promise.<AuthenticationFlow>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.logout = function(purgeSettings) {
      if (purgeSettings !== true && purgeSettings !== false)
        purgeSettings = false;
      return new Promise(function (resolve, reject) {
        exec(function(resp) {
          resolve(self);
        }, reject, TAG, 'logout', [authFlowKey, purgeSettings]);
      });
    };

    /**
     * Option object to be used with {@link RemoteAuthenticationFlow#getHeaders}
     * @typedef {Object} RemoteAuthenticationFlow~GetHeadersOptions
     * @property {String} fedAuthSecuredUrl - URL for which cookies and headers need to retrieved. Applicable only for for FedAuth.
     * @property {Array.<String>} oauthScopes - Scopes for which header is requested.
     * Need to be set for OAuth cases where fine grained control on the token is needed.
     * If not specified, the first OAuth token available will be returned. Applicable only for for OAuth
     */
    /**
     * This method is used to get Authorization headers and any custom headers to be set for making XHR requests to secured end points.
     * Headers are returned as an object in a format that can be directly added to the XHR request headers.
     * <pre>
     * authFlow.getHeaders().then(function(headers){
     *   var request; // Represents an XHR request
     *   ...
     *   for (var key in headers) {
     *     if (headers.hasOwnProperty(key)) {
     *       request.setRequestHeader(key, headers[key]);
     *     }
     *   }
     *   ...
     * }
     * </pre>
     * @function getHeaders
     * @memberof RemoteAuthenticationFlow.prototype
     * @param {RemoteAuthenticationFlow~GetHeadersOptions} options - options to be used
     * @return {Promise.<Object.<string, string>>} - headers needed to be used for accessing secured resource.
     *
     * |     Type of Auth        |  What headers are returned | Comments |
     * | :---------------------- | :------------------------- | :------- |
     * | HttpBasicAuthentication | Basic auth header | Generated from stored credentials. {@link HttpBasicAuthPropertiesBuilder#offlineAuthAllowed} should be true for SDK to store the credentials. |
     * | FederatedAuthentication | Relevant cookies as header | options.fedAuthSecuredUrl has to be set |
     * | FederatedAuthentication with {@link FedAuthPropertiesBuilder#parseTokenRelayResponse} turned ON | Bearer token | Can specify options.oauthScopes to get token for scope or a set of scopes. |
     * | OAuth, OpenID           | Bearer token | Can specify options.oauthScopes to get token for scope or a set of scopes. |
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.getHeaders = function(options) {
      // Backwards compatibility
      var opt = {};
      if (arguments[0] !== null && typeof arguments[0] === "object") {
        opt.fedAuthSecuredUrl = arguments[0].fedAuthSecuredUrl;
        opt.oauthScopes = arguments[0].oauthScopes;
      } else {
        if (typeof arguments[0] === "string") {
          console.warn("Parameter fedAuthSecuredUrl in AuthenticationFlow.getHeaders is deprecated.  Use options.fedAuthSecuredUrl.");
          opt.fedAuthSecuredUrl = arguments[0];
        }
        if (typeof arguments[1] === "object") {
          console.warn("Parameter oauthScopes in AuthenticationFlow.getHeaders is deprecated. Use options.oauthScopes.");
          opt.oauthScopes = arguments[1];
        }
      }
      // End: Backwards compatibility

      return new Promise(function (resolve, reject) {
        exec(resolve, reject, TAG, 'getHeaders', [authFlowKey, opt.fedAuthSecuredUrl, opt.oauthScopes]);
      });
    };
  };

  RemoteAuthenticationFlow.prototype.constructor = RemoteAuthenticationFlow;

  /**
   * @class HttpBasicAuthenticationFlow
   * @classdesc This class represents HTTP Basic authentication flow object.
   * This class is not directly instantiated.
   * Instance of this class can be obtained when the promise returned from {@link init} resolves, when using properties from {@link HttpBasicAuthPropertiesBuilder}.
   * Setting up and using HTTP basic authentication flow is explained in the {@link HttpBasicAuthenticationFlow|builder} documentation.
   * @hideconstructor
   * @extends RemoteAuthenticationFlow
   * @param {string} authFlowKey - Unique key for identifying an auth flow.
   * @param {Object} authProps - properties object obtained from {@link Builder#build}
   */
  var HttpBasicAuthenticationFlow = function(authFlowKey, authProps) {
    RemoteAuthenticationFlow.call(this, authFlowKey, authProps);
    var self = this;
    /**
     * This method resets the idle timeout. This can be used in {@link RemoteAuthPropertiesBuilder~timeoutCallback|timeout callback} to reset timeout when idle timeout occurs.
     * @function resetIdleTimeout
     * @memberof HttpBasicAuthenticationFlow.prototype
     * @return {Promise.<HttpBasicAuthenticationFlow>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.resetIdleTimeout = function() {
      return new Promise(function (resolve, reject) {
        exec(function(resp) {
          resolve(self);
        }, reject, TAG, 'resetIdleTimeout', [authFlowKey]);
      });
    };
  };

  HttpBasicAuthenticationFlow.prototype = Object.create(RemoteAuthenticationFlow.prototype);
  HttpBasicAuthenticationFlow.prototype.constructor = HttpBasicAuthenticationFlow;

  /**
   * @class LocalAuthenticationFlow
   * @classdesc This class represents local authentication flow object which can be used for performing local authentications.
   * This class is not directly instantiated.
   * Instance of this class can be obtained when the promise returned from {@link init} resolves, when using properties from {@link LocalAuthPropertiesBuilder}.
   * Setting up and using a local authentication flow is explained in the {@link LocalAuthPropertiesBuilder|builder} documentation.
   * <p>
   * Multiple {@link LocalAuthPropertiesBuilder.LocalAuthenticatorType|types} of local authentications are supported by this flow.
   * App can provide a configuration area where user can enable / disable the local authentication types that app wants to support.
   * App should use the {@link LocalAuthenticationFlow#getManager|manager} instance for {@link LocalAuthenticationFlowManager#enable|enabling}
   * and {@link LocalAuthenticationFlowManager#disable|disabling} local authentications.
   * While enabling PIN authentication, {@link LocalAuthPropertiesBuilder#pinChallengeCallback|pin challenge callback} will be invoked.
   * Note that Fingerprint or Biometric cannot be enabled unless PIN is already enabled.
   * Also, PIN cannot be disabled when Fingerprint or Biometric is enabled.
   * App UI can take care of this in the UI by manipulating the UI controls.
   * For PIN based authentication, app can provide an option for the user to change pin.
   * App should invoke {@link LocalAuthenticationFlow#getManager|manager} instance's
   * {@link LocalAuthenticationFlowManager#changePin|changePin} method for doing this.
   * {@link LocalAuthPropertiesBuilder#pinChallengeCallback|Pin challenge callback} will be invoked at this time.</p>
   * <p>For a given {@link LocalAuthenticationFlow} there is always a primary authentication, the one that was enabled by the user last.
   * So, if user enabled PIN, then that is the primary authentication.
   * If user enabled Fingerprint or Biometric, then that is the primary authentication. Even though PIN is still active, it becomes secondary authentication.
   * Local authentication can be triggered by invoking {@link LocalAuthenticationFlow#login}. This will trigger the primary authentication.
   * When PIN authentication is triggered, {@link LocalAuthPropertiesBuilder#pinChallengeCallback|pin challenge callback} will be invoked.
   * When Fingerprint or Biometric authentication is triggered, then the device prompts the user to provide the relevant biometric.
   * User will have an option to fallback on the secondary authentication, which is PIN, as per the device's policies.
   * This is a standard mechanism provided by devices to help user to access the app even when user is unable to provide biometric.
   * </p>
   * <p>There is no concept of logging out in case of local auth. So {@link LocalAuthenticationFlow#logout} is a noop.</p>
   * <p>Often local authentication is used in conjunction with a remote authentication. The objective is to have
   * user log in once and not to prompt user for credentials, until needed due to session expiry or server policy.
   * In this usecase, user logs in for the first time with the credentials and configures / authorizes
   * app to use fingerprint or biometric login. This has to be implemented by the app as a setting or on the login screen.
   * Once fingerprint or biometric authentication is allowed / enabled by the user, app should seek fingerprint or biometric whenever user login is needed.
   * App should perform remote login transparently in the background. This can be achieved by chaining local
   * authentication with remote authentication.
   * </p>
   * <p> More specifically, to implement this use case, app has to trigger login on {@link LocalAuthenticationFlow}
   * first and after that is successful, trigger login on {@link RemoteAuthenticationFlow}. If the {@link RemoteAuthenticationFlow}
   * is able to do login transparently, without user credentials, we have the desired outcome.
   * For this, {@link RemoteAuthenticationFlow} should support
   * {@link HttpBasicAuthPropertiesBuilder#autoLoginAllowed|auto login} as {@link HttpBasicAuthPropertiesBuilder} does
   * or support refresh tokens as {@link OAuthPropertiesBuilder} or {@link OpenIDConnectPropertiesBuilder} does.
   * </p>
   * <p> Another common use case with local authentication is to prompt user to provide fingerprint or biometric when app
   * is relaunched or comes to foreground from background. This can be done by invoking {@link LocalAuthenticationFlow#login}
   * in the resume listener / on startup as appropriate.
   * {@link LocalAuthenticationFlow#login} can be invoked any time after {@link LocalAuthenticationFlow} is initialized
   * and any number of times as needed. Each time user will be challenged.
   * </p>
   * @hideconstructor
   * @extends AuthenticationFlow
   * @param {Object} authProps - properties object obtained from {@link Builder#build}
   */
  var LocalAuthenticationFlow = function(authProps) {
    var id = authProps[authPropertyKeys.LocalAuthFlowId];
    AuthenticationFlow.call(this, id, authProps);
    var maxAttempts = authProps[authPropertyKeys.MaxLoginAttempts];
    var currentAttempt;
    var pinCallback = authProps[authPropertyKeys.PinChallengeCallback];
    var lastAuthenticated;
    var self = this;

    var loginUsingPin = function(resolve, reject, primaryAuth, err) {
      var options = {};
      options.loginAttemptCount = currentAttempt;
      pinCallback(LocalAuthPropertiesBuilder.PinChallengeReason.Login, {
        cancel: function() {
          reject(getError(errorCodes.UserCancelledAuthentication));
        },
        submit: function(currentPin, newPin) {
          exec(function() {
            lastAuthenticated = primaryAuth;
            resolve({flow: self, loginPin: currentPin});
          }, function(err) {
            if (currentAttempt >= maxAttempts)
              reject(err);
            else {
              currentAttempt++;
              loginUsingPin(resolve, reject, primaryAuth, err);
            }
          }, TAG, 'authenticatePin', [id, currentPin]);
        }
      }, err, options);
    };

    var loginUsingPinWithRetry = function(primaryAuth) {
      currentAttempt = 1;
      return new Promise(function(resolve, reject) {
        loginUsingPin(resolve, reject, primaryAuth);
      });
    };

    var manager = new LocalAuthenticationFlowManager(authProps, loginUsingPinWithRetry);

    /**
     * Returns the local auth manager associated with this flow.
     * @function getManager
     * @memberof LocalAuthenticationFlow.prototype
     * @return {LocalAuthenticationFlowManager}
     */
    this.getManager = function() {
      return manager;
    };

    /**
     * This method is used to login. <p>The promise is resolved when login succeeds. The user can be redirected to the app once this happens.</p>
     * <p>The promise gets rejected with an {@link AuthError} object which contains information on the reason of failure.
     * This information can be used to shown the reason why login did not succeed.
     * App keep track of these failures and implement specific policies related to maximum attempts and steps to do after multiple failures here.</p>
     * <p>Sample usage:</p><pre>
     * cordova.plugins.IdmAuthFlows.init(authProps).then(
     *   function(authenticationFlow) {
     *     var loginPromise = authenticationFlow.login();
     *     loginPromise.then(...);
     *     loginPromise.catch(...);
     *   }
     * );
     * </pre>
     * <p>In case of PIN authentication,
     * {@link LocalAuthPropertiesBuilder~localAuthPinChallengeCallback | PIN challenge callback} will be invoked.
     * App should show UI for collecting PIN from the user and pass it back to the plugin via callback as explained in {@link LocalAuthPropertiesBuilder} documentation.
     * </p>
     * <p>In case of fingerprint or biometric based local authentication, the device native UI for collecting biometric will be provided to the user.
     * This UI can be customized by the app as explained in {@link LocalAuthPropertiesBuilder} documentation.
     * User will have a way to fall back on to PIN based authentication as per device policies. In this case the PIN authentication flow will kick in.
     * </p>
     * @function login
     * @memberof LocalAuthenticationFlow.prototype
     * @return {Promise.<AuthenticationFlow>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.login = function() {
      return new Promise(function(resolve, reject) {
        manager.getEnabled().then(function(enabled) {
          if (enabled.length === 0 || enabled[0] === undefined) {
            reject(getError(errorCodes.NoLocalAuthEnabled));
            return;
          }

          var primaryAuth = enabled[0];

          if (!isTypeOf(LocalAuthPropertiesBuilder.LocalAuthenticatorType, primaryAuth)) {
            // This should not happen.
            reject(getError(errorCodes.UnknownLocalAuthenticatorType));
            return;
          }

          if (primaryAuth === LocalAuthPropertiesBuilder.LocalAuthenticatorType.PIN) {
            loginUsingPinWithRetry(primaryAuth)
              .then(function(result) {
                resolve(result.flow);
              })
              .catch(function(err) {
                reject(err);
              });
          } else if (primaryAuth === LocalAuthPropertiesBuilder.LocalAuthenticatorType.Biometric ||
                      primaryAuth === LocalAuthPropertiesBuilder.LocalAuthenticatorType.Fingerprint) {
            exec(function(resp){
              if (resp === "fallback") {
                loginUsingPinWithRetry(primaryAuth)
                  .then(function(result) {
                    resolve(result.flow);
                  })
                  .catch(function(err) {
                    reject(err);
                  });
              } else {
                lastAuthenticated = primaryAuth;
                resolve(self);
              }
            }, reject, TAG, 'authenticateBiometric', [id, primaryAuth, authProps[authPropertyKeys.Translations]]);
          }
        }).catch(function(err) {
          reject(getError(errorCodes.GetEnabledAuthsError));
        });
      });
    };

    /**
     * Logout is a noop for local authentication. The promise returned resolves immediately.
     * @function logout
     * @memberof LocalAuthenticationFlow.prototype
     * @param {boolean} purgeSettings - pass true to reset saved information for this auth. Not applicable in this case.
     * @return {Promise.<AuthenticationFlow>}
     */
    this.logout = function(purgeSettings) {
      Promise.resolve(self);
    };

    /**
     * This method is used for checking if the user is authenticated or not.
     * @function isAuthenticated
     * @memberof LocalAuthenticationFlow.prototype
     * @return {Promise.<boolean>}
     */
    this.isAuthenticated = function(options) {
      // Since there is no concept of logout with local auth,
      // once user logged in, is always logged in.
      if (lastAuthenticated)
        return Promise.resolve(true);

      // If there is no lastAuthenticated,
      // we need to check if there are any authenticators enabled.
      return new Promise(function(resolve, reject) {
        manager.getEnabled().then(function(enabled) {
          resolve(enabled.length == 0);
        }).catch(function(err) {
          reject(getError(errorCodes.GetEnabledAuthsError));
        });
      });
    };
  };

  LocalAuthenticationFlow.prototype = Object.create(AuthenticationFlow.prototype);
  LocalAuthenticationFlow.prototype.constructor = LocalAuthenticationFlow;

  /**
   * @classdesc This class represents local authentication manager object which can be
   * used for managing local authentication. This class is not directly instantiated.
   * Instance of this class is returned from {@link LocalAuthenticationFlow#getManager}
   * General usage of this class is explained in {@link LocalAuthenticationFlow} documentation.
   * @class LocalAuthenticationFlowManager
   * @hideconstructor
   * @param {Object} authProps - properties object obtained from {@link Builder#build}
   * @param {Function} loginUsingPinWithRetry - method that can be used to have user login using PIN.
   */
  var LocalAuthenticationFlowManager = function(authProps, loginUsingPinWithRetry) {
    var id = authProps[authPropertyKeys.LocalAuthFlowId];
    var maxAttempts = authProps[authPropertyKeys.MaxLoginAttempts];
    var pinCallback = authProps[authPropertyKeys.PinChallengeCallback];
    var enablePromise, disablePromise;
    var self = this;

    /**
     * Get all enabled local authenticator types, in primary first order.
     * This means that if {@link LocalAuthenticationFlow#login} is triggered on the corresponding {@link LocalAuthenticationFlow},
     * it will trigger the first authentication type returned.
     * Note: PIN is always allowed. Fingerprint or Biometric is allowed based on device capabilities.
     * @function getEnabled
     * @memberof LocalAuthenticationFlowManager.prototype
     * @return {Promise.<Array.<LocalAuthPropertiesBuilder.LocalAuthenticatorType>>} Enabled local auths, in primary first order.
     */
    this.getEnabled = function() {
      var promises = [];

      if (enablePromise)
        promises.push(enablePromise);
      if (disablePromise)
        promises.push(disablePromise);

      return Promise.all(promises)
        .then(function() {
          return new Promise(function(resolve, reject) {
            exec(function(enabledAuthsPrimaryFirst){
              resolve(enabledAuthsPrimaryFirst);
            }, reject, TAG, 'enabledLocalAuthsPrimaryFirst', [id]);
          })
        });
    };

    /**
     * Enable authenticator denoted by localAuthenticationType.
     * Note: PIN has to be enabled before fingerprint or biometric is enabled.
     * @function enable
     * @memberof LocalAuthenticationFlowManager.prototype
     * @param {LocalAuthPropertiesBuilder.LocalAuthenticatorType} type - local auth type to be enabled.
     * @return {Promise.<LocalAuthPropertiesBuilder.LocalAuthenticatorType>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.enable = function(localAuthenticationType) {
      if (enablePromise)
        return Promise.reject(getError(errorCodes.OngoingTask));

      enablePromise = new Promise(function(resolve, reject) {
        if (!isTypeOf(LocalAuthPropertiesBuilder.LocalAuthenticatorType, localAuthenticationType)) {
          reject(getError(errorCodes.UnknownLocalAuthenticatorType));
          return;
        }

        self.getEnabled().then(function(enabledAuths) {
          if (enabledAuths.indexOf(localAuthenticationType) != -1) {
            resolve();
            return;
          }

          if (localAuthenticationType === LocalAuthPropertiesBuilder.LocalAuthenticatorType.Biometric ||
                localAuthenticationType === LocalAuthPropertiesBuilder.LocalAuthenticatorType.Fingerprint) {
            if (enabledAuths.indexOf(LocalAuthPropertiesBuilder.LocalAuthenticatorType.PIN) == -1) {
              reject(getError(errorCodes.EnableBiometricWhenPinDisabled));
              return;
            }

            pinCallback(LocalAuthPropertiesBuilder.PinChallengeReason.Login, {
                cancel: function() {
                  reject(getError(errorCodes.UserCancelledAuthentication));
                },
                submit: function(currentPin, newPin) {
                  exec(function() {
                    exec(function() {
                      resolve();
                    }, reject, TAG, 'enableLocalAuth', [id, localAuthenticationType, currentPin]);
                  }, reject, TAG, 'authenticatePin', [id, currentPin]);
                }
              });
          } else if (localAuthenticationType === LocalAuthPropertiesBuilder.LocalAuthenticatorType.PIN) {
            pinCallback(LocalAuthPropertiesBuilder.PinChallengeReason.SetPin, {
              cancel: function() {
                reject(getError(errorCodes.UserCancelledAuthentication));
              },
              submit: function(currentPin, newPin) {
                exec(resolve, reject, TAG, 'enableLocalAuth', [id, localAuthenticationType, newPin]);
              }
            });
          }
        });
      });

      var clearPromise = function() {
        enablePromise = undefined;
      };

      enablePromise.then(clearPromise).catch(clearPromise);

      return enablePromise;
    };

    /**
     * Disable local authenticator denoted by localAuthenticationType.
     * Note: User can disable PIN only after disabling fingerprint or biometric.
     *
     * @function disable
     * @memberof LocalAuthenticationFlowManager.prototype
     * @param {LocalAuthPropertiesBuilder.LocalAuthenticatorType} type - local auth type to be disabled.
     * @return {Promise.<LocalAuthPropertiesBuilder.LocalAuthenticatorType>}
     */
    this.disable = function(localAuthenticationType) {
      if (disablePromise)
        return Promise.reject(getError(errorCodes.OngoingTask));

      disablePromise = new Promise(function(resolve, reject) {
        if (!isTypeOf(LocalAuthPropertiesBuilder.LocalAuthenticatorType, localAuthenticationType)) {
          reject(getError(errorCodes.UnknownLocalAuthenticatorType));
          return;
        }

        self.getEnabled().then(function(enabledAuths) {
          var primaryAuth = enabledAuths[0];
          if (enabledAuths.indexOf(localAuthenticationType) == -1 || primaryAuth === undefined) {
            resolve();
            return;
          }

          if (localAuthenticationType === LocalAuthPropertiesBuilder.LocalAuthenticatorType.PIN &&
              (primaryAuth === LocalAuthPropertiesBuilder.LocalAuthenticatorType.Biometric ||
                primaryAuth === LocalAuthPropertiesBuilder.LocalAuthenticatorType.Fingerprint)) {
            reject(getError(errorCodes.DisablePinWhenBiometricEnabled));
            return;
          }

          loginUsingPinWithRetry(LocalAuthPropertiesBuilder.LocalAuthenticatorType.PIN)
          .then(function() {
            exec(resolve, reject, TAG, 'disableLocalAuth', [id, localAuthenticationType]);
          })
          .catch(function(err) {
            reject(err);
          });
        });
      });

      var clearPromise = function() {
        disablePromise = undefined;
      };

      disablePromise.then(clearPromise).catch(clearPromise);

      return disablePromise;
    };

    /**
     * Change pin for currently enabled local authenticator.
     *
     * @function changePin
     * @memberof LocalAuthenticationFlowManager.prototype
     * @return {Promise.<undefined>}
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    this.changePin = function() {
      var changePinPromise = new Promise(function(resolve, reject) {
        self.getEnabled().then(function(enabledAuths) {
          if (enabledAuths.indexOf(LocalAuthPropertiesBuilder.LocalAuthenticatorType.PIN) == -1) {
            reject(getError(errorCodes.ChangePinWhenPinNotEnabled));
            return;
          }

          loginUsingPinWithRetry(LocalAuthPropertiesBuilder.LocalAuthenticatorType.PIN)
          .then(function(result) {
            pinCallback(LocalAuthPropertiesBuilder.PinChallengeReason.ChangePin, {
              cancel: function() {
                reject(getError(errorCodes.UserCancelledAuthentication));
              },
              submit: function(currentPin, newPin) {
                exec(resolve, reject, TAG, 'changePin', [id, result.loginPin, newPin]);
              }
            });
          }).catch(function(err) {
            reject(err);
          });
        });
      });

      return changePinPromise;
    };
  };

  /**
   * @classdesc This class contains helper methods for local authentication related functionality.
   * @class LocalAuthenticationHelper
   * @hideconstructor
   */
  var LocalAuthenticationHelper =  {
    /**
     * Device level availability states for local authentication.
     * @enum
     * @readonly
     * @memberof LocalAuthenticationHelper
     */
    Availability: {
      /**
       * Local auth type is supported and configured on device.
       * This type of local auth is ready to be enabled.
       * @type {string}
       */
      Enrolled: 'Enrolled',
      /**
       * Local auth type is supported but not configured on device.
       * User should be prompted to enroll the auth type on device and try again.
       * @type {string}
       */
      NotEnrolled: 'NotEnrolled',
      /**
       * Local auth type is supported but is locked out due to many failed attempts.
       * User should be prompted to unlock the auth type and then try again.
       * @type {string}
       */
      LockedOut: 'LockedOut',
      /**
       * Local auth type is not supported by the device.
       * @type {string}
       */
      NotAvailable: 'NotAvailable'
    },

     /**
     * The method returns capabilities supported by device.
     * LocalAuthentications supported by the device can be obtained from this.
     * @function getLocalAuthSupportInfo
     * @memberof LocalAuthenticationHelper
     * @return {Promise.<Object.<LocalAuthPropertiesBuilder.LocalAuthenticatorType, LocalAuthenticationHelper.Availability>>}
     *   Local authentications supported by the device and their availability.
     */
    getLocalAuthSupportInfo: function() {
      return new Promise(function(resolve, reject) {
        exec(function(resp) {
          resolve(resp);
        }, reject, TAG, 'getLocalAuthSupportInfo');
      });
    }
  };

  return {
    /**
     * Deprecated: Use {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode}
     * @enum
     * @readonly
     * @deprecated Use {@link HttpBasicAuthPropertiesBuilder.ConnectivityMode}
     */
    ConnectivityModes: {
      /**
       * Connectivity mode is online
       * @type {string}
       */
      Online:'Online',
      /**
       * Connectivity mode is offline
       * @type {string}
       */
      Offline:'Offline',
      /**
       * Connectivity mode is auto
       * @type {string}
       */
      Auto:'Auto'
    },
    /**
     * Deprecated: Use {@link OAuthPropertiesBuilder.OAuthAuthorizationGrantType}
     * @enum
     * @readonly
     * @deprecated Use {@link OAuthPropertiesBuilder.OAuthAuthorizationGrantType}
     */
    OAuthAuthorizationGrantTypes: {
      /**
       * OAuth grant type is implicit
       * @type {string}
       */
      OAuthImplicit:'OAuthImplicit',
      /**
       * OAuth grant type is authorization code
       * @type {string}
       */
      OAuthAuthorizationCode:'OAuthAuthorizationCode',
      /**
       * OAuth grant type is resource owner
       * @type {string}
       */
      OAuthResourceOwner:'OAuthResourceOwner',
      /**
       * OAuth grant type is client credentials
       * @type {string}
       */
      OAuthClientCredentials:'OAuthClientCredentials'
    },
    /**
     * Deprecated: Use {@link RemoteAuthPropertiesBuilder~AuthChallenge}
     * @enum
     * @readonly
     * @deprecated Use {@link RemoteAuthPropertiesBuilder~AuthChallenge}
     */
    AuthChallenge: {
      /**
       * Saved user name or user input
       * @type {String}
       */
      UserName: 'username_key',
      /**
       * Saved password or user input
       * @type {String}
       */
      Password: 'password_key',
      /**
       * Saved identity domain or user input
       * @type {String=}
       */
      IdentityDomain: 'iddomain_key',
      /**
       * Any error with previous login attempt
       * @type {Error=}
       */
      Error: 'error',
    },
    /**
     * Deprecated: Use {@link RemoteAuthPropertiesBuilder~TimeoutResponse}
     * @enum
     * @readonly
     * @deprecated Use {@link RemoteAuthPropertiesBuilder~TimeoutResponse}
     */
    TimeoutResponse: {
      /**
       * Type of timeout.
       * @type {TimeoutType}
       */
      TimeoutType:'TimeoutType',
      /**
       * Time in seconds after which timeout will happen.
       * @type {String}
       */
      TimeLeftToTimeout:'TimeLeftToTimeout'
    },
    /**
     * Deprecated: Use {@link RemoteAuthPropertiesBuilder.TimeoutType}
     * @enum
     * @readonly
     * @deprecated Use {@link RemoteAuthPropertiesBuilder.TimeoutType}
     */
    TimeoutType: {
      /**
       * Timeout type is session timeout
       * @type {string}
       */
     SessionTimeout:'SESSION_TIMEOUT',
      /**
       * Timeout type is idle timeout
       * @type {string}
       */
     IdleTimeout:'IDLE_TIMEOUT'
    },
    /**
     * Deprecated: Use {@link OAuthPropertiesBuilder.BrowserMode}
     * @enum
     * @readonly
     * @deprecated Use {@link OAuthPropertiesBuilder.BrowserMode}
     */
    BrowserMode: {
      /**
       * Browser mode is external. An external browser will be opened.
       * @type {string}
       */
      External: 'External',
      /**
       * Browser mode is embedded. An embedded browser will be opened within the app.
       * @type {string}
       */
      Embedded: 'Embedded'
    },
    /**
     * @typedef {Object} AuthError - Error object passed to the callback with a Promise is rejected.
     * @property {String} errorCode - Error code as defined in the plugin documentation or system error code only in case of iOS.
     * @property {ErrorSource} errorSource - Source of the error.
     * @property {String=} translatedErrorMessage - Available in case of System error messages only for iOS.
     */
    /**
     * Deprecated: Use {@link AuthError}
     * @enum
     * @readonly
     * @deprecated Use {@link AuthError}
     */
    Error: {
      /**
       * Error code as defined in the plugin documentation or system error code only in case of iOS.
       * @type {String}
       */
      ErrorCode: 'errorCode',
      /**
       * Source of the error.
       * @type {ErrorSource}
       */
      ErrorSource: 'errorSource',
      /**
       * Available in case of System error messages only for iOS.
       * @type {String=}
       */
      TranslatedErrorMessage: 'translatedErrorMessage'
    },
    /**
     * Deprecated: Use {@link ErrorSource}.
     * @enum
     * @readonly
     * @deprecated Use {@link ErrorSource}.
     */
    ErrorSources: {
      /**
       * Source of the error is plugin.
       * @type {string}
       */
      Plugin: 'plugin',
      /**
       * Source of the error is system.
       * @type {string}
       */
      System: 'system'
    },
    /**
     * Enum values for {@link Error#ErrorSource}.
     * @enum
     * @readonly
     */
    ErrorSource: {
      /**
       * Source of the error is plugin.
       * @type {string}
       */
      Plugin: 'plugin',
      /**
       * Source of the error is system.
       * @type {string}
       */
      System: 'system'
    },
    RemoteAuthPropertiesBuilder: RemoteAuthPropertiesBuilder,
    HttpBasicAuthPropertiesBuilder: HttpBasicAuthPropertiesBuilder,
    FedAuthPropertiesBuilder: FedAuthPropertiesBuilder,
    OAuthPropertiesBuilder: OAuthPropertiesBuilder,
    OpenIDConnectPropertiesBuilder: OpenIDConnectPropertiesBuilder,
    LocalAuthPropertiesBuilder: LocalAuthPropertiesBuilder,
    LocalAuthenticationHelper: LocalAuthenticationHelper,
    /**
     * Starting point for creating an {@link AuthenticationFlow} instance.
     * Example usage:
     * <pre>
     * var authProps = new cordova.plugins.IdmAuthFlows.HttpBasicAuthPropertiesBuilder()
     *                     ...
     *                     ...
     *                     .build();
     * var initPromise = cordova.plugins.IdmAuthFlows.init(authProps);
     * initPromise.then(...).catch(...)
     * </pre>
     * @function init
     * @param {Object} authProps - An object containing configuration for authentication constructed
     * using one of the {@link PropertiesBuilder|builder} sub classes provided.
     * @return {Promise.<AuthenticationFlow>}
     * <p>Promise is resolved with a subclass of {@link AuthenticationFlow} depending on the type of authentication being inited:</p>
     * <ul style="list-style: none;">
     *  <li>Using {@link HttpBasicAuthPropertiesBuilder} will receive {@link HttpBasicAuthenticationFlow}</li>
     *  <li>Using {@link FedAuthPropertiesBuilder} or {@link OAuthPropertiesBuilder} or {@link OpenIDConnectPropertiesBuilder} will receive {@link RemoteAuthenticationFlow}</li>
     *  <li>Using {@link LocalAuthPropertiesBuilder} will receive {@link LocalAuthenticationFlow}</li>
     * </ul>
     * If the promise is rejected, the callback will receive and object of type {@link AuthError}
     */
    init: function(authProps)
    {
      if (!authProps || !authProps[authPropertyKeys.AuthServerType])
        return Promise.reject(getError("P1005"));

      if (authProps[authPropertyKeys.AuthServerType] === authServerTypes.LocalAuthenticator) {
        return Promise.resolve(new LocalAuthenticationFlow(authProps));
      }

      // Backwards compatibility
      if (typeof arguments[1] === "function") {
        console.warn("Parameter timeoutCallback in IdmAuthFlows.init is deprecated. Use authPropsBuilder.timeoutCallback().");
      }

      var timeoutCb = arguments[1];
      if (typeof authProps[authPropertyKeys.TimeoutCallback] === 'function')
        timeoutCb = authProps[authPropertyKeys.TimeoutCallback];
      // End: Backwards compatibility

      return new Promise(function (resolve, reject) {
        initializeRemoteFlow(authProps, timeoutCb, resolve, reject);
      });
    },
    /**
     * <p>Deprecated: Use new HttpBasicAuthPropertiesBuilder</p>
     * The object returned is a builder which can be used to create the authentication props for HttpBasicAuthentication.
     * Builder exposes methods to add properties relevant to HttpBasicAuthentication.
     * Example usage:
     * <pre><code>
     *    var authProps = IdmAuthFlows.newHttpBasicAuthPropertiesBuilder('appName',
     *                'http://loginUrl',
     *                'http://logoutUrl')
     *     .idleTimeOutInSeconds(300)
     *     .sessionTimeOutInSeconds(6000)
     *     .percentageToIdleTimeout(80)
     *     .maxLoginAttempts(2)
     *     .connectivityMode(IdmAuthFlows.ConnectivityMode.Offline)
     *     .offlineAuthAllowed(true)
     *     .customAuthHeaders({'a':'b'})
     *     .rememberUsernameAllowed(true)
     *     .rememberCredentialsAllowed(false)
     *     .autoLoginAllowed(false)
     *     .rememberUsernameDefault(true)
     *     .rememberCredentialDefault(true)
     *     .autoLoginDefault(false)
     *     .put('customKey1', 'customValue1')
     *     .put('customKey2', true)
     *     .build();
     * </code></pre>
     * @function newHttpBasicAuthPropertiesBuilder
     * @return {HttpBasicAuthPropertiesBuilder}
     * @deprecated Use new HttpBasicAuthPropertiesBuilder
     */
    newHttpBasicAuthPropertiesBuilder: function(appName, loginUrl, logoutUrl)
    {
      return new HttpBasicAuthPropertiesBuilder(appName, loginUrl, logoutUrl);
    },
    /**
     * <p>Deprecated: Use new FedAuthPropertiesBuilder</p>
     * The object returned is a builder which can be used to create the authentication props for FederatedAuthentication.
     * Builder exposes methods to add properties relevant to FederatedAuthentication.
     * Example usage:
     * <pre><code>
     *   var authProps = IdmAuthFlows.newFedAuthPropertiesBuilder('appName', 'http://login/url', 'http://logout/url',
     *                                                            'http://login/success', 'http://logout/failed')
     *     .logoutSuccessURL('http://logout/success')
     *     .logoutFailureUrl('http://logout/failed')
     *     .confirmLogoutAutomatically(true)
     *     .confirmLogoutButtonId('buttonId')
     *     .sessionTimeOutInSeconds(6000)
     *     .logoutTimeOutInSeconds(60)
     *     .customAuthHeaders({'header':'value'})
     *     .put('customKey1', 'customValue1')
     *     .put('customKey2', true)
     *     .build();
     * </code></pre>
     * @function newFedAuthPropertiesBuilder
     * @return {FedAuthPropertiesBuilder}
     * @deprecated Use new FedAuthPropertiesBuilder
     */
    newFedAuthPropertiesBuilder: function(appName, loginUrl, logoutUrl, loginSuccessUrl, loginFailureUrl)
    {
      return new FedAuthPropertiesBuilder(appName, loginUrl, logoutUrl, loginSuccessUrl, loginFailureUrl);
    },
    /**
     * <p>Deprecated: Use new OAuthPropertiesBuilder</p>
     * The object returned is a builder which can be used to create the authentication props for OAuthAuthentication.
     * Builder exposes methods to add properties relevant to OAuthAuthentication.
     * Example usage:
     * <pre><code>
     *   var authProps = IdmAuthFlows.newOAuthPropertiesBuilder('appName',
     *                                                          IdmAuthFlows.OAuthAuthorizationGrantType.OAuthResourceOwner,
     *                                                          'http://token/endpoint',
     *                                                          'clientId')
     *     .oAuthScope(['scope1', 'scope2'])
     *     .oAuthClientSecret('clientSecret')
     *     .oAuthAuthorizationEndpoint('http://auth/endpoint')
     *     .oAuthRedirectEndpoint('http://redirect/endpoint')
     *     .logoutURL('http://logout/url')
     *     .browserMode(IdmAuthFlows.BrowserMode.External)
     *     .logoutTimeOutInSeconds(60)
     *     .customAuthHeaders({'header':'value'})
     *     .put('customKey1', 'customValue1')
     *     .put('customKey2', true)
     *     .build();
     * </code></pre>
     * @function newOAuthPropertiesBuilder
     * @return {OAuthPropertiesBuilder}
     * @deprecated Use new OAuthPropertiesBuilder
     */
    newOAuthPropertiesBuilder: function(appName, grantType, tokenEndpoint, clientId)
    {
      return new OAuthPropertiesBuilder(appName, grantType, tokenEndpoint, clientId);
    },
    /**
     * <p>Deprecated: Use new OpenIDConnectPropertiesBuilder</p>
     * The object returned is a builder which can be used to create the authentication props for OpenIdConnect.
     * Builder exposes methods to add properties relevant to OpenId Connect authentication.
     * Example usage:
     * <pre><code>
     *   var authProps = IdmAuthFlows.newOpenIDConnectPropertiesBuilder('appName',
     *                                                          IdmAuthFlows.OAuthAuthorizationGrantType.OAuthResourceOwner,
     *                                                          'http://openid/discovery/url',
     *                                                          'clientId')
     *     .oAuthClientSecret('clientSecret')
     *     .oAuthScope(['scope1', 'scope2'])
     *     .browserMode(IdmAuthFlows.BrowserMode.External)
     *     .logoutTimeOutInSeconds(60)
     *     .customAuthHeaders({'header':'value'})
     *     .put('customKey1', 'customValue1')
     *     .put('customKey2', true)
     *     .build();
     * </code></pre>
     * @function newOpenIDConnectPropertiesBuilder
     * @return {OpenIDConnectPropertiesBuilder}
     * @deprecated Use new OpenIDConnectPropertiesBuilder
     */
    newOpenIDConnectPropertiesBuilder: function(appName, grantType, discoveryEndpoint, clientId)
    {
      return new OpenIDConnectPropertiesBuilder(appName, grantType, discoveryEndpoint, clientId);
    }
  };
}();

module.exports = IdmAuthFlows;