jwt-autorefresh
Version:
Factory to schedule and execute calls to refresh token endpoints in advance of token expiration.
127 lines (109 loc) • 5.13 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
exports.default = autorefresh;
var _jwtSimple = require('jwt-simple');
var _chai = require('chai');
var _bunyan = require('bunyan');
var IS_DEV = process.env.NODE_ENV !== 'production';
var CODES = { DELAY: 'DELAY',
DELAY_ERROR: 'DELAY_ERROR',
INVALID_JWT: 'INVALID_JWT',
EXECUTE: 'EXECUTE',
SCHEDULE: 'SCHEDULE',
START: 'START',
CANCEL: 'CANCEL'
};
var format = function format(code, message) {
return code + '|' + message;
};
var validate = function validate(_ref) {
var refresh = _ref.refresh,
leadSeconds = _ref.leadSeconds,
_ref$log = _ref.log,
log = _ref$log === undefined ? (0, _bunyan.createLogger)({ name: 'autorefresh', level: IS_DEV ? 'warn' : 'error' }) : _ref$log;
if (IS_DEV) {
_chai.assert.ok(refresh, 'autorefresh requires a refresh function parameter');
_chai.assert.ok(leadSeconds, 'autorefresh requires a leadSeconds number or function returning a number in seconds parameter');
_chai.assert.typeOf(refresh, 'function', 'autorefresh refresh parameter must be a function');
(0, _chai.assert)(['number', 'function'].includes(typeof leadSeconds === 'undefined' ? 'undefined' : _typeof(leadSeconds)), 'function', 'autorefresh refresh parameter must be a function');
}
return { refresh: refresh, leadSeconds: leadSeconds, log: log };
};
function autorefresh(opts) {
var _validate = validate(opts),
refresh = _validate.refresh,
leadSeconds = _validate.leadSeconds,
log = _validate.log;
var timeoutID = null;
var calculateDelay = function calculateDelay(access_token) {
try {
if (IS_DEV) {
_chai.assert.ok(access_token, 'calculateDelay expects an access_token parameter');
_chai.assert.typeOf(access_token, 'string', 'access_token should be a string');
}
var _decode = (0, _jwtSimple.decode)(access_token, null, true),
exp = _decode.exp,
nbf = _decode.nbf;
if (IS_DEV) {
_chai.assert.ok(exp, 'autorefresh requires JWT token with "exp" standard claim');
if (nbf) {
_chai.assert.typeOf(nbf, 'number', 'nbf claim should be a future NumericDate value');
_chai.assert.isBelow(nbf, exp, '"nbf" claim should be less than "exp" claim if it exists');
}
}
var lead = typeof leadSeconds === 'function' ? leadSeconds() : leadSeconds;
if (IS_DEV) {
_chai.assert.typeOf(lead, 'number', 'leadSeconds must be or return a number');
_chai.assert.isAbove(lead, 0, 'lead seconds must resolve to a positive number of seconds');
}
var refreshAtMS = (exp - lead) * 1000;
var delay = refreshAtMS - Date.now();
log.info(format(CODES.DELAY, 'calculated autorefresh delay => ' + (delay / 1000).toFixed(1) + ' seconds'));
return delay;
} catch (err) {
if (/$Unexpected token [A-Za-z] in JSON/.test(err.message)) throw new Error(format(CODES.INVALID_JWT, 'JWT token was not a valid format => ' + access_token));
throw new Error(format(CODES.DELAY_ERROR, 'error occurred calculating autorefresh delay => ' + err.message));
}
};
var _schedule = function _schedule(access_token) {
if (IS_DEV) _chai.assert.typeOf(access_token, 'string', '_schedule expects a string access_token parameter');
var delay = calculateDelay(access_token);
if (IS_DEV) _chai.assert.isAbove(delay, 0, 'next auto refresh should always be in the future');
return schedule(delay);
};
var execute = function execute() {
clearTimeout(timeoutID);
log.info(format(CODES.EXECUTE, 'executing refresh'));
var result = refresh();
if (typeof result === 'string') return _schedule(result);
_chai.assert.ok(result.then, 'refresh must return the access_token or a string that resolves to the access_token');
return result.then(function (access_token) {
return _schedule(access_token);
}).catch(function (err) {
log.error(err, format(CODES.INVALID_REFRESH, 'refresh rejected with an error => ' + err.message));
throw err;
});
};
var schedule = function schedule(delay) {
clearTimeout(timeoutID);
log.info(format(CODES.SCHEDULE, 'scheduled refresh in ' + (delay / 1000).toFixed(1) + ' seconds'));
timeoutID = setTimeout(function () {
return execute();
}, delay);
};
var start = function start(access_token) {
log.info(format(CODES.START, 'autorefresh started'));
var delay = calculateDelay(access_token);
if (IS_DEV) _chai.assert.typeOf(delay, 'number', 'calculateDelay must return a number in milliseconds');
if (delay > 0) schedule(delay);else execute();
var stop = function stop() {
clearTimeout(timeoutID);
log.info(format(CODES.CANCEL, 'autorefresh cancelled'));
};
return stop;
};
return start;
}