Skip to content

Instantly share code, notes, and snippets.

@insin
Last active April 9, 2018 14:09
Show Gist options
  • Save insin/4b040ed7af5e863a015f to your computer and use it in GitHub Desktop.
Save insin/4b040ed7af5e863a015f to your computer and use it in GitHub Desktop.
Redux auth duck (where duck = https://github.com/erikras/ducks-modular-redux)
var {handleResponse} = require('.utils')
var {setErrorMessages} = require('./messages')
var CHECK_TOKEN = 'auth/CHECK_TOKEN'
var CHECK_TOKEN_FAILURE = 'auth/CHECK_TOKEN_FAILURE'
var CHECK_TOKEN_SUCCESS = 'auth/CHECK_TOKEN_SUCCESS'
var LOGIN = 'auth/LOGIN'
var LOGIN_FAILURE = 'auth/LOGIN_FAILURE'
var LOGIN_SUCCESS = 'auth/LOGIN_SUCCESS'
var SHOWED_WELCOME = 'auth/SHOWED_WELCOME'
var LOGOUT = 'auth/LOGOUT'
var json = window.localStorage.auth
var initialState = json ? JSON.parse(json) : {token: null, user: null}
function auth(state={ // eslint-disable-line space-infix-ops
...initialState,
checkingToken: false,
loggingIn: false,
showWelcome: false
}, action) {
switch (action.type) {
case CHECK_TOKEN:
return {...state, checkingToken: true}
case CHECK_TOKEN_FAILURE:
case CHECK_TOKEN_SUCCESS:
return {...state, checkingToken: false}
case LOGIN:
return {...state, loggingIn: true}
case LOGIN_FAILURE:
return {...state, loggingIn: false}
case LOGIN_SUCCESS:
window.localStorage.auth = JSON.stringify(action.payload)
return {...state, loggingIn: false, showWelcome: true, ...action.payload}
case SHOWED_WELCOME:
return {...state, showWelcome: false}
case LOGOUT:
delete window.localStorage.auth
return {...state, token: null, user: null}
}
return state
}
auth.checkToken = () => (dispatch, getState) => {
dispatch({type: CHECK_TOKEN})
window.fetch('/api/check-token', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({token: getState().auth.token})
})
.then(handleResponse)
.then(({valid}) => {
if (!valid) {
dispatch(auth.logout())
dispatch(setErrorMessages(['Login expired - please log in again.']))
}
dispatch({type: CHECK_TOKEN_SUCCESS})
})
.catch(error => {
dispatch(setErrorMessages([`Error checking authentication token: ${error.message}`]))
dispatch({type: CHECK_TOKEN_FAILURE})
})
}
auth.login = (username, password) => dispatch => {
dispatch({type: LOGIN})
window.fetch('/api/authenticate', {
headers: {
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify({username, password})
})
.then(handleResponse)
.then(payload => dispatch({type: LOGIN_SUCCESS, payload}))
.catch(error => {
dispatch(setErrorMessages([`Error logging in: ${error.message}`]))
dispatch({type: LOGIN_FAILURE})
})
}
auth.logout = () => ({type: LOGOUT})
auth.showedWelcome = () => ({type: SHOWED_WELCOME})
auth.LOGOUT = LOGOUT
module.exports = auth
var SET_ERROR_MESSAGES = 'messages/SET_ERROR_MESSAGES'
var FLASH_SUCCESS_MESSAGE = 'messages/FLASH_SUCCESS_MESSAGE'
var CLEAR_ERROR_MESSAGES = 'messages/CLEAR_ERROR_MESSAGES'
var CLEAR_SUCCESS_MESSAGE = 'messages/CLEAR_SUCCESS_MESSAGE'
var CLEAR_MESSAGES = 'messages/CLEAR_MESSAGES'
function messages(state={ // eslint-disable-line space-infix-ops
errorMessages: null,
successMessage: null,
successTimeout: null
}, action) {
switch (action.type) {
case SET_ERROR_MESSAGES:
return {...state, errorMessages: action.messages}
case FLASH_SUCCESS_MESSAGE:
return {...state, successMessage: action.message, successTimeout: action.timeout}
case CLEAR_ERROR_MESSAGES:
return {...state, errorMessages: null}
case CLEAR_MESSAGES:
return {...state, errorMessages: null, successMessage: null, successTimeout: null}
case CLEAR_SUCCESS_MESSAGE:
return {...state, successMessage: null, successTimeout: null}
}
return state
}
function maybeClearTimeout(timeout) {
if (timeout) {
window.clearTimeout(timeout)
}
}
messages.flashSuccessMessage = (message, dismissAfter=3000) => (dispatch, getState) => { // eslint-disable-line space-infix-ops
maybeClearTimeout(getState().messages.successTimeout)
var timeout = window.setTimeout(() => dispatch({type: CLEAR_SUCCESS_MESSAGE}), dismissAfter)
dispatch({type: FLASH_SUCCESS_MESSAGE, message, timeout})
}
messages.setErrorMessages = (messages) => ({type: SET_ERROR_MESSAGES, messages})
messages.clearErrorMessages = () => ({type: CLEAR_ERROR_MESSAGES})
messages.clearMessages = () => (dispatch, getState) => {
maybeClearTimeout(getState().messages.successTimeout)
dispatch({type: CLEAR_MESSAGES})
}
messages.clearSuccessMessage = () => (dispatch, getState) => {
maybeClearTimeout(getState().messages.successTimeout)
dispatch({type: CLEAR_SUCCESS_MESSAGE})
}
module.exports = messages
var JSON_CONTENT_TYPE_CHECK = /application\/json/
/**
* Throws an error if a window.fetch response has a non-2XX status code, using a
* 'message' property from response JSON if present, otherwise using the
* response status and text.
* For successful responses, returns a JSON promise or the response itself based
* on the content-type header.
*/
function handleResponse(response) {
var isJSON = JSON_CONTENT_TYPE_CHECK.test(response.headers.get('content-type'))
if (response.ok) {
return isJSON ? response.json() : response
}
if (isJSON) {
return response.json().then(json => {
var error = new Error(`${response.statusText}${json.message && `: ${json.message}`}`)
error.response = response
throw error
})
}
else {
return response.text().then(text => {
var error = new Error(`${response.statusText}${text && `: ${text}`}`)
error.response = response
throw error
})
}
}
module.exports = {handleResponse}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment