Created
December 22, 2016 14:28
-
-
Save pulpfree/5cfb8cd3c05756f149b82fc995584efe to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <!-- | |
| Created using JS Bin | |
| http://jsbin.com | |
| Copyright (c) 2016 by yelouafi (http://jsbin.com/sodowa/9/edit) | |
| Released under the MIT license: http://jsbin.mit-license.org | |
| --> | |
| <meta name="robots" content="noindex"> | |
| <html> | |
| <head> | |
| <meta name="description" content="redux-saga login example"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser-polyfill.min.js"></script> | |
| <script src="https://npmcdn.com/redux/dist/redux.min.js"></script> | |
| <script src="https://npmcdn.com/redux-saga/dist/redux-saga.js"></script> | |
| <meta charset="utf-8"> | |
| <title>react redux example</title> | |
| </head> | |
| <body> | |
| <div id="root"></div> | |
| <script id="jsbin-javascript"> | |
| // noprotect | |
| //jshint esnext:true, asi:true | |
| console.clear() | |
| const { | |
| createStore, | |
| combineReducers, | |
| applyMiddleware, | |
| bindActionCreators | |
| } = Redux | |
| const { | |
| default: createSagaMiddleware, | |
| effects: {take, put, call, fork, join, cancel, race} } = ReduxSaga | |
| /////////////////////////////////////////////////////////////////// | |
| // | |
| // Utils | |
| // | |
| const log = v => console.log(v) | |
| const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)) | |
| const action = type => (payload={}) => ({type, ...payload}) | |
| function createRequestTypes(base) { | |
| return { | |
| REQUEST : `${base}_REQUEST`, | |
| SUCCESS : `${base}_SUCCESS`, | |
| ERROR : `${base}_ERROR` | |
| } | |
| } | |
| function apify(fn) { | |
| return (...args) => | |
| new Promise((resolve, reject) => { | |
| setTimeout(() => { | |
| try { | |
| resolve(fn(...args)) | |
| } catch(e) { | |
| reject(e) | |
| } | |
| }, 1000) | |
| }) | |
| } | |
| /////////////////////////////////////////////////////////////// | |
| // | |
| // API | |
| // | |
| const users = [ | |
| {name: 'admin', password: 'admin'}, | |
| {name: 'guest', password: 'guest'} | |
| ] | |
| let tokenId = 0 | |
| function authFn({token, name, password}) { | |
| if(token) { | |
| return refreshTokenFn(token) | |
| } | |
| else { | |
| const valid = users.some( | |
| u => u.name === name && u.password === password | |
| ) | |
| if(valid) | |
| return {expires_in: 2000, id: ++tokenId, $$token: true} | |
| else | |
| throw 'Invalid credentials' | |
| } | |
| } | |
| function refreshTokenFn(token) { | |
| if(!token.$$token) | |
| throw 'Invalid token' | |
| return {...token, id: ++tokenId} | |
| } | |
| const api = { | |
| authorize: apify(authFn), | |
| refreshToken: apify(refreshTokenFn) | |
| } | |
| /////////////////////////////////////////////////////////////// | |
| // | |
| // Actions | |
| // | |
| const LOGIN = createRequestTypes('LOGIN') | |
| const login = { | |
| request : (name, password) => action(LOGIN.REQUEST)({name, password}), | |
| success : (token) => action(LOGIN.SUCCESS)({token}), | |
| error : (error) => action(LOGIN.ERROR)({error}) | |
| } | |
| const LOGOUT = 'LOGOUT' | |
| const logout = action(LOGOUT) | |
| /////////////////////////////////////////////////////////////// | |
| // | |
| // Reducers | |
| // | |
| const PENDING = 'PENDING' | |
| const IN = 'IN' | |
| const OUT = 'OUT' | |
| function user(state = null, action) { | |
| switch(action.type) { | |
| case LOGIN.REQUEST: | |
| return { | |
| name: action.name, | |
| password: action.password, | |
| status: PENDING | |
| } | |
| case LOGIN.SUCCESS: | |
| return { | |
| ...state, | |
| status: IN, | |
| token: action.token | |
| } | |
| case LOGIN.ERROR: | |
| return { | |
| ...state, | |
| status: OUT, | |
| token: null, | |
| error: action.error | |
| } | |
| case LOGOUT: | |
| return null | |
| default: | |
| return state | |
| } | |
| } | |
| const rootReducer = combineReducers({ | |
| user | |
| }) | |
| /////////////////////////////////////////////////////////////// | |
| // | |
| // Sagas | |
| // | |
| function* authorize(credentials) { | |
| const token = yield call(api.authorize, credentials) | |
| yield put( login.success(token) ) | |
| return token | |
| } | |
| function* authAndRefreshTokenOnExpiry(name, password) { | |
| let token = yield call(authorize, {name, password}) | |
| while(true) { | |
| yield call(delay, token.expires_in) | |
| token = yield call(authorize, {token}) | |
| } | |
| } | |
| function* watchAuth() { | |
| while(true) { | |
| const {name, password} = yield take(LOGIN.REQUEST) | |
| yield race([ | |
| take(LOGOUT), | |
| call(authAndRefreshTokenOnExpiry, name, password) | |
| ]) | |
| } | |
| } | |
| /////////////////////////////////////////////////////////////// | |
| // | |
| // Create the store. Log states into the console | |
| // | |
| const sagaMiddleware = createSagaMiddleware() | |
| const store = createStore( | |
| rootReducer, | |
| applyMiddleware(sagaMiddleware) | |
| ) | |
| sagaMiddleware.run(watchAuth) | |
| let lastState | |
| store.subscribe(() => { | |
| const state = store.getState() | |
| if(state !== lastState) { | |
| lastState = state | |
| console.log('user:', lastState.user) | |
| } | |
| }) | |
| /* test */ | |
| store.dispatch(login.request('admin', 'admin')) | |
| setTimeout(() => { | |
| store.dispatch(logout()) | |
| }, 14333) | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment