UNPKG

7.49 kBJavaScriptView Raw
1import { clearAllCached, createSelector } from 'redux-bundler'
2import tryIt from 'tryit'
3import { FIREBASE_TOKEN_FAILED } from './firebase'
4const actions = {
5 CLEAR_LOGIN_STATE: 'CLEAR_LOGIN_STATE',
6 DO_LOGIN_START: 'DO_LOGIN_START',
7 DO_LOGIN_SUCCESS: 'DO_LOGIN_SUCCESS',
8 DO_LOGIN_ERROR: 'DO_LOGIN_ERROR',
9 DO_LOGOUT: 'DO_LOGOUT',
10 TOKEN_RECEIVED: 'TOKEN_RECEIVED',
11 TOKEN_EXPIRED: 'TOKEN_EXPIRED',
12 FETCH_FB_TOKEN: 'FETCH_FB_TOKEN'
13}
14
15const doLogin = (email, superUser = false) => ({ dispatch, gqlFetch }) => {
16 dispatch({ type: actions.DO_LOGIN_START, payload: { email, attemptType: 'login' } })
17 gqlFetch(`mutation {
18 triggerLogin(email:"${email}", url:"${window.location.href}", claimSuperUser:${superUser}) {
19 email
20 token
21 signInUrl
22 }
23 }`)
24 .then(
25 (result) => {
26 const triggeredLogin = result.data.triggerLogin
27 if (triggeredLogin) {
28 dispatch({ type: actions.DO_LOGIN_SUCCESS, payload: triggeredLogin })
29 }
30 },
31 (error) => {
32 if (error.length && error[0].message === 'UserNotFound') {
33 dispatch({ type: actions.DO_LOGIN_ERROR, error: new Error('UserNotFound') })
34 } else {
35 dispatch({ type: actions.DO_LOGIN_ERROR, error })
36 }
37 }
38 )
39}
40
41const doSignUp = email => ({ dispatch, gqlFetch }) => {
42 dispatch({ type: actions.DO_LOGIN_START, payload: { email, attemptType: 'signup' } })
43 gqlFetch(`mutation {
44 triggerSignUp(email:"${email}", url:"${window.location.href}") {
45 email
46 token
47 signInUrl
48 }
49 }`)
50 .then(
51 (result) => {
52 const triggeredLogin = result.data.triggerSignUp
53 if (triggeredLogin) {
54 dispatch({ type: actions.DO_LOGIN_SUCCESS, payload: triggeredLogin })
55 }
56 },
57 (error) => {
58 dispatch({ type: actions.DO_LOGIN_ERROR, error })
59 }
60 )
61}
62
63const doLogout = (broadcast = true) => ({ dispatch }) => {
64 dispatch({ type: actions.DO_LOGOUT })
65 tryIt(() => {
66 // trigger storage event to log out other tabs
67 if (broadcast) {
68 window.localStorage.logout = true
69 }
70 const { debug } = window.localStorage
71 window.localStorage.clear()
72 if (debug) {
73 window.localStorage.debug = true
74 }
75 })
76 clearAllCached().then(() => {
77 window.location = '/'
78 })
79}
80
81const readTokens = () => {
82 const res = {}
83 tryIt(() => {
84 res.token = window.localStorage.token
85 res.fbToken = window.localStorage.fbToken
86 })
87 return res
88}
89
90const writeTokens = (token, fbToken) => {
91 tryIt(() => {
92 if (token) {
93 window.localStorage.token = token
94 }
95 if (fbToken) {
96 window.localStorage.fbToken = fbToken
97 }
98 })
99}
100
101const doReceiveToken = (tokenObject) => ({ dispatch }) => {
102 const { token, fbToken } = tokenObject
103 dispatch({ type: actions.TOKEN_RECEIVED, payload: tokenObject })
104 writeTokens(token, fbToken)
105}
106
107const selectAuthToken = state => state.auth.token
108const selectFirebaseToken = state => state.auth.fbToken
109
110const doFetchFirebaseToken = () => ({gqlFetch, dispatch}) => {
111 dispatch({type: actions.FETCH_FB_TOKEN})
112 gqlFetch('{firebaseToken}')
113 .then(({data}) => {
114 const fbToken = data.firebaseToken
115 if (fbToken) {
116 dispatch(doReceiveToken({fbToken}))
117 } else {
118 throw Error('could not fetch FB token')
119 }
120 })
121}
122
123const selectIsLoggedIn = createSelector(
124 selectAuthToken,
125 token => !!token
126)
127
128const init = (store) => {
129 const hashObject = store.selectHashObject()
130 if (hashObject.token || hashObject.fbToken) {
131 store.doReceiveToken(hashObject)
132 store.doUpdateHash('', {replace: true})
133 }
134 window.addEventListener('storage', (event) => {
135 // receive logout hint
136 if (event.key === 'logout') {
137 // don't broadcast since we're the
138 // receiver
139 store.doLogout(false)
140 }
141 if (event.key === 'token') {
142 store.doReceiveToken({token: event.newValue})
143 }
144 if (event.key === 'fbToken') {
145 store.doReceiveToken({fbToken: event.newValue})
146 }
147 })
148}
149
150export default {
151 name: 'auth',
152 init,
153 getReducer: () => {
154 const { token, fbToken } = readTokens() || {token: null, fbToken: null}
155 const initialState = {
156 token,
157 fbToken,
158 error: false,
159 loading: false,
160 email: null,
161 tokenExpired: false,
162 loggingOut: false,
163 attemptType: null,
164 fetchingFbToken: false,
165 userFound: true,
166 success: false
167 }
168
169 return (state = initialState, {type, payload, error}) => {
170 if (type === actions.DO_LOGIN_START) {
171 return Object.assign({}, state, {
172 success: false,
173 error: false,
174 loading: true
175 }, payload)
176 }
177 if (type === actions.DO_LOGIN_ERROR) {
178 return Object.assign({}, state, {
179 success: false,
180 loading: false,
181 error: true,
182 failedLogin: Boolean(payload && payload.failedLogin),
183 userFound: error.message !== 'UserNotFound'
184 })
185 }
186 if (type === actions.DO_LOGIN_SUCCESS) {
187 return Object.assign({}, state, {
188 success: true,
189 loading: false,
190 error: false,
191 signInUrl: payload.signInUrl,
192 userFound: true
193 })
194 }
195 if (type === actions.CLEAR_LOGIN_STATE) {
196 return Object.assign({}, state, {
197 success: false,
198 loading: false,
199 error: false,
200 email: null,
201 signInUrl: '',
202 userFound: true,
203 attemptType: null
204 })
205 }
206 if (type === actions.TOKEN_RECEIVED) {
207 const extraChanges = {}
208 if (payload.fbToken) {
209 extraChanges.fetchingFbToken = false
210 }
211 return Object.assign({}, state, payload, extraChanges)
212 }
213 if (type === actions.TOKEN_EXPIRED) {
214 return Object.assign({}, state, {
215 tokenExpired: true
216 })
217 }
218 if (type === actions.FETCH_FB_TOKEN) {
219 return Object.assign({}, state, {
220 fetchingFbToken: true
221 })
222 }
223 if (type === FIREBASE_TOKEN_FAILED) {
224 return Object.assign({}, state, {
225 fbToken: ''
226 })
227 }
228 if (type === actions.DO_LOGOUT) {
229 return Object.assign({}, state, {
230 loggingOut: true
231 })
232 }
233 return state
234 }
235 },
236 selectAuthToken,
237 selectFirebaseToken,
238 selectIsLoggedIn,
239 selectAuthState: (state) => state.auth,
240 selectAuthEmail: createSelector(
241 'selectAuthState',
242 state => state.email
243 ),
244 selectAuthSignInUrl: createSelector(
245 'selectAuthState',
246 state => state.signInUrl
247 ),
248 selectAuthStatus: createSelector(
249 'selectAuthState',
250 (state) => {
251 if (state.loading) {
252 return 'sending'
253 } else if (state.userFound === false) {
254 return 'not found'
255 } else if (state.error) {
256 return 'error'
257 } else if (state.success) {
258 return 'success'
259 }
260 return ''
261 }
262 ),
263 reactShouldTriggerLogout: createSelector(
264 'selectAuthState',
265 auth => {
266 if (auth.tokenExpired && !auth.loggingOut) {
267 return doLogout()
268 }
269 }
270 ),
271 doLogout,
272 doLogin,
273 doSignUp,
274 doReceiveToken,
275 doFetchFirebaseToken,
276 doClearLoginState: () => ({type: actions.CLEAR_LOGIN_STATE}),
277 reactShouldFetchFirebaseToken: createSelector(
278 'selectAuthState',
279 'selectIsOnline',
280 (auth, online) => {
281 if (online && auth.token && !auth.fbToken && !auth.fetchingFbToken) {
282 return {actionCreator: 'doFetchFirebaseToken'}
283 }
284 }
285 )
286}