connect-redis
Advanced tools
Comparing version 6.0.0 to 6.1.0
@@ -1,1 +0,1 @@ | ||
module.exports = require('./lib/connect-redis') | ||
module.exports = require("./lib/connect-redis") |
@@ -13,2 +13,3 @@ /*! | ||
const noop = () => {} | ||
const TOMBSTONE = "TOMBSTONE" | ||
@@ -19,6 +20,6 @@ class RedisStore extends Store { | ||
if (!options.client) { | ||
throw new Error('A client must be directly provided to the RedisStore') | ||
throw new Error("A client must be directly provided to the RedisStore") | ||
} | ||
this.prefix = options.prefix == null ? 'sess:' : options.prefix | ||
this.prefix = options.prefix == null ? "sess:" : options.prefix | ||
this.scanCount = Number(options.scanCount) || 100 | ||
@@ -32,3 +33,3 @@ this.serializer = options.serializer || JSON | ||
get(sid, cb = noop) { | ||
get(sid, cb = noop, showTombs = false) { | ||
let key = this.prefix + sid | ||
@@ -39,2 +40,3 @@ | ||
if (!data) return cb() | ||
if (data === TOMBSTONE) return cb(null, showTombs ? data : undefined) | ||
@@ -52,24 +54,36 @@ let result | ||
set(sid, sess, cb = noop) { | ||
let args = [this.prefix + sid] | ||
this.get( | ||
sid, | ||
(err, oldSess) => { | ||
if (oldSess === TOMBSTONE) { | ||
return cb() | ||
} else if (oldSess && oldSess.lastModified !== sess.lastModified) { | ||
sess = mergeDeep(oldSess, sess) | ||
} | ||
let args = [this.prefix + sid] | ||
let value | ||
sess.lastModified = Date.now() | ||
try { | ||
value = this.serializer.stringify(sess) | ||
} catch (er) { | ||
return cb(er) | ||
} | ||
args.push(value) | ||
args.push("EX", this._getTTL(sess)) | ||
let value | ||
try { | ||
value = this.serializer.stringify(sess) | ||
} catch (er) { | ||
return cb(er) | ||
} | ||
args.push(value) | ||
let ttl = 1 | ||
if (!this.disableTTL) { | ||
ttl = this._getTTL(sess) | ||
args.push("EX", ttl) | ||
} | ||
let ttl = 1 | ||
if (!this.disableTTL) { | ||
ttl = this._getTTL(sess) | ||
args.push('EX', ttl) | ||
} | ||
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) | ||
} | ||
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) | ||
} | ||
}, | ||
true | ||
) | ||
} | ||
@@ -82,4 +96,4 @@ | ||
if (err) return cb(err) | ||
if (ret !== 1) return cb(null, 'EXPIRED') | ||
cb(null, 'OK') | ||
if (ret !== 1) return cb(null, "EXPIRED") | ||
cb(null, "OK") | ||
}) | ||
@@ -90,3 +104,5 @@ } | ||
let key = this.prefix + sid | ||
this.client.del(key, cb) | ||
this.client.set([key, TOMBSTONE, "EX", 300], (err) => { | ||
cb(err, 1) | ||
}) | ||
} | ||
@@ -102,5 +118,5 @@ | ||
length(cb = noop) { | ||
this._getAllKeys((err, keys) => { | ||
this.all((err, result) => { | ||
if (err) return cb(err) | ||
return cb(null, keys.length) | ||
return cb(null, result.length) | ||
}) | ||
@@ -132,3 +148,3 @@ } | ||
result = sessions.reduce((accum, data, index) => { | ||
if (!data) return accum | ||
if (!data || data === TOMBSTONE) return accum | ||
data = this.serializer.parse(data) | ||
@@ -159,3 +175,3 @@ data.id = keys[index].substr(prefixLen) | ||
_getAllKeys(cb = noop) { | ||
let pattern = this.prefix + '*' | ||
let pattern = this.prefix + "*" | ||
this._scanKeys({}, 0, pattern, this.scanCount, cb) | ||
@@ -165,3 +181,3 @@ } | ||
_scanKeys(keys = {}, cursor, pattern, count, cb = noop) { | ||
let args = [cursor, 'match', pattern, 'count', count] | ||
let args = [cursor, "match", pattern, "count", count] | ||
this.client.scan(args, (err, data) => { | ||
@@ -187,1 +203,33 @@ if (err) return cb(err) | ||
} | ||
/** | ||
* Simple object check. | ||
* @param item | ||
* @returns {boolean} | ||
*/ | ||
function isObject(item) { | ||
return item && typeof item === "object" && !Array.isArray(item) | ||
} | ||
/** | ||
* Deep merge two objects. | ||
* @param target | ||
* @param ...sources | ||
*/ | ||
function mergeDeep(target, ...sources) { | ||
if (!sources.length) return target | ||
const source = sources.shift() | ||
if (isObject(target) && isObject(source)) { | ||
for (const key in source) { | ||
if (isObject(source[key])) { | ||
if (!target[key]) Object.assign(target, { [key]: {} }) | ||
mergeDeep(target[key], source[key]) | ||
} else { | ||
Object.assign(target, { [key]: source[key] }) | ||
} | ||
} | ||
} | ||
return mergeDeep(target, ...sources) | ||
} |
{ | ||
"name": "connect-redis", | ||
"description": "Redis session store for Connect", | ||
"version": "6.0.0", | ||
"version": "6.1.0", | ||
"author": "TJ Holowaychuk <tj@vision-media.ca>", | ||
@@ -21,6 +21,8 @@ "contributors": [ | ||
"ioredis": "^4.17.1", | ||
"mockdate": "^2.0.5", | ||
"nyc": "^15.0.1", | ||
"prettier": "^2.0.5", | ||
"redis": "^3.1.2", | ||
"redis-mock": "^0.56.3" | ||
"redis-mock": "^0.56.3", | ||
"redis-v3": "npm:redis@3", | ||
"redis-v4": "npm:redis@4" | ||
}, | ||
@@ -36,5 +38,5 @@ "engines": { | ||
"lint": "eslint index.js test lib", | ||
"fmt": "prettier --write \"**/*.{js,md,json,*rc}\"", | ||
"fmt-check": "prettier --check \"**/*.{js,md,json,*rc}\"" | ||
"fmt": "prettier --write .", | ||
"fmt-check": "prettier --check ." | ||
} | ||
} |
@@ -5,4 +5,2 @@ ![Build Status](https://github.com/tj/connect-redis/workflows/build/badge.svg?branch=master) [![npm](https://img.shields.io/npm/v/connect-redis.svg)](https://npmjs.com/package/connect-redis) [![code-style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://gitter.im/jlongster/prettier) ![Downloads](https://img.shields.io/npm/dm/connect-redis.svg) | ||
**Migrating to V4?** See [this guide](migration-to-v4.md) on what's changed. | ||
## Installation | ||
@@ -25,8 +23,18 @@ | ||
```js | ||
const redis = require('redis') | ||
const session = require('express-session') | ||
const session = require("express-session") | ||
let RedisStore = require("connect-redis")(session) | ||
let RedisStore = require('connect-redis')(session) | ||
let redisClient = redis.createClient() | ||
// redis@v4 | ||
const { createClient } = require("redis") | ||
let redisClient = createClient({ legacyMode: true }) | ||
redisClient.connect().catch(console.error) | ||
// redis@v3 | ||
const { createClient } = require("redis") | ||
let redisClient = createClient() | ||
// ioredis | ||
const Redis = require("ioredis") | ||
let redisClient = new Redis() | ||
app.use( | ||
@@ -36,3 +44,3 @@ session({ | ||
saveUninitialized: false, | ||
secret: 'keyboard cat', | ||
secret: "keyboard cat", | ||
resave: false, | ||
@@ -55,3 +63,3 @@ }) | ||
- [redis][1] | ||
- [redis][1] (v3, v4 with `legacyMode: true`) | ||
- [ioredis](https://github.com/luin/ioredis) | ||
@@ -112,3 +120,3 @@ - [redis-mock](https://github.com/yeahoffline/redis-mock) for testing. | ||
```js | ||
client.on('error', console.error) | ||
client.on("error", console.error) | ||
``` | ||
@@ -124,3 +132,3 @@ | ||
if (!req.session) { | ||
return next(new Error('oh no')) // handle error | ||
return next(new Error("oh no")) // handle error | ||
} | ||
@@ -127,0 +135,0 @@ next() // otherwise continue |
@@ -1,9 +0,12 @@ | ||
const test = require('blue-tape') | ||
const redisSrv = require('../test/redis-server') | ||
const session = require('express-session') | ||
const redis = require('redis') | ||
const ioRedis = require('ioredis') | ||
const redisMock = require('redis-mock') | ||
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") | ||
const MockDate = require("mockdate") | ||
let RedisStore = require('../')(session) | ||
let RedisStore = require("../")(session) | ||
MockDate.set("2000-11-22") | ||
@@ -20,22 +23,22 @@ let p = | ||
test('setup', redisSrv.connect) | ||
test("setup", redisSrv.connect) | ||
test('defaults', async (t) => { | ||
t.throws(() => new RedisStore(), 'client is required') | ||
test("defaults", async (t) => { | ||
t.throws(() => new RedisStore(), "client is required") | ||
var client = redis.createClient(redisSrv.port, 'localhost') | ||
var client = redisV3.createClient(redisSrv.port, "localhost") | ||
var store = new RedisStore({ client }) | ||
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') | ||
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('node_redis', async (t) => { | ||
var client = redis.createClient(redisSrv.port, 'localhost') | ||
test("node_redis v3", async (t) => { | ||
var client = redisV3.createClient(redisSrv.port, "localhost") | ||
var store = new RedisStore({ client }) | ||
@@ -46,10 +49,21 @@ await lifecycleTest(store, t) | ||
test('ioredis', async (t) => { | ||
var client = ioRedis.createClient(redisSrv.port, 'localhost') | ||
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() | ||
}) | ||
test("ioredis", async (t) => { | ||
var client = ioRedis.createClient(redisSrv.port, "localhost") | ||
var store = new RedisStore({ client }) | ||
await lifecycleTest(store, t) | ||
client.disconnect() | ||
}) | ||
test('redis-mock client', async (t) => { | ||
test("redis-mock client", async (t) => { | ||
var client = redisMock.createClient() | ||
@@ -60,21 +74,44 @@ var store = new RedisStore({ client }) | ||
test('teardown', redisSrv.disconnect) | ||
test("teardown", redisSrv.disconnect) | ||
async function lifecycleTest(store, t) { | ||
let res = await p(store, 'set')('123', { foo: 'bar' }) | ||
t.equal(res, 'OK', 'set value') | ||
await p(store, "set")("123", { foo: "bar3" }) | ||
let res = await p(store, "get")("123") | ||
t.same(res, { foo: "bar3", lastModified: 974851200000 }, "get value 1") | ||
await p(store, "set")("123", { | ||
foo: "bar3", | ||
luke: "skywalker", | ||
obi: "wan", | ||
lastModified: 974851000000, | ||
}) | ||
await p(store, "set")("123", { | ||
luke: "skywalker", | ||
lastModified: 974851000000, | ||
}) | ||
res = await p(store, "get")("123") | ||
t.same( | ||
res, | ||
{ foo: "bar3", luke: "skywalker", obi: "wan", lastModified: 974851200000 }, | ||
"get merged value" | ||
) | ||
res = await p(store, 'get')('123') | ||
t.same(res, { foo: 'bar' }, 'get value') | ||
res = await p(store, "clear")() | ||
t.ok(res >= 1, "cleared key") | ||
res = await p(store.client, 'ttl')('sess:123') | ||
t.ok(res >= 86399, 'check one day ttl') | ||
res = await p(store, "set")("123", { foo: "bar" }) | ||
t.equal(res, "OK", "set value") | ||
res = await p(store, "get")("123") | ||
t.same(res, { foo: "bar", lastModified: 974851200000 }, "get value") | ||
res = await p(store.client, "ttl")("sess:123") | ||
t.ok(res >= 86399, "check one day ttl") | ||
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, "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') | ||
res = await p(store.client, "ttl")("sess:456") | ||
t.ok(res <= 60, "check expires ttl") | ||
@@ -84,16 +121,16 @@ ttl = 90 | ||
// 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, "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.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, "length")() | ||
t.equal(res, 2, "stored two keys length") | ||
res = await p(store, 'ids')() | ||
res = await p(store, "ids")() | ||
res.sort() | ||
t.same(res, ['123', '456'], 'stored two keys ids') | ||
t.same(res, ["123", "456"], "stored two keys ids") | ||
res = await p(store, 'all')() | ||
res = await p(store, "all")() | ||
res.sort((a, b) => (a.id > b.id ? 1 : -1)) | ||
@@ -103,42 +140,51 @@ t.same( | ||
[ | ||
{ id: '123', foo: 'bar' }, | ||
{ id: '456', cookie: { expires } }, | ||
{ id: "123", foo: "bar", lastModified: 974851200000 }, | ||
{ id: "456", cookie: { expires }, lastModified: 974851200000 }, | ||
], | ||
'stored two keys data' | ||
"stored two keys data" | ||
) | ||
res = await p(store, 'destroy')('456') | ||
t.equal(res, 1, 'destroyed one') | ||
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, "get")("456") | ||
t.equal(res, undefined, "tombstoned one") | ||
res = await p(store, 'clear')() | ||
t.equal(res, 1, 'cleared remaining key') | ||
res = await p(store, "set")("456", { a: "new hope" }) | ||
t.equal(res, undefined, "tombstoned set") | ||
res = await p(store, 'length')() | ||
t.equal(res, 0, 'no key remains') | ||
res = await p(store, "get")("456") | ||
t.equal(res, undefined, "tombstoned two") | ||
res = await p(store, "length")() | ||
t.equal(res, 1, "one key remains") | ||
res = await p(store, "clear")() | ||
t.equal(res, 2, "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, "length")() | ||
t.equal(res, count, "bulk count") | ||
res = await p(store, 'clear')() | ||
t.equal(res, count, 'bulk clear') | ||
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, "set")("789", { cookie: { expires } }) | ||
t.equal(res, "OK", "set value") | ||
res = await p(store, 'length')() | ||
t.equal(res, 1, 'one key exists (session 789)') | ||
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, "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') | ||
res = await p(store, "length")() | ||
t.equal(res, 0, "no key remains and that includes session 789") | ||
} | ||
@@ -150,6 +196,6 @@ | ||
store.set( | ||
's' + sid, | ||
"s" + sid, | ||
{ | ||
cookie: { expires: new Date(Date.now() + 1000) }, | ||
data: 'some data', | ||
data: "some data", | ||
}, | ||
@@ -156,0 +202,0 @@ (err) => { |
@@ -1,2 +0,2 @@ | ||
const spawn = require('child_process').spawn | ||
const spawn = require("child_process").spawn | ||
const port = (exports.port = 18543) | ||
@@ -7,8 +7,8 @@ let redisSrv | ||
new Promise((resolve, reject) => { | ||
redisSrv = spawn('redis-server', ['--port', port, '--loglevel', 'notice'], { | ||
stdio: 'inherit', | ||
redisSrv = spawn("redis-server", ["--port", port, "--loglevel", "notice"], { | ||
stdio: "inherit", | ||
}) | ||
redisSrv.on('error', function (err) { | ||
reject(new Error('Error caught spawning the server:' + err.message)) | ||
redisSrv.on("error", function (err) { | ||
reject(new Error("Error caught spawning the server:" + err.message)) | ||
}) | ||
@@ -20,4 +20,4 @@ | ||
exports.disconnect = function () { | ||
redisSrv.kill('SIGKILL') | ||
redisSrv.kill("SIGKILL") | ||
return Promise.resolve() | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
21344
381
136
11
13
1