Skip to content

Instantly share code, notes, and snippets.

@cowboyd
Last active November 29, 2016 14:25
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cowboyd/429b47348412805055758066aeb4aa25 to your computer and use it in GitHub Desktop.
Save cowboyd/429b47348412805055758066aeb4aa25 to your computer and use it in GitHub Desktop.
Use ember-concurrency to load the facebook API on demand
/* global FB */
import Ember from 'ember';
import { task } from 'ember-concurrency';
const { RSVP } = Ember;
/**
* Service that lazy loads the Facebook JavaScript SDK, but
* provides a synchronous API
*/
export default Ember.Service.extend({
/**
* Run an arbitrary block of code using the `FB` api once it is
* guaranteed to have been loaded by passing it a callback. Once the
* SDK is known to have been loaded, then the callback will be
* invoked synchronously.
*
* The callback should be a function accepting a single parameter
* which is the `FB` object serving as the primary namespace of the
* Facebook SDK. E.g.
*
* facebook.run(FB => FB.login() );
*
* or
* facebook.run(FB => FB.XFMBL.parse(document.body));
*
* @method run
* @param {Function} - callback to run against API
*/
run(callback) {
return this.get('runTask').perform(callback);
},
/**
* Ember Concurrency task making sure that operations against the
* SDK run inside the same callstack.
*
* Normally, when blocking for the SDK to load, you would use a
* promise callback to make sure your code runs in the proper
* order. Unfortunatley, that means losing the calling context to
* the next tick of the Ember run loop.
*
* This task takes the asynchronous portion (waiting for the
* Facebook SDK to load) and yields to it; continuing with the
* computation once it is ready. In this way the calling context and
* call stack are preserved.
*
* @private
*/
runTask: task(function * (callback, thisArg = window) {
let sdk = yield this.setupSDK();
return callback.call(thisArg, sdk);
}),
/**
* Inject the Facebook SDK script into the page, and initialize it.
*
* This injects the script that loads the SDK. Note that contrary to
* the Facebook docs, this script has the `async` flag set to true
* so that it will not block in any way whatsoever. In that way, it
* should be available to scripts as soon as possible
*
* @private
*/
setupSDK() {
let promise = new RSVP.Promise((resolve, reject)=> {
window.fbAsyncInit = ()=> {
try {
FB.init(config.FB);
resolve(FB);
} catch (e) {
reject(e);
}
};
// this code is scraped straight from the facebook sdk
// normally it is injected via a script tag, but since we're
// doing it programatically, there is no need.
// for details:
// https://developers.facebook.com/docs/javascript/quickstart#loading
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = `//connect.facebook.net/en_US/sdk.js`;
js.async = "true";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
});
this.setupSDK = ()=> promise;
return promise;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment