diff --git a/README.md b/README.md index f782025..1c882b4 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ account.on('signout', redirectToHome) - [account.one](#accountone) - [account.off](#accountoff) - [Events](#events) +- [Hooks](#hooks) - [Requests](#requests) ### Constructor @@ -994,6 +995,42 @@ hoodie.off('connectionstatus:disconnected', showNotification) +### Hooks + +```js +// clear user’s local store signin and after signout +account.hook.before('signin', function (options) { + return localUserStore.clear() +}) +account.hook.after('signout', function (options) { + return localUserStore.clear() +}) +``` + + + + + + + + + + + + + + + + +
+ Hook + + Arguments +
signinoptions as they were passed into account.signIn(options)
signout{}
+ +See [before-after-hook](https://www.npmjs.com/package/before-after-hook) for +more information. + ### Requests Hoodie comes with a list of built-in account requests, which can be disabled, diff --git a/admin/index.js b/admin/index.js index 19baa9a..ffa9937 100644 --- a/admin/index.js +++ b/admin/index.js @@ -1,6 +1,7 @@ module.exports = AccountAdmin var EventEmitter = require('events').EventEmitter +var Hook = require('before-after-hook') var getAccount = require('../utils/get-account') @@ -36,7 +37,8 @@ function AccountAdmin (options) { cacheKey: cacheKey, emitter: emitter, account: getAccount({cacheKey: cacheKey}), - url: options.url + url: options.url, + hook: new Hook() } var admin = { @@ -68,7 +70,8 @@ function AccountAdmin (options) { on: events.on.bind(null, state), one: events.one.bind(null, state), - off: events.off.bind(null, state) + off: events.off.bind(null, state), + hook: state.hook.api } // sessions.add can use accounts.find to lookup user id by username diff --git a/index.js b/index.js index c5ef7aa..c2bd219 100644 --- a/index.js +++ b/index.js @@ -38,6 +38,7 @@ function Account (options) { on: events.on.bind(null, state), one: events.one.bind(null, state), off: events.off.bind(null, state), + hook: state.hook.api, validate: require('./lib/validate').bind(null, state) } } diff --git a/lib/sign-in.js b/lib/sign-in.js index d9816f5..e55b2bd 100644 --- a/lib/sign-in.js +++ b/lib/sign-in.js @@ -3,7 +3,6 @@ module.exports = signIn var Promise = require('lie') var clone = require('lodash/clone') var get = require('lodash/get') -var invokeMap = require('lodash/invokeMap') var internals = module.exports.internals = {} internals.deserialise = require('../utils/deserialise') @@ -16,77 +15,50 @@ function signIn (state, options) { return Promise.reject(new Error('options.username and options.password is required')) } - var preHooks = [] - // note: the `pre:signin` & `post:signin` events are not considered public - // APIs and might change in future without notice - // https://github.com/hoodiehq/hoodie-account-client/issues/65 - state.emitter.emit('pre:signin', { hooks: preHooks }) - - return Promise.resolve() - - .then(function () { - return Promise.all(invokeMap(preHooks, 'call')) - }) - - .then(function () { + return state.hook('signin', options, function (options) { return internals.request({ url: state.url + '/session', method: 'PUT', body: internals.serialise('session', options) }) - }) - .then(function (response) { - var data = internals.deserialise(response.body, { - include: 'account' - }) + .then(function (response) { + var data = internals.deserialise(response.body, { + include: 'account' + }) - // admins don’t have an account - if (!data.account) { - data.account = { - username: options.username + // admins don’t have an account + if (!data.account) { + data.account = { + username: options.username + } } - } - - // If the username hasn’t changed, emit 'reauthenticate' instead of 'signin' - var emitEvent = 'signin' - if (get(state, 'account.username') === options.username) { - emitEvent = 'reauthenticate' - } - state.account = { - username: data.account.username, - session: { - id: data.id + // If the username hasn’t changed, emit 'reauthenticate' instead of 'signin' + var emitEvent = 'signin' + if (get(state, 'account.username') === options.username) { + emitEvent = 'reauthenticate' } - } - if (data.account.id) { - state.account.id = data.account.id - } - - internals.saveAccount({ - cacheKey: state.cacheKey, - account: state.account - }) - - state.emitter.emit(emitEvent, clone(state.account)) - - return data.account - }) + state.account = { + username: data.account.username, + session: { + id: data.id + } + } - .then(function (account) { - var postHooks = [] + if (data.account.id) { + state.account.id = data.account.id + } - // note: the `pre:signin` & `post:signin` events are not considered public - // APIs and might change in future without notice - // https://github.com/hoodiehq/hoodie-account-client/issues/65 - state.emitter.emit('post:signin', { hooks: postHooks }) + internals.saveAccount({ + cacheKey: state.cacheKey, + account: state.account + }) - return Promise.all(invokeMap(postHooks, 'call')) + state.emitter.emit(emitEvent, clone(state.account)) - .then(function () { - return clone(account) + return data.account }) }) } diff --git a/lib/sign-out.js b/lib/sign-out.js index 116bdee..500785f 100644 --- a/lib/sign-out.js +++ b/lib/sign-out.js @@ -1,7 +1,6 @@ module.exports = signOut var clone = require('lodash/clone') -var invokeMap = require('lodash/invokeMap') var internals = module.exports.internals = {} internals.request = require('../utils/request') @@ -16,19 +15,7 @@ function signOut (state) { var accountProperties = internals.get(state) - var preHooks = [] - // note: the `pre:signout` & `post:signout` events are not considered public - // APIs and might change in future without notice - // https://github.com/hoodiehq/hoodie-account-client/issues/65 - state.emitter.emit('pre:signout', { hooks: preHooks }) - - return Promise.resolve() - - .then(function () { - return Promise.all(invokeMap(preHooks, 'call')) - }) - - .then(function () { + return state.hook('signout', function () { return internals.request({ method: 'DELETE', url: state.url + '/session', @@ -36,29 +23,17 @@ function signOut (state) { authorization: 'Session ' + state.account.session.id } }) - }) - - .then(function () { - internals.clearSession({ - cacheKey: state.cacheKey - }) - - var accountClone = clone(state.account) - - delete state.account - state.emitter.emit('signout', accountClone) - - var postHooks = [] + .then(function () { + internals.clearSession({ + cacheKey: state.cacheKey + }) - // note: the `pre:signout` & `post:signout` events are not considered public - // APIs and might change in future without notice - // https://github.com/hoodiehq/hoodie-account-client/issues/65 - state.emitter.emit('post:signout', { hooks: postHooks }) + var accountClone = clone(state.account) + delete state.account - return Promise.all(invokeMap(postHooks, 'call')) + state.emitter.emit('signout', accountClone) - .then(function () { return clone(accountProperties) }) }) diff --git a/package.json b/package.json index 144f870..8892a63 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "uglify-js": "^2.4.24" }, "dependencies": { + "before-after-hook": "^1.0.1", "humble-localstorage": "^1.4.2", "lie": "^3.0.1", "lodash": "^4.0.0", diff --git a/test/integration/hooks-test.js b/test/integration/hooks-test.js index 6732e60..b9509cf 100644 --- a/test/integration/hooks-test.js +++ b/test/integration/hooks-test.js @@ -16,7 +16,7 @@ var options = { test('sign in with pre & post hooks', function (t) { store.clear() - t.plan(2) + t.plan(5) nock(baseURL) .put('/session') @@ -28,21 +28,20 @@ test('sign in with pre & post hooks', function (t) { id: 'abc4567' }) - account.on('pre:signin', function (options) { - options.hooks.push(function () { - return new Promise(function (resolve, reject) { - setTimeout(function () { - callOrder.push(1) - resolve() - }, 100) - }) + account.hook.before('signin', function (signInOptions) { + t.deepEqual(signInOptions, options, 'before signin hook receives options') + return new Promise(function (resolve) { + setTimeout(function () { + callOrder.push(1) + resolve() + }, 100) }) }) - account.on('post:signin', function (options) { - options.hooks.push(function () { - callOrder.push(3) - }) + account.hook.after('signin', function (session, signInOptions) { + t.deepEqual(session, {id: 'abc4567', username: 'chicken@docs.com'}, 'after signin hook receives session') + t.deepEqual(signInOptions, options, 'after signin hook receives options') + callOrder.push(3) }) account.on('signin', function () { @@ -70,10 +69,8 @@ test('sign in with throw in pre hook', function (t) { id: 'abc4567' }) - account.on('pre:signin', function (options) { - options.hooks.push(function () { - throw new Error('signin aborted') - }) + account.hook.before('signin', function () { + throw new Error('signin aborted') }) account.on('signin', function () { @@ -102,10 +99,8 @@ test('sign in with throw in post hook', function (t) { id: 'abc4567' }) - account.on('post:signin', function (options) { - options.hooks.push(function () { - throw new Error('post:signin ooops') - }) + account.hook.after('signin', function (options) { + throw new Error('post:signin ooops') }) account.on('signin', function () { @@ -141,21 +136,17 @@ test('sign out with pre & post hooks', function (t) { id: 'abc4567' }) - account.on('pre:signout', function (options) { - options.hooks.push(function () { - return new Promise(function (resolve, reject) { - setTimeout(function () { - callOrder.push(1) - resolve() - }, 100) - }) + account.hook.before('signout', function () { + return new Promise(function (resolve, reject) { + setTimeout(function () { + callOrder.push(1) + resolve() + }, 100) }) }) - account.on('post:signout', function (options) { - options.hooks.push(function () { - callOrder.push(3) - }) + account.hook.after('signout', function () { + callOrder.push(3) }) account.on('signout', function () { @@ -189,10 +180,8 @@ test('sign out with throw in pre hook', function (t) { id: 'abc4567' }) - account.on('pre:signout', function (options) { - options.hooks.push(function () { - throw new Error('signout aborted') - }) + account.hook.before('signout', function () { + throw new Error('signout aborted') }) account.on('signout', function () { @@ -227,10 +216,8 @@ test('sign out with throw in post hook', function (t) { id: 'abc4567' }) - account.on('post:signout', function (options) { - options.hooks.push(function () { - throw new Error('post:signout ooops') - }) + account.hook.after('signout', function () { + throw new Error('post:signout ooops') }) account.on('signout', function () { diff --git a/test/unit/sign-in-test.js b/test/unit/sign-in-test.js index 1618383..adb123e 100644 --- a/test/unit/sign-in-test.js +++ b/test/unit/sign-in-test.js @@ -2,11 +2,16 @@ var simple = require('simple-mock') var test = require('tape') var signIn = require('../../lib/sign-in') +var hookMock = simple.stub().callFn(function (name, options, callback) { + return callback(options) +}) test('signIn without options', function (t) { t.plan(1) - signIn({}) + signIn({ + hook: hookMock + }) .then(t.fail.bind(t, 'must reject')) .catch(t.pass.bind(t, 'rejects with error')) }) @@ -14,7 +19,9 @@ test('signIn without options', function (t) { test('signIn without password', function (t) { t.plan(1) - signIn({}, { + signIn({ + hook: hookMock + }, { username: 'username' }) @@ -26,7 +33,9 @@ test('signIn without password', function (t) { test('signIn without username', function (t) { t.plan(1) - signIn({}, { + signIn({ + hook: hookMock + }, { password: 'password' }) @@ -39,6 +48,7 @@ test('successful account.signIn(options)', function (t) { t.plan(6) var state = { + hook: hookMock, url: 'http://example.com', cacheKey: 'cacheKey123', emitter: { @@ -97,7 +107,9 @@ test('signIn with request error', function (t) { simple.mock(signIn.internals, 'request').rejectWith(new Error('Ooops')) - signIn({}) + signIn({ + hook: hookMock + }) .then(t.fail.bind(t, 'must reject')) @@ -113,6 +125,7 @@ test('signIn with same username', function (t) { t.plan(2) var state = { + hook: hookMock, url: 'http://example.com', cacheKey: 'cacheKey123', emitter: { @@ -141,8 +154,8 @@ test('signIn with same username', function (t) { }) .then(function (accountProperties) { - t.is(state.emitter.emit.callCount, 3, '3 Events emitted') - t.is(state.emitter.emit.calls[1].arg, 'reauthenticate', 'Correct event emitted') + t.is(state.emitter.emit.callCount, 1, '1 Event emitted') + t.is(state.emitter.emit.calls[0].arg, 'reauthenticate', 'Correct event emitted') simple.restore() }) diff --git a/test/unit/sign-out-test.js b/test/unit/sign-out-test.js index 79e1dc7..c8919cd 100644 --- a/test/unit/sign-out-test.js +++ b/test/unit/sign-out-test.js @@ -2,6 +2,7 @@ var simple = require('simple-mock') var test = require('tape') var signOut = require('../../lib/sign-out') +var hookMock = simple.stub().callbackWith() test('signOut()', function (t) { t.plan(3) @@ -13,6 +14,7 @@ test('signOut()', function (t) { simple.mock(signOut.internals, 'clearSession').callFn(function () {}) var state = { + hook: hookMock, url: 'http://example.com', cacheKey: 'cacheKey123', account: { @@ -54,6 +56,7 @@ test('signOut() with request error', function (t) { simple.mock(signOut.internals, 'request').rejectWith(new Error('Ooops')) signOut({ + hook: hookMock, account: { session: {} }, @@ -73,6 +76,7 @@ test('signOut() without being signed in', function (t) { t.plan(2) signOut({ + hook: hookMock, account: {}, emitter: { emit: simple.stub() diff --git a/utils/get-state.js b/utils/get-state.js index 7db7693..37b92fb 100644 --- a/utils/get-state.js +++ b/utils/get-state.js @@ -1,5 +1,6 @@ module.exports = getState +var Hook = require('before-after-hook') var EventEmitter = require('events').EventEmitter var get = require('lodash/get') @@ -26,9 +27,10 @@ function getState (options) { }) var state = { + account: storedAccount, cacheKey: cacheKey, emitter: options.emitter || new EventEmitter(), - account: storedAccount, + hook: new Hook(), url: options.url, validate: options.validate || function () {} }