diff --git a/README.md b/README.md index 6307c59..1b9d26f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,16 @@ OpenFB allows you to login to Facebook and execute any Facebook Graph API reques Here are a few code examples... +Init: + +``` +openFB.init({ + oauthRedirectURL:'https://cdn.rawgit.com/ccoenraets/OpenFB/master/oauthcallback.html', + appSecret:facebook.appSecret, + appId:facebook.appId +}); +``` + Login using Facebook: ``` @@ -35,6 +45,12 @@ openFB.api( }); ``` +You can use [this](https://cdn.rawgit.com/ccoenraets/OpenFB/master/oauthcallback.html) Facebook `Valid OAuth redirect URI` for development: + +``` +https://cdn.rawgit.com/ccoenraets/OpenFB/master/oauthcallback.html +``` + The approach used in OpenFB (plain OAuth + direct requests to Graph API endpoints) is simple and lightweight, but it is definitely not perfect. Pros: @@ -88,11 +104,12 @@ The library works for both browser-based apps and Cordova/PhoneGap apps. When ru cordova create sample com.openfb.sample sample ``` -1. Add the InAppBrowser Plugin +1. Add the InAppBrowser Plugin and NetworkInformation ``` cd sample - cordova plugins add org.apache.cordova.inappbrowser + cordova plugins add cordova-plugin-inappbrowser + cordova plugins add cordova-plugin-network-information ``` 1. Delete the contents of the ```www``` directory diff --git a/logoutcallback.html b/logoutcallback.html index 499f325..2395112 100644 --- a/logoutcallback.html +++ b/logoutcallback.html @@ -1,6 +1,7 @@
diff --git a/ngopenfb.js b/ngopenfb.js index 4da909b..7df5deb 100644 --- a/ngopenfb.js +++ b/ngopenfb.js @@ -45,6 +45,18 @@ angular.module('ngOpenFB', []) $window.openFB.api(obj); return deferred.promise; } + + function graph(obj) { + var deferred = $q.defer(); + obj.success = function(result) { + deferred.resolve(result); + }; + obj.error = function(error) { + deferred.reject(error); + }; + $window.openFB.graph(obj); + return deferred.promise; + } function revokePermissions() { var deferred = $q.defer(); @@ -68,6 +80,10 @@ angular.module('ngOpenFB', []) ); return deferred.promise; } + + function getAuthResponse(callback) { + return $window.openFB.getAuthResponse(callback); + } return { init: init, @@ -75,7 +91,9 @@ angular.module('ngOpenFB', []) logout: logout, revokePermissions: revokePermissions, api: api, - getLoginStatus: getLoginStatus + graph: graph, + getLoginStatus: getLoginStatus, + getAuthResponse: getAuthResponse }; - }); \ No newline at end of file + }); diff --git a/openfb.js b/openfb.js index 864983c..ccc4b66 100644 --- a/openfb.js +++ b/openfb.js @@ -9,298 +9,471 @@ */ var openFB = (function () { - var loginURL = 'https://www.facebook.com/dialog/oauth', - - logoutURL = 'https://www.facebook.com/logout.php', - - // By default we store fbtoken in sessionStorage. This can be overridden in init() - tokenStore = window.sessionStorage, - - // The Facebook App Id. Required. Set using init(). - fbAppId, - - context = window.location.pathname.substring(0, window.location.pathname.lastIndexOf("/")), - - baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + context, - - // Default OAuth redirect URL. Can be overriden in init() - oauthRedirectURL = baseURL + '/oauthcallback.html', - - // Default Cordova OAuth redirect URL. Can be overriden in init() - cordovaOAuthRedirectURL = "https://www.facebook.com/connect/login_success.html", - - // Default Logout redirect URL. Can be overriden in init() - logoutRedirectURL = baseURL + '/logoutcallback.html', - - // Because the OAuth login spans multiple processes, we need to keep the login callback function as a variable - // inside the module instead of keeping it local within the login function. - loginCallback, - - // Indicates if the app is running inside Cordova - runningInCordova, - - // Used in the exit event handler to identify if the login has already been processed elsewhere (in the oauthCallback function) - loginProcessed; - - // MAKE SURE YOU INCLUDE IN YOUR index.html, OTHERWISE runningInCordova will always by false. - // You don't need to (and should not) add the actual cordova.js file to your file system: it will be added automatically - // by the Cordova build process - document.addEventListener("deviceready", function () { - runningInCordova = true; - }, false); - - /** - * Initialize the OpenFB module. You must use this function and initialize the module with an appId before you can - * use any other function. - * @param params - init paramters - * appId: (Required) The id of the Facebook app, - * tokenStore: (optional) The store used to save the Facebook token. If not provided, we use sessionStorage. - * loginURL: (optional) The OAuth login URL. Defaults to https://www.facebook.com/dialog/oauth. - * logoutURL: (optional) The logout URL. Defaults to https://www.facebook.com/logout.php. - * oauthRedirectURL: (optional) The OAuth redirect URL. Defaults to [baseURL]/oauthcallback.html. - * cordovaOAuthRedirectURL: (optional) The OAuth redirect URL. Defaults to https://www.facebook.com/connect/login_success.html. - * logoutRedirectURL: (optional) The logout redirect URL. Defaults to [baseURL]/logoutcallback.html. - * accessToken: (optional) An already authenticated access token. - */ - function init(params) { - - if (params.appId) { - fbAppId = params.appId; - } else { - throw 'appId parameter not set in init()'; - } - - if (params.tokenStore) { - tokenStore = params.tokenStore; - } - - if (params.accessToken) { - tokenStore.fbAccessToken = params.accessToken; - } - - loginURL = params.loginURL || loginURL; - logoutURL = params.logoutURL || logoutURL; - oauthRedirectURL = params.oauthRedirectURL || oauthRedirectURL; - cordovaOAuthRedirectURL = params.cordovaOAuthRedirectURL || cordovaOAuthRedirectURL; - logoutRedirectURL = params.logoutRedirectURL || logoutRedirectURL; - - } - - /** - * Checks if the user has logged in with openFB and currently has a session api token. - * @param callback the function that receives the loginstatus - */ - function getLoginStatus(callback) { - var token = tokenStore.fbAccessToken, - loginStatus = {}; - if (token) { - loginStatus.status = 'connected'; - loginStatus.authResponse = {accessToken: token}; - } else { - loginStatus.status = 'unknown'; - } - if (callback) callback(loginStatus); - } - - /** - * Login to Facebook using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window. - * If running in Cordova container, it happens using the In-App Browser. Don't forget to install the In-App Browser - * plugin in your Cordova project: cordova plugins add org.apache.cordova.inappbrowser. - * - * @param callback - Callback function to invoke when the login process succeeds - * @param options - options.scope: The set of Facebook permissions requested - * @returns {*} - */ - function login(callback, options) { - - var loginWindow, - startTime, - scope = '', - redirectURL = runningInCordova ? cordovaOAuthRedirectURL : oauthRedirectURL; - - if (!fbAppId) { - return callback({status: 'unknown', error: 'Facebook App Id not set.'}); - } - - // Inappbrowser load start handler: Used when running in Cordova only - function loginWindow_loadStartHandler(event) { - var url = event.url; - if (url.indexOf("access_token=") > 0 || url.indexOf("error=") > 0) { - // When we get the access token fast, the login window (inappbrowser) is still opening with animation - // in the Cordova app, and trying to close it while it's animating generates an exception. Wait a little... - var timeout = 600 - (new Date().getTime() - startTime); - setTimeout(function () { - loginWindow.close(); - }, timeout > 0 ? timeout : 0); - oauthCallback(url); - } - } - - // Inappbrowser exit handler: Used when running in Cordova only - function loginWindow_exitHandler() { - console.log('exit and remove listeners'); - // Handle the situation where the user closes the login window manually before completing the login process - if (loginCallback && !loginProcessed) loginCallback({status: 'user_cancelled'}); - loginWindow.removeEventListener('loadstop', loginWindow_loadStopHandler); - loginWindow.removeEventListener('exit', loginWindow_exitHandler); - loginWindow = null; - console.log('done removing listeners'); - } - - if (options && options.scope) { - scope = options.scope; - } - - loginCallback = callback; - loginProcessed = false; - - startTime = new Date().getTime(); - loginWindow = window.open(loginURL + '?client_id=' + fbAppId + '&redirect_uri=' + redirectURL + - '&response_type=token&scope=' + scope, '_blank', 'location=no,clearcache=yes'); - - // If the app is running in Cordova, listen to URL changes in the InAppBrowser until we get a URL with an access_token or an error - if (runningInCordova) { - loginWindow.addEventListener('loadstart', loginWindow_loadStartHandler); - loginWindow.addEventListener('exit', loginWindow_exitHandler); - } - // Note: if the app is running in the browser the loginWindow dialog will call back by invoking the - // oauthCallback() function. See oauthcallback.html for details. - - } - - /** - * Called either by oauthcallback.html (when the app is running the browser) or by the loginWindow loadstart event - * handler defined in the login() function (when the app is running in the Cordova/PhoneGap container). - * @param url - The oautchRedictURL called by Facebook with the access_token in the querystring at the ned of the - * OAuth workflow. - */ - function oauthCallback(url) { - // Parse the OAuth data received from Facebook - var queryString, - obj; - - loginProcessed = true; - if (url.indexOf("access_token=") > 0) { - queryString = url.substr(url.indexOf('#') + 1); - obj = parseQueryString(queryString); - tokenStore.fbAccessToken = obj['access_token']; - if (loginCallback) loginCallback({status: 'connected', authResponse: {accessToken: obj['access_token']}}); - } else if (url.indexOf("error=") > 0) { - queryString = url.substring(url.indexOf('?') + 1, url.indexOf('#')); - obj = parseQueryString(queryString); - if (loginCallback) loginCallback({status: 'not_authorized', error: obj.error}); - } else { - if (loginCallback) loginCallback({status: 'not_authorized'}); - } - } - - /** - * Logout from Facebook, and remove the token. - * IMPORTANT: For the Facebook logout to work, the logoutRedirectURL must be on the domain specified in "Site URL" in your Facebook App Settings - * - */ - function logout(callback) { - var logoutWindow, - token = tokenStore.fbAccessToken; - - /* Remove token. Will fail silently if does not exist */ - tokenStore.removeItem('fbtoken'); - - if (token) { - logoutWindow = window.open(logoutURL + '?access_token=' + token + '&next=' + logoutRedirectURL, '_blank', 'location=no,clearcache=yes'); - if (runningInCordova) { - setTimeout(function() { - logoutWindow.close(); - }, 700); - } - } - - if (callback) { - callback(); - } - - } - - /** - * Lets you make any Facebook Graph API request. - * @param obj - Request configuration object. Can include: - * method: HTTP method: GET, POST, etc. Optional - Default is 'GET' - * path: path in the Facebook graph: /me, /me.friends, etc. - Required - * params: queryString parameters as a map - Optional - * success: callback function when operation succeeds - Optional - * error: callback function when operation fails - Optional - */ - function api(obj) { - - var method = obj.method || 'GET', - params = obj.params || {}, - xhr = new XMLHttpRequest(), - url; - - params['access_token'] = tokenStore.fbAccessToken; - - url = 'https://graph.facebook.com' + obj.path + '?' + toQueryString(params); - - xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 200) { - if (obj.success) obj.success(JSON.parse(xhr.responseText)); - } else { - var error = xhr.responseText ? JSON.parse(xhr.responseText).error : {message: 'An error has occurred'}; - if (obj.error) obj.error(error); - } - } - }; - - xhr.open(method, url, true); - xhr.send(); - } - - /** - * Helper function to de-authorize the app - * @param success - * @param error - * @returns {*} - */ - function revokePermissions(success, error) { - return api({method: 'DELETE', - path: '/me/permissions', - success: function () { - success(); - }, - error: error}); - } - - function parseQueryString(queryString) { - var qs = decodeURIComponent(queryString), - obj = {}, - params = qs.split('&'); - params.forEach(function (param) { - var splitter = param.split('='); - obj[splitter[0]] = splitter[1]; - }); - return obj; - } - - function toQueryString(obj) { - var parts = []; - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i])); - } - } - return parts.join("&"); - } - - // The public API - return { - init: init, - login: login, - logout: logout, - revokePermissions: revokePermissions, - api: api, - oauthCallback: oauthCallback, - getLoginStatus: getLoginStatus - } - + var loginURL = 'https://www.facebook.com/dialog/oauth', + + logoutURL = 'https://www.facebook.com/logout.php', + + authResponse = null, + + disconnected = false, + + // By default we store fbtoken in sessionStorage. This can be overridden in init() + tokenStore = window.sessionStorage, + + // The Facebook App Id and/or Secret. Required. Set using init(). + fbAppId, + fbAppSecret, + + context = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/')), + + baseURL = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '') + context, + + // Default OAuth redirect URL. Can be overriden in init() + oauthRedirectURL = baseURL + '/oauthcallback.html', + + // Default Cordova OAuth redirect URL. Can be overriden in init() + cordovaOAuthRedirectURL = 'https://www.facebook.com/connect/login_success.html', + + // Default Logout redirect URL. Can be overriden in init() + logoutRedirectURL = baseURL + '/logoutcallback.html', + + // Because the OAuth login spans multiple processes, we need to keep the login callback function as a variable + // inside the module instead of keeping it local within the login function. + loginCallback, + + // Indicates if the app is running inside Cordova + runningInCordova = !/^(http(s)?:\/\/)/g.test(window.document.URL), + + // Used in the exit event handler to identify if the login has already been processed elsewhere (in the oauthCallback function) + loginProcessed, + + // Used in the exit event handler to identify if the logout has already been processed elsewhere (in the logoutCallback function) + logoutProcessed; + + /** + * Initialize the OpenFB module. You must use this function and initialize the module with an appId before you can + * use any other function. + * @param params - init paramters + * appId: (Required) The id of the Facebook app, + * tokenStore: (optional) The store used to save the Facebook token. If not provided, we use sessionStorage. + * loginURL: (optional) The OAuth login URL. Defaults to https://www.facebook.com/dialog/oauth. + * logoutURL: (optional) The logout URL. Defaults to https://www.facebook.com/logout.php. + * oauthRedirectURL: (optional) The OAuth redirect URL. Defaults to [baseURL]/oauthcallback.html. + * cordovaOAuthRedirectURL: (optional) The OAuth redirect URL. Defaults to https://www.facebook.com/connect/login_success.html. + * logoutRedirectURL: (optional) The logout redirect URL. Defaults to [baseURL]/logoutcallback.html. + */ + function init(params) { + if (params.appId) { + fbAppId = params.appId; + } else { + throw 'appId parameter not set in init()'; + } + + if (params.appSecret) { + fbAppSecret = params.appSecret; + } else { + throw 'secretId parameter not set in init()'; + } + + if (params.tokenStore) { + tokenStore = params.tokenStore; + } + + // keep running after application has been closed + if(tokenStore.fbAuthResponse){ + authResponse = JSON.parse(tokenStore.fbAuthResponse); + } + + loginURL = params.loginURL || loginURL; + logoutURL = params.logoutURL || logoutURL; + oauthRedirectURL = params.oauthRedirectURL || oauthRedirectURL; + cordovaOAuthRedirectURL = params.cordovaOAuthRedirectURL || cordovaOAuthRedirectURL; + logoutRedirectURL = params.logoutRedirectURL || logoutRedirectURL; + } + + /** + * Inspecting access tokens + */ + function debugToken(tokenToInspect, appTokenOrAdminToken, callback){ + graph({ path:'/debug_token', + params: { input_token:tokenToInspect, access_token:appTokenOrAdminToken }, + success:function(response){ + if (callback) callback(response.data||{}); + }, + error:function(error){ + if (callback) callback({}); + } + }); + } + + /** + * Checks if the user has logged in with openFB and currently has a session api token. + * @param callback the function that receives the loginstatus + */ + function getLoginStatus(callback) { + var token = authResponse ? authResponse.accessToken : null, + loginStatus = {}; + if (token) { + debugToken(token, fbAppId+'|'+fbAppSecret, function(response){ + if(response.is_valid) { + loginStatus.status = 'connected'; + loginStatus.authResponse = authResponse; + } else { + loginStatus.status = 'revoked_access'; + tokenStore.removeItem('fbAuthResponse'); + tokenStore.removeItem('fbtoken'); + authResponse = null; + } + if (callback) callback(loginStatus); + }); + } else { + loginStatus.status = 'unknown'; + if (callback) callback(loginStatus); + } + } + + /** + * Login to Facebook using OAuth. If running in a Browser, the OAuth workflow happens in a a popup window. + * If running in Cordova container, it happens using the In-App Browser. Don't forget to install the In-App Browser + * plugin in your Cordova project: cordova plugins add org.apache.cordova.inappbrowser. + * + * @param callback - Callback function to invoke when the login process succeeds + * @param options - options.scope: The set of Facebook permissions requested + * @returns {*} + */ + function login(callback, options) { + + var loginWindow, + startTime, + io_error, + scope = '', + auth_type = '', + redirectURL = runningInCordova ? cordovaOAuthRedirectURL : oauthRedirectURL; + + if (!fbAppId) { + return callback({ status:'unknown', error:'Facebook App Id not set.' }); + } + + // `cordova-plugin-network-information` offline handler: Used when running in Cordova only + function document_offline(){ + disconnected = true; + loginWindow && loginWindow.close(); + } + + // `cordova-plugin-network-information` online handler: Used when running in Cordova only + function document_online(){ + disconnected = false; + } + + // Inappbrowser load start handler: Used when running in Cordova only + function loginWindow_loadStartHandler(evt) { + var url = evt.url; + if (url.indexOf('access_token=') > 0 || url.indexOf('error=') > 0) { + // When we get the access token fast, the login window (inappbrowser) is still opening with animation + // in the Cordova app, and trying to close it while it's animating generates an exception. Wait a little... + var timeout = 600 - (new Date().getTime() - startTime); + setTimeout(function () { + loginWindow.close(); + }, timeout > 0 ? timeout : 0); + oauthCallback(url); + } + } + + // Inappbrowser load error handler fires when occurs an error when loading a URL: Used when running in Cordova only + function loginWindow_loadErrorHandler(){ + // To avoid this error in Android, do not forget: + //