ParkTrack — авторизация и истечение сессии
Архитектурная схема (archify) →
Самое интересное здесь — не сама авторизация (обычный Bearer-токен), а то, как истечение сессии одной строкой кода долетает до навигации и переключает весь экран без единого ручного вызова Navigator.push.
Токен на каждый запрос
AuthInterceptor.onRequest читает токен из TokenStorage (защищённое хранилище, flutter_secure_storage) и подставляет Authorization: Bearer $token в заголовки — на уровне Dio-интерцептора, а не в каждом отдельном API-вызове вручную. Ни один экран или репозиторий не думает про токен явно.
Цепочка от 401 до экрана логина
Четыре независимых слоя связаны в одну реакцию, каждый ничего не знает о соседях сверх необходимого:
AuthInterceptor.onError— если сервер вернул 401, интерцептор чистит токены (_tokenStorage.clearTokens()) и ставитsessionExpiredNotifier.value = true.sessionExpiredNotifier— это простойValueNotifier<bool>изflutter/foundation, а не Riverpod-провайдер. Он специально живёт вне Riverpod:AuthInterceptor— обычный Dart-класс без доступа кRef, и именноValueNotifierдаёт дешёвый способ "просигналить наружу" из кода, который в дереве провайдеров не участвует.AuthNotifier(RiverpodStateNotifier) подписывается на этотValueNotifierв конструкторе (addListener) и в колбэке_onSessionExpiredпереводит СВОЁ состояние вAuthState.unauthenticated(). Так сигнал из "внешнего" мира попадает обратно в Riverpod-граф.routerProviderделаетref.watch(authStateProvider)— как только состояние сталоunauthenticated, срабатываетredirect, иgo_routerперекидывает пользователя на/loginс ЛЮБОГО экрана, где бы он ни был в этот момент (карта, поиск, профиль).
AuthInterceptor (401) → ValueNotifier → AuthNotifier.state → routerProvider.redirect → /login
Почему это устроено именно так, а не через прямой Navigator
Если бы AuthInterceptor дёргал Navigator напрямую, пришлось бы протаскивать BuildContext или глобальный NavigatorKey в сетевой слой — смешение ответственности (HTTP-код внутри интерцептора решал бы, КАК именно рисуется навигация). Вместо этого интерцептор просто меняет один bool, а решение "что теперь показать" полностью остаётся на go_router, который и так уже отвечает за то, какой экран показывать при каком состоянии авторизации — единая точка принятия решения о маршруте, а не две (обычный логин/логаут и отдельно "экстренный" выход по 401).
Проверка себя: checkSession() в AuthNotifier защищена флагами _isCheckingSession/_sessionChecked, чтобы повторный вызов (например, при пересборке виджета) не гонял /auth/me заново — вызывается ровно один раз за жизнь провайдера, с таймаутом 8 секунд и явным unauthenticated при любой ошибке или таймауте, а не зависанием в loading.