From 21bd956d6d21b058b3ef6d44511fbcb95a1b001d Mon Sep 17 00:00:00 2001 From: Tema Smirnov Date: Tue, 28 Aug 2018 13:53:47 +0300 Subject: [PATCH] v0.0.6 (#20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improved detection of empty session data * ✅ Improved tests (better coverage) * ✅ Added new tests (better coverage) * Tests for `format.serialize` and `format.deserialize` functions of lowdb storage * Test for `isPromise()` function * Exporting `isPromise()` function now * 🔧 Ignoring file `sessions.json`, which comes after running tests * ⬆️ Upgrading dependencies * Added .npmrc (disable package-lock.json) * 📝 Improve documentation & JSDoc config [ci skip] * 💥 Default storage type: storageFileAsync -> storageFileSync, see #16 * 👌 Possible fix for #16 and #13 * 🔧 Move ESLint config from package.json to .eslintrc [ci skip] * Fixing tests (TravisCI failing builds for Node 6) --- .eslintignore | 2 ++ .eslintrc | 19 +++++++++++ .gitignore | 1 + .npmrc | 1 + .vscode/launch.json | 15 +++++++++ README.md | 11 ++++-- examples/extra.js | 11 ++++-- jsdoc.json | 36 ++++++++++++++++++-- lib/session.js | 30 ++++++++--------- package.json | 31 +++-------------- tests/general.js | 71 +++++++++++++++++++++++++++++++++++++++ tests/storageFileAsync.js | 32 ++++++++++++++++-- tests/storageFileSync.js | 23 +++++++++++-- 13 files changed, 229 insertions(+), 54 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc create mode 100644 .npmrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..3465ead --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +docs/ diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..5d7ab73 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,19 @@ +{ + "env": { + "es6": true, + "node": true, + "mocha": true + }, + "extends": "standard", + "plugins": ["mocha"], + "parserOptions": { + "ecmaVersion": 2016 + }, + "rules": { + "one-var": 0, + "comma-dangle": 0, + "new-cap": 0, + "padded-blocks": 0, + "brace-style": [0, "1tbs", { "allowSingleLine": true }] + } +} diff --git a/.gitignore b/.gitignore index d323e1a..2af24b7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ coverage # Databases *_db.json +sessions.json # Created by https://www.gitignore.io/api/node,linux,macos,windows,visualstudiocode diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..43c97e7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.vscode/launch.json b/.vscode/launch.json index 7abc3c0..1390919 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,5 +11,20 @@ "DEBUG": "telegraf:*" }, "console": "integratedTerminal" + }, + { + "type": "node", + "request": "launch", + "name": "Tests", + "runtimeExecutable": "npm", + "runtimeArgs": [ + "run", + "test" + ], + "console": "integratedTerminal", + "env": { + "DEBUG": "telegraf:session-local:test" + }, + "port": 9229 }] } diff --git a/README.md b/README.md index caae652..7f76d0b 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ const localSession = new LocalSession({ database: 'example_db.json', // Name of session property object in Telegraf Context (default: 'session') property: 'session', - // Type of lowdb storage (default: 'storagefileAsync') - storage: LocalSession.storagefileAsync, + // Type of lowdb storage (default: 'storageFileSync') + storage: LocalSession.storageFileAsync, // Format of storage/database (default: JSON.stringify / JSON.parse) format: { serialize: (obj) => JSON.stringify(obj, null, 2), // null & 2 for pretty-formatted JSON @@ -86,6 +86,13 @@ const localSession = new LocalSession({ state: { messages: [] } }) +// Wait for database async initialization finished (storageFileAsync or your own asynchronous storage adapter) +localSession.DB.then(DB => { + // Database now initialized, so now you can retrieve anything you want from it + console.log('Current LocalSession DB:', DB.value()) + // console.log(DB.get('sessions').getById('1:1').value()) +}) + // Telegraf will use `telegraf-session-local` configured above middleware with overrided `property` name Bot.use(localSession.middleware(property)) diff --git a/examples/extra.js b/examples/extra.js index bf5f9bf..f57b152 100644 --- a/examples/extra.js +++ b/examples/extra.js @@ -12,8 +12,8 @@ const localSession = new LocalSession({ database: 'example_db.json', // Name of session property object in Telegraf Context (default: 'session') property: 'session', - // Type of lowdb storage (default: 'storagefileAsync') - storage: LocalSession.storagefileAsync, + // Type of lowdb storage (default: 'storageFileSync') + storage: LocalSession.storageFileAsync, // Format of storage/database (default: JSON.stringify / JSON.parse) format: { serialize: (obj) => JSON.stringify(obj, null, 2), // null & 2 for pretty-formatted JSON @@ -23,6 +23,13 @@ const localSession = new LocalSession({ state: { messages: [] } }) +// Wait for database async initialization finished (storageFileAsync or your own asynchronous storage adapter) +localSession.DB.then(DB => { + // Database now initialized, so now you can retrieve anything you want from it + console.log('Current LocalSession DB:', DB.value()) + // console.log(DB.get('sessions').getById('1:1').value()) +}) + // Telegraf will use `telegraf-session-local` configured above middleware with overrided `property` name Bot.use(localSession.middleware(property)) diff --git a/jsdoc.json b/jsdoc.json index c91b8e5..62d5b4e 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -32,7 +32,39 @@ } }, "docdash": { - "static": false, - "sort": false + "static": true, + "sort": true, + "sectionOrder": [ + "Modules", + "Namespaces", + "Classes", + "Externals", + "Events", + "Mixins", + "Tutorials", + "Interfaces" + ], + "search": true, + "collapse": false, + "typedefs": true, + "meta": { + "title": "Telegraf Session local Documentation", + "keyword": "Telegram Telegraf Session Local Database JSON XML", + "description": "Documentation for Telegraf Session local" + }, + "menu": { + "GitHub Repository ⧉": { + "href": "https://github.com/RealSpeaker/telegraf-session-local", + "target": "_blank", + "class": "menu-item", + "id": "link_repo" + }, + "NPM Package ⧉": { + "href": "https://www.npmjs.com/package/telegraf-session-local", + "target": "_blank", + "class": "menu-item", + "id": "link_npm" + } + } } } diff --git a/lib/session.js b/lib/session.js index 7d5714d..766cd98 100644 --- a/lib/session.js +++ b/lib/session.js @@ -13,7 +13,7 @@ const * @param {String} [options.property] - Name of property in {@link https://telegraf.js.org/#/?id=context|Telegraf Context} where session object will be located `(default: 'session')` * @param {Object} [options.state] - Initial state of database. You can use it to pre-init database Arrays/Objects to store your own data `(default: {})` * @param {Function} [options.getSessionKey] - Function to get identifier for session from {@link https://telegraf.js.org/#/?id=context|Telegraf Context} (may implement it on your own) - * @param {Object} [options.storage] - lowdb storage option for implementing your own storage read/write operations `(default: {@link LocalSession.storageFileAsync|LocalSession.storageFileAsync})` + * @param {Object} [options.storage] - lowdb storage option for implementing your own storage read/write operations `(default: {@link LocalSession.storageFileSync|LocalSession.storageFileSync})` * @param {Function} [options.storage.read] - lowdb storage read function, must return an object or a Promise * @param {Function} [options.storage.write] - lowdb storage write function, must return undefined or a Promise * @param {Object} [options.format] - lowdb storage format option for implementing your own database format for read/write operations @@ -24,7 +24,8 @@ const class LocalSession { constructor (options = {}) { this.options = Object.assign({ - storage: LocalSession.storageFileAsync, + // TODO: Use storageFileAsync as default with support of Promise or Promise-like initialization, see: https://git.io/fA3ZN + storage: LocalSession.storageFileSync, database: 'sessions.json', property: 'session', state: { }, @@ -54,12 +55,10 @@ class LocalSession { if (this.options.storage === LocalSession.storageMemory) { debug('Initiating: lowdb adapter: storageMemory') this._adapter = new LocalSession.storageMemory(this.options.database, defaultAdaptersOptions) - // TODO: Remove back-compat conditional in next version due to deprecation storagefileAsync - } else if (this.options.storage === LocalSession.storageFileAsync || this.options.storage === LocalSession.storagefileAsync) { + } else if (this.options.storage === LocalSession.storageFileAsync) { debug('Initiating: lowdb adapter: storageFileAsync') this._adapter = new LocalSession.storageFileAsync(this.options.database, defaultAdaptersOptions) - // TODO: Remove back-compat conditional in next version due to deprecation of storagefileSync - } else if (this.options.storage === LocalSession.storageFileSync || this.options.storage === LocalSession.storagefileSync) { + } else if (this.options.storage === LocalSession.storageFileSync) { debug('Initiating: lowdb adapter: storageFileSync') this._adapter = new LocalSession.storageFileSync(this.options.database, defaultAdaptersOptions) } else { @@ -72,7 +71,9 @@ class LocalSession { if (isPromise(DbInstance)) { debug('DbInstance is Promise like') // TODO: Split it from constructor, because this code will produce glitches if async initiating may take too long time - DbInstance.then((DB) => { + this.DB = DbInstance + this.DB.then((DB) => { + debug('DbInstance Promise resolved') this.DB = DB _initDB.call(this) }) @@ -119,7 +120,7 @@ class LocalSession { this._called(arguments) if (!key) return // If there's no data provided or it's empty, we should remove session record from database - if (!data || Object.keys(data).length === 0) { + if (this.DB._.isEmpty(data)) { debug('Removing session #', key) return this.DB.get('sessions').removeById(key).write() } @@ -190,12 +191,10 @@ class LocalSession { * * @memberof! LocalSession * @name LocalSession.storagefileSync - * @deprecated since version 0.0.5 renamed to {@link LocalSession.storageFileSync|LocalSession.storageFileSync} - * @global + * @alias LocalSession.storageFileSync * @readonly */ static get storagefileSync () { - console.warn('> DeprWarn: LocalSession.storagefileSync renamed to storageFileSync') return storageFileSync } @@ -216,13 +215,10 @@ class LocalSession { * * @memberof! LocalSession * @name LocalSession.storagefileAsync - * @deprecated since version 0.0.5 renamed to {@link LocalSession.storageFileAsync|LocalSession.storageFileAsync} - * @deprecated since version 0.0.5 - * @global + * @alias LocalSession.storageFileAsync * @readonly */ static get storagefileAsync () { - console.warn('> DeprWarn: LocalSession.storagefileAsync renamed to storageFileAsync') return storageFileAsync } @@ -279,6 +275,7 @@ function _initDB () { // If database is empty, fill it with empty Array of sessions and optionally with initial state this.DB.defaults(Object.assign({ sessions: [] }, this.options.state)).write() debug('Initiating finished') + return true } // Credits to `is-promise` package @@ -287,7 +284,7 @@ function isPromise (obj) { } /** - * @overview {@link http://telegraf.js.org/|Telegraf} Session middleware for storing sessions locally (memory/fileSync/fileAsync/...) + * @overview {@link http://telegraf.js.org/|Telegraf} Session middleware for storing sessions locally (Memory/FileSync/FileAsync/...) * @module telegraf-session-local * @version 0.0.5 * @license MIT @@ -298,3 +295,4 @@ function isPromise (obj) { * @exports LocalSession */ module.exports = LocalSession +module.exports.isPromise = isPromise diff --git a/package.json b/package.json index 5f1fc13..1dec9aa 100644 --- a/package.json +++ b/package.json @@ -42,27 +42,6 @@ "coverage": "nyc report --reporter=text-lcov | coveralls", "jsdoc": "node_modules/.bin/jsdoc -c jsdoc.json" }, - "eslintConfig": { - "extends": [ - "standard" - ], - "plugins": [ - "mocha" - ], - "env": { - "mocha": true, - "node": true - }, - "rules": { - "one-var": 0, - "comma-dangle": 0, - "new-cap": 0, - "brace-style": [0, "1tbs", { "allowSingleLine": true }] - } - }, - "eslintIgnore": [ - "docs/*" - ], "dependencies": { "debug": "^3.1.0", "lodash-id": "^0.14.0", @@ -74,17 +53,17 @@ "devDependencies": { "coveralls": "^3.0.0", "docdash": "^1.0.0", - "eslint": "^5.0.0", + "eslint": "^5.4.0", "eslint-config-standard": "^11.0.0", - "eslint-plugin-import": "^2.12.0", - "eslint-plugin-mocha": "^5.0.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-mocha": "^5.2.0", "eslint-plugin-node": "^7.0.1", "eslint-plugin-promise": "^4.0.0", "eslint-plugin-standard": "^3.1.0", "jsdoc": "^3.5.5", "mocha": "^5.2.0", - "nyc": "^12.0.2", - "should": "^13.1.0", + "nyc": "^13.0.1", + "should": "^13.2.3", "telegraf": "^2.0.0 || ^3.0.0" } } diff --git a/tests/general.js b/tests/general.js index ab62aeb..8d936c4 100644 --- a/tests/general.js +++ b/tests/general.js @@ -10,6 +10,43 @@ describe('Telegraf Session local : General', () => { let bot = {} let localSession = new LocalSession(options) + it('Should works without specifying any options for LocalSession', (done) => { + bot = new Telegraf() + let session = new LocalSession() + bot.on('text', session.middleware(), (ctx) => { + should.exist(ctx.session) + done() + }) + // Temporary using setTimeout() because `telegraf-local-session` doesn't handle async adapters correctly yet + setTimeout(() => { + bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) + }, 25) + }) + + it('Should use custom `format.serialize` and `format.deserialize` functions', (done) => { + bot = new Telegraf() + let session = new LocalSession({ + database: 'test_sync_db.json', + storage: LocalSession.storageFileSync, + format: { + // By default lowdb uses pretty-printed JSON string: JSON.stringify(obj, null, 2) + // We will override that behaviour calling it `oneline`, making one-lined JSON string + serialize: function oneline (obj) { + return JSON.stringify(obj) + }, + deserialize: JSON.parse + } + }) + bot.on('text', session.middleware(), (ctx) => { + should.exist(ctx.session) + ctx.session.wow = true + // ctx.session.should.have.property('wow') + // ctx.session.foo.should.be.equal(true) + done() + }) + bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) + }) + it('Should have access to lowdb instance via ctx.sessionDB', (done) => { bot = new Telegraf() bot.on('text', localSession.middleware(), (ctx) => { @@ -39,4 +76,38 @@ describe('Telegraf Session local : General', () => { }) bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) }) + + it('Should return `undefined` when context has no `from` field', (done) => { + bot = new Telegraf() + bot.on('text', localSession.middleware(), (ctx) => { + debug('Telegraf context `from` field: %o', ctx.from) + should.not.exists(localSession.getSessionKey(ctx)) + done() + }) + bot.handleUpdate({ message: { chat: { id: 1 }, text: 'hey' } }) + }) + + it('Should return `undefined` when no key provided for session to be saved', (done) => { + bot = new Telegraf() + bot.on('text', localSession.middleware(), (ctx) => { + let sessionKey = localSession.getSessionKey(ctx) + debug('Real session key calculated by LocalSession: %s', sessionKey) + should.not.exists(localSession.saveSession(undefined, { authenticated: false })) + done() + }) + bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) + }) + + it('Should detect if object is Promise/like or not', (done) => { + const isPromise = require('../lib/session').isPromise + function notPromise () { return null } + function promise () { return new Promise((resolve, reject) => resolve(null)) } + function promiseLike () { return { then: cb => cb(null) } } + isPromise(undefined).should.be.equal(false) + isPromise(true).should.be.equal(false) + isPromise(notPromise()).should.be.equal(false) + isPromise(promise()).should.be.equal(true) + isPromise(promiseLike()).should.be.equal(true) + done() + }) }) diff --git a/tests/storageFileAsync.js b/tests/storageFileAsync.js index 7e37e5f..d5af4e1 100644 --- a/tests/storageFileAsync.js +++ b/tests/storageFileAsync.js @@ -6,9 +6,15 @@ const debug = require('debug')('telegraf:session-local:test'), options = { database: 'test_async_db.json', storage: LocalSession.storageFileAsync } +let bot = {} +let localSession = new LocalSession(options) + +// Wait for database async initialization finished +before((done) => { + localSession.DB.then((DB) => { done() }) +}) + describe('Telegraf Session local : storageFileAsync', () => { - let bot = {} - let localSession = new LocalSession(options) it('storageFileAsync: Should retrieve and save session', (done) => { const key = '1:1' // ChatID:FromID @@ -16,7 +22,7 @@ describe('Telegraf Session local : storageFileAsync', () => { debug('getSession %O', session) should.exist(session) session.foo = 42 - localSession.saveSession(key, session).then(_session => { + localSession.saveSession(key, session).then((_session) => { debug('Saved session %O', _session) should.exist(_session) _session.data.should.be.deepEqual({ foo: 42 }) @@ -71,4 +77,24 @@ describe('Telegraf Session local : storageFileAsync', () => { }) bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) }) + + it('storageFileAsync: Should work properly with deprecated stoarge name - storagefileAsync', (done) => { + let _options = Object.assign({ storage: LocalSession.storagefileAsync }, options) + let _localSession = new LocalSession(_options) + // Wait for database async initialization finished + _localSession.DB.then((DB) => { + // console.log(DB.get('sessions').getById('1:1').value()) + const key = '1:1' // ChatID:FromID + let session = _localSession.getSession(key) + debug('getSession %O', session) + should.exist(session) + session.foo = 42 + _localSession.saveSession(key, session).then((_session) => { + debug('Saved session %O', _session) + should.exist(_session) + _session.data.should.be.deepEqual({ foo: 42 }) + done() + }) + }) + }) }) diff --git a/tests/storageFileSync.js b/tests/storageFileSync.js index 6598a1d..6b0ccec 100644 --- a/tests/storageFileSync.js +++ b/tests/storageFileSync.js @@ -6,9 +6,10 @@ const debug = require('debug')('telegraf:session-local:test'), options = { database: 'test_sync_db.json', storage: LocalSession.storageFileSync } +let bot = {} +let localSession = new LocalSession(options) + describe('Telegraf Session local : storageFileSync', () => { - let bot = {} - let localSession = new LocalSession(options) it('storageFileSync: Should retrieve and save session', (done) => { const key = '1:1' // ChatID:FromID @@ -16,7 +17,7 @@ describe('Telegraf Session local : storageFileSync', () => { debug('getSession %O', session) should.exist(session) session.foo = 42 - localSession.saveSession(key, session).then(_session => { + localSession.saveSession(key, session).then((_session) => { debug('Saved session %O', _session) should.exist(_session) _session.data.should.be.deepEqual({ foo: 42 }) @@ -71,4 +72,20 @@ describe('Telegraf Session local : storageFileSync', () => { }) bot.handleUpdate({ message: { chat: { id: 1 }, from: { id: 1 }, text: 'hey' } }) }) + + it('storageFileSync: Should work properly with deprecated stoarge name - storagefileSync', (done) => { + let _options = Object.assign({ storage: LocalSession.storagefileSync }, options) + let _localSession = new LocalSession(_options) + const key = '1:1' // ChatID:FromID + let session = _localSession.getSession(key) + debug('getSession %O', session) + should.exist(session) + session.foo = 42 + _localSession.saveSession(key, session).then((_session) => { + debug('Saved session %O', _session) + should.exist(_session) + _session.data.should.be.deepEqual({ foo: 42 }) + done() + }) + }) })