diff --git a/src/js/mixins/api_access.js b/src/js/mixins/api_access.js index a7d72d7ed..70856779d 100644 --- a/src/js/mixins/api_access.js +++ b/src/js/mixins/api_access.js @@ -28,7 +28,7 @@ define([ this.getBeeHive().getService('Api').setVals({ access_token : data.token_type + ':' + data.access_token, refresh_token : data.refresh_token, - expires_in : data.expires_in + expire_in : data.expire_in }); console.warn('Redefining access_token: ' + data.access_token); diff --git a/src/js/services/api.js b/src/js/services/api.js index 0c7153d97..b1517bdce 100644 --- a/src/js/services/api.js +++ b/src/js/services/api.js @@ -9,7 +9,9 @@ define([ 'js/components/api_response', 'js/components/api_query', 'js/components/api_feedback', - 'js/mixins/hardened' + 'js/mixins/hardened', + 'js/mixins/api_access', + 'moment' ], function( _, @@ -20,7 +22,9 @@ define([ ApiResponse, ApiQuery, ApiFeedback, - Hardened + Hardened, + ApiAccess, + Moment ) { var Api = GenericModule.extend({ @@ -30,10 +34,9 @@ define([ access_token: null, refresh_token: null, - expires_in: null, + expire_in: null, defaultTimeoutInMs: 60000, - activate: function(beehive) { this.setBeeHive(beehive); }, @@ -99,10 +102,10 @@ define([ } }); - Api.prototype.request = function(request, options) { + Api.prototype._request = function(request, options){ options = _.extend({}, options, request.get('options')); - + var data, self = this, query = request.get('query'); @@ -171,12 +174,44 @@ define([ jqXhr = jqXhr.promise(jqXhr); return jqXhr; + + }; + + //stubbable for testing + Api.prototype.getCurrentUTCMoment = function(){ + return Moment().utc(); + }; + + Api.prototype.request = function(request, options) { + + var that = this; + + if (!this.expire_in) return that._request(request, options); + + //expire_in is in UTC, not local time + var expiration = Moment.utc(this.expire_in); + var now = this.getCurrentUTCMoment(); + + var difference = now.diff(expiration, 'minutes'); + //fewer than 2 minutes before token expires + if (difference > -2 ){ + var d = $.Deferred(); + this.getApiAccess().done(function(){ + d.resolve(that._request(request, options)); + }); + return d.promise(); + } + else { + return that._request(request, options); + } + }; _.extend(Api.prototype, Mixin.BeeHive); _.extend(Api.prototype, Hardened); + _.extend(Api.prototype, ApiAccess); + return Api }); - diff --git a/test/mocha/js/services/api.spec.js b/test/mocha/js/services/api.spec.js index 38f6f7764..0d9c02425 100644 --- a/test/mocha/js/services/api.spec.js +++ b/test/mocha/js/services/api.spec.js @@ -8,14 +8,16 @@ define([ 'js/services/api', 'js/components/api_request', 'js/components/api_query', - 'js/components/api_response' + 'js/components/api_response', + 'moment' ], function( $, _, Api, ApiRequest, ApiQuery, - ApiResponse + ApiResponse, + Moment ) { describe("Api Service (api.spec.js)", function() { @@ -181,6 +183,51 @@ define([ expect(spy.thisValues[0]).to.have.ownProperty('api', 'request'); }); + it("should automatically request a new token if there is less than 2 minutes before token expiration", function(){ + + var api = new Api({url: '/api/1'}); // url is there, but i want to be explicit + + api.access_token = 'foo'; + api.expire_in = "2016-08-16T18:12:00" + + api.getCurrentUTCMoment = function(){ + //mock a EDT time + return Moment.parseZone("2016-08-16T14:11:00-04:00").utc(); + } + + api._request = sinon.spy(); + + api.getApiAccess = sinon.spy(function(){ + var d = $.Deferred(); + d.resolve(); + api.access_token = 'boo' + return d.promise(); + }); + + var q = new ApiQuery({q: 'foo'}); + + api.request(new ApiRequest({target: '/test', query: q, sender: 'woo'})); + + expect(api.getApiAccess.callCount).to.eql(1); + expect(api.access_token).to.eql('boo'); + expect(api._request.callCount).to.eql(1); + + //this request should not lead to an access_token refresh bc + //expiration is 3 minutes in the future + + api.expire_in = "2016-08-16T18:13:05.982Z"; + + api.getCurrentUTCMoment = function(){ + return Moment("2016-08-16T18:11:05.982Z").utc(); + } + + api.request(new ApiRequest({target: '/test', query: q, sender: 'woo'})); + + expect(api.getApiAccess.callCount).to.eql(1); + expect(api._request.callCount).to.eql(2); + + }); + describe("Testing request options", function() { var ajaxSpy; @@ -199,7 +246,7 @@ define([ expect(ajaxSpy.lastCall.args[0].cache).to.eql(true); expect(ajaxSpy.lastCall.args[0].contentType).to.eql('application/x-www-form-urlencoded'); expect(ajaxSpy.lastCall.args[0].xhrFields).to.eql(undefined); - + api.modifyRequestOptions = function(opts) { opts.xhrFields = { withCredentials: true