@roadmunk/connect-mongo
Advanced tools
Comparing version 2.0.1 to 2.0.2
{ | ||
"name": "@roadmunk/connect-mongo", | ||
"version": "2.0.1", | ||
"version": "2.0.2", | ||
"description": "MongoDB session store for Express and Connect", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -0,1 +1,7 @@ | ||
# Roadmunk Changes: | ||
- NOTE: the patch version is slightly higher than upstream due to some minor typo fixes. | ||
+ this should be fixed the next time we pull from upstream | ||
# connect-mongo | ||
@@ -2,0 +8,0 @@ |
478
src/index.js
@@ -1,4 +0,4 @@ | ||
'use strict' | ||
'use strict'; | ||
const MongoClient = require('mongodb') | ||
const MongoClient = require('mongodb'); | ||
@@ -10,6 +10,6 @@ function withCallback(promise, cb) { | ||
promise | ||
.then(res => cb(null, res)) | ||
.catch(cb) | ||
.then(res => cb(null, res)) | ||
.catch(cb); | ||
} | ||
return promise | ||
return promise; | ||
} | ||
@@ -19,226 +19,226 @@ | ||
// Copy each property of the session to a new object | ||
const obj = {} | ||
let prop | ||
const obj = {}; | ||
let prop; | ||
for (prop in session) { | ||
if (prop === 'cookie') { | ||
if (prop === 'cookie') { | ||
// Convert the cookie instance to an object, if possible | ||
// This gets rid of the duplicate object under session.cookie.data property | ||
obj.cookie = session.cookie.toJSON ? session.cookie.toJSON() : session.cookie | ||
} else { | ||
obj[prop] = session[prop] | ||
obj.cookie = session.cookie.toJSON ? session.cookie.toJSON() : session.cookie; | ||
} else { | ||
obj[prop] = session[prop]; | ||
} | ||
} | ||
} | ||
return obj | ||
} | ||
return obj; | ||
} | ||
function computeTransformFunctions(options, defaultStringify) { | ||
if (options.serialize || options.unserialize) { | ||
return { | ||
function computeTransformFunctions(options, defaultStringify) { | ||
if (options.serialize || options.unserialize) { | ||
return { | ||
serialize: options.serialize || defaultSerializeFunction, | ||
unserialize: options.unserialize || (x => x) | ||
} | ||
} | ||
unserialize: options.unserialize || (x => x) | ||
}; | ||
} | ||
if (options.stringify === false || defaultStringify === false) { | ||
return { | ||
if (options.stringify === false || defaultStringify === false) { | ||
return { | ||
serialize: defaultSerializeFunction, | ||
unserialize: x => x | ||
} | ||
} | ||
unserialize: x => x | ||
}; | ||
} | ||
if (options.stringify === true || defaultStringify === true) { | ||
return { | ||
if (options.stringify === true || defaultStringify === true) { | ||
return { | ||
serialize: JSON.stringify, | ||
unserialize: JSON.parse | ||
} | ||
} | ||
} | ||
unserialize: JSON.parse | ||
}; | ||
} | ||
} | ||
module.exports = function (connect) { | ||
const Store = connect.Store || connect.session.Store | ||
const MemoryStore = connect.MemoryStore || connect.session.MemoryStore | ||
module.exports = function (connect) { | ||
const Store = connect.Store || connect.session.Store; | ||
const MemoryStore = connect.MemoryStore || connect.session.MemoryStore; | ||
class MongoStore extends Store { | ||
constructor(options) { | ||
options = options || {} | ||
class MongoStore extends Store { | ||
constructor(options) { | ||
options = options || {}; | ||
/* Fallback */ | ||
if (options.fallbackMemory && MemoryStore) { | ||
return new MemoryStore() | ||
return new MemoryStore(); | ||
} | ||
super(options) | ||
super(options); | ||
/* Options */ | ||
this.ttl = options.ttl || 1209600 // 14 days | ||
this.collectionName = options.collection || 'sessions' | ||
this.autoRemove = options.autoRemove || 'native' | ||
this.autoRemoveInterval = options.autoRemoveInterval || 10 | ||
this.transformFunctions = computeTransformFunctions(options, true) | ||
this.ttl = options.ttl || 1209600; // 14 days | ||
this.collectionName = options.collection || 'sessions'; | ||
this.autoRemove = options.autoRemove || 'native'; | ||
this.autoRemoveInterval = options.autoRemoveInterval || 10; | ||
this.transformFunctions = computeTransformFunctions(options, true); | ||
this.options = options | ||
this.options = options; | ||
this.changeState('init') | ||
this.changeState('init'); | ||
const newConnectionCallback = (err, db) => { | ||
if (err) { | ||
this.connectionFailed(err) | ||
} else { | ||
this.handleNewConnectionAsync(db) | ||
const newConnectionCallback = (err, db) => { | ||
if (err) { | ||
this.connectionFailed(err); | ||
} else { | ||
this.handleNewConnectionAsync(db); | ||
} | ||
} | ||
}; | ||
if (options.url) { | ||
// New native connection using url + mongoOptions | ||
MongoClient.connect(options.url, options.mongoOptions || {}, newConnectionCallback) | ||
} else if (options.mongooseConnection) { | ||
// Re-use existing or upcoming mongoose connection | ||
if (options.mongooseConnection.readyState === 1) { | ||
this.handleNewConnectionAsync(options.mongooseConnection.db) | ||
} else { | ||
options.mongooseConnection.once('open', () => this.handleNewConnectionAsync(options.mongooseConnection.db)) | ||
} | ||
} else if (options.db && options.db.listCollections) { | ||
// Re-use existing or upcoming native connection | ||
if (options.url) { | ||
// New native connection using url + mongoOptions | ||
MongoClient.connect(options.url, options.mongoOptions || {}, newConnectionCallback); | ||
} else if (options.mongooseConnection) { | ||
// Re-use existing or upcoming mongoose connection | ||
if (options.mongooseConnection.readyState === 1) { | ||
this.handleNewConnectionAsync(options.mongooseConnection.db); | ||
} else { | ||
options.mongooseConnection.once('open', () => this.handleNewConnectionAsync(options.mongooseConnection.db)); | ||
} | ||
} else if (options.db && options.db.listCollections) { | ||
// Re-use existing or upcoming native connection | ||
if (options.db.openCalled || options.db.openCalled === undefined) { // OpenCalled is undefined in mongodb@2.x | ||
this.handleNewConnectionAsync(options.db) | ||
} else { | ||
options.db.open(newConnectionCallback) | ||
} | ||
} else if (options.dbPromise) { | ||
options.dbPromise | ||
.then(db => this.handleNewConnectionAsync(db)) | ||
.catch(err => this.connectionFailed(err)) | ||
} else { | ||
throw new Error('Connection strategy not found') | ||
} | ||
this.changeState('connecting') | ||
this.handleNewConnectionAsync(options.db); | ||
} else { | ||
options.db.open(newConnectionCallback); | ||
} | ||
} else if (options.dbPromise) { | ||
options.dbPromise | ||
.then(db => this.handleNewConnectionAsync(db)) | ||
.catch(err => this.connectionFailed(err)); | ||
} else { | ||
throw new Error('Connection strategy not found'); | ||
} | ||
connectionFailed(err) { | ||
this.changeState('disconnected') | ||
throw err | ||
} | ||
this.changeState('connecting'); | ||
} | ||
handleNewConnectionAsync(db) { | ||
this.db = db | ||
return this | ||
.setCollection(db.collection(this.collectionName)) | ||
.setAutoRemoveAsync() | ||
.then(() => this.changeState('connected')) | ||
} | ||
connectionFailed(err) { | ||
this.changeState('disconnected'); | ||
throw err; | ||
} | ||
setAutoRemoveAsync() { | ||
const removeQuery = {expires: {$lt: new Date()}} | ||
switch (this.autoRemove) { | ||
case 'native': | ||
return this.collection.createIndex({expires: 1}, {expireAfterSeconds: 0}) | ||
case 'interval': | ||
this.timer = setInterval(() => this.collection.remove(removeQuery, {w: 0}), this.autoRemoveInterval * 1000 * 60) | ||
this.timer.unref() | ||
return Promise.resolve() | ||
default: | ||
return Promise.resolve() | ||
} | ||
} | ||
handleNewConnectionAsync(db) { | ||
this.db = db; | ||
return this | ||
.setCollection(db.collection(this.collectionName)) | ||
.setAutoRemoveAsync() | ||
.then(() => this.changeState('connected')); | ||
} | ||
changeState(newState) { | ||
if (newState !== this.state) { | ||
this.state = newState | ||
this.emit(newState) | ||
} | ||
} | ||
setAutoRemoveAsync() { | ||
const removeQuery = {expires: {$lt: new Date()}}; | ||
switch (this.autoRemove) { | ||
case 'native': | ||
return this.collection.createIndex({expires: 1}, {expireAfterSeconds: 0}); | ||
case 'interval': | ||
this.timer = setInterval(() => this.collection.remove(removeQuery, {w: 0}), this.autoRemoveInterval * 1000 * 60); | ||
this.timer.unref(); | ||
return Promise.resolve(); | ||
default: | ||
return Promise.resolve(); | ||
} | ||
} | ||
setCollection(collection) { | ||
if (this.timer) { | ||
clearInterval(this.timer) | ||
} | ||
this.collectionReadyPromise = undefined | ||
this.collection = collection | ||
changeState(newState) { | ||
if (newState !== this.state) { | ||
this.state = newState; | ||
this.emit(newState); | ||
} | ||
} | ||
return this | ||
} | ||
setCollection(collection) { | ||
if (this.timer) { | ||
clearInterval(this.timer); | ||
} | ||
this.collectionReadyPromise = undefined; | ||
this.collection = collection; | ||
collectionReady() { | ||
let promise = this.collectionReadyPromise | ||
if (!promise) { | ||
promise = new Promise((resolve, reject) => { | ||
return this; | ||
} | ||
collectionReady() { | ||
let promise = this.collectionReadyPromise; | ||
if (!promise) { | ||
promise = new Promise((resolve, reject) => { | ||
if (this.state === 'connected') { | ||
return resolve(this.collection) | ||
return resolve(this.collection); | ||
} | ||
if (this.state === 'connecting') { | ||
return this.once('connected', () => resolve(this.collection)) | ||
} | ||
reject(new Error('Not connected')) | ||
}) | ||
this.collectionReadyPromise = promise | ||
} | ||
return promise | ||
} | ||
return this.once('connected', () => resolve(this.collection)); | ||
} | ||
reject(new Error('Not connected')); | ||
}); | ||
this.collectionReadyPromise = promise; | ||
} | ||
return promise; | ||
} | ||
computeStorageId(sessionId) { | ||
if (this.options.transformId && typeof this.options.transformId === 'function') { | ||
return this.options.transformId(sessionId) | ||
} | ||
return sessionId | ||
} | ||
computeStorageId(sessionId) { | ||
if (this.options.transformId && typeof this.options.transformId === 'function') { | ||
return this.options.transformId(sessionId); | ||
} | ||
return sessionId; | ||
} | ||
/* Public API */ | ||
/* Public API */ | ||
get(sid, callback) { | ||
get(sid, callback) { | ||
return withCallback(this.collectionReady() | ||
.then(collection => collection.findOne({ | ||
_id: this.computeStorageId(sid), | ||
$or: [ | ||
_id: this.computeStorageId(sid), | ||
$or: [ | ||
{expires: {$exists: false}}, | ||
{expires: {$gt: new Date()}} | ||
] | ||
})) | ||
.then(session => { | ||
if (session) { | ||
const s = this.transformFunctions.unserialize(session.session) | ||
if (this.options.touchAfter > 0 && session.lastModified) { | ||
s.lastModified = session.lastModified | ||
} | ||
this.emit('get', sid) | ||
return s | ||
} | ||
}) | ||
, callback) | ||
} | ||
})) | ||
.then(session => { | ||
if (session) { | ||
const s = this.transformFunctions.unserialize(session.session); | ||
if (this.options.touchAfter > 0 && session.lastModified) { | ||
s.lastModified = session.lastModified; | ||
} | ||
this.emit('get', sid); | ||
return s; | ||
} | ||
}) | ||
, callback); | ||
} | ||
set(sid, session, callback) { | ||
set(sid, session, callback) { | ||
// Removing the lastModified prop from the session object before update | ||
if (this.options.touchAfter > 0 && session && session.lastModified) { | ||
delete session.lastModified | ||
} | ||
if (this.options.touchAfter > 0 && session && session.lastModified) { | ||
delete session.lastModified; | ||
} | ||
session.rmlastModified = Date.now(); | ||
let s; | ||
session.rmlastModified = Date.now(); | ||
let s; | ||
try { | ||
s = { _id: this.computeStorageId(sid), session: this.transformFunctions.serialize(session), rmlastModified: session.rmlastModified }; | ||
} catch (err) { | ||
return callback(err) | ||
} | ||
try { | ||
s = { _id: this.computeStorageId(sid), session: this.transformFunctions.serialize(session), rmlastModified: session.rmlastModified }; | ||
} catch (err) { | ||
return callback(err); | ||
} | ||
if (session && session.cookie && session.cookie.expires) { | ||
s.expires = new Date(session.cookie.expires) | ||
} else { | ||
// If there's no expiration date specified, it is | ||
// browser-session cookie or there is no cookie at all, | ||
// as per the connect docs. | ||
// | ||
// So we set the expiration to two-weeks from now | ||
// - as is common practice in the industry (e.g Django) - | ||
// or the default specified in the options. | ||
s.expires = new Date(Date.now() + (this.ttl * 1000)) | ||
} | ||
if (session && session.cookie && session.cookie.expires) { | ||
s.expires = new Date(session.cookie.expires); | ||
} else { | ||
// If there's no expiration date specified, it is | ||
// browser-session cookie or there is no cookie at all, | ||
// as per the connect docs. | ||
// | ||
// So we set the expiration to two-weeks from now | ||
// - as is common practice in the industry (e.g Django) - | ||
// or the default specified in the options. | ||
s.expires = new Date(Date.now() + (this.ttl * 1000)); | ||
} | ||
if (this.options.touchAfter > 0) { | ||
s.lastModified = new Date() | ||
} | ||
if (this.options.touchAfter > 0) { | ||
s.lastModified = new Date(); | ||
} | ||
@@ -249,84 +249,84 @@ return withCallback(this.collectionReady() | ||
if (rawResponse.result) { | ||
rawResponse = rawResponse.result | ||
rawResponse = rawResponse.result; | ||
} | ||
if (rawResponse && rawResponse.upserted) { | ||
this.emit('create', sid) | ||
} else { | ||
this.emit('update', sid) | ||
} | ||
this.emit('set', sid) | ||
}) | ||
, callback) | ||
} | ||
if (rawResponse && rawResponse.upserted) { | ||
this.emit('create', sid); | ||
} else { | ||
this.emit('update', sid); | ||
} | ||
this.emit('set', sid); | ||
}) | ||
, callback); | ||
} | ||
touch(sid, session, callback) { | ||
const updateFields = {} | ||
const touchAfter = this.options.touchAfter * 1000 | ||
const lastModified = session.lastModified ? session.lastModified.getTime() : 0 | ||
const currentDate = new Date() | ||
touch(sid, session, callback) { | ||
const updateFields = {}; | ||
const touchAfter = this.options.touchAfter * 1000; | ||
const lastModified = session.lastModified ? session.lastModified.getTime() : 0; | ||
const currentDate = new Date(); | ||
// If the given options has a touchAfter property, check if the | ||
// current timestamp - lastModified timestamp is bigger than | ||
// the specified, if it's not, don't touch the session | ||
if (touchAfter > 0 && lastModified > 0) { | ||
const timeElapsed = currentDate.getTime() - session.lastModified | ||
// current timestamp - lastModified timestamp is bigger than | ||
// the specified, if it's not, don't touch the session | ||
if (touchAfter > 0 && lastModified > 0) { | ||
const timeElapsed = currentDate.getTime() - session.lastModified; | ||
if (timeElapsed < touchAfter) { | ||
return callback() | ||
} | ||
updateFields.lastModified = currentDate | ||
} | ||
if (timeElapsed < touchAfter) { | ||
return callback(); | ||
} | ||
updateFields.lastModified = currentDate; | ||
} | ||
if (session && session.cookie && session.cookie.expires) { | ||
updateFields.expires = new Date(session.cookie.expires) | ||
} else { | ||
updateFields.expires = new Date(Date.now() + (this.ttl * 1000)) | ||
} | ||
if (session && session.cookie && session.cookie.expires) { | ||
updateFields.expires = new Date(session.cookie.expires); | ||
} else { | ||
updateFields.expires = new Date(Date.now() + (this.ttl * 1000)); | ||
} | ||
let oldRmlastModified = session.rmlastModified; | ||
session.rmlastModified = Date.now(); | ||
let oldRmlastModified = session.rmlastModified; | ||
session.rmlastModified = Date.now(); | ||
try { | ||
Object.assign(updateFields, { session: this.transformFunctions.serialize(session), rmlastModified: session.rmlastModified }); | ||
} catch (err) { | ||
return callback(err); | ||
} | ||
return withCallback(this.collectionReady() | ||
.then(collection => collection.update({ _id: this.computeStorageId(sid), rmlastModified: oldRmlastModified }, { $set: updateFields })) | ||
.then(commandResult => { | ||
// silent failure | ||
if (!(commandResults.length > 0 && commandResults[0].result.nModified === 0)) { | ||
this.emit('touch', sid, session); | ||
} | ||
}) | ||
, callback) | ||
} | ||
try { | ||
Object.assign(updateFields, { session: this.transformFunctions.serialize(session), rmlastModified: session.rmlastModified }); | ||
} catch (err) { | ||
return callback(err); | ||
} | ||
return withCallback(this.collectionReady() | ||
.then(collection => collection.update({ _id: this.computeStorageId(sid), rmlastModified: oldRmlastModified }, { $set: updateFields })) | ||
.then(commandResults => { | ||
// silent failure | ||
if (!(commandResults.length > 0 && commandResults[0].result.nModified === 0)) { | ||
this.emit('touch', sid, session); | ||
} | ||
}) | ||
, callback); | ||
} | ||
destroy(sid, callback) { | ||
destroy(sid, callback) { | ||
return withCallback(this.collectionReady() | ||
.then(collection => collection.remove({_id: this.computeStorageId(sid)})) | ||
.then(() => this.emit('destroy', sid)) | ||
, callback) | ||
} | ||
.then(() => this.emit('destroy', sid)) | ||
, callback); | ||
} | ||
length(callback) { | ||
length(callback) { | ||
return withCallback(this.collectionReady() | ||
.then(collection => collection.count({})) | ||
, callback) | ||
} | ||
, callback); | ||
} | ||
clear(callback) { | ||
clear(callback) { | ||
return withCallback(this.collectionReady() | ||
.then(collection => collection.drop()) | ||
, callback) | ||
} | ||
, callback); | ||
} | ||
close() { | ||
if (this.db) { | ||
this.db.close() | ||
} | ||
} | ||
close() { | ||
if (this.db) { | ||
this.db.close(); | ||
} | ||
} | ||
} | ||
return MongoStore | ||
return MongoStore; | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
178967
10
255
285
2