Skip to content

Instantly share code, notes, and snippets.

@georgeportillo
Created May 19, 2017 00:39
Show Gist options
  • Save georgeportillo/c24ab323a7e562bcaef9cb56529925b6 to your computer and use it in GitHub Desktop.
Save georgeportillo/c24ab323a7e562bcaef9cb56529925b6 to your computer and use it in GitHub Desktop.
module.exports = "facebook";
(function(window, angular, undefined) {
'use strict';
// Module global settings.
var settings = {};
// Module global flags.
var flags = {
sdk: false,
ready: false
};
// Deferred Object which will be resolved when the Facebook SDK is ready
// and the `fbAsyncInit` function is called.
var loadDeferred;
/**
* @name facebook
* @kind function
* @description
* An Angularjs module to take approach of Facebook javascript sdk.
*
* @author Luis Carlos Osorio Jayk <[email protected]>
*/
angular.module('facebook', []).
// Declare module settings value
value('settings', settings).
// Declare module flags value
value('flags', flags).
/**
* Facebook provider
*/
provider('Facebook', [
function() {
/**
* Facebook appId
* @type {Number}
*/
settings.appId = null;
this.setAppId = function(appId) {
settings.appId = appId;
};
this.getAppId = function() {
return settings.appId;
};
/**
* Locale language, english by default
* @type {String}
*/
settings.locale = 'en_US';
this.setLocale = function(locale) {
settings.locale = locale;
};
this.getLocale = function() {
return settings.locale;
};
/**
* Set if you want to check the authentication status
* at the start up of the app
* @type {Boolean}
*/
settings.status = true;
this.setStatus = function(status) {
settings.status = status;
};
this.getStatus = function() {
return settings.status;
};
/**
* Adding a Channel File improves the performance of the javascript SDK,
* by addressing issues with cross-domain communication in certain browsers.
* @type {String}
*/
settings.channelUrl = null;
this.setChannel = function(channel) {
settings.channelUrl = channel;
};
this.getChannel = function() {
return settings.channelUrl;
};
/**
* Enable cookies to allow the server to access the session
* @type {Boolean}
*/
settings.cookie = true;
this.setCookie = function(cookie) {
settings.cookie = cookie;
};
this.getCookie = function() {
return settings.cookie;
};
/**
* Parse XFBML
* @type {Boolean}
*/
settings.xfbml = true;
this.setXfbml = function(enable) {
settings.xfbml = enable;
};
this.getXfbml = function() {
return settings.xfbml;
};
/**
* Auth Response
* @type {Object}
*/
this.setAuthResponse = function(obj) {
settings.authResponse = obj || true;
};
this.getAuthResponse = function() {
return settings.authResponse;
};
/**
* Frictionless Requests
* @type {Boolean}
*/
settings.frictionlessRequests = false;
this.setFrictionlessRequests = function(enable) {
settings.frictionlessRequests = enable;
};
this.getFrictionlessRequests = function() {
return settings.frictionlessRequests;
};
/**
* HideFlashCallback
* @type {Object}
*/
settings.hideFlashCallback = null;
this.setHideFlashCallback = function(obj) {
settings.hideFlashCallback = obj || null;
};
this.getHideFlashCallback = function() {
return settings.hideFlashCallback;
};
/**
* Custom option setting
* key @type {String}
* value @type {*}
* @return {*}
*/
this.setInitCustomOption = function(key, value) {
if (!angular.isString(key)) {
return false;
}
settings[key] = value;
return settings[key];
};
/**
* get init option
* @param {String} key
* @return {*}
*/
this.getInitOption = function(key) {
// If key is not String or If non existing key return null
if (!angular.isString(key) || !settings.hasOwnProperty(key)) {
return false;
}
return settings[key];
};
/**
* load SDK
*/
settings.loadSDK = true;
this.setLoadSDK = function(a) {
settings.loadSDK = !!a;
};
this.getLoadSDK = function() {
return settings.loadSDK;
};
/**
* SDK version
*/
settings.version = 'v2.0';
this.setSdkVersion = function(version) {
settings.version = version;
};
this.getSdkVersion = function() {
return settings.version;
};
/**
* Init Facebook API required stuff
* This will prepare the app earlier (on settingsuration)
* @arg {Object/String} initSettings
* @arg {Boolean} _loadSDK (optional, true by default)
*/
this.init = function(initSettings, _loadSDK) {
// If string is passed, set it as appId
if (angular.isString(initSettings)) {
settings.appId = initSettings;
}
if(angular.isNumber(initSettings)) {
settings.appId = initSettings.toString();
}
// If object is passed, merge it with app settings
if (angular.isObject(initSettings)) {
angular.extend(settings, initSettings);
}
// Set if Facebook SDK should be loaded automatically or not.
if (angular.isDefined(_loadSDK)) {
settings.loadSDK = !!_loadSDK;
}
};
/**
* This defined the Facebook service
*/
this.$get = [
'$q',
'$rootScope',
'$timeout',
'$window',
function($q, $rootScope, $timeout, $window) {
/**
* This is the NgFacebook class to be retrieved on Facebook Service request.
*/
function NgFacebook() {
this.appId = settings.appId;
}
/**
* Ready state method
* @return {Boolean}
*/
NgFacebook.prototype.isReady = function() {
return flags.ready;
};
NgFacebook.prototype.login = function () {
var d = $q.defer(),
args = Array.prototype.slice.call(arguments),
userFn,
userFnIndex; // Converts arguments passed into an array
// Get user function and it's index in the arguments array,
// to replace it with custom function, allowing the usage of promises
angular.forEach(args, function(arg, index) {
if (angular.isFunction(arg)) {
userFn = arg;
userFnIndex = index;
}
});
// Replace user function intended to be passed to the Facebook API with a custom one
// for being able to use promises.
if (angular.isFunction(userFn) && angular.isNumber(userFnIndex)) {
args.splice(userFnIndex, 1, function(response) {
$timeout(function() {
if (response && angular.isUndefined(response.error)) {
d.resolve(response);
} else {
d.reject(response);
}
if (angular.isFunction(userFn)) {
userFn(response);
}
});
});
}
// review(mrzmyr): generalize behaviour of isReady check
if (this.isReady()) {
$window.FB.login.apply($window.FB, args);
} else {
$timeout(function() {
d.reject("Facebook.login() called before Facebook SDK has loaded.");
});
}
return d.promise;
};
/**
* Map some asynchronous Facebook SDK methods to NgFacebook
*/
angular.forEach([
'logout',
'api',
'ui',
'getLoginStatus'
], function(name) {
NgFacebook.prototype[name] = function() {
var d = $q.defer(),
args = Array.prototype.slice.call(arguments), // Converts arguments passed into an array
userFn,
userFnIndex;
// Get user function and it's index in the arguments array,
// to replace it with custom function, allowing the usage of promises
angular.forEach(args, function(arg, index) {
if (angular.isFunction(arg)) {
userFn = arg;
userFnIndex = index;
}
});
// Replace user function intended to be passed to the Facebook API with a custom one
// for being able to use promises.
if (angular.isFunction(userFn) && angular.isNumber(userFnIndex)) {
args.splice(userFnIndex, 1, function(response) {
$timeout(function() {
if (response && angular.isUndefined(response.error)) {
d.resolve(response);
} else {
d.reject(response);
}
if (angular.isFunction(userFn)) {
userFn(response);
}
});
});
}
$timeout(function() {
// Call when loadDeferred be resolved, meaning Service is ready to be used.
loadDeferred.promise.then(function() {
$window.FB[name].apply(FB, args);
});
});
return d.promise;
};
});
/**
* Map Facebook sdk XFBML.parse() to NgFacebook.
*/
NgFacebook.prototype.parseXFBML = function() {
var d = $q.defer();
$timeout(function() {
// Call when loadDeferred be resolved, meaning Service is ready to be used
loadDeferred.promise.then(function() {
$window.FB.XFBML.parse();
d.resolve();
});
});
return d.promise;
};
/**
* Map Facebook SDK subscribe/unsubscribe method to NgFacebook.
* Use it as Facebook.subscribe / Facebook.unsubscribe in the service.
*/
angular.forEach([
'subscribe',
'unsubscribe',
], function(name) {
NgFacebook.prototype[name] = function() {
var d = $q.defer(),
args = Array.prototype.slice.call(arguments), // Get arguments passed into an array
userFn,
userFnIndex;
// Get user function and it's index in the arguments array,
// to replace it with custom function, allowing the usage of promises
angular.forEach(args, function(arg, index) {
if (angular.isFunction(arg)) {
userFn = arg;
userFnIndex = index;
}
});
// Replace user function intended to be passed to the Facebook API with a custom one
// for being able to use promises.
if (angular.isFunction(userFn) && angular.isNumber(userFnIndex)) {
args.splice(userFnIndex, 1, function(response) {
$timeout(function() {
if (response && angular.isUndefined(response.error)) {
d.resolve(response);
} else {
d.reject(response);
}
if (angular.isFunction(userFn)) {
userFn(response);
}
});
});
}
$timeout(function() {
// Call when loadDeferred be resolved, meaning Service is ready to be used
loadDeferred.promise.then(function() {
$window.FB.Event[name].apply(FB, args);
});
});
return d.promise;
};
});
return new NgFacebook(); // Singleton
}
];
}
]).
/**
* Module initialization
*/
run([
'$rootScope',
'$q',
'$window',
'$timeout',
function($rootScope, $q, $window, $timeout) {
// Define global loadDeffered to notify when Service callbacks are safe to use
loadDeferred = $q.defer();
var loadSDK = settings.loadSDK;
delete(settings['loadSDK']); // Remove loadSDK from settings since this isn't part from Facebook API.
/**
* Define fbAsyncInit required by Facebook API
*/
$window.fbAsyncInit = function() {
// Initialize our Facebook app
$timeout(function() {
if (!settings.appId) {
throw 'Missing appId setting.';
}
FB.init(settings);
flags.ready = true;
/**
* Subscribe to Facebook API events and broadcast through app.
*/
angular.forEach({
'auth.login': 'login',
'auth.logout': 'logout',
'auth.prompt': 'prompt',
'auth.sessionChange': 'sessionChange',
'auth.statusChange': 'statusChange',
'auth.authResponseChange': 'authResponseChange',
'xfbml.render': 'xfbmlRender',
'edge.create': 'like',
'edge.remove': 'unlike',
'comment.create': 'comment',
'comment.remove': 'uncomment'
}, function(mapped, name) {
FB.Event.subscribe(name, function(response) {
$timeout(function() {
$rootScope.$broadcast('Facebook:' + mapped, response);
});
});
});
// Broadcast Facebook:load event
$rootScope.$broadcast('Facebook:load');
loadDeferred.resolve(FB);
});
};
/**
* Inject Facebook root element in DOM
*/
(function addFBRoot() {
var fbroot = document.getElementById('fb-root');
if (!fbroot) {
fbroot = document.createElement('div');
fbroot.id = 'fb-root';
document.body.insertBefore(fbroot, document.body.childNodes[0]);
}
return fbroot;
})();
/**
* SDK script injecting
*/
if(loadSDK) {
(function injectScript() {
var src = '//connect.facebook.net/' + settings.locale + '/sdk.js',
script = document.createElement('script');
script.id = 'facebook-jssdk';
script.async = true;
// Prefix protocol
// for sure we don't want to ignore things, but this tests exists,
// but it isn't recognized by istanbul, so we give it a 'ignore if'
/* istanbul ignore if */
if ($window.location.protocol.indexOf('file:') !== -1) {
src = 'https:' + src;
}
script.src = src;
script.onload = function() {
flags.sdk = true;
};
// Fix for IE < 9, and yet supported by latest browsers
document.getElementsByTagName('head')[0].appendChild(script);
})();
}
}
]);
})(window, angular);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment