Commit f0312ef8 authored by Robert Knight's avatar Robert Knight

Merge pull request #2594 from hypothesis/fix-groups-race

Fix race for session data on startup
parents d026bccf 804def7d
......@@ -7,6 +7,9 @@ require('angular-jwt')
streamer = require('./streamer')
resolve =
# Ensure that we have available a) the current authenticated userid, and b)
# the list of user groups.
sessionState: ['session', (session) -> session.load().$promise]
store: ['store', (store) -> store.$promise]
streamer: streamer.connect
threading: [
......
'use strict';
var Promise = require('core-js/library/es6/promise');
var angular = require('angular');
var CACHE_TTL = 5 * 60 * 1000; // 5 minutes
var ACCOUNT_ACTIONS = [
['login', 'POST'],
['logout', 'POST'],
......@@ -29,7 +32,7 @@ function sessionActions(options) {
}
// Finally, add a simple method for getting the current session state
actions.load = {method: 'GET'};
actions._load = {method: 'GET'};
if (typeof options !== 'undefined') {
for (var act in actions) {
......@@ -68,9 +71,38 @@ function session($document, $http, $resource, flash) {
var endpoint = new URL('/app', base).href;
var resource = $resource(endpoint, {}, actions);
// Blank inital model state
// Blank initial model state
resource.state = {};
// Cache the result of _load()
var lastLoad;
var lastLoadTime;
/**
* @name session.load()
* @description
* Fetches the session data from the server. This function returns an object
* with a $promise property which resolves to the session data.
*
* N.B. The data is cached for CACHE_TTL across all actions of the session
* service: that is, a call to login() will update the session data and a call
* within CACHE_TTL milliseconds to load() will return that data rather than
* triggering a new request.
*/
resource.load = function () {
if (!lastLoadTime || (Date.now() - lastLoadTime) > CACHE_TTL) {
lastLoad = resource._load();
lastLoadTime = Date.now();
// If the load fails, we need to clear out lastLoadTime so another load
// attempt will succeed.
lastLoad.$promise.catch(function () {
lastLoadTime = null;
});
}
return lastLoad;
};
function prepare(data, headersGetter) {
var csrfTok = resource.state.csrf;
if (typeof csrfTok !== 'undefined') {
......@@ -105,6 +137,10 @@ function session($document, $http, $resource, flash) {
// Copy the model data (including the CSRF token) into `resource.state`.
angular.copy(model, resource.state);
// Replace lastLoad with the latest data, and update lastLoadTime.
lastLoad = {$promise: Promise.resolve(model), $resolved: true};
lastLoadTime = Date.now();
// Return the model
return model;
}
......
......@@ -112,5 +112,43 @@ describe('h:session', function () {
$httpBackend.flush();
assert.deepEqual(session.state, response.model);
});
it('an immediately-following call to #load() should not trigger a new request', function () {
$httpBackend.expectPOST(url).respond({});
session.login();
$httpBackend.flush();
session.load();
});
});
describe('#load()', function () {
var url = 'http://foo.com/app';
it('should fetch the session data', function () {
$httpBackend.expectGET(url).respond({});
session.load();
$httpBackend.flush();
});
it('should cache the session data', function () {
$httpBackend.expectGET(url).respond({});
session.load();
session.load();
$httpBackend.flush();
});
it('should eventually expire the cache', function () {
var clock = sandbox.useFakeTimers();
$httpBackend.expectGET(url).respond({});
session.load();
$httpBackend.flush();
clock.tick(301 * 1000);
$httpBackend.expectGET(url).respond({});
session.load();
$httpBackend.flush();
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment