From abda418a1063ba157974eedc180f2621a5529db7 Mon Sep 17 00:00:00 2001 From: Joe Grayauskie Date: Thu, 1 Jun 2017 04:22:56 -0400 Subject: [PATCH 01/84] =?UTF-8?q?Create=20capability=20to=20create=20cooki?= =?UTF-8?q?es=20that=20expire=20when=20browser=20is=20close=E2=80=A6=20(#7?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Readme.md | 5 ++++- lib/context.js | 17 +++++++++------ test/store.test.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index 490cb0b..ebe3589 100644 --- a/Readme.md +++ b/Readme.md @@ -47,7 +47,10 @@ app.keys = ['some secret hurr']; const CONFIG = { key: 'koa:sess', /** (string) cookie key (default is koa:sess) */ - maxAge: 86400000, /** (number) maxAge in ms (default is 1 days) */ + /** (number || 'session') maxAge in ms (default is 1 days) */ + /** 'session' will result in a cookie that expires when session/browser is closed */ + /** Warning: If a session cookie is stolen, this cookie will never expire */ + maxAge: 86400000, 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) */ diff --git a/lib/context.js b/lib/context.js index 88cea73..28ca1c8 100644 --- a/lib/context.js +++ b/lib/context.js @@ -153,7 +153,7 @@ class ContextSession { valid(json) { if (!json) return false; - if (!json._expire || json._expire < Date.now()) { + if (json._expire && json._expire < Date.now()) { debug('expired session'); return false; } @@ -242,13 +242,18 @@ class ContextSession { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; - - const maxAge = opts.maxAge || ONE_DAY; - let json = this.session.toJSON(); // set expire for check - json._expire = maxAge + Date.now(); - json._maxAge = maxAge; + const 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 + opts.maxAge = undefined; + } else { + // set expire for check + json._expire = maxAge + Date.now(); + json._maxAge = maxAge; + } // save to external store if (externalKey) { diff --git a/test/store.test.js b/test/store.test.js index 008a35f..3e96e84 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -356,6 +356,60 @@ describe('Koa Session External Store', () => { }); describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 'session' }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.body = 200; + return; + } + this.body = this.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + it('should use the default maxAge when improper string given', done => { + const app = App({ maxAge: 'not the right string' }); + + app.use(function* () { + if (this.method === 'POST') { + this.session.message = 'hi'; + this.body = 200; + return; + } + this.body = this.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + }); describe('and not expire', () => { it('should not expire the session', done => { const app = App({ maxAge: 100 }); From 89f9850fe07be03949df6864f9f958c9dd056754 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 1 Jun 2017 16:35:19 +0800 Subject: [PATCH 02/84] fix test case --- test/store.test.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/store.test.js b/test/store.test.js index 3e96e84..46712d7 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -360,13 +360,13 @@ describe('Koa Session External Store', () => { it('should not expire the session', done => { const app = App({ maxAge: 'session' }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.body = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; return; } - this.body = this.session.message; + ctx.body = ctx.session.message; }); const server = app.listen(); @@ -386,13 +386,13 @@ describe('Koa Session External Store', () => { it('should use the default maxAge when improper string given', done => { const app = App({ maxAge: 'not the right string' }); - app.use(function* () { - if (this.method === 'POST') { - this.session.message = 'hi'; - this.body = 200; + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; return; } - this.body = this.session.message; + ctx.body = ctx.session.message; }); const server = app.listen(); From e763f6a42672510f71e6fe308b9c799990f173ce Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 1 Jun 2017 16:35:58 +0800 Subject: [PATCH 03/84] test: test on node 8 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 4eb70eb..ec9e856 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,6 @@ sudo: false language: node_js node_js: - '7' + - '8' script: 'npm run test-travis' after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' From acda04c0ff35423df6b00f23821bfd64469737bf Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 1 Jun 2017 16:36:33 +0800 Subject: [PATCH 04/84] Release 5.1.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 5e9a798..4810926 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.1.0 / 2017-06-01 +================== + + * Create capability to create cookies that expire when browser is close… (#77) + 5.0.0 / 2017-03-12 ================== diff --git a/package.json b/package.json index ca161a4..7760292 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "5.0.0", + "version": "5.1.0", "keywords": [ "koa", "middleware", From bb1dbd66fec214277dcc48d2b5e659e5cae4e1e2 Mon Sep 17 00:00:00 2001 From: Justin Date: Sat, 10 Jun 2017 10:04:30 -0400 Subject: [PATCH 05/84] Create Readme.md (#78) --- Readme.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index ebe3589..11aa5aa 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,9 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. default is cookie-based session and support external store. + Simple session middleware for Koa. default is cookie-based session and support external store. + + *Requires Node 7.6 or greater for async/await support* ## Installation From a5cefa8863ade64fb99420270a48e920ad1e408c Mon Sep 17 00:00:00 2001 From: Shawn Date: Thu, 15 Jun 2017 14:52:34 +0800 Subject: [PATCH 06/84] feat: support options.ContextStore (#81) --- Readme.md | 2 + index.js | 18 +- lib/context.js | 4 +- package.json | 1 + test/context_store.js | 25 ++ test/contextstore.test.js | 639 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 683 insertions(+), 6 deletions(-) create mode 100644 test/context_store.js create mode 100644 test/contextstore.test.js diff --git a/Readme.md b/Readme.md index 11aa5aa..931a782 100644 --- a/Readme.md +++ b/Readme.md @@ -108,6 +108,8 @@ console.log('listening on port 3000'); Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** + If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. + ### Session#isNew Returns __true__ if the session is new. diff --git a/index.js b/index.js index 47992fc..24a7d9d 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); +const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); const _CONTEXT_SESSION = Symbol('context#_contextSession'); @@ -78,10 +79,19 @@ function formatOpts(opts) { opts.decode = util.decode; } - if (opts.store) { - assert(typeof opts.store.get === 'function', 'store.get must be function'); - assert(typeof opts.store.set === 'function', 'store.set must be function'); - assert(typeof opts.store.destroy === 'function', 'store.destroy must be function'); + const store = opts.store; + if (store) { + assert(is.function(store.get), 'store.get must be function'); + assert(is.function(store.set), 'store.set must be function'); + assert(is.function(store.destroy), 'store.destroy must be function'); + } + + const ContextStore = opts.ContextStore; + if (ContextStore) { + assert(is.class(ContextStore), 'ContextStore must be a class'); + assert(is.function(ContextStore.prototype.get), 'ContextStore.prototype.get must be function'); + assert(is.function(ContextStore.prototype.set), 'ContextStore.prototype.set must be function'); + assert(is.function(ContextStore.prototype.destroy), 'ContextStore.prototype.destroy must be function'); } return opts; diff --git a/lib/context.js b/lib/context.js index 28ca1c8..fedf076 100644 --- a/lib/context.js +++ b/lib/context.js @@ -16,7 +16,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; this.opts = Object.assign({}, opts); - this.store = this.opts.store; + this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } /** @@ -177,7 +177,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.opts.store) this.externalKey = externalKey || uid.sync(24); + if (this.store) this.externalKey = externalKey || uid.sync(24); this.session = new Session(this.ctx, val); } diff --git a/package.json b/package.json index 7760292..023ab5d 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "dependencies": { "crc": "^3.4.4", "debug": "^2.2.0", + "is-type-of": "^1.0.0", "uid-safe": "^2.1.3" }, "engines": { diff --git a/test/context_store.js b/test/context_store.js new file mode 100644 index 0000000..8a053eb --- /dev/null +++ b/test/context_store.js @@ -0,0 +1,25 @@ +'use strict'; + +// this is a stupid nonsense example just to test + +const sessions = {}; + +class ContextStore { + constructor(ctx) { + this.ctx = ctx; + } + + async get(key) { + return sessions[key]; + } + + async set(key, value) { + sessions[key] = value; + } + + async destroy(key) { + sessions[key] = undefined; + } +} + +module.exports = ContextStore; diff --git a/test/contextstore.test.js b/test/contextstore.test.js new file mode 100644 index 0000000..acd98f8 --- /dev/null +++ b/test/contextstore.test.js @@ -0,0 +1,639 @@ +'use strict'; + +const Koa = require('koa'); +const request = require('supertest'); +const should = require('should'); +const mm = require('mm'); +const session = require('..'); +const ContextStore = require('./context_store'); + +describe('Koa Session External Context Store', () => { + let cookie; + + describe('when the session contains a ;', () => { + it('should still work', done => { + const app = App(); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect(204, (err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie']; + request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';', done); + }); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'greetings'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session; + ctx.body = 'greetings'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.body = ''; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, (err, res) => { + if (err) return done(err); + cookie = res.header['set-cookie'].join(';'); + done(); + }); + }); + + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, done); + }); + + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message.should.equal('hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + request(app.listen()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = null; + ctx.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect(200, (err, res) => { + if (err) return done(err); + res.header.should.not.have.property('set-cookie'); + done(); + }); + }); + }); + + describe('an object', () => { + it('should create a session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, done); + }); + }); + + describe('anything else', () => { + it('should throw', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = 'asdf'; + }); + + request(app.listen()) + .get('/') + .expect(500, done); + }); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = ctx.session.inspect(); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200, done); + }); + }); + + describe('.length', () => { + it('should return session length', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.length); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('1') + .expect(200, done); + }); + }); + + describe('.populated', () => { + it('should return session populated', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.populated); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('true') + .expect(200, done); + }); + }); + + describe('.save()', () => { + it('should save session', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.save(); + ctx.body = 'hello'; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=.+;/) + .expect('hello') + .expect(200, done); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', done => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async function(ctx, next) { + try { + await next(); + } catch (err) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session({ ContextStore }, app)); + + app.use(async function(ctx, next) { + ctx.session.name = 'funny'; + await next(); + }); + + app.use(async function(ctx) { + ctx.throw(401); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(401, done); + }); + }); + + describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + it('should use the default maxAge when improper string given', done => { + const app = App({ maxAge: 'not the right string' }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + cookie.should.containEql('expires='); + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + }); + describe('and not expire', () => { + it('should not expire the session', done => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('hi', done); + }); + }); + }); + + describe('and expired', () => { + it('should expire the sess', done => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session.message || ''; + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie'].join(';'); + + setTimeout(() => { + request(server) + .get('/') + .set('cookie', cookie) + .expect('', done); + }, 200); + }); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', done => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + ctx.body = ctx.session.maxAge; + }); + + request(app.listen()) + .get('/') + .expect('100', done); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.session.maxAge = 100; + ctx.body = ctx.session.foo; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + + it('should save even session not change', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.maxAge = 100; + ctx.body = ctx.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + + it('should save when create session only with maxAge', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200, done); + }); + }); + + describe('when store return empty', () => { + it('should create new Session', done => { + const app = App({ signed: false }); + + app.use(async function(ctx) { + ctx.body = String(ctx.session.isNew); + }); + + request(app.listen()) + .get('/') + .set('cookie', 'koa:sess=invalid-key') + .expect('true') + .expect(200, done); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', done => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + ContextStore, + }, app)); + app.use(async function(ctx) { + if (!ctx.session.foo) { + ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200, (err, res) => { + should.not.exist(err); + const data = res.body; + const cookies = res.headers['set-cookie'].join(';'); + cookies.should.containEql('koa:sess='); + + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data, err => { + should.not.exist(err); + + // should ignore uid:123 session and create a new session for uid:456 + request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200, (err, res) => { + should.not.exist(err); + res.body.uid.should.equal('456'); + res.body.foo.should.not.equal(data.foo); + done(); + }); + }); + }); + }); + }); + + describe('ctx.session', () => { + after(mm.restore); + + it('should be mocked', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + mm(app.context, 'session', { + foo: 'bar', + }); + + request(app.listen()) + .get('/') + .expect({ + foo: 'bar', + }) + .expect(200, done); + }); + }); +}); + +function App(options) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options = options || {}; + options.ContextStore = ContextStore; + app.use(session(options, app)); + return app; +} From 50e1345572c9c87c6ea73211cf085a8ca046d874 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 15 Jun 2017 14:53:22 +0800 Subject: [PATCH 07/84] Release 5.2.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 4810926..3208994 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.2.0 / 2017-06-15 +================== + + * feat: support options.ContextStore (#81) + 5.1.0 / 2017-06-01 ================== diff --git a/package.json b/package.json index 023ab5d..587d340 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware", "repository": "koajs/session", - "version": "5.1.0", + "version": "5.2.0", "keywords": [ "koa", "middleware", From e153c0be9c215c6ec0fab41590281f71a588401a Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Sat, 17 Jun 2017 00:22:47 +0800 Subject: [PATCH 08/84] feat: support rolling (#84) --- Readme.md | 6 ++++-- lib/context.js | 17 +++++++++++------ test/cookie.test.js | 40 ++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 931a782..2131312 100644 --- a/Readme.md +++ b/Readme.md @@ -56,7 +56,9 @@ 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 **/ }; + app.use(session(CONFIG, app)); // or if you prefer all default config, just use => app.use(session(app)); @@ -101,8 +103,8 @@ console.log('listening on port 3000'); You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be async function): - - `get(key)`: get session object by key - - `set(key, sess, maxAge)`: set session object for key, with a `maxAge` (in ms) + - `get(key, maxAge, { rolling })`: get session object by key + - `set(key, sess, maxAge, { rolling, changed })`: set session object for key, with a `maxAge` (in ms) - `destroy(key)`: destroy session for key diff --git a/lib/context.js b/lib/context.js index fedf076..29a9dac 100644 --- a/lib/context.js +++ b/lib/context.js @@ -79,7 +79,7 @@ class ContextSession { return; } - const json = await this.store.get(externalKey); + const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling }); if (!this.valid(json)) { // create a new `externalKey` this.create(); @@ -203,19 +203,21 @@ class ContextSession { } // 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; - // do nothing if not changed - if (prevHash === util.hash(json)) return; + changed = prevHash !== util.hash(json); + // do nothing if not changed and not in rolling mode + if (!this.opts.rolling && !changed) return; } if (typeof opts.beforeSave === 'function') { debug('before save'); opts.beforeSave(ctx, session); } - await this.save(); + await this.save(changed); } /** @@ -238,7 +240,7 @@ class ContextSession { * @api private */ - async save() { + async save(changed) { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; @@ -258,7 +260,10 @@ class ContextSession { // save to external store if (externalKey) { debug('save %j to external key %s', json, externalKey); - await this.store.set(externalKey, json, maxAge); + await this.store.set(externalKey, json, maxAge, { + changed, + rolling: opts.rolling, + }); this.ctx.cookies.set(key, externalKey, opts); return; } diff --git a/test/cookie.test.js b/test/cookie.test.js index b1fa1e5..dc15fb9 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -755,6 +755,46 @@ describe('Koa Session Cookie', () => { }); }); }); + + describe('when rolling set to true', () => { + let app; + before(() => { + app = App({ rolling: true }); + + app.use(function* () { + console.log(this.path); + if (this.path === '/set') this.session = { foo: 'bar' }; + this.body = this.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 exists and not change', done => { + request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }) + .end((err, res) => { + should.not.exist(err); + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(res => { + res.headers['set-cookie'].should.have.length(2); + }) + .expect({ foo: 'bar' }, done); + }); + }); + }); }); function App(options) { diff --git a/test/store.test.js b/test/store.test.js index 46712d7..9e0bf77 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -627,6 +627,45 @@ describe('Koa Session External Store', () => { .expect(200, done); }); }); + + describe('when rolling set to true', () => { + let app; + before(() => { + app = App({ rolling: true }); + + 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 exists and not change', done => { + request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }) + .end((err, res) => { + should.not.exist(err); + res.headers['set-cookie'].should.have.length(2); + const cookie = res.headers['set-cookie'].join(';'); + request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(res => { + res.headers['set-cookie'].should.have.length(2); + }) + .expect({ foo: 'bar' }, done); + }); + }); + }); }); function App(options) { From d958bb542f856610c6e44c19f6ab3f248ec21a34 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sat, 17 Jun 2017 00:25:53 +0800 Subject: [PATCH 09/84] Release 5.3.0 --- History.md | 5 +++++ package.json | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 3208994..52710dd 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.3.0 / 2017-06-17 +================== + + * feat: support rolling (#84) + 5.2.0 / 2017-06-15 ================== diff --git a/package.json b/package.json index 587d340..b6a374e 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "koa-session", - "description": "Koa cookie session middleware", + "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.2.0", + "version": "5.3.0", "keywords": [ "koa", "middleware", From e82726d5b315c18df2a306a6cd77e6db3f017336 Mon Sep 17 00:00:00 2001 From: Shawn Date: Mon, 3 Jul 2017 16:50:23 +0800 Subject: [PATCH 10/84] feat: opts.genid (#87) --- Readme.md | 2 ++ lib/context.js | 2 +- test/contextstore.test.js | 4 ++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 2131312..6108fb9 100644 --- a/Readme.md +++ b/Readme.md @@ -110,6 +110,8 @@ console.log('listening on port 3000'); Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** + The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. + If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. ### Session#isNew diff --git a/lib/context.js b/lib/context.js index 29a9dac..f7664bf 100644 --- a/lib/context.js +++ b/lib/context.js @@ -177,7 +177,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || uid.sync(24); + if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx) || uid.sync(24); this.session = new Session(this.ctx, val); } diff --git a/test/contextstore.test.js b/test/contextstore.test.js index acd98f8..0905fae 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -91,6 +91,7 @@ describe('Koa Session External Context Store', () => { .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); + cookie.indexOf('_suffix').should.greaterThan(1); done(); }); }); @@ -634,6 +635,9 @@ function App(options) { app.keys = [ 'a', 'b' ]; options = options || {}; options.ContextStore = ContextStore; + options.genid = () => { + return Date.now() + '_suffix'; + }; app.use(session(options, app)); return app; } From fd8acf239c9db1d9ae6db8ccbe38831b06b6226a Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 3 Jul 2017 16:52:40 +0800 Subject: [PATCH 11/84] Release 5.4.0 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 52710dd..66ea28b 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,9 @@ +5.4.0 / 2017-07-03 +================== + + * feat: opts.genid (#87) + 5.3.0 / 2017-06-17 ================== diff --git a/package.json b/package.json index b6a374e..078bdb7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.3.0", + "version": "5.4.0", "keywords": [ "koa", "middleware", From ec88cfb095ddbfa9a0db465e3f9e459fb6f92bec Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Fri, 4 Aug 2017 10:53:30 +0800 Subject: [PATCH 12/84] feat: support options.prefix for external store (#93) --- Readme.md | 4 +++- index.js | 6 ++++++ lib/context.js | 3 +-- test/store.test.js | 29 +++++++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 6108fb9..f425b73 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. default is cookie-based session and support external store. + Simple session middleware for Koa. default is cookie-based session and support external store. *Requires Node 7.6 or greater for async/await support* @@ -112,6 +112,8 @@ console.log('listening on port 3000'); The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. + If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. + If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. ### Session#isNew diff --git a/index.js b/index.js index 24a7d9d..d7978c1 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); +const uid = require('uid-safe'); const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); @@ -94,6 +95,11 @@ function formatOpts(opts) { assert(is.function(ContextStore.prototype.destroy), 'ContextStore.prototype.destroy must be function'); } + if (!opts.genid) { + if (opts.prefix) opts.genid = () => opts.prefix + uid.sync(24); + else opts.genid = () => uid.sync(24); + } + return opts; } diff --git a/lib/context.js b/lib/context.js index f7664bf..f1dc702 100644 --- a/lib/context.js +++ b/lib/context.js @@ -2,7 +2,6 @@ const debug = require('debug')('koa-session:context'); const Session = require('./session'); -const uid = require('uid-safe'); const util = require('./util'); const ONE_DAY = 24 * 60 * 60 * 1000; @@ -177,7 +176,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx) || uid.sync(24); + if (this.store) this.externalKey = externalKey || this.opts.genid(); this.session = new Session(this.ctx, val); } diff --git a/test/store.test.js b/test/store.test.js index 9e0bf77..82996ac 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -666,6 +666,35 @@ describe('Koa Session External Store', () => { }); }); }); + + describe('when prefix present', () => { + it('should still work', done => { + const app = App({ prefix: 'sess:' }); + + app.use(async ctx => { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + + const server = app.listen(); + + request(server) + .post('/') + .expect(204, (err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie']; + cookie.join().should.match(/koa:sess=sess:/); + request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';', done); + }); + }); + }); }); function App(options) { From cb6d2c8448f21f6cf42efdb031798c9608f05602 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 4 Aug 2017 10:54:51 +0800 Subject: [PATCH 13/84] Release 5.5.0 --- History.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 66ea28b..2bbec70 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.5.0 / 2017-08-04 +================== + +**features** + * [[`ec88cfb`](http://github.com/koajs/session/commit/ec88cfb095ddbfa9a0db465e3f9e459fb6f92bec)] - feat: support options.prefix for external store (#93) (Yiyu He <>) + 5.4.0 / 2017-07-03 ================== diff --git a/package.json b/package.json index 078bdb7..c05e259 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.4.0", + "version": "5.5.0", "keywords": [ "koa", "middleware", @@ -39,4 +39,4 @@ "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" } -} +} \ No newline at end of file From 906277a3c9995ed4f07d2cee55e3020af0c75168 Mon Sep 17 00:00:00 2001 From: Nate Silva Date: Thu, 3 Aug 2017 19:58:10 -0700 Subject: [PATCH 14/84] docs: copyediting (#85) some minor spelling and grammar changes --- Readme.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index f425b73..bee6427 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. default is cookie-based session and support external store. + Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. *Requires Node 7.6 or greater for async/await support* @@ -95,20 +95,21 @@ console.log('listening on port 3000'); ### External Session Stores - Session will store in cookie by default, but it has some disadvantages: + The session is stored in a cookie by default, but it has some disadvantages: - - Session stored in client side unencrypted. - - [Browser cookie always have length limit](http://browsercookielimits.squawky.net/). + - Session is stored on client side unencrypted + - [Browser cookies always have length limits](http://browsercookielimits.squawky.net/) - You can store the session content in external stores(redis, mongodb or other DBs) by pass `options.store` with three methods(need to be async function): + You can store the session content in external stores (Redis, MongoDB or other DBs) by passing `options.store` with three methods (these need to be async functions): - `get(key, maxAge, { rolling })`: get session object by key - `set(key, sess, maxAge, { rolling, changed })`: set session object for key, with a `maxAge` (in ms) - `destroy(key)`: destroy session for key - Once you passed `options.store`, session is strong dependent on your external store, you can't access session if your external store is down. **Use external session stores only if necessary, avoid use session as a cache, keep session lean and stored by cookie!** + Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid uisng session as a cache, keep the session lean, and store it in a cookie if possible!** + The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. From c040b5997d35267a3a65becf91e327615ff17fa5 Mon Sep 17 00:00:00 2001 From: Runrioter Wung Date: Fri, 25 Aug 2017 00:25:19 +0800 Subject: [PATCH 15/84] chore: fix example bug and use syntactic sugar (#97) --- example.js | 4 ++-- index.js | 4 +--- package.json | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/example.js b/example.js index abf009d..ea47cce 100644 --- a/example.js +++ b/example.js @@ -1,7 +1,7 @@ var session = require('./'); -var koa = require('koa'); -var app = koa(); +var Koa = require('koa'); +var app = new Koa(); app.keys = ['some secret hurr']; diff --git a/index.js b/index.js index d7978c1..b095060 100644 --- a/index.js +++ b/index.js @@ -24,9 +24,7 @@ const _CONTEXT_SESSION = Symbol('context#_contextSession'); module.exports = function(opts, app) { // session(app[, opts]) if (opts && typeof opts.use === 'function') { - const tmp = app; - app = opts; - opts = tmp; + [ app, opts ] = [ opts, app ]; } // app required if (!app || typeof app.use !== 'function') { diff --git a/package.json b/package.json index c05e259..5091185 100644 --- a/package.json +++ b/package.json @@ -39,4 +39,4 @@ "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", "lint": "eslint lib test index.js" } -} \ No newline at end of file +} From b976b10212f522b675711badb7ce1bc9a909d19d Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Fri, 17 Nov 2017 03:26:19 -0600 Subject: [PATCH 16/84] perf: no need to assign opts (#103) --- lib/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index f1dc702..c4622a1 100644 --- a/lib/context.js +++ b/lib/context.js @@ -14,7 +14,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; - this.opts = Object.assign({}, opts); + this.opts = opts || {}; this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } From 5e41b4726e4c996c4314588cc7b02d24b3943e10 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 17 Nov 2017 17:26:42 +0800 Subject: [PATCH 17/84] Release 5.5.1 --- History.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 2bbec70..c3d4749 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,12 @@ +5.5.1 / 2017-11-17 +================== + +**others** + * [[`b976b10`](http://github.com/koajs/session/commit/b976b10212f522b675711badb7ce1bc9a909d19d)] - perf: no need to assign opts (#103) (Yiyu He <>) + * [[`c040b59`](http://github.com/koajs/session/commit/c040b5997d35267a3a65becf91e327615ff17fa5)] - chore: fix example bug and use syntactic sugar (#97) (Runrioter Wung <>) + * [[`906277a`](http://github.com/koajs/session/commit/906277a3c9995ed4f07d2cee55e3020af0c75168)] - docs: copyediting (#85) (Nate Silva <>) + 5.5.0 / 2017-08-04 ================== diff --git a/package.json b/package.json index 5091185..d12da0b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.5.0", + "version": "5.5.1", "keywords": [ "koa", "middleware", From f00c1ef9857fec52e1aaf981ba9a8e837b3e7ffa Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Tue, 9 Jan 2018 17:31:01 +0800 Subject: [PATCH 18/84] feat: emit events when session invalid (#108) session key add Date.now() --- Readme.md | 12 ++++++++++-- index.js | 4 ++-- lib/context.js | 19 +++++++++++++------ package.json | 1 + test/store.test.js | 21 ++++++++++++++++++++- 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Readme.md b/Readme.md index bee6427..3edb50d 100644 --- a/Readme.md +++ b/Readme.md @@ -26,7 +26,7 @@ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. + Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. *Requires Node 7.6 or greater for async/await support* @@ -111,12 +111,20 @@ console.log('listening on port 3000'); Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid uisng session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid`, which defaults to `uid.sync(24)`. + The way of generating external session id is controlled by the `options.genid`, which defaults to `Date.now() + '-' + uid.sync(24)`. If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. +### Events + +`koa-session` will emit event on `app` when session expired or invalid: + +- `session:missed`: can't get session value from external store. +- `session:invalid`: session value is invalid. +- `session:expired`: session value is expired. + ### Session#isNew Returns __true__ if the session is new. diff --git a/index.js b/index.js index b095060..ebedc97 100644 --- a/index.js +++ b/index.js @@ -94,8 +94,8 @@ function formatOpts(opts) { } if (!opts.genid) { - if (opts.prefix) opts.genid = () => opts.prefix + uid.sync(24); - else opts.genid = () => uid.sync(24); + if (opts.prefix) opts.genid = () => `${opts.prefix}${Date.now()}-${uid.sync(24)}`; + else opts.genid = () => `${Date.now()}-${uid.sync(24)}`; } return opts; diff --git a/lib/context.js b/lib/context.js index c4622a1..ea9cd74 100644 --- a/lib/context.js +++ b/lib/context.js @@ -14,6 +14,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; + this.app = ctx.app; this.opts = opts || {}; this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } @@ -79,7 +80,7 @@ class ContextSession { } const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling }); - if (!this.valid(json)) { + if (!this.valid(json, externalKey)) { // create a new `externalKey` this.create(); return; @@ -144,23 +145,29 @@ class ContextSession { /** * verify session(expired or ) - * @param {Object} json session object + * @param {Object} value session object + * @param {Object} key session externalKey(optional) * @return {Boolean} valid * @api private */ - valid(json) { - if (!json) return false; + valid(value, key) { + if (!value) { + this.app.emit('session:missed', { key, value }); + return false; + } - if (json._expire && json._expire < Date.now()) { + if (value._expire && value._expire < Date.now()) { debug('expired session'); + this.app.emit('session:expired', { key, value }); return false; } const valid = this.opts.valid; - if (typeof valid === 'function' && !valid(this.ctx, json)) { + if (typeof valid === 'function' && !valid(this.ctx, value)) { // valid session value fail, ignore this session debug('invalid session'); + this.app.emit('session:invalid', { key, value }); return false; } return true; diff --git a/package.json b/package.json index d12da0b..f6c0513 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "crc": "^3.4.4", "debug": "^2.2.0", "is-type-of": "^1.0.0", + "pedding": "^1.1.0", "uid-safe": "^2.1.3" }, "engines": { diff --git a/test/store.test.js b/test/store.test.js index 82996ac..d4e2f77 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -6,6 +6,8 @@ const should = require('should'); const mm = require('mm'); const session = require('..'); const store = require('./store'); +const pedding = require('pedding'); +const assert = require('assert'); describe('Koa Session External Store', () => { let cookie; @@ -442,8 +444,13 @@ describe('Koa Session External Store', () => { describe('and expired', () => { it('should expire the sess', done => { + done = pedding(done, 2); const app = App({ maxAge: 100 }); - + app.on('session:expired', args => { + assert(args.key.match(/^\d+-/)); + assert(args.value); + done(); + }); app.use(async function(ctx) { if (ctx.method === 'POST') { ctx.session.message = 'hi'; @@ -535,12 +542,18 @@ describe('Koa Session External Store', () => { describe('when store return empty', () => { it('should create new Session', done => { + done = pedding(done, 2); const app = App({ signed: false }); app.use(async function(ctx) { ctx.body = String(ctx.session.isNew); }); + app.on('session:missed', args => { + assert(args.key === 'invalid-key'); + done(); + }); + request(app.listen()) .get('/') .set('cookie', 'koa:sess=invalid-key') @@ -551,6 +564,7 @@ describe('Koa Session External Store', () => { describe('when valid and beforeSave set', () => { it('should ignore session when uid changed', done => { + done = pedding(done, 2); const app = new Koa(); app.keys = [ 'a', 'b' ]; @@ -573,6 +587,11 @@ describe('Koa Session External Store', () => { uid: ctx.cookies.get('uid'), }; }); + app.on('session:invalid', args => { + assert(args.key); + assert(args.value); + done(); + }); request(app.callback()) .get('/') From d1d4b95cc67240f61b33647925725cf8a1c592ec Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 9 Jan 2018 17:32:04 +0800 Subject: [PATCH 19/84] Release 5.6.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c3d4749..d207321 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.6.0 / 2018-01-09 +================== + +**features** + * [[`f00c1ef`](http://github.com/koajs/session/commit/f00c1ef9857fec52e1aaf981ba9a8e837b3e7ffa)] - feat: emit events when session invalid (#108) (Yiyu He <>) + 5.5.1 / 2017-11-17 ================== diff --git a/package.json b/package.json index f6c0513..70d301c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.5.1", + "version": "5.6.0", "keywords": [ "koa", "middleware", From a2401c85b486a87a4bf933e457b09088496735d7 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 9 Jan 2018 17:38:25 +0800 Subject: [PATCH 20/84] feat: emit event expose ctx --- lib/context.js | 9 +++++---- test/store.test.js | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/context.js b/lib/context.js index ea9cd74..4f3c899 100644 --- a/lib/context.js +++ b/lib/context.js @@ -152,22 +152,23 @@ class ContextSession { */ valid(value, key) { + const ctx = this.ctx; if (!value) { - this.app.emit('session:missed', { key, value }); + this.app.emit('session:missed', { key, value, ctx }); return false; } if (value._expire && value._expire < Date.now()) { debug('expired session'); - this.app.emit('session:expired', { key, value }); + this.app.emit('session:expired', { key, value, ctx }); return false; } const valid = this.opts.valid; - if (typeof valid === 'function' && !valid(this.ctx, value)) { + if (typeof valid === 'function' && !valid(ctx, value)) { // valid session value fail, ignore this session debug('invalid session'); - this.app.emit('session:invalid', { key, value }); + this.app.emit('session:invalid', { key, value, ctx }); return false; } return true; diff --git a/test/store.test.js b/test/store.test.js index d4e2f77..b582d4e 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -449,6 +449,7 @@ describe('Koa Session External Store', () => { app.on('session:expired', args => { assert(args.key.match(/^\d+-/)); assert(args.value); + assert(args.ctx); done(); }); app.use(async function(ctx) { @@ -551,6 +552,7 @@ describe('Koa Session External Store', () => { app.on('session:missed', args => { assert(args.key === 'invalid-key'); + assert(args.ctx); done(); }); @@ -590,6 +592,7 @@ describe('Koa Session External Store', () => { app.on('session:invalid', args => { assert(args.key); assert(args.value); + assert(args.ctx); done(); }); From 2027e75fbca1db444efa3c03b528f33fa3301952 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 9 Jan 2018 17:39:51 +0800 Subject: [PATCH 21/84] Release 5.7.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index d207321..c7bf0b0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.7.0 / 2018-01-09 +================== + +**features** + * [[`a2401c8`](http://github.com/koajs/session/commit/a2401c85b486a87a4bf933e457b09088496735d7)] - feat: emit event expose ctx (dead-horse <>) + 5.6.0 / 2018-01-09 ================== diff --git a/package.json b/package.json index 70d301c..3e2e70d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.6.0", + "version": "5.7.0", "keywords": [ "koa", "middleware", From 72fa5fec71a8fa3c4e8b75226b401e965d8d31c7 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 11 Jan 2018 23:43:55 +0800 Subject: [PATCH 22/84] fix: emit event in next tick --- lib/context.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index 4f3c899..77095ad 100644 --- a/lib/context.js +++ b/lib/context.js @@ -154,13 +154,13 @@ class ContextSession { valid(value, key) { const ctx = this.ctx; if (!value) { - this.app.emit('session:missed', { key, value, ctx }); + this.emit('missed', { key, value, ctx }); return false; } if (value._expire && value._expire < Date.now()) { debug('expired session'); - this.app.emit('session:expired', { key, value, ctx }); + this.emit('expired', { key, value, ctx }); return false; } @@ -168,12 +168,23 @@ class ContextSession { if (typeof valid === 'function' && !valid(ctx, value)) { // valid session value fail, ignore this session debug('invalid session'); - this.app.emit('session:invalid', { key, value, ctx }); + this.emit('invalid', { key, value, ctx }); return false; } return true; } + /** + * @param {String} event event name + * @param {Object} data event data + * @api private + */ + emit(event, data) { + setImmediate(() => { + this.app.emit(`session:${event}`, data); + }); + } + /** * create a new session and attach to ctx.sess * From 6260c6d8d2ca3ed9261e36133d2cf5957ebd31db Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 11 Jan 2018 23:45:26 +0800 Subject: [PATCH 23/84] Release 5.7.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c7bf0b0..7dd85a4 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.7.1 / 2018-01-11 +================== + +**fixes** + * [[`72fa5fe`](http://github.com/koajs/session/commit/72fa5fec71a8fa3c4e8b75226b401e965d8d31c7)] - fix: emit event in next tick (dead-horse <>) + 5.7.0 / 2018-01-09 ================== diff --git a/package.json b/package.json index 3e2e70d..674c0aa 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.7.0", + "version": "5.7.1", "keywords": [ "koa", "middleware", From bb5f4bf86da802cb37cd5e3a990b5bbcc4f6d144 Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Wed, 17 Jan 2018 15:35:29 +0800 Subject: [PATCH 24/84] feat: support opts.renew (#111) --- Readme.md | 3 ++- lib/context.js | 44 +++++++++++++++++++++++++++++---------- package.json | 1 + test/store.test.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 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..3e60839 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,47 @@ 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 ''; + + // 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 ''; + return 'renew'; + } + + return ''; + } + /** * 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..9943ffb 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('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 9854d03429e67f4dbce8bd211f8f9db8a1f82f75 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 17 Jan 2018 15:36:11 +0800 Subject: [PATCH 25/84] Release 5.8.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 7dd85a4..91c8fcb 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.8.0 / 2018-01-17 +================== + +**features** + * [[`bb5f4bf`](http://github.com/koajs/session/commit/bb5f4bf86da802cb37cd5e3a990b5bbcc4f6d144)] - feat: support opts.renew (#111) (Yiyu He <>) + 5.7.1 / 2018-01-11 ================== diff --git a/package.json b/package.json index 3a61042..9060d2a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.7.1", + "version": "5.8.0", "keywords": [ "koa", "middleware", From bdb4fd45a7c247c94f0035585104b004e36ec725 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:36:02 +0800 Subject: [PATCH 26/84] fix: ensure store expired after cookie --- 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, From 622a2d803ff0d42ff1620e3c576b089b3725a65f Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 17 Jan 2018 15:37:43 +0800 Subject: [PATCH 27/84] Release 5.8.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 91c8fcb..9d87396 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.8.1 / 2018-01-17 +================== + +**fixes** + * [[`bdb4fd4`](http://github.com/koajs/session/commit/bdb4fd45a7c247c94f0035585104b004e36ec725)] - fix: ensure store expired after cookie (dead-horse <>) + 5.8.0 / 2018-01-17 ================== diff --git a/package.json b/package.json index 9060d2a..53fed2a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.0", + "version": "5.8.1", "keywords": [ "koa", "middleware", From c48e1e054566fe09c81ff50f530c6f230f07c7d5 Mon Sep 17 00:00:00 2001 From: Wellington Soares Date: Fri, 15 Jun 2018 23:22:41 -0300 Subject: [PATCH 28/84] Update Readme.md (#123) fixing a small typo --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 9b6720f..c35313a 100644 --- a/Readme.md +++ b/Readme.md @@ -109,7 +109,7 @@ console.log('listening on port 3000'); - `destroy(key)`: destroy session for key - Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid uisng session as a cache, keep the session lean, and store it in a cookie if possible!** + Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** The way of generating external session id is controlled by the `options.genid`, which defaults to `Date.now() + '-' + uid.sync(24)`. From c487944c22056fdd37433bdeab3d665dbd116744 Mon Sep 17 00:00:00 2001 From: Adriano Date: Wed, 11 Jul 2018 13:58:07 -0300 Subject: [PATCH 29/84] fix: Fixes a bug that reset the cookie expire date to the default (1 day) when using browser sessions (maxAge: 'session') (#117) --- lib/context.js | 1 + lib/session.js | 1 + test/contextstore.test.js | 43 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/lib/context.js b/lib/context.js index df162ee..ac80990 100644 --- a/lib/context.js +++ b/lib/context.js @@ -291,6 +291,7 @@ class ContextSession { // do not set _expire in json if maxAge is set to 'session' // also delete maxAge from options opts.maxAge = undefined; + json._session = true; } else { // set expire for check json._expire = maxAge + Date.now(); diff --git a/lib/session.js b/lib/session.js index b585f2a..61d1320 100644 --- a/lib/session.js +++ b/lib/session.js @@ -20,6 +20,7 @@ class Session { for (const k in obj) { // restore maxAge from store if (k === '_maxAge') this._ctx.sessionOptions.maxAge = obj._maxAge; + else if (k === '_session') this._ctx.sessionOptions.maxAge = 'session'; else this[k] = obj[k]; } } diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 0905fae..a190045 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -384,6 +384,49 @@ describe('Koa Session External Context Store', () => { .expect('hi', done); }); }); + it('should not expire the session after multiple session changes', done => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + ctx.session.count = (ctx.session.count || 0) + 1; + ctx.body = `hi ${ctx.session.count}`; + }); + const server = app.listen(); + + request(server) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect('hi 1') + .end((err, res) => { + if (err) return done(err); + let cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa:sess/) + .expect('hi 2') + .end((err, res) => { + if (err) return done(err); + cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + + request(server) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa:sess/) + .expect('hi 3') + .end((err, res) => { + if (err) return done(err); + cookie = res.headers['set-cookie'].join(';'); + cookie.should.not.containEql('expires='); + + done(); + }); + }); + }); + }); it('should use the default maxAge when improper string given', done => { const app = App({ maxAge: 'not the right string' }); From 90506055366a31205b0895592eb00d43f8d9da28 Mon Sep 17 00:00:00 2001 From: Daniel Tseng Date: Thu, 12 Jul 2018 01:01:12 +0800 Subject: [PATCH 30/84] deps: Upgrade debug@^3.1.0 (#107) This version of debug addresses a minor ReDoS issue. See https://nodesecurity.io/advisories/534 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 53fed2a..3968273 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "license": "MIT", "dependencies": { "crc": "^3.4.4", - "debug": "^2.2.0", + "debug": "^3.1.0", "is-type-of": "^1.0.0", "pedding": "^1.1.0", "uid-safe": "^2.1.3" From d15a102b2f736ad4ebb41d6d19c54363466e3216 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 12 Jul 2018 01:04:10 +0800 Subject: [PATCH 31/84] Release 5.8.2 --- History.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 9d87396..4eec28f 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,14 @@ +5.8.2 / 2018-07-12 +================== + +**fixes** + * [[`c487944`](http://github.com/koajs/session/commit/c487944c22056fdd37433bdeab3d665dbd116744)] - fix: Fixes a bug that reset the cookie expire date to the default (1 day) when using browser sessions (maxAge: 'session') (#117) (Adriano <>) + +**others** + * [[`9050605`](http://github.com/koajs/session/commit/90506055366a31205b0895592eb00d43f8d9da28)] - deps: Upgrade debug@^3.1.0 (#107) (Daniel Tseng <>) + * [[`c48e1e0`](http://github.com/koajs/session/commit/c48e1e054566fe09c81ff50f530c6f230f07c7d5)] - Update Readme.md (#123) (Wellington Soares <>) + 5.8.1 / 2018-01-17 ================== diff --git a/package.json b/package.json index 3968273..dca32e4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.1", + "version": "5.8.2", "keywords": [ "koa", "middleware", From 95272ff912af8dd31ae9f038df9540d8b6c019d7 Mon Sep 17 00:00:00 2001 From: Maples7 Date: Wed, 1 Aug 2018 13:42:54 +0800 Subject: [PATCH 32/84] fix typo in README.md (#134) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index c35313a..f2ee958 100644 --- a/Readme.md +++ b/Readme.md @@ -116,7 +116,7 @@ console.log('listening on port 3000'); If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. - If your session store requires data or utilities from context, `opts.ContextStore` is alse supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. + If your session store requires data or utilities from context, `opts.ContextStore` is also supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. ### Events From 6f1a41cf499f55532f0e7ce0de04d778a0466496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=96=E7=8C=A9?= Date: Wed, 22 Aug 2018 20:32:56 +0800 Subject: [PATCH 33/84] fix: session not works (#136) --- lib/context.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index ac80990..aba8e7d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -15,7 +15,7 @@ class ContextSession { constructor(ctx, opts) { this.ctx = ctx; this.app = ctx.app; - this.opts = opts || {}; + this.opts = Object.assign({}, opts); this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } From 2497dc550a3c8689fd0de4886f2ae5eebff5a490 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 22 Aug 2018 20:38:36 +0800 Subject: [PATCH 34/84] Release 5.8.3 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 4eec28f..71026e1 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +5.8.3 / 2018-08-22 +================== + +**fixes** + * [[`6f1a41c`](http://github.com/koajs/session/commit/6f1a41cf499f55532f0e7ce0de04d778a0466496)] - fix: session not works (#136) (吖猩 <>) + +**others** + * [[`95272ff`](http://github.com/koajs/session/commit/95272ff912af8dd31ae9f038df9540d8b6c019d7)] - fix typo in README.md (#134) (Maples7 <>) + 5.8.2 / 2018-07-12 ================== diff --git a/package.json b/package.json index dca32e4..38e0c40 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.2", + "version": "5.8.3", "keywords": [ "koa", "middleware", From 724140076b65867b1a0cffee4f061971be8751c0 Mon Sep 17 00:00:00 2001 From: Jonas Galvez Date: Tue, 28 Aug 2018 12:20:44 -0300 Subject: [PATCH 35/84] feat: Add autoCommit option (#139) --- Readme.md | 5 ++++ index.js | 5 +++- lib/context.js | 2 +- lib/session.js | 16 +++++++++++-- test/contextstore.test.js | 48 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index f2ee958..efcd6e7 100644 --- a/Readme.md +++ b/Readme.md @@ -53,6 +53,7 @@ const CONFIG = { /** 'session' will result in a cookie that expires when session/browser is closed */ /** Warning: If a session cookie is stolen, this cookie will never expire */ maxAge: 86400000, + autoCommit: true, /** (boolean) automatically commit headers (default true) */ 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) */ @@ -150,6 +151,10 @@ if (this.session.isNew) { Save this session no matter whether it is populated. +### Session#manuallyCommit() + + Session headers are auto committed by default. Use this if `autoCommit` is set to `false`. + ### Destroying a session To destroy a session simply set it to `null`: diff --git a/index.js b/index.js index ebedc97..7cb9edc 100644 --- a/index.js +++ b/index.js @@ -42,7 +42,9 @@ module.exports = function(opts, app) { } catch (err) { throw err; } finally { - await sess.commit(); + if (opts.autoCommit) { + await sess.commit(); + } } }; }; @@ -67,6 +69,7 @@ function formatOpts(opts) { if (opts.overwrite == null) opts.overwrite = true; if (opts.httpOnly == null) opts.httpOnly = true; if (opts.signed == null) opts.signed = true; + if (opts.autoCommit == null) opts.autoCommit = true; debug('session options %j', opts); diff --git a/lib/context.js b/lib/context.js index aba8e7d..90c0c32 100644 --- a/lib/context.js +++ b/lib/context.js @@ -196,7 +196,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); if (this.store) this.externalKey = externalKey || this.opts.genid(); - this.session = new Session(this.ctx, val); + this.session = new Session(this, val); } /** diff --git a/lib/session.js b/lib/session.js index 61d1320..6961e3b 100644 --- a/lib/session.js +++ b/lib/session.js @@ -12,8 +12,9 @@ class Session { * @api private */ - constructor(ctx, obj) { - this._ctx = ctx; + constructor(sessionContext, obj) { + this._sessCtx = sessionContext; + this._ctx = sessionContext.ctx; if (!obj) { this.isNew = true; } else { @@ -111,6 +112,17 @@ class Session { save() { this._requireSave = true; } + + /** + * commit this session's headers if autoCommit is set to false + * + * @api public + */ + + async manuallyCommit() { + await this._sessCtx.commit(); + } + } module.exports = Session; diff --git a/test/contextstore.test.js b/test/contextstore.test.js index a190045..84f30b7 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -356,6 +356,54 @@ describe('Koa Session External Context Store', () => { }); }); + describe('when autoCommit is present', () => { + describe('and set to false', () => { + it('should not set headers if manuallyCommit() isn\'t called', done => { + const app = App({ autoCommit: false }); + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + const server = app.listen(); + + request(server) + .post('/') + .end((err, res) => { + if (err) return done(err); + const cookie = res.headers['set-cookie']; + should.not.exist(cookie); + }) + .expect(200, done); + }); + it('should set headers if manuallyCommit() is called', done => { + const app = App({ autoCommit: false }); + app.use(async function(ctx, next) { + if (ctx.method === 'POST') { + ctx.session.message = 'dummy'; + } + await next(); + }); + app.use(async function(ctx) { + ctx.body = 200; + await ctx.session.manuallyCommit(); + }); + const server = app.listen(); + + request(server) + .post('/') + .expect('Set-Cookie', /koa:sess/) + .end(err => { + if (err) return done(err); + }) + .expect(200, done); + }); + }); + }); + describe('when maxAge present', () => { describe('and set to be a session cookie', () => { it('should not expire the session', done => { From 4ea5a63470b936642a18875997e1a627794d198d Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 28 Aug 2018 23:21:17 +0800 Subject: [PATCH 36/84] Release 5.9.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 71026e1..f593854 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.9.0 / 2018-08-28 +================== + +**features** + * [[`7241400`](http://github.com/koajs/session/commit/724140076b65867b1a0cffee4f061971be8751c0)] - feat: Add autoCommit option (#139) (Jonas Galvez <>) + 5.8.3 / 2018-08-22 ================== diff --git a/package.json b/package.json index 38e0c40..11bc5ec 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.8.3", + "version": "5.9.0", "keywords": [ "koa", "middleware", From 81906f7724ef009dc14686f4990af35c716f6db9 Mon Sep 17 00:00:00 2001 From: Tree Xie Date: Mon, 29 Oct 2018 00:13:04 +0800 Subject: [PATCH 37/84] feat: support options.externalKey #88 (#149) --- Readme.md | 7 +++++ index.js | 6 +++++ lib/context.js | 17 +++++++++--- test/externalkey.test.js | 56 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 test/externalkey.test.js diff --git a/Readme.md b/Readme.md index efcd6e7..6a00431 100644 --- a/Readme.md +++ b/Readme.md @@ -127,6 +127,13 @@ console.log('listening on port 3000'); - `session:invalid`: session value is invalid. - `session:expired`: session value is expired. +### Custom External Key + +External key is used the cookie by default, but you can use `options.externalKey` to customize your own external key methods. `options.externalKey` with two methods: + +- `get(ctx)`: get the external key +- `set(ctx, value)`: set the external key + ### Session#isNew Returns __true__ if the session is new. diff --git a/index.js b/index.js index 7cb9edc..606ab13 100644 --- a/index.js +++ b/index.js @@ -88,6 +88,12 @@ function formatOpts(opts) { assert(is.function(store.destroy), 'store.destroy must be function'); } + const externalKey = opts.externalKey; + if (externalKey) { + assert(is.function(externalKey.get), 'externalKey.get must be function'); + assert(is.function(externalKey.set), 'externalKey.set must be function'); + } + const ContextStore = opts.ContextStore; if (ContextStore) { assert(is.class(ContextStore), 'ContextStore must be a class'); diff --git a/lib/context.js b/lib/context.js index 90c0c32..410996b 100644 --- a/lib/context.js +++ b/lib/context.js @@ -70,8 +70,15 @@ class ContextSession { const ctx = this.ctx; const opts = this.opts; - const externalKey = ctx.cookies.get(opts.key, opts); - debug('get external key from cookie %s', externalKey); + let externalKey; + if (opts.externalKey) { + externalKey = opts.externalKey.get(ctx); + debug('get external key from custom %s', externalKey); + } else { + externalKey = ctx.cookies.get(opts.key, opts); + debug('get external key from cookie %s', externalKey); + } + if (!externalKey) { // create a new `externalKey` @@ -309,7 +316,11 @@ class ContextSession { changed, rolling: opts.rolling, }); - this.ctx.cookies.set(key, externalKey, opts); + if (opts.externalKey) { + opts.externalKey.set(this.ctx, externalKey); + } else { + this.ctx.cookies.set(key, externalKey, opts); + } return; } diff --git a/test/externalkey.test.js b/test/externalkey.test.js new file mode 100644 index 0000000..c3625cb --- /dev/null +++ b/test/externalkey.test.js @@ -0,0 +1,56 @@ +'use strict'; +const Koa = require('koa'); +const request = require('supertest'); +const assert = require('assert'); +const session = require('..'); +const store = require('./store'); +const TOKEN_KEY = 'User-Token'; +describe('Koa Session External Key', () => { + describe('when the external key set/get is invalid', () => { + it('should throw a error', () => { + try { + new App({ + externalKey: {}, + }); + } catch (err) { + assert.equal(err.message, 'externalKey.get must be function'); + } + }); + }); + describe('custom get/set external key', () => { + it('should still work', done => { + const app = App(); + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + const server = app.listen(); + request(server) + .post('/') + .expect(204, (err, res) => { + if (err) return done(err); + const token = res.get(TOKEN_KEY); + request(server) + .get('/') + .set(TOKEN_KEY, token) + .expect(';', done); + }); + }); + }); +}); +function App(options) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options = options || {}; + options.store = store; + options.externalKey = options.externalKey || { + get: ctx => ctx.get(TOKEN_KEY), + set: (ctx, value) => ctx.set(TOKEN_KEY, value), + }; + app.use(session(options, app)); + return app; +} From 21fab9e5ac09cb182e3adfc52aee876b18861b24 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Mon, 29 Oct 2018 00:13:31 +0800 Subject: [PATCH 38/84] Release 5.10.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index f593854..ee7a4a0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.10.0 / 2018-10-29 +================== + +**features** + * [[`81906f7`](http://github.com/koajs/session/commit/81906f7724ef009dc14686f4990af35c716f6db9)] - feat: support options.externalKey #88 (#149) (Tree Xie <>) + 5.9.0 / 2018-08-28 ================== diff --git a/package.json b/package.json index 11bc5ec..d33ca05 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.9.0", + "version": "5.10.0", "keywords": [ "koa", "middleware", From 89c048adc5a64b6c12c87047b766ac34be10af77 Mon Sep 17 00:00:00 2001 From: Vitaliy Zaytsev Date: Tue, 11 Dec 2018 13:03:24 +0600 Subject: [PATCH 39/84] fix: moved "pedding" package to dev dependencies (#155) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d33ca05..319b103 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "mm": "^2.1.0", "mocha": "3", "mz-modules": "^2.0.0", + "pedding": "^1.1.0", "should": "8", "supertest": "2" }, @@ -29,7 +30,6 @@ "crc": "^3.4.4", "debug": "^3.1.0", "is-type-of": "^1.0.0", - "pedding": "^1.1.0", "uid-safe": "^2.1.3" }, "engines": { From 5f12f7019b4fbb3ce1d495c1c7fb8a234ae16818 Mon Sep 17 00:00:00 2001 From: killa Date: Tue, 18 Dec 2018 11:30:01 +0800 Subject: [PATCH 40/84] feat: allow init multi session middleware (#159) --- index.js | 3 +++ lib/util.js | 4 ++-- test/cookie.test.js | 13 +++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 606ab13..f2db549 100644 --- a/index.js +++ b/index.js @@ -120,6 +120,9 @@ function formatOpts(opts) { */ function extendContext(context, opts) { + if (context.hasOwnProperty(CONTEXT_SESSION)) { + return; + } Object.defineProperties(context, { [CONTEXT_SESSION]: { get() { diff --git a/lib/util.js b/lib/util.js index 2bdd7b8..759197c 100644 --- a/lib/util.js +++ b/lib/util.js @@ -13,7 +13,7 @@ module.exports = { */ decode(string) { - const body = new Buffer(string, 'base64').toString('utf8'); + const body = Buffer.from(string, 'base64').toString('utf8'); const json = JSON.parse(body); return json; }, @@ -28,7 +28,7 @@ module.exports = { encode(body) { body = JSON.stringify(body); - return new Buffer(body).toString('base64'); + return Buffer.from(body).toString('base64'); }, hash(sess) { diff --git a/test/cookie.test.js b/test/cookie.test.js index dc15fb9..bf0e569 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -1,5 +1,6 @@ 'use strict'; +const assert = require('assert'); const Koa = require('koa'); const request = require('supertest'); const should = require('should'); @@ -795,6 +796,18 @@ describe('Koa Session Cookie', () => { }); }); }); + + describe('init multi session middleware', () => { + it('should work', () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + const s1 = session({}, app); + const s2 = session({}, app); + assert(s1); + assert(s2); + }); + }); }); function App(options) { From e66fbd16bb05d5a4b8001ceb155c3f7440490407 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 18 Dec 2018 11:36:23 +0800 Subject: [PATCH 41/84] Release 5.10.1 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index ee7a4a0..22f0972 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +5.10.1 / 2018-12-18 +================== + +**features** + * [[`5f12f70`](http://github.com/koajs/session/commit/5f12f7019b4fbb3ce1d495c1c7fb8a234ae16818)] - feat: allow init multi session middleware (#159) (killa <>) + +**fixes** + * [[`89c048a`](http://github.com/koajs/session/commit/89c048adc5a64b6c12c87047b766ac34be10af77)] - fix: moved "pedding" package to dev dependencies (#155) (Vitaliy Zaytsev <>) + 5.10.0 / 2018-10-29 ================== diff --git a/package.json b/package.json index 319b103..7563660 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.10.0", + "version": "5.10.1", "keywords": [ "koa", "middleware", From 1600aabdfa6a86973e3fab9f4064c3ed82b10604 Mon Sep 17 00:00:00 2001 From: Vitaliy Zaytsev Date: Tue, 18 Dec 2018 12:11:54 +0600 Subject: [PATCH 42/84] test: changed "ctx.session is mockable" tests names to more appropriate (#158) --- test/contextstore.test.js | 2 +- test/store.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 84f30b7..1d3058e 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -700,7 +700,7 @@ describe('Koa Session External Context Store', () => { describe('ctx.session', () => { after(mm.restore); - it('should be mocked', done => { + it('can be mocked', done => { const app = App(); app.use(async function(ctx) { diff --git a/test/store.test.js b/test/store.test.js index 9943ffb..a5e8147 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -631,7 +631,7 @@ describe('Koa Session External Store', () => { describe('ctx.session', () => { after(mm.restore); - it('should be mocked', done => { + it('can be mocked', done => { const app = App(); app.use(async function(ctx) { From 23ad8718a9a392c0c563893a10b5ca9f6fd70ebe Mon Sep 17 00:00:00 2001 From: Douglas Wade Date: Wed, 19 Dec 2018 18:50:31 -0800 Subject: [PATCH 43/84] deps: Fix security vulnerabilities from npm audit (#163) --- package-lock.json | 2001 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 10 +- 2 files changed, 2006 insertions(+), 5 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..01bbb9f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2001 @@ +{ + "name": "koa-session", + "version": "5.10.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "dev": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "1.5.2", + "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "cookies": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", + "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "keygrip": "~1.0.3" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "d": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", + "dev": true + }, + "es5-ext": { + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-config-egg": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", + "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-assert": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.0.tgz", + "integrity": "sha512-tPVv62a6l3BbQoM/N5qo969l0OFxqpnQzNUPeYfTP6Spo4zkgWeDBD1D5thI7sDLg7jCCihXTLB0X8UtdyAy8A==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.1" + } + }, + "http-errors": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", + "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "ieee754": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", + "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "is-class-hotfix": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", + "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", + "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-type-of": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", + "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", + "requires": { + "core-util-is": "^1.0.2", + "is-class-hotfix": "~0.0.6", + "isstream": "~0.1.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "keygrip": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", + "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", + "dev": true + }, + "ko-sleep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", + "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "koa": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.6.2.tgz", + "integrity": "sha512-KdnBFhTgh9ysMMoYe4J4fLvaKjT7mF3nRYV8MjxLzx6qywFNeptqi4xevyUltg1fZl2CFJ+HeLXuCGx07Yvl/A==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.7.1", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "koa-is-json": "^1.0.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mm": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/mm/-/mm-2.4.1.tgz", + "integrity": "sha512-NXWPSHcaTtC8GqcJ5w1FvX28ieWwEobrsfngBugrbyhAEg710i2W+vZuMuIBW6VnFt0l4b9WsjvqMytvmc3HmQ==", + "dev": true, + "requires": { + "is-type-of": "^1.0.0", + "ko-sleep": "^1.0.2", + "muk-prop": "^1.0.0", + "thenify": "^3.2.1" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "muk-prop": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", + "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "mz-modules": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", + "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", + "dev": true, + "requires": { + "glob": "^7.1.2", + "ko-sleep": "^1.0.3", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "rimraf": "^2.6.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pedding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", + "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", + "dev": true + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "qs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", + "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==", + "dev": true + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", + "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "should": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", + "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", + "dev": true, + "requires": { + "should-equal": "0.8.0", + "should-format": "0.3.2", + "should-type": "0.2.0" + } + }, + "should-equal": { + "version": "0.8.0", + "resolved": "http://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", + "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-format": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", + "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-type": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", + "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + } + }, + "supertest": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.3.0.tgz", + "integrity": "sha512-dMQSzYdaZRSANH5LL8kX3UpgK9G1LRh/jnggs/TI0W2Sz7rkMx9Y48uia3K9NgcaWEV28tYkBnXE4tiFC77ygQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "optional": true + } + } + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 7563660..ff25fb3 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,11 @@ "istanbul": "0", "koa": "2", "mm": "^2.1.0", - "mocha": "3", + "mocha": "^5.2.0", "mz-modules": "^2.0.0", "pedding": "^1.1.0", "should": "8", - "supertest": "2" + "supertest": "^3.3.0" }, "license": "MIT", "dependencies": { @@ -36,9 +36,9 @@ "node": ">=7.6" }, "scripts": { - "test": "npm run lint && NODE_ENV=test mocha --require should --reporter spec test/*.test.js", - "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --require should test/*.test.js", - "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --require should test/*.test.js", + "test": "npm run lint && NODE_ENV=test mocha --exit --require should --reporter spec test/*.test.js", + "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --exit --require should test/*.test.js", + "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", "lint": "eslint lib test index.js" } } From c2b4259ccef6095cad2f3ff51968b21cea993d13 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Mon, 29 Apr 2019 16:46:57 +0800 Subject: [PATCH 44/84] fix: remove package-lock.json --- package-lock.json | 2001 --------------------------------------------- 1 file changed, 2001 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 01bbb9f..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2001 +0,0 @@ -{ - "name": "koa-session", - "version": "5.10.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "dev": true, - "requires": { - "mime-types": "~2.1.18", - "negotiator": "0.6.1" - } - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true, - "optional": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "async": { - "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-content-type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", - "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", - "dev": true, - "requires": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "cookies": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", - "integrity": "sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "keygrip": "~1.0.3" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "requires": { - "buffer": "^5.1.0" - } - }, - "d": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "dev": true, - "requires": { - "es5-ext": "^0.10.9" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-inject": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", - "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", - "dev": true - }, - "es5-ext": { - "version": "0.10.46", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", - "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-config-egg": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", - "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "http://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - } - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "handlebars": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", - "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", - "dev": true, - "requires": { - "async": "^2.5.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", - "dev": true, - "requires": { - "lodash": "^4.17.10" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "http-assert": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.0.tgz", - "integrity": "sha512-tPVv62a6l3BbQoM/N5qo969l0OFxqpnQzNUPeYfTP6Spo4zkgWeDBD1D5thI7sDLg7jCCihXTLB0X8UtdyAy8A==", - "dev": true, - "requires": { - "deep-equal": "~1.0.1", - "http-errors": "~1.7.1" - } - }, - "http-errors": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", - "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "inquirer": { - "version": "0.12.0", - "resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true - }, - "is-class-hotfix": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", - "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-generator-function": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", - "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", - "dev": true - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.19.0.tgz", - "integrity": "sha512-mG0f/unGX1HZ5ep4uhRaPOS8EkAY8/j6mDRMJrutq4CqhoJWYp7qAlonIPy3TV7p3ju4TK9fo/PbnoksWmsp5Q==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-type-of": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", - "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", - "requires": { - "core-util-is": "^1.0.2", - "is-class-hotfix": "~0.0.6", - "isstream": "~0.1.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "http://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "keygrip": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz", - "integrity": "sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==", - "dev": true - }, - "ko-sleep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", - "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "koa": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.6.2.tgz", - "integrity": "sha512-KdnBFhTgh9ysMMoYe4J4fLvaKjT7mF3nRYV8MjxLzx6qywFNeptqi4xevyUltg1fZl2CFJ+HeLXuCGx07Yvl/A==", - "dev": true, - "requires": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.7.1", - "debug": "~3.1.0", - "delegates": "^1.0.0", - "depd": "^1.1.2", - "destroy": "^1.0.4", - "error-inject": "^1.0.0", - "escape-html": "^1.0.3", - "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", - "koa-compose": "^4.1.0", - "koa-convert": "^1.2.0", - "koa-is-json": "^1.0.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", - "vary": "^1.1.2" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", - "dev": true - }, - "koa-convert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", - "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", - "dev": true, - "requires": { - "co": "^4.6.0", - "koa-compose": "^3.0.0" - }, - "dependencies": { - "koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "dev": true, - "requires": { - "any-promise": "^1.1.0" - } - } - } - }, - "koa-is-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", - "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", - "dev": true - }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "dev": true, - "requires": { - "mime-db": "~1.37.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "mm": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/mm/-/mm-2.4.1.tgz", - "integrity": "sha512-NXWPSHcaTtC8GqcJ5w1FvX28ieWwEobrsfngBugrbyhAEg710i2W+vZuMuIBW6VnFt0l4b9WsjvqMytvmc3HmQ==", - "dev": true, - "requires": { - "is-type-of": "^1.0.0", - "ko-sleep": "^1.0.2", - "muk-prop": "^1.0.0", - "thenify": "^3.2.1" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - }, - "muk-prop": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", - "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "mz-modules": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", - "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", - "dev": true, - "requires": { - "glob": "^7.1.2", - "ko-sleep": "^1.0.3", - "mkdirp": "^0.5.1", - "pump": "^3.0.0", - "rimraf": "^2.6.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "http://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", - "dev": true - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "pedding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", - "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", - "dev": true - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "progress": { - "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "qs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.6.0.tgz", - "integrity": "sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==", - "dev": true - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.9.0.tgz", - "integrity": "sha512-TZNye00tI67lwYvzxCxHGjwTNlUV70io54/Ed4j6PscB8xVfuBJpRenI/o6dVk0cY0PYTY27AgCoGGxRnYuItQ==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "^1.3.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "should": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", - "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", - "dev": true, - "requires": { - "should-equal": "0.8.0", - "should-format": "0.3.2", - "should-type": "0.2.0" - } - }, - "should-equal": { - "version": "0.8.0", - "resolved": "http://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", - "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-format": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", - "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-type": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", - "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "http://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "http://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "dev": true, - "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - } - }, - "supertest": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.3.0.tgz", - "integrity": "sha512-dMQSzYdaZRSANH5LL8kX3UpgK9G1LRh/jnggs/TI0W2Sz7rkMx9Y48uia3K9NgcaWEV28tYkBnXE4tiFC77ygQ==", - "dev": true, - "requires": { - "methods": "^1.1.2", - "superagent": "^3.8.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "3.8.3", - "resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "through": { - "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.18" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "ylru": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", - "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", - "dev": true - } - } -} From b79134d6854173bf46d6703e79636a58f9282e15 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Mon, 29 Apr 2019 17:27:05 +0800 Subject: [PATCH 45/84] feat: make sure session id is global unique (#170) --- .travis.yml | 2 ++ Readme.md | 4 ++-- index.js | 6 +++--- package.json | 4 +++- test/genid_bench.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 2 +- 6 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 test/genid_bench.js diff --git a/.travis.yml b/.travis.yml index ec9e856..80b9e4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,7 @@ language: node_js node_js: - '7' - '8' + - '10' + - '12' script: 'npm run test-travis' after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' diff --git a/Readme.md b/Readme.md index 6a00431..337a177 100644 --- a/Readme.md +++ b/Readme.md @@ -113,7 +113,7 @@ console.log('listening on port 3000'); Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid`, which defaults to `Date.now() + '-' + uid.sync(24)`. + The way of generating external session id is controlled by the `options.genid`, which defaults to `uuid.v4()`. If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. @@ -127,7 +127,7 @@ console.log('listening on port 3000'); - `session:invalid`: session value is invalid. - `session:expired`: session value is expired. -### Custom External Key +### Custom External Key External key is used the cookie by default, but you can use `options.externalKey` to customize your own external key methods. `options.externalKey` with two methods: diff --git a/index.js b/index.js index f2db549..63dca92 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); -const uid = require('uid-safe'); +const uuid = require('uuid/v4'); const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); @@ -103,8 +103,8 @@ function formatOpts(opts) { } if (!opts.genid) { - if (opts.prefix) opts.genid = () => `${opts.prefix}${Date.now()}-${uid.sync(24)}`; - else opts.genid = () => `${Date.now()}-${uid.sync(24)}`; + if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid()}`; + else opts.genid = uuid; } return opts; diff --git a/package.json b/package.json index ff25fb3..9f701d4 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "lib" ], "devDependencies": { + "benchmark": "^2.1.4", "eslint": "3", "eslint-config-egg": "3", "istanbul": "0", @@ -22,6 +23,7 @@ "mocha": "^5.2.0", "mz-modules": "^2.0.0", "pedding": "^1.1.0", + "uid-safe": "^2.1.3", "should": "8", "supertest": "^3.3.0" }, @@ -30,7 +32,7 @@ "crc": "^3.4.4", "debug": "^3.1.0", "is-type-of": "^1.0.0", - "uid-safe": "^2.1.3" + "uuid": "^3.3.2" }, "engines": { "node": ">=7.6" diff --git a/test/genid_bench.js b/test/genid_bench.js new file mode 100644 index 0000000..8ff2900 --- /dev/null +++ b/test/genid_bench.js @@ -0,0 +1,45 @@ +'use strict'; + +const Benchmark = require('benchmark'); +const uuid = require('uuid'); +const uid = require('uid-safe'); + +const suite = new Benchmark.Suite(); + +const genidByUid = () => `${Date.now()}-${uid.sync(24)}`; +const genidByUuidV1 = () => uuid.v1(); +const genidByUuidV4 = () => uuid.v4(); + +console.log('genidByUid() => %s', genidByUid()); +console.log('genidByUuidV1() => %s', genidByUuidV1()); +console.log('genidByUuidV4() => %s', genidByUuidV4()); + +// add tests +suite +.add('uid()', function() { + genidByUid(); +}) +.add('genidByUuidV1()', function() { + genidByUuidV1(); +}) +.add('genidByUuidV4()', function() { + genidByUuidV4(); +}) +// add listeners +.on('cycle', function(event) { + console.log(String(event.target)); +}) +.on('complete', function() { + console.log('Fastest is ' + this.filter('fastest').map('name')); +}) +// run async +.run({ async: true }); + +// genidByUid() => 1556529339180-DRnQyEqlYjGr_Zq_42fHpdFMlBfVlAoG +// genidByUuidV1() => 5af3b830-6a5f-11e9-91fb-abc918efca3d +// genidByUuidV4() => 27088fa8-8436-4c8b-aae7-76ba316db9e3 +// +// uid() x 260,850 ops/sec ±1.50% (84 runs sampled) +// genidByUuidV1() x 1,181,483 ops/sec ±0.93% (86 runs sampled) +// genidByUuidV4() x 301,840 ops/sec ±1.40% (83 runs sampled) +// Fastest is genidByUuidV1() diff --git a/test/store.test.js b/test/store.test.js index a5e8147..7f9587c 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -448,7 +448,7 @@ describe('Koa Session External Store', () => { done = pedding(done, 2); const app = App({ maxAge: 100 }); app.on('session:expired', args => { - assert(args.key.match(/^\d+-/)); + assert(args.key.match(/^\w+-/)); assert(args.value); assert(args.ctx); done(); From 5ca903f7f9e3c2fd901f62d7e41da391e4cff258 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Mon, 29 Apr 2019 17:31:05 +0800 Subject: [PATCH 46/84] Release 5.11.0 --- History.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 22f0972..e498d77 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,17 @@ +5.11.0 / 2019-04-29 +================== + +**features** + * [[`b79134d`](http://github.com/koajs/session/commit/b79134d6854173bf46d6703e79636a58f9282e15)] - feat: make sure session id is global unique (#170) (fengmk2 <>) + +**fixes** + * [[`c2b4259`](http://github.com/koajs/session/commit/c2b4259ccef6095cad2f3ff51968b21cea993d13)] - fix: remove package-lock.json (fengmk2 <>) + +**others** + * [[`23ad871`](http://github.com/koajs/session/commit/23ad8718a9a392c0c563893a10b5ca9f6fd70ebe)] - deps: Fix security vulnerabilities from npm audit (#163) (Douglas Wade <>) + * [[`1600aab`](http://github.com/koajs/session/commit/1600aabdfa6a86973e3fab9f4064c3ed82b10604)] - test: changed "ctx.session is mockable" tests names to more appropriate (#158) (Vitaliy Zaytsev <>) + 5.10.1 / 2018-12-18 ================== diff --git a/package.json b/package.json index 9f701d4..45ba845 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.10.1", + "version": "5.11.0", "keywords": [ "koa", "middleware", From 39ca830a99ae7fcab2bd499a1f2a87de53fd1944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=BE=E6=9D=BE?= <1733458402@qq.com> Date: Fri, 17 May 2019 13:04:17 +0800 Subject: [PATCH 47/84] =?UTF-8?q?feat:=20add=20the=20parameter=20"ctx"=20t?= =?UTF-8?q?o=20the=20function=20"genid"=20so=20can=20get=20the=20=E2=80=A6?= =?UTF-8?q?=20(#173)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/context.js | 2 +- test/contextstore.test.js | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/context.js b/lib/context.js index 410996b..b9d444b 100644 --- a/lib/context.js +++ b/lib/context.js @@ -202,7 +202,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || this.opts.genid(); + if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx); this.session = new Session(this, val); } diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 1d3058e..57cfb44 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -96,6 +96,26 @@ describe('Koa Session External Context Store', () => { }); }); + it('should pass sid to middleware', done => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.state.sid.indexOf('_suffix').should.greaterThan(1); + ctx.body = ''; + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess/) + .expect(200, (err, res) => { + if (err) return done(err); + cookie = res.header['set-cookie'].join(';'); + cookie.indexOf('_suffix').should.greaterThan(1); + done(); + }); + }); + it('should not Set-Cookie', done => { const app = App(); @@ -726,8 +746,10 @@ function App(options) { app.keys = [ 'a', 'b' ]; options = options || {}; options.ContextStore = ContextStore; - options.genid = () => { - return Date.now() + '_suffix'; + options.genid = ctx => { + const sid = Date.now() + '_suffix'; + ctx.state.sid = sid; + return sid; }; app.use(session(options, app)); return app; From 3d57a443c7e0050d4066c871bf8da2656cda99f1 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 17 May 2019 13:05:06 +0800 Subject: [PATCH 48/84] docs: add genid(ctx) in readme --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 337a177..c327c2e 100644 --- a/Readme.md +++ b/Readme.md @@ -113,9 +113,9 @@ console.log('listening on port 3000'); Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid`, which defaults to `uuid.v4()`. + The way of generating external session id is controlled by the `options.genid(ctx)`, which defaults to `uuid.v4()`. - If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid` present. + If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid(ctx)` present. If your session store requires data or utilities from context, `opts.ContextStore` is also supported. `ContextStore` must be a class which claims three instance methods demonstrated above. `new ContextStore(ctx)` will be executed on every request. From a4dcdc467a1e1672256207304a8e9ceafce97f70 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 17 May 2019 13:05:18 +0800 Subject: [PATCH 49/84] Release 5.12.0 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index e498d77..76fd3f6 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +5.12.0 / 2019-05-17 +================== + +**features** + * [[`39ca830`](http://github.com/koajs/session/commit/39ca830a99ae7fcab2bd499a1f2a87de53fd1944)] - feat: add the parameter "ctx" to the function "genid" so can get the … (#173) (松松 <<1733458402@qq.com>>) + +**others** + * [[`3d57a44`](http://github.com/koajs/session/commit/3d57a443c7e0050d4066c871bf8da2656cda99f1)] - docs: add genid(ctx) in readme (dead-horse <>) + 5.11.0 / 2019-04-29 ================== diff --git a/package.json b/package.json index 45ba845..4a38ac9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.11.0", + "version": "5.12.0", "keywords": [ "koa", "middleware", From 77968e3ff6fb5d4f4a36665474ccd992fed689ec Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 00:02:43 +0800 Subject: [PATCH 50/84] fix: ensure ctx.session always has value --- lib/context.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index b9d444b..ac3a546 100644 --- a/lib/context.js +++ b/lib/context.js @@ -34,7 +34,9 @@ class ContextSession { if (session === false) return null; // cookie session store - if (!this.store) this.initFromCookie(); + if (this.session) return this.session; + // create an empty session or init from cookie + this.store ? this.create() : this.initFromCookie(); return this.session; } From bdf51c3cce5d49d2255b9b4a499587a0026f7309 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 00:04:43 +0800 Subject: [PATCH 51/84] Release 5.12.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 76fd3f6..df11f48 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.12.1 / 2019-07-10 +================== + +**fixes** + * [[`77968e3`](http://github.com/koajs/session/commit/77968e3ff6fb5d4f4a36665474ccd992fed689ec)] - fix: ensure ctx.session always has value (dead-horse <>) + 5.12.0 / 2019-05-17 ================== diff --git a/package.json b/package.json index 4a38ac9..a1a072a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.0", + "version": "5.12.1", "keywords": [ "koa", "middleware", From c23bab4023b95c65be46b4eeaf089608ddaa738e Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 02:00:50 +0800 Subject: [PATCH 52/84] fix: remvoe unused code --- lib/context.js | 2 -- test/store.test.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/context.js b/lib/context.js index ac3a546..c0437b4 100644 --- a/lib/context.js +++ b/lib/context.js @@ -33,8 +33,6 @@ class ContextSession { // unset if (session === false) return null; - // cookie session store - if (this.session) return this.session; // create an empty session or init from cookie this.store ? this.create() : this.initFromCookie(); return this.session; diff --git a/test/store.test.js b/test/store.test.js index 7f9587c..f2f0765 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -769,6 +769,42 @@ describe('Koa Session External Store', () => { should.not.exist(res.headers['set-cookie']); }); }); + + describe('when get session before middleware', () => { + it('should return empty session', async () => { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + const options = {}; + options.store = store; + app.use(async (ctx, next) => { + // will not take effect + ctx.session.should.be.ok(); + ctx.session.foo = '123'; + await next(); + }); + app.use(session(options, app)); + app.use(async ctx => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + + let res = await request(app.callback()) + .get('/') + .expect({}); + + 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' }); + }); + }); }); function App(options) { From 10bb12246699101a0c87a2f3e2e09b1a79e10e33 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 10 Jul 2019 02:02:02 +0800 Subject: [PATCH 53/84] Release 5.12.2 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index df11f48..679f32a 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.12.2 / 2019-07-10 +================== + +**fixes** + * [[`c23bab4`](http://github.com/koajs/session/commit/c23bab4023b95c65be46b4eeaf089608ddaa738e)] - fix: remvoe unused code (dead-horse <>) + 5.12.1 / 2019-07-10 ================== diff --git a/package.json b/package.json index a1a072a..1395265 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.1", + "version": "5.12.2", "keywords": [ "koa", "middleware", From 909d93fc6b74c6e29b0e83f555f1fc4002a6a108 Mon Sep 17 00:00:00 2001 From: Justin Date: Fri, 23 Aug 2019 14:12:31 +1000 Subject: [PATCH 54/84] fix: correctly expire cookies for nullified sessions Changes: - On destroy, set 'maxAge' to be false - On destroy, set the 'expires' flag to be UNIXTIME epoch, which the 'Cookie' module relies on --- lib/context.js | 8 +++++++- lib/util.js | 2 ++ test/cookie.test.js | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/context.js b/lib/context.js index c0437b4..1bb41a8 100644 --- a/lib/context.js +++ b/lib/context.js @@ -4,6 +4,7 @@ const debug = require('debug')('koa-session:context'); const Session = require('./session'); const util = require('./util'); +const COOKIE_EXP_DATE = new Date(util.CookieDateEpoch); const ONE_DAY = 24 * 60 * 60 * 1000; class ContextSession { @@ -273,7 +274,12 @@ class ContextSession { */ async remove() { - const opts = this.opts; + // Override the default options so that we can properly expire the session cookies + const opts = Object.assign({}, this.opts, { + expires: COOKIE_EXP_DATE, + maxAge: false, + }); + const ctx = this.ctx; const key = opts.key; const externalKey = this.externalKey; diff --git a/lib/util.js b/lib/util.js index 759197c..b3a1a28 100644 --- a/lib/util.js +++ b/lib/util.js @@ -34,4 +34,6 @@ module.exports = { hash(sess) { return crc(JSON.stringify(sess)); }, + + CookieDateEpoch: 'Thu, 01 Jan 1970 00:00:00 GMT', }; diff --git a/test/cookie.test.js b/test/cookie.test.js index bf0e569..1f14d2a 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -249,6 +249,47 @@ describe('Koa Session Cookie', () => { }); }); + describe('after session set to null with signed cookie', () => { + it('should return expired cookies', done => { + const app = App({ + signed: true, + }); + + app.use(async function(ctx) { + ctx.session.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa:sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200, done); + }); + }); + + describe('after session set to null without signed cookie', () => { + it('should return expired cookies', done => { + const app = App({ + signed: false, + }); + + app.use(async function(ctx) { + ctx.session.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200, done); + }); + }); + describe('when get session after set to null', () => { it('should return null', done => { const app = App(); From f81d7130ff4fd44ad97bd0b807034bd46964db8a Mon Sep 17 00:00:00 2001 From: dead-horse Date: Fri, 23 Aug 2019 13:05:22 +0800 Subject: [PATCH 55/84] Release 5.12.3 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 679f32a..c1cba58 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.12.3 / 2019-08-23 +================== + +**fixes** + * [[`909d93f`](http://github.com/koajs/session/commit/909d93fc6b74c6e29b0e83f555f1fc4002a6a108)] - fix: correctly expire cookies for nullified sessions (Justin <>) + 5.12.2 / 2019-07-10 ================== diff --git a/package.json b/package.json index 1395265..afa488b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.2", + "version": "5.12.3", "keywords": [ "koa", "middleware", From cb09a09cfa4767610d7cc7282a0de2a3a651c6ae Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 1 Feb 2020 19:50:29 +0800 Subject: [PATCH 56/84] feat: support session cookie sameSite options (#193) --- Readme.md | 1 + index.js | 1 + test/cookie.test.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/Readme.md b/Readme.md index c327c2e..60028d8 100644 --- a/Readme.md +++ b/Readme.md @@ -59,6 +59,7 @@ const CONFIG = { 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) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ + sameSite: '', /** (string) session cookie sameSite options (default '', don't set it) */ }; app.use(session(CONFIG, app)); diff --git a/index.js b/index.js index 63dca92..d6bbcea 100644 --- a/index.js +++ b/index.js @@ -68,6 +68,7 @@ function formatOpts(opts) { // defaults if (opts.overwrite == null) opts.overwrite = true; if (opts.httpOnly == null) opts.httpOnly = true; + if (opts.sameSite == null) opts.sameSite = ''; if (opts.signed == null) opts.signed = true; if (opts.autoCommit == null) opts.autoCommit = true; diff --git a/test/cookie.test.js b/test/cookie.test.js index 1f14d2a..cd71825 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -94,6 +94,8 @@ describe('Koa Session Cookie', () => { .expect(204, (err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie']; + // samesite is not set + assert(!cookie.join(';').includes('samesite')); request(server) .get('/') .set('Cookie', cookie.join(';')) @@ -244,6 +246,11 @@ describe('Koa Session Cookie', () => { .get('/') .set('Cookie', cookie) .expect('Set-Cookie', /koa:sess/) + .expect(res => { + const cookie = res.headers['set-cookie']; + // samesite is not set + assert(!cookie.join(';').includes('samesite')); + }) .expect(200, done); }); }); @@ -694,6 +701,44 @@ describe('Koa Session Cookie', () => { }); }); + describe('options.sameSite', () => { + it('should return opt.sameSite=none', done => { + const app = App({ sameSite: 'none' }); + + app.use(async function(ctx) { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + request(app.listen()) + .get('/') + .expect(res => { + const cookie = res.headers['set-cookie'].join('|'); + assert(cookie.includes('path=/; samesite=none; httponly')); + }) + .expect('bar') + .expect(200, done); + }); + + it('should return opt.sameSite=lax', done => { + const app = App({ sameSite: 'lax' }); + + app.use(async function(ctx) { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + request(app.listen()) + .get('/') + .expect(res => { + const cookie = res.headers['set-cookie'].join('|'); + assert(cookie.includes('path=/; samesite=lax; httponly')); + }) + .expect('bar') + .expect(200, done); + }); + }); + describe('when valid and beforeSave set', () => { it('should ignore session when uid changed', done => { const app = new Koa(); From aabe6eba77f472895aa22511d5fa9d2e01f0bd02 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sat, 1 Feb 2020 19:51:52 +0800 Subject: [PATCH 57/84] Release 5.13.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c1cba58..171c66b 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.13.0 / 2020-02-01 +================== + +**features** + * [[`cb09a09`](http://github.com/koajs/session/commit/cb09a09cfa4767610d7cc7282a0de2a3a651c6ae)] - feat: support session cookie sameSite options (#193) (fengmk2 <>) + 5.12.3 / 2019-08-23 ================== diff --git a/package.json b/package.json index afa488b..79703e5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.12.3", + "version": "5.13.0", "keywords": [ "koa", "middleware", From ecd1f5edaa6ff1e77cc461d1107432b394ce21d2 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 1 Feb 2020 20:21:59 +0800 Subject: [PATCH 58/84] fix: don't set any value to sameSite by default (#194) --- Readme.md | 2 +- index.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 60028d8..844a32b 100644 --- a/Readme.md +++ b/Readme.md @@ -59,7 +59,7 @@ const CONFIG = { 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) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ - sameSite: '', /** (string) session cookie sameSite options (default '', don't set it) */ + sameSite: null, /** (string) session cookie sameSite options (default null, don't set it) */ }; app.use(session(CONFIG, app)); diff --git a/index.js b/index.js index d6bbcea..e4f3052 100644 --- a/index.js +++ b/index.js @@ -68,7 +68,8 @@ function formatOpts(opts) { // defaults if (opts.overwrite == null) opts.overwrite = true; if (opts.httpOnly == null) opts.httpOnly = true; - if (opts.sameSite == null) opts.sameSite = ''; + // delete null sameSite config + if (opts.sameSite == null) delete opts.sameSite; if (opts.signed == null) opts.signed = true; if (opts.autoCommit == null) opts.autoCommit = true; From a46d043820c71293aadddd0573b8e7f2fc7ff18d Mon Sep 17 00:00:00 2001 From: dead-horse Date: Sat, 1 Feb 2020 20:22:24 +0800 Subject: [PATCH 59/84] Release 5.13.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 171c66b..37f76a6 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +5.13.1 / 2020-02-01 +================== + +**fixes** + * [[`ecd1f5e`](http://github.com/koajs/session/commit/ecd1f5edaa6ff1e77cc461d1107432b394ce21d2)] - fix: don't set any value to sameSite by default (#194) (fengmk2 <>) + 5.13.0 / 2020-02-01 ================== diff --git a/package.json b/package.json index 79703e5..3528393 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.13.0", + "version": "5.13.1", "keywords": [ "koa", "middleware", From 17b32801145af597f29c0bc367d460129c80d233 Mon Sep 17 00:00:00 2001 From: Dominic Egginton Date: Sat, 18 Apr 2020 14:11:18 +0100 Subject: [PATCH 60/84] add secure flag to readme exmaple (#196) --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 844a32b..80b60ba 100644 --- a/Readme.md +++ b/Readme.md @@ -59,6 +59,7 @@ const CONFIG = { 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) */ renew: false, /** (boolean) renew session when session is nearly expired, so we can always keep user logged in. (default is false)*/ + secure: true, /** (boolean) secure cookie*/ sameSite: null, /** (string) session cookie sameSite options (default null, don't set it) */ }; From 23f7f52a9bd1e21094d7cfa42fc7b9178be546d4 Mon Sep 17 00:00:00 2001 From: Dominic Egginton Date: Fri, 24 Apr 2020 05:11:33 +0100 Subject: [PATCH 61/84] chore: disabled package-lock.json (#198) --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file From 7fca3deda6cb78524992adfd1f520a6e664d52b0 Mon Sep 17 00:00:00 2001 From: Zac Anger Date: Tue, 28 Apr 2020 16:15:54 +0000 Subject: [PATCH 62/84] [BREAKING CHANGE] fix: RFC6265 default cookie name (#197) --- History.md | 6 + Readme.md | 4 +- index.js | 4 +- package-lock.json | 2076 +++++++++++++++++++++++++++++++++++++ test/contextstore.test.js | 40 +- test/cookie.test.js | 40 +- test/store.test.js | 32 +- 7 files changed, 2142 insertions(+), 60 deletions(-) create mode 100644 package-lock.json diff --git a/History.md b/History.md index 37f76a6..40a77ce 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,9 @@ +6.0.0 / 2020-04-26 +================== + +**fixes** + * [[`d34fc8e`](https://github.com/koajs/session/commit/d34fc8e0395bd3dc0c8cceda4374039a4d414060)] - fix: RFC6265 compliant default cookie name (#197) (zacanger <>) + * [BREAKING CHANGE]: Default cookie is now `koa.sess` rather than `koa:sess` 5.13.1 / 2020-02-01 ================== diff --git a/Readme.md b/Readme.md index 80b60ba..fb5664f 100644 --- a/Readme.md +++ b/Readme.md @@ -48,7 +48,7 @@ const app = new Koa(); app.keys = ['some secret hurr']; const CONFIG = { - key: 'koa:sess', /** (string) cookie key (default is koa:sess) */ + key: 'koa.sess', /** (string) cookie key (default is koa.sess) */ /** (number || 'session') maxAge in ms (default is 1 days) */ /** 'session' will result in a cookie that expires when session/browser is closed */ /** Warning: If a session cookie is stolen, this cookie will never expire */ @@ -84,7 +84,7 @@ console.log('listening on port 3000'); ### Options The cookie name is controlled by the `key` option, which defaults - to "koa:sess". All other options are passed to `ctx.cookies.get()` and + to "koa.sess". All other options are passed to `ctx.cookies.get()` and `ctx.cookies.set()` allowing you to control security, domain, path, and signing among other settings. diff --git a/index.js b/index.js index e4f3052..816a15a 100644 --- a/index.js +++ b/index.js @@ -13,7 +13,7 @@ const _CONTEXT_SESSION = Symbol('context#_contextSession'); /** * Initialize session middleware with `opts`: * - * - `key` session cookie name ["koa:sess"] + * - `key` session cookie name ["koa.sess"] * - all other options are passed as cookie options * * @param {Object} [opts] @@ -60,7 +60,7 @@ module.exports = function(opts, app) { function formatOpts(opts) { opts = opts || {}; // key - opts.key = opts.key || 'koa:sess'; + opts.key = opts.key || 'koa.sess'; // back-compat maxage if (!('maxAge' in opts)) opts.maxAge = opts.maxage; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..d60aff9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2076 @@ +{ + "name": "koa-session", + "version": "5.13.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true, + "optional": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "benchmark": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", + "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", + "dev": true, + "requires": { + "lodash": "^4.17.4", + "platform": "^1.3.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dev": true, + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", + "dev": true + }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dev": true, + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "requires": { + "buffer": "^5.1.0" + } + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", + "dev": true + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + } + } + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-config-egg": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", + "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "dev": true + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dev": true, + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "interpret": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", + "dev": true + }, + "is-class-hotfix": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", + "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "dev": true + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", + "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-type-of": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", + "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", + "requires": { + "core-util-is": "^1.0.2", + "is-class-hotfix": "~0.0.6", + "isstream": "~0.1.2" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dev": true, + "requires": { + "tsscmp": "1.0.6" + } + }, + "ko-sleep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", + "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "koa": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.11.0.tgz", + "integrity": "sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==", + "dev": true, + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", + "dev": true + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dev": true, + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dev": true, + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dev": true, + "requires": { + "mime-db": "1.43.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mm": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/mm/-/mm-2.5.0.tgz", + "integrity": "sha512-ilm+lGEBNm7Cw45um9ax0tbApiNwQV3PY6Yk1ol+wtA8c98hHuJqTgmdKB6rYQJTUC2QrhBfoWwN+/766ZlrYA==", + "dev": true, + "requires": { + "is-type-of": "^1.0.0", + "ko-sleep": "^1.0.2", + "muk-prop": "^1.0.0", + "thenify": "^3.2.1" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "muk-prop": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", + "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "mz-modules": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", + "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", + "dev": true, + "requires": { + "glob": "^7.1.2", + "ko-sleep": "^1.0.3", + "mkdirp": "^0.5.1", + "pump": "^3.0.0", + "rimraf": "^2.6.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pedding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", + "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", + "dev": true + }, + "platform": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", + "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", + "dev": true + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", + "dev": true + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "should": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", + "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", + "dev": true, + "requires": { + "should-equal": "0.8.0", + "should-format": "0.3.2", + "should-type": "0.2.0" + } + }, + "should-equal": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", + "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-format": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", + "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", + "dev": true, + "requires": { + "should-type": "0.2.0" + } + }, + "should-type": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", + "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + } + }, + "supertest": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", + "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", + "slice-ansi": "0.0.4", + "string-width": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", + "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.3" + } + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dev": true, + "requires": { + "random-bytes": "~1.0.0" + } + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "dev": true + } + } +} diff --git a/test/contextstore.test.js b/test/contextstore.test.js index 57cfb44..cf71eb5 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -87,7 +87,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -107,7 +107,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -200,7 +200,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') .set('Cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -218,7 +218,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -253,7 +253,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -285,7 +285,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect({ foo: 'bar' }) .expect(200, done); }); @@ -302,7 +302,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('1') .expect(200, done); }); @@ -319,7 +319,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('true') .expect(200, done); }); @@ -336,7 +336,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('hello') .expect(200, done); }); @@ -371,7 +371,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(401, done); }); }); @@ -415,7 +415,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end(err => { if (err) return done(err); }) @@ -441,7 +441,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -463,7 +463,7 @@ describe('Koa Session External Context Store', () => { request(server) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect('hi 1') .end((err, res) => { if (err) return done(err); @@ -473,7 +473,7 @@ describe('Koa Session External Context Store', () => { request(server) .get('/') .set('cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect('hi 2') .end((err, res) => { if (err) return done(err); @@ -483,7 +483,7 @@ describe('Koa Session External Context Store', () => { request(server) .get('/') .set('cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect('hi 3') .end((err, res) => { if (err) return done(err); @@ -510,7 +510,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -539,7 +539,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -570,7 +570,7 @@ describe('Koa Session External Context Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -655,7 +655,7 @@ describe('Koa Session External Context Store', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-key') + .set('cookie', 'koa.sess=invalid-key') .expect('true') .expect(200, done); }); @@ -693,7 +693,7 @@ describe('Koa Session External Context Store', () => { should.not.exist(err); const data = res.body; const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') diff --git a/test/cookie.test.js b/test/cookie.test.js index cd71825..568d2b1 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -153,7 +153,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -245,7 +245,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') .set('Cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(res => { const cookie = res.headers['set-cookie']; // samesite is not set @@ -270,8 +270,8 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('Set-Cookie', /koa:sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa\.sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) .expect('true') .expect(200, done); }); @@ -291,7 +291,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) .expect('true') .expect(200, done); }); @@ -309,7 +309,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=;/) + .expect('Set-Cookie', /koa\.sess=;/) .expect('true') .expect(200, done); }); @@ -326,7 +326,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-session;') + .set('cookie', 'koa.sess=invalid-session;') .expect('true') .expect(200, done); }); @@ -347,8 +347,8 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-session;') - .expect('Set-Cookie', /koa:sess=;/) + .set('cookie', 'koa.sess=invalid-session;') + .expect('Set-Cookie', /koa\.sess=;/) .expect(500, done); }); }); @@ -390,7 +390,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect({ foo: 'bar' }) .expect(200, done); }); @@ -407,7 +407,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('1') .expect(200, done); }); @@ -424,7 +424,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('true') .expect(200, done); }); @@ -441,7 +441,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('hello') .expect(200, done); }); @@ -460,7 +460,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -495,7 +495,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -543,7 +543,7 @@ describe('Koa Session Cookie', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(401, done); }); }); @@ -566,7 +566,7 @@ describe('Koa Session Cookie', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -597,7 +597,7 @@ describe('Koa Session Cookie', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -691,7 +691,7 @@ describe('Koa Session Cookie', () => { .expect(200, (err, res) => { should.not.exist(err); const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') @@ -770,7 +770,7 @@ describe('Koa Session Cookie', () => { should.not.exist(err); const data = res.body; const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') diff --git a/test/store.test.js b/test/store.test.js index f2f0765..d1bbf93 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -90,7 +90,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, (err, res) => { if (err) return done(err); cookie = res.header['set-cookie'].join(';'); @@ -182,7 +182,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') .set('Cookie', cookie) - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -200,7 +200,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -235,7 +235,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(200, done); }); }); @@ -267,7 +267,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect({ foo: 'bar' }) .expect(200, done); }); @@ -284,7 +284,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('1') .expect(200, done); }); @@ -301,7 +301,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('true') .expect(200, done); }); @@ -318,7 +318,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess=.+;/) + .expect('Set-Cookie', /koa\.sess=.+;/) .expect('hello') .expect(200, done); }); @@ -353,7 +353,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .expect(401, done); }); }); @@ -375,7 +375,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -401,7 +401,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -430,7 +430,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -467,7 +467,7 @@ describe('Koa Session External Store', () => { request(server) .post('/') - .expect('Set-Cookie', /koa:sess/) + .expect('Set-Cookie', /koa\.sess/) .end((err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie'].join(';'); @@ -559,7 +559,7 @@ describe('Koa Session External Store', () => { request(app.listen()) .get('/') - .set('cookie', 'koa:sess=invalid-key') + .set('cookie', 'koa.sess=invalid-key') .expect('true') .expect(200, done); }); @@ -604,7 +604,7 @@ describe('Koa Session External Store', () => { should.not.exist(err); const data = res.body; const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa:sess='); + cookies.should.containEql('koa.sess='); request(app.callback()) .get('/') @@ -710,7 +710,7 @@ describe('Koa Session External Store', () => { .expect(204, (err, res) => { if (err) return done(err); const cookie = res.headers['set-cookie']; - cookie.join().should.match(/koa:sess=sess:/); + cookie.join().should.match(/koa\.sess=sess:/); request(server) .get('/') .set('Cookie', cookie.join(';')) From 6c9df069b0ee9da5410e0ff1e652c8c1a274f07f Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 29 Apr 2020 00:16:52 +0800 Subject: [PATCH 63/84] chore: remove package-lock --- .npmrc | 2 +- package-lock.json | 2076 --------------------------------------------- 2 files changed, 1 insertion(+), 2077 deletions(-) delete mode 100644 package-lock.json diff --git a/.npmrc b/.npmrc index 9cf9495..43c97e7 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -package-lock=false \ No newline at end of file +package-lock=false diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index d60aff9..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2076 +0,0 @@ -{ - "name": "koa-session", - "version": "5.13.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", - "dev": true - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true, - "optional": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, - "benchmark": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz", - "integrity": "sha1-CfPeMckWQl1JjMLuVloOvzwqVik=", - "dev": true, - "requires": { - "lodash": "^4.17.4", - "platform": "^1.3.3" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-content-type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", - "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", - "dev": true, - "requires": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "cli-width": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", - "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "cookiejar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==", - "dev": true - }, - "cookies": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", - "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", - "dev": true, - "requires": { - "depd": "~2.0.0", - "keygrip": "~1.1.0" - }, - "dependencies": { - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "crc": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", - "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "requires": { - "buffer": "^5.1.0" - } - }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "requires": { - "ms": "^2.1.1" - } - }, - "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "dev": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-inject": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", - "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=", - "dev": true - }, - "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "dev": true, - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-map": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", - "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-set": "~0.1.5", - "es6-symbol": "~3.1.1", - "event-emitter": "~0.3.5" - } - }, - "es6-set": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", - "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14", - "es6-iterator": "~2.0.1", - "es6-symbol": "3.1.1", - "event-emitter": "~0.3.5" - }, - "dependencies": { - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - } - } - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", - "dev": true, - "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", - "dev": true - } - } - }, - "escope": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", - "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", - "dev": true, - "requires": { - "es6-map": "^0.1.3", - "es6-weak-map": "^2.0.1", - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint": { - "version": "3.19.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", - "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", - "dev": true, - "requires": { - "babel-code-frame": "^6.16.0", - "chalk": "^1.1.3", - "concat-stream": "^1.5.2", - "debug": "^2.1.1", - "doctrine": "^2.0.0", - "escope": "^3.6.0", - "espree": "^3.4.0", - "esquery": "^1.0.0", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "glob": "^7.0.3", - "globals": "^9.14.0", - "ignore": "^3.2.0", - "imurmurhash": "^0.1.4", - "inquirer": "^0.12.0", - "is-my-json-valid": "^2.10.0", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.5.1", - "json-stable-stringify": "^1.0.0", - "levn": "^0.3.0", - "lodash": "^4.0.0", - "mkdirp": "^0.5.0", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.1", - "pluralize": "^1.2.1", - "progress": "^1.1.8", - "require-uncached": "^1.0.2", - "shelljs": "^0.7.5", - "strip-bom": "^3.0.0", - "strip-json-comments": "~2.0.1", - "table": "^3.7.8", - "text-table": "~0.2.0", - "user-home": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-config-egg": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-egg/-/eslint-config-egg-3.2.0.tgz", - "integrity": "sha1-eb/dndaNh21oIHQSuQUymgfUL4g=", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", - "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "dev": true, - "requires": { - "type": "^2.0.0" - }, - "dependencies": { - "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" - } - }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", - "dev": true, - "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" - } - }, - "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formidable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", - "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "generate-function": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", - "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", - "dev": true, - "requires": { - "is-property": "^1.0.2" - } - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "http-assert": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", - "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", - "dev": true, - "requires": { - "deep-equal": "~1.0.1", - "http-errors": "~1.7.2" - } - }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", - "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "ansi-regex": "^2.0.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "readline2": "^1.0.1", - "run-async": "^0.1.0", - "rx-lite": "^3.1.2", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==", - "dev": true - }, - "is-class-hotfix": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/is-class-hotfix/-/is-class-hotfix-0.0.6.tgz", - "integrity": "sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-generator-function": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", - "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", - "dev": true - }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz", - "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-type-of": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-type-of/-/is-type-of-1.2.1.tgz", - "integrity": "sha512-uK0kyX9LZYhSDS7H2sVJQJop1UnWPWmo5RvR3q2kFH6AUHYs7sOrVg0b4nyBHw29kRRNFofYN/JbHZDlHiItTA==", - "requires": { - "core-util-is": "^1.0.2", - "is-class-hotfix": "~0.0.6", - "isstream": "~0.1.2" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "istanbul": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", - "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", - "dev": true, - "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, - "keygrip": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", - "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", - "dev": true, - "requires": { - "tsscmp": "1.0.6" - } - }, - "ko-sleep": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ko-sleep/-/ko-sleep-1.0.3.tgz", - "integrity": "sha1-KKKgoUhei39BX/SI3uF9JHiKsII=", - "dev": true, - "requires": { - "ms": "^2.0.0" - } - }, - "koa": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.11.0.tgz", - "integrity": "sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==", - "dev": true, - "requires": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.8.0", - "debug": "~3.1.0", - "delegates": "^1.0.0", - "depd": "^1.1.2", - "destroy": "^1.0.4", - "encodeurl": "^1.0.2", - "error-inject": "^1.0.0", - "escape-html": "^1.0.3", - "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", - "koa-compose": "^4.1.0", - "koa-convert": "^1.2.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", - "vary": "^1.1.2" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", - "dev": true - }, - "koa-convert": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", - "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", - "dev": true, - "requires": { - "co": "^4.6.0", - "koa-compose": "^3.0.0" - }, - "dependencies": { - "koa-compose": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", - "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", - "dev": true, - "requires": { - "any-promise": "^1.1.0" - } - } - } - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", - "dev": true, - "requires": { - "mime-db": "1.43.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mm": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/mm/-/mm-2.5.0.tgz", - "integrity": "sha512-ilm+lGEBNm7Cw45um9ax0tbApiNwQV3PY6Yk1ol+wtA8c98hHuJqTgmdKB6rYQJTUC2QrhBfoWwN+/766ZlrYA==", - "dev": true, - "requires": { - "is-type-of": "^1.0.0", - "ko-sleep": "^1.0.2", - "muk-prop": "^1.0.0", - "thenify": "^3.2.1" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "muk-prop": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/muk-prop/-/muk-prop-1.2.1.tgz", - "integrity": "sha512-NdkOVav3GoIkBZqMUneU435HW0a90zitpuO1erPRhOQdPtl65dXD3G9/1k46G6/0ZMau4CJFFUHkMKVsyNZT+w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", - "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", - "dev": true - }, - "mz-modules": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mz-modules/-/mz-modules-2.1.0.tgz", - "integrity": "sha512-sjk8lcRW3vrVYnZ+W+67L/2rL+jbO5K/N6PFGIcLWTiYytNr22Ah9FDXFs+AQntTM1boZcoHi5qS+CV1seuPog==", - "dev": true, - "requires": { - "glob": "^7.1.2", - "ko-sleep": "^1.0.3", - "mkdirp": "^0.5.1", - "pump": "^3.0.0", - "rimraf": "^2.6.1" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", - "dev": true - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=", - "dev": true - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "pedding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pedding/-/pedding-1.1.0.tgz", - "integrity": "sha1-97E4wojUvVhOraEhX1vZJPHh5mc=", - "dev": true - }, - "platform": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.5.tgz", - "integrity": "sha512-TuvHS8AOIZNAlE77WUDiR4rySV/VMptyMfcfeoMgs4P8apaZM3JrnbzBiixKUv+XR6i+BXrQh8WAnjaSPFO65Q==", - "dev": true - }, - "pluralize": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", - "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", - "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "qs": { - "version": "6.9.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", - "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", - "dev": true - }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readline2": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", - "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "mute-stream": "0.0.5" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", - "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", - "dev": true, - "requires": { - "once": "^1.3.0" - } - }, - "rx-lite": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", - "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "should": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/should/-/should-8.4.0.tgz", - "integrity": "sha1-XmCInT5kS73Tl6MM00+tKPz5C8A=", - "dev": true, - "requires": { - "should-equal": "0.8.0", - "should-format": "0.3.2", - "should-type": "0.2.0" - } - }, - "should-equal": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-0.8.0.tgz", - "integrity": "sha1-o/BXMv9FusG3ukEvhAiFaBlkEpk=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-format": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/should-format/-/should-format-0.3.2.tgz", - "integrity": "sha1-pZgx4Bot3uFJkRvHFIvlyAMZ4f8=", - "dev": true, - "requires": { - "should-type": "0.2.0" - } - }, - "should-type": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/should-type/-/should-type-0.2.0.tgz", - "integrity": "sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", - "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "superagent": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", - "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", - "dev": true, - "requires": { - "component-emitter": "^1.2.0", - "cookiejar": "^2.1.0", - "debug": "^3.1.0", - "extend": "^3.0.0", - "form-data": "^2.3.1", - "formidable": "^1.2.0", - "methods": "^1.1.1", - "mime": "^1.4.1", - "qs": "^6.5.1", - "readable-stream": "^2.3.5" - } - }, - "supertest": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.4.2.tgz", - "integrity": "sha512-WZWbwceHUo2P36RoEIdXvmqfs47idNNZjCuJOqDz6rvtkk8ym56aU5oglORCpPeXGxT7l9rkJ41+O1lffQXYSA==", - "dev": true, - "requires": { - "methods": "^1.1.2", - "superagent": "^3.8.3" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", - "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", - "dev": true, - "requires": { - "ajv": "^4.7.0", - "ajv-keywords": "^1.0.0", - "chalk": "^1.1.1", - "lodash": "^4.0.0", - "slice-ansi": "0.0.4", - "string-width": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "thenify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", - "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", - "dev": true, - "requires": { - "any-promise": "^1.0.0" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tsscmp": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", - "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", - "dev": true - }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.1.tgz", - "integrity": "sha512-JUPoL1jHsc9fOjVFHdQIhqEEJsQvfKDjlubcCilu8U26uZ73qOg8VsN8O1jbuei44ZPlwL7kmbAdM4tzaUvqnA==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.3" - } - }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dev": true, - "requires": { - "random-bytes": "~1.0.0" - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, - "ylru": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", - "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", - "dev": true - } - } -} From e630f7386f269d233f64e270c0eecd1eda91312b Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 29 Apr 2020 00:23:20 +0800 Subject: [PATCH 64/84] docs: add migrate from 5.x to 6.x --- Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Readme.md b/Readme.md index fb5664f..35bae2d 100644 --- a/Readme.md +++ b/Readme.md @@ -36,6 +36,10 @@ $ npm install koa-session ``` +## Notice + +6.x changed the default cookie key from `koa:sess` to `koa.sess` to ensure `set-cookie` value valid with HTTP spec.[see issue](https://github.com/koajs/session/issues/28). If you want to be compatible with the previous version, you can manually set `config.key` to `koa:sess`. + ## Example View counter example: From 136b9034c374b9a51f05973d79e1a8d9a2c83b90 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Wed, 29 Apr 2020 00:25:28 +0800 Subject: [PATCH 65/84] Release 6.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3528393..f05b363 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "5.13.1", + "version": "6.0.0", "keywords": [ "koa", "middleware", From f76559568bb7f6321cab3f44ae759521deca3dd1 Mon Sep 17 00:00:00 2001 From: Dominic Egginton Date: Tue, 28 Apr 2020 17:26:12 +0100 Subject: [PATCH 66/84] chore: Create LICENSE File (#195) --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eaf38ab --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2014-present Koajs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 32e352665f2adbcda34d1d990bb6c5d875c0b625 Mon Sep 17 00:00:00 2001 From: Ngorror Date: Thu, 8 Oct 2020 11:35:20 +0200 Subject: [PATCH 67/84] feat: add context to external store .get() and .set() options params (#201) --- Readme.md | 6 +-- lib/context.js | 5 ++- test/store_with_ctx.js | 23 +++++++++++ test/store_with_ctx.test.js | 81 +++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 test/store_with_ctx.js create mode 100644 test/store_with_ctx.test.js diff --git a/Readme.md b/Readme.md index 35bae2d..ee59469 100644 --- a/Readme.md +++ b/Readme.md @@ -111,9 +111,9 @@ console.log('listening on port 3000'); You can store the session content in external stores (Redis, MongoDB or other DBs) by passing `options.store` with three methods (these need to be async functions): - - `get(key, maxAge, { rolling })`: get session object by key - - `set(key, sess, maxAge, { rolling, changed })`: set session object for key, with a `maxAge` (in ms) - - `destroy(key)`: destroy session for key + - `get(key, maxAge, { rolling, ctx })`: get session object by key + - `set(key, sess, maxAge, { rolling, changed, ctx })`: set session object for key, with a `maxAge` (in ms) + - `destroy(key, {ctx})`: destroy session for key Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** diff --git a/lib/context.js b/lib/context.js index 1bb41a8..ccb8d5c 100644 --- a/lib/context.js +++ b/lib/context.js @@ -87,7 +87,7 @@ class ContextSession { return; } - const json = await this.store.get(externalKey, opts.maxAge, { rolling: opts.rolling }); + const json = await this.store.get(externalKey, opts.maxAge, { ctx, rolling: opts.rolling }); if (!this.valid(json, externalKey)) { // create a new `externalKey` this.create(); @@ -284,7 +284,7 @@ class ContextSession { const key = opts.key; const externalKey = this.externalKey; - if (externalKey) await this.store.destroy(externalKey); + if (externalKey) await this.store.destroy(externalKey, { ctx }); ctx.cookies.set(key, '', opts); } @@ -320,6 +320,7 @@ class ContextSession { } await this.store.set(externalKey, json, maxAge, { changed, + ctx: this.ctx, rolling: opts.rolling, }); if (opts.externalKey) { diff --git a/test/store_with_ctx.js b/test/store_with_ctx.js new file mode 100644 index 0000000..2ebd234 --- /dev/null +++ b/test/store_with_ctx.js @@ -0,0 +1,23 @@ +'use strict'; + +const sessions = {}; + +module.exports = { + async get(key, maxAge, options) { + // check access to options.ctx + options.ctx.state.test = 'get'; + return sessions[key]; + }, + + async set(key, sess, maxAge, options) { + // check access to options.ctx + options.ctx.state.test = 'set'; + sessions[key] = sess; + }, + + async destroy(key, options) { + // check access to options.ctx + options.ctx.state.test = 'destroyed'; + sessions[key] = undefined; + }, +}; diff --git a/test/store_with_ctx.test.js b/test/store_with_ctx.test.js new file mode 100644 index 0000000..21fe43a --- /dev/null +++ b/test/store_with_ctx.test.js @@ -0,0 +1,81 @@ +'use strict'; + +const Koa = require('koa'); +const request = require('supertest'); +const session = require('..'); +const store = require('./store_with_ctx'); + +describe('Koa Session External Store methods can acceess Koa context', () => { + let cookie; + + describe('new session', () => { + describe('when not accessed', () => { + it('should not set ctx.state.test variable', async () => { + const app = App(); + + request(app.callback()) + .get('/') + .expect('undefined'); + }); + }); + + describe('when populated', () => { + it('should set ctx.state.test variable', done => { + const app = App(); + + app.use(async ctx => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + }); + + request(app.listen()) + .get('/set') + .expect(200, (err, res) => { + if (err) return done(err); + cookie = res.header['set-cookie'].join(';'); + res.text.should.equal('set'); + done(); + }); + }); + }); + + describe('when accessed', () => { + it('should access ctx.state.test variable', async () => { + const app = App(); + + request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('get'); + }); + }); + + describe('session destroyed', () => { + it('should access ctx.state.test variable', async () => { + const app = App(); + + app.use(async ctx => { + if (ctx.path === '/destroy') ctx.session = null; + }); + + request(app.callback()) + .get('/destroy') + .set('Cookie', cookie) + .expect('destroyed'); + }); + }); + }); +}); + +function App(options) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options = options || {}; + options.store = store; + app.use(async (ctx, next) => { + await next(); + ctx.body = ctx.state.test === undefined ? 'undefined' : ctx.state.test; + }); + + app.use(session(options, app)); + return app; +} From bbd9d238778e4bb8f686f20cd3454a6a9e8115bb Mon Sep 17 00:00:00 2001 From: dead-horse Date: Thu, 8 Oct 2020 17:40:06 +0800 Subject: [PATCH 68/84] Release 6.1.0 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 40a77ce..1744063 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,12 @@ + +6.1.0 / 2020-10-08 +================== + +**features** + * [[`32e3526`](http://github.com/koajs/session/commit/32e352665f2adbcda34d1d990bb6c5d875c0b625)] - feat: add context to external store .get() and .set() options params (#201) (Ngorror <>) + +**others** + * [[`f765595`](http://github.com/koajs/session/commit/f76559568bb7f6321cab3f44ae759521deca3dd1)] - chore: Create LICENSE File (#195) (Dominic Egginton <>) 6.0.0 / 2020-04-26 ================== diff --git a/package.json b/package.json index f05b363..81a3fa6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.0.0", + "version": "6.1.0", "keywords": [ "koa", "middleware", From 7cde341db1691ee6885eb1b11ff3a3a3632fd5ce Mon Sep 17 00:00:00 2001 From: Yiyu He Date: Tue, 30 Mar 2021 18:27:14 +0800 Subject: [PATCH 69/84] feat: add session.externalKey (#207) --- Readme.md | 4 ++++ lib/context.js | 2 +- lib/session.js | 15 +++++++++++++-- test/contextstore.test.js | 4 +++- test/cookie.test.js | 4 +++- test/externalkey.test.js | 3 +++ test/store.test.js | 4 +++- 7 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index ee59469..b5f0561 100644 --- a/Readme.md +++ b/Readme.md @@ -160,6 +160,10 @@ if (this.session.isNew) { Set cookie's maxAge. +### Session#externalKey + + Get session external key, only exist when external session store present. + ### Session#save() Save this session no matter whether it is populated. diff --git a/lib/context.js b/lib/context.js index ccb8d5c..95681a0 100644 --- a/lib/context.js +++ b/lib/context.js @@ -204,7 +204,7 @@ class ContextSession { create(val, externalKey) { debug('create session with val: %j externalKey: %s', val, externalKey); if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx); - this.session = new Session(this, val); + this.session = new Session(this, val, this.externalKey); } /** diff --git a/lib/session.js b/lib/session.js index 6961e3b..c9019af 100644 --- a/lib/session.js +++ b/lib/session.js @@ -4,6 +4,8 @@ * Session model. */ +const inspect = Symbol.for('nodejs.util.inspect.custom'); + class Session { /** * Session constructor @@ -12,9 +14,10 @@ class Session { * @api private */ - constructor(sessionContext, obj) { + constructor(sessionContext, obj, externalKey) { this._sessCtx = sessionContext; this._ctx = sessionContext.ctx; + this._externalKey = externalKey; if (!obj) { this.isNew = true; } else { @@ -52,7 +55,7 @@ class Session { * @api public */ - inspect() { + [inspect]() { return this.toJSON(); } @@ -103,6 +106,14 @@ class Session { this._requireSave = true; } + /** + * get session external key + * only exist if opts.store present + */ + get externalKey() { + return this._externalKey; + } + /** * save this session no matter whether it is populated * diff --git a/test/contextstore.test.js b/test/contextstore.test.js index cf71eb5..b31fada 100644 --- a/test/contextstore.test.js +++ b/test/contextstore.test.js @@ -7,6 +7,8 @@ const mm = require('mm'); const session = require('..'); const ContextStore = require('./context_store'); +const inspect = Symbol.for('nodejs.util.inspect.custom'); + describe('Koa Session External Context Store', () => { let cookie; @@ -280,7 +282,7 @@ describe('Koa Session External Context Store', () => { app.use(async function(ctx) { ctx.session.foo = 'bar'; - ctx.body = ctx.session.inspect(); + ctx.body = ctx.session[inspect](); }); request(app.listen()) diff --git a/test/cookie.test.js b/test/cookie.test.js index 568d2b1..f3fe914 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -6,6 +6,8 @@ const request = require('supertest'); const should = require('should'); const session = require('..'); +const inspect = Symbol.for('nodejs.util.inspect.custom'); + describe('Koa Session Cookie', () => { let cookie; @@ -385,7 +387,7 @@ describe('Koa Session Cookie', () => { app.use(async function(ctx) { ctx.session.foo = 'bar'; - ctx.body = ctx.session.inspect(); + ctx.body = ctx.session[inspect](); }); request(app.listen()) diff --git a/test/externalkey.test.js b/test/externalkey.test.js index c3625cb..3dfd8c1 100644 --- a/test/externalkey.test.js +++ b/test/externalkey.test.js @@ -5,6 +5,7 @@ const assert = require('assert'); const session = require('..'); const store = require('./store'); const TOKEN_KEY = 'User-Token'; + describe('Koa Session External Key', () => { describe('when the external key set/get is invalid', () => { it('should throw a error', () => { @@ -24,8 +25,10 @@ describe('Koa Session External Key', () => { if (ctx.method === 'POST') { ctx.session.string = ';'; ctx.status = 204; + assert(ctx.session.externalKey); } else { ctx.body = ctx.session.string; + assert(ctx.session.externalKey === ctx.get(TOKEN_KEY)); } }); const server = app.listen(); diff --git a/test/store.test.js b/test/store.test.js index d1bbf93..caf5375 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -10,6 +10,8 @@ const pedding = require('pedding'); const assert = require('assert'); const sleep = require('mz-modules/sleep'); +const inspect = Symbol.for('nodejs.util.inspect.custom'); + describe('Koa Session External Store', () => { let cookie; @@ -262,7 +264,7 @@ describe('Koa Session External Store', () => { app.use(async function(ctx) { ctx.session.foo = 'bar'; - ctx.body = ctx.session.inspect(); + ctx.body = ctx.session[inspect](); }); request(app.listen()) From 07ca4d7653e864841326117c90e435a90633a0d7 Mon Sep 17 00:00:00 2001 From: dead-horse Date: Tue, 30 Mar 2021 18:27:33 +0800 Subject: [PATCH 70/84] Release 6.2.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 1744063..a5b4e51 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +6.2.0 / 2021-03-30 +================== + +**features** + * [[`7cde341`](http://github.com/koajs/session/commit/7cde341db1691ee6885eb1b11ff3a3a3632fd5ce)] - feat: add session.externalKey (#207) (Yiyu He <>) + 6.1.0 / 2020-10-08 ================== diff --git a/package.json b/package.json index 81a3fa6..6feecb9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.1.0", + "version": "6.2.0", "keywords": [ "koa", "middleware", From 878669ee2c734e3d9902d83f57e21d2113178b79 Mon Sep 17 00:00:00 2001 From: zhennann Date: Tue, 3 Jan 2023 18:00:54 +0800 Subject: [PATCH 71/84] feat: update uuid to v8 (#218) --- index.js | 6 +++--- package.json | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 816a15a..4d149aa 100644 --- a/index.js +++ b/index.js @@ -4,7 +4,7 @@ const debug = require('debug')('koa-session'); const ContextSession = require('./lib/context'); const util = require('./lib/util'); const assert = require('assert'); -const uuid = require('uuid/v4'); +const uuid = require('uuid'); const is = require('is-type-of'); const CONTEXT_SESSION = Symbol('context#contextSession'); @@ -105,8 +105,8 @@ function formatOpts(opts) { } if (!opts.genid) { - if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid()}`; - else opts.genid = uuid; + if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid.v4()}`; + else opts.genid = uuid.v4; } return opts; diff --git a/package.json b/package.json index 6feecb9..87249f9 100644 --- a/package.json +++ b/package.json @@ -23,16 +23,16 @@ "mocha": "^5.2.0", "mz-modules": "^2.0.0", "pedding": "^1.1.0", - "uid-safe": "^2.1.3", "should": "8", - "supertest": "^3.3.0" + "supertest": "^3.3.0", + "uid-safe": "^2.1.3" }, "license": "MIT", "dependencies": { - "crc": "^3.4.4", - "debug": "^3.1.0", - "is-type-of": "^1.0.0", - "uuid": "^3.3.2" + "crc": "^4.0.0", + "debug": "^4.3.3", + "is-type-of": "^1.2.1", + "uuid": "^8.3.2" }, "engines": { "node": ">=7.6" From df2d28ffb177272739964eb5a503f93db870aa28 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:14:21 +0800 Subject: [PATCH 72/84] test: run ci on GitHub Action (#222) --- .github/workflows/nodejs.yml | 27 +++++++++++++++++++++++++++ .travis.yml | 9 --------- Readme.md | 21 ++------------------- package.json | 4 ++-- 4 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/nodejs.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 0000000..a46f3e4 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,27 @@ +name: Node.js CI + +on: + push: + branch: master + pull_request: + branch: master + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [8, 10, 12, 14, 16, 18] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm run lint + - run: npm run ci + - run: npx codecov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 80b9e4a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false -language: node_js -node_js: - - '7' - - '8' - - '10' - - '12' -script: 'npm run test-travis' -after_script: 'npm install coveralls@2 && cat ./coverage/lcov.info | coveralls' diff --git a/Readme.md b/Readme.md index b5f0561..03275e1 100644 --- a/Readme.md +++ b/Readme.md @@ -1,34 +1,17 @@ # koa-session [![NPM version][npm-image]][npm-url] -[![build status][travis-image]][travis-url] -[![Test coverage][coveralls-image]][coveralls-url] -[![Gittip][gittip-image]][gittip-url] -[![David deps][david-image]][david-url] -[![iojs version][iojs-image]][iojs-url] -[![node version][node-image]][node-url] +[![Node.js CI](https://github.com/koajs/session/actions/workflows/nodejs.yml/badge.svg)](https://github.com/koajs/session/actions/workflows/nodejs.yml) [![npm download][download-image]][download-url] [npm-image]: https://img.shields.io/npm/v/koa-session.svg?style=flat-square [npm-url]: https://npmjs.org/package/koa-session -[travis-image]: https://img.shields.io/travis/koajs/session.svg?style=flat-square -[travis-url]: https://travis-ci.org/koajs/session -[coveralls-image]: https://img.shields.io/coveralls/koajs/session.svg?style=flat-square -[coveralls-url]: https://coveralls.io/r/koajs/session?branch=master -[gittip-image]: https://img.shields.io/gittip/fengmk2.svg?style=flat-square -[gittip-url]: https://www.gittip.com/fengmk2/ -[david-image]: https://img.shields.io/david/koajs/session.svg?style=flat-square -[david-url]: https://david-dm.org/koajs/session -[iojs-image]: https://img.shields.io/badge/io.js-%3E=_1.0-yellow.svg?style=flat-square -[iojs-url]: http://iojs.org/ -[node-image]: https://img.shields.io/badge/node.js-%3E=_7.6-green.svg?style=flat-square -[node-url]: http://nodejs.org/download/ [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. - *Requires Node 7.6 or greater for async/await support* + *Requires Node 8.0.0 or greater for async/await support* ## Installation diff --git a/package.json b/package.json index 87249f9..3797976 100644 --- a/package.json +++ b/package.json @@ -35,12 +35,12 @@ "uuid": "^8.3.2" }, "engines": { - "node": ">=7.6" + "node": ">=8.0.0" }, "scripts": { "test": "npm run lint && NODE_ENV=test mocha --exit --require should --reporter spec test/*.test.js", "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --exit --require should test/*.test.js", - "test-travis": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", + "ci": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", "lint": "eslint lib test index.js" } } From 75f5706cc540e6573707ad1fb5e104dac98f44a4 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:15:23 +0800 Subject: [PATCH 73/84] Release 6.3.0 --- History.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index a5b4e51..c06a9c0 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,13 @@ +6.3.0 / 2023-01-03 +================== + +**features** + * [[`878669e`](http://github.com/koajs/session/commit/878669ee2c734e3d9902d83f57e21d2113178b79)] - feat: update uuid to v8 (#218) (zhennann <>) + +**others** + * [[`df2d28f`](http://github.com/koajs/session/commit/df2d28ffb177272739964eb5a503f93db870aa28)] - test: run ci on GitHub Action (#222) (fengmk2 <>) + 6.2.0 / 2021-03-30 ================== diff --git a/package.json b/package.json index 3797976..abf0ef6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.2.0", + "version": "6.3.0", "keywords": [ "koa", "middleware", From e2de39e6acaf5eef9c8660cbd864ecccaa2b60d0 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:32:35 +0800 Subject: [PATCH 74/84] fix: keep crc v3 (#223) avoid peerDependencies WARNING https://github.com/koajs/session/pull/218/files#r1060445443 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index abf0ef6..fe91994 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "license": "MIT", "dependencies": { - "crc": "^4.0.0", + "crc": "^3.8.0", "debug": "^4.3.3", "is-type-of": "^1.2.1", "uuid": "^8.3.2" From 6b7043156471d0a7fe91666fe909baec21743590 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Tue, 3 Jan 2023 18:33:05 +0800 Subject: [PATCH 75/84] Release 6.3.1 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c06a9c0..00f139c 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +6.3.1 / 2023-01-03 +================== + +**fixes** + * [[`e2de39e`](http://github.com/koajs/session/commit/e2de39e6acaf5eef9c8660cbd864ecccaa2b60d0)] - fix: keep crc v3 (#223) (fengmk2 <>) + 6.3.0 / 2023-01-03 ================== diff --git a/package.json b/package.json index fe91994..747493e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.3.0", + "version": "6.3.1", "keywords": [ "koa", "middleware", From 4cd3bef4fc1900b847d2e133e3b2599a711f1aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Sat, 4 Feb 2023 15:53:32 +0100 Subject: [PATCH 76/84] feat: Add Session.regenerate() method (#221) And make both save() and regenerate() compatible with koa-passport closes https://github.com/koajs/session/pull/74 --- lib/context.js | 12 +++++++----- lib/session.js | 32 ++++++++++++++++++++++++++++---- test/cookie.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ test/store.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 9 deletions(-) diff --git a/lib/context.js b/lib/context.js index 95681a0..d3efbec 100644 --- a/lib/context.js +++ b/lib/context.js @@ -213,7 +213,7 @@ class ContextSession { * @api public */ - async commit() { + async commit({ save = false, regenerate = false } = {}) { const session = this.session; const opts = this.opts; const ctx = this.ctx; @@ -226,8 +226,13 @@ class ContextSession { await this.remove(); return; } + if (regenerate) { + await this.remove(); + if (this.store) this.externalKey = opts.genid && opts.genid(ctx); + } - const reason = this._shouldSaveSession(); + // force save session when `session._requireSave` set + const reason = save || regenerate || session._requireSave ? 'force' : this._shouldSaveSession(); debug('should save session: %s', reason); if (!reason) return; @@ -243,9 +248,6 @@ class ContextSession { 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 ''; diff --git a/lib/session.js b/lib/session.js index c9019af..202acc1 100644 --- a/lib/session.js +++ b/lib/session.js @@ -117,11 +117,23 @@ class Session { /** * save this session no matter whether it is populated * + * @param {Function} callback the optional function to call after saving the session * @api public */ - save() { - this._requireSave = true; + save(callback) { + return this.commit({ save: true }, callback); + } + + /** + * regenerate this session + * + * @param {Function} callback the optional function to call after regenerating the session + * @api public + */ + + regenerate(callback) { + return this.commit({ regenerate: true }, callback); } /** @@ -130,10 +142,22 @@ class Session { * @api public */ - async manuallyCommit() { - await this._sessCtx.commit(); + manuallyCommit() { + return this.commit(); } + commit(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + const promise = this._sessCtx.commit(options); + if (callback) { + promise.then(() => callback(), callback); + } else { + return promise; + } + } } module.exports = Session; diff --git a/test/cookie.test.js b/test/cookie.test.js index f3fe914..69b0683 100644 --- a/test/cookie.test.js +++ b/test/cookie.test.js @@ -674,6 +674,48 @@ describe('Koa Session Cookie', () => { }); }); + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', done => { + const app = new App(); + const message = 'hi'; + app.use(async function(ctx, next) { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async function(ctx, next) { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session.regenerate(); + } + await next(); + }); + + app.use(async function(ctx) { + ctx.session.message.should.equal(message); + ctx.body = ''; + }); + let koaSession = null; + request(app.callback()) + .get('/') + .expect(200, (err, res) => { + should.not.exist(err); + koaSession = res.headers['set-cookie'][0]; + koaSession.should.containEql('koa.sess='); + request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200, (err, res) => { + should.not.exist(err); + const cookies = res.headers['set-cookie'][0]; + cookies.should.containEql('koa.sess='); + cookies.should.not.equal(koaSession); + done(); + }); + }); + }); + }); + describe('when get session before enter session middleware', () => { it('should work', done => { const app = new Koa(); diff --git a/test/store.test.js b/test/store.test.js index caf5375..be3a5fe 100644 --- a/test/store.test.js +++ b/test/store.test.js @@ -544,6 +544,48 @@ describe('Koa Session External Store', () => { }); }); + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', done => { + const app = new App(); + const message = 'hi'; + app.use(async function(ctx, next) { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async function(ctx, next) { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session.regenerate(); + } + await next(); + }); + + app.use(async function(ctx) { + ctx.session.message.should.equal(message); + ctx.body = ''; + }); + let koaSession = null; + request(app.callback()) + .get('/') + .expect(200, (err, res) => { + should.not.exist(err); + koaSession = res.headers['set-cookie'][0]; + koaSession.should.containEql('koa.sess='); + request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200, (err, res) => { + should.not.exist(err); + const cookies = res.headers['set-cookie'][0]; + cookies.should.containEql('koa.sess='); + cookies.should.not.equal(koaSession); + done(); + }); + }); + }); + }); + describe('when store return empty', () => { it('should create new Session', done => { done = pedding(done, 2); From 98630aaf6587b199969490f8503641554aeee917 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sat, 4 Feb 2023 22:56:54 +0800 Subject: [PATCH 77/84] Release 6.4.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 00f139c..1e86323 100644 --- a/History.md +++ b/History.md @@ -1,4 +1,10 @@ +6.4.0 / 2023-02-04 +================== + +**features** + * [[`4cd3bef`](http://github.com/koajs/session/commit/4cd3bef4fc1900b847d2e133e3b2599a711f1aea)] - feat: Add Session.regenerate() method (#221) (Jürg Lehni <>) + 6.3.1 / 2023-01-03 ================== diff --git a/package.json b/package.json index 747493e..a61dc55 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "koa-session", "description": "Koa cookie session middleware with external store support", "repository": "koajs/session", - "version": "6.3.1", + "version": "6.4.0", "keywords": [ "koa", "middleware", From 575864c6ae7504da15e72e8112a99757f3eee188 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 19 Jan 2025 15:31:25 +0800 Subject: [PATCH 78/84] feat: support cjs and esm both by tshy (#228) BREAKING CHANGE: drop Node.js < 18.19.0 support --- .eslintrc | 8 +- .github/workflows/nodejs.yml | 32 +- .github/workflows/release.yml | 13 + .gitignore | 3 + History.md => CHANGELOG.md | 0 Readme.md | 80 +- example.cjs | 20 + example.js | 18 - index.js | 151 --- lib/session.js | 163 --- lib/util.js | 39 - package.json | 83 +- lib/context.js => src/context.ts | 212 ++-- src/index.ts | 305 ++++++ src/session.ts | 139 +++ src/util.ts | 26 + test/context_store.js | 25 - test/context_store.ts | 22 + test/contextstore.test.js | 758 -------------- test/contextstore.test.ts | 677 +++++++++++++ test/cookie.test.js | 948 ------------------ test/cookie.test.ts | 900 +++++++++++++++++ test/externalkey.test.js | 59 -- test/externalkey.test.ts | 60 ++ test/genid_bench.js | 45 - test/store.js | 17 - test/store.test.js | 861 ---------------- test/store.test.ts | 800 +++++++++++++++ test/store.ts | 15 + ...ith_ctx.test.js => store_with_ctx.test.ts} | 65 +- test/{store_with_ctx.js => store_with_ctx.ts} | 12 +- tsconfig.json | 10 + 32 files changed, 3258 insertions(+), 3308 deletions(-) create mode 100644 .github/workflows/release.yml rename History.md => CHANGELOG.md (100%) create mode 100644 example.cjs delete mode 100644 example.js delete mode 100644 index.js delete mode 100644 lib/session.js delete mode 100644 lib/util.js rename lib/context.js => src/context.ts (56%) create mode 100644 src/index.ts create mode 100644 src/session.ts create mode 100644 src/util.ts delete mode 100644 test/context_store.js create mode 100644 test/context_store.ts delete mode 100644 test/contextstore.test.js create mode 100644 test/contextstore.test.ts delete mode 100644 test/cookie.test.js create mode 100644 test/cookie.test.ts delete mode 100644 test/externalkey.test.js create mode 100644 test/externalkey.test.ts delete mode 100644 test/genid_bench.js delete mode 100644 test/store.js delete mode 100644 test/store.test.js create mode 100644 test/store.test.ts create mode 100644 test/store.ts rename test/{store_with_ctx.test.js => store_with_ctx.test.ts} (59%) rename test/{store_with_ctx.js => store_with_ctx.ts} (55%) create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc index 7364c13..9bcdb46 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,6 @@ { - "extends": "eslint-config-egg", - "parserOptions": { - "ecmaVersion": 2017 - } + "extends": [ + "eslint-config-egg/typescript", + "eslint-config-egg/lib/rules/enforce-node-prefix" + ] } diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index a46f3e4..c70132d 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,27 +1,17 @@ -name: Node.js CI +name: CI on: push: - branch: master + branches: [ master ] pull_request: - branch: master + branches: [ master ] jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [8, 10, 12, 14, 16, 18] - - steps: - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - run: npm install - - run: npm run lint - - run: npm run ci - - run: npx codecov + Job: + name: Node.js + uses: node-modules/github-actions/.github/workflows/node-test.yml@master + with: + os: 'ubuntu-latest, macos-latest, windows-latest' + version: '18.19.0, 18, 20, 22' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..035a626 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,13 @@ +name: Release + +on: + push: + branches: [ master ] + +jobs: + release: + name: Node.js + uses: koajs/github-actions/.github/workflows/node-release.yml@master + secrets: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} diff --git a/.gitignore b/.gitignore index 659fedb..388e6c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules coverage *.log +.tshy* +.eslintcache +dist diff --git a/History.md b/CHANGELOG.md similarity index 100% rename from History.md rename to CHANGELOG.md diff --git a/Readme.md b/Readme.md index 03275e1..34ee099 100644 --- a/Readme.md +++ b/Readme.md @@ -2,34 +2,45 @@ [![NPM version][npm-image]][npm-url] [![Node.js CI](https://github.com/koajs/session/actions/workflows/nodejs.yml/badge.svg)](https://github.com/koajs/session/actions/workflows/nodejs.yml) +[![Test coverage][codecov-image]][codecov-url] +[![Known Vulnerabilities][snyk-image]][snyk-url] [![npm download][download-image]][download-url] +[![Node.js Version](https://img.shields.io/node/v/koajs/session.svg?style=flat)](https://nodejs.org/en/download/) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://makeapullrequest.com) [npm-image]: https://img.shields.io/npm/v/koa-session.svg?style=flat-square [npm-url]: https://npmjs.org/package/koa-session +[codecov-image]: https://codecov.io/gh/koajs/session/branch/master/graph/badge.svg +[codecov-url]: https://codecov.io/gh/koajs/session +[snyk-image]: https://snyk.io/test/npm/koa-session/badge.svg?style=flat-square +[snyk-url]: https://snyk.io/test/npm/koa-session [download-image]: https://img.shields.io/npm/dm/koa-session.svg?style=flat-square [download-url]: https://npmjs.org/package/koa-session - Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. - - *Requires Node 8.0.0 or greater for async/await support* +Simple session middleware for Koa. Defaults to cookie-based sessions and supports external stores. ## Installation -```js -$ npm install koa-session +```bash +npm install koa-session ``` ## Notice -6.x changed the default cookie key from `koa:sess` to `koa.sess` to ensure `set-cookie` value valid with HTTP spec.[see issue](https://github.com/koajs/session/issues/28). If you want to be compatible with the previous version, you can manually set `config.key` to `koa:sess`. +7.x has a breaking change: drop Node.js < 18.19.0 support. And it support CommonJS and ESM both. + +6.x changed the default cookie key from `koa:sess` to `koa.sess` to ensure `set-cookie` value valid with HTTP spec. +[See issue](https://github.com/koajs/session/issues/28). +If you want to be compatible with the previous version, you can manually set `config.key` to `koa:sess`. ## Example - View counter example: +View counter example: ```js -const session = require('koa-session'); -const Koa = require('koa'); +import Koa from 'koa'; +import session from 'koa-session'; + const app = new Koa(); app.keys = ['some secret hurr']; @@ -70,38 +81,35 @@ console.log('listening on port 3000'); ### Options - The cookie name is controlled by the `key` option, which defaults - to "koa.sess". All other options are passed to `ctx.cookies.get()` and - `ctx.cookies.set()` allowing you to control security, domain, path, - and signing among other settings. +The cookie name is controlled by the `key` option, which defaults +to "koa.sess". All other options are passed to `ctx.cookies.get()` and +`ctx.cookies.set()` allowing you to control security, domain, path, +and signing among other settings. #### Custom `encode/decode` Support - Use `options.encode` and `options.decode` to customize your own encode/decode methods. +Use `options.encode` and `options.decode` to customize your own encode/decode methods. ### Hooks - - `valid()`: valid session value before use it - - `beforeSave()`: hook before save session +- `valid()`: valid session value before use it +- `beforeSave()`: hook before save session ### External Session Stores - The session is stored in a cookie by default, but it has some disadvantages: - - - Session is stored on client side unencrypted - - [Browser cookies always have length limits](http://browsercookielimits.squawky.net/) +The session is stored in a cookie by default, but it has some disadvantages: +- Session is stored on client side unencrypted +- [Browser cookies always have length limits](http://browsercookielimits.squawky.net/) You can store the session content in external stores (Redis, MongoDB or other DBs) by passing `options.store` with three methods (these need to be async functions): - - `get(key, maxAge, { rolling, ctx })`: get session object by key - - `set(key, sess, maxAge, { rolling, changed, ctx })`: set session object for key, with a `maxAge` (in ms) - - `destroy(key, {ctx})`: destroy session for key - +- `get(key, maxAge, { rolling, ctx })`: get session object by key +- `set(key, sess, maxAge, { rolling, changed, ctx })`: set session object for key, with a `maxAge` (in ms) +- `destroy(key, {ctx})`: destroy session for key Once you pass `options.store`, session storage is dependent on your external store -- you can't access the session if your external store is down. **Use external session stores only if necessary, avoid using session as a cache, keep the session lean, and store it in a cookie if possible!** - The way of generating external session id is controlled by the `options.genid(ctx)`, which defaults to `uuid.v4()`. If you want to add prefix for all external session id, you can use `options.prefix`, it will not work if `options.genid(ctx)` present. @@ -125,7 +133,7 @@ External key is used the cookie by default, but you can use `options.externalKey ### Session#isNew - Returns __true__ if the session is new. +Returns **true** if the session is new. ```js if (this.session.isNew) { @@ -137,27 +145,27 @@ if (this.session.isNew) { ### Session#maxAge - Get cookie's maxAge. +Get cookie's maxAge. ### Session#maxAge= - Set cookie's maxAge. +Set cookie's maxAge. ### Session#externalKey - Get session external key, only exist when external session store present. +Get session external key, only exist when external session store present. ### Session#save() - Save this session no matter whether it is populated. +Save this session no matter whether it is populated. ### Session#manuallyCommit() - Session headers are auto committed by default. Use this if `autoCommit` is set to `false`. +Session headers are auto committed by default. Use this if `autoCommit` is set to `false`. ### Destroying a session - To destroy a session simply set it to `null`: +To destroy a session simply set it to `null`: ```js this.session = null; @@ -165,4 +173,10 @@ this.session = null; ## License - MIT +[MIT](LICENSE) + +## Contributors + +[![Contributors](https://contrib.rocks/image?repo=koajs/session)](https://github.com/koajs/session/graphs/contributors) + +Made with [contributors-img](https://contrib.rocks). diff --git a/example.cjs b/example.cjs new file mode 100644 index 0000000..f238ff5 --- /dev/null +++ b/example.cjs @@ -0,0 +1,20 @@ + +const Koa = require('koa'); +const { createSession } = require('./'); + +const app = new Koa(); + +app.keys = [ 'some secret hurr' ]; + +app.use(createSession(app)); + +app.use(async (ctx, next) => { + if (ctx.path === '/favicon.ico') return next(); + + let n = ctx.session.views || 0; + ctx.session.views = ++n; + ctx.body = n + ' views'; +}); + +app.listen(3000); +console.log('listening on port http://localhost:3000'); diff --git a/example.js b/example.js deleted file mode 100644 index ea47cce..0000000 --- a/example.js +++ /dev/null @@ -1,18 +0,0 @@ - -var session = require('./'); -var Koa = require('koa'); -var app = new Koa(); - -app.keys = ['some secret hurr']; - -app.use(session(app)); - -app.use(function* (next){ - if ('/favicon.ico' == this.path) return; - var n = this.session.views || 0; - this.session.views = ++n; - this.body = n + ' views'; -}); - -app.listen(3000); -console.log('listening on port 3000'); diff --git a/index.js b/index.js deleted file mode 100644 index 4d149aa..0000000 --- a/index.js +++ /dev/null @@ -1,151 +0,0 @@ -'use strict'; - -const debug = require('debug')('koa-session'); -const ContextSession = require('./lib/context'); -const util = require('./lib/util'); -const assert = require('assert'); -const uuid = require('uuid'); -const is = require('is-type-of'); - -const CONTEXT_SESSION = Symbol('context#contextSession'); -const _CONTEXT_SESSION = Symbol('context#_contextSession'); - -/** - * Initialize session middleware with `opts`: - * - * - `key` session cookie name ["koa.sess"] - * - all other options are passed as cookie options - * - * @param {Object} [opts] - * @param {Application} app, koa application instance - * @api public - */ - -module.exports = function(opts, app) { - // session(app[, opts]) - if (opts && typeof opts.use === 'function') { - [ app, opts ] = [ opts, app ]; - } - // app required - if (!app || typeof app.use !== 'function') { - throw new TypeError('app instance required: `session(opts, app)`'); - } - - opts = formatOpts(opts); - extendContext(app.context, opts); - - return async function session(ctx, next) { - const sess = ctx[CONTEXT_SESSION]; - if (sess.store) await sess.initFromExternal(); - try { - await next(); - } catch (err) { - throw err; - } finally { - if (opts.autoCommit) { - await sess.commit(); - } - } - }; -}; - -/** - * format and check session options - * @param {Object} opts session options - * @return {Object} new session options - * - * @api private - */ - -function formatOpts(opts) { - opts = opts || {}; - // key - opts.key = opts.key || 'koa.sess'; - - // back-compat maxage - if (!('maxAge' in opts)) opts.maxAge = opts.maxage; - - // defaults - if (opts.overwrite == null) opts.overwrite = true; - if (opts.httpOnly == null) opts.httpOnly = true; - // delete null sameSite config - if (opts.sameSite == null) delete opts.sameSite; - if (opts.signed == null) opts.signed = true; - if (opts.autoCommit == null) opts.autoCommit = true; - - debug('session options %j', opts); - - // setup encoding/decoding - if (typeof opts.encode !== 'function') { - opts.encode = util.encode; - } - if (typeof opts.decode !== 'function') { - opts.decode = util.decode; - } - - const store = opts.store; - if (store) { - assert(is.function(store.get), 'store.get must be function'); - assert(is.function(store.set), 'store.set must be function'); - assert(is.function(store.destroy), 'store.destroy must be function'); - } - - const externalKey = opts.externalKey; - if (externalKey) { - assert(is.function(externalKey.get), 'externalKey.get must be function'); - assert(is.function(externalKey.set), 'externalKey.set must be function'); - } - - const ContextStore = opts.ContextStore; - if (ContextStore) { - assert(is.class(ContextStore), 'ContextStore must be a class'); - assert(is.function(ContextStore.prototype.get), 'ContextStore.prototype.get must be function'); - assert(is.function(ContextStore.prototype.set), 'ContextStore.prototype.set must be function'); - assert(is.function(ContextStore.prototype.destroy), 'ContextStore.prototype.destroy must be function'); - } - - if (!opts.genid) { - if (opts.prefix) opts.genid = () => `${opts.prefix}${uuid.v4()}`; - else opts.genid = uuid.v4; - } - - return opts; -} - -/** - * extend context prototype, add session properties - * - * @param {Object} context koa's context prototype - * @param {Object} opts session options - * - * @api private - */ - -function extendContext(context, opts) { - if (context.hasOwnProperty(CONTEXT_SESSION)) { - return; - } - Object.defineProperties(context, { - [CONTEXT_SESSION]: { - get() { - if (this[_CONTEXT_SESSION]) return this[_CONTEXT_SESSION]; - this[_CONTEXT_SESSION] = new ContextSession(this, opts); - return this[_CONTEXT_SESSION]; - }, - }, - session: { - get() { - return this[CONTEXT_SESSION].get(); - }, - set(val) { - this[CONTEXT_SESSION].set(val); - }, - configurable: true, - }, - sessionOptions: { - get() { - return this[CONTEXT_SESSION].opts; - }, - }, - }); -} diff --git a/lib/session.js b/lib/session.js deleted file mode 100644 index 202acc1..0000000 --- a/lib/session.js +++ /dev/null @@ -1,163 +0,0 @@ -'use strict'; - -/** - * Session model. - */ - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -class Session { - /** - * Session constructor - * @param {Context} ctx - * @param {Object} obj - * @api private - */ - - constructor(sessionContext, obj, externalKey) { - this._sessCtx = sessionContext; - this._ctx = sessionContext.ctx; - this._externalKey = externalKey; - if (!obj) { - this.isNew = true; - } else { - for (const k in obj) { - // restore maxAge from store - if (k === '_maxAge') this._ctx.sessionOptions.maxAge = obj._maxAge; - else if (k === '_session') this._ctx.sessionOptions.maxAge = 'session'; - else this[k] = obj[k]; - } - } - } - - /** - * JSON representation of the session. - * - * @return {Object} - * @api public - */ - - toJSON() { - const obj = {}; - - Object.keys(this).forEach(key => { - if (key === 'isNew') return; - if (key[0] === '_') return; - obj[key] = this[key]; - }); - - return obj; - } - - /** - * - * alias to `toJSON` - * @api public - */ - - [inspect]() { - return this.toJSON(); - } - - /** - * Return how many values there are in the session object. - * Used to see if it's "populated". - * - * @return {Number} - * @api public - */ - - get length() { - return Object.keys(this.toJSON()).length; - } - - /** - * populated flag, which is just a boolean alias of .length. - * - * @return {Boolean} - * @api public - */ - - get populated() { - return !!this.length; - } - - /** - * get session maxAge - * - * @return {Number} - * @api public - */ - - get maxAge() { - return this._ctx.sessionOptions.maxAge; - } - - /** - * set session maxAge - * - * @param {Number} - * @api public - */ - - set maxAge(val) { - this._ctx.sessionOptions.maxAge = val; - // maxAge changed, must save to cookie and store - this._requireSave = true; - } - - /** - * get session external key - * only exist if opts.store present - */ - get externalKey() { - return this._externalKey; - } - - /** - * save this session no matter whether it is populated - * - * @param {Function} callback the optional function to call after saving the session - * @api public - */ - - save(callback) { - return this.commit({ save: true }, callback); - } - - /** - * regenerate this session - * - * @param {Function} callback the optional function to call after regenerating the session - * @api public - */ - - regenerate(callback) { - return this.commit({ regenerate: true }, callback); - } - - /** - * commit this session's headers if autoCommit is set to false - * - * @api public - */ - - manuallyCommit() { - return this.commit(); - } - - commit(options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - const promise = this._sessCtx.commit(options); - if (callback) { - promise.then(() => callback(), callback); - } else { - return promise; - } - } -} - -module.exports = Session; diff --git a/lib/util.js b/lib/util.js deleted file mode 100644 index b3a1a28..0000000 --- a/lib/util.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -const crc = require('crc').crc32; - -module.exports = { - - /** - * Decode the base64 cookie value to an object. - * - * @param {String} string - * @return {Object} - * @api private - */ - - decode(string) { - const body = Buffer.from(string, 'base64').toString('utf8'); - const json = JSON.parse(body); - return json; - }, - - /** - * Encode an object into a base64-encoded JSON string. - * - * @param {Object} body - * @return {String} - * @api private - */ - - encode(body) { - body = JSON.stringify(body); - return Buffer.from(body).toString('base64'); - }, - - hash(sess) { - return crc(JSON.stringify(sess)); - }, - - CookieDateEpoch: 'Thu, 01 Jan 1970 00:00:00 GMT', -}; diff --git a/package.json b/package.json index a61dc55..725d6c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { "name": "koa-session", "description": "Koa cookie session middleware with external store support", - "repository": "koajs/session", + "repository": { + "type": "git", + "url": "git@github.com:koajs/session.git" + }, "version": "6.4.0", "keywords": [ "koa", @@ -9,38 +12,70 @@ "session", "cookie" ], - "files": [ - "index.js", - "lib" - ], "devDependencies": { - "benchmark": "^2.1.4", - "eslint": "3", - "eslint-config-egg": "3", - "istanbul": "0", + "@arethetypeswrong/cli": "^0.17.1", + "@eggjs/bin": "7", + "@eggjs/supertest": "8", + "@eggjs/tsconfig": "1", + "@types/crc": "^3.8.3", + "@types/koa": "^2.15.0", + "@types/mocha": "10", + "@types/node": "22", + "eslint": "8", + "eslint-config-egg": "14", "koa": "2", - "mm": "^2.1.0", - "mocha": "^5.2.0", - "mz-modules": "^2.0.0", - "pedding": "^1.1.0", - "should": "8", - "supertest": "^3.3.0", - "uid-safe": "^2.1.3" + "mm": "4", + "rimraf": "6", + "tshy": "3", + "tshy-after": "1", + "typescript": "5" }, "license": "MIT", "dependencies": { "crc": "^3.8.0", "debug": "^4.3.3", - "is-type-of": "^1.2.1", - "uuid": "^8.3.2" + "is-type-of": "^2.2.0", + "uuid": "^8.3.2", + "zod": "^3.24.1" }, "engines": { - "node": ">=8.0.0" + "node": ">= 18.19.0" }, "scripts": { - "test": "npm run lint && NODE_ENV=test mocha --exit --require should --reporter spec test/*.test.js", - "test-cov": "NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- --exit --require should test/*.test.js", - "ci": "npm run lint && NODE_ENV=test node ./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- --exit --require should test/*.test.js", - "lint": "eslint lib test index.js" - } + "lint": "eslint --cache src test --ext .ts", + "pretest": "npm run clean && npm run lint -- --fix", + "test": "egg-bin test", + "preci": "npm run clean && npm run lint", + "ci": "egg-bin cov", + "postci": "npm run prepublishOnly && npm run clean", + "clean": "rimraf dist", + "prepublishOnly": "tshy && tshy-after && attw --pack" + }, + "type": "module", + "tshy": { + "exports": { + ".": "./src/index.ts", + "./package.json": "./package.json" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist", + "src" + ], + "types": "./dist/commonjs/index.d.ts", + "main": "./dist/commonjs/index.js", + "module": "./dist/esm/index.js" } diff --git a/lib/context.js b/src/context.ts similarity index 56% rename from lib/context.js rename to src/context.ts index d3efbec..c31a751 100644 --- a/lib/context.js +++ b/src/context.ts @@ -1,52 +1,52 @@ -'use strict'; +import { debuglog } from 'node:util'; +import { Session } from './session.js'; +import util from './util.js'; +import type { SessionOptions } from './index.js'; -const debug = require('debug')('koa-session:context'); -const Session = require('./session'); -const util = require('./util'); +const debug = debuglog('koa-session:context'); const COOKIE_EXP_DATE = new Date(util.CookieDateEpoch); const ONE_DAY = 24 * 60 * 60 * 1000; -class ContextSession { +export class ContextSession { + ctx: any; + app: any; + opts: SessionOptions; + store: SessionOptions['store']; + session: Session | false; + externalKey?: string; + prevHash?: number; + /** * context session constructor - * @api public */ - - constructor(ctx, opts) { + constructor(ctx: any, opts: SessionOptions) { this.ctx = ctx; this.app = ctx.app; - this.opts = Object.assign({}, opts); + this.opts = { ...opts }; this.store = this.opts.ContextStore ? new this.opts.ContextStore(ctx) : this.opts.store; } /** * internal logic of `ctx.session` * @return {Session} session object - * - * @api public */ - - get() { - const session = this.session; + get(): Session | null { // already retrieved - if (session) return session; + if (this.session) return this.session; // unset - if (session === false) return null; + if (this.session === false) return null; // create an empty session or init from cookie this.store ? this.create() : this.initFromCookie(); - return this.session; + return this.session as Session; } /** * internal logic of `ctx.session=` * @param {Object} val session object - * - * @api public */ - - set(val) { + set(val: Record | null) { if (val === null) { this.session = false; return; @@ -63,7 +63,7 @@ class ContextSession { * init session from external store * will be called in the front of session middleware * - * @api public + * @public */ async initFromExternal() { @@ -87,23 +87,23 @@ class ContextSession { return; } - const json = await this.store.get(externalKey, opts.maxAge, { ctx, rolling: opts.rolling }); - if (!this.valid(json, externalKey)) { + const sessionData = await this.store!.get(externalKey, opts.maxAge as number, { ctx, rolling: opts.rolling }); + if (!this.valid(sessionData, externalKey)) { + debug('invalid session data, create a new session'); // create a new `externalKey` this.create(); return; } // create with original `externalKey` - this.create(json, externalKey); - this.prevHash = util.hash(this.session.toJSON()); + this.create(sessionData, externalKey); + this.prevHash = util.hash((this.session as Session).toJSON()); } /** * init session from cookie - * @api private + * @private */ - initFromCookie() { debug('init from cookie'); const ctx = this.ctx; @@ -115,68 +115,67 @@ class ContextSession { return; } - let json; - debug('parse %s', cookie); + let sessionData: Record; + debug('parse cookie: %j', cookie); try { - json = opts.decode(cookie); - } catch (err) { + sessionData = opts.decode(cookie); + } catch (err: unknown) { // backwards compatibility: // create a new session if parsing fails. - // new Buffer(string, 'base64') does not seem to crash + // `Buffer.from(string, 'base64')` does not seem to crash // when `string` is not base64-encoded. // but `JSON.parse(string)` will crash. debug('decode %j error: %s', cookie, err); - if (!(err instanceof SyntaxError)) { + if (err instanceof Error && !(err instanceof SyntaxError)) { // clean this cookie to ensure next request won't throw again ctx.cookies.set(opts.key, '', opts); - // ctx.onerror will unset all headers, and set those specified in err - err.headers = { + // `ctx.onerror` will unset all headers, and set those specified in err + Reflect.set(err, 'headers', { 'set-cookie': ctx.response.get('set-cookie'), - }; + }); throw err; } this.create(); return; } - debug('parsed %j', json); - - if (!this.valid(json)) { + debug('parsed session data: %j', sessionData); + if (!this.valid(sessionData)) { + // create a new session if the session data is invalid this.create(); + debug('invalid session data, create a new session'); return; } // support access `ctx.session` before session middleware - this.create(json); - this.prevHash = util.hash(this.session.toJSON()); + this.create(sessionData); + this.prevHash = util.hash((this.session as Session).toJSON()); } /** - * verify session(expired or ) - * @param {Object} value session object - * @param {Object} key session externalKey(optional) - * @return {Boolean} valid - * @api private + * verify session(expired or custom verification) + * @param {Object} sessionData session data + * @param {Object} [key] session externalKey(optional) + * @private */ - - valid(value, key) { + protected valid(sessionData: Record, key?: string) { const ctx = this.ctx; - if (!value) { - this.emit('missed', { key, value, ctx }); + if (!sessionData) { + this.emit('missed', { key, value: sessionData, ctx }); return false; } - if (value._expire && value._expire < Date.now()) { + if (typeof sessionData._expire === 'number' && sessionData._expire < Date.now()) { debug('expired session'); - this.emit('expired', { key, value, ctx }); + this.emit('expired', { key, value: sessionData, ctx }); return false; } const valid = this.opts.valid; - if (typeof valid === 'function' && !valid(ctx, value)) { + if (typeof valid === 'function' && !valid(ctx, sessionData)) { // valid session value fail, ignore this session debug('invalid session'); - this.emit('invalid', { key, value, ctx }); + this.emit('invalid', { key, value: sessionData, ctx }); return false; } return true; @@ -185,9 +184,9 @@ class ContextSession { /** * @param {String} event event name * @param {Object} data event data - * @api private + * @private */ - emit(event, data) { + emit(event: string, data: unknown) { setImmediate(() => { this.app.emit(`session:${event}`, data); }); @@ -196,45 +195,49 @@ class ContextSession { /** * create a new session and attach to ctx.sess * - * @param {Object} [val] session data + * @param {Object} [sessionData] session data * @param {String} [externalKey] session external key - * @api private */ - - create(val, externalKey) { - debug('create session with val: %j externalKey: %s', val, externalKey); - if (this.store) this.externalKey = externalKey || this.opts.genid && this.opts.genid(this.ctx); - this.session = new Session(this, val, this.externalKey); + protected create(sessionData?: Record, externalKey?: string) { + debug('create session with data: %j, externalKey: %s', sessionData, externalKey); + if (this.store) { + this.externalKey = externalKey ?? this.opts.genid?.(this.ctx); + } + this.session = new Session(this, sessionData, this.externalKey); } /** * Commit the session changes or removal. - * - * @api public */ - async commit({ save = false, regenerate = false } = {}) { const session = this.session; const opts = this.opts; const ctx = this.ctx; // not accessed - if (undefined === session) return; + if (session === undefined) { + return; + } // removed if (session === false) { await this.remove(); return; } + if (regenerate) { await this.remove(); - if (this.store) this.externalKey = opts.genid && opts.genid(ctx); + if (this.store) { + this.externalKey = opts.genid?.(ctx); + } } // force save session when `session._requireSave` set const reason = save || regenerate || session._requireSave ? 'force' : this._shouldSaveSession(); - debug('should save session: %s', reason); - if (!reason) return; + debug('should save session: %j', reason); + if (!reason) { + return; + } if (typeof opts.beforeSave === 'function') { debug('before save'); @@ -246,81 +249,90 @@ class ContextSession { _shouldSaveSession() { const prevHash = this.prevHash; - const session = this.session; + const session = this.session as Session; // do nothing if new and not populated - const json = session.toJSON(); - if (!prevHash && !Object.keys(json).length) return ''; + const sessionData = session.toJSON(); + if (!prevHash && !Object.keys(sessionData).length) { + return ''; + } // save if session changed - const changed = prevHash !== util.hash(json); - if (changed) return 'changed'; + const changed = prevHash !== util.hash(sessionData); + if (changed) { + return 'changed'; + } // save if opts.rolling set - if (this.opts.rolling) return 'rolling'; + if (this.opts.rolling) { + return 'rolling'; + } // 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 && maxAge && expire - Date.now() < maxAge / 2) return 'renew'; + if (expire && maxAge && expire - Date.now() < maxAge / 2) { + return 'renew'; + } } + // don't save return ''; } /** * remove session - * @api private + * @private */ - async remove() { // Override the default options so that we can properly expire the session cookies - const opts = Object.assign({}, this.opts, { + const opts = { + ...this.opts, expires: COOKIE_EXP_DATE, maxAge: false, - }); - + }; const ctx = this.ctx; const key = opts.key; const externalKey = this.externalKey; - if (externalKey) await this.store.destroy(externalKey, { ctx }); + if (externalKey) { + await this.store!.destroy(externalKey, { ctx }); + } ctx.cookies.set(key, '', opts); } /** * save session - * @api private + * @private */ - - async save(changed) { + async save(changed: boolean) { const opts = this.opts; const key = opts.key; const externalKey = this.externalKey; - let json = this.session.toJSON(); + const sessionData = (this.session as Session).toJSON(); // set expire for check 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 opts.maxAge = undefined; - json._session = true; + sessionData._session = true; } else { // set expire for check - json._expire = maxAge + Date.now(); - json._maxAge = maxAge; + sessionData._expire = maxAge + Date.now(); + sessionData._maxAge = maxAge; } // save to external store if (externalKey) { - debug('save %j to external key %s', json, externalKey); + debug('save %j to external key %s', sessionData, externalKey); if (typeof maxAge === 'number') { // ensure store expired after cookie maxAge += 10000; } - await this.store.set(externalKey, json, maxAge, { + await this.store!.set(externalKey, sessionData, maxAge as number, { changed, ctx: this.ctx, rolling: opts.rolling, @@ -333,13 +345,11 @@ class ContextSession { return; } - // save to cookie - debug('save %j to cookie', json); - json = opts.encode(json); - debug('save %s', json); - - this.ctx.cookies.set(key, json, opts); + // save to cookie with base64 encode string + debug('save session data %j to cookie', sessionData); + const base64String = opts.encode(sessionData); + debug('save session data json base64 format: %s to cookie key: %s with options: %j', + base64String, key, opts); + this.ctx.cookies.set(key, base64String, opts); } } - -module.exports = ContextSession; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0652ffe --- /dev/null +++ b/src/index.ts @@ -0,0 +1,305 @@ +import assert from 'node:assert'; +import { debuglog } from 'node:util'; +import { randomUUID } from 'node:crypto'; +import { isClass } from 'is-type-of'; +import z from 'zod'; +import { ContextSession } from './context.js'; +import util from './util.js'; + +const debug = debuglog('koa-session'); + +const GET_CONTEXT_SESSION = Symbol('get contextSession'); +const CONTEXT_SESSION_INSTANCE = Symbol('contextSession instance'); + +const SessionOptionsSchema = z.object({ + /** + * cookie key + * Default is `koa.sess` + */ + key: z.string().default('koa.sess'), + /** + * maxAge in ms + * Default is `86400000`, one day + * If set to 'session' will result in a cookie that expires when session/browser is closed + * + * Warning: If a session cookie is stolen, this cookie will never expire + */ + maxAge: z.union([ z.number(), z.literal('session') ]).optional(), + /** + * automatically commit headers + * Default is `true` + */ + autoCommit: z.boolean().default(true), + /** + * cookie value can overwrite or not + * Default is `true` + */ + overwrite: z.boolean().default(true), + /** + * httpOnly or not + * Default is `true` + */ + httpOnly: z.boolean().default(true), + /** + * signed or not + * Default is `true` + */ + signed: z.boolean().default(true), + /** + * 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: z.boolean().default(false), + /** + * renew session when session is nearly expired, so we can always keep user logged in. + * Default is `false` + */ + renew: z.boolean().default(false), + /** + * secure cookie + * Default is `undefined`, will be set to `true` if the connection is over HTTPS, otherwise `false`. + */ + secure: z.boolean().optional(), + /** + * session cookie sameSite options + * Default is `undefined`, meaning don't set it + */ + sameSite: z.string().optional(), + /** + * External key is used the cookie by default, + * but you can use `options.externalKey` to customize your own external key methods. + */ + externalKey: z.object({ + /** + * get the external key + * `(ctx) => string` + */ + get: z.function() + .args(z.any()) + .returns(z.string()), + /** + * set the external key + * `(ctx, key) => void` + */ + set: z.function() + .args(z.any(), z.string()) + .returns(z.void()), + }).optional(), + /** + * session storage is dependent on your external store + */ + store: z.object({ + /** + * get session data by key + * `(key, maxAge, { rolling, ctx }) => sessionData | Promise` + */ + get: z.function() + .args(z.string(), z.number(), z.object({ rolling: z.boolean(), ctx: z.any() })) + .returns(z.promise(z.any())), + /** + * set session data for key, with a `maxAge` (in ms) + * `(key, sess, maxAge, { rolling, changed, ctx }) => void | Promise` + */ + set: z.function() + .args(z.string(), z.any(), z.number(), z.object({ rolling: z.boolean(), changed: z.boolean(), ctx: z.any() })) + .returns(z.promise(z.void())), + /** + * destroy session data for key + * `(key, { ctx })=> void | Promise` + */ + destroy: z.function() + .args(z.string(), z.object({ ctx: z.any() })) + .returns(z.promise(z.void())), + }).optional(), + /** + * If your session store requires data or utilities from context, `opts.ContextStore` is also supported. + * `ContextStore` must be a class which claims three instance methods demonstrated above. + * `new ContextStore(ctx)` will be executed on every request. + */ + ContextStore: z.any().optional(), + encode: z.function() + .args(z.any()) + .returns(z.string()) + .optional() + .default(() => util.encode), + decode: z.function() + .args(z.string()) + .returns(z.any()) + .default(() => util.decode), + /** + * If you want to generate a new session id, you can use `genid` option to customize it. + * Default is a function that uses `randomUUID()`. + * `(ctx) => string` + */ + genid: z.function() + .args(z.any()) + .returns(z.string()) + .optional(), + /** + * If you want to prefix the session id, you can use `prefix` option to customize it. + * It will not work if `options.genid(ctx)` present. + */ + prefix: z.string().optional(), + /** + * valid session value before use it + * `(ctx, sessionData) => boolean` + */ + valid: z.function() + .args(z.any(), z.any()) + .returns(z.any()) + .optional(), + /** + * hook before save session + * `(ctx, sessionModel) => void` + */ + beforeSave: z.function() + .args(z.any(), z.any()) + .returns(z.void()) + .optional(), +}); + +const DEFAULT_SESSION_OPTIONS = SessionOptionsSchema.parse({}); + +export type SessionOptions = z.infer; +export type CreateSessionOptions = Partial; + +type Middleware = (ctx: any, next: any) => Promise; + +/** + * Initialize session middleware with `opts`: + * + * - `key` session cookie name ["koa.sess"] + * - all other options are passed as cookie options + * + * @param {Object} [opts] session options + * @param {Application} app koa application instance + * @public + */ +export function createSession(opts: CreateSessionOptions, app: any): Middleware; +export function createSession(app: any, opts?: CreateSessionOptions): Middleware; +export function createSession(opts: CreateSessionOptions | any, app: any): Middleware { + // session(app[, opts]) + if (opts && 'use' in opts && typeof opts.use === 'function') { + [ app, opts ] = [ opts, app ]; + } + // app required + if (typeof app?.use !== 'function') { + throw new TypeError('app instance required: `session(opts, app)`'); + } + + // back-compat maxage + if (opts && !('maxAge' in opts) && 'maxage' in opts) { + Reflect.set(opts, 'maxAge', Reflect.get(opts, 'maxage')); + if (process.env.NODE_ENV !== 'production') { + console.warn('DeprecationWarning: `maxage` option has been renamed to `maxAge`'); + } + } + let options = { + ...DEFAULT_SESSION_OPTIONS, + ...opts, + }; + SessionOptionsSchema.parse(options); + options = formatOptions(options); + extendContext(app.context, options); + + return async function session(ctx: any, next: any) { + const sess = ctx[GET_CONTEXT_SESSION]; + if (sess.store) { + await sess.initFromExternal(); + } + try { + await next(); + } catch (err) { + throw err; + } finally { + if (options.autoCommit) { + await sess.commit(); + } + } + }; +} + +// Usage: `import session from 'koa-session'` +export default createSession; + +/** + * format and check session options + */ +function formatOptions(opts: SessionOptions) { + // defaults + if (opts.overwrite == null) opts.overwrite = true; + if (opts.httpOnly == null) opts.httpOnly = true; + // delete null sameSite config + if (opts.sameSite == null) delete opts.sameSite; + if (opts.signed == null) opts.signed = true; + if (opts.autoCommit == null) opts.autoCommit = true; + + debug('session options %j', opts); + const store = opts.store; + if (store) { + assert(typeof store.get === 'function', 'store.get must be function'); + assert(typeof store.set === 'function', 'store.set must be function'); + assert(typeof store.destroy === 'function', 'store.destroy must be function'); + } + + const externalKey = opts.externalKey; + if (externalKey) { + assert(typeof externalKey.get === 'function', 'externalKey.get must be function'); + assert(typeof externalKey.set === 'function', 'externalKey.set must be function'); + } + + const ContextStore = opts.ContextStore; + if (ContextStore) { + assert(isClass(ContextStore), 'ContextStore must be a class'); + assert(typeof ContextStore.prototype.get === 'function', 'ContextStore.prototype.get must be function'); + assert(typeof ContextStore.prototype.set === 'function', 'ContextStore.prototype.set must be function'); + assert(typeof ContextStore.prototype.destroy === 'function', 'ContextStore.prototype.destroy must be function'); + } + + if (!opts.genid) { + if (opts.prefix) { + opts.genid = () => `${opts.prefix}${randomUUID()}`; + } else { + opts.genid = () => randomUUID(); + } + } + return opts; +} + +/** + * extend context prototype, add session properties + * + * @param {Object} context koa's context prototype + * @param {Object} opts session options + */ +function extendContext(context: object, opts: SessionOptions) { + if (context.hasOwnProperty(GET_CONTEXT_SESSION)) { + return; + } + Object.defineProperties(context, { + [GET_CONTEXT_SESSION]: { + get() { + if (this[CONTEXT_SESSION_INSTANCE]) { + return this[CONTEXT_SESSION_INSTANCE]; + } + this[CONTEXT_SESSION_INSTANCE] = new ContextSession(this, opts); + return this[CONTEXT_SESSION_INSTANCE]; + }, + }, + session: { + get() { + return this[GET_CONTEXT_SESSION].get(); + }, + set(val) { + this[GET_CONTEXT_SESSION].set(val); + }, + configurable: true, + }, + sessionOptions: { + get() { + return this[GET_CONTEXT_SESSION].opts; + }, + }, + }); +} diff --git a/src/session.ts b/src/session.ts new file mode 100644 index 0000000..024b553 --- /dev/null +++ b/src/session.ts @@ -0,0 +1,139 @@ +import { inspect } from 'node:util'; +import type { ContextSession } from './context.js'; + +type Callback = (err?: Error) => void; + +/** + * Session model + */ +export class Session { + #sessCtx: ContextSession; + #ctx: any; + #externalKey?: string; + isNew = false; + _requireSave = false; + // session expire time, will be set from sessionData + _expire?: number; + + constructor(sessionContext: ContextSession, sessionData?: Record, externalKey?: string) { + this.#sessCtx = sessionContext; + this.#ctx = sessionContext.ctx; + this.#externalKey = externalKey; + if (!sessionData) { + this.isNew = true; + } else { + for (const k in sessionData) { + // restore maxAge from store + if (k === '_maxAge') { + this.#ctx.sessionOptions.maxAge = sessionData._maxAge; + } else if (k === '_session') { + // set maxAge to 'session' if it's a session lifetime + this.#ctx.sessionOptions.maxAge = 'session'; + } else { + Reflect.set(this, k, sessionData[k]); + } + } + } + } + + /** + * JSON representation of the session. + */ + toJSON() { + const obj: Record = {}; + for (const key in this) { + if (key === 'isNew') continue; + // skip private stuff + if (key[0] === '_') continue; + const value = this[key]; + // skip functions + if (typeof value === 'function') continue; + obj[key] = value; + } + return obj; + } + + /** + * alias to `toJSON` + */ + [inspect.custom]() { + return this.toJSON(); + } + + /** + * Return how many values there are in the session object. + * Used to see if it's "populated". + */ + get length() { + return Object.keys(this.toJSON()).length; + } + + /** + * populated flag, which is just a boolean alias of .length. + */ + get populated() { + return !!this.length; + } + + /** + * get session maxAge + */ + get maxAge(): number { + return this.#ctx.sessionOptions.maxAge; + } + + /** + * set session maxAge + */ + set maxAge(val: number) { + this.#ctx.sessionOptions.maxAge = val; + // maxAge changed, must save to cookie and store + this._requireSave = true; + } + + /** + * get session external key + * only exist if opts.store present + */ + get externalKey() { + return this.#externalKey; + } + + /** + * save this session no matter whether it is populated + * + * @param {Function} [callback] the optional function to call after saving the session + */ + save(callback?: Callback) { + return this.commit({ save: true }, callback); + } + + /** + * regenerate this session + * + * @param {Function} [callback] the optional function to call after regenerating the session + */ + regenerate(callback?: Callback) { + return this.commit({ regenerate: true }, callback); + } + + /** + * commit this session's headers if autoCommit is set to false + */ + manuallyCommit() { + return this.commit(); + } + + commit(options?: any, callback?: Callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + const promise = this.#sessCtx.commit(options); + if (callback) { + promise.then(() => callback(), callback); + } else { + return promise; + } + } +} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..39f0806 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,26 @@ +import crc from 'crc'; + +export default { + /** + * Decode the base64 cookie value to an object + * @private + */ + decode(base64String: string): Record { + const body = Buffer.from(base64String, 'base64').toString('utf8'); + const json = JSON.parse(body); + return json; + }, + + /** + * Encode an object into a base64-encoded JSON string + */ + encode(data: Record) { + return Buffer.from(JSON.stringify(data)).toString('base64'); + }, + + hash(data: Record) { + return crc.crc32(JSON.stringify(data)); + }, + + CookieDateEpoch: 'Thu, 01 Jan 1970 00:00:00 GMT', +}; diff --git a/test/context_store.js b/test/context_store.js deleted file mode 100644 index 8a053eb..0000000 --- a/test/context_store.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -// this is a stupid nonsense example just to test - -const sessions = {}; - -class ContextStore { - constructor(ctx) { - this.ctx = ctx; - } - - async get(key) { - return sessions[key]; - } - - async set(key, value) { - sessions[key] = value; - } - - async destroy(key) { - sessions[key] = undefined; - } -} - -module.exports = ContextStore; diff --git a/test/context_store.ts b/test/context_store.ts new file mode 100644 index 0000000..fb15761 --- /dev/null +++ b/test/context_store.ts @@ -0,0 +1,22 @@ +// this is a stupid nonsense example just to test + +const sessions: Record = {}; + +export default class ContextStore { + ctx: any; + constructor(ctx: any) { + this.ctx = ctx; + } + + async get(key: string) { + return sessions[key]; + } + + async set(key: string, value: unknown) { + sessions[key] = value; + } + + async destroy(key: string) { + sessions[key] = undefined; + } +} diff --git a/test/contextstore.test.js b/test/contextstore.test.js deleted file mode 100644 index b31fada..0000000 --- a/test/contextstore.test.js +++ /dev/null @@ -1,758 +0,0 @@ -'use strict'; - -const Koa = require('koa'); -const request = require('supertest'); -const should = require('should'); -const mm = require('mm'); -const session = require('..'); -const ContextStore = require('./context_store'); - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -describe('Koa Session External Context Store', () => { - let cookie; - - describe('when the session contains a ;', () => { - it('should still work', done => { - const app = App(); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('new session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and not populated', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session; - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when populated', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - cookie.indexOf('_suffix').should.greaterThan(1); - done(); - }); - }); - - it('should pass sid to middleware', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.state.sid.indexOf('_suffix').should.greaterThan(1); - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - cookie.indexOf('_suffix').should.greaterThan(1); - done(); - }); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - }); - - describe('saved session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed but not changed', () => { - it('should be the same session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, done); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and changed', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.money = '$$$'; - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - }); - - describe('when session is', () => { - describe('null', () => { - it('should expire the session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = null; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('an empty object', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = {}; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('an object', () => { - it('should create a session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { message: 'hello' }; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('anything else', () => { - it('should throw', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - }); - - describe('session', () => { - describe('.inspect()', () => { - it('should return session content', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = ctx.session[inspect](); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect({ foo: 'bar' }) - .expect(200, done); - }); - }); - - describe('.length', () => { - it('should return session length', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.length); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('1') - .expect(200, done); - }); - }); - - describe('.populated', () => { - it('should return session populated', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.populated); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('.save()', () => { - it('should save session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.save(); - ctx.body = 'hello'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('hello') - .expect(200, done); - }); - }); - }); - - describe('when an error is thrown downstream and caught upstream', () => { - it('should still save the session', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - - app.use(async function(ctx, next) { - try { - await next(); - } catch (err) { - ctx.status = err.status; - ctx.body = err.message; - } - }); - - app.use(session({ ContextStore }, app)); - - app.use(async function(ctx, next) { - ctx.session.name = 'funny'; - await next(); - }); - - app.use(async function(ctx) { - ctx.throw(401); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(401, done); - }); - }); - - describe('when autoCommit is present', () => { - describe('and set to false', () => { - it('should not set headers if manuallyCommit() isn\'t called', done => { - const app = App({ autoCommit: false }); - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - should.not.exist(cookie); - }) - .expect(200, done); - }); - it('should set headers if manuallyCommit() is called', done => { - const app = App({ autoCommit: false }); - app.use(async function(ctx, next) { - if (ctx.method === 'POST') { - ctx.session.message = 'dummy'; - } - await next(); - }); - app.use(async function(ctx) { - ctx.body = 200; - await ctx.session.manuallyCommit(); - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end(err => { - if (err) return done(err); - }) - .expect(200, done); - }); - }); - }); - - describe('when maxAge present', () => { - describe('and set to be a session cookie', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 'session' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - it('should not expire the session after multiple session changes', done => { - const app = App({ maxAge: 'session' }); - - app.use(async function(ctx) { - ctx.session.count = (ctx.session.count || 0) + 1; - ctx.body = `hi ${ctx.session.count}`; - }); - const server = app.listen(); - - request(server) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect('hi 1') - .end((err, res) => { - if (err) return done(err); - let cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect('hi 2') - .end((err, res) => { - if (err) return done(err); - cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect('hi 3') - .end((err, res) => { - if (err) return done(err); - cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - - done(); - }); - }); - }); - }); - it('should use the default maxAge when improper string given', done => { - const app = App({ maxAge: 'not the right string' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - describe('and not expire', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - - describe('and expired', () => { - it('should expire the sess', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.status = 200; - return; - } - - ctx.body = ctx.session.message || ''; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - setTimeout(() => { - request(server) - .get('/') - .set('cookie', cookie) - .expect('', done); - }, 200); - }); - }); - }); - }); - - describe('ctx.session.maxAge', () => { - it('should return opt.maxAge', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - ctx.body = ctx.session.maxAge; - }); - - request(app.listen()) - .get('/') - .expect('100', done); - }); - }); - - describe('ctx.session.maxAge=', () => { - it('should set sessionOptions.maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.session.maxAge = 100; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save even session not change', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.maxAge = 100; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save when create session only with maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { maxAge: 100 }; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - }); - - describe('when store return empty', () => { - it('should create new Session', done => { - const app = App({ signed: false }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-key') - .expect('true') - .expect(200, done); - }); - }); - - describe('when valid and beforeSave set', () => { - it('should ignore session when uid changed', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({ - valid(ctx, sess) { - return ctx.cookies.get('uid') === sess.uid; - }, - beforeSave(ctx, sess) { - sess.uid = ctx.cookies.get('uid'); - }, - ContextStore, - }, app)); - app.use(async function(ctx) { - if (!ctx.session.foo) { - ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); - } - - ctx.body = { - foo: ctx.session.foo, - uid: ctx.cookies.get('uid'), - }; - }); - - request(app.callback()) - .get('/') - .set('Cookie', 'uid=123') - .expect(200, (err, res) => { - should.not.exist(err); - const data = res.body; - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=123') - .expect(200) - .expect(data, err => { - should.not.exist(err); - - // should ignore uid:123 session and create a new session for uid:456 - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=456') - .expect(200, (err, res) => { - should.not.exist(err); - res.body.uid.should.equal('456'); - res.body.foo.should.not.equal(data.foo); - done(); - }); - }); - }); - }); - }); - - describe('ctx.session', () => { - after(mm.restore); - - it('can be mocked', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - mm(app.context, 'session', { - foo: 'bar', - }); - - request(app.listen()) - .get('/') - .expect({ - foo: 'bar', - }) - .expect(200, done); - }); - }); -}); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.ContextStore = ContextStore; - options.genid = ctx => { - const sid = Date.now() + '_suffix'; - ctx.state.sid = sid; - return sid; - }; - app.use(session(options, app)); - return app; -} diff --git a/test/contextstore.test.ts b/test/contextstore.test.ts new file mode 100644 index 0000000..e77f295 --- /dev/null +++ b/test/contextstore.test.ts @@ -0,0 +1,677 @@ +import { strict as assert } from 'node:assert'; +import { scheduler } from 'node:timers/promises'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import { mm } from 'mm'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import ContextStore from './context_store.js'; + +const inspect = Symbol.for('nodejs.util.inspect.custom'); + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.ContextStore = ContextStore; + options.genid = ctx => { + const sid = Date.now() + '_suffix'; + ctx.state.sid = sid; + return sid; + }; + app.use(session(options, app)); + return app; +} + +describe('Koa Session External Context Store', () => { + let cookie: string; + + describe('when the session contains a ;', () => { + it('should still work', async () => { + const app = App(); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string; + } + }); + + const res = await request(app.callback()) + .post('/') + .expect(204); + const cookie = res.get('Set-Cookie')!; + assert(cookie, 'should have set cookie'); + await request(app.callback()) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session; + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.match(cookie, /\d+_suffix/); + }); + + it('should pass sid to middleware', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.message = 'hello'; + assert.match(ctx.state.sid, /\d+_suffix/); + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + + cookie = res.get('Set-Cookie')!.join(';'); + assert.match(cookie, /\d+_suffix/); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', async () => { + const app = App(); + + app.use(async function(ctx) { + assert.equal(ctx.session.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + assert.equal(ctx.session.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = null; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + }); + + describe('an object', () => { + it('should create a session', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('anything else', () => { + it('should throw', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = ctx.session[inspect](); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200); + }); + }); + + describe('.length', () => { + it('should return session length', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.length); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('1') + .expect(200); + }); + }); + + describe('.populated', () => { + it('should return session populated', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.body = String(ctx.session.populated); + }); + + await request(app.listen()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('true') + .expect(200); + }); + }); + + describe('.save()', () => { + it('should save session', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.save(); + ctx.body = 'hello'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('hello') + .expect(200); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async function(ctx, next) { + try { + await next(); + } catch (err: any) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session({ ContextStore }, app)); + + app.use(async function(ctx, next) { + ctx.session.name = 'funny'; + await next(); + }); + + app.use(async function(ctx) { + ctx.throw(401); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(401); + }); + }); + + describe('when autoCommit is present', () => { + describe('and set to false', () => { + it('should not set headers if manuallyCommit() isn\'t called', async () => { + const app = App({ autoCommit: false }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const res = await request(app.callback()) + .post('/') + .expect(200); + assert.equal(res.get('Set-Cookie'), undefined, 'should not have set cookie'); + }); + + it('should set headers if manuallyCommit() is called', async () => { + const app = App({ autoCommit: false }); + app.use(async function(ctx, next) { + if (ctx.method === 'POST') { + ctx.session.message = 'dummy'; + } + await next(); + }); + app.use(async function(ctx) { + ctx.body = 200; + await ctx.session.manuallyCommit(); + }); + + await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + }); + + describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const res = await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + + const cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + + it('should not expire the session after multiple session changes', async () => { + const app = App({ maxAge: 'session' }); + + app.use(async function(ctx) { + ctx.session.count = (ctx.session.count || 0) + 1; + ctx.body = `hi ${ctx.session.count}`; + }); + + let res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect('hi 1') + .expect(200); + let cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect('hi 2') + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect('hi 3') + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.doesNotMatch(cookie, /expires=/); + }); + + it('should throw error when the maxAge improper string given', () => { + assert.throws(() => { + App({ maxAge: 'not the right string' } as any); + }, /Invalid input/); + }); + }); + + describe('and not expire', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session.message; + }); + + const res = await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + const cookie = res.get('Set-Cookie')!.join(';'); + await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and expired', () => { + it('should expire the sess', async () => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session.message || ''; + }); + + const res = await request(app.callback()) + .post('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + const cookie = res.get('Set-Cookie')!.join(';'); + await scheduler.wait(200); + await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect(''); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', async () => { + const app = App({ maxAge: 100 }); + + app.use(async function(ctx) { + ctx.body = ctx.session.maxAge; + }); + + await request(app.callback()) + .get('/') + .expect('100'); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.foo = 'bar'; + ctx.session.maxAge = 100; + ctx.body = ctx.session.foo; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save even session not change', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session.maxAge = 100; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save when create session only with maxAge', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + }); + + describe('when store return empty', () => { + it('should create new Session', async () => { + const app = App({ signed: false }); + + app.use(async function(ctx) { + ctx.body = String(ctx.session.isNew); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-key') + .expect('true') + .expect(200); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + ContextStore, + }, app)); + + app.use(async function(ctx) { + if (!ctx.session.foo) { + ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + let res = await request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200); + + const data = res.body; + const cookies = res.get('Set-Cookie')!.join(';'); + assert.match(cookies, /koa\.sess=/); + + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data); + + // should ignore uid:123 session and create a new session for uid:456 + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200); + assert.equal(res.body.uid, '456'); + assert.notEqual(res.body.foo, data.foo); + }); + }); + + describe('ctx.session', () => { + after(mm.restore); + + it('can be mocked', async () => { + const app = App(); + + app.use(async function(ctx) { + ctx.body = ctx.session; + }); + + mm(app.context, 'session', { + foo: 'bar', + }); + + await request(app.callback()) + .get('/') + .expect({ + foo: 'bar', + }) + .expect(200); + }); + }); +}); diff --git a/test/cookie.test.js b/test/cookie.test.js deleted file mode 100644 index 69b0683..0000000 --- a/test/cookie.test.js +++ /dev/null @@ -1,948 +0,0 @@ -'use strict'; - -const assert = require('assert'); -const Koa = require('koa'); -const request = require('supertest'); -const should = require('should'); -const session = require('..'); - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -describe('Koa Session Cookie', () => { - let cookie; - - describe('when options.signed = true', () => { - describe('when app.keys are set', () => { - it('should work', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({}, app)); - - app.use(async function(ctx) { - ctx.session.message = 'hi'; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, done); - }); - }); - - describe('when app.keys are not set', () => { - it('should throw and clean this cookie', done => { - const app = new Koa(); - - app.use(session(app)); - - app.use(async function(ctx) { - ctx.session.message = 'hi'; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - - describe('when app not set', () => { - it('should throw', () => { - const app = new Koa(); - (function() { - app.use(session()); - }).should.throw('app instance required: `session(opts, app)`'); - }); - }); - }); - - describe('when options.signed = false', () => { - describe('when app.keys are not set', () => { - it('should work', done => { - const app = new Koa(); - - app.use(session({ signed: false }, app)); - - app.use(async function(ctx) { - ctx.session.message = 'hi'; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, done); - }); - }); - }); - - describe('when the session contains a ;', () => { - it('should still work', done => { - const app = App(); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - // samesite is not set - assert(!cookie.join(';').includes('samesite')); - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('new session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and not populated', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session; - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when populated', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - done(); - }); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - }); - - describe('saved session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed but not changed', () => { - it('should be the same session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, done); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and changed', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.money = '$$$'; - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect(res => { - const cookie = res.headers['set-cookie']; - // samesite is not set - assert(!cookie.join(';').includes('samesite')); - }) - .expect(200, done); - }); - }); - }); - - describe('after session set to null with signed cookie', () => { - it('should return expired cookies', done => { - const app = App({ - signed: true, - }); - - app.use(async function(ctx) { - ctx.session.hello = {}; - ctx.session = null; - ctx.body = String(ctx.session === null); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('Set-Cookie', /koa\.sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('true') - .expect(200, done); - }); - }); - - describe('after session set to null without signed cookie', () => { - it('should return expired cookies', done => { - const app = App({ - signed: false, - }); - - app.use(async function(ctx) { - ctx.session.hello = {}; - ctx.session = null; - ctx.body = String(ctx.session === null); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) - .expect('true') - .expect(200, done); - }); - }); - - describe('when get session after set to null', () => { - it('should return null', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.hello = {}; - ctx.session = null; - ctx.body = String(ctx.session === null); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('when decode session', () => { - describe('SyntaxError', () => { - it('should create new session', done => { - const app = App({ signed: false }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-session;') - .expect('true') - .expect(200, done); - }); - }); - - describe('Other Error', () => { - it('should throw', done => { - const app = App({ - signed: false, - decode() { - throw new Error('decode error'); - }, - }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-session;') - .expect('Set-Cookie', /koa\.sess=;/) - .expect(500, done); - }); - }); - }); - - describe('when encode session error', () => { - it('should throw', done => { - const app = App({ - encode() { - throw new Error('encode error'); - }, - }); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = 'hello'; - }); - - app.once('error', (err, ctx) => { - err.message.should.equal('encode error'); - should.exists(ctx); - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - - describe('session', () => { - describe('.inspect()', () => { - it('should return session content', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = ctx.session[inspect](); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect({ foo: 'bar' }) - .expect(200, done); - }); - }); - - describe('.length', () => { - it('should return session length', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.length); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('1') - .expect(200, done); - }); - }); - - describe('.populated', () => { - it('should return session populated', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.populated); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('.save()', () => { - it('should save session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.save(); - ctx.body = 'hello'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('hello') - .expect(200, done); - }); - }); - }); - - describe('when session is', () => { - describe('null', () => { - it('should expire the session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = null; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('an empty object', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = {}; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('an object', () => { - it('should create a session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { message: 'hello' }; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('anything else', () => { - it('should throw', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - }); - - describe('when an error is thrown downstream and caught upstream', () => { - it('should still save the session', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - - app.use(async function(ctx, next) { - try { - await next(); - } catch (err) { - ctx.status = err.status; - ctx.body = err.message; - } - }); - - app.use(session(app)); - - app.use(async function(ctx, next) { - ctx.session.name = 'funny'; - await next(); - }); - - app.use(async function(ctx) { - ctx.throw(401); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(401, done); - }); - }); - - describe('when maxAge present', () => { - describe('and not expire', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - - describe('and expired', () => { - it('should expire the sess', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.status = 200; - return; - } - - ctx.body = ctx.session.message || ''; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - setTimeout(() => { - request(server) - .get('/') - .set('cookie', cookie) - .expect('', done); - }, 200); - }); - }); - }); - }); - - describe('ctx.session.maxAge', () => { - it('should return opt.maxAge', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - ctx.body = ctx.session.maxAge; - }); - - request(app.listen()) - .get('/') - .expect('100', done); - }); - }); - - describe('ctx.session.maxAge=', () => { - it('should set sessionOptions.maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.session.maxAge = 100; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save even session not change', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.maxAge = 100; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save when create session only with maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { maxAge: 100 }; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - }); - - describe('ctx.session.regenerate', () => { - it('should change the session key, but not content', done => { - const app = new App(); - const message = 'hi'; - app.use(async function(ctx, next) { - ctx.session = { message: 'hi' }; - await next(); - }); - - app.use(async function(ctx, next) { - const sessionKey = ctx.cookies.get('koa.sess'); - if (sessionKey) { - await ctx.session.regenerate(); - } - await next(); - }); - - app.use(async function(ctx) { - ctx.session.message.should.equal(message); - ctx.body = ''; - }); - let koaSession = null; - request(app.callback()) - .get('/') - .expect(200, (err, res) => { - should.not.exist(err); - koaSession = res.headers['set-cookie'][0]; - koaSession.should.containEql('koa.sess='); - request(app.callback()) - .get('/') - .set('Cookie', koaSession) - .expect(200, (err, res) => { - should.not.exist(err); - const cookies = res.headers['set-cookie'][0]; - cookies.should.containEql('koa.sess='); - cookies.should.not.equal(koaSession); - done(); - }); - }); - }); - }); - - describe('when get session before enter session middleware', () => { - it('should work', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(async function(ctx, next) { - ctx.session.foo = 'hi'; - await next(); - }); - app.use(session({}, app)); - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.callback()) - .get('/') - .expect(200, (err, res) => { - should.not.exist(err); - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies) - .expect(200, done); - }); - }); - }); - - describe('options.sameSite', () => { - it('should return opt.sameSite=none', done => { - const app = App({ sameSite: 'none' }); - - app.use(async function(ctx) { - ctx.session = { foo: 'bar' }; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect(res => { - const cookie = res.headers['set-cookie'].join('|'); - assert(cookie.includes('path=/; samesite=none; httponly')); - }) - .expect('bar') - .expect(200, done); - }); - - it('should return opt.sameSite=lax', done => { - const app = App({ sameSite: 'lax' }); - - app.use(async function(ctx) { - ctx.session = { foo: 'bar' }; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect(res => { - const cookie = res.headers['set-cookie'].join('|'); - assert(cookie.includes('path=/; samesite=lax; httponly')); - }) - .expect('bar') - .expect(200, done); - }); - }); - - describe('when valid and beforeSave set', () => { - it('should ignore session when uid changed', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({ - valid(ctx, sess) { - return ctx.cookies.get('uid') === sess.uid; - }, - beforeSave(ctx, sess) { - sess.uid = ctx.cookies.get('uid'); - }, - }, app)); - app.use(async function(ctx) { - if (!ctx.session.foo) { - ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); - } - - ctx.body = { - foo: ctx.session.foo, - uid: ctx.cookies.get('uid'), - }; - }); - - request(app.callback()) - .get('/') - .set('Cookie', 'uid=123') - .expect(200, (err, res) => { - should.not.exist(err); - const data = res.body; - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=123') - .expect(200) - .expect(data, err => { - should.not.exist(err); - - // should ignore uid:123 session and create a new session for uid:456 - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=456') - .expect(200, (err, res) => { - should.not.exist(err); - res.body.uid.should.equal('456'); - res.body.foo.should.not.equal(data.foo); - done(); - }); - }); - }); - }); - }); - - describe('when options.encode and options.decode are functions', () => { - describe('they are used to encode/decode stored cookie values', () => { - it('should work', done => { - let encodeCallCount = 0; - let decodeCallCount = 0; - - function encode(data) { - ++encodeCallCount; - return JSON.stringify({ enveloped: data }); - } - function decode(data) { - ++decodeCallCount; - return JSON.parse(data).enveloped; - } - - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - app.use(session({ - encode, - decode, - }, app)); - - app.use(async function(ctx) { - ctx.session.counter = (ctx.session.counter || 0) + 1; - ctx.body = ctx.session; - return; - }); - - request(app.callback()) - .get('/') - .expect(() => { encodeCallCount.should.above(0, 'encode was not called'); }) - .expect(200, (err, res) => { - should.not.exist(err); - res.body.counter.should.equal(1, 'expected body to be equal to session.counter'); - const cookies = res.headers['set-cookie'].join(';'); - request(app.callback()) - .get('/') - .set('Cookie', cookies) - .expect(() => { decodeCallCount.should.be.above(0, 'decode was not called'); }) - .expect(200, (err, res) => { - should.not.exist(err); - res.body.counter.should.equal(2); - done(); - }); - }); - }); - }); - }); - - describe('when rolling set to true', () => { - let app; - before(() => { - app = App({ rolling: true }); - - app.use(function* () { - console.log(this.path); - if (this.path === '/set') this.session = { foo: 'bar' }; - this.body = this.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 exists and not change', done => { - request(app.callback()) - .get('/set') - .expect({ foo: 'bar' }) - .end((err, res) => { - should.not.exist(err); - res.headers['set-cookie'].should.have.length(2); - const cookie = res.headers['set-cookie'].join(';'); - request(app.callback()) - .get('/') - .set('cookie', cookie) - .expect(res => { - res.headers['set-cookie'].should.have.length(2); - }) - .expect({ foo: 'bar' }, done); - }); - }); - }); - - describe('init multi session middleware', () => { - it('should work', () => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - const s1 = session({}, app); - const s2 = session({}, app); - assert(s1); - assert(s2); - }); - }); -}); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - app.use(session(options, app)); - return app; -} diff --git a/test/cookie.test.ts b/test/cookie.test.ts new file mode 100644 index 0000000..add102e --- /dev/null +++ b/test/cookie.test.ts @@ -0,0 +1,900 @@ +import { strict as assert } from 'node:assert'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import session, { type CreateSessionOptions } from '../src/index.js'; + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + app.use(session(options, app)); + return app; +} + +const inspect = Symbol.for('nodejs.util.inspect.custom'); + +describe('Koa Session Cookie', () => { + let cookie: string; + + describe('when options.signed = true', () => { + describe('when app.keys are set', () => { + it('should work', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({}, app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hi'; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect(200); + }); + }); + + describe('when app.keys are not set', () => { + it('should throw and clean this cookie', async () => { + const app = new Koa(); + + app.use(session(app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hi'; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + + describe('when app not set', () => { + it('should throw', () => { + const app = new Koa(); + assert.throws(() => { + app.use((session as any)()); + }, /app instance required: `session\(opts, app\)`/); + assert.throws(() => { + app.use(session({})); + }, /app instance required: `session\(opts, app\)`/); + }); + }); + }); + + describe('when options.signed = false', () => { + describe('when app.keys are not set', () => { + it('should work', async () => { + const app = new Koa(); + + app.use(session({ signed: false }, app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hi'; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect(200); + }); + }); + }); + + describe('when the session contains a ;', () => { + it('should still work', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session!.string; + } + }); + + const server = app.callback(); + + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + // samesite is not set + assert(!cookie.join(';').includes('samesite')); + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session; + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hello'; + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + const newCookie = res.get('Set-Cookie')!; + // samesite is not set + assert(!newCookie.join(';').includes('samesite')); + }); + }); + }); + + describe('after session set to null with signed cookie', () => { + it('should return expired cookies', async () => { + const app = App({ + signed: true, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', + /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('Set-Cookie', + /koa\.sess.sig=(.*); path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200); + }); + }); + + describe('after session set to null without signed cookie', () => { + it('should return expired cookies', async () => { + const app = App({ + signed: false, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=; path=\/; expires=Thu, 01 Jan 1970 00:00:00 GMT/) + .expect('true') + .expect(200); + }); + }); + + describe('when get session after set to null', () => { + it('should return null', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.hello = {}; + ctx.session = null; + ctx.body = String(ctx.session === null); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=;/) + .expect('true') + .expect(200); + }); + }); + + describe('when decode session', () => { + describe('SyntaxError', () => { + it('should create new session', async () => { + const app = App({ signed: false }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = String(ctx.session.isNew); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-session;') + .expect('true') + .expect(200); + }); + }); + + describe('Other Error', () => { + it('should throw', async () => { + const app = App({ + signed: false, + decode() { + throw new Error('decode error'); + }, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = String(ctx.session!.isNew); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-session;') + .expect('Set-Cookie', /koa\.sess=;/) + .expect(500); + }); + }); + }); + + describe('when encode session error', () => { + it('should throw', async () => { + const app = App({ + encode() { + throw new Error('encode error'); + }, + }); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = 'hello'; + }); + + app.once('error', (err, ctx) => { + assert.equal(err.message, 'encode error'); + assert(ctx); + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = ctx.session![inspect](); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200); + }); + }); + + describe('.length', () => { + it('should return session length', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.length); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('1') + .expect(200); + }); + }); + + describe('.populated', () => { + it('should return session populated', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.populated); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('true') + .expect(200); + }); + }); + + describe('.save()', () => { + it('should save session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.save(); + ctx.body = 'hello'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('hello') + .expect(200); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = null; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + assert.equal(res.header['set-cookie'], undefined); + }); + }); + + describe('an object', () => { + it('should create a session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('anything else', () => { + it('should throw', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect(/Internal Server Error/) + .expect(500); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + try { + await next(); + } catch (err: any) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session(app)); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session.name = 'funny'; + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + ctx.throw(401); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(401); + }); + }); + + describe('when maxAge present', () => { + describe('and not expire', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session!.message; + }); + + const server = app.callback(); + + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and expired', () => { + it('should expire the sess', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session.message || ''; + }); + + const server = app.callback(); + + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await new Promise(resolve => setTimeout(resolve, 200)); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect(''); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session!.maxAge; + }); + + await request(app.callback()) + .get('/') + .expect('100'); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.session!.maxAge = 100; + ctx.body = ctx.session!.foo; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save even session not change', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.maxAge = 100; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save when create session only with maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + }); + + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', async () => { + const app = App(); + const message = 'hi'; + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session!.regenerate(); + } + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, message); + ctx.body = ''; + }); + let res = await request(app.callback()) + .get('/') + .expect(200); + + const koaSession = res.get('Set-Cookie')!.join(';'); + assert.match(koaSession, /koa\.sess=/); + res = await request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200); + + const cookies = res.get('Set-Cookie')!.join(';'); + assert.match(cookies, /koa\.sess=/); + assert.notEqual(cookies, koaSession); + }); + }); + + describe('when get session before enter session middleware', () => { + it('should work', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session!.foo = 'hi'; + await next(); + }); + app.use(session({}, app)); + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + const cookies = res.get('Set-Cookie')!.join(';'); + assert(cookies.includes('koa.sess=')); + + await request(app.callback()) + .get('/') + .set('Cookie', cookies) + .expect(200); + }); + }); + + describe('options.sameSite', () => { + it('should return opt.sameSite=none', async () => { + const app = App({ sameSite: 'none' }); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + const res = await request(app.callback()) + .get('/') + .expect('bar') + .expect(200); + const cookie = res.get('Set-Cookie')!.join('|'); + assert(cookie.includes('path=/; samesite=none; httponly')); + }); + + it('should return opt.sameSite=lax', async () => { + const app = App({ sameSite: 'lax' }); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { foo: 'bar' }; + ctx.body = ctx.session.foo; + }); + + const res = await request(app.callback()) + .get('/') + .expect('bar') + .expect(200); + const cookie = res.get('Set-Cookie')!.join('|'); + assert(cookie.includes('path=/; samesite=lax; httponly')); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + }, app)); + app.use(async (ctx: Koa.Context) => { + if (!ctx.session!.foo) { + ctx.session!.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session!.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200); + + const data = res.body; + const cookies = res.get('Set-Cookie')!.join(';'); + assert(cookies.includes('koa.sess=')); + + await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200) + .expect(data); + + // should ignore uid:123 session and create a new session for uid:456 + const res2 = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200); + + assert.equal(res2.body.uid, '456'); + assert.notEqual(res2.body.foo, data.foo); + }); + }); + + describe('when options.encode and options.decode are functions', () => { + describe('they are used to encode/decode stored cookie values', () => { + it('should work', async () => { + let encodeCallCount = 0; + let decodeCallCount = 0; + + function encode(data: any) { + ++encodeCallCount; + return JSON.stringify({ enveloped: data }); + } + function decode(data: string) { + ++decodeCallCount; + return JSON.parse(data).enveloped; + } + + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + app.use(session({ + encode, + decode, + }, app)); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.counter = (ctx.session!.counter || 0) + 1; + ctx.body = ctx.session; + return; + }); + + const res = await request(app.callback()) + .get('/') + .expect(() => { assert(encodeCallCount > 0, 'encode was not called'); }) + .expect(200); + + assert.equal(res.body.counter, 1, 'expected body to be equal to session.counter'); + const cookies = res.get('Set-Cookie')!.join(';'); + const res2 = await request(app.callback()) + .get('/') + .set('Cookie', cookies) + .expect(() => { assert(decodeCallCount > 0, 'decode was not called'); }) + .expect(200); + + assert.equal(res2.body.counter, 2); + }); + }); + }); + + describe('when rolling set to true', () => { + let app: Koa; + before(() => { + app = App({ rolling: true }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', async () => { + const res = await request(app.callback()) + .get('/') + .expect({}); + + assert.equal(res.headers['set-cookie'], undefined); + }); + + it('should send set-cookie when session exists and not change', async () => { + const res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + assert.equal(res.get('Set-Cookie')!.length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + const res2 = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + assert.equal(res2.headers['set-cookie'].length, 2); + }); + }); + + describe('init multi session middleware', () => { + it('should work', () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + const s1 = session({}, app); + const s2 = session({}, app); + assert(s1); + assert(s2); + }); + }); +}); diff --git a/test/externalkey.test.js b/test/externalkey.test.js deleted file mode 100644 index 3dfd8c1..0000000 --- a/test/externalkey.test.js +++ /dev/null @@ -1,59 +0,0 @@ -'use strict'; -const Koa = require('koa'); -const request = require('supertest'); -const assert = require('assert'); -const session = require('..'); -const store = require('./store'); -const TOKEN_KEY = 'User-Token'; - -describe('Koa Session External Key', () => { - describe('when the external key set/get is invalid', () => { - it('should throw a error', () => { - try { - new App({ - externalKey: {}, - }); - } catch (err) { - assert.equal(err.message, 'externalKey.get must be function'); - } - }); - }); - describe('custom get/set external key', () => { - it('should still work', done => { - const app = App(); - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - assert(ctx.session.externalKey); - } else { - ctx.body = ctx.session.string; - assert(ctx.session.externalKey === ctx.get(TOKEN_KEY)); - } - }); - const server = app.listen(); - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const token = res.get(TOKEN_KEY); - request(server) - .get('/') - .set(TOKEN_KEY, token) - .expect(';', done); - }); - }); - }); -}); -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.store = store; - options.externalKey = options.externalKey || { - get: ctx => ctx.get(TOKEN_KEY), - set: (ctx, value) => ctx.set(TOKEN_KEY, value), - }; - app.use(session(options, app)); - return app; -} diff --git a/test/externalkey.test.ts b/test/externalkey.test.ts new file mode 100644 index 0000000..ef69845 --- /dev/null +++ b/test/externalkey.test.ts @@ -0,0 +1,60 @@ +import { strict as assert } from 'node:assert'; +import Koa from 'koa'; +import { ZodError } from 'zod'; +import { request } from '@eggjs/supertest'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import store from './store.js'; + +const TOKEN_KEY = 'User-Token'; + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.store = store; + options.externalKey = options.externalKey ?? { + get: ctx => ctx.get(TOKEN_KEY), + set: (ctx, value) => ctx.set(TOKEN_KEY, value), + }; + app.use(session(options, app)); + return app; +} + +describe('Koa Session External Key', () => { + describe('when the external key set/get is invalid', () => { + it('should throw a error', () => { + assert.throws(() => { + App({ + externalKey: {} as any, + }); + }, err => { + assert(err instanceof ZodError); + assert.match(err.message, /externalKey/); + return true; + }); + }); + }); + + describe('custom get/set external key', () => { + it('should still work', async () => { + const app = App(); + app.use(async function(ctx) { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + assert(ctx.session.externalKey); + } else { + ctx.body = ctx.session.string; + assert.equal(ctx.session.externalKey, ctx.get(TOKEN_KEY)); + } + }); + const res = await request(app.callback()) + .post('/') + .expect(204); + const token = res.get(TOKEN_KEY)!; + await request(app.callback()) + .get('/') + .set(TOKEN_KEY as any, token) + .expect(';'); + }); + }); +}); diff --git a/test/genid_bench.js b/test/genid_bench.js deleted file mode 100644 index 8ff2900..0000000 --- a/test/genid_bench.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict'; - -const Benchmark = require('benchmark'); -const uuid = require('uuid'); -const uid = require('uid-safe'); - -const suite = new Benchmark.Suite(); - -const genidByUid = () => `${Date.now()}-${uid.sync(24)}`; -const genidByUuidV1 = () => uuid.v1(); -const genidByUuidV4 = () => uuid.v4(); - -console.log('genidByUid() => %s', genidByUid()); -console.log('genidByUuidV1() => %s', genidByUuidV1()); -console.log('genidByUuidV4() => %s', genidByUuidV4()); - -// add tests -suite -.add('uid()', function() { - genidByUid(); -}) -.add('genidByUuidV1()', function() { - genidByUuidV1(); -}) -.add('genidByUuidV4()', function() { - genidByUuidV4(); -}) -// add listeners -.on('cycle', function(event) { - console.log(String(event.target)); -}) -.on('complete', function() { - console.log('Fastest is ' + this.filter('fastest').map('name')); -}) -// run async -.run({ async: true }); - -// genidByUid() => 1556529339180-DRnQyEqlYjGr_Zq_42fHpdFMlBfVlAoG -// genidByUuidV1() => 5af3b830-6a5f-11e9-91fb-abc918efca3d -// genidByUuidV4() => 27088fa8-8436-4c8b-aae7-76ba316db9e3 -// -// uid() x 260,850 ops/sec ±1.50% (84 runs sampled) -// genidByUuidV1() x 1,181,483 ops/sec ±0.93% (86 runs sampled) -// genidByUuidV4() x 301,840 ops/sec ±1.40% (83 runs sampled) -// Fastest is genidByUuidV1() diff --git a/test/store.js b/test/store.js deleted file mode 100644 index e344c72..0000000 --- a/test/store.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -const sessions = {}; - -module.exports = { - async get(key) { - return sessions[key]; - }, - - async set(key, value) { - sessions[key] = value; - }, - - async destroy(key) { - sessions[key] = undefined; - }, -}; diff --git a/test/store.test.js b/test/store.test.js deleted file mode 100644 index be3a5fe..0000000 --- a/test/store.test.js +++ /dev/null @@ -1,861 +0,0 @@ -'use strict'; - -const Koa = require('koa'); -const request = require('supertest'); -const should = require('should'); -const mm = require('mm'); -const session = require('..'); -const store = require('./store'); -const pedding = require('pedding'); -const assert = require('assert'); -const sleep = require('mz-modules/sleep'); - -const inspect = Symbol.for('nodejs.util.inspect.custom'); - -describe('Koa Session External Store', () => { - let cookie; - - describe('when the session contains a ;', () => { - it('should still work', done => { - const app = App(); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('new session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and not populated', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session; - ctx.body = 'greetings'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when populated', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message = 'hello'; - ctx.body = ''; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - done(); - }); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - }); - - describe('saved session', () => { - describe('when not accessed', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed but not changed', () => { - it('should be the same session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, done); - }); - - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.message.should.equal('hello'); - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('when accessed and changed', () => { - it('should Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.money = '$$$'; - ctx.body = 'aklsdjflasdjf'; - }); - - request(app.listen()) - .get('/') - .set('Cookie', cookie) - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - }); - - describe('when session is', () => { - describe('null', () => { - it('should expire the session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = null; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('an empty object', () => { - it('should not Set-Cookie', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = {}; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(200, (err, res) => { - if (err) return done(err); - res.header.should.not.have.property('set-cookie'); - done(); - }); - }); - }); - - describe('an object', () => { - it('should create a session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { message: 'hello' }; - ctx.body = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(200, done); - }); - }); - - describe('anything else', () => { - it('should throw', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = 'asdf'; - }); - - request(app.listen()) - .get('/') - .expect(500, done); - }); - }); - }); - - describe('session', () => { - describe('.inspect()', () => { - it('should return session content', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = ctx.session[inspect](); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect({ foo: 'bar' }) - .expect(200, done); - }); - }); - - describe('.length', () => { - it('should return session length', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.length); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('1') - .expect(200, done); - }); - }); - - describe('.populated', () => { - it('should return session populated', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.body = String(ctx.session.populated); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('true') - .expect(200, done); - }); - }); - - describe('.save()', () => { - it('should save session', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.save(); - ctx.body = 'hello'; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess=.+;/) - .expect('hello') - .expect(200, done); - }); - }); - }); - - describe('when an error is thrown downstream and caught upstream', () => { - it('should still save the session', done => { - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - - app.use(async function(ctx, next) { - try { - await next(); - } catch (err) { - ctx.status = err.status; - ctx.body = err.message; - } - }); - - app.use(session({ store }, app)); - - app.use(async function(ctx, next) { - ctx.session.name = 'funny'; - await next(); - }); - - app.use(async function(ctx) { - ctx.throw(401); - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /koa\.sess/) - .expect(401, done); - }); - }); - - describe('when maxAge present', () => { - describe('and set to be a session cookie', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 'session' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.not.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - it('should use the default maxAge when improper string given', done => { - const app = App({ maxAge: 'not the right string' }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - cookie.should.containEql('expires='); - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - describe('and not expire', () => { - it('should not expire the session', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.body = 200; - return; - } - ctx.body = ctx.session.message; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - request(server) - .get('/') - .set('cookie', cookie) - .expect('hi', done); - }); - }); - }); - - describe('and expired', () => { - it('should expire the sess', done => { - done = pedding(done, 2); - const app = App({ maxAge: 100 }); - app.on('session:expired', args => { - assert(args.key.match(/^\w+-/)); - assert(args.value); - assert(args.ctx); - done(); - }); - app.use(async function(ctx) { - if (ctx.method === 'POST') { - ctx.session.message = 'hi'; - ctx.status = 200; - return; - } - - ctx.body = ctx.session.message || ''; - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect('Set-Cookie', /koa\.sess/) - .end((err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie'].join(';'); - - setTimeout(() => { - request(server) - .get('/') - .set('cookie', cookie) - .expect('', done); - }, 200); - }); - }); - }); - }); - - describe('ctx.session.maxAge', () => { - it('should return opt.maxAge', done => { - const app = App({ maxAge: 100 }); - - app.use(async function(ctx) { - ctx.body = ctx.session.maxAge; - }); - - request(app.listen()) - .get('/') - .expect('100', done); - }); - }); - - describe('ctx.session.maxAge=', () => { - it('should set sessionOptions.maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.foo = 'bar'; - ctx.session.maxAge = 100; - ctx.body = ctx.session.foo; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save even session not change', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session.maxAge = 100; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - - it('should save when create session only with maxAge', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.session = { maxAge: 100 }; - ctx.body = ctx.session; - }); - - request(app.listen()) - .get('/') - .expect('Set-Cookie', /expires=/) - .expect(200, done); - }); - }); - - describe('ctx.session.regenerate', () => { - it('should change the session key, but not content', done => { - const app = new App(); - const message = 'hi'; - app.use(async function(ctx, next) { - ctx.session = { message: 'hi' }; - await next(); - }); - - app.use(async function(ctx, next) { - const sessionKey = ctx.cookies.get('koa.sess'); - if (sessionKey) { - await ctx.session.regenerate(); - } - await next(); - }); - - app.use(async function(ctx) { - ctx.session.message.should.equal(message); - ctx.body = ''; - }); - let koaSession = null; - request(app.callback()) - .get('/') - .expect(200, (err, res) => { - should.not.exist(err); - koaSession = res.headers['set-cookie'][0]; - koaSession.should.containEql('koa.sess='); - request(app.callback()) - .get('/') - .set('Cookie', koaSession) - .expect(200, (err, res) => { - should.not.exist(err); - const cookies = res.headers['set-cookie'][0]; - cookies.should.containEql('koa.sess='); - cookies.should.not.equal(koaSession); - done(); - }); - }); - }); - }); - - describe('when store return empty', () => { - it('should create new Session', done => { - done = pedding(done, 2); - const app = App({ signed: false }); - - app.use(async function(ctx) { - ctx.body = String(ctx.session.isNew); - }); - - app.on('session:missed', args => { - assert(args.key === 'invalid-key'); - assert(args.ctx); - done(); - }); - - request(app.listen()) - .get('/') - .set('cookie', 'koa.sess=invalid-key') - .expect('true') - .expect(200, done); - }); - }); - - describe('when valid and beforeSave set', () => { - it('should ignore session when uid changed', done => { - done = pedding(done, 2); - const app = new Koa(); - - app.keys = [ 'a', 'b' ]; - app.use(session({ - valid(ctx, sess) { - return ctx.cookies.get('uid') === sess.uid; - }, - beforeSave(ctx, sess) { - sess.uid = ctx.cookies.get('uid'); - }, - store, - }, app)); - app.use(async function(ctx) { - if (!ctx.session.foo) { - ctx.session.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); - } - - ctx.body = { - foo: ctx.session.foo, - uid: ctx.cookies.get('uid'), - }; - }); - app.on('session:invalid', args => { - assert(args.key); - assert(args.value); - assert(args.ctx); - done(); - }); - - request(app.callback()) - .get('/') - .set('Cookie', 'uid=123') - .expect(200, (err, res) => { - should.not.exist(err); - const data = res.body; - const cookies = res.headers['set-cookie'].join(';'); - cookies.should.containEql('koa.sess='); - - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=123') - .expect(200) - .expect(data, err => { - should.not.exist(err); - - // should ignore uid:123 session and create a new session for uid:456 - request(app.callback()) - .get('/') - .set('Cookie', cookies + ';uid=456') - .expect(200, (err, res) => { - should.not.exist(err); - res.body.uid.should.equal('456'); - res.body.foo.should.not.equal(data.foo); - done(); - }); - }); - }); - }); - }); - - describe('ctx.session', () => { - after(mm.restore); - - it('can be mocked', done => { - const app = App(); - - app.use(async function(ctx) { - ctx.body = ctx.session; - }); - - mm(app.context, 'session', { - foo: 'bar', - }); - - request(app.listen()) - .get('/') - .expect({ - foo: 'bar', - }) - .expect(200, done); - }); - }); - - describe('when rolling set to true', () => { - let app; - before(() => { - app = App({ rolling: true }); - - 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 exists and not change', done => { - request(app.callback()) - .get('/set') - .expect({ foo: 'bar' }) - .end((err, res) => { - should.not.exist(err); - res.headers['set-cookie'].should.have.length(2); - const cookie = res.headers['set-cookie'].join(';'); - request(app.callback()) - .get('/') - .set('cookie', cookie) - .expect(res => { - res.headers['set-cookie'].should.have.length(2); - }) - .expect({ foo: 'bar' }, done); - }); - }); - }); - - describe('when prefix present', () => { - it('should still work', done => { - const app = App({ prefix: 'sess:' }); - - app.use(async ctx => { - if (ctx.method === 'POST') { - ctx.session.string = ';'; - ctx.status = 204; - } else { - ctx.body = ctx.session.string; - } - }); - - const server = app.listen(); - - request(server) - .post('/') - .expect(204, (err, res) => { - if (err) return done(err); - const cookie = res.headers['set-cookie']; - cookie.join().should.match(/koa\.sess=sess:/); - request(server) - .get('/') - .set('Cookie', cookie.join(';')) - .expect(';', done); - }); - }); - }); - - describe('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']); - }); - }); - - describe('when get session before middleware', () => { - it('should return empty session', async () => { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - const options = {}; - options.store = store; - app.use(async (ctx, next) => { - // will not take effect - ctx.session.should.be.ok(); - ctx.session.foo = '123'; - await next(); - }); - app.use(session(options, app)); - app.use(async ctx => { - if (ctx.path === '/set') ctx.session = { foo: 'bar' }; - ctx.body = ctx.session; - }); - - let res = await request(app.callback()) - .get('/') - .expect({}); - - 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' }); - }); - }); -}); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.store = store; - app.use(session(options, app)); - return app; -} diff --git a/test/store.test.ts b/test/store.test.ts new file mode 100644 index 0000000..39ba1b8 --- /dev/null +++ b/test/store.test.ts @@ -0,0 +1,800 @@ +import { strict as assert } from 'node:assert'; +import { scheduler } from 'node:timers/promises'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import { mm } from 'mm'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import store from './store.js'; + +const inspect = Symbol.for('nodejs.util.inspect.custom'); + +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.store = store; + app.use(session(options, app)); + return app; +} + +describe('Koa Session External Store', () => { + let cookie: string; + + describe('when the session contains a ;', () => { + it('should still work', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session!.string; + } + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('new session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when accessed and not populated', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session; + ctx.body = 'greetings'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when populated', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.message = 'hello'; + ctx.body = ''; + }); + + const res = await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + + cookie = res.get('Set-Cookie')!.join(';'); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + }); + + describe('saved session', () => { + describe('when not accessed', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when accessed but not changed', () => { + it('should be the same session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + }); + + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, 'hello'); + ctx.body = 'aklsdjflasdjf'; + }); + + const res = await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when accessed and changed', () => { + it('should Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.money = '$$$'; + ctx.body = 'aklsdjflasdjf'; + }); + + await request(app.callback()) + .get('/') + .set('Cookie', cookie) + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + }); + + describe('when session is', () => { + describe('null', () => { + it('should expire the session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = null; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('an empty object', () => { + it('should not Set-Cookie', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = {}; + ctx.body = 'asdf'; + }); + + const res = await request(app.callback()) + .get('/') + .expect(200); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('an object', () => { + it('should create a session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { message: 'hello' }; + ctx.body = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(200); + }); + }); + + describe('anything else', () => { + it('should throw', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = 'asdf'; + }); + + await request(app.callback()) + .get('/') + .expect(500); + }); + }); + }); + + describe('session', () => { + describe('.inspect()', () => { + it('should return session content', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = ctx.session![inspect](); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect({ foo: 'bar' }) + .expect(200); + }); + }); + + describe('.length', () => { + it('should return session length', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.length); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('1') + .expect(200); + }); + }); + + describe('.populated', () => { + it('should return session populated', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.body = String(ctx.session!.populated); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('true') + .expect(200); + }); + }); + + describe('.save()', () => { + it('should save session', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.save(); + ctx.body = 'hello'; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess=.+;/) + .expect('hello') + .expect(200); + }); + }); + }); + + describe('when an error is thrown downstream and caught upstream', () => { + it('should still save the session', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + try { + await next(); + } catch (err: any) { + ctx.status = err.status; + ctx.body = err.message; + } + }); + + app.use(session({ store }, app)); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session!.name = 'funny'; + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + ctx.throw(401); + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /koa\.sess/) + .expect(401); + }); + }); + + describe('when maxAge present', () => { + describe('and set to be a session cookie', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 'session' }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session!.message; + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + assert(!cookie.includes('expires=')); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and not expire', () => { + it('should not expire the session', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.body = 200; + return; + } + ctx.body = ctx.session!.message; + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect('hi'); + }); + }); + + describe('and expired', () => { + it('should expire the sess', async () => { + const app = App({ maxAge: 100 }); + app.on('session:expired', args => { + assert(args.key.match(/^\w+-/)); + assert(args.value); + assert(args.ctx); + }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.message = 'hi'; + ctx.status = 200; + return; + } + + ctx.body = ctx.session!.message || ''; + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect('Set-Cookie', /koa\.sess/); + + const cookie = res.get('Set-Cookie')!.join(';'); + + await new Promise(resolve => setTimeout(resolve, 200)); + + await request(server) + .get('/') + .set('cookie', cookie) + .expect(''); + }); + }); + }); + + describe('ctx.session.maxAge', () => { + it('should return opt.maxAge', async () => { + const app = App({ maxAge: 100 }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session!.maxAge; + }); + + await request(app.callback()) + .get('/') + .expect('100'); + }); + }); + + describe('ctx.session.maxAge=', () => { + it('should set sessionOptions.maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.foo = 'bar'; + ctx.session!.maxAge = 100; + ctx.body = ctx.session!.foo; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save even session not change', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session!.maxAge = 100; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + + it('should save when create session only with maxAge', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.session = { maxAge: 100 }; + ctx.body = ctx.session; + }); + + await request(app.callback()) + .get('/') + .expect('Set-Cookie', /expires=/) + .expect(200); + }); + }); + + describe('ctx.session.regenerate', () => { + it('should change the session key, but not content', async () => { + const app = App(); + const message = 'hi'; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + ctx.session = { message: 'hi' }; + await next(); + }); + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + const sessionKey = ctx.cookies.get('koa.sess'); + if (sessionKey) { + await ctx.session!.regenerate(); + } + await next(); + }); + + app.use(async (ctx: Koa.Context) => { + assert.equal(ctx.session!.message, message); + ctx.body = ''; + }); + + let res = await request(app.callback()) + .get('/') + .expect(200); + + const koaSession = res.get('Set-Cookie')!.join(';'); + assert.match(koaSession, /koa\.sess=/); + res = await request(app.callback()) + .get('/') + .set('Cookie', koaSession) + .expect(200); + + const cookies = res.get('Set-Cookie')!.join(';'); + assert.match(cookies, /koa\.sess=/); + assert.notEqual(cookies, koaSession); + }); + }); + + describe('when store return empty', () => { + it('should create new Session', async () => { + const app = App({ signed: false }); + + app.use(async (ctx: Koa.Context) => { + ctx.body = String(ctx.session!.isNew); + }); + + app.on('session:missed', args => { + assert.equal(args.key, 'invalid-key'); + assert(args.ctx); + }); + + await request(app.callback()) + .get('/') + .set('cookie', 'koa.sess=invalid-key') + .expect('true') + .expect(200); + }); + }); + + describe('when valid and beforeSave set', () => { + it('should ignore session when uid changed', async () => { + const app = new Koa(); + + app.keys = [ 'a', 'b' ]; + app.use(session({ + valid(ctx, sess) { + return ctx.cookies.get('uid') === sess.uid; + }, + beforeSave(ctx, sess) { + sess.uid = ctx.cookies.get('uid'); + }, + store, + }, app)); + + app.use(async (ctx: Koa.Context) => { + if (!ctx.session!.foo) { + ctx.session!.foo = Date.now() + '|uid:' + ctx.cookies.get('uid'); + } + + ctx.body = { + foo: ctx.session!.foo, + uid: ctx.cookies.get('uid'), + }; + }); + + app.on('session:invalid', args => { + assert(args.key); + assert(args.value); + assert(args.ctx); + }); + + let res = await request(app.callback()) + .get('/') + .set('Cookie', 'uid=123') + .expect(200); + + const data = res.body; + const cookies = res.get('Set-Cookie')!.join(';'); + assert(cookies.includes('koa.sess=')); + + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=123') + .expect(200); + + assert.deepEqual(res.body, data); + + res = await request(app.callback()) + .get('/') + .set('Cookie', cookies + ';uid=456') + .expect(200); + + assert.equal(res.body.uid, '456'); + assert.notEqual(res.body.foo, data.foo); + }); + }); + + describe('ctx.session', () => { + after(mm.restore); + + it('can be mocked', async () => { + const app = App(); + + app.use(async (ctx: Koa.Context) => { + ctx.body = ctx.session; + }); + + mm(app.context, 'session', { + foo: 'bar', + }); + + await request(app.callback()) + .get('/') + .expect({ + foo: 'bar', + }) + .expect(200); + }); + }); + + describe('when rolling set to true', () => { + let app: Koa; + before(() => { + app = App({ rolling: true }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', async () => { + const res = await request(app.callback()) + .get('/') + .expect({}); + + assert(!res.headers['set-cookie']); + }); + + it('should send set-cookie when session exists and not change', async () => { + let res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + assert.equal(res.headers['set-cookie'].length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + + assert.equal(res.headers['set-cookie'].length, 2); + }); + }); + + describe('when prefix present', () => { + it('should still work', async () => { + const app = App({ prefix: 'sess:' }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session!.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session!.string; + } + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + assert(cookie.join().includes('koa.sess=sess:')); + + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + }); + }); + + describe('when renew set to true', () => { + let app: Koa; + before(() => { + app = App({ renew: true, maxAge: 2000 }); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + }); + + it('should not send set-cookie when session not exists', async () => { + const res = await request(app.callback()) + .get('/') + .expect({}); + + assert(!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' }); + + assert.equal(res.get('Set-Cookie')!.length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + + await scheduler.wait(1200); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + + assert.equal(res.headers['set-cookie'].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' }); + + assert.equal(res.headers['set-cookie'].length, 2); + const cookie = res.get('Set-Cookie')!.join(';'); + + await scheduler.wait(500); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + + assert(!res.headers['set-cookie']); + }); + }); + + describe('when get session before middleware', () => { + it('should return empty session', async () => { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + const options = { store }; + + app.use(async (ctx: Koa.Context, next: Koa.Next) => { + assert(ctx.session); + ctx.session.foo = '123'; + await next(); + }); + + app.use(session(options, app)); + + app.use(async (ctx: Koa.Context) => { + if (ctx.path === '/set') ctx.session = { foo: 'bar' }; + ctx.body = ctx.session; + }); + + let res = await request(app.callback()) + .get('/') + .expect({}); + + res = await request(app.callback()) + .get('/set') + .expect({ foo: 'bar' }); + + const cookie = res.get('Set-Cookie')!.join(';'); + await scheduler.wait(1200); + + res = await request(app.callback()) + .get('/') + .set('cookie', cookie) + .expect({ foo: 'bar' }); + }); + }); +}); diff --git a/test/store.ts b/test/store.ts new file mode 100644 index 0000000..43ccf80 --- /dev/null +++ b/test/store.ts @@ -0,0 +1,15 @@ +const sessions: Record = {}; + +export default { + async get(key: string) { + return sessions[key]; + }, + + async set(key: string, value: unknown) { + sessions[key] = value; + }, + + async destroy(key: string) { + sessions[key] = undefined; + }, +}; diff --git a/test/store_with_ctx.test.js b/test/store_with_ctx.test.ts similarity index 59% rename from test/store_with_ctx.test.js rename to test/store_with_ctx.test.ts index 21fe43a..e7331a5 100644 --- a/test/store_with_ctx.test.js +++ b/test/store_with_ctx.test.ts @@ -1,40 +1,49 @@ -'use strict'; +import { strict as assert } from 'node:assert'; +import Koa from 'koa'; +import { request } from '@eggjs/supertest'; +import session, { type CreateSessionOptions } from '../src/index.js'; +import store from './store_with_ctx.js'; -const Koa = require('koa'); -const request = require('supertest'); -const session = require('..'); -const store = require('./store_with_ctx'); +function App(options: CreateSessionOptions = {}) { + const app = new Koa(); + app.keys = [ 'a', 'b' ]; + options.store = store; + app.use(async (ctx, next) => { + await next(); + ctx.body = ctx.state.test === undefined ? 'undefined' : ctx.state.test; + }); + + app.use(session(options, app)); + return app; +} -describe('Koa Session External Store methods can acceess Koa context', () => { - let cookie; +describe('Koa Session External Store methods can access Koa context', () => { + let cookie: string; describe('new session', () => { describe('when not accessed', () => { it('should not set ctx.state.test variable', async () => { const app = App(); - request(app.callback()) + await request(app.callback()) .get('/') .expect('undefined'); }); }); describe('when populated', () => { - it('should set ctx.state.test variable', done => { + it('should set ctx.state.test variable', async () => { const app = App(); app.use(async ctx => { if (ctx.path === '/set') ctx.session = { foo: 'bar' }; }); - request(app.listen()) - .get('/set') - .expect(200, (err, res) => { - if (err) return done(err); - cookie = res.header['set-cookie'].join(';'); - res.text.should.equal('set'); - done(); - }); + const res = await request(app.callback()) + .get('/set') + .expect(200); + cookie = res.get('Set-Cookie')!.join(';'); + assert.equal(res.text, 'set'); }); }); @@ -42,7 +51,7 @@ describe('Koa Session External Store methods can acceess Koa context', () => { it('should access ctx.state.test variable', async () => { const app = App(); - request(app.callback()) + await request(app.callback()) .get('/') .set('Cookie', cookie) .expect('get'); @@ -54,10 +63,12 @@ describe('Koa Session External Store methods can acceess Koa context', () => { const app = App(); app.use(async ctx => { - if (ctx.path === '/destroy') ctx.session = null; + if (ctx.path === '/destroy') { + ctx.session = null; + } }); - request(app.callback()) + await request(app.callback()) .get('/destroy') .set('Cookie', cookie) .expect('destroyed'); @@ -65,17 +76,3 @@ describe('Koa Session External Store methods can acceess Koa context', () => { }); }); }); - -function App(options) { - const app = new Koa(); - app.keys = [ 'a', 'b' ]; - options = options || {}; - options.store = store; - app.use(async (ctx, next) => { - await next(); - ctx.body = ctx.state.test === undefined ? 'undefined' : ctx.state.test; - }); - - app.use(session(options, app)); - return app; -} diff --git a/test/store_with_ctx.js b/test/store_with_ctx.ts similarity index 55% rename from test/store_with_ctx.js rename to test/store_with_ctx.ts index 2ebd234..7e50c7b 100644 --- a/test/store_with_ctx.js +++ b/test/store_with_ctx.ts @@ -1,21 +1,19 @@ -'use strict'; +const sessions: Record = {}; -const sessions = {}; - -module.exports = { - async get(key, maxAge, options) { +export default { + async get(key: string, _maxAge: number, options: any) { // check access to options.ctx options.ctx.state.test = 'get'; return sessions[key]; }, - async set(key, sess, maxAge, options) { + async set(key: string, sess: Record, _maxAge: number, options: any) { // check access to options.ctx options.ctx.state.test = 'set'; sessions[key] = sess; }, - async destroy(key, options) { + async destroy(key: string, options: any) { // check access to options.ctx options.ctx.state.test = 'destroyed'; sessions[key] = undefined; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ff41b73 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext" + } +} From 6d068ffcb7a0976a8da6d70f32624575f9061393 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 19 Jan 2025 07:32:03 +0000 Subject: [PATCH 79/84] Release 7.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [skip ci] ## [7.0.0](https://github.com/koajs/session/compare/v6.4.0...v7.0.0) (2025-01-19) ### ⚠ BREAKING CHANGES * drop Node.js < 18.19.0 support ### Features * support cjs and esm both by tshy ([#228](https://github.com/koajs/session/issues/228)) ([575864c](https://github.com/koajs/session/commit/575864c6ae7504da15e72e8112a99757f3eee188)) --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e86323..f2a9a24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# Changelog + +## [7.0.0](https://github.com/koajs/session/compare/v6.4.0...v7.0.0) (2025-01-19) + + +### ⚠ BREAKING CHANGES + +* drop Node.js < 18.19.0 support + +### Features + +* support cjs and esm both by tshy ([#228](https://github.com/koajs/session/issues/228)) ([575864c](https://github.com/koajs/session/commit/575864c6ae7504da15e72e8112a99757f3eee188)) 6.4.0 / 2023-02-04 ================== diff --git a/package.json b/package.json index 725d6c3..eea068b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git@github.com:koajs/session.git" }, - "version": "6.4.0", + "version": "7.0.0", "keywords": [ "koa", "middleware", From 3421008381a3aacf2e43d52314749eb850851193 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 19 Jan 2025 16:16:45 +0800 Subject: [PATCH 80/84] fix: remove unused dependencies (#229) --- __snapshots__/index.test.ts.js | 9 +++++++++ package.json | 3 +-- src/index.ts | 8 ++++---- test/index.test.ts | 11 +++++++++++ 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 __snapshots__/index.test.ts.js create mode 100644 test/index.test.ts diff --git a/__snapshots__/index.test.ts.js b/__snapshots__/index.test.ts.js new file mode 100644 index 0000000..6b9c1e0 --- /dev/null +++ b/__snapshots__/index.test.ts.js @@ -0,0 +1,9 @@ +exports['test/index.test.ts SessionOptions schema should have a valid schema 1'] = { + "key": "koa.sess", + "autoCommit": true, + "overwrite": true, + "httpOnly": true, + "signed": true, + "rolling": false, + "renew": false +} diff --git a/package.json b/package.json index eea068b..c92a61c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "koa": "2", "mm": "4", "rimraf": "6", + "snap-shot-it": "^7.9.10", "tshy": "3", "tshy-after": "1", "typescript": "5" @@ -33,9 +34,7 @@ "license": "MIT", "dependencies": { "crc": "^3.8.0", - "debug": "^4.3.3", "is-type-of": "^2.2.0", - "uuid": "^8.3.2", "zod": "^3.24.1" }, "engines": { diff --git a/src/index.ts b/src/index.ts index 0652ffe..e116660 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ const debug = debuglog('koa-session'); const GET_CONTEXT_SESSION = Symbol('get contextSession'); const CONTEXT_SESSION_INSTANCE = Symbol('contextSession instance'); -const SessionOptionsSchema = z.object({ +export const SessionOptions = z.object({ /** * cookie key * Default is `koa.sess` @@ -159,9 +159,9 @@ const SessionOptionsSchema = z.object({ .optional(), }); -const DEFAULT_SESSION_OPTIONS = SessionOptionsSchema.parse({}); +const DEFAULT_SESSION_OPTIONS = SessionOptions.parse({}); -export type SessionOptions = z.infer; +export type SessionOptions = z.infer; export type CreateSessionOptions = Partial; type Middleware = (ctx: any, next: any) => Promise; @@ -199,7 +199,7 @@ export function createSession(opts: CreateSessionOptions | any, app: any): Middl ...DEFAULT_SESSION_OPTIONS, ...opts, }; - SessionOptionsSchema.parse(options); + SessionOptions.parse(options); options = formatOptions(options); extendContext(app.context, options); diff --git a/test/index.test.ts b/test/index.test.ts new file mode 100644 index 0000000..3b49ed6 --- /dev/null +++ b/test/index.test.ts @@ -0,0 +1,11 @@ +import snapshot from 'snap-shot-it'; +import { SessionOptions } from '../src/index.js'; + +describe('test/index.test.ts', () => { + describe('SessionOptions schema', () => { + it('should have a valid schema', () => { + const parsed = SessionOptions.parse({}); + snapshot(parsed); + }); + }); +}); From 6dfdcf675f28d2661bbeb59b9d42bf5c5afdb47f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 19 Jan 2025 08:17:28 +0000 Subject: [PATCH 81/84] Release 7.0.1 [skip ci] ## [7.0.1](https://github.com/koajs/session/compare/v7.0.0...v7.0.1) (2025-01-19) ### Bug Fixes * remove unused dependencies ([#229](https://github.com/koajs/session/issues/229)) ([3421008](https://github.com/koajs/session/commit/3421008381a3aacf2e43d52314749eb850851193)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2a9a24..0f1bd57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [7.0.1](https://github.com/koajs/session/compare/v7.0.0...v7.0.1) (2025-01-19) + + +### Bug Fixes + +* remove unused dependencies ([#229](https://github.com/koajs/session/issues/229)) ([3421008](https://github.com/koajs/session/commit/3421008381a3aacf2e43d52314749eb850851193)) + ## [7.0.0](https://github.com/koajs/session/compare/v6.4.0...v7.0.0) (2025-01-19) diff --git a/package.json b/package.json index c92a61c..2cfa6bc 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git@github.com:koajs/session.git" }, - "version": "7.0.0", + "version": "7.0.1", "keywords": [ "koa", "middleware", From c45bf8b236f8b79f4d2e4ed40b7fae585f30330b Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 19 Jan 2025 17:34:26 +0800 Subject: [PATCH 82/84] fix: make sure options instance is not mutated (#230) --- src/index.ts | 19 +++++++++++-------- test/store.test.ts | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/index.ts b/src/index.ts index e116660..74ec11a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -188,19 +188,23 @@ export function createSession(opts: CreateSessionOptions | any, app: any): Middl throw new TypeError('app instance required: `session(opts, app)`'); } + const options: SessionOptions = opts ?? {}; + // back-compat maxage - if (opts && !('maxAge' in opts) && 'maxage' in opts) { - Reflect.set(opts, 'maxAge', Reflect.get(opts, 'maxage')); + if (!('maxAge' in options) && 'maxage' in options) { + Reflect.set(options, 'maxAge', Reflect.get(options, 'maxage')); if (process.env.NODE_ENV !== 'production') { - console.warn('DeprecationWarning: `maxage` option has been renamed to `maxAge`'); + console.warn('[koa-session] DeprecationWarning: `maxage` option has been renamed to `maxAge`'); } } - let options = { + + // keep backwards compatibility: make sure options instance is not mutated + Object.assign(options, { ...DEFAULT_SESSION_OPTIONS, - ...opts, - }; + ...options, + }); SessionOptions.parse(options); - options = formatOptions(options); + formatOptions(options); extendContext(app.context, options); return async function session(ctx: any, next: any) { @@ -264,7 +268,6 @@ function formatOptions(opts: SessionOptions) { opts.genid = () => randomUUID(); } } - return opts; } /** diff --git a/test/store.test.ts b/test/store.test.ts index 39ba1b8..3f300ab 100644 --- a/test/store.test.ts +++ b/test/store.test.ts @@ -11,7 +11,7 @@ const inspect = Symbol.for('nodejs.util.inspect.custom'); function App(options: CreateSessionOptions = {}) { const app = new Koa(); app.keys = [ 'a', 'b' ]; - options.store = store; + options.store = options.store ?? store; app.use(session(options, app)); return app; } @@ -21,14 +21,15 @@ describe('Koa Session External Store', () => { describe('when the session contains a ;', () => { it('should still work', async () => { - const app = App(); + const options: CreateSessionOptions = { store }; + const app = App(options); app.use(async (ctx: Koa.Context) => { if (ctx.method === 'POST') { - ctx.session!.string = ';'; + ctx.session.string = ';'; ctx.status = 204; } else { - ctx.body = ctx.session!.string; + ctx.body = ctx.session.string; } }); @@ -43,6 +44,37 @@ describe('Koa Session External Store', () => { .set('Cookie', cookie.join(';')) .expect(';'); }); + + it('should disable store on options', async () => { + const options: CreateSessionOptions = { store }; + const app = App(options); + + app.use(async (ctx: Koa.Context) => { + if (ctx.method === 'POST') { + ctx.session.string = ';'; + ctx.status = 204; + } else { + ctx.body = ctx.session.string ?? 'new session create'; + } + }); + + const server = app.callback(); + const res = await request(server) + .post('/') + .expect(204); + + const cookie = res.get('Set-Cookie')!; + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect(';'); + + options.store = undefined; + await request(server) + .get('/') + .set('Cookie', cookie.join(';')) + .expect('new session create'); + }); }); describe('new session', () => { From 36beadadf9db645ae5a0c8544544e4e6cfb203cc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 19 Jan 2025 09:35:09 +0000 Subject: [PATCH 83/84] Release 7.0.2 [skip ci] ## [7.0.2](https://github.com/koajs/session/compare/v7.0.1...v7.0.2) (2025-01-19) ### Bug Fixes * make sure options instance is not mutated ([#230](https://github.com/koajs/session/issues/230)) ([c45bf8b](https://github.com/koajs/session/commit/c45bf8b236f8b79f4d2e4ed40b7fae585f30330b)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1bd57..10dfcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [7.0.2](https://github.com/koajs/session/compare/v7.0.1...v7.0.2) (2025-01-19) + + +### Bug Fixes + +* make sure options instance is not mutated ([#230](https://github.com/koajs/session/issues/230)) ([c45bf8b](https://github.com/koajs/session/commit/c45bf8b236f8b79f4d2e4ed40b7fae585f30330b)) + ## [7.0.1](https://github.com/koajs/session/compare/v7.0.0...v7.0.1) (2025-01-19) diff --git a/package.json b/package.json index 2cfa6bc..808789e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "git@github.com:koajs/session.git" }, - "version": "7.0.1", + "version": "7.0.2", "keywords": [ "koa", "middleware", From 968ae94b8a1f3f8ed53cb1e6dbab1c1c6704329f Mon Sep 17 00:00:00 2001 From: "Sixia \"Leask\" Huang" Date: Sun, 6 Apr 2025 18:00:42 -0400 Subject: [PATCH 84/84] Update Readme.md (#231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Providing `nul` as `sameSite` will lead to errors. Zod requires sameSite to be a string, so the description in the documentation was inaccurate and has been corrected. ``` ZodError: [ { "code": "invalid_type", "expected": "string", "received": "null", "path": [ "sameSite" ], "message": "Expected string, received null" } ] ``` ## Checklist - [✅] I have ensured my pull request is not behind the main or master branch of the original repository. - [✅] I have rebased all commits where necessary so that reviewing this pull request can be done without having to merge it first. - [✅] I have written a commit message that passes commitlint linting. - [✅] I have ensured that my code changes pass linting tests. - [✅] I have ensured that my code changes pass unit tests. - [✅] I have described my pull request and the reasons for code changes along with context if necessary. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 34ee099..61c14ac 100644 --- a/Readme.md +++ b/Readme.md @@ -58,7 +58,7 @@ const CONFIG = { 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)*/ secure: true, /** (boolean) secure cookie*/ - sameSite: null, /** (string) session cookie sameSite options (default null, don't set it) */ + sameSite: null, /** (string) session cookie sameSite options (default null, do not provide this key if you are not restricting sameSite) */ }; app.use(session(CONFIG, app));