sessions
Version:
NodeJS session management
304 lines (248 loc) • 6.57 kB
JavaScript
var util = require("util"),
events = require("events"),
fs = require("fs"),
path = require("path"),
sessionStores = {};
function trim(str) {
return str.replace(/^\s+/g, "").replace(/\s+$/g, "");
};
function Sessions(store, opts, storeOpts) {
events.EventEmitter.call(this);
this.opts = opts || {};
this.store = (typeof store != "undefined" && store !== null ? new store(storeOpts || {}) : new module.exports.stores.memory(storeOpts || {}));
this._currentSessions = 0;
this._checkTimeoutId = null;
if (!this.opts.hasOwnProperty("expires")) {
this.opts.expires = 300;
}
if (!this.opts.hasOwnProperty("domain")) {
this.opts.domain = null;
}
if (!this.opts.hasOwnProperty("path")) {
this.opts.path = "/";
}
}
util.inherits(Sessions, events.EventEmitter);
Sessions.prototype.total = function () {
return this._currentSessions;
};
Sessions.prototype.get = function (uid, cb) {
var sessions = this;
this.store.get(uid, function (err, meta, data) {
if (err) {
return cb(err);
}
return cb(null, buildSessionUtils(uid, meta, data, sessions.store, sessions.opts.expires, sessions), sessions);
});
return this;
};
Sessions.prototype.create = function () {
var sessions = this,
uid = null,
data = {},
meta = { expires: (this.opts.expires !== null ? Date.now() + (this.opts.expires * 1000) : null) },
cb = null;
for (var i = 0; i < arguments.length; i++) {
switch (typeof arguments[i]) {
case "string":
uid = arguments[i];
break;
case "object":
data = arguments[i];
break;
case "function":
cb = arguments[i];
break;
}
}
if (uid === null) {
uid = this.uid();
}
this.store.add(uid, meta, data, function (err, data) {
if (err) {
return cb(err);
}
if (meta.expires !== null && sessions._checkTimeoutId === null) {
sessions._checkTimeoutId = setTimeout(function () {
sessions.checkExpiration();
}, (sessions.opts.expires + 1) * 1000);
}
return cb(null, buildSessionUtils(uid, meta, data, sessions.store, sessions.opts.expires, sessions), sessions);
});
};
Sessions.prototype.uid = function () {
return String(Math.random()).substr(2) + String(Math.random()).substr(2);
};
Sessions.prototype.httpRequest = function (req, res, data, cb) {
var cookies = parseCookies(req),
sessions = this;
if (typeof data == "function") {
cb = data;
}
if (!cookies.hasOwnProperty("uid")) {
return this.create(data || {}, function (err, session) {
if (err) {
return cb(err);
}
setHttpCookie(res, "uid", session.uid(), session.expires(), sessions.opts.path, sessions.opts.domain);
return cb(null, session, sessions);
});
}
this.get(cookies.uid, function (err, session) {
if (err) {
return sessions.create(data || {}, function (err, session) {
if (err) {
return cb(err);
}
setHttpCookie(res, "uid", session.uid(), session.expires(), sessions.opts.path, sessions.opts.domain);
return cb(null, session);
});
} else {
return cb(null, session, sessions);
}
});
return this;
};
Sessions.prototype.checkExpiration = function () {
var sessions = this,
conf = { dt: Date.now(), next: null };
this.store.uids(function (err, uids) {
if (err) {
return;
}
var sess, i = 0, l = uids.length, missing = l;
sessions.emit("expirecheck");
for (; i < l; i++) {
checkSessionExpiration(sessions, uids[i], conf, function () {
missing -= 1;
if (missing === 0) {
if (conf.next !== null) {
i = (conf.next - Date.now()) / 1000;
sessions._checkTimeoutId = setTimeout(function () {
sessions.checkExpiration();
}, (i > 0 ? (i + 1) : 10) * 1000);
}
}
});
}
});
};
initStores();
module.exports = Sessions;
module.exports.stores = sessionStores;
module.exports.parseCookies = parseCookies;
function initStores() {
var stores, store, i = 0;
try {
stores = fs.readdirSync(__dirname + "/store/");
for (; i < stores.length; i++) {
store = path.basename(stores[i], path.extname(stores[i]));
try {
sessionStores[store] = require(__dirname + "/store/" + stores[i]);
} catch (ex) {
}
}
} catch (e) {
throw e;
}
}
function parseCookies(req) {
var cookies = {};
req.headers.cookie && req.headers.cookie.split(";").forEach(function (param) {
var part = param.split("=", 2);
cookies[trim(part[0].toLowerCase())] = trim(part[1]) || true;
});
return cookies;
}
function checkSessionExpiration(sessions, uid, conf, cb) {
sessions.store.get(uid, function (err, sess) {
if (err) return cb();
if (sess.expires === null) {
return cb();
}
if (sess.expires <= conf.dt) {
sessions.emit("expired", uid, false);
sessions.store.remove(uid);
return cb();
}
if (conf.next === null) {
conf.next = sess.expires;
return cb();
}
if (sess.expires < conf.next) {
conf.next = sess.expires;
}
return cb();
});
}
function setHttpCookie(res, key, value, expires, path, domain) {
var cookie = [], dt;
cookie.push(key + "=" + value);
if (domain !== null) {
cookie.push("domain=" + domain);
}
if (path !== null) {
cookie.push("path=" + path);
}
if (expires !== null) {
if (util.isDate(expires)) {
dt = expires;
} else {
dt = new Date();
dt.setTime(expires);
}
cookie.push("expires=" + dt.toGMTString());
}
res.setHeader("Set-cookie", cookie.join("; "));
}
function buildSessionUtils(uid, meta, data, store, expires, sessions) {
return {
get: function (key) {
if (typeof key == "undefined") {
return data;
}
return data[key] || null;
},
set: function (key, value, cb) {
if (typeof key == "object") {
cb = value;
store.set(uid, {}, key, cb);
} else {
var o = {};
o[key] = value;
data[key] = value;
store.set(uid, {}, o, cb);
}
return this;
},
remove: function () {
var args = [ uid ].concat(Array.prototype.slice.apply(arguments));
for (var i = 1; i < args.length; i++) {
if (typeof args[i] == "string") {
delete data[args[i]];
}
}
if (typeof args[args.length - 1] != "function") {
args.push(function () {});
}
store.remove.apply(store, args);
return this;
},
refresh: function (expire) {
meta.expires = Date.now() + ((expire || expires) * 1000);
store.set(uid, meta, {});
return this;
},
expire: function () {
store.remove(uid);
sessions.emit("expired", uid, true);
return this;
},
uid: function () {
return uid;
},
expires: function () {
return meta.expires;
}
};
}