Source: authorization/facebook-authorization.js

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

/**
 * Class used to authorize a user against Facebook and use the OAuth token from Facebook
 * to authenicate against Oracle Mobile Cloud Service. Callers should use
 * MobileBackend's [FacebookAuthorization()]{@link MobileBackend#authorization} property.
 * Derives from {@link Authorization}.
 * @constructor
 * @global
 */
function FacebookAuthorization(config, backend, appKey, utils, platform, logger) {
  var HEADERS = utils.HEADERS;

  Authorization.call(this, backend, appKey, utils, platform, logger);

  var _backendId = utils.validateConfiguration(config.backendId);
  var _anonymousToken = utils.validateConfiguration(config.anonymousToken);
  var _facebookAppId = utils.validateConfiguration(config.facebookAppId);

  var expiredTime = null;

  var _this = this;

  /**
   * Returns the Facebook Application Id token for the current backend.
   */
  this.getFacebookAppId = function () {
    return _facebookAppId;
  };

  /**
   * Callback invoked after successfully authenticating.
   * @callback Authorization~authenticateSuccessCallback
   * @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.
   */

  /**
   * Callback invoked on error while authenticating.
   * @callback Authorization~authenticateErrorCallback
   * @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.
   */

  /**
   * Authenticates a user with the given credentials against Facebook. The user remains logged in until logout() is called.
   * In the Facebook Developer console you must define the domain that the application will use.
   * in the Facebook Developer UI, When you add a platform for the application, you choose Website and set the site URL to http://localhost/.
   * @param [successCallback] {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead).
   * @param [errorCallback] {Authorization~authenticateErrorCallback} Optional callback invoked on failure (deprecated use promises instead).
   * @return {Promise.<NetworkResponse|NetworkResponse>}
   */
  this.authenticate = function (successCallback, errorCallback) {

    this.logout();

    if (window.cordova) {
      var metadata = cordova.require('cordova/plugin_list').metadata;

      if (IsInAppBrowserInstalled(metadata) !== true) {
        if (errorCallback != null) {
          errorCallback(100, 'Could not find InAppBrowser plugin, use command "cordova plugin add cordova-plugin-inappbrowser"');
          return undefined;
        } else {
          return Promise.reject(new NetworkResponse(100, 'Could not find InAppBrowser plugin, use command "cordova plugin add cordova-plugin-inappbrowser"'));
        }
      } else {
        return authenticateInvoke(successCallback, errorCallback);
      }
    } else {
      if (errorCallback != null) {
        errorCallback(400, 'Bad Request - This method require Cordova framework');
        return undefined;
      } else {
        return Promise.reject(new NetworkResponse(400, 'Bad Request - This method require Cordova framework'));
      }
    }
  };

  function authenticateInvoke (successCallback, errorCallback){

    return new Promise(invoke)
      .then(invokeSuccess)
      .catch(invokeError);

    function invoke(resolve, reject){
      var clientId = _this.getFacebookAppId();

      var redirect_uri = 'http://localhost/callback';
      var flowUrl = 'https://www.facebook.com/dialog/oauth?client_id=' + clientId + '&redirect_uri=' + redirect_uri + '&response_type=token&scope=' + 'public_profile';
      var browserRef = window.open(flowUrl, '_blank', 'location=no,clearsessioncache=yes,clearcache=yes');
      browserRef.show();

      logger.info('Opening InAppBrowser to url: ' + flowUrl);

      browserRef.addEventListener('loadstart', function loadStart(event) {
        if ((event.url).indexOf(redirect_uri) === 0) {
          browserRef.close();
          var callbackResponse = (event.url).split('#')[1];
          var responseParameters = (callbackResponse).split('&');
          var social_token = {};
          for (var i = 0; i < responseParameters.length; i++) {
            social_token[responseParameters[i].split('=')[0]] = responseParameters[i].split('=')[1];
          }
          if (social_token.access_token) {
            expiredTime = Date.now() + social_token.expires_in * 1000;
            resolve(new NetworkResponse(200, social_token));
          } else {
            if ((event.url).indexOf('error_code=100') !== 0 && !_this.isAuthorized) {
              reject(new NetworkResponse(100, 'Cannot authenticate via a web browser'));
            }
          }
        }
      });

      browserRef.addEventListener('exit', function () {
        if (!_this._getIsAuthorized()) {
          reject(new NetworkResponse(100, 'Cannot authenticate via a web browser'));
        }
      })
    }

    function invokeSuccess(response){
      _this._authenticateSuccess(response, response.data.access_token);
      expiredTime = Date.now() + response.data.expires_in * 1000;

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

    function invokeError(response){
      _this._authenticateError(response);
      if (errorCallback) {
        errorCallback(response.statusCode, response.data);
      } else {
        return Promise.reject(response);
      }
    }

  }

  /**
   * Authenticates an anonymous user against the service. The user remains logged in until logout() is called.
   * @param successCallback {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead).
   * @param errorCallback {Authorization~authenticateErrorCallback} Optional callback invoked on failure (deprecated use promises instead).
   * @return {Promise.<NetworkResponse|NetworkResponse>}
   */
  this.authenticateAnonymous = function (successCallback, errorCallback) {

    var authorizationToken = 'Basic ' + _anonymousToken;
    var headers = {};
    headers[HEADERS.ORACLE_MOBILE_BACKEND_ID] = _backendId;

    return this._authenticateAnonymousInvoke(authorizationToken,
      headers,
      backend.getPlatformUrl("users/login"),
      utils.HTTP_METHODS.GET)
      .then(invokeServiceSuccess, invokeServiceError);

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

    function invokeServiceError(response) {
      if(errorCallback) {
        errorCallback(response.statusCode, response.data);
      } else {
        return Promise.reject(response);
      }
    }
  };

  this._anonymousTokenResponseConverter = function(response){
    return { orgResponse: response.orgResponse, anonymousAccessToken: response.authorizationToken };
  };

  /**
   * Checks to see if the OAuth token is null, undefined, NaN, AN empty string (''), 0, or false. It also checks the timestamp
   * for when the token was first retrieved to see if it was still valid.
   * @returns {Boolean}
   */
  this.isTokenValid = function () {

  if (this.getAccessToken() || this._getAnonymousAccessToken()) {
    logger.verbose('Token is not null or empty');

    var currentTime = Date.now();
    if (currentTime >= expiredTime) {
      logger.info('Token has expired or user has not been authenticate with the service/Facebook');
      return false;
    }
    else {
      logger.verbose('Token is still valid');
      return true;
    }
  } else {
    return false;
  }
};

  /**
   * Refreshes the authentication token if it has expired from Facebook. The authentication scheme should support refresh.
   * @param successCallback {Authorization~authenticateSuccessCallback} Optional callback invoked on success (deprecated use promises instead).
   * @param errorCallback {Authorization~authenticateErrorCallback} Optional callback invoked on failure (deprecated use promises instead).
   * @return {Promise.<NetworkResponse|NetworkResponse>}
   */
  this.refreshToken = function(successCallback,errorCallback) {
    var isTokenValid = _this.isTokenValid();
    if (isTokenValid && this.getAccessToken() == null && this._getIsAnonymous()) {
      if (successCallback) {
        logger.error('Anonymous token is valid, you do not need to refresh.');
        successCallback(200, this._getAnonymousAccessToken());
      }
      return Promise.resolve(new NetworkResponse(200, this._getAnonymousAccessToken()));
    } else if (isTokenValid && this._getAnonymousAccessToken() && !this._getIsAnonymous()) {
      if (successCallback) {
        logger.error('Authenticated token is valid, you do not need to refresh.');
        successCallback(200, this.getAccessToken());
      }
      return Promise.resolve(new NetworkResponse(200, this._getAnonymousAccessToken()));
    } else {
      logger.error('Token is not valid and has expired, refreshing token from Facebook.');
      return this.authenticate(successCallback, errorCallback);
    }
  };

  /**
   * Logs out the current user and clears credentials and tokens.
   */
  this.logout = function() {
    this._clearState();
    expiredTime = Date.now() * 1000;
  };


  this._getHttpHeaders = function(headers) {
    if (this.getAccessToken() != null && typeof this.getAccessToken() == 'string') {
      headers[HEADERS.AUTHORIZATION] = 'Basic ' + _anonymousToken;
      headers[HEADERS.ORACLE_MOBILE_SOCIAL_ACCESS_TOKEN] = this.getAccessToken();
      headers[HEADERS.ORACLE_MOBILE_SOCIAL_IDENTITY_PROVIDER] = 'facebook';
    }

    headers[HEADERS.ORACLE_MOBILE_BACKEND_ID] = _backendId;
    headers[HEADERS.ORACLE_MOBILE_APPLICATION_KEY] = this._getApplicationKey();
  };


  this._getAnonymousHttpHeaders = function (headers) {

    if (this._getAnonymousAccessToken() && typeof this._getAnonymousAccessToken() == 'string') {
      headers[HEADERS.AUTHORIZATION] = this._getAnonymousAccessToken();
    }
    headers[HEADERS.ORACLE_MOBILE_BACKEND_ID] = _backendId;
    headers[HEADERS.ORACLE_MOBILE_APPLICATION_KEY] = this._getApplicationKey();
  }
}


/**
 * Checks to see if the correct plugin is installed into the application.
 * @return {boolean}
 */
var IsInAppBrowserInstalled = function (metadata) {

  var inAppBrowserNames = ['cordova-plugin-inappbrowser', 'org.apache.cordova.inappbrowser'];
  return inAppBrowserNames.some(function (name) {
    return metadata.hasOwnProperty(name);
  });
};

FacebookAuthorization.prototype = Object.create(Authorization.prototype);
FacebookAuthorization.prototype.constructor = FacebookAuthorization;