@oat-sa/tao-core-sdk
Version:
Core libraries of TAO
203 lines (194 loc) • 7.26 kB
JavaScript
define(['lodash', 'core/store'], function (_, store) { 'use strict';
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
store = store && Object.prototype.hasOwnProperty.call(store, 'default') ? store['default'] : store;
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2019 Open Assessment Technologies SA
*/
/**
* @typedef {Object} token - A token object
* @property {String} value - Long alphanumeric string
* @property {Number} receivedAt - Creation timestamp
*/
const defaultConfig = {
maxSize: 6,
// Default number of tokens to store
tokenTimeLimit: 1000 * 60 * 24,
// Default token TTL (24 minutes)
store: 'memory' // In memory storage is preferred by default over the indexeddb or localStorage implementations
};
/**
* Create a token store
* @param {Object} [options]
* @param {Number} [options.maxSize = 6] - the store limit
* @param {Number} [options.tokenTimeLimit] - time in milliseconds each token remains valid for
* @returns {tokenStore}
*/
function tokenStoreFactory(options) {
const config = _.defaults(options || {}, defaultConfig);
const getStoreBackend = () => store.backends[config.store] || store.backends[defaultConfig.store];
const getStore = () => store('tokenStore.tokens', getStoreBackend());
/**
* @typedef tokenStore
*/
return {
/**
* Get the oldest token from the queue
* Remove its store entry as well
*
* @returns {Promise<Object>} the token object
*/
dequeue() {
return this.getIndex().then(latestIndex => {
const key = _.first(latestIndex);
if (!key) {
return Promise.resolve();
}
return getStore().then(storage => storage.getItem(key)).then(token => this.remove(key).then(() => token));
});
},
/**
* Add a new token object to the queue
* Add an entry to the store as well
*
* @param {token} token - the token object
* @param {String} token.value - long alphanumeric string
* @param {Number} token.receivedAt - timestamp
* @returns {Promise<Boolean>} - true if added
*/
enqueue(token) {
// Handle legacy param type:
if (_.isString(token)) {
token = {
value: token,
receivedAt: Date.now()
};
}
return getStore().then(storage => storage.setItem(token.value, token)).then(updated => {
if (updated) {
return this.enforceMaxSize().then(() => true);
}
return false;
});
},
/**
* Generate a new (chronologically-sorted) index from the store contents
* (because it would not be unique if stored in the module)
*
* @returns {Promise<Array>}
*/
getIndex() {
return this.getTokens().then(tokens => Object.values(tokens).sort((t1, t2) => t1.receivedAt - t2.receivedAt).map(token => token.value));
},
/**
* Check whether the given token is in the store
*
* @param {String} key - token string
* @returns {Promise<Boolean>}
*/
has(key) {
return this.getIndex().then(latestIndex => latestIndex.includes(key));
},
/**
* Remove the token from the queue and the store
*
* @param {String} key - token string
* @returns {Promise<Boolean>} resolves once removed
*/
remove(key) {
return this.has(key).then(result => {
if (result) {
return getStore().then(storage => storage.removeItem(key));
}
return false;
});
},
/**
* Empty the queue and store
* @returns {Promise}
*/
clear() {
return getStore().then(storage => storage.clear());
},
/**
* Gets all tokens in the store
* @returns {Promise<Array>} - token objects
*/
getTokens() {
return getStore().then(storage => storage.getItems());
},
/**
* Gets the current size of the store
* @returns {Promise<Number>}
*/
getSize() {
return this.getIndex().then(latestIndex => latestIndex.length);
},
/**
* Setter for maximum pool size
* @param {Number} size
*/
setMaxSize(size) {
if (_.isNumber(size) && size > 0 && size !== config.maxSize) {
config.maxSize = size;
this.enforceMaxSize();
}
},
/**
* Removes oldest tokens, if the pool is above its size limit
* (Could happen if maxSize is reduced during the life of the tokenStore)
* @returns {Promise} - resolves when done
*/
enforceMaxSize() {
return this.getIndex().then(latestIndex => {
const excess = latestIndex.length - config.maxSize;
if (excess > 0) {
const keysToRemove = latestIndex.slice(0, excess);
return Promise.all(keysToRemove.map(key => this.remove(key)));
}
return true;
});
},
/**
* Checks one token and removes it from the store if expired.
* If the timeLimit is lesser than or equal to 0, no time limit is applied.
* @param {token} token - the token object
* @returns {Promise<Boolean>}
*/
checkExpiry(token) {
const {
tokenTimeLimit
} = config;
if (tokenTimeLimit > 0 && Date.now() - token.receivedAt > tokenTimeLimit) {
return this.remove(token.value);
}
return Promise.resolve(true);
},
/**
* Checks all the tokens in the store to see if they expired
* @returns {Promise<Boolean>} - resolves to true
*/
expireOldTokens() {
return this.getTokens()
// Check each token's expiry, synchronously:
.then(tokens => Object.values(tokens).reduce((previousPromise, nextToken) => previousPromise.then(() => this.checkExpiry(nextToken)), Promise.resolve()))
// All done
.then(() => true);
}
};
}
return tokenStoreFactory;
});