client/src/sagas/networkSaga.js
author ymh <ymh.work@gmail.com>
Tue, 29 Mar 2022 11:23:56 +0200
changeset 211 244a90638e80
parent 168 ea92f4fe783d
permissions -rw-r--r--
Added tag 0.2.3 for changeset 3de92ddba2de

import * as types from '../constants/actionTypes';
import { all, call, fork, race, take, cancelled, put, select } from 'redux-saga/effects'
import config from '../config';
import jwt_decode from 'jwt-decode';
import moment from 'moment';
import { delay } from 'redux-saga';
import { setOnlineStatus } from '../actions/networkActions';
import { getToken } from '../selectors/authSelectors';



function pingServer(client, token) {
  const decodedToken = jwt_decode(token);
  const currentTs = moment.now()/1000;

  const timeout = new Promise((resolve, reject) => {
    setTimeout(reject, config.networkStatusTimeout, 'request timed out');
  });

  if((decodedToken.exp-currentTs) < 300) {
    return Promise
      .race([timeout, client.post('/api/auth/refresh/', { token })]);
  } else {
    // We do a GET because a HEAD generate a preflight CORS OPTION request. The GET does not.
    return Promise
      .race([timeout, client.get('/api/auth/user/')]);
  }
}

function* pollData(context) {
  const token = yield select(getToken);
  // No token : we wait for a login
  if(!token) {
    yield take(types.AUTH_LOGIN_SUCCESS);
  }
  try {
    const res = yield pingServer(context.client, token);
    yield put(setOnlineStatus(true));
    if(res.token) {
      yield put({
        type: types.AUTH_STORE_TOKEN_ASYNC,
        token: res.token,
      });
    }
  } catch (error) {
    yield put(setOnlineStatus(false));
    //TODO: This is ugly...
    const data = error.data;
    if ((data && data.non_field_errors &&
      data.non_field_errors &&
      data.non_field_errors.length &&
      data.non_field_errors.length > 0 &&
      ( data.non_field_errors[0] === 'Signature has expired.' ||
        data.non_field_errors[0] === 'Refresh has expired.' )) ||
      (error.detail && (
        error.detail === 'Signature has expired.' ||
        error.detail=== 'Refresh has expired.'
      ))
    ) {
      yield put({
        type: types.AUTH_DEAUTHENTICATE
      });
    }
  } finally {
    if (yield cancelled()) {
      // pollDate cancelled
      // if there is a token : this was a LOGIN, set status to ok
      // if there is no token : this was a LOGOUT, set status to ko and wait for login
      const token = yield select(getToken);
      yield put(setOnlineStatus(Boolean(token)));
    }
  }
}

function* callDelay(context) {
  try {
    yield call(delay, config.networkStatusInterval);
  } finally {
    if (yield cancelled()) {
      // pollDate cancelled
      // if there is a token : this was a LOGIN, set status to ok
      // if there is no token : this was a LOGOUT, set status to ko and wait for login
      const token = yield select(getToken);
      yield put(setOnlineStatus(Boolean(token)));
    }
  }
}

// Wait for successful response, then fire another request
// Cancel polling if user logs out or log in
function* watchPollData(context) {
  while (true) {
    yield race({
      poll: all([call(pollData, context), call(callDelay, context)]),
      logout: take(types.AUTH_LOGOUT),
      login: take(types.AUTH_LOGIN_SUCCESS)
    });
  }
}

// Daemonize tasks in parallel
export default function* root(context) {
  yield all([
    fork(watchPollData, context)
    // other watchers here
  ]);
}