redux-devshare
Version:
[![NPM version][npm-image]][npm-url] [![NPM downloads][npm-downloads-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Code Climate][climate-image]][climate-url] [![Code Coverage][coverage-i
404 lines (370 loc) • 12.9 kB
JavaScript
import { omit, isArray, isString, isFunction, forEach, set, get } from 'lodash'
import jwtDecode from 'jwt-decode'
import { actionTypes, defaultJWTProps } from '../constants'
import { promisesForPopulate, getPopulateObjs } from '../utils/populate'
import { getLoginMethodAndParams } from '../utils/auth'
const {
SET,
SET_PROFILE,
LOGIN,
LOGOUT,
LOGIN_ERROR,
UNAUTHORIZED_ERROR,
AUTHENTICATION_INIT_STARTED,
AUTHENTICATION_INIT_FINISHED
} = actionTypes
/**
* @description Dispatch login error action
* @param {Function} dispatch - Action dispatch function
* @param {Object} authError - Error object
* @private
*/
export const dispatchLoginError = (dispatch, authError) =>
dispatch({
type: LOGIN_ERROR,
authError
})
/**
* @description Dispatch login error action
* @param {Function} dispatch - Action dispatch function
* @param {Object} authError - Error object
* @private
*/
export const dispatchUnauthorizedError = (dispatch, authError) =>
dispatch({
type: UNAUTHORIZED_ERROR,
authError
})
/**
* @description Dispatch login action
* @param {Function} dispatch - Action dispatch function
* @param {Object} auth - Auth data object
* @private
*/
export const dispatchLogin = (dispatch, auth) =>
dispatch({
type: LOGIN,
auth,
authError: null
})
/**
* @description Remove listener from user profile
* @param {Object} firebase - Internal firebase object
* @private
*/
export const unWatchUserProfile = (firebase) => {
const authUid = firebase._.authUid
const userProfile = firebase._.config.userProfile
if (firebase._.profileWatch) {
firebase.database()
.ref()
.child(`${userProfile}/${authUid}`)
.off('value', firebase._.profileWatch)
firebase._.profileWatch = null
}
}
/**
* @description Watch user profile
* @param {Function} dispatch - Action dispatch function
* @param {Object} firebase - Internal firebase object
* @private
*/
export const watchUserProfile = (dispatch, firebase) => {
const authUid = firebase._.authUid
const userProfile = firebase._.config.userProfile
unWatchUserProfile(firebase)
if (firebase._.config.userProfile) {
firebase._.profileWatch = firebase.database()
.ref()
.child(`${userProfile}/${authUid}`)
.on('value', snap => {
const {
profileParamsToPopulate,
autoPopulateProfile,
setProfilePopulateResults
} = firebase._.config
if (!profileParamsToPopulate || (!isArray(profileParamsToPopulate) && !isString(profileParamsToPopulate))) {
dispatch({
type: SET_PROFILE,
profile: snap.val()
})
} else {
// Convert each populate string in array into an array of once query promises
promisesForPopulate(firebase, snap.val(), profileParamsToPopulate)
.then(data => {
// Dispatch action with profile combined with populated parameters
// Auto Populate profile
if (autoPopulateProfile) {
const populates = getPopulateObjs(profileParamsToPopulate)
const profile = snap.val()
forEach(populates, (p) => {
set(profile, p.child, get(data, `${p.root}.${snap.val()[p.child]}`))
})
dispatch({
type: SET_PROFILE,
profile
})
} else {
// dispatch with unpopulated profile data
dispatch({
type: SET_PROFILE,
profile: snap.val()
})
}
// Fire actions for placement of data gathered in populate into redux
if (setProfilePopulateResults) {
forEach(data, (result, path) => {
dispatch({
type: SET,
path,
data: result,
timestamp: Date.now(),
requesting: false,
requested: true
})
})
}
})
}
})
}
}
/**
* @description Create user profile if it does not already exist. `updateProifleOnLogin: false`
* can be passed to config to dsiable updating. Profile factory is applied if it exists and is a function.
* @param {Function} dispatch - Action dispatch function
* @param {Object} firebase - Internal firebase object
* @param {Object} userData - User data object (response from authenticating)
* @param {Object} profile - Profile data to place in new profile
* @return {Promise}
* @private
*/
export const createUserProfile = (dispatch, firebase, userData, profile) => {
if (!firebase._.config.userProfile) {
return Promise.resolve(userData)
}
const { database, _: { config } } = firebase
if (isFunction(config.profileFactory)) {
profile = config.profileFactory(userData, profile)
}
if (isFunction(config.profileDecorator)) {
if (isFunction(console.warn)) { // eslint-disable-line no-console
console.warn('profileDecorator is Depreceated and will be removed in future versions. Please use profileFactory.') // eslint-disable-line no-console
}
profile = config.profileDecorator(userData, profile)
}
// Check for user's profile at userProfile path if provided
return database()
.ref()
.child(`${config.userProfile}/${userData.uid}`)
.once('value')
.then(profileSnap =>
// update profile only if doesn't exist or if set by config
!config.updateProfileOnLogin && profileSnap.val() !== null
? profileSnap.val()
: profileSnap.ref.update(profile) // Update the profile
.then(() => profile)
.catch(err => {
// Error setting profile
dispatchUnauthorizedError(dispatch, err)
return Promise.reject(err)
})
)
.catch(err => {
// Error reading user profile
dispatchUnauthorizedError(dispatch, err)
return Promise.reject(err)
})
}
/**
* @description Initialize authentication state change listener that
* watches user profile and dispatches login action
* @param {Function} dispatch - Action dispatch function
* @private
*/
export const init = (dispatch, firebase) => {
dispatch({ type: AUTHENTICATION_INIT_STARTED })
firebase.auth().onAuthStateChanged(authData => {
if (!authData) {
return dispatch({ type: LOGOUT })
}
firebase._.authUid = authData.uid
watchUserProfile(dispatch, firebase)
dispatchLogin(dispatch, authData)
// Run onAuthStateChanged if it exists in config
if (firebase._.config.onAuthStateChanged) {
firebase._.config.onAuthStateChanged(authData, firebase)
}
})
if (firebase._.config.enableRedirectHandling) {
firebase.auth().getRedirectResult()
.then((authData) => {
if (authData && authData.user) {
const { user } = authData
firebase._.authUid = user.uid
watchUserProfile(dispatch, firebase)
dispatchLogin(dispatch, user)
createUserProfile(
dispatch,
firebase,
user,
{
email: user.email,
displayName: user.providerData[0].displayName || user.email,
avatarUrl: user.providerData[0].photoURL,
providerData: user.providerData
}
)
}
}).catch((error) => {
dispatchLoginError(dispatch, error)
return Promise.reject(error)
})
}
firebase.auth().currentUser
dispatch({ type: AUTHENTICATION_INIT_FINISHED })
}
/**
* @description Login with errors dispatched
* @param {Function} dispatch - Action dispatch function
* @param {Object} firebase - Internal firebase object
* @param {Object} credentials - Login credentials
* @param {Object} credentials.email - Email to login with (only needed for email login)
* @param {Object} credentials.password - Password to login with (only needed for email login)
* @param {Object} credentials.provider - Provider name such as google, twitter (only needed for 3rd party provider login)
* @param {Object} credentials.type - Popup or redirect (only needed for 3rd party provider login)
* @param {Object} credentials.token - Custom or provider token
* @return {Promise}
* @private
*/
export const login = (dispatch, firebase, credentials) => {
dispatchLoginError(dispatch, null)
let { method, params } = getLoginMethodAndParams(firebase, credentials)
return firebase.auth()[method](...params)
.then((userData) => {
// Handle null response from getRedirectResult before redirect has happened
if (!userData) return Promise.resolve(null)
// For email auth return uid (createUser is used for creating a profile)
if (userData.email) return userData.uid
// For token auth, the user key doesn't exist. Instead, return the JWT.
if (method === 'signInWithCustomToken') {
// Extract the extra data in the JWT token for user object
const { stsTokenManager: { accessToken }, uid } = userData.toJSON()
const extraJWTData = omit(jwtDecode(accessToken), defaultJWTProps)
return createUserProfile(
dispatch,
firebase,
{ uid },
{ ...extraJWTData, uid }
)
}
// Create profile when logging in with external provider
const { user } = userData
return createUserProfile(
dispatch,
firebase,
user,
{
email: user.email,
displayName: user.providerData[0].displayName || user.email,
avatarUrl: user.providerData[0].photoURL,
providerData: user.providerData
}
)
})
.catch(err => {
dispatchLoginError(dispatch, err)
return Promise.reject(err)
})
}
/**
* @description Logout of firebase and dispatch logout event
* @param {Function} dispatch - Action dispatch function
* @param {Object} firebase - Internal firebase object
* @private
*/
export const logout = (dispatch, firebase) => {
firebase.auth().signOut()
dispatch({ type: LOGOUT })
firebase._.authUid = null
unWatchUserProfile(firebase)
return Promise.resolve(firebase)
}
/**
* @description Create a new user in auth and add an account to userProfile root
* @param {Function} dispatch - Action dispatch function
* @param {Object} firebase - Internal firebase object
* @param {Object} credentials - Login credentials
* @return {Promise}
* @private
*/
export const createUser = (dispatch, firebase, { email, password, signIn }, profile) => {
dispatchLoginError(dispatch, null)
if (!email || !password) {
dispatchLoginError(dispatch, new Error('Email and Password are required to create user'))
return Promise.reject(new Error('Email and Password are Required'))
}
return firebase.auth()
.createUserWithEmailAndPassword(email, password)
.then((userData) =>
// Login to newly created account if signIn flag is true
firebase.auth().currentUser || (!!signIn && signIn === false)
? createUserProfile(dispatch, firebase, userData, profile)
: login(dispatch, firebase, { email, password })
.then(() => createUserProfile(dispatch, firebase, userData, profile || { email }))
.catch(err => {
if (err) {
switch (err.code) {
case 'auth/user-not-found':
dispatchLoginError(dispatch, new Error('The specified user account does not exist.'))
break
default:
dispatchLoginError(dispatch, err)
}
}
return Promise.reject(err)
})
)
.catch((err) => {
dispatchLoginError(dispatch, err)
return Promise.reject(err)
})
}
/**
* @description Send password reset email to provided email
* @param {Function} dispatch - Action dispatch function
* @param {Object} firebase - Internal firebase object
* @param {String} email - Email to send recovery email to
* @return {Promise}
* @private
*/
export const resetPassword = (dispatch, firebase, email) => {
dispatchLoginError(dispatch, null)
return firebase.auth()
.sendPasswordResetEmail(email)
.catch((err) => {
if (err) {
switch (err.code) {
case 'auth/user-not-found':
dispatchLoginError(dispatch, new Error('The specified user account does not exist.'))
break
default:
dispatchLoginError(dispatch, err)
}
return Promise.reject(err)
}
})
}
export default {
dispatchLoginError,
dispatchUnauthorizedError,
dispatchLogin,
unWatchUserProfile,
watchUserProfile,
init,
createUserProfile,
login,
logout,
createUser,
resetPassword
}