/**
* 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;