From 1fd12a6dbb0a48b0ae1974860cb06961d711c339 Mon Sep 17 00:00:00 2001 From: HotelSync Date: Tue, 17 Mar 2026 19:19:19 +0300 Subject: [PATCH] Fix null token in session: refresh on mount if token missing If localStorage session has user but no token (corrupted/migrated session), attempt a token refresh via httpOnly cookie. If refresh fails, clear session and force re-login. Prevents WebSocket 'missing token' skip. Co-Authored-By: Claude Sonnet 4.6 --- src/contexts/AuthContext.tsx | 46 ++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index ddab047..3833121 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,7 +1,9 @@ -import { createContext, useContext, useState } from 'react' +import { createContext, useContext, useEffect, useState } from 'react' import type { User, AuthSession } from '../types' import { api, ApiError } from '../lib/api' +const BASE = (import.meta.env.VITE_API_URL as string | undefined) ?? 'https://api.hotelsync.ru' + interface AuthContextValue { session: AuthSession | null user: User | null @@ -11,11 +13,45 @@ interface AuthContextValue { const AuthContext = createContext(null) -export function AuthProvider({ children }: { children: React.ReactNode }) { - const [session, setSession] = useState(() => { +function loadSession(): AuthSession | null { + try { const stored = localStorage.getItem('hotelsync-session') - return stored ? JSON.parse(stored) : null - }) + if (!stored) return null + const parsed = JSON.parse(stored) as AuthSession + // If session is missing token — discard it, we'll refresh below + if (!parsed?.user) { localStorage.removeItem('hotelsync-session'); return null } + return parsed + } catch { + localStorage.removeItem('hotelsync-session') + return null + } +} + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [session, setSession] = useState(loadSession) + + // On mount: if session exists but token is missing/null, try to refresh via cookie + useEffect(() => { + if (!session) return + if (session.token) return // token present, all good + + fetch(`${BASE}/api/auth/refresh`, { method: 'POST', credentials: 'include' }) + .then(r => r.ok ? r.json() : null) + .then((data: { access_token?: string } | null) => { + if (data?.access_token) { + const s: AuthSession = { ...session, token: data.access_token } + setSession(s) + localStorage.setItem('hotelsync-session', JSON.stringify(s)) + } else { + setSession(null) + localStorage.removeItem('hotelsync-session') + } + }) + .catch(() => { + setSession(null) + localStorage.removeItem('hotelsync-session') + }) + }, []) // eslint-disable-line react-hooks/exhaustive-deps const login = async (email: string, password: string): Promise => { try {