diff --git a/src/actions.js b/src/actions.js index 216dfba..9e7f318 100644 --- a/src/actions.js +++ b/src/actions.js @@ -3,7 +3,7 @@ import { createAction } from 'redux-actions' const ACTION_NAMESPACE = '@@redux-sessions/' export const setToken = createAction(ACTION_NAMESPACE + 'SET_TOKEN', - (token, { userKey, persist }={}) => ({ token, userKey, persist }) + (token, { userType, persist }={}) => ({ token, userType, persist }) ) export const clearToken = createAction(ACTION_NAMESPACE + 'CLEAR_TOKEN') diff --git a/src/enhancer.js b/src/enhancer.js index 0597627..ba4d836 100644 --- a/src/enhancer.js +++ b/src/enhancer.js @@ -1,8 +1,10 @@ import { saveSessionState } from './persistenceHelpers' import { debounce } from 'lodash' +const DEFAULT_DEBOUNCE_INTERVAL = 500 + // Adds storage functionality to sessions info -function enhancer ({ persist=true, debounceInterval=500 }={}) { +function enhancer ({ persist=true, debounceInterval=DEFAULT_DEBOUNCE_INTERVAL }={}) { return function enhance (createStore) { return function newCreateStore (...args) { const store = createStore(...args) diff --git a/src/persistenceHelpers.js b/src/persistenceHelpers.js index 7563425..f07ae83 100644 --- a/src/persistenceHelpers.js +++ b/src/persistenceHelpers.js @@ -1,40 +1,59 @@ -import { map, last, uniq, set } from 'lodash' +import { map, uniq, set } from 'lodash' import { storage } from './utils' -import { get } from 'lodash/fp' -// Storage keys +/****************************************************** + Helpers for saving/loading session info from storage +*******************************************************/ +// This string prefixes the key of everything we store const STORAGE_PREFIX = 'redux-sessions' -function tokenStorageKey (userKey) { - return STORAGE_PREFIX + ':token:' + userKey +// Creates a storage key for a given user type and the data type being stored. +// E.g. ('user', 'token') -> 'redux-sessions:user:token' +function serializeStorageKey (userType, dataType) { + return [ STORAGE_PREFIX, dataType, userType ].join(':') } -function persistStorageKey (userKey) { - return STORAGE_PREFIX + ':persist:' + userKey +// Splits a storage key into its parts so we can retrieve info about what's being stored. +function deserializeStorageKey (storageKey) { + // eslint-disable-next-line + const [ _, dataType, userType ] = storageKey.split(':') + return { dataType, userType } } -function getUserKeyFromStorageKey (storageKey) { - return last(storageKey.split(':')) +// Given a storage key, returns the user type. +function getUserTypeFromStorageKey (storageKey) { + const { userType } = deserializeStorageKey(storageKey) + return userType } -// Storage helpers +// Returns the storage key for storing a user token. +function tokenStorageKey (userType) { + return serializeStorageKey(userType, 'token') +} + +// Returns the storage key for storing a user's persistence. +function persistStorageKey (userType) { + return serializeStorageKey(userType, 'persist') +} +// Loads the redux state from local / session storage. +// We use the storage prefix to figure out which values we've saved. export function loadSessionState () { - const userKeys = uniq(storage.getAllKeys() - .filter(key => key.startsWith(STORAGE_PREFIX)) - .map(getUserKeyFromStorageKey)) + const storageKeys = storage.getAllKeys().filter(key => key.startsWith(STORAGE_PREFIX)) + const userTypes = uniq(storageKeys.map(getUserTypeFromStorageKey)) const state = {} - userKeys.forEach(userKey => set(state, userKey, { - token: storage.getItem(tokenStorageKey(userKey)), - persist: !!storage.getItem(persistStorageKey(userKey)), + userTypes.forEach(userType => set(state, userType, { + token: storage.getItem(tokenStorageKey(userType)), + persist: !!storage.getItem(persistStorageKey(userType)), })) return state } +// Saves the redux state to local / session storage. export function saveSessionState (state) { - return map(state, ({ token, persist }, userKey) => { - storage.setItem(tokenStorageKey(userKey), token, { persist }) - storage.setItem(persistStorageKey(userKey), persist, { persist }) + return map(state, ({ token, persist }, userType) => { + storage.setItem(tokenStorageKey(userType), token, { persist }) + storage.setItem(persistStorageKey(userType), persist, { persist }) }) } diff --git a/src/reducer.js b/src/reducer.js index 432db29..8d4299b 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -3,18 +3,18 @@ import { set, get } from 'lodash/fp' import * as actions from './actions' import { loadSessionState } from './persistenceHelpers' -const DEFAULT_USER_KEY = 'user' +const DEFAULT_USER_TYPE = 'user' // Reducer const initialState = loadSessionState() const reducer = handleActions({ - [actions.setToken]: (state, { payload: { token, userKey=DEFAULT_USER_KEY, persist=true } }) => { - return set(userKey, { token, persist }, state) + [actions.setToken]: (state, { payload: { token, userType=DEFAULT_USER_TYPE, persist=true } }) => { + return set(userType, { token, persist }, state) }, - [actions.clearToken]: (state, { payload: { userKey=DEFAULT_USER_KEY }={}}) => { - return set(userKey, { token: null, persist: false }, state) + [actions.clearToken]: (state, { payload: { userType=DEFAULT_USER_TYPE }={}}) => { + return set(userType, { token: null, persist: false }, state) }, }, initialState) @@ -22,9 +22,9 @@ const reducer = handleActions({ const selectors = {} -selectors.token = function (state, { userKey=DEFAULT_USER_KEY }={}) { +selectors.token = function (state, { userType=DEFAULT_USER_TYPE }={}) { if (!get('sessions', state)) throw new Error('redux-sessions: state not found. Did you remember to attach the reducer at key `sessions`?') - return get('sessions.' + userKey + '.token', state) + return get('sessions.' + userType + '.token', state) } selectors.isAuthenticated = function (state, options) { diff --git a/test/persistenceHelpers.test.js b/test/persistenceHelpers.test.js index ca04187..688e790 100644 --- a/test/persistenceHelpers.test.js +++ b/test/persistenceHelpers.test.js @@ -14,7 +14,7 @@ describe('loadSessionState()', () => { const clientPersist = false storage.setItem('redux-sessions:token:advisor', advisorToken) storage.setItem('redux-sessions:persist:advisor', advisorPersist) - storage.setItem('something-else', 'baz') + storage.setItem('something-else', 'baz') // should be ignored storage.setItem('redux-sessions:token:client', clientToken, { persist: false }) storage.setItem('redux-sessions:persist:client', clientPersist, { persist: false }) const state = loadSessionState() diff --git a/test/reducer.test.js b/test/reducer.test.js index e1cfd81..6a96583 100644 --- a/test/reducer.test.js +++ b/test/reducer.test.js @@ -10,11 +10,11 @@ describe('actions.setToken()', () => { const newState = reducer(initialState, action) expect(newState.user.token).toEqual(token) }) - it('can receive a custom user key', () => { + it('can receive a custom user type', () => { const token = 'foo' - const userKey = 'bar' + const userType = 'bar' const initialState = {} - const action = actions.setToken(token, { userKey }) + const action = actions.setToken(token, { userType }) const newState = reducer(initialState, action) expect(newState.bar.token).toEqual(token) }) @@ -47,10 +47,10 @@ describe('actions.clearToken()', () => { const newState = reducer(initialState, action) expect(newState.user.persist).toEqual(false) }) - it('can receive a custom user key', () => { - const userKey = 'bar' + it('can receive a custom user type', () => { + const userType = 'bar' const initialState = { user: { token: 'foo' } } - const action = actions.clearToken({ userKey }) + const action = actions.clearToken({ userType }) const newState = reducer(initialState, action) expect(newState.user.token).toEqual('foo') expect(newState.bar.token).toEqual(null) @@ -69,11 +69,11 @@ describe('selectors.token()', () => { const state = { sessions: { user: { token }}} expect(selectors.token(state)).toEqual(token) }) - it('can receive a custom user key', () => { + it('can receive a custom user type', () => { const token = 'foo' - const userKey = 'bar' + const userType = 'bar' const state = { sessions: { bar: { token }}} - expect(selectors.token(state, { userKey })).toEqual(token) + expect(selectors.token(state, { userType })).toEqual(token) }) }) @@ -91,11 +91,11 @@ describe('selectors.isAuthenticated()', () => { const state = { sessions: {}} expect(selectors.isAuthenticated(state)).toEqual(false) }) - it('can receive a custom user key', () => { + it('can receive a custom user type', () => { const token = 'foo' - const userKey = 'bar' + const userType = 'bar' const state = { sessions: { bar: { token }}} - expect(selectors.isAuthenticated(state, { userKey })).toEqual(true) + expect(selectors.isAuthenticated(state, { userType })).toEqual(true) }) }) @@ -113,11 +113,11 @@ describe('selectors.isUnauthenticated()', () => { const state = { sessions: {}} expect(selectors.isUnauthenticated(state)).toEqual(true) }) - it('can receive a custom user key', () => { + it('can receive a custom user type', () => { const token = 'foo' - const userKey = 'bar' + const userType = 'bar' const state = { sessions: { bar: { token }}} - expect(selectors.isUnauthenticated(state, { userKey })).toEqual(false) + expect(selectors.isUnauthenticated(state, { userType })).toEqual(false) }) }) diff --git a/test/setup.js b/test/setup.js index 71ad4b1..0a934d5 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,7 +1,7 @@ import Storage from 'dom-storage' // This file will be run before each individual test file. -// Here we use it to set up some storage mocks. +// Here we're using it to set up some storage mocks. global.localStorage = new Storage(null, { strict: true }) global.sessionStorage = new Storage(null, { strict: true })