18 KiB
🔐 ПЛАН БЕЗОПАСНОЙ МИГРАЦИИ НА AuthContext
Дата создания: 2025-09-18
Автор: Claude AI + Вероника Смирнова
Статус: Готов к реализации
📊 Результаты глубокой диагностики
Анализ текущей реализации useAuth
Размер и сложность:
- 657 строк кода
- 65 файлов используют useAuth()
- Сложная логика с rollback механизмами
- Интеграция с Apollo Client через localStorage
Ключевые компоненты:
-
State Management
user: User | null
- данные пользователяisAuthenticated: boolean
- статус авторизацииisLoading: boolean
- индикатор загрузкиisCheckingAuth: boolean
- защита от дублирования
-
Методы авторизации
sendSmsCode
- отправка SMSverifySmsCode
- проверка кодаcheckAuth
- проверка текущей сессииlogout
- выход
-
Методы регистрации
registerFulfillmentOrganization
registerSellerOrganization
registerOrganization
(универсальный)
-
Интеграции
- Apollo Client для GraphQL
- localStorage для токенов
- refreshApolloClient для синхронизации
Выявленные проблемы
-
Множественные экземпляры состояния
AppShell → useAuth() → useState (копия 1) Sidebar → useAuth() → useState (копия 2) Component → useAuth() → useState (копия 3)
-
Race conditions
- checkAuth вызывается параллельно из разных компонентов
- isCheckingAuth защищает только локальный экземпляр
-
Отсутствие синхронизации
- Обновления в одном компоненте не видны в других
- GET_ME выполняется многократно
-
Проблемы с SSR
- Прямое обращение к localStorage
- window checks разбросаны по коду
🎯 Архитектура решения
Новая структура с AuthContext
AuthProvider (глобальное состояние)
├── Apollo Provider
│ └── Auth Link (токены из контекста)
├── State Management
│ ├── user
│ ├── isAuthenticated
│ └── isLoading
└── Methods
├── Authentication
├── Registration
└── Session Management
Преимущества
- Единое состояние - все компоненты видят одни данные
- Оптимизация запросов - GET_ME выполняется 1 раз
- Синхронизация - изменения видны везде мгновенно
- SSR совместимость - централизованные проверки
- Типобезопасность - строгая типизация контекста
📋 Поэтапный план миграции
ЭТАП 0: Подготовка [30 мин]
Цель: Создать безопасную среду для миграции
-
Создать backup текущего состояния
git add . git commit -m "backup: перед миграцией на AuthContext" git branch backup-before-auth-context
-
Создать feature branch
git checkout -b feature/auth-context-migration
-
Подготовить структуру папок
src/ ├── contexts/ │ └── auth/ │ ├── AuthContext.tsx # Основной контекст │ ├── AuthProvider.tsx # Provider компонент │ ├── types.ts # TypeScript типы │ └── utils.ts # Вспомогательные функции
ЭТАП 1: Создание AuthContext с минимальной функциональностью [1 час]
Цель: Создать работающий контекст без нарушения существующего функционала
-
Создать типы (
src/contexts/auth/types.ts
)export interface User { id: string phone: string avatar?: string managerName?: string organization?: Organization } export interface Organization { id: string inn: string type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE' // ... остальные поля } export interface AuthState { user: User | null isAuthenticated: boolean isLoading: boolean } export interface AuthContextType extends AuthState { // Методы будем добавлять постепенно checkAuth: () => Promise<void> logout: () => void }
-
Создать контекст (
src/contexts/auth/AuthContext.tsx
)import { createContext } from 'react' import type { AuthContextType } from './types' export const AuthContext = createContext<AuthContextType | null>(null)
-
Создать базовый Provider (
src/contexts/auth/AuthProvider.tsx
)import { useState, useCallback, useEffect } from 'react' import { AuthContext } from './AuthContext' import { getAuthToken, removeAuthToken } from '@/lib/apollo-client' export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState<User | null>(null) const [isAuthenticated, setIsAuthenticated] = useState(false) const [isLoading, setIsLoading] = useState(true) // Минимальная реализация checkAuth const checkAuth = useCallback(async () => { const token = getAuthToken() if (!token) { setIsAuthenticated(false) setUser(null) setIsLoading(false) return } // TODO: Добавить GET_ME запрос setIsLoading(false) }, []) // Минимальная реализация logout const logout = useCallback(() => { removeAuthToken() setUser(null) setIsAuthenticated(false) window.location.href = '/' }, []) useEffect(() => { checkAuth() }, [checkAuth]) const value = { user, isAuthenticated, isLoading, checkAuth, logout } return ( <AuthContext.Provider value={value}> {children} </AuthContext.Provider> ) }
-
Создать временный хук-обертку (
src/hooks/useAuth.ts
)// В начале файла добавляем import { useContext } from 'react' import { AuthContext } from '@/contexts/auth/AuthContext' // Временный флаг для постепенной миграции const USE_AUTH_CONTEXT = false export const useAuth = (): UseAuthReturn => { if (USE_AUTH_CONTEXT) { const context = useContext(AuthContext) if (!context) { throw new Error('useAuth must be used within AuthProvider') } // Адаптер для совместимости API return { ...context, // Методы-заглушки для совместимости sendSmsCode: async () => ({ success: false, message: 'Not implemented' }), verifySmsCode: async () => ({ success: false, message: 'Not implemented' }), registerFulfillmentOrganization: async () => ({ success: false, message: 'Not implemented' }), registerSellerOrganization: async () => ({ success: false, message: 'Not implemented' }), registerOrganization: async () => ({ success: false, message: 'Not implemented' }), updateUser: () => {} } } // Существующая реализация остается без изменений // ... весь текущий код }
ЭТАП 2: Тестирование базовой интеграции [30 мин]
Цель: Убедиться что ничего не сломалось
-
Добавить AuthProvider в providers.tsx
import { AuthProvider } from '@/contexts/auth/AuthProvider' export function Providers({ children }: { children: React.ReactNode }) { return ( <ApolloProvider client={apolloClient}> <AuthProvider> <SidebarProvider> {children} </SidebarProvider> </AuthProvider> </ApolloProvider> ) }
-
Включить USE_AUTH_CONTEXT для одного компонента
- Начать с простого компонента (например, UserProfile в sidebar)
- Проверить что компонент рендерится
- Проверить что нет ошибок в консоли
-
Rollback план
- Если есть ошибки - установить USE_AUTH_CONTEXT = false
- Исправить проблемы
- Повторить тестирование
ЭТАП 3: Миграция основного функционала [2 часа]
Цель: Перенести всю логику в AuthContext
-
Перенести checkAuth с GET_ME
const checkAuth = useCallback(async () => { if (isCheckingAuth.current) return const token = getAuthToken() if (!token) { setIsAuthenticated(false) setUser(null) setIsLoading(false) return } isCheckingAuth.current = true setIsLoading(true) try { const { data } = await apolloClient.query({ query: GET_ME, errorPolicy: 'all', fetchPolicy: 'network-only' }) if (data?.me) { setUser(data.me) setIsAuthenticated(true) setUserData(data.me) } } catch (error) { // Обработка ошибок } finally { isCheckingAuth.current = false setIsLoading(false) } }, [])
-
Перенести SMS методы
- sendSmsCode
- verifySmsCode
- Сохранить всю логику с логированием
-
Перенести методы регистрации
- registerFulfillmentOrganization
- registerSellerOrganization
- registerOrganization
- Сохранить rollback механизмы
-
Добавить updateUser
const updateUser = useCallback((updatedUser: Partial<User>) => { setUser(current => { if (!current) return current const updated = { ...current, ...updatedUser } setUserData(updated) // Синхронизация с localStorage return updated }) }, [])
ЭТАП 4: Постепенная миграция компонентов [1 день]
Цель: Безопасно перевести все компоненты на новую систему
-
Приоритетные компоненты (первая очередь)
- AppShell
- Sidebar и все его варианты
- AuthGuard
-
Критические компоненты (вторая очередь)
- Страницы авторизации (login, register)
- DashboardHome
- useRoleGuard
-
Остальные компоненты (третья очередь)
- Разбить на группы по 10-15 файлов
- Мигрировать группами
- Тестировать после каждой группы
Процесс для каждого компонента:
- Включить USE_AUTH_CONTEXT локально
- Проверить функциональность
- Если работает - коммит
- Если нет - откат и исправление
ЭТАП 5: Оптимизация и очистка [1 час]
Цель: Удалить старый код и оптимизировать
-
Удалить старую реализацию из useAuth
- Оставить только обертку для контекста
- Удалить локальные useState
- Удалить дублированную логику
-
Оптимизировать рендеринг
// Мемоизация значения контекста const value = useMemo(() => ({ user, isAuthenticated, isLoading, checkAuth, logout, // ... другие методы }), [user, isAuthenticated, isLoading])
-
Добавить DevTools
if (process.env.NODE_ENV === 'development') { (window as any).__AUTH_STATE__ = { user, isAuthenticated } }
ЭТАП 6: Финальное тестирование [1 час]
Цель: Убедиться что все работает
-
Функциональные тесты
- Авторизация по SMS
- Регистрация всех типов организаций
- Отображение sidebar
- Переходы между страницами
- Выход из системы
- Обновление страницы (F5)
-
Тесты производительности
- Нет множественных GET_ME запросов
- Нет лишних ре-рендеров
- Быстрая загрузка после F5
-
Регрессионные тесты
- Все 65 компонентов работают
- API ключи сохраняются
- Роутинг по типам организаций
🚨 Риски и митигация
Риск 1: Поломка авторизации
Митигация:
- Постепенная миграция через флаг USE_AUTH_CONTEXT
- Возможность быстрого отката
- Тестирование на каждом этапе
Риск 2: Потеря состояния
Митигация:
- Сохранение в localStorage остается
- Rollback механизмы сохраняются
- Логирование всех изменений
Риск 3: Проблемы с SSR
Митигация:
- Все проверки window в одном месте
- useEffect для клиентских операций
- Правильная инициализация состояния
Риск 4: Race conditions
Митигация:
- useRef для флагов загрузки
- Отмена дублированных запросов
- Правильная очередность операций
📊 Метрики успеха
-
Функциональность
- ✅ Все 65 компонентов работают
- ✅ Sidebar отображается корректно
- ✅ Авторизация стабильна
-
Производительность
- ✅ GET_ME вызывается 1 раз
- ✅ Нет задержек при навигации
- ✅ Быстрая загрузка после F5
-
Качество кода
- ✅ Единое место управления состоянием
- ✅ Типобезопасность
- ✅ Отсутствие дублирования
🔄 Rollback план
Если что-то пойдет не так на любом этапе:
-
Быстрый откат
git checkout backup-before-auth-context
-
Частичный откат
- Установить USE_AUTH_CONTEXT = false
- Вернуть проблемные компоненты на старую версию
- Исправить проблемы в изолированной ветке
-
Восстановление данных
- localStorage сохраняется
- Токены остаются валидными
- Пользователи не заметят проблем
📝 Чек-лист готовности
Перед началом миграции убедитесь:
- Создан backup текущего состояния
- Команда предупреждена о работах
- Подготовлен план коммуникации при проблемах
- Есть доступ к логам и мониторингу
- Определено время для миграции (лучше в период низкой активности)
🎯 Ожидаемый результат
После завершения миграции:
-
Немедленные улучшения
- Sidebar работает стабильно
- Состояние синхронизировано между компонентами
- Уменьшено количество запросов к API
-
Долгосрочные преимущества
- Готовность к добавлению новых функций
- Упрощенная отладка
- Лучшая производительность
- Возможность добавления продвинутых функций (персистентность, refresh tokens)
Документ будет обновляться по ходу выполнения миграции