redisess
Version:
Powerful redis session manager for NodeJS
250 lines (249 loc) • 7.77 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Session = void 0;
const tslib_1 = require("tslib");
const putil_promisify_1 = tslib_1.__importDefault(require("putil-promisify"));
const util_1 = tslib_1.__importDefault(require("util"));
const zlib_1 = tslib_1.__importDefault(require("zlib"));
const unzip = util_1.default.promisify(zlib_1.default.unzip);
class Session {
/**
*
* @param {Backend} backend
* @param {Object} opts
* @param {string} opts.sessionId
* @param {string} [opts.userId]
* @param {number} [opts.ttl]
* @constructor
*/
constructor(backend, opts) {
this._backend = backend;
this._sessionId = opts.sessionId;
this._userId = opts.userId || '';
this._ttl = opts.ttl ?? 30 * 60;
this._lastAccess = 0;
this._expires = 0;
}
/**
* Retrieves session id value
*
* @return {string}
*/
get sessionId() {
return this._sessionId;
}
/**
* Retrieves user id value
*
* @return {string}
*/
get userId() {
return this._userId;
}
/**
* Retrieves Time-To-Live value
*
* @return {number}
*/
get ttl() {
return this._ttl;
}
/**
* Retrieves the time (unix) of last access
*
* @return {number}
*/
get lastAccess() {
return this._lastAccess;
}
/**
* Retrieves the time (unix) that session be expired.
*
* @return {number}
*/
get expires() {
return this._expires;
}
/**
* Retrieves duration that session be expired.
*
* @return {number}
*/
get expiresIn() {
return this._expires ? this._expires - this._backend.now() : 0;
}
get valid() {
return !!(this._sessionId && this._userId && this._lastAccess);
}
/**
* Retrieves idle duration in seconds
*
* @return {number}
*/
get idle() {
return this._backend.now() - this.lastAccess;
}
/**
* Reads session info from redis server
*
* @return {Promise}
*/
async read() {
const backend = this._backend;
const sessKey = backend.ns + ':sess_' + this.sessionId;
const client = await backend.getClient();
const args = ['us', 'la', 'ex', 'ttl'];
/* istanbul ignore else */
if (backend.additionalFields) {
for (const key of backend.additionalFields.keys())
args.push('f' + key);
}
const resp = await putil_promisify_1.default.fromCallback(cb => client.hmget(sessKey, ...args, cb));
this._userId = resp[0] || '';
this._lastAccess = Number(resp[1]) || 0;
this._expires = Number(resp[2]) || 0;
this._ttl = Number(resp[3]) || 0;
/* istanbul ignore else */
if (backend.additionalFields) {
for (const [i, f] of backend.additionalFields.entries()) {
this[f] = resp[4 + i];
}
}
}
/**
* Retrieves user data from session
*
* @param {string|Array<String>|Object<String,*>} key
* @return {Promise<*>}
*/
async get(key) {
const backend = this._backend;
const sessKey = backend.ns + ':sess_' + this.sessionId;
const fromTyped = async (v) => {
let x = v.substring(1);
switch (v[0]) {
case 'b':
x = Buffer.from(x, 'base64');
break;
case 'd':
x = new Date(x);
break;
case 'n':
x = Number(x);
break;
case 'o':
x = JSON.parse((await unzip(Buffer.from(x, 'base64'))).toString());
break;
default:
break;
}
return x;
};
const client = await backend.getClient();
// Prepare keys to query
let keys;
if (Array.isArray(key)) {
keys = key.slice();
for (const [i, k] of keys.entries())
keys[i] = '$' + k;
}
else if (typeof key === 'object') {
keys = Object.keys(key);
for (const [i, k] of keys.entries())
keys[i] = '$' + k;
}
else
keys = ['$' + key];
// Query values for keys
const resp = await putil_promisify_1.default.fromCallback(cb => client.hmget(sessKey, keys, cb));
// Do type conversion
for (const [i, v] of resp.entries())
resp[i] = await fromTyped(v);
if (Array.isArray(key))
return resp;
if (typeof key === 'object') {
for (const [i, k] of keys.entries()) {
key[k.substring(1)] = resp[i];
}
return key;
}
return resp[0];
}
async set(arg0, arg1) {
const backend = this._backend;
const sessKey = backend.ns + ':sess_' + this.sessionId;
const client = await backend.getClient();
const values = typeof arg0 === 'object'
? this._prepareUserData(arg0)
: this._prepareUserData('' + arg0, arg1);
const resp = await putil_promisify_1.default.fromCallback(cb => client.hmset(sessKey, values, cb));
/* istanbul ignore next */
if (!String(resp).includes('OK')) {
throw new Error('Redis write operation failed');
}
return Math.floor(values.length / 2);
}
/**
* Kills the session
*
* @return {Promise}
*/
async kill() {
const backend = this._backend;
const client = await backend.getClient();
const { sessionId, userId } = this;
const resp = await backend.killScript.execute(client, backend.ns, sessionId, userId);
/* istanbul ignore next */
if (!resp) {
throw new Error('Unable to store session due to an unknown error');
}
}
/**
*
* @return {Promise}
* @private
*/
async write() {
const backend = this._backend;
const client = await backend.getClient();
this._lastAccess = backend.now();
this._expires = this._ttl ? this._lastAccess + this._ttl : 0;
const { sessionId, userId, lastAccess, expires, ttl } = this;
const args = [backend.ns, lastAccess, userId, sessionId, expires, ttl];
/* istanbul ignore else */
if (backend.additionalFields) {
for (const f of backend.additionalFields)
args.push(this[f] || null);
}
const resp = await backend.writeScript.execute(client, ...args);
/* istanbul ignore next */
if (!resp) {
throw new Error('Unable to store session due to an unknown error');
}
}
_prepareUserData(arg0, arg1) {
const makeTyped = v => {
if (v instanceof Buffer)
return 'b' + v.toString('base64');
if (v instanceof Date)
return 'd' + v.toISOString();
if (typeof v === 'number')
return 'n' + String(v);
if (typeof v === 'object') {
return 'o' + zlib_1.default.deflateSync(JSON.stringify(v)).toString('base64');
}
return 's' + String(v);
};
let values = [];
if (typeof arg0 === 'object') {
for (const k of Object.keys(arg0)) {
values.push('$' + k);
values.push(makeTyped(arg0[k]));
}
}
else
values = ['$' + arg0, makeTyped(arg1)];
return values;
}
}
exports.Session = Session;