redisess
Version:
Powerful redis session manager for NodeJS
235 lines (234 loc) • 7.86 kB
JavaScript
import crypto from 'crypto';
import promisify from 'putil-promisify';
import { Backend } from './backend.js';
import { Session } from './session.js';
/**
*
* @class
*/
export class SessionManager {
/**
*
* @param {Object} client
* @param {Object} [props]
* @param {Object} [props.namespace='sm']
* @param {number} [props.ttl] Time-To-Live value in seconds
* @param {number} [props.wipeInterval=1000]
* @param {Array<String>} [props.additionalFields]
*/
constructor(client, props = {}) {
this._backend = new Backend(client, props);
if (!(client && typeof client.hmset === 'function')) {
throw new TypeError('You must provide redis instance');
}
this._additionalFields = [...(props.additionalFields || [])];
client.once('close', () => this.quit());
}
get additionalFields() {
return this._additionalFields;
}
get namespace() {
return this._backend.ns;
}
get ttl() {
return this._backend.ttl;
}
/**
* Returns the number of sessions within the last n seconds.
* @param {number} [secs] The elapsed time since the last activity of the session. Returns total count of sessions If not defined or zero
* @return {Promise<Number>}
*/
async count(secs = 0) {
const client = await this._backend.getClient();
secs = Number(secs);
const prefix = this._backend.ns;
const resp = await promisify.fromCallback(cb => client.zcount(prefix + ':ACTIVITY', secs ? Math.floor(this._backend.now() - secs) : '-inf', '+inf', cb));
return Number(resp);
}
/**
* Retrieves session count of single user which were active within the last n seconds.
*
* @param {string} userId
* @param {number} [secs]
* @return {Promise<number>}
*/
async countForUser(userId, secs = 0) {
if (!userId)
throw new TypeError('You must provide userId');
const client = await this._backend.getClient();
secs = Number(secs);
const resp = await client.zcount(this._backend.ns + ':user_' + userId, secs ? Math.floor(this._backend.now() - secs) : '-inf', '+inf');
return Number(resp);
}
/**
* Creates a new session for the user
*
* @param {string} userId
* @param {Object.<string, *>} [props]
* @param {number} [props.ttl] Time-To-Live value in seconds
* @param props
*/
async create(userId, props) {
if (!userId)
throw new TypeError('You must provide userId');
props = props || {};
const ttl = Number(props.ttl) >= 0 ? Number(props.ttl) : this.ttl;
const sessionId = this._createSessionId();
const session = new Session(this._backend, {
sessionId,
userId,
ttl,
});
/* istanbul ignore else */
if (this._backend.additionalFields) {
for (const f of this._backend.additionalFields)
session[f] = props[f];
}
await session.write();
return session;
}
/**
* Retrieves session by sessionId
*
* @param {string} sessionId
* @param {boolean} [noUpdate=false]
* @return {Promise<ResultSession>}
*/
async get(sessionId, noUpdate = false) {
if (!sessionId)
throw new TypeError('You must provide sessionId');
const session = new Session(this._backend, { sessionId });
await session.read();
if (!session.valid)
return undefined;
if (noUpdate)
return session;
await session.write();
return session;
}
/**
* Retrieves all session ids which were active within the last n seconds.
*
* @param {number} [secs]
* @return {Promise<Array<String>>}
*/
async getAllSessions(secs) {
const client = await this._backend.getClient();
secs = secs || Number.MAX_SAFE_INTEGER;
return await promisify.fromCallback(cb => client.zrevrangebyscore(this._backend.ns + ':ACTIVITY', '+inf', secs ? Math.floor(this._backend.now() - secs) : '-inf', cb));
}
/**
* Retrieves all user ids which were active within the last n seconds.
*
* @param {number} [secs]
* @return {Promise<Array<String>>}
*/
async getAllUsers(secs = 0) {
const client = await this._backend.getClient();
secs = Number(secs);
return await promisify.fromCallback(cb => client.zrevrangebyscore(this._backend.ns + ':USERS', '+inf', secs ? Math.floor(this._backend.now() - secs) : '-inf', cb));
}
/**
* Retrieves session ids of single user which were active within the last n seconds.
*
* @param {string} userId
* @param {number} [n]
* @return {Promise<Array<String>>}
*/
async getUserSessions(userId, n = 0) {
if (!userId)
throw new TypeError('You must provide userId');
const client = await this._backend.getClient();
n = Number(n);
return await promisify.fromCallback(cb => client.zrevrangebyscore(this._backend.ns + ':user_' + userId, '+inf', n ? Math.floor(this._backend.now() - n) : '-inf', cb));
}
/**
* Retrieves oldest session of user
*
* @param {string} userId
* @param {boolean} [noUpdate=false]
* @return {Promise<ResultSession>}
*/
async getOldestUserSession(userId, noUpdate = false) {
if (!userId)
throw new TypeError('You must provide userId');
const client = await this._backend.getClient();
const sessionId = await promisify.fromCallback(cb => client.zrevrange(this._backend.ns + ':user_' + userId, -1, -1, cb));
if (sessionId && sessionId.length) {
return await this.get(sessionId[0], noUpdate);
}
}
/**
* Returns true if sessionId exists, false otherwise,
*
* @param {string} sessionId
* @return {Promise<Boolean>}
*/
async exists(sessionId) {
if (!sessionId)
throw new TypeError('You must provide sessionId');
const client = await this._backend.getClient();
const resp = await promisify.fromCallback(cb => client.exists(this._backend.ns + ':sess_' + sessionId, cb));
return !!Number(resp);
}
/**
* Kills single session
*
* @param {string} sessionId
* @return {Promise<void>}
*/
async kill(sessionId) {
if (!sessionId)
throw new TypeError('You must provide sessionId');
const session = await this.get(sessionId, true);
if (session)
return await session.kill();
}
/**
* Kills all sessions of user
*
* @param {string} userId
* @return {Promise}
*/
async killUser(userId) {
if (!userId)
throw new TypeError('You must provide userId');
const sessions = await this.getUserSessions(userId);
for (const sid of sessions) {
await this.kill(sid);
}
}
/**
* Kills all sessions for application
*
* @return {Promise}
*/
async killAll() {
const client = await this._backend.getClient();
await this._backend.killAllScript.execute(client, this._backend.ns + ':*');
}
async now() {
const client = await this._backend.getClient();
await this._backend.syncTime(client);
return this._backend.now();
}
/* istanbul ignore next */
/**
* Stops wipe timer
*/
quit() {
this._backend.quit();
}
async wipe() {
return this._backend.wipe();
}
// noinspection JSMethodCanBeStatic
/**
*
* @return {string}
* @private
*/
_createSessionId() {
return crypto.randomBytes(16).toString('hex');
}
}