Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@sailshq/connect-redis

Package Overview
Dependencies
Maintainers
5
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sailshq/connect-redis - npm Package Compare versions

Comparing version 3.2.1 to 6.1.3

license

2

index.js

@@ -1,1 +0,1 @@

module.exports = require('./lib/connect-redis');
module.exports = require("./lib/connect-redis")
/*!
* Connect - Redis
* Copyright(c) 2012 TJ Holowaychuk <tj@vision-media.ca>
* Copyright(c) 2010-2020 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
var debug = require('debug')('connect:redis');
var redis = require('redis');
var util = require('util');
var noop = function(){};
/**
* One day in seconds.
*/
var oneDay = 86400;
function getTTL(store, sess) {
var maxAge = sess.cookie.maxAge;
return store.ttl || (typeof maxAge === 'number'
? Math.floor(maxAge / 1000)
: oneDay);
}
/**
* Return the `RedisStore` extending `express`'s session Store.
*
* @param {object} express session
* @return {Function}
* @api public
*/
module.exports = function (session) {
const Store = session.Store
/**
* Express's session Store.
*/
// All callbacks should have a noop if none provided for compatibility
// with the most Redis clients.
const noop = () => {}
var Store = session.Store;
class RedisStore extends Store {
constructor(options = {}) {
super(options)
if (!options.client) {
throw new Error("A client must be directly provided to the RedisStore")
}
/**
* Initialize RedisStore with the given `options`.
*
* @param {Object} options
* @api public
*/
function RedisStore (options) {
if (!(this instanceof RedisStore)) {
throw new TypeError('Cannot call RedisStore constructor as a function');
this.prefix = options.prefix == null ? "sess:" : options.prefix
this.scanCount = Number(options.scanCount) || 100
this.serializer = options.serializer || JSON
this.client = options.client
this.ttl = options.ttl || 86400 // One day in seconds.
this.disableTTL = options.disableTTL || false
this.disableTouch = options.disableTouch || false
}
var self = this;
get(sid, cb = noop) {
let key = this.prefix + sid
options = options || {};
Store.call(this, options);
this.prefix = options.prefix == null
? 'sess:'
: options.prefix;
this.client.get(key, (err, data) => {
if (err) return cb(err)
if (!data) return cb()
delete options.prefix;
this.serializer = options.serializer || JSON;
if (options.url) {
options.socket = options.url;
let result
try {
result = this.serializer.parse(data)
} catch (err) {
return cb(err)
}
return cb(null, result)
})
}
// convert to redis connect params
if (options.client) {
this.client = options.client;
}
else if (options.socket) {
this.client = redis.createClient(options.socket, options);
}
else {
this.client = redis.createClient(options);
}
set(sid, sess, cb = noop) {
let args = [this.prefix + sid]
// logErrors
if(options.logErrors){
// if options.logErrors is function, allow it to override. else provide default logger. useful for large scale deployment
// which may need to write to a distributed log
if(typeof options.logErrors != 'function'){
options.logErrors = function (err) {
console.error('Warning: connect-redis reported a client error: ' + err);
};
let value
try {
value = this.serializer.stringify(sess)
} catch (er) {
return cb(er)
}
this.client.on('error', options.logErrors);
}
args.push(value)
if (options.pass) {
this.client.auth(options.pass, function (err) {
if (err) {
throw err;
}
});
}
let ttl = 1
if (!this.disableTTL) {
ttl = this._getTTL(sess)
args.push("EX", ttl)
}
this.ttl = options.ttl;
this.disableTTL = options.disableTTL;
if (options.unref) this.client.unref();
if ('db' in options) {
if (typeof options.db !== 'number') {
console.error('Warning: connect-redis expects a number for the "db" option');
if (ttl > 0) {
this.client.set(args, cb)
} else {
// If the resulting TTL is negative we can delete / destroy the key
this.destroy(sid, cb)
}
}
self.client.select(options.db);
self.client.on('connect', function () {
self.client.select(options.db);
});
touch(sid, sess, cb = noop) {
if (this.disableTouch || this.disableTTL) return cb()
let key = this.prefix + sid
this.client.expire(key, this._getTTL(sess), (err, ret) => {
if (err) return cb(err)
if (ret !== 1) return cb(null, "EXPIRED")
cb(null, "OK")
})
}
self.client.on('error', function (er) {
debug('Redis returned err', er);
self.emit('disconnect', er);
});
destroy(sid, cb = noop) {
let key = this.prefix + sid
this.client.del(key, cb)
}
self.client.on('connect', function () {
self.emit('connect');
});
}
clear(cb = noop) {
this._getAllKeys((err, keys) => {
if (err) return cb(err)
this.client.del(keys, cb)
})
}
/**
* Inherit from `Store`.
*/
length(cb = noop) {
this._getAllKeys((err, keys) => {
if (err) return cb(err)
return cb(null, keys.length)
})
}
util.inherits(RedisStore, Store);
ids(cb = noop) {
let prefixLen = this.prefix.length
/**
* Attempt to fetch session by the given `sid`.
*
* @param {String} sid
* @param {Function} fn
* @api public
*/
this._getAllKeys((err, keys) => {
if (err) return cb(err)
keys = keys.map((key) => key.substr(prefixLen))
return cb(null, keys)
})
}
RedisStore.prototype.get = function (sid, fn) {
var store = this;
var psid = store.prefix + sid;
if (!fn) fn = noop;
debug('GET "%s"', sid);
all(cb = noop) {
let prefixLen = this.prefix.length
store.client.get(psid, function (er, data) {
if (er) return fn(er);
if (!data) return fn();
this._getAllKeys((err, keys) => {
if (err) return cb(err)
if (keys.length === 0) return cb(null, [])
var result;
data = data.toString();
debug('GOT %s', data);
this.client.mget(keys, (err, sessions) => {
if (err) return cb(err)
try {
result = store.serializer.parse(data);
}
catch (er) {
return fn(er);
}
return fn(null, result);
});
};
/**
* Commit the given `sess` object associated with the given `sid`.
*
* @param {String} sid
* @param {Session} sess
* @param {Function} fn
* @api public
*/
RedisStore.prototype.set = function (sid, sess, fn) {
var store = this;
var args = [store.prefix + sid];
if (!fn) fn = noop;
try {
var jsess = store.serializer.stringify(sess);
let result
try {
result = sessions.reduce((accum, data, index) => {
if (!data) return accum
data = this.serializer.parse(data)
data.id = keys[index].substr(prefixLen)
accum.push(data)
return accum
}, [])
} catch (e) {
err = e
}
return cb(err, result)
})
})
}
catch (er) {
return fn(er);
}
args.push(jsess);
if (!store.disableTTL) {
var ttl = getTTL(store, sess);
args.push('EX', ttl);
debug('SET "%s" %s ttl:%s', sid, jsess, ttl);
} else {
debug('SET "%s" %s', sid, jsess);
_getTTL(sess) {
let ttl
if (sess && sess.cookie && sess.cookie.expires) {
let ms = Number(new Date(sess.cookie.expires)) - Date.now()
ttl = Math.ceil(ms / 1000)
} else {
ttl = this.ttl
}
return ttl
}
store.client.set(args, function (er) {
if (er) return fn(er);
debug('SET complete');
fn.apply(null, arguments);
});
};
/**
* Destroy the session associated with the given `sid`.
*
* @param {String} sid
* @api public
*/
RedisStore.prototype.destroy = function (sid, fn) {
debug('DEL "%s"', sid);
if (Array.isArray(sid)) {
var multi = this.client.multi();
var prefix = this.prefix;
sid.forEach(function (s) {
multi.del(prefix + s);
});
multi.exec(fn);
} else {
sid = this.prefix + sid;
this.client.del(sid, fn);
_getAllKeys(cb = noop) {
let pattern = this.prefix + "*"
this._scanKeys({}, 0, pattern, this.scanCount, cb)
}
};
/**
* Refresh the time-to-live for the session with the given `sid`.
*
* @param {String} sid
* @param {Session} sess
* @param {Function} fn
* @api public
*/
_scanKeys(keys = {}, cursor, pattern, count, cb = noop) {
let args = [cursor, "match", pattern, "count", count]
this.client.scan(args, (err, data) => {
if (err) return cb(err)
RedisStore.prototype.touch = function (sid, sess, fn) {
var store = this;
var psid = store.prefix + sid;
if (!fn) fn = noop;
if (store.disableTTL) return fn();
let [nextCursorId, scanKeys] = data
for (let key of scanKeys) {
keys[key] = true
}
var ttl = getTTL(store, sess);
// This can be a string or a number. We check both.
if (Number(nextCursorId) !== 0) {
return this._scanKeys(keys, nextCursorId, pattern, count, cb)
}
debug('EXPIRE "%s" ttl:%s', sid, ttl);
store.client.expire(psid, ttl, function (er) {
if (er) return fn(er);
debug('EXPIRE complete');
fn.apply(this, arguments);
});
};
cb(null, Object.keys(keys))
})
}
}
/**
* Fetch all sessions' ids
*
* @param {Function} fn
* @api public
*/
RedisStore.prototype.ids = function (fn) {
var store = this;
var pattern = store.prefix + '*';
var prefixLength = store.prefix.length;
if (!fn) fn = noop;
debug('KEYS "%s"', pattern);
store.client.keys(pattern, function (er, keys) {
if (er) return fn(er);
debug('KEYS complete');
keys = keys.map(function (key) {
return key.substr(prefixLength);
});
return fn(null, keys);
});
};
/**
* Fetch all sessions
*
* @param {Function} fn
* @api public
*/
RedisStore.prototype.all = function (fn) {
var store = this;
var pattern = store.prefix + '*';
var prefixLength = store.prefix.length;
if (!fn) fn = noop;
debug('KEYS "%s"', pattern);
store.client.keys(pattern, function (er, keys) {
if (er) return fn(er);
debug('KEYS complete');
var multi = store.client.multi();
keys.forEach(function (key) {
multi.get(key);
});
multi.exec(function (er, sessions) {
if (er) return fn(er);
var result;
try {
result = sessions.map(function (data, index) {
data = data.toString();
data = store.serializer.parse(data);
data.id = keys[index].substr(prefixLength);
return data;
});
} catch (er) {
return fn(er);
}
return fn(null, result);
});
});
};
return RedisStore;
};
return RedisStore
}
{
"name": "@sailshq/connect-redis",
"description": "Redis session store for your Sails app.",
"version": "3.2.1",
"version": "6.1.3",
"author": "TJ Holowaychuk <tj@vision-media.ca>",

@@ -13,28 +13,34 @@ "contributors": [

"type": "git",
"url": "git@github.com:visionmedia/connect-redis.git"
"url": "git@github.com:mikermcneil/connect-redis.git"
},
"dependencies": {
"debug": "^2.2.0",
"redis": "^2.1.0"
},
"devDependencies": {
"blue-tape": "^0.1.8",
"bluebird": "^2.3.2",
"eslint": "^1.6.0",
"express-session": "^1.9.1",
"ioredis": "^1.7.5",
"istanbul": "^0.3.2",
"tape": "^4.2.1"
"blue-tape": "^1.0.0",
"eslint": "^7.4.0",
"eslint-config-prettier": "^8.3.0",
"express-session": "^1.17.0",
"ioredis": "^4.17.1",
"nyc": "^15.0.1",
"prettier": "^2.0.5",
"redis-mock": "^0.56.3",
"redis-v3": "npm:redis@3",
"redis-v4": "npm:redis@4"
},
"engines": {
"node": "*"
"node": ">=12"
},
"bugs": {
"url": "https://github.com/visionmedia/connect-redis/issues"
"url": "https://github.com/balderdashy/sails/issues"
},
"scripts": {
"test": "DEBUG=* istanbul cover tape \"test/*-test.js\"",
"bench": "node bench/redisbench.js",
"lint": "eslint index.js test lib bench"
}
"test": "nyc tape \"test/*-test.js\"",
"lint": "eslint index.js test lib",
"fmt": "prettier --write .",
"fmt-check": "prettier --check ."
},
"keywords": [
"connect",
"redis",
"session",
"express"
]
}

@@ -1,122 +0,177 @@

var P = require('bluebird');
var test = require('blue-tape');
var redisSrv = require('./redis-server');
var session = require('express-session');
var RedisStore = require('../')(session);
var redis = require('redis');
var ioRedis = require('ioredis');
const test = require("blue-tape")
const redisSrv = require("../test/redis-server")
const session = require("express-session")
const redisV3 = require("redis-v3")
const redisV4 = require("redis-v4")
const ioRedis = require("ioredis")
const redisMock = require("redis-mock")
// Takes a store through all the operations
function lifecycleTest (store, t) {
P.promisifyAll(store);
let RedisStore = require("../")(session)
return redisSrv.connect()
.then(function () {
return store.setAsync('123', { cookie: { maxAge: 2000 }, name: 'tj' });
let p =
(ctx, method) =>
(...args) =>
new Promise((resolve, reject) => {
ctx[method](...args, (err, d) => {
if (err) reject(err)
resolve(d)
})
})
.then(function (ok) {
t.equal(ok, 'OK', '#set() ok');
return store.getAsync('123');
})
.then(function (data) {
t.deepEqual({ cookie: { maxAge: 2000 }, name: 'tj' }, data, '#get() ok');
})
.then(function () {
return store.setAsync('123', { cookie: { maxAge: undefined }, name: 'tj' });
})
.then(function (ok) {
t.equal(ok, 'OK', '#set() no maxAge ok');
return store.destroyAsync('123');
})
.then(function (ok) {
t.equal(ok, 1, '#destroy() ok');
store.client.end();
return redisSrv.disconnect();
});
}
test('defaults', function (t) {
var store = new RedisStore();
t.equal(store.prefix, 'sess:', 'defaults to sess:');
t.notOk(store.ttl, 'ttl not set');
t.notOk(store.disableTTL, 'disableTTL not set');
t.ok(store.client, 'creates client');
test("setup", redisSrv.connect)
store.client.end();
t.end();
});
test("defaults", async (t) => {
t.throws(() => new RedisStore(), "client is required")
test('basic', function (t) {
t.throws(RedisStore, TypeError, 'constructor not callable as function');
var store = new RedisStore({ port: 8543 });
return lifecycleTest(store, t);
});
var client = redisV3.createClient(redisSrv.port, "localhost")
var store = new RedisStore({ client })
test('existing client', function (t) {
var client = redis.createClient(8543, 'localhost');
var store = new RedisStore({ client: client });
return lifecycleTest(store, t);
});
t.equal(store.client, client, "stores client")
t.equal(store.prefix, "sess:", "defaults to sess:")
t.equal(store.ttl, 86400, "defaults to one day")
t.equal(store.scanCount, 100, "defaults SCAN count to 100")
t.equal(store.serializer, JSON, "defaults to JSON serialization")
t.equal(store.disableTouch, false, "defaults to having `touch` enabled")
t.equal(store.disableTTL, false, "defaults to having `ttl` enabled")
client.end(false)
})
test('io redis client', function (t) {
var client = ioRedis.createClient(8543, 'localhost');
var store = new RedisStore({ client: client });
return lifecycleTest(store, t);
});
test("node_redis v3", async (t) => {
var client = redisV3.createClient(redisSrv.port, "localhost")
var store = new RedisStore({ client })
await lifecycleTest(store, t)
client.end(false)
})
test('options', function (t) {
var store = new RedisStore({
host: 'localhost',
port: 8543,
prefix: 'tobi',
ttl: 1000,
disableTTL: true,
db: 1,
unref: true,
pass: 'secret'
});
test("node_redis v4", async (t) => {
var client = redisV4.createClient({
url: `redis://localhost:${redisSrv.port}`,
legacyMode: true,
})
await client.connect()
var store = new RedisStore({ client })
await lifecycleTest(store, t)
await client.disconnect()
})
t.equal(store.prefix, 'tobi', 'uses provided prefix');
t.equal(store.ttl, 1000, 'ttl set');
t.ok(store.disableTTL, 'disableTTL set');
t.ok(store.client, 'creates client');
t.equal(store.client.address, 'localhost:8543', 'sets host and port');
test("ioredis", async (t) => {
var client = ioRedis.createClient(redisSrv.port, "localhost")
var store = new RedisStore({ client })
await lifecycleTest(store, t)
client.disconnect()
})
var socketStore = new RedisStore({ socket: 'word' });
t.equal(socketStore.client.address, 'word', 'sets socket address');
socketStore.client.end();
test("redis-mock client", async (t) => {
var client = redisMock.createClient()
var store = new RedisStore({ client })
await lifecycleTest(store, t)
})
var urlStore = new RedisStore({ url: 'redis://127.0.0.1:8888' });
t.equal(urlStore.client.address, '127.0.0.1:8888', 'sets url address');
urlStore.client.end();
test("teardown", redisSrv.disconnect)
var hostNoPort = new RedisStore({ host: 'host' });
t.equal(hostNoPort.client.address, 'host:6379', 'sets default port');
hostNoPort.client.end();
async function lifecycleTest(store, t) {
let res = await p(store, "set")("123", { foo: "bar" })
t.equal(res, "OK", "set value")
return lifecycleTest(store, t);
});
res = await p(store, "get")("123")
t.same(res, { foo: "bar" }, "get value")
test('interups', function (t) {
var store = P.promisifyAll(new RedisStore({ port: 8543, connect_timeout: 500 }));
return store.setAsync('123', { cookie: { maxAge: 2000 }, name: 'tj' })
.catch(function (er) {
t.ok(/broken/.test(er.message), 'failed connection');
store.client.end();
});
});
res = await p(store.client, "ttl")("sess:123")
t.ok(res >= 86399, "check one day ttl")
test('serializer', function (t) {
var serializer = {
stringify: function() { return 'XXX'+JSON.stringify.apply(JSON, arguments); },
parse: function(x) {
t.ok(x.match(/^XXX/));
return JSON.parse(x.substring(3));
let ttl = 60
let expires = new Date(Date.now() + ttl * 1000).toISOString()
res = await p(store, "set")("456", { cookie: { expires } })
t.equal(res, "OK", "set cookie expires")
res = await p(store.client, "ttl")("sess:456")
t.ok(res <= 60, "check expires ttl")
ttl = 90
let newExpires = new Date(Date.now() + ttl * 1000).toISOString()
// note: cookie.expires will not be updated on redis (see https://github.com/tj/connect-redis/pull/285)
res = await p(store, "touch")("456", { cookie: { expires: newExpires } })
t.equal(res, "OK", "set cookie expires touch")
res = await p(store.client, "ttl")("sess:456")
t.ok(res > 60, "check expires ttl touch")
res = await p(store, "length")()
t.equal(res, 2, "stored two keys length")
res = await p(store, "ids")()
res.sort()
t.same(res, ["123", "456"], "stored two keys ids")
res = await p(store, "all")()
res.sort((a, b) => (a.id > b.id ? 1 : -1))
t.same(
res,
[
{ id: "123", foo: "bar" },
{ id: "456", cookie: { expires } },
],
"stored two keys data"
)
res = await p(store, "destroy")("456")
t.equal(res, 1, "destroyed one")
res = await p(store, "length")()
t.equal(res, 1, "one key remains")
res = await p(store, "clear")()
t.equal(res, 1, "cleared remaining key")
res = await p(store, "length")()
t.equal(res, 0, "no key remains")
let count = 1000
await load(store, count)
res = await p(store, "length")()
t.equal(res, count, "bulk count")
res = await p(store, "clear")()
t.equal(res, count, "bulk clear")
expires = new Date(Date.now() + ttl * 1000).toISOString() // expires in the future
res = await p(store, "set")("789", { cookie: { expires } })
t.equal(res, "OK", "set value")
res = await p(store, "length")()
t.equal(res, 1, "one key exists (session 789)")
expires = new Date(Date.now() - ttl * 1000).toISOString() // expires in the past
res = await p(store, "set")("789", { cookie: { expires } })
t.equal(res, 1, "returns 1 because destroy was invoked")
res = await p(store, "length")()
t.equal(res, 0, "no key remains and that includes session 789")
}
function load(store, count) {
return new Promise((resolve, reject) => {
let set = (sid) => {
store.set(
"s" + sid,
{
cookie: { expires: new Date(Date.now() + 1000) },
data: "some data",
},
(err) => {
if (err) {
return reject(err)
}
if (sid === count) {
return resolve()
}
set(sid + 1)
}
)
}
};
t.equal(serializer.stringify('UnitTest'), 'XXX"UnitTest"');
t.equal(serializer.parse(serializer.stringify('UnitTest')), 'UnitTest');
var store = new RedisStore({ port: 8543, serializer: serializer });
return lifecycleTest(store, t);
});
set(1)
})
}

@@ -1,21 +0,21 @@

var P = require('bluebird');
var spawn = require('child_process').spawn;
var redisSrv;
var port = exports.port = 8543;
const spawn = require("child_process").spawn
const port = (exports.port = 18543)
let redisSrv
exports.connect = function () {
if (redisSrv) return P.resolve();
exports.connect = () =>
new Promise((resolve, reject) => {
redisSrv = spawn("redis-server", ["--port", port, "--loglevel", "notice"], {
stdio: "inherit",
})
redisSrv = spawn('redis-server', ['--port', port, '--loglevel', 'verbose'], { stdio: 'ignore' });
return P.delay(1000);
};
redisSrv.on("error", function (err) {
reject(new Error("Error caught spawning the server:" + err.message))
})
setTimeout(resolve, 1500)
})
exports.disconnect = function () {
if (!redisSrv) return P.resolve();
return new P(function (res) {
redisSrv.kill();
redisSrv.once('close', res);
redisSrv = null;
});
};
redisSrv.kill("SIGKILL")
return Promise.resolve()
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc