const path = require("path");
const nunjucks = require("nunjucks");
+const FileStore = require("./session-store");
+
const DATA_DIR = path.join(__dirname, "data");
const STATE_FILE = path.join(DATA_DIR, "lmno-state.json");
const STATS_FILE = path.join(DATA_DIR, "lmno-stats.jsonl");
+const SESSIONS_FILE = path.join(DATA_DIR, "lmno-sessions.json");
const RETIREMENT_AGE_MS = 24 * 60 * 60 * 1000;
try {
app.use(cors());
app.use(body_parser.urlencoded({ extended: false }));
app.use(body_parser.json());
+/* Ensure data directory exists before creating session store. */
+if (!fs.existsSync(DATA_DIR)) {
+ fs.mkdirSync(DATA_DIR);
+}
+
app.use(session({
secret: lmno_config.session_secret,
resave: false,
- saveUninitialized: false
+ saveUninitialized: false,
+ store: new FileStore(SESSIONS_FILE)
}));
const njx = nunjucks.configure("templates", {
--- /dev/null
+const session = require("express-session");
+const fs = require("fs");
+
+/* A simple file-backed session store for express-session.
+ *
+ * Sessions are kept in memory for fast access and written to a JSON
+ * file on modification (which is infrequent — just nickname and login
+ * changes, not every request, since express-session is configured with
+ * resave: false). Atomic writes (tmp file + rename) prevent corruption.
+ */
+class FileStore extends session.Store {
+ constructor(filepath) {
+ super();
+ this.filepath = filepath;
+ this.sessions = {};
+ this._load();
+ }
+
+ _load() {
+ try {
+ const data = fs.readFileSync(this.filepath, 'utf8');
+ this.sessions = JSON.parse(data);
+ const count = Object.keys(this.sessions).length;
+ console.log(`Restored ${count} session(s) from ${this.filepath}`);
+ } catch (e) {
+ /* File doesn't exist yet — start with an empty store. */
+ this.sessions = {};
+ }
+ }
+
+ _save() {
+ const tmp = this.filepath + '.tmp';
+ fs.writeFileSync(tmp, JSON.stringify(this.sessions));
+ fs.renameSync(tmp, this.filepath);
+ }
+
+ get(sid, callback) {
+ const sess = this.sessions[sid];
+ callback(null, sess ? JSON.parse(sess) : null);
+ }
+
+ set(sid, sess, callback) {
+ this.sessions[sid] = JSON.stringify(sess);
+ this._save();
+ callback(null);
+ }
+
+ destroy(sid, callback) {
+ delete this.sessions[sid];
+ this._save();
+ callback(null);
+ }
+}
+
+module.exports = FileStore;