165 lines
4.3 KiB
JavaScript
165 lines
4.3 KiB
JavaScript
import { Store } from "express-session";
|
|
function optionalCb(err, data, cb) {
|
|
if (cb) return cb(err, data);
|
|
if (err) throw err;
|
|
return data;
|
|
}
|
|
class RedisStore extends Store {
|
|
client;
|
|
prefix;
|
|
scanCount;
|
|
serializer;
|
|
ttl;
|
|
disableTTL;
|
|
disableTouch;
|
|
constructor(opts) {
|
|
super();
|
|
this.prefix = opts.prefix == null ? "sess:" : opts.prefix;
|
|
this.scanCount = opts.scanCount || 100;
|
|
this.serializer = opts.serializer || JSON;
|
|
this.ttl = opts.ttl || 86400;
|
|
this.disableTTL = opts.disableTTL || false;
|
|
this.disableTouch = opts.disableTouch || false;
|
|
this.client = opts.client;
|
|
}
|
|
async get(sid, cb) {
|
|
let key = this.prefix + sid;
|
|
try {
|
|
let data = await this.client.get(key);
|
|
if (!data) return optionalCb(null, null, cb);
|
|
return optionalCb(null, await this.serializer.parse(data), cb);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
async set(sid, sess, cb) {
|
|
let key = this.prefix + sid;
|
|
let ttl = this.getTTL(sess);
|
|
try {
|
|
if (ttl > 0) {
|
|
let val = this.serializer.stringify(sess);
|
|
if (this.disableTTL) await this.client.set(key, val);
|
|
else
|
|
await this.client.set(key, val, {
|
|
expiration: { type: "EX", value: ttl }
|
|
});
|
|
return optionalCb(null, null, cb);
|
|
}
|
|
return this.destroy(sid, cb);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
async touch(sid, sess, cb) {
|
|
let key = this.prefix + sid;
|
|
if (this.disableTouch || this.disableTTL) return optionalCb(null, null, cb);
|
|
try {
|
|
await this.client.expire(key, this.getTTL(sess));
|
|
return optionalCb(null, null, cb);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
async destroy(sid, cb) {
|
|
let key = this.prefix + sid;
|
|
try {
|
|
await this.client.del([key]);
|
|
return optionalCb(null, null, cb);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
async clear(cb) {
|
|
try {
|
|
let keys = await this.getAllKeys();
|
|
if (!keys.length) return optionalCb(null, null, cb);
|
|
await this.client.del(keys);
|
|
return optionalCb(null, null, cb);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
async length(cb) {
|
|
try {
|
|
let keys = await this.getAllKeys();
|
|
return optionalCb(null, keys.length, cb);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
async ids(cb) {
|
|
let len = this.prefix.length;
|
|
try {
|
|
let keys = await this.getAllKeys();
|
|
return optionalCb(
|
|
null,
|
|
keys.map((k) => k.substring(len)),
|
|
cb
|
|
);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
async all(cb) {
|
|
let len = this.prefix.length;
|
|
try {
|
|
let keys = await this.getAllKeys();
|
|
if (keys.length === 0) return optionalCb(null, [], cb);
|
|
let data = await this.client.mGet(keys);
|
|
let results = data.reduce((acc, raw, idx) => {
|
|
if (!raw) return acc;
|
|
let sess = this.serializer.parse(raw);
|
|
sess.id = keys[idx].substring(len);
|
|
acc.push(sess);
|
|
return acc;
|
|
}, []);
|
|
return optionalCb(null, results, cb);
|
|
} catch (err) {
|
|
return optionalCb(err, null, cb);
|
|
}
|
|
}
|
|
getTTL(sess) {
|
|
if (typeof this.ttl === "function") {
|
|
return this.ttl(sess);
|
|
}
|
|
let ttl;
|
|
if (sess?.cookie?.expires) {
|
|
let ms = Number(new Date(sess.cookie.expires)) - Date.now();
|
|
ttl = Math.ceil(ms / 1e3);
|
|
} else {
|
|
ttl = this.ttl;
|
|
}
|
|
return ttl;
|
|
}
|
|
async getAllKeys() {
|
|
let pattern = this.prefix + "*";
|
|
let set = /* @__PURE__ */ new Set();
|
|
for await (let keys of this.scanIterator(pattern, this.scanCount)) {
|
|
for (let key of keys) {
|
|
set.add(key);
|
|
}
|
|
}
|
|
return set.size > 0 ? Array.from(set) : [];
|
|
}
|
|
scanIterator(match, count) {
|
|
let client = this.client;
|
|
if (!("masters" in client)) {
|
|
return client.scanIterator({ MATCH: match, COUNT: count });
|
|
}
|
|
return async function* () {
|
|
for (let master of client.masters) {
|
|
let c = await client.nodeClient(master);
|
|
for await (let keys of c.scanIterator({
|
|
COUNT: count,
|
|
MATCH: match
|
|
})) {
|
|
yield keys;
|
|
}
|
|
}
|
|
}();
|
|
}
|
|
}
|
|
export {
|
|
RedisStore
|
|
};
|