From 82acec3d486694ba3909666d8915b5b2b00b2984 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:12:40 +0800 Subject: [PATCH 1/4] feat: support opts.renew --- Readme.md | 3 ++- lib/context.js | 42 +++++++++++++++++++++++++++---------- package.json | 1 + test/store.test.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 12 deletions(-) diff --git a/Readme.md b/Readme.md index 3edb50d..9b6720f 100644 --- a/Readme.md +++ b/Readme.md @@ -56,7 +56,8 @@ const CONFIG = { overwrite: true, /** (boolean) can overwrite or not (default true) */ httpOnly: true, /** (boolean) httpOnly or not (default true) */ signed: true, /** (boolean) signed or not (default true) */ - rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. default is false **/ + rolling: false, /** (boolean) Force a session identifier cookie to be set on every response. The expiration is reset to the original maxAge, resetting the expiration countdown. (default is false) */ + renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ }; app.use(session(CONFIG, app)); diff --git a/lib/context.js b/lib/context.js index 77095ad..b6ce24f 100644 --- a/lib/context.js +++ b/lib/context.js @@ -207,7 +207,6 @@ class ContextSession { async commit() { const session = this.session; - const prevHash = this.prevHash; const opts = this.opts; const ctx = this.ctx; @@ -220,24 +219,45 @@ class ContextSession { return; } - // force save session when `session._requireSave` set - let changed = true; - if (!session._requireSave) { - const json = session.toJSON(); - // do nothing if new and not populated - if (!prevHash && !Object.keys(json).length) return; - changed = prevHash !== util.hash(json); - // do nothing if not changed and not in rolling mode - if (!this.opts.rolling && !changed) return; - } + const reason = this._shouldSaveSession(); + debug('should save session: %s', reason); + if (!reason) return; if (typeof opts.beforeSave === 'function') { debug('before save'); opts.beforeSave(ctx, session); } + const changed = reason === 'changed'; await this.save(changed); } + _shouldSaveSession() { + const prevHash = this.prevHash; + const session = this.session; + + // force save session when `session._requireSave` set + if (session._requireSave) return 'force'; + + // do nothing if new and not populated + const json = session.toJSON(); + if (!prevHash && !Object.keys(json).length) return false; + + // save if session changed + const changed = prevHash !== util.hash(json); + if (changed) return 'changed'; + + // save if opts.rolling set + if (this.opts.rolling) return 'rolling'; + + // save if opts.renew and session will expired + if (this.opts.renew) { + const expire = session._expire; + // renew when session will expired in maxAge / 2 + if (!expire || expire - Date.now() > session.maxAge / 2) return false; + return 'renew'; + } + } + /** * remove session * @api private diff --git a/package.json b/package.json index 674c0aa..3a61042 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "koa": "2", "mm": "^2.1.0", "mocha": "3", + "mz-modules": "^2.0.0", "should": "8", "supertest": "2" }, diff --git a/test/store.test.js b/test/store.test.js index b582d4e..60a6a5e 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -8,6 +8,7 @@ const session = require('..'); const store = require('./store'); const pedding = require('pedding'); const assert = require('assert'); +const sleep = require('mz-modules/sleep'); describe('Koa Session External Store', () => { let cookie; @@ -717,6 +718,57 @@ describe('Koa Session External Store', () => { }); }); }); + + describe.only('when renew set to true', () => { + let app; + before(() => { + app = App({ renew: true, maxAge: 2000 }); + + app.use(async ctx => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', () => { + return request(app.callback()) + .get('/') + .expect({}) + .expect(res => { + should.not.exist(res.headers['set-cookie']); + }); + }); + + it('should send set-cookie when session near expire and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + await sleep(1200); + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + res.headers['set-cookie'].should.have.length(2); + }); + + it('should not send set-cookie when session not near expire and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + await sleep(500); + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + should.not.exist(res.headers['set-cookie']); + }); + }); }); function App(options) { From 053fbc2a673504f00028f289d85971869a582c5e Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:16:01 +0800 Subject: [PATCH 2/4] f --- test/store.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/store.test.js b/test/store.test.js index 60a6a5e..9943ffb 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -719,7 +719,7 @@ describe('Koa Session External Store', () => { }); }); - describe.only('when renew set to true', () => { + describe('when renew set to true', () => { let app; before(() => { app = App({ renew: true, maxAge: 2000 }); From 0cb8191e2979cafb761151bb9bc5f91a886d5a32 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:19:52 +0800 Subject: [PATCH 3/4] f --- lib/context.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/context.js b/lib/context.js index b6ce24f..3e60839 100644 --- a/lib/context.js +++ b/lib/context.js @@ -240,7 +240,7 @@ class ContextSession { // do nothing if new and not populated const json = session.toJSON(); - if (!prevHash && !Object.keys(json).length) return false; + if (!prevHash && !Object.keys(json).length) return ''; // save if session changed const changed = prevHash !== util.hash(json); @@ -253,9 +253,11 @@ class ContextSession { if (this.opts.renew) { const expire = session._expire; // renew when session will expired in maxAge / 2 - if (!expire || expire - Date.now() > session.maxAge / 2) return false; + if (!expire || expire - Date.now() > session.maxAge / 2) return ''; return 'renew'; } + + return ''; } /** From d591f14949ee936e3d9ecbe5d3aa8d9e95230a3f Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:36:02 +0800 Subject: [PATCH 4/4] f --- lib/context.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index 3e60839..df162ee 100644 --- a/lib/context.js +++ b/lib/context.js @@ -252,9 +252,9 @@ class ContextSession { // save if opts.renew and session will expired if (this.opts.renew) { const expire = session._expire; + const maxAge = session.maxAge; // renew when session will expired in maxAge / 2 - if (!expire || expire - Date.now() > session.maxAge / 2) return ''; - return 'renew'; + if (expire && maxAge && expire - Date.now() < maxAge / 2) return 'renew'; } return ''; @@ -286,7 +286,7 @@ class ContextSession { const externalKey = this.externalKey; let json = this.session.toJSON(); // set expire for check - const maxAge = opts.maxAge ? opts.maxAge : ONE_DAY; + let maxAge = opts.maxAge ? opts.maxAge : ONE_DAY; if (maxAge === 'session') { // do not set _expire in json if maxAge is set to 'session' // also delete maxAge from options @@ -300,6 +300,10 @@ class ContextSession { // save to external store if (externalKey) { debug('save %j to external key %s', json, externalKey); + if (typeof maxAge === 'number') { + // ensure store expired after cookie + maxAge += 10000; + } await this.store.set(externalKey, json, maxAge, { changed, rolling: opts.rolling,