Source: mobile-backend/mobile-backend.js

/**
 * Copyright© 2016, Oracle and/or its affiliates. All rights reserved.
 */

/**
 * Represents a mobile backend in Oracle Mobile Cloud Service and provides access to all capabilities of the backend.
 * Callers should use MobileBackendManager's [getMobileBackend()]{@link MobileBackendManager#getMobileBackend} method.
 * @constructor
 * @global
 */
function MobileBackend(manager, name, config, platform, utils, logger, persistence) {
  var _this = this;

  var AUTHENTICATION_TYPES = utils.AUTHENTICATION_TYPES;
  var PLATFORM_PATH = 'mobile/platform';
  var CUSTOM_CODE_PATH = 'mobile/custom';

  var HEADERS = utils.HEADERS;


  this._config = config;
  this._baseUrl = utils.validateConfiguration(this._config.baseUrl);


  var _authenticationType = null;


  this._getCustomCodeUri = function(path){
    var url = "/" + CUSTOM_CODE_PATH;
    if (strEndsWith(path,"/")) {
      path = path.slice(0, -1);
    }

    return url +  '/' + path;
  };

  /**
   * The name of the MobileBackend as read from the configuration.
   * @type {String}
   * @name MobileBackend#name
   * @readonly
   */
  this.name = name;


  /**
   * Get current authorization object.
   * @returns {Authorization}
   */
  this.authorization = null;

  /**
   * Current authorization object.
   * @type {Authorization}
   * @name MobileBackend#Authorization
   * @readonly
   * @deprecated Will be removed in next version. Use {@link MobileBackend#authorization} instead.
   */
  Object.defineProperty(this, "Authorization", {
    get: function() {
      return _this.authorization;
    }
  });

  /**
   * Returns the Diagnostics object that enables end-end debugging across application and cloud.
   * @returns {Diagnostics}
   */
  this.diagnostics = new Diagnostics(platform, utils);

  /**
   * Returns the Diagnostics object that enables end-end debugging across application and cloud.
   * @type {Diagnostics}
   * @name MobileBackend#Diagnostics
   * @deprecated Will be deleted in next version. Use {@link MobileBackend#diagnostics} instead.
   */
  Object.defineProperty(this, "Diagnostics", {
    get: function() {
      return _this.diagnostics;
    }
  });

  /**
   * Returns the CustomCode object that enables calls to custom APIs.
   * @returns {CustomCode}
   */
  this.customCode = new CustomCode(this, utils, platform);

  /**
   * Returns the CustomCode object that enables calls to custom APIs.
   * @type {CustomCode}
   * @name MobileBackend#CustomCode
   * @readonly
   * @deprecated Will be deleted in next version. Use {@link MobileBackend#customCode} instead.
   */
  Object.defineProperty(this, "CustomCode", {
    get: function() {
      return _this.customCode;
    }
  });

  /**
   * Returns the Analytics object that enables capture of mobile analytics events.
   * @returns {Analytics}
   */
  this.analytics = new Analytics(this, platform, utils, logger);

  /**
   * Returns the Analytics object that enables capture of mobile analytics events.
   * @type {Analytics}
   * @name MobileBackend#Analytics
   * @readonly
   * @deprecated Will be deleted in next version. Use {@link MobileBackend#analytics} instead.
   */
  Object.defineProperty(this, "Analytics", {
    get: function() {
      return _this.analytics;
    }
  });

  /**
   * Returns the Storage object that provides cloud-based object storage capabilities.
   * @returns {Storage}
   */
  this.storage = new Storage(this, utils, platform, logger);

  /**
   * Returns the Storage object that provides cloud-based object storage capabilities.
   * @type {Storage}
   * @name MobileBackend#Storage
   * @readonly
   * @deprecated Will be deleted in next version. Use {@link MobileBackend#storage} instead.
   */
  Object.defineProperty(this, "Storage", {
    get: function() {
      return _this.storage;
    }
  });

  if(persistence) {
    /**
     * Returns the Synchronization object that provides caching and synchronization capabilities.
     * @readonly
     * @name MobileBackend#synchronization
     * @type {Synchronization}
     * @deprecated Will be deleted in next version. Use {@link MobileBackend#synchronization} instead.
     */
    Object.defineProperty(this, "Synchronization", {
      get: function () {
        return _this.synchronization;
      }
    });

    /**
     * Returns the Synchronization object that provides caching and synchronization capabilities.
     * @returns {Synchronization}
     */
    this.synchronization = new Synchronization(manager, this, this._config.synchronization, utils, platform, persistence);
  }

  if(Notifications){
    /**
     * Returns the Notifications object that provides notification capabilities.
     * @returns {Notifications}
     */
    this.notifications = new Notifications(this, utils, platform, logger);

    /**
     * Returns the Notifications object that provides notification capabilities.
     * @type {Notifications}
     * @name MobileBackend#Notifications
     * @readonly
     * @deprecated Will be deleted in next version. Use {@link MobileBackend#notifications} instead.
     */
    Object.defineProperty(this, "Notifications", {
      get: function() {
        return _this.notifications;
      }
    });
  }

  /**
   * Returns an instance of the application configuration object.
   * Callers can download the configuration from the service by invoking loadAppConfig().
   * @returns {Object}
   */
  this.appConfig = {};

  /**
   * Returns an instance of the application configuration object.
   * Callers can download the configuration from the service by invoking loadAppConfig().
   * @deprecated Will be deleted in next version. Use {@link MobileBackend#appConfig} instead.
   * @name MobileBackend#AppConfig
   * @readonly
   * @type {Object}
   */
  Object.defineProperty(this, "AppConfig", {
    get: function() {
      return _this.appConfig;
    }
  });

  /**
   * Constructs a full URL by prepending the prefix for platform API REST endpoints to the given endpoint path.
   * @param path {String} The relative path of the endpoint following the platform prefix, i.e. {BaseUrl}/mobile/platform.
   * @returns {String} The full URL.
   */
  this.getPlatformUrl = function (path) {

    var url = _this._config.baseUrl;

    // dev instance hack, replace port ends with 1 with 7777
    if(_authenticationType == "ssoAuth" && strEndsWith(_this._config.baseUrl,"1")){
      url = url.substring(0, url.length - 4) + "7777";
    }

    url = utils.validateConfiguration(url) + "/" + PLATFORM_PATH;
    if (!strEndsWith(url, "/")) {
      url += "/";
    }
    return url + path;
  };


  /**
   * Constructs a full URL by prepending the prefix for custom API REST endpoints to the given endpoint path.
   * @param path {String} The relative path of the endpoint following the platform prefix, i.e. {BaseUrl}/mobile/custom.
   * @returns {String} The full URL.
   */
  this.getCustomCodeUrl = function (path) {
    return utils.validateConfiguration(_this._config.baseUrl) + _this._getCustomCodeUri(path);
  };

  /**
   * Constructs a full URL, including the prefix, for the OAuth token endpoint.
   * @returns {String} The full URL for the OAuth token endpoint.
   */
  this.getOAuthTokenUrl = function () {
    var tokenUri = utils.validateConfiguration(this._config.authorization.oAuth.tokenEndpoint);
    if(!strEndsWith(tokenUri,"/")) {
      tokenUri += "/"
    }
    return tokenUri;
  };

  /**
   * Constructs a full URL, including the prefix, for the SSO token endpoint.
   * @returns {String} The full URL for the SSO token endpoint.
   */
  this.getSSOAuthTokenUrl = function () {
    var tokenUri = utils.validateConfiguration(_this._config.authorization.ssoAuth.tokenEndpoint);
    if(!strEndsWith(tokenUri,"/")) {
      tokenUri += "/"
    }
    return tokenUri;
  };

  /**
   * Populates auth and diagnostics HTTP headers for making REST calls to a mobile backend.
   * @param [headers] {Object} An optional object with which to populate with the headers.
   * @returns {Object} The headers parameter that is passed in. If not provided, a new object with the populated
   * headers as properties of that object is created.
   */
  this.getHttpHeaders = function (module, headers) {
    if (!headers) {
      headers = {};
    }

    _this.diagnostics._getHttpHeaders(headers);

    if(_this.authorization) {

      if (_this.authorization._getIsAuthorized() && _this.authorization._getIsAnonymous()) {
        _this.authorization._getAnonymousHttpHeaders(headers);
      }
      else {
        _this.authorization._getHttpHeaders(headers);
      }
    }

    headers[HEADERS.ORACLE_MOBILE_CLIENT_SDK_INFO] = this.getClientSDKInfoHeader(module);

    return headers;
  };

  this.getClientSDKInfoHeader = function(module){
    var infoHeader = manager.platform.isCordova ? utils.PLATFORM_NAMES.CORDOVA : utils.PLATFORM_NAMES.JAVASCRIPT;
    infoHeader += ' ' + manager.mcsVersion;
    infoHeader += module && module !== '' ? ' [' + module + ']' : '';
    return infoHeader;
  };


  /**
   * Returns the Authentication type.
   * @return {String} Authentication type
   * @deprecated Will be deleted in next version. Use {@link MobileBackend#getAuthenticationType} instead.
   */
  this.getAuthenticationTypeVariable = function(){
    return _authenticationType;
  };

  /**
   * Sets Authentication variable for MobileBackend.
   * @param type
   * @deprecated Will be deleted in next version. Use {@link MobileBackend#setAuthenticationType} instead.
   */
  this.setAuthenticationTypeVariable = function(type){
    _authenticationType = type;
  };

  /**
   * Returns the Authentication type.
   * @return {String} Authentication type
   */
  this.getAuthenticationType = function(){
    return _authenticationType;
  };

  /**
   * Returns the Authorization object that provides authorization capabilities and access to user properties.
   * @param {string} type.
   * For Basic Authentication, you would specify "basicAuth" to use the Basic Authentication security schema.
   * For OAuth authentication, you would specify "oAuth" to use OAuth Authentication security schema.
   * If you put any type other than those two, it will throw an Exception stating that the type of Authentication that you provided
   * is not supported at this time.
   * @type {Authorization}
   * @example <caption>Example usage of mobileBackend.setAuthenticationType()</caption>
   * @example var mobileBackend = mcs.mobileBackendManager.getMobileBackend('YOUR_BACKEND_NAME');
   * @example mobileBackend.setAuthenicationType("basicAuth");
   * //Basic Authorization schema
   * @example mobileBackend.setAuthenicationType("oAuth");
   * //OAuth Authorization schema
   * @example mobileBackend.setAuthenicationType("facebookAuth");
   * //Facebook Authorization schema
   * @example mobileBackend.setAuthenicationType("ssoAuth");
   * //Single Sign On Authorization schema
   * @example mobileBackend.setAuthenicationType("tokenAuth");
   * //Token Exchange Authorization schema
   */
  this.setAuthenticationType = function(type) {

    var authType = utils.validateConfiguration(type);

    _this.authorization = null;

    if (!_this._config.authorization.hasOwnProperty(authType)) {
      throw logger.Exception("No Authentication Type called " + type +
        " is defined in MobileBackendManager.config " + "\n" +
        "check MobileBackendManager.config in authorization object for the following objects:" + "\n" +
        AUTHENTICATION_TYPES.BASIC + "\n" +
        AUTHENTICATION_TYPES.OAUTH + "\n"+
        AUTHENTICATION_TYPES.FACEBOOK + "\n"+
        AUTHENTICATION_TYPES.TOKEN + "\n"+
        AUTHENTICATION_TYPES.SSO);
    }

    if (_this.authorization && _this.authorization._getIsAuthorized()) {
      _this.authorization.logout();
    }

    if (authType === AUTHENTICATION_TYPES.BASIC) {
      _this.authorization = new BasicAuthorization(_this._config.authorization.basicAuth, _this, _this._config.applicationKey, utils, platform, logger);
      logger.info(  "Your Authentication type: " + authType);
      _authenticationType = authType;
    }
    else if (authType === AUTHENTICATION_TYPES.OAUTH) {
      _this.authorization = new OAuthAuthorization(_this._config.authorization.oAuth, _this, _this._config.applicationKey, utils, platform, logger);
      logger.info(  "Your Authentication type: " + authType);
      _authenticationType = authType;
    }
    else if(authType === AUTHENTICATION_TYPES.FACEBOOK){
      _this.authorization = new FacebookAuthorization(_this._config.authorization.facebookAuth,_this, _this._config.applicationKey, utils, platform, logger);
      logger.info(  "Your Authentication type: " + authType);
      _authenticationType = authType;
    }
    else if(authType === AUTHENTICATION_TYPES.SSO){
      _this.authorization = new SSOAuthorization(_this._config.authorization.ssoAuth, _this, _this._config.applicationKey, utils, platform, logger);
      logger.info( "Your Authentication type: " + authType);
      _authenticationType = authType;
    } else if(authType === AUTHENTICATION_TYPES.TOKEN){
      _this.authorization = new ExternalTokenExchangeAuthorization(_this._config.authorization.tokenAuth, _this, _this._config.applicationKey, utils, platform, logger);
      logger.info( "Your Authentication type: " + authType);
      _authenticationType = authType;
    }
    return _this.authorization;
  };

    /**
     * Callback invoked after downloading the application configuration.
     * @callback MobileBackend~appConfigSuccessCallback
     * @param statusCode {Number} Any HTTP status code returned from the server, if available.
     * @param appConfig {Object} The downloaded application configuration object.
     * @deprecated Use promises instead
     */

    /**
     * Callback invoked on an error while downloading the application configuration.
     * @callback MobileBackend~errorCallback
     * @param statusCode {Number} Any HTTP status code returned from the server, if available.
     * @param message {String} The HTTP payload from the server, if available, or an error message.
     * @deprecated Use promises instead
     */

    /**
     * Downloads the configuration from the service. The AppConfig property will contain the downloaded configuration.
     * @param [successCallback] {MobileBackend~appConfigSuccessCallback} Optional callback invoked on success (deprecated use promises instead).
     * @param [errorCallback] {MobileBackend~errorCallback} Optional callback invoked on failure (deprecated use promises instead).
     * @return {Promise.<NetworkResponse|NetworkResponse>}
     */
  this.loadAppConfig = function(successCallback, errorCallback) {

    if (!_this.authorization._getIsAuthorized()) {
      return _this.authorization.authenticateAnonymous()
        .then(loadAppConfig)
        .then(loadAppConfigSuccess, loadAppConfigFail);
    } else {
      return loadAppConfig()
        .then(loadAppConfigSuccess, loadAppConfigFail);
    }

    function loadAppConfigSuccess(response){
      if(successCallback){
        successCallback(response.statusCode, response.data);
      }
      return response;
    }

    function loadAppConfigFail(response){
      if(errorCallback != null) {
        errorCallback(response.statusCode, response.data);
      } else {
        return Promise.reject(response);
      }
    }

    function loadAppConfig() {

      var headers = _this.getHttpHeaders(utils.MODULE_NAMES.APP_CONFIG);
      headers["Content-Type"] = "application/json";

      return platform.invokeService({
        method: 'GET',
        url: _this.getPlatformUrl("appconfig/client"),
        headers: headers
      }).catch(invokeServiceFail);

      function invokeServiceFail(response){
        logger.error("App config download failed! with status code: " + response.statusCode);
        return Promise.reject(response);
      }
    }
  };

  // private methods

  /*
   * Checks to see if the string ends with a suffix.
   * @return {boolean}
   */
  function strEndsWith(str, suffix) {
    return str.match(suffix + '$') == suffix;
  }
}