Skip to content

Instantly share code, notes, and snippets.

@willlma
Last active August 29, 2015 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save willlma/201cba5d84655f2eff54 to your computer and use it in GitHub Desktop.
Save willlma/201cba5d84655f2eff54 to your computer and use it in GitHub Desktop.
A better way to refresh oauth tokens for Google API JavaScript Client Library (gapi)

When using the Google API Client Library for JavaScript, none of the code samples demonstrate how to get a new token after the previous one has expired. Tokens expire after an hour, so it can't be assumed that the initial token will suffice for every user's stay on your site. The part of the FAQ that deals with refreshing tokens suggests to

refresh the auth token after 45 minutes

Which implies that one should set an interval for 45 minutes and refresh the tokens. The problem with that approach is that when your computer goes to sleep, javascript timers are put on hold, and sometimes take some time to fire after resuming the page. Unfortunately, the first thing your user may want to do when they resume using their computer is to check if there are any updates to your site, and when you make an api call for them, it will fail. So we need to check if our token is still valid before making any calls to the API.

Since I last looked into this problem, I noticed that Google updated the library to return a JavaScript Promise when you make a call to the library.

var promise = gapi.client.plus.people.search(params);

Because promises are chainable, we can build a pre-check into each API call. I'm using a promise library called Q because I like the syntax better than standard promises, but with a little shuffling this would work with standard promises.

My goal is to be able to make a call like the above without having to wait for it to fail then getting another token then calling the same method again. Instead, I do a precheck. Has the token expired? If not, make the API call. If so, get a token then make the call.

<script src="https://apis.google.com/js/client.js?onload=init"></script>
'use strict'; // I'm using ES6's let, so strict mode is required in Chrome
var plus, nextAuthTime;
var postAuth = function(authResult, deferred) {
if (authResult.error==='immediate_failed')
return deferred.reject('auth failed: '+authResult);
// set an interval for token expiration - 10 minutes
var interval = (authResult.expires_in - 600) * 1000;
nextAuthTime = Date.now() + interval;
// I still set a timer so that the user doesn't have to wait
// for authorization to take place every time a call is made
setTimeout(auth, interval);
if (!plus) plus = addPrecheck(gapi.client.plus);
if (deferred) deferred.resolve();
}
var auth = function(deferred) {
gapi.auth.authorize({
client_id: '…',
scope: '…',
immediate: true
}, function(authResult) {
postAuth(authResult, deferred);
});
}
var init = function() {
gapi.client.load('plus', 'v1').then(auth);
}
var addPrecheck = function(api) {
var checkAuth = function() {
var deferred = $q.defer();
if (Date.now() < nextAuthTime) deferred.resolve();
else auth(deferred);
return deferred.promise;
}
var prechecked = {};
// ES6's let is very important here, as a new endpoint variable is created
// each time rather than overwriting the previous one
for (let endpoint in api) {
prechecked[endpoint] = {};
for (let method in api[endpoint]) {
prechecked[endpoint][method] = function() {
var args = [].slice.call(arguments);
return checkAuth().then(function() {
return api[endpoint][method].apply(api[endpoint], args);
});
}
}
}
return prechecked;
}

Now you can simply make calls with

plus.people.search(params).then(function(people) {
  // do something with the people
}, function(error) {
  // failed to get people
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment