Fix WS offline: nginx WebSocket headers + keepalive ping + drag-to-move fix
nginx: add /ws location with Upgrade/Connection headers and 1h timeout useHotelSocket: ping every 30s to prevent idle timeout, read fresh token from localStorage on reconnect (handles 1h JWT expiry gracefully) BookingCalendar: use elementFromPoint for reliable drag-to-move target Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,15 @@ interface Options {
|
||||
onMessage: (msg: WsMessage) => void
|
||||
}
|
||||
|
||||
function getFreshToken(): string | null {
|
||||
try {
|
||||
const s = localStorage.getItem('hotelsync-session')
|
||||
return s ? (JSON.parse(s) as { token: string }).token : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function useHotelSocket({ slug, token, onMessage }: Options) {
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
const cbRef = useRef(onMessage)
|
||||
@@ -30,14 +39,32 @@ export function useHotelSocket({ slug, token, onMessage }: Options) {
|
||||
|
||||
let destroyed = false
|
||||
let reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let pingTimer: ReturnType<typeof setInterval> | null = null
|
||||
let attempts = 0
|
||||
|
||||
function startPing(ws: WebSocket) {
|
||||
if (pingTimer) clearInterval(pingTimer)
|
||||
// Send a ping every 30s to keep nginx / proxies from closing idle connection
|
||||
pingTimer = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'ping' }))
|
||||
}
|
||||
}, 30_000)
|
||||
}
|
||||
|
||||
function connect() {
|
||||
if (destroyed) return
|
||||
|
||||
// Always read fresh token so reconnects after 1h token expiry use the refreshed JWT
|
||||
const currentToken = getFreshToken() ?? token
|
||||
if (!currentToken) {
|
||||
console.warn('[WS] no token available')
|
||||
return
|
||||
}
|
||||
|
||||
const base = (import.meta.env.VITE_API_URL as string | undefined) ?? 'https://api.hotelsync.ru'
|
||||
const wsBase = base.replace(/^https/, 'wss').replace(/^http(?!s)/, 'ws')
|
||||
const url = `${wsBase}/ws?hotel=${encodeURIComponent(slug)}&token=${encodeURIComponent(token!)}`
|
||||
const url = `${wsBase}/ws?hotel=${encodeURIComponent(slug)}&token=${encodeURIComponent(currentToken)}`
|
||||
console.log('[WS] connecting to', url.replace(/token=[^&]+/, 'token=***'))
|
||||
|
||||
const ws = new WebSocket(url)
|
||||
@@ -47,11 +74,17 @@ export function useHotelSocket({ slug, token, onMessage }: Options) {
|
||||
if (destroyed) { ws.close(); return }
|
||||
setConnected(true)
|
||||
attempts = 0
|
||||
startPing(ws)
|
||||
console.log('[WS] connected ✓ hotel:', slug)
|
||||
}
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
try { cbRef.current(JSON.parse(e.data as string) as WsMessage) } catch { /* ignore */ }
|
||||
try {
|
||||
const msg = JSON.parse(e.data as string) as WsMessage
|
||||
// ignore pong / ping from server
|
||||
if ((msg as unknown as { type: string }).type === 'pong') return
|
||||
cbRef.current(msg)
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
ws.onerror = (ev) => {
|
||||
@@ -61,6 +94,7 @@ export function useHotelSocket({ slug, token, onMessage }: Options) {
|
||||
ws.onclose = (ev) => {
|
||||
wsRef.current = null
|
||||
setConnected(false)
|
||||
if (pingTimer) { clearInterval(pingTimer); pingTimer = null }
|
||||
if (destroyed) return
|
||||
const delay = Math.min(1000 * 2 ** attempts, 30_000)
|
||||
attempts++
|
||||
@@ -74,6 +108,7 @@ export function useHotelSocket({ slug, token, onMessage }: Options) {
|
||||
return () => {
|
||||
destroyed = true
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer)
|
||||
if (pingTimer) clearInterval(pingTimer)
|
||||
wsRef.current?.close()
|
||||
wsRef.current = null
|
||||
setConnected(false)
|
||||
|
||||
Reference in New Issue
Block a user