промежуточное ПО для токена обновления redux

У меня есть промежуточное ПО, которое может перейти к токену обновления до выполнения следующего действия, а затем запустить другое действие по истечении срока действия токена доступа.

Но если я делаю более одного запроса за раз и токен доступа закончился, я пытаюсь получить столько токена обновления, сколько запрашиваю. Я проверяю свойство isLoading в состоянии, чтобы предотвратить это. Но после запроса значение isLoading истинно в редукторе, в промежуточном программном обеспечении оно кажется ложным, поэтому он запрашивает снова и снова.

Я отправляю refreshTokenPromise в действии fetching_refresh_token, но никогда не получаю state.refreshTokenPromise, он всегда не определен.

У меня однозначно проблемы с государством.

Итак, вот мой вопрос, как я могу получить доступ к изменяющемуся значению состояния в промежуточном программном обеспечении?

Промежуточное ПО для обновления токена: (эта версия несколько раз попадает в конечную точку)

import { AsyncStorage } from 'react-native';
import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
import moment from 'moment';
import Api from '../lib/api';
import {
  FETCHING_REFRESH_TOKEN,
  FETCHING_REFRESH_TOKEN_SUCCESS,
  FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';

export default function tokenMiddleware({ dispatch, getState }) {
  return next => async (action) => {
    if (typeof action === 'function') {
      const state = getState();
      if (state) {
        const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
        if (expiresIn && isExpired(JSON.parse(expiresIn))) {
          if (!state.refreshToken.isLoading) {
            return refreshToken(dispatch).then(() => next(action));
          }
          return state.refreshTokenPromise.then(() => next(action));
        }
      }
    }
    return next(action);
  };
}

async function refreshToken(dispatch) {
  const clientId = await AsyncStorage.getItem('CLIENT_ID');
  const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
  const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');

  const userObject = {
    grant_type: 'refresh_token',
    client_id: JSON.parse(clientId),
    client_secret: JSON.parse(clientSecret),
    refresh_token: refreshToken1,
  };

  const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');

  const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
    await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
    await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
    await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));

    dispatch({
      type: FETCHING_REFRESH_TOKEN_SUCCESS,
      data: res,
    });

    return res ? Promise.resolve(res) : Promise.reject({
      message: 'could not refresh token',
    });
  }).catch((err) => {
    dispatch({
      type: FETCHING_REFRESH_TOKEN_FAILURE,
    });

    throw err;
  });

  dispatch({
    type: FETCHING_REFRESH_TOKEN,
    refreshTokenPromise,
  });

  return refreshTokenPromise;
}

function isExpired(expiresIn) {
  return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
}

Обновить редуктор токенов:

import {
  FETCHING_REFRESH_TOKEN,
  FETCHING_REFRESH_TOKEN_SUCCESS,
  FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';

const initialState = {
  token: [],
  isLoading: false,
  error: false,
};

export default function refreshTokenReducer(state = initialState, action) {
  switch (action.type) {
    case FETCHING_REFRESH_TOKEN:
      return {
        ...state,
        token: [],
        isLoading: true,
      };
    case FETCHING_REFRESH_TOKEN_SUCCESS:
      return {
        ...state,
        isLoading: false,
        token: action.data,
      };
    case FETCHING_REFRESH_TOKEN_FAILURE:
      return {
        ...state,
        isLoading: false,
        error: true,
      };
    default:
      return state;
  }
}

Тем временем, когда я отправляю его в функцию getState to refreshToken, я получаю значение изменения состояния в refreshToken. Но в этой версии токен обновления переходит к другим действиям без обновления.

Версия с патчем Monkey: (эта версия делает только 1 запрос)

import { AsyncStorage } from 'react-native';
import { MIN_TOKEN_LIFESPAN } from 'react-native-dotenv';
import moment from 'moment';
import Api from '../lib/api';
import {
  FETCHING_REFRESH_TOKEN,
  FETCHING_REFRESH_TOKEN_SUCCESS,
  FETCHING_REFRESH_TOKEN_FAILURE } from '../actions/constants';

export default function tokenMiddleware({ dispatch, getState }) {
  return next => async (action) => {
    if (typeof action === 'function') {
      const state = getState();
      if (state) {
        const expiresIn = await AsyncStorage.getItem('EXPIRES_IN');
        if (expiresIn && isExpired(JSON.parse(expiresIn))) {
          if (!state.refreshTokenPromise) {
            return refreshToken(dispatch, getState).then(() => next(action));
          }
          return state.refreshTokenPromise.then(() => next(action));
        }
      }
    }
    return next(action);
  };
}

async function refreshToken(dispatch, getState) {
  const clientId = await AsyncStorage.getItem('CLIENT_ID');
  const clientSecret = await AsyncStorage.getItem('CLIENT_SECRET');
  const refreshToken1 = await AsyncStorage.getItem('REFRESH_TOKEN');

  const userObject = {
    grant_type: 'refresh_token',
    client_id: JSON.parse(clientId),
    client_secret: JSON.parse(clientSecret),
    refresh_token: refreshToken1,
  };

  if (!getState().refreshToken.isLoading) {
    const userParams = Object.keys(userObject).map(key => encodeURIComponent(key) + '=' + encodeURIComponent(userObject[key])).join('&');

    const refreshTokenPromise = Api.post('/token', userParams).then(async (res) => {
      await AsyncStorage.setItem('ACCESS_TOKEN', res.access_token);
      await AsyncStorage.setItem('REFRESH_TOKEN', res.refresh_token);
      await AsyncStorage.setItem('EXPIRES_IN', JSON.stringify(res['.expires']));

      dispatch({
        type: FETCHING_REFRESH_TOKEN_SUCCESS,
        data: res,
      });

      return res ? Promise.resolve(res) : Promise.reject({
        message: 'could not refresh token',
      });
    }).catch((err) => {
      dispatch({
        type: FETCHING_REFRESH_TOKEN_FAILURE,
      });

      throw err;
    });

    dispatch({
      type: FETCHING_REFRESH_TOKEN,
      refreshTokenPromise,
    });

    return refreshTokenPromise;
  }
}

function isExpired(expiresIn) {
  return moment(expiresIn).diff(moment(), 'seconds') < MIN_TOKEN_LIFESPAN;
}

Спасибо.


person ccoeder    schedule 15.10.2017    source источник
comment
Я не вижу, где вы проверяете значение isLoading в промежуточном программном обеспечении.   -  person Freez    schedule 16.10.2017
comment
Я пробовал другие вещи, исправил, вы бы посмотрели еще раз?   -  person ccoeder    schedule 16.10.2017
comment
Конечно ! Мне любопытно   -  person Freez    schedule 16.10.2017
comment
Вы посмотрели?   -  person ccoeder    schedule 16.10.2017
comment
Вроде хорошо, но я думаю, вам стоит попробовать промежуточное ПО github.com/redux-saga/redux- saga, если вы хотите написать ее правильно.   -  person Freez    schedule 17.10.2017
comment
См. takeLatest помощник redux-saga.js.org/docs/api / # takelatestpattern-saga-args   -  person Freez    schedule 17.10.2017
comment
Могу ли я использовать это с redux-thunk?   -  person ccoeder    schedule 17.10.2017
comment
Да, вы можете использовать два промежуточного программного обеспечения в одном проекте, но я не думаю, что для вашего варианта использования вам больше понадобится redux-thunk.   -  person Freez    schedule 17.10.2017
comment
Привет, вы нашли какое-нибудь решение, которое не связано с сокращениями-сагами?   -  person Greco Jonathan    schedule 23.08.2018
comment
@Hooli Я разместил свой ответ ниже, пожалуйста, посмотрите.   -  person ccoeder    schedule 29.08.2018


Ответы (2)


Я решил эту проблему с помощью промежуточного программного обеспечения axios. Я думаю, это довольно мило.

import { AsyncStorage } from 'react-native';
import Config from 'react-native-config';
import axios from 'axios';
import { store } from '../store';
import { refreshToken } from '../actions/refreshToken'; // eslint-disable-line

const instance = axios.create({
  baseURL: Config.API_URL,
});

let authTokenRequest;

function resetAuthTokenRequest() {
  authTokenRequest = null;
}

async function getAuthToken() {
  const clientRefreshToken = await AsyncStorage.getItem('clientRefreshToken');

  if (!authTokenRequest) {
    authTokenRequest = store.dispatch(refreshToken(clientRefreshToken));

    authTokenRequest.then(
      () => {
        const {
          token: { payload },
        } = store.getState();

        // save payload to async storage
      },
      () => {
        resetAuthTokenRequest();
      },
    );
  }

  return authTokenRequest;
}

instance.interceptors.response.use(
  response => response,
  async (error) => {
    const originalRequest = error.config;

    if (
      error.response.status === 401
      && !originalRequest._retry // eslint-disable-line no-underscore-dangle
    ) {
      return getAuthToken()
        .then(() => {
          const {
            token: {
              payload: { 'access-token': accessToken, client, uid },
            },
          } = store.getState();

          originalRequest.headers['access-token'] = accessToken;
          originalRequest.headers.client = client;
          originalRequest.headers.uid = uid;
          originalRequest._retry = true; // eslint-disable-line no-underscore-dangle

          return axios(originalRequest);
        })
        .catch(err => Promise.reject(err));
    }

    return Promise.reject(error);
  },
);

export default instance;

Если у вас возникла проблема, не стесняйтесь спрашивать.

person ccoeder    schedule 29.08.2018
comment
Привет, а где именно это добавить? - person Jeff; 22.09.2019

вы могли бы извлечь выгоду из саги-редукс

https://github.com/redux-saga/redux-saga

redux-sagas - это просто фоновый бегун, который отслеживает ваши действия и может реагировать, когда выполняется какое-то конкретное действие. Вы можете прослушивать все действия и реагировать на все, или вы можете реагировать только на последние, как указано в комментариях.

https://redux-saga.js.org/docs/api/#takelatestpattern-saga-args

в то время как redux-thunk - это просто еще один способ создавать действия на ходу и ждать, пока произойдет некоторый ввод-вывод, а затем создать еще несколько действий, когда ввод-вывод будет выполнен. Это больше похоже на шаблон синхронизированного кода, а redux-sagas больше похожи на многопоточность. В основном потоке у вас запущено приложение, а в фоновом потоке у вас есть мониторы саги и реакции.

person Lukas Liesis    schedule 20.10.2017
comment
Возникла проблема с попыткой реализовать саги, чтобы сделать то, что обсуждалось OP. Кажется, мне придется реализовать это в каждой функции генератора? Разве ответ OP не лучше, если поместить его в промежуточное ПО API axios? - person Jacob F. Davis C-CISO; 01.10.2020
comment
вы можете отреагировать на одно и то же событие несколькими сагами. так что вы можете иметь функцию, которая реагирует на все саги, если хотите. Посмотрите базовый образец регистратора здесь: redux-saga.js.org/docs/advanced/ FutureActions.html Вкратце: просто используйте takeEvery('*', function* something(action){...}), если хотите выполнять все действия с этой функцией. Вы также можете просто иметь функцию, необходимую для действий x y z, и просто добавить ее в описание корневой саги. Не нужно повторяться в нескольких обработчиках саг. Вы также можете делать что-то с функциями более высокого порядка. погуглите о тех, если неизвестно. - person Lukas Liesis; 03.10.2020