296 lines
6.1 KiB
JavaScript
296 lines
6.1 KiB
JavaScript
|
/*!
|
||
|
* memorystore
|
||
|
* Copyright(c) 2020 Rocco Musolino <@roccomuso>
|
||
|
* MIT Licensed
|
||
|
*/
|
||
|
|
||
|
var debug = require('debug')('memorystore')
|
||
|
var LRU = require('lru-cache')
|
||
|
var util = require('util')
|
||
|
|
||
|
/**
|
||
|
* One day in milliseconds.
|
||
|
*/
|
||
|
|
||
|
var oneDay = 86400000
|
||
|
|
||
|
function getTTL (options, sess, sid) {
|
||
|
if (typeof options.ttl === 'number') return options.ttl
|
||
|
if (typeof options.ttl === 'function') return options.ttl(options, sess, sid)
|
||
|
if (options.ttl) throw new TypeError('`options.ttl` must be a number or function.')
|
||
|
|
||
|
var maxAge = (sess && sess.cookie) ? sess.cookie.maxAge : null
|
||
|
return (typeof maxAge === 'number'
|
||
|
? Math.floor(maxAge)
|
||
|
: oneDay)
|
||
|
}
|
||
|
|
||
|
function prune (store) {
|
||
|
debug('Pruning expired entries')
|
||
|
store.forEach(function (value, key) {
|
||
|
store.get(key)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
var defer = typeof setImmediate === 'function'
|
||
|
? setImmediate
|
||
|
: function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }
|
||
|
|
||
|
/**
|
||
|
* Return the `MemoryStore` extending `express`'s session Store.
|
||
|
*
|
||
|
* @param {object} express session
|
||
|
* @return {Function}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
module.exports = function (session) {
|
||
|
/**
|
||
|
* Express's session Store.
|
||
|
*/
|
||
|
|
||
|
var Store = session.Store
|
||
|
|
||
|
/**
|
||
|
* Initialize MemoryStore with the given `options`.
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
function MemoryStore (options) {
|
||
|
if (!(this instanceof MemoryStore)) {
|
||
|
throw new TypeError('Cannot call MemoryStore constructor as a function')
|
||
|
}
|
||
|
|
||
|
options = options || {}
|
||
|
Store.call(this, options)
|
||
|
|
||
|
this.options = {}
|
||
|
this.options.checkPeriod = options.checkPeriod
|
||
|
this.options.max = options.max || Infinity
|
||
|
this.options.ttl = options.ttl
|
||
|
this.options.dispose = options.dispose
|
||
|
this.options.stale = options.stale
|
||
|
this.options.noDisposeOnSet = options.noDisposeOnSet
|
||
|
|
||
|
this.serializer = options.serializer || JSON
|
||
|
this.store = LRU(this.options)
|
||
|
debug('Init MemoryStore')
|
||
|
|
||
|
this.startInterval()
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Inherit from `Store`.
|
||
|
*/
|
||
|
|
||
|
util.inherits(MemoryStore, Store)
|
||
|
|
||
|
/**
|
||
|
* Attempt to fetch session by the given `sid`.
|
||
|
*
|
||
|
* @param {String} sid
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.get = function (sid, fn) {
|
||
|
var store = this.store
|
||
|
|
||
|
debug('GET "%s"', sid)
|
||
|
|
||
|
var data = store.get(sid)
|
||
|
if (!data) return fn()
|
||
|
|
||
|
debug('GOT %s', data)
|
||
|
var err = null
|
||
|
var result
|
||
|
try {
|
||
|
result = this.serializer.parse(data)
|
||
|
} catch (er) {
|
||
|
err = er
|
||
|
}
|
||
|
|
||
|
fn && defer(fn, err, result)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Commit the given `sess` object associated with the given `sid`.
|
||
|
*
|
||
|
* @param {String} sid
|
||
|
* @param {Session} sess
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.set = function (sid, sess, fn) {
|
||
|
var store = this.store
|
||
|
|
||
|
var ttl = getTTL(this.options, sess, sid)
|
||
|
try {
|
||
|
var jsess = this.serializer.stringify(sess)
|
||
|
} catch (err) {
|
||
|
fn && defer(fn, err)
|
||
|
}
|
||
|
|
||
|
store.set(sid, jsess, ttl)
|
||
|
debug('SET "%s" %s ttl:%s', sid, jsess, ttl)
|
||
|
fn && defer(fn, null)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Destroy the session associated with the given `sid`.
|
||
|
*
|
||
|
* @param {String} sid
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.destroy = function (sid, fn) {
|
||
|
var store = this.store
|
||
|
|
||
|
if (Array.isArray(sid)) {
|
||
|
sid.forEach(function (s) {
|
||
|
debug('DEL "%s"', s)
|
||
|
store.del(s)
|
||
|
})
|
||
|
} else {
|
||
|
debug('DEL "%s"', sid)
|
||
|
store.del(sid)
|
||
|
}
|
||
|
fn && defer(fn, null)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Refresh the time-to-live for the session with the given `sid`.
|
||
|
*
|
||
|
* @param {String} sid
|
||
|
* @param {Session} sess
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.touch = function (sid, sess, fn) {
|
||
|
var store = this.store
|
||
|
|
||
|
var ttl = getTTL(this.options, sess, sid)
|
||
|
|
||
|
debug('EXPIRE "%s" ttl:%s', sid, ttl)
|
||
|
var err = null
|
||
|
if (store.get(sid) !== undefined) {
|
||
|
try {
|
||
|
var s = this.serializer.parse(store.get(sid))
|
||
|
s.cookie = sess.cookie
|
||
|
store.set(sid, this.serializer.stringify(s), ttl)
|
||
|
} catch (e) {
|
||
|
err = e
|
||
|
}
|
||
|
}
|
||
|
fn && defer(fn, err)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fetch all sessions' ids
|
||
|
*
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.ids = function (fn) {
|
||
|
var store = this.store
|
||
|
|
||
|
var Ids = store.keys()
|
||
|
debug('Getting IDs: %s', Ids)
|
||
|
fn && defer(fn, null, Ids)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fetch all sessions
|
||
|
*
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.all = function (fn) {
|
||
|
var store = this.store
|
||
|
var self = this
|
||
|
|
||
|
debug('Fetching all sessions')
|
||
|
var err = null
|
||
|
var result = {}
|
||
|
try {
|
||
|
store.forEach(function (val, key) {
|
||
|
result[key] = self.serializer.parse(val)
|
||
|
})
|
||
|
} catch (e) {
|
||
|
err = e
|
||
|
}
|
||
|
fn && defer(fn, err, result)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete all sessions from the store
|
||
|
*
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.clear = function (fn) {
|
||
|
var store = this.store
|
||
|
debug('delete all sessions from the store')
|
||
|
store.reset()
|
||
|
fn && defer(fn, null)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the count of all sessions in the store
|
||
|
*
|
||
|
* @param {Function} fn
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.length = function (fn) {
|
||
|
var store = this.store
|
||
|
debug('getting length', store.itemCount)
|
||
|
fn && defer(fn, null, store.itemCount)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Start the check interval
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.startInterval = function () {
|
||
|
var self = this
|
||
|
var ms = this.options.checkPeriod
|
||
|
if (ms && typeof ms === 'number') {
|
||
|
clearInterval(this._checkInterval)
|
||
|
debug('starting periodic check for expired sessions')
|
||
|
this._checkInterval = setInterval(function () {
|
||
|
prune(self.store) // iterates over the entire cache proactively pruning old entries
|
||
|
}, Math.floor(ms)).unref()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stop the check interval
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.stopInterval = function () {
|
||
|
debug('stopping periodic check for expired sessions')
|
||
|
clearInterval(this._checkInterval)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Remove only expired entries from the store
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
MemoryStore.prototype.prune = function () {
|
||
|
prune(this.store)
|
||
|
}
|
||
|
|
||
|
return MemoryStore
|
||
|
}
|