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 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
import { createContext, useContext, useState } from 'react'
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
import type { User, AuthSession } from '../types'
|
import type { User, AuthSession } from '../types'
|
||||||
import { api, ApiError } from '../lib/api'
|
import { api, ApiError } from '../lib/api'
|
||||||
|
|
||||||
|
const BASE = (import.meta.env.VITE_API_URL as string | undefined) ?? 'https://api.hotelsync.ru'
|
||||||
|
|
||||||
interface AuthContextValue {
|
interface AuthContextValue {
|
||||||
session: AuthSession | null
|
session: AuthSession | null
|
||||||
user: User | null
|
user: User | null
|
||||||
@@ -11,11 +13,45 @@ interface AuthContextValue {
|
|||||||
|
|
||||||
const AuthContext = createContext<AuthContextValue | null>(null)
|
const AuthContext = createContext<AuthContextValue | null>(null)
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
function loadSession(): AuthSession | null {
|
||||||
const [session, setSession] = useState<AuthSession | null>(() => {
|
try {
|
||||||
const stored = localStorage.getItem('hotelsync-session')
|
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<AuthSession | null>(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<User | null> => {
|
const login = async (email: string, password: string): Promise<User | null> => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user