1 | import { clearAllCached, createSelector } from 'redux-bundler'
|
2 | import tryIt from 'tryit'
|
3 | import { FIREBASE_TOKEN_FAILED } from './firebase'
|
4 | const 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 |
|
15 | const 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 |
|
41 | const 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 |
|
63 | const doLogout = (broadcast = true) => ({ dispatch }) => {
|
64 | dispatch({ type: actions.DO_LOGOUT })
|
65 | tryIt(() => {
|
66 |
|
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 |
|
81 | const readTokens = () => {
|
82 | const res = {}
|
83 | tryIt(() => {
|
84 | res.token = window.localStorage.token
|
85 | res.fbToken = window.localStorage.fbToken
|
86 | })
|
87 | return res
|
88 | }
|
89 |
|
90 | const 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 |
|
101 | const doReceiveToken = (tokenObject) => ({ dispatch }) => {
|
102 | const { token, fbToken } = tokenObject
|
103 | dispatch({ type: actions.TOKEN_RECEIVED, payload: tokenObject })
|
104 | writeTokens(token, fbToken)
|
105 | }
|
106 |
|
107 | const selectAuthToken = state => state.auth.token
|
108 | const selectFirebaseToken = state => state.auth.fbToken
|
109 |
|
110 | const 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 |
|
123 | const selectIsLoggedIn = createSelector(
|
124 | selectAuthToken,
|
125 | token => !!token
|
126 | )
|
127 |
|
128 | const 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 |
|
136 | if (event.key === 'logout') {
|
137 |
|
138 |
|
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 |
|
150 | export 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 | }
|