Всем привет! Меня зовут Александр Панфилов, я фронтенд-разработчик и тимлид с 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 authSlice. Цель этого slice - содержать всю логику управления токенами и быть единым местом их хранения. Важно подметить - значения токенов нужно получать только из store во всем приложении.
Структура slice:
- initialState - первоначальное значение для скейта slice. Токены берут свои значений из куков. Свойство userInSystem говорит о том находится ли пользователь в админке или нет.
- экшн tokenRecived - метод для обновления токенов. Сразу обновляем значения и в глобальном store, и в куках.
- экшн loggedOut - метод для сброса токенов в глобальном store и в cookies.
- экшн setUserInSystem - метод для управления свойством userInSystem.
Логика обновления токенов. В прошлой реализации я этот функционал использовал в компоненте. Мне эта практика не понравилась, поэтому я принял решение перенести ее в middleware.
В моем случае потребовалось два middleware
- Для периодического опроса на актуальность access токена и его обновление
- Проверка пользователя на его активность на сайте.
Отдельный комментарий, кому-то может показаться, что я делаю то же самое, что и в первой реализации. С точки зрения общей логики - да, с точки зрения реализации - абсолютно иное решение.
Разберем первый middleware, отвечающий за периодический опрос токена.
На картинке выше изображено следующее:
- refreshTokenMiddleware - создание middleware, который прослушивает события и при соблюдении условия выполняет заданную логику.
- refreshInterval - интервал, по которому будет сбрасываться выполнение функции.
- startListening - создание слушателя
- predicate - возвращает условие, по которому будет выполняться функционал слушателя. В моем случае - смена userInSystem с false на true. Это обозначает, что пользователь зашел в админку.
- effect - функция, которая сработает, если predicate вернет true. В самой функции проверяется access токен. Логика обновления находится в функции checkAccessToken. Стоит обратить внимание, что она вызывается два раза. Первый раз - она отрабатывает в ходе выполнения кода, во второй запускается в интервале. Условие в начале effect необходимо для предотвращения запуска нескольких таймеров.
Если перед таймером не запускать checkAccessToken - алгоритм проверки актуальности токена будет работать некорректно.
Разберем слушателя middleware для второго случая, когда пользователь вышел из админки.
На картинке выше:
- predicate содержит в себе условие, которое сработает, когда пользователь вышел из системы и его статус был изменен.
- effect содержит в себе функцию, которая сбросит выполнение функции проверки access токена.
Таким образом, когда пользователь зайдет в админку, то у него сразу будет опрашиваться статус токена. Когда пользователь уходит из админки, функция прекращает опрос.
Разберем слушатель во втором 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 и когда их лучше применять, в очередной раз пересмотрел свои взгляды на разработку в очередной раз. Разработал для себя алгоритм использования искусственного интеллекта в разработке. Всем спасибо, что дочитали. Увидимся в следующих статьях.