Skip to content

Commit 155a8d5

Browse files
authored
Merge pull request #14 from jerelmiller/initial-state
Initial state
2 parents 821ba53 + a1081a0 commit 155a8d5

File tree

14 files changed

+270
-9
lines changed

14 files changed

+270
-9
lines changed

README.md

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ yarn add redux-simple-auth
2525

2626
### Setup
2727

28-
This library ships with middleware and a reducer. You will need to do the
29-
following:
28+
This library ships with middleware, a reducer, and an optional store enhancer.
29+
You will need to do the following:
3030

3131
##### Apply the middleware
3232

@@ -60,6 +60,32 @@ export default combineReducers({
6060
})
6161
```
6262

63+
##### Optionally add the store enhancer
64+
65+
In order to use the enhancer, you will need to provide it with the storage used.
66+
If you do not need a custom storage adapter, you may import the default storage.
67+
68+
```javascript
69+
// ...
70+
import {
71+
createAuthMiddleware,
72+
getInitialAuthState,
73+
storage // or custom storage creator
74+
} from 'redux-simple-auth'
75+
import { createStore, compose, applyMiddleware } from 'redux'
76+
77+
const authMiddleware = createAuthMiddleware(/*...*/)
78+
79+
const store = createStore(
80+
rootReducer,
81+
/* initialState, */
82+
compose(
83+
applyMiddleware(authMiddleware),
84+
getInitialAuthState({ storage })
85+
)
86+
)
87+
```
88+
6389
## How does it work?
6490

6591
Redux Simple Auth aims to make authentication and authorization within your
@@ -429,6 +455,21 @@ const authMiddleware = createAuthMiddleware({
429455
needed for authorization. It accepts a header name for its first argument and
430456
that header's value as its second argument.
431457

458+
### Store Enhancer
459+
460+
There may be cases where you may want the redux store initialized with the
461+
sesion data from the storage device. The store enhancer does just that. On store
462+
initialization, it will ask the storage device for the session data.
463+
464+
```javascript
465+
const enhancer = getInitialAuthState({ storage })
466+
```
467+
468+
**Options:**
469+
470+
* `storage` (_object_): The storage mechanism used to store the session. **This
471+
must** be the same storage device configured with the middleware.
472+
432473
## Actions
433474

434475
Redux Simple Auth ships with several actions to aide in authentication for your
@@ -498,6 +539,49 @@ import { invalidateSession } from 'redux-simple-auth'
498539
store.dispatch(invalidateSession())
499540
```
500541

542+
## Selectors
543+
544+
To aid in selecting specific session state, redux simple auth ships with a few
545+
selectors for your convenience. All selectors take the store `state` as an
546+
argument. Note this is the entire store state, not just the session state.
547+
548+
### `getSessionData(state)`
549+
550+
(_object_) Returns the session data set when user was authenticated. If not yet
551+
authenticated, this returns an empty object.
552+
553+
```javascript
554+
import { getSessionData } from 'redux-simple-auth'
555+
556+
const mapStateToProps = state => ({
557+
session: getSessionData(state)
558+
})
559+
```
560+
561+
### `getIsAuthenticated(state)`
562+
563+
(_boolean_) Returns whether the user is authenticated.
564+
565+
```javascript
566+
import { getIsAuthenticated } from 'redux-simple-auth'
567+
568+
const mapStateToProps = state => ({
569+
isAuthenticated: getIsAuthenticated(state)
570+
})
571+
```
572+
573+
### `getAuthenticator(state)`
574+
575+
(_string_) Returns the `authenticator` used when authenticating. If not yet
576+
authenticated, this is set to `null`.
577+
578+
```javascript
579+
import { getAuthenticator } from 'redux-simple-auth'
580+
581+
const mapStateToProps = state => ({
582+
authenticator: getAuthenticator(state)
583+
})
584+
```
501585

502586
## License
503587

src/actionTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const AUTHENTICATE = `${prefix}/AUTHENTICATE`
44
export const AUTHENTICATE_FAILED = `${prefix}/AUTHENTICATE_FAILED`
55
export const AUTHENTICATE_SUCCEEDED = `${prefix}/AUTHENTICATE_SUCCEEDED`
66
export const FETCH = `${prefix}/FETCH`
7+
export const INITIALIZE = `${prefix}/INITIALIZE`
78
export const INVALIDATE_SESSION = `${prefix}/INVALIDATE_SESSION`
89
export const RESTORE = `${prefix}/RESTORE`
910
export const RESTORE_FAILED = `${prefix}/RESTORE_FAILED`

src/actions.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
AUTHENTICATE_FAILED,
44
AUTHENTICATE_SUCCEEDED,
55
FETCH,
6+
INITIALIZE,
67
INVALIDATE_SESSION,
78
RESTORE,
89
RESTORE_FAILED
@@ -41,3 +42,8 @@ export const restore = payload => ({
4142
export const restoreFailed = () => ({
4243
type: RESTORE_FAILED
4344
})
45+
46+
export const initialize = payload => ({
47+
type: INITIALIZE,
48+
payload
49+
})

src/enhancer.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import isPlainObject from 'lodash.isplainobject'
2+
import { initialize } from './actions'
3+
import reducer from './reducer'
4+
5+
const validateStorage = storage => {
6+
if (!isPlainObject(storage) || storage.restore == null) {
7+
throw new Error(
8+
'Expected `storage` to be a valid storage. You either forgot to ' +
9+
'include it or you passed an invalid storage object'
10+
)
11+
}
12+
}
13+
14+
const enhancer = ({ storage } = {}) => {
15+
validateStorage(storage)
16+
17+
return createStore => (rootReducer, preloadedState, enhancer) => {
18+
const initialState = {
19+
session: reducer(null, initialize(storage.restore())),
20+
...preloadedState
21+
}
22+
23+
return createStore(rootReducer, initialState, enhancer)
24+
}
25+
}
26+
27+
export default enhancer

src/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@ export { authenticate, fetch, invalidateSession } from './actions'
55
export { default as createAdaptiveStore } from './storage/adaptive'
66
export { default as createLocalStorageStore } from './storage/localStorage'
77
export { default as createCookieStore } from './storage/cookie'
8+
export { default as getInitialAuthState } from './enhancer'
9+
export { default as storage } from './storage/default'
10+
export {
11+
getSessionData,
12+
getIsAuthenticated,
13+
getAuthenticator
14+
} from './selectors'

src/middleware.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import createAdaptiveStore from './storage/adaptive'
1+
import defaultStorage from './storage/default'
22
import isPlainObject from 'lodash.isplainobject'
33
import { AUTHENTICATE, FETCH } from './actionTypes'
44
import {
@@ -52,7 +52,7 @@ export default (config = {}) => {
5252
authenticator,
5353
authenticators,
5454
authorize,
55-
storage = createAdaptiveStore()
55+
storage = defaultStorage
5656
} = config
5757

5858
const findAuthenticator = authenticator

src/reducer.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
AUTHENTICATE_FAILED,
33
AUTHENTICATE_SUCCEEDED,
4+
INITIALIZE,
45
INVALIDATE_SESSION,
56
RESTORE,
67
RESTORE_FAILED
@@ -14,6 +15,14 @@ const initialState = {
1415

1516
const reducer = (state = initialState, action) => {
1617
switch (action.type) {
18+
case INITIALIZE:
19+
const { authenticated: { authenticator, ...data } = {} } = action.payload
20+
21+
return {
22+
authenticator,
23+
data,
24+
isAuthenticated: false
25+
}
1726
case AUTHENTICATE_SUCCEEDED:
1827
return {
1928
...state,
@@ -46,3 +55,7 @@ const reducer = (state = initialState, action) => {
4655
}
4756

4857
export default reducer
58+
59+
export const getData = state => state.data
60+
export const getIsAuthenticated = state => state.isAuthenticated
61+
export const getAuthenticator = state => state.authenticator

src/selectors.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as fromSession from './reducer'
2+
3+
export const getSessionData = state => fromSession.getData(state.session)
4+
5+
export const getIsAuthenticated = state =>
6+
fromSession.getIsAuthenticated(state.session)
7+
8+
export const getAuthenticator = state =>
9+
fromSession.getAuthenticator(state.session)

src/storage/default.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import createAdaptiveStore from './adaptive'
2+
3+
export default createAdaptiveStore()

test/enhancer.spec.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import enhancer from '../src/enhancer'
2+
import createMockStorage from './utils/testStorage'
3+
4+
describe('enhancer', () => {
5+
it('returns enhanced store with initial storage state', () => {
6+
const enhancedCreateStore = enhancer({ storage: createMockStorage() })
7+
8+
expect(enhancedCreateStore).toBeInstanceOf(Function)
9+
})
10+
11+
it('sets up store with initial storage state', () => {
12+
const mock = jest.fn()
13+
const dummyData = {
14+
authenticated: {
15+
authenticator: 'dummy',
16+
token: 'abcde'
17+
}
18+
}
19+
const storage = createMockStorage(dummyData)
20+
const createStore = jest.fn()
21+
22+
const enhancedCreateStore = enhancer({ storage })(createStore)(
23+
mock,
24+
null,
25+
mock
26+
)
27+
28+
expect(createStore).toHaveBeenCalledWith(
29+
mock,
30+
{
31+
session: {
32+
authenticator: 'dummy',
33+
data: { token: 'abcde' },
34+
isAuthenticated: false
35+
}
36+
},
37+
mock
38+
)
39+
})
40+
41+
it('throws when no storage given', () => {
42+
expect(() => enhancer({ storage: null })).toThrow(
43+
'Expected `storage` to be a valid storage. You either forgot to ' +
44+
'include it or you passed an invalid storage object'
45+
)
46+
})
47+
})

0 commit comments

Comments
 (0)