UNPKG

redisess

Version:

Powerful redis session manager for NodeJS

250 lines (249 loc) 7.77 kB
"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;