Skip to content

Instantly share code, notes, and snippets.

@simshanith
Last active August 29, 2015 13:56
Show Gist options
  • Save simshanith/8891036 to your computer and use it in GitHub Desktop.
Save simshanith/8891036 to your computer and use it in GitHub Desktop.
Facebook JS SDK Wrapper Module for a Simple Photo Selector App
// Wrap `FB` from SDK with utility & application methods.
// Dependencies
// ---
// facebook: Facebook's Provided JS SDK, loaded from Facebook's servers.
// https://developers.facebook.com/docs/javascript/
// ---
// underscore: lodash.underscore provides AMD wrapper (among other benefits)
// http://lodash.com/
// ---
// q: fast, consistent promises
// http://documentup.com/kriskowal/q/
// ---
// social-config: application helper that reads JSON embedded in page, keyed by domain
// Implementation left as an exercise for the reader.
// ---
// safeLog: minimal console.log wrapper
// Source below
define(['facebook', 'underscore', 'q', 'social-config', 'safeLog'], function(FB, _, Q, socialConfig, safeLog){
// declare extended permissions.
// should separate read and write, based on action.
// In this example (take from <http://leagueofcaptains.gatorade.com>), relying on an `app.currentPage` variable.
// https://developers.facebook.com/docs/reference/login/extended-permissions
var neededPermissions = [];
if ( app.currentPage === 'creator' ) {
neededPermissions.push('publish_actions');
}
if ( app.currentPage === 'cover' ) {
neededPermissions.push('user_photos');
}
// initialize with app id based on environemnt
// initialize with app id based on environemnt
FB.init({
appId : socialConfig.fb_app_id,
// appId : '1437004476564609',
version: 'v2.0'
});
// Create a deferred for Facebook login state.
// Resolved only on successful login.
// Never rejected; just left pending.
// We're optimists.
var loginQ = new Q.defer();
// options for Facebook Login Dialog
var loginOpts = {
// construct scope from array
scope: neededPermissions.join(',')
};
function checkPermissions() {
var deferred = new Q.defer();
// resolved if all needed are accepted;
// otherwise rejected
// now using 2.0
//https://developers.facebook.com/docs/facebook-login/login-flow-for-web/v2.0#re-asking-declined-permissions
FB.api('/me/permissions', function(resp) {
// some response checking
if( !resp || !resp.data || !resp.data.length) {
deferred.reject(false);
}
// grab permissions granted
var perms = _.pluck(_.where(resp.data, {status: 'granted'}), 'permission');
// compare with needed permissions
var checkedPerms = _.intersection(neededPermissions, perms);
safeLog(checkedPerms);
if(checkedPerms.length === neededPermissions.length) {
// good to go, resolve with true.
deferred.resolve(true);
} else {
deferred.reject(false);
}
});
return deferred.promise;
};
FB.checkPermissions = checkPermissions;
function resolveWhenPermissed() {
FB.checkPermissions().then(function() {
// good to go, resolve with auth response.
loginQ.resolve( FB.getAuthResponse() );
}, function() {
safeLog('needed permission declined. allowing rerequest.');
loginQ = new Q.defer();
loginOpts.auth_type = 'rerequest';
});
}
// Check if already authed.
FB.getLoginStatus(function(auth) {
if( auth && auth.status === 'connected' ) {
// logged in; check permissions.
resolveWhenPermissed();
}
});
// Calls passed function when logged in.
// Uses deferred's promise to popup login flow only once.
FB.loginOnce = function(callback) {
// gracefully fail with default no-op for callback.
var cont = _.isFunction(callback) ? callback : function(){};
/* @TODO: detect popup window to see if flow is in progress */
// if it hasn't resolved yet, try to login.
if( loginQ.promise.isPending() ) {
// promise it will happen.
loginQ.promise.then(cont);
FB.login(function(resp) {
if(resp && resp.status === 'connected') {
resolveWhenPermissed();
} else {
safeLog('error logging in to facebook');
}
}, loginOpts);
} else {
cont( FB.getAuthResponse() );
}
};
FB.albums = null;
FB.getAlbums = function(callback){
// gracefully fail with default no-op for callback.
var cont = _.isFunction(callback) ? callback : function(){};
// if initialized, execute callback and return immediately.
if( FB.albums !== null ) {
cont(FB.albums);
return FB.albums;
}
// otherwise, construct the query.
var queries = {
// grab cover photo ids & names of user's albums.
"query1":"SELECT cover_pid, name FROM album WHERE owner=me()",
// grab cover photo details of user's albums.
// chain query based on cover photo ids retrieved above.
"query2":"SELECT aid, pid, src, created FROM photo WHERE pid IN(SELECT cover_pid FROM #query1) ORDER BY created DESC"
};
FB.api({
method: 'fql.multiquery',
queries: queries
}, handleResponse);
function handleResponse(result) {
// query1
var albums = result[0].fql_result_set;
// query2
var images = result[1].fql_result_set;
// construct albums data with album id, name, and cover photo url.
FB.albums = _.chain(albums).map(function(album, i, albums) {
var found = _.findWhere(images, {pid: album.cover_pid});
if( found ) {
return {
id: found.aid,
name: album.name,
url: found.src
};
}
}).compact().value();
// execute callback and return.
cont(FB.albums);
return FB.albums;
}
};
FB.getImages = function(id, callback){
if( !id ) {
throw new Error('Album Id required.');
}
var cont = _.isFunction(callback) ? callback : function(){};
var album = null;
// check if this album's photos have been retrieved.
if( FB.albums !== null ) {
// grab the album by id.
album = _.findWhere(FB.albums, {id: id});
}
if( album && album.photos ) {
// call the callback & return the photos immediately.
cont(album.photos);
return album.photos;
}
// no photos detected; constuct the api call.
FB.api({
method: 'fql.query',
// grab photos by album id.
query: "SELECT aid, pid, src, src_big, created FROM photo WHERE aid=" + id + " ORDER BY created DESC"
}, handleResponse);
function handleResponse(result) {
// stick the photos on the album.
if( album ) {
album.photos = result;
}
// call the callback & return result.
cont(result);
return album.photos;
}
};
return FB;
});
// Minimal console.log wrapper.
// Dependency
// ---
// ES5 Shim: Bootstrap fuller ES5 spec into application. Methods ironically not used here.
// Array.prototype.slice is well supported.
// https://github.com/es-shims/es5-shim/
define(['es5'], function() {
function safeLog() {
var logs;
if(window.console&&window.console.log){
logs = Array.prototype.slice.call(arguments);
try {
console.log.apply(console, logs);
} catch(e) {
// attempt to log array
console.log(logs);
}
}
}
window.safeLog = safeLog;
return window.safeLog;
});
@simshanith
Copy link
Author

Updated with a Facebook API v2.0 example.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment