roosevelt
Version:
🧸 MVC web framework for Node.js designed to make Express easier to use.
212 lines (181 loc) • 4.61 kB
JavaScript
// this is a hard fork of better-sqlite3-session-store: https://github.com/attestate/better-sqlite3-session-store
// this fork includes the changes from this PR: https://github.com/attestate/better-sqlite3-session-store/pull/12
const noop = () => {}
const oneDay = 86400000
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,
unrefInterval: (options.expired && options.expired.unrefInterval) ||
false
}
this.client = options.client
this.createDb()
if (this.expired.clear) {
this.startInterval()
}
}
startInterval () {
const timeout = setInterval(
this.clearExpiredSessions.bind(this),
this.expired.intervalMs
)
if (this.expired.unrefInterval) {
timeout.unref()
}
}
clearExpiredSessions () {
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
// express' temporal unit of choice is milliseconds: https://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
(
,
,
)
`)
.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 = 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 =
WHERE 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
}