Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
unreleased
==========

* Support promises in `session.save()`, `session.reload()`, `session.destroy()`
and `session.regenerate()` when called without a callback
- `save()` resolves to the session it was called on, `reload()` and
`regenerate()` resolve to the new `req.session`, and `destroy()`
resolves to `undefined`
* Replace `uid-safe` dependency with built-in `crypto.randomBytes` for session ID generation
- Session IDs keep the same format as before (32-character base64url strings)

Expand Down
90 changes: 86 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,41 +366,102 @@ app.get('/', function(req, res, next) {
})
```

#### Session.regenerate(callback)
#### Session.regenerate(callback) => Promise

To regenerate the session simply invoke the method. Once complete,
a new SID and `Session` instance will be initialized at `req.session`
and the `callback` will be invoked.

When called without a callback, a `Promise` is returned instead, which
resolves to the newly created session.

```js
req.session.regenerate(function(err) {
// will have a new session here
})

// or promises
req.session.regenerate().then(function(session) {
// session is the new req.session
}).catch(function(err) {
// a problem...
})

// or async/await
(async function() {
try {
// resolves to the new req.session
await req.session.regenerate()
} catch(err) {
// a problem...
}
})()
```

#### Session.destroy(callback)
#### Session.destroy(callback) => Promise

Destroys the session and will unset the `req.session` property.
Once complete, the `callback` will be invoked.

When called without a callback, a `Promise` is returned instead, which
resolves to `undefined` once the session is destroyed.

```js
req.session.destroy(function(err) {
// cannot access session here
})

// or promises
req.session.destroy().then(function() {
// cannot access session here
}).catch(function(err) {
// a problem...
})

// or async/await
(async function() {
try {
await req.session.destroy()
// cannot access session here
} catch(err) {
// a problem...
}
})()
```

#### Session.reload(callback)
#### Session.reload(callback) => Promise

Reloads the session data from the store and re-populates the
`req.session` object. Once complete, the `callback` will be invoked.

When called without a callback, a `Promise` is returned instead, which
resolves to the reloaded session: a new `Session` object at `req.session`
representing the same session.

```js
req.session.reload(function(err) {
// session updated
})

// or promises
req.session.reload().then(function(session) {
// session is the reloaded req.session
}).catch(function(err) {
// a problem...
})

// or async/await
(async function() {
try {
// resolves to the reloaded req.session
await req.session.reload()
} catch(err) {
// a problem...
}
})()
```

#### Session.save(callback)
#### Session.save(callback) => Promise

Save the session back to the store, replacing the contents on the store with the
contents in memory (though a store may do something else--consult the store's
Expand All @@ -414,10 +475,31 @@ does not need to be called.
There are some cases where it is useful to call this method, for example,
redirects, long-lived requests or in WebSockets.

When called without a callback, a `Promise` is returned instead, which
resolves to the session the method was called on — even if `req.session`
has since been replaced (for example by `regenerate()` or `reload()`).

```js
req.session.save(function(err) {
// session saved
})

// or promises
req.session.save().then(function(session) {
// session saved; session is the one save() was called on
}).catch(function(err) {
// a problem...
})

// or async/await
(async function() {
try {
// resolves to the session save() was called on
await req.session.save()
} catch(err) {
// a problem...
}
})()
```

#### Session.touch()
Expand Down
8 changes: 6 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,13 +384,17 @@ function session(options) {

function reload(callback) {
debug('reloading %s', this.id)
_reload.call(this, rewrapmethods(this, callback))
if (typeof callback === 'function') {
return _reload.call(this, rewrapmethods(this, callback))
}

return _reload.call(this).finally(rewrapmethods(this, function () {}))
}

function save() {
debug('saving %s', this.id);
savedHash = hash(this);
_save.apply(this, arguments);
return _save.apply(this, arguments);
}

Object.defineProperty(sess, 'reload', {
Expand Down
83 changes: 60 additions & 23 deletions session/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,18 @@ defineMethod(Session.prototype, 'resetMaxAge', function resetMaxAge() {
/**
* Save the session data with optional callback `fn(err)`.
*
* @param {Function} fn
* @return {Session} for chaining
* @param {Function} [fn]
* @return {Session|Promise} for chaining
* @api public
*/

defineMethod(Session.prototype, 'save', function save(fn) {
this.req.sessionStore.set(this.id, this, fn || function(){});
return this;
var self = this;
var store = this.req.sessionStore;

return callbackOrPromise(this, fn, function (done) {
store.set(self.id, self, done);
}, self);
});

/**
Expand All @@ -80,49 +84,60 @@ defineMethod(Session.prototype, 'save', function save(fn) {
* `req.session` property will be a new `Session` object,
* although representing the same session.
*
* @param {Function} fn
* @return {Session} for chaining
* @param {Function} [fn]
* @return {Session|Promise} for chaining
* @api public
*/

defineMethod(Session.prototype, 'reload', function reload(fn) {
var req = this.req
var store = this.req.sessionStore

store.get(this.id, function(err, sess){
if (err) return fn(err);
if (!sess) return fn(new Error('failed to load session'));
store.createSession(req, sess);
fn();
var req = this.req;
var store = this.req.sessionStore;
var id = this.id;

return callbackOrPromise(this, fn, function (done) {
store.get(id, function (err, sess) {
if (err) return done(err);
if (!sess) return done(new Error('failed to load session'));
store.createSession(req, sess);
done();
});
});
return this;
});

/**
* Destroy `this` session.
*
* @param {Function} fn
* @return {Session} for chaining
* @param {Function} [fn]
* @return {Session|Promise} for chaining
* @api public
*/

defineMethod(Session.prototype, 'destroy', function destroy(fn) {
var store = this.req.sessionStore;
var id = this.id;

delete this.req.session;
this.req.sessionStore.destroy(this.id, fn);
return this;

return callbackOrPromise(this, fn, function (done) {
store.destroy(id, done);
});
});

/**
* Regenerate this request's session.
*
* @param {Function} fn
* @return {Session} for chaining
* @param {Function} [fn]
* @return {Session|Promise} for chaining
* @api public
*/

defineMethod(Session.prototype, 'regenerate', function regenerate(fn) {
this.req.sessionStore.regenerate(this.req, fn);
return this;
var req = this.req;
var store = this.req.sessionStore;

return callbackOrPromise(this, fn, function (done) {
store.regenerate(req, done);
});
});

/**
Expand All @@ -141,3 +156,25 @@ function defineMethod(obj, name, fn) {
writable: true
});
};

/**
* Run `executor(done)` in callback or promise style: with a callback,
* return `session` for chaining; without one, return a `Promise`
* resolving to `value`, or the request's current session by default.
*
* @private
*/

function callbackOrPromise(session, callback, executor, value) {
if (typeof callback === 'function') {
executor(callback)
return session
}

return new Promise(function (resolve, reject) {
executor(function (err) {
if (err) return reject(err)
resolve(value !== undefined ? value : session.req.session)
})
})
}
Loading
Loading