UNPKG

@oat-sa/tao-core-sdk

Version:
203 lines (194 loc) 7.26 kB
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; });