client/src/sagas/networkSaga.js
changeset 97 69eaef18b01b
parent 88 2a861fed6bde
child 129 d48946d164c6
equal deleted inserted replaced
96:b58463d7dc8e 97:69eaef18b01b
     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 }