better-sqlite3-session-store
Version:
A session store for express-session.
231 lines (199 loc) • 4.51 kB
JavaScript
// @format
const noop = () => {};
// NOTE: 1d = 86400s = 86400000ms
const oneDay = 86400000;
// NOTE: In Milliseconds
const clearExpiredInterval = 900000;
const tableName = "sessions";
const schema = `
CREATE TABLE IF NOT EXISTS ${tableName}
(
sid TEXT NOT NULL PRIMARY KEY,
sess JSON NOT NULL,
expire TEXT NOT NULL
)
`;
module.exports = ({ Store }) => {
class SqliteStore extends Store {
constructor(options = {}) {
super(options);
if (!options.client) {
throw new Error("A client must be directly provided to SqliteStore");
}
this.expired = {
clear: (options.expired && options.expired.clear) || true,
intervalMs:
(options.expired && options.expired.intervalMs) ||
clearExpiredInterval,
};
this.client = options.client;
this.createDb();
if (this.expired.clear) {
this.startInterval();
}
}
startInterval() {
setInterval(
this.clearExpiredSessions.bind(this),
this.expired.intervalMs
);
}
clearExpiredSessions() {
let res;
try {
this.client
.prepare(
`
DELETE FROM ${tableName} WHERE datetime('now') > datetime(expire)
`
)
.run();
} catch (err) {
console.error(err);
}
}
createDb() {
this.client.exec(schema);
}
set(sid, sess, cb = noop) {
let age;
// NOTE: Express's temporal unit of choice is milliseconds:
// http://expressjs.com/en/resources/middleware/session.html#:~:text=cookie.maxAge
if (sess.cookie && sess.cookie.maxAge) {
age = sess.cookie.maxAge;
} else {
age = oneDay;
}
const now = new Date().getTime();
const expire = new Date(now + age).toISOString();
const entry = { sid, sess: JSON.stringify(sess), expire };
let res;
try {
res = this.client
.prepare(
`
INSERT OR REPLACE INTO
${tableName}
VALUES
(
@sid,
@sess,
@expire
)
`
)
.run(entry);
} catch (err) {
cb(err);
return;
}
cb(null, res);
}
get(sid, cb = noop) {
let res;
try {
res = this.client
.prepare(
`
SELECT sess
FROM ${tableName}
WHERE sid = @sid AND datetime('now') < datetime(expire)
`
)
.get({ sid });
} catch (err) {
cb(err);
return;
}
if (res && res.sess) {
cb(null, JSON.parse(res.sess));
} else {
cb(null, null);
}
}
destroy(sid, cb = noop) {
let res;
try {
res = this.client
.prepare(
`
DELETE FROM ${tableName} WHERE sid = ?
`
)
.run(sid);
} catch (err) {
cb(err);
return;
}
cb(null, res);
}
length(cb = noop) {
let res;
try {
res = this.client
.prepare(
`
SELECT COUNT(*) as count FROM ${tableName}
`
)
.get();
} catch (err) {
cb(err);
return;
}
cb(null, res.count);
}
clear(cb = noop) {
let res;
try {
res = this.client.prepare(`DELETE FROM ${tableName}`).run();
} catch (err) {
cb(err);
return;
}
cb(null, res);
}
touch(sid, sess, cb = noop) {
const entry = { sid };
if (sess && sess.cookie && sess.cookie.expires) {
entry.expire = new Date(sess.cookie.expires).toISOString();
} else {
const now = new Date().getTime();
entry.expire = new Date(now + oneDay).toISOString();
}
let res;
try {
res = this.client
.prepare(
`
UPDATE ${tableName}
SET expire = @expire
WHERE sid = @sid AND datetime('now') < datetime(expire)
`
)
.run(entry);
} catch (err) {
cb(err);
return;
}
cb(null, res);
}
all(cb = noop) {
let res;
try {
res = this.client
.prepare(
`
SELECT * FROM ${tableName}
`
)
.all();
} catch (err) {
cb(err);
return;
}
cb(null, res);
}
}
return SqliteStore;
};