1 import * as types from '../constants/actionTypes'; |
1 import * as types from '../constants/actionTypes'; |
2 import { all, call, fork, race, take, cancelled, put, select } from 'redux-saga/effects' |
2 import { all, call, fork, race, take, cancelled, put, select } from 'redux-saga/effects' |
3 import config from '../config'; |
3 import config from '../config'; |
|
4 import * as persistConstants from 'redux-persist/constants'; |
|
5 import jwt_decode from 'jwt-decode'; |
|
6 import moment from 'moment'; |
4 |
7 |
5 // Utility function to delay effects |
8 // Utility function to delay effects |
6 function delay(millis) { |
9 function delay(millis) { |
7 const promise = new Promise(resolve => { |
10 const promise = new Promise(resolve => { |
8 setTimeout(() => resolve(true), millis) |
11 setTimeout(() => resolve(true), millis) |
9 }); |
12 }); |
10 return promise; |
13 return promise; |
11 } |
14 } |
12 |
15 |
13 function pingServer(client, token) { |
16 function pingServer(client, token) { |
14 if(token) { |
17 const decodedToken = jwt_decode(token); |
15 const timeout = new Promise((resolve, reject) => { |
18 const currentTs = moment.now()/1000; |
16 setTimeout(reject, config.networkStatusTimeout, 'request timed out'); |
19 |
17 }); |
20 const timeout = new Promise((resolve, reject) => { |
|
21 setTimeout(reject, config.networkStatusTimeout, 'request timed out'); |
|
22 }); |
|
23 |
|
24 if((decodedToken.exp-currentTs) < 300) { |
18 return Promise |
25 return Promise |
19 .race([timeout, client.post('/api/auth/refresh/', { token })]); |
26 .race([timeout, client.post('/api/auth/refresh/', { token })]); |
20 } else { |
27 } else { |
21 return Promise.reject({ error: 'No token in the store'}) |
28 // We do a GET because a HEAD generate a preflight CORS OPTION request. The GET does not. |
|
29 return Promise |
|
30 .race([timeout, client.get('/api/auth/user/')]); |
22 } |
31 } |
23 } |
32 } |
24 |
33 |
25 // Fetch data every 20 seconds |
|
26 function* pollData(context) { |
34 function* pollData(context) { |
|
35 const token = yield select(state => state.token); |
|
36 // No token : we wait for a login |
|
37 if(!token) { |
|
38 yield take(types.AUTH_LOGIN_SUCCESS); |
|
39 } |
27 try { |
40 try { |
28 yield call(delay, config.networkStatusInterval); |
|
29 const token = yield select(state => state.token); |
|
30 const res = yield pingServer(context.client, token); |
41 const res = yield pingServer(context.client, token); |
31 yield call(context.callback, true); |
42 yield call(context.callback, true); |
32 yield put({ |
43 if(res.token) { |
33 type: types.AUTH_STORE_TOKEN_ASYNC, |
44 yield put({ |
34 token: res.token, |
45 type: types.AUTH_STORE_TOKEN_ASYNC, |
35 }); |
46 token: res.token, |
|
47 }); |
|
48 } |
36 } catch (error) { |
49 } catch (error) { |
37 yield call(context.callback, false); |
50 yield call(context.callback, false); |
38 // if the error is that there is no token, then we know we have to wait for a login |
51 //TODO: This is ugly... |
39 if(error.error && error.error === 'No token in the store') { |
52 if ((error.non_field_errors && |
40 yield take(types.AUTH_LOGIN_SUCCESS); |
|
41 } else if (error.non_field_errors && |
|
42 error.non_field_errors && |
53 error.non_field_errors && |
43 error.non_field_errors.length && |
54 error.non_field_errors.length && |
44 error.non_field_errors.length > 0 && |
55 error.non_field_errors.length > 0 && |
45 ( error.non_field_errors[0] === 'Signature has expired.' || |
56 ( error.non_field_errors[0] === 'Signature has expired.' || |
46 error.non_field_errors[0] === 'Refresh has expired.' ) |
57 error.non_field_errors[0] === 'Refresh has expired.' )) || |
|
58 (error.detail && ( |
|
59 error.detail === 'Signature has expired.' || |
|
60 error.detail=== 'Refresh has expired.' |
|
61 )) |
47 ) { |
62 ) { |
48 yield put({ |
63 yield put({ |
49 type: types.AUTH_DEAUTHENTICATE |
64 type: types.AUTH_DEAUTHENTICATE |
50 }); |
65 }); |
51 } |
66 } |
53 if (yield cancelled()) { |
68 if (yield cancelled()) { |
54 // pollDate cancelled |
69 // pollDate cancelled |
55 // if there is a token : this was a LOGIN, set status to ok |
70 // if there is a token : this was a LOGIN, set status to ok |
56 // if there is no token : this was a LOGOUT, set status to ko and wait for login |
71 // if there is no token : this was a LOGOUT, set status to ko and wait for login |
57 const token = yield select(state => state.token); |
72 const token = yield select(state => state.token); |
58 if(token) { |
73 yield call(context.callback, Boolean(token)); |
59 yield call(context.callback, true); |
74 } |
60 } else { |
75 } |
61 yield call(context.callback, false); |
76 } |
62 yield take(types.AUTH_LOGIN_SUCCESS); |
77 |
63 } |
78 function* callDelay(context) { |
|
79 try { |
|
80 yield call(delay, config.networkStatusInterval); |
|
81 } finally { |
|
82 if (yield cancelled()) { |
|
83 // pollDate cancelled |
|
84 // if there is a token : this was a LOGIN, set status to ok |
|
85 // if there is no token : this was a LOGOUT, set status to ko and wait for login |
|
86 const token = yield select(state => state.token); |
|
87 yield call(context.callback, Boolean(token)); |
64 } |
88 } |
65 } |
89 } |
66 } |
90 } |
67 |
91 |
68 // Wait for successful response, then fire another request |
92 // Wait for successful response, then fire another request |
69 // Cancel polling if user logs out or log in |
93 // Cancel polling if user logs out or log in |
70 function* watchPollData(context) { |
94 function* watchPollData(context) { |
|
95 |
|
96 //wait for the state to be rehydrated |
|
97 yield take(persistConstants.REHYDRATE); |
|
98 |
71 while (true) { |
99 while (true) { |
72 yield race([ |
100 yield race([ |
73 call(pollData, context), |
101 all([call(pollData, context), call(callDelay, context)]), |
74 take(types.AUTH_LOGOUT), |
102 take(types.AUTH_LOGOUT), |
75 take(types.AUTH_LOGIN_SUCCESS) |
103 take(types.AUTH_LOGIN_SUCCESS) |
76 ]); |
104 ]); |
77 } |
105 } |
78 } |
106 } |