Найти в Дзене

Реализация управления сессией в React с помощью RTK Query и Middleware

Всем привет! Меня зовут Александр Панфилов, я фронтенд-разработчик и тимлид с 9-летним опытом. В этой статье я хочу рассказать о, казалось, банальной функционале - сессии в приложении. Моя предыдущая реализация системы сессий работала, но в ней было две ключевые проблемы: Была очень ресурсоемкий; При выходе из админки выводилось два сообщения об истекшей сессии. На тот момент времени меня это все устраивало и ничего лучше я не смог придумать. Сейчас я достиг предела в развитии своей первой версии админки и мною было принято решение ее переписать. Для ускорения процесса я обратился к искусственному интеллекту за помощью в составлении алгоритма. Он состоял из следующих шагов: В rtk query в prepareHeaders установить нужные заголовки авторизации Написать slice, содержащий в себе данные заголовков для авторизации Использовать middleware для автоматического обновления токенов Запускать весь процесс в модуле админки. Более подробно процесс можно разобрать по адресу https://chat.deepseek.com/
Оглавление

Всем привет! Меня зовут Александр Панфилов, я фронтенд-разработчик и тимлид с 9-летним опытом. В этой статье я хочу рассказать о, казалось, банальной функционале - сессии в приложении.

Моя предыдущая реализация системы сессий работала, но в ней было две ключевые проблемы:

  • Была очень ресурсоемкий;
  • При выходе из админки выводилось два сообщения об истекшей сессии.

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

Для ускорения процесса я обратился к искусственному интеллекту за помощью в составлении алгоритма. Он состоял из следующих шагов:

  • В rtk query в prepareHeaders установить нужные заголовки авторизации
  • Написать slice, содержащий в себе данные заголовков для авторизации
  • Использовать middleware для автоматического обновления токенов
  • Запускать весь процесс в модуле админки.

Более подробно процесс можно разобрать по адресу https://chat.deepseek.com/share/ecwdxbki4q50zak80c

1-я реализация

Расскажу подробнее о функционале 1-й версии. Сессия состояла из двух функций:

  • Функция, которая запускается по интервалу

Эту функцию я разработал для ситуаций, когда пользователь продолжительное количество времени не взаимодействует с сайтом. Вызовы от событий в браузере не поступали и через определенный промежуток времени нужно разлогинить пользователя из админки и перевести на страницу авторизации.

  • Функция, которая реагирует на событие

Эта функция очень тяжелая. Она вызывается на следующие события:

  • клик;
  • скрол;
  • нажатие кнопки;
  • движение мышки.

Все эти четыре события часто вызывались параллельно, сильно нагружая процессор пользовательского компьютера.

Идея работы этих функций вместе заключалась в следующем:

Когда пользователь начинает использовать систему, то в браузере будут срабатывать события. Алгоритм понимает, что пользователь активен и продлевает время жизни access токена.

Если пользователь становился неактивным, то в заданный интервал производится запрос на проверку корректности access токена. Если этот токен истекал - сессия считается законченной и пользователя необходимо перевести на страницу авторизации.

Функция, которая запускается на события в браузере
Функция, которая запускается на события в браузере

Разберем подробно из чего состоит функция, которая отрабатывает на события в браузере. На картинке выше видно следующее:

  • Функция вызывается в side effect, чтобы код выполнился один раз в компоненте и при переходе между страницами события были удалены
  • Функция callbackListener отвечает за обновление токена в экшене updateTokens. Далее функцию callbackListener помещаю в обертку throttle, чтобы ограничить ее вызов один раз в 3 секунды и предотвратить утечку памяти.
  • Функция callbackListener в обертке вешается на click, keypress, scroll, mousemove.
  • Метод return возвращает функцию, которая выполняет удаление методов на прослушивание события, добавленные ранее.
Функция запуска по интервалу
Функция запуска по интервалу

Разберем подробно функцию запуска по интервалу. На картинке выше видно следующее:

  • Функция запускается в side effect, чтобы ее код был выполнен один раз в компоненте.
  • Функция interval вызывается каждые 90 секунд и проверяет актуальность access токена, а в return возвращается функция, которая прекращает выполнение таймера, по которому проверяется актуальность токена.

2-я версия

Во второй версии я выше описал шаги, разберем их подробнее. Первый пункт состоит из добавления заголовка с токеном в запрос. Это было одной из первых моих проблем. Я использую автоматическую генерацию и не могу добавить запрос в сгенерированный код. Если я это сделаю, то при следующем обновлении контракта мой код будет стерт.

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

Добавление заголовка в запрос
Добавление заголовка в запрос

На картинке, в функции prepareHeaders получаем доступ к заголовкам и глобальному стейту. Из глобального стейта получаем токен и добавляем к заголовку запроса. Также, здесь можно получить данные об вызываемом экшене и из него получить тип. По этому типу можно определить происходит ли вызов. По этому алгоритму можно исключить вызовы, для которых заголовок не нужен.

Slice для работы с токенами
Slice для работы с токенами

Перейдем к следующему шагу. В предыдущем шаге мы упоминали slice authSlice. Цель этого slice - содержать всю логику управления токенами и быть единым местом их хранения. Важно подметить - значения токенов нужно получать только из store во всем приложении.

Структура slice:

  • initialState - первоначальное значение для скейта slice. Токены берут свои значений из куков. Свойство userInSystem говорит о том находится ли пользователь в админке или нет.
  • экшн tokenRecived - метод для обновления токенов. Сразу обновляем значения и в глобальном store, и в куках.
  • экшн loggedOut - метод для сброса токенов в глобальном store и в cookies.
  • экшн setUserInSystem - метод для управления свойством userInSystem.

Логика обновления токенов. В прошлой реализации я этот функционал использовал в компоненте. Мне эта практика не понравилась, поэтому я принял решение перенести ее в middleware.

В моем случае потребовалось два middleware

  • Для периодического опроса на актуальность access токена и его обновление
  • Проверка пользователя на его активность на сайте.

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

Разберем первый middleware, отвечающий за периодический опрос токена.

Middleware для опроса актуальности токена access
Middleware для опроса актуальности токена access

На картинке выше изображено следующее:

  • refreshTokenMiddleware - создание middleware, который прослушивает события и при соблюдении условия выполняет заданную логику.
  • refreshInterval - интервал, по которому будет сбрасываться выполнение функции.
  • startListening - создание слушателя
  • predicate - возвращает условие, по которому будет выполняться функционал слушателя. В моем случае - смена userInSystem с false на true. Это обозначает, что пользователь зашел в админку.
  • effect - функция, которая сработает, если predicate вернет true. В самой функции проверяется access токен. Логика обновления находится в функции checkAccessToken. Стоит обратить внимание, что она вызывается два раза. Первый раз - она отрабатывает в ходе выполнения кода, во второй запускается в интервале. Условие в начале effect необходимо для предотвращения запуска нескольких таймеров.

Если перед таймером не запускать checkAccessToken - алгоритм проверки актуальности токена будет работать некорректно.

Разберем слушателя middleware для второго случая, когда пользователь вышел из админки.

Middleware для сброса функции интервала
Middleware для сброса функции интервала

На картинке выше:

  • predicate содержит в себе условие, которое сработает, когда пользователь вышел из системы и его статус был изменен.
  • effect содержит в себе функцию, которая сбросит выполнение функции проверки access токена.

Таким образом, когда пользователь зайдет в админку, то у него сразу будет опрашиваться статус токена. Когда пользователь уходит из админки, функция прекращает опрос.

Разберем слушатель во втором middleware.

Middleware для проверки активности пользователя на сайте
Middleware для проверки активности пользователя на сайте

Разберем подробно из чего состоит функция:

  • actionCreator — отслеживает выполнение конкретного экшина или группы экшинов. В данном примере отслеживается экшн setUserInSystem. Он принимает в себя булевый тип данных.
  • effect — содержит в себе функцию, отслеживающую активность пользователя на сайте. Разберем эту функцию подробней:
  • shouldRedirectToAuth — функция, содержит логику, по которой определяем, нужно ли перевести пользователя на страницу авторизации
  • redirectToAuthPage — фунция, содерджит логику для сброса токена
  • trackActivity — функция для запуска алгоритма проверки активности пользователя.
  • Условие на добавление или удаление функции на события. Если в payload равно true, то добавляем функцию на события, в противном случае — удаляем.

Логика работы функций trackActivity, redirectToAuthPage, shouldRedirectToAuth заключается в следующем:

  • trackActivity вызывается на событие браузера;
  • по необходимости сбрасывается таймер, который в случае неактивности вызовет проверку (shouldRedirectToAuth) и, если нужно, сброс токена (redirectToAuthPage)

Текущая реализация middleware лучше предыдущей по следующим пунктам:

  • понятнее в реализации
  • вся логика содержится в middleware
  • проще расширять под новые требования
  • не нужно добавлять хуки в компонент.

Запуск всего функционала.

Запуск сессии
Запуск сессии

Соберем весь алгоритм сессии в одну картину. На картинке выше прошу обратить внимание на side эффект в котором меняется значение userInSystem. После этого по условию запускаются два выше описанных middleware. В них происходят вызовы эндпоинтов, в которых добавляем заголовки.

По истечении времени жизни токена процесс запускается в обратном порядке. В отрицательном ответе на запрос проверки жизни токена приходит отрицательный ответ, в первом middleware стираются токены, в компоненте происходит редирект на страницу авторизации и после этого во втором middleware происходит отписка функции от событий браузера.

Работа авторизации в моем блоге, отображенная в схеме
Работа авторизации в моем блоге, отображенная в схеме

Выводы

В данной статье я поделился своей реализацией сессии в react и rtk query, я лучше понял, как работает middleware и когда их лучше применять, в очередной раз пересмотрел свои взгляды на разработку в очередной раз. Разработал для себя алгоритм использования искусственного интеллекта в разработке. Всем спасибо, что дочитали. Увидимся в следующих статьях.