TempStorage: Generic refactoring of TokenStorage
The Tempstorage module holds an ``initialized`` promise that is resolved once the data has been retrieved once from the worker. When that's done, all data is directly accessible through getters/setters. Data that is set in one tab/window through the modules getters/setters will be accessible in other tabs/windows as well. Change-Id: I7011995954fe362f6e45d2999fb998e5029cc769
This commit is contained in:
parent
c241346d16
commit
02d168f9db
10
gulpfile.js
10
gulpfile.js
|
@ -7,9 +7,9 @@ var shell = require('gulp-shell');
|
|||
// var uglify = require('gulp-uglify');
|
||||
var webpack = require('gulp-webpack');
|
||||
var webpackConfig = require('./webpack.config.js');
|
||||
var tokenWorkerConfig = require('./tokenworker.webpack.config.js');
|
||||
var tempStorageWorkerConfig = require('./tempstorageworker.webpack.config.js');
|
||||
|
||||
gulp.task('webpack-app', ['webpack-token-worker'], function() {
|
||||
gulp.task('webpack-app', ['webpack-tempstorage-worker'], function() {
|
||||
return gulp.src('./src/js/index.js')
|
||||
.pipe(webpack(webpackConfig))
|
||||
.pipe(gulp.dest('dist/js'))
|
||||
|
@ -43,9 +43,9 @@ gulp.task('fonts', function() {
|
|||
.pipe(gulp.dest('./dist/fonts'));
|
||||
});
|
||||
|
||||
gulp.task('webpack-token-worker', function() {
|
||||
return gulp.src('./src/js/workers/TokenWorker.js')
|
||||
.pipe(webpack(tokenWorkerConfig))
|
||||
gulp.task('webpack-tempstorage-worker', function() {
|
||||
return gulp.src('./src/js/workers/TempStorageWorker.js')
|
||||
.pipe(webpack(tempStorageWorkerConfig))
|
||||
.pipe(gulp.dest('./dist/js'))
|
||||
.pipe(browserSync.stream());
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ const AppDispatcher = require('../../js/dispatchers/AppDispatcher');
|
|||
const KeystoneApiService = require('../../js/services/KeystoneApiService');
|
||||
const LoginActions = require('../../js/actions/LoginActions');
|
||||
const LoginConstants = require('../../js/constants/LoginConstants');
|
||||
const AuthTokenStorage = require('../../js/services/AuthTokenStorage.js');
|
||||
const TempStorage = require('../../js/services/TempStorage.js');
|
||||
|
||||
let mockKeystoneAccess = {
|
||||
token: {
|
||||
|
@ -21,10 +21,13 @@ describe('LoginActions', () => {
|
|||
});
|
||||
|
||||
it('creates action to login user with keystoneAccess response', () => {
|
||||
spyOn(AuthTokenStorage, 'storeTokenId');
|
||||
spyOn(TempStorage, 'setItem');
|
||||
spyOn(AppDispatcher, 'dispatch').and.callThrough();
|
||||
LoginActions.loginUser(mockKeystoneAccess);
|
||||
expect(AuthTokenStorage.storeTokenId).toHaveBeenCalledWith(mockKeystoneAccess.token.id);
|
||||
expect(TempStorage.setItem).toHaveBeenCalledWith(
|
||||
'keystoneAuthTokenId',
|
||||
mockKeystoneAccess.token.id
|
||||
);
|
||||
expect(AppDispatcher.dispatch).toHaveBeenCalledWith({
|
||||
actionType: LoginConstants.LOGIN_USER,
|
||||
keystoneAccess: mockKeystoneAccess
|
||||
|
@ -32,10 +35,10 @@ describe('LoginActions', () => {
|
|||
});
|
||||
|
||||
it('creates action to logout user', () => {
|
||||
spyOn(AuthTokenStorage, 'removeTokenId');
|
||||
spyOn(TempStorage, 'removeItem');
|
||||
spyOn(AppDispatcher, 'dispatch').and.callThrough();
|
||||
LoginActions.logoutUser();
|
||||
expect(AuthTokenStorage.removeTokenId).toHaveBeenCalled();
|
||||
expect(TempStorage.removeItem).toHaveBeenCalled();
|
||||
expect(AppDispatcher.dispatch).toHaveBeenCalledWith({
|
||||
actionType: LoginConstants.LOGOUT_USER
|
||||
});
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
const AuthTokenStorage = require('../../js/services/AuthTokenStorage.js');
|
||||
const LoginActions = require('../../js/actions/LoginActions');
|
||||
|
||||
describe('AuthTokenStorage.', () => {
|
||||
describe('```AuthTokenStorage.getTokenId```', () => {
|
||||
let cbObj = { cb: (token) => {} };
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(cbObj, 'cb');
|
||||
});
|
||||
|
||||
it('calls the given callback with the token if one has been set.', (done) => {
|
||||
LoginActions.loginUser({
|
||||
token: 'token-from-login',
|
||||
user: null,
|
||||
serviceCatalog: null,
|
||||
metadata: null
|
||||
});
|
||||
// unset the sessionStorage value to make sure
|
||||
// the value is retreived from the worker.
|
||||
spyOn(sessionStorage, 'getItem').and.returnValue(null);
|
||||
AuthTokenStorage.getTokenId(cbObj.cb);
|
||||
done();
|
||||
expect(cbObj.cb).toHaveBeenCalledWith('token-from-login');
|
||||
});
|
||||
|
||||
it('gives precedence to the token in sessionStorage if one is set', () => {
|
||||
spyOn(sessionStorage, 'getItem').and.returnValue('token-from-session-storage');
|
||||
AuthTokenStorage.getTokenId(cbObj.cb);
|
||||
expect(cbObj.cb).toHaveBeenCalledWith('token-from-session-storage');
|
||||
});
|
||||
});
|
||||
|
||||
describe('```AuthTokenStorage.onLogoutUser```', () => {
|
||||
it('removes the token from sessionStorage', () => {
|
||||
spyOn(sessionStorage, 'removeItem');
|
||||
AuthTokenStorage.removeTokenId();
|
||||
expect(sessionStorage.removeItem).toHaveBeenCalledWith('keystoneAuthTokenId');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
const TempStorage = require('../../js/services/TempStorage.js');
|
||||
const WORKER_URL = '/js/rdo_director_ui_tempstorage_worker.js';
|
||||
|
||||
describe('TempStorage', () => {
|
||||
describe('.getItem', () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.removeItem('someKey');
|
||||
});
|
||||
|
||||
it('returns ``null`` if no value has been set yet.', () => {
|
||||
sessionStorage.removeItem('someKey');
|
||||
expect(TempStorage.getItem('someKey')).toBeNull();
|
||||
});
|
||||
|
||||
it('returns the value from sessionStorage if it is set', () => {
|
||||
sessionStorage.setItem('someKey', 'someValue');
|
||||
expect(TempStorage.getItem('someKey')).toEqual('someValue');
|
||||
});
|
||||
});
|
||||
|
||||
describe('worker updates', () => {
|
||||
let worker;
|
||||
|
||||
if(window && window.SharedWorker) {
|
||||
worker = new window.SharedWorker(WORKER_URL);
|
||||
worker.port.start();
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
sessionStorage.removeItem('someKey');
|
||||
});
|
||||
|
||||
it('update the sessionStorage items as well', () => {
|
||||
expect(TempStorage.getItem('someKey')).toBeNull();
|
||||
worker.port.postMessage({someKey: 'updated'});
|
||||
setTimeout(() => {
|
||||
expect(TempStorage.getItem('someKey')).toEqual('updated');
|
||||
expect(sessionStorage.getItem('someKey')).toEqual('updated');
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('```.setItem```', () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.removeItem('someKey');
|
||||
});
|
||||
|
||||
it('updates the worker as well as sessionStorage', () => {
|
||||
expect(TempStorage.getItem('someKey')).toBeNull();
|
||||
TempStorage.setItem('someKey', 'newVal');
|
||||
setTimeout(() => {
|
||||
expect(sessionStorage.getItem('someKey')).toBe('newVal');
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,5 +1,5 @@
|
|||
import AppDispatcher from '../dispatchers/AppDispatcher.js';
|
||||
import AuthTokenStorage from '../services/AuthTokenStorage.js';
|
||||
import TempStorage from '../services/TempStorage.js';
|
||||
import KeystoneApiService from '../services/KeystoneApiService';
|
||||
import LoginConstants from '../constants/LoginConstants';
|
||||
|
||||
|
@ -9,7 +9,7 @@ export default {
|
|||
},
|
||||
|
||||
loginUser(keystoneAccess) {
|
||||
AuthTokenStorage.storeTokenId(keystoneAccess.token.id);
|
||||
TempStorage.setItem('keystoneAuthTokenId', keystoneAccess.token.id);
|
||||
AppDispatcher.dispatch({
|
||||
actionType: LoginConstants.LOGIN_USER,
|
||||
keystoneAccess: keystoneAccess
|
||||
|
@ -17,7 +17,7 @@ export default {
|
|||
},
|
||||
|
||||
logoutUser() {
|
||||
AuthTokenStorage.removeTokenId();
|
||||
TempStorage.removeItem('keystoneAuthTokenId');
|
||||
AppDispatcher.dispatch({
|
||||
actionType: LoginConstants.LOGOUT_USER
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react';
|
|||
import * as Router from 'react-router';
|
||||
|
||||
import App from './components/App';
|
||||
import AuthTokenStorage from './services/AuthTokenStorage.js';
|
||||
import TempStorage from './services/TempStorage.js';
|
||||
import Login from './components/Login';
|
||||
import LoginActions from './actions/LoginActions';
|
||||
import Overview from './components/overview/Overview';
|
||||
|
@ -21,8 +21,8 @@ let routes = (
|
|||
</Route>
|
||||
);
|
||||
|
||||
|
||||
AuthTokenStorage.getTokenId((keystoneAuthTokenId) => {
|
||||
TempStorage.initialized.then(() => {
|
||||
let keystoneAuthTokenId = TempStorage.getItem('keystoneAuthTokenId');
|
||||
if (keystoneAuthTokenId) {
|
||||
LoginActions.authenticateUserViaToken(keystoneAuthTokenId);
|
||||
}
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
class AuthTokenStorage {
|
||||
|
||||
constructor() {
|
||||
this._createWorkerInstance();
|
||||
}
|
||||
|
||||
_createWorkerInstance() {
|
||||
if(window && window.SharedWorker) {
|
||||
this.worker = new window.SharedWorker('/js/rdo_director_ui_token_worker.js');
|
||||
this.worker.port.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently stored token ID.
|
||||
* @returns {String|Boolean} The token Id or false.
|
||||
*/
|
||||
getTokenId(cb) {
|
||||
let tokenId = sessionStorage.getItem('keystoneAuthTokenId');
|
||||
|
||||
// If there was a tokenId stored in sessionStorage,
|
||||
// just call the cb and leave.
|
||||
if(tokenId) {
|
||||
return cb(tokenId);
|
||||
}
|
||||
|
||||
// Create callback for temporary worker message listener.
|
||||
let fn = (e) => {
|
||||
cb(e.data);
|
||||
this.worker.port.removeEventListener('message', fn);
|
||||
};
|
||||
this.worker.port.addEventListener('message', fn);
|
||||
this.worker.port.postMessage(null);
|
||||
}
|
||||
|
||||
storeTokenId(tokenId) {
|
||||
sessionStorage.setItem('keystoneAuthTokenId', tokenId);
|
||||
this.worker.port.postMessage(tokenId);
|
||||
}
|
||||
|
||||
removeTokenId() {
|
||||
sessionStorage.removeItem('keystoneAuthTokenId');
|
||||
if(this.worker) {
|
||||
// Post a value of false to erase the token.
|
||||
this.worker.port.postMessage(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new AuthTokenStorage();
|
|
@ -0,0 +1,81 @@
|
|||
import when from 'when';
|
||||
|
||||
const WORKER_URL = '/js/rdo_director_ui_tempstorage_worker.js';
|
||||
|
||||
class TempStorage {
|
||||
|
||||
constructor() {
|
||||
this._createWorkerInstance();
|
||||
// This promise is resolved when the store has been loaded from
|
||||
// the worker for the first time.
|
||||
this._def = when.defer();
|
||||
this.initialized = this._def.promise;
|
||||
this._initStore();
|
||||
}
|
||||
|
||||
_createWorkerInstance() {
|
||||
if(window && window.SharedWorker) {
|
||||
this.worker = new window.SharedWorker(WORKER_URL);
|
||||
this.worker.port.start();
|
||||
}
|
||||
}
|
||||
|
||||
_initStore() {
|
||||
if(this.worker) {
|
||||
|
||||
this.worker.port.onmessage = e => {
|
||||
if(e.data !== null && typeof e.data === 'object') {
|
||||
for(let key in e.data) {
|
||||
let val = e.data[key];
|
||||
if(val === undefined) {
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
else {
|
||||
// sessionStorage can only store text, so serialize if necessary.
|
||||
val = (typeof(val) === 'object') ? JSON.stringify(val) : val;
|
||||
sessionStorage.setItem(key, val);
|
||||
}
|
||||
}
|
||||
this._def.resolve(e.data);
|
||||
}
|
||||
};
|
||||
this.worker.port.postMessage(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item from the store.
|
||||
*/
|
||||
getItem(key) {
|
||||
let item = sessionStorage.getItem(key);
|
||||
// Try to deserialize, if the original value was an object/array.
|
||||
try {
|
||||
return JSON.parse(item);
|
||||
} catch(err) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/modifiy an item in the store
|
||||
*/
|
||||
setItem(key, val) {
|
||||
let storeObj = {};
|
||||
// sessionStorage can only store text, so serialize if necessary.
|
||||
val = (typeof(val) === 'object') ? JSON.stringify(val) : val;
|
||||
storeObj[key] = val;
|
||||
this.worker.port.postMessage(storeObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the store
|
||||
*/
|
||||
removeItem(key) {
|
||||
let storeObj = {};
|
||||
storeObj[key] = undefined;
|
||||
this.worker.port.postMessage(storeObj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new TempStorage();
|
|
@ -0,0 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
let store = {};
|
||||
let ports = [];
|
||||
|
||||
self.onconnect = connEvent => {
|
||||
|
||||
ports.push(connEvent.ports[0]);
|
||||
connEvent.ports[0].onmessage = e => {
|
||||
if(e.data !== null) {
|
||||
if(typeof(e.data) === 'object') {
|
||||
for(let key in e.data) {
|
||||
store[key] = e.data[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
ports.forEach(port => {
|
||||
port.postMessage(store);
|
||||
});
|
||||
};
|
||||
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
let message = undefined;
|
||||
|
||||
self.addEventListener('connect', e => {
|
||||
|
||||
let port = e.ports[0];
|
||||
|
||||
port.addEventListener('message', e => {
|
||||
if(e.data !== null) {
|
||||
message = e.data;
|
||||
}
|
||||
port.postMessage(message);
|
||||
}, false);
|
||||
|
||||
port.start();
|
||||
|
||||
}, false);
|
|
@ -1,8 +1,8 @@
|
|||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
output: {
|
||||
filename: 'rdo_director_ui_token_worker.js',
|
||||
sourceMapFilename: 'rdo_director_ui_token_worker.js.map'
|
||||
filename: 'rdo_director_ui_tempstorage_worker.js',
|
||||
sourceMapFilename: 'rdo_director_ui_tempstorage_worker.js.map'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
Loading…
Reference in New Issue