Files
sbd2-frontend/src/contexts/SocketContext.tsx

140 lines
3.4 KiB
TypeScript

import React, { createContext, useContext, useEffect, useState, useCallback } from 'react'
import { io, Socket } from 'socket.io-client'
import { toast } from 'sonner'
import { useAuth } from './AuthContext'
import { authEvents, AUTH_EVENTS } from '../lib/events'
interface SocketContextType {
socket: Socket | null
isConnected: boolean
connectionError: string | null
isReconnecting: boolean
}
const SocketContext = createContext<SocketContextType | undefined>(undefined)
interface SocketProviderProps {
children: React.ReactNode
}
export function SocketProvider({ children }: SocketProviderProps) {
const { user, loading } = useAuth()
const [socket, setSocket] = useState<Socket | null>(null)
const [isConnected, setIsConnected] = useState(false)
const [connectionError, setConnectionError] = useState<string | null>(null)
const [isReconnecting, setIsReconnecting] = useState(false)
const createSocket = useCallback(() => {
if (!user) return null
const newSocket = io('http://localhost:8000', {
withCredentials: true,
transports: ['polling', 'websocket'],
timeout: 20000,
forceNew: true,
autoConnect: true,
})
newSocket.on('connect', () => {
setIsConnected(true)
setConnectionError(null)
setIsReconnecting(false)
})
newSocket.on('disconnect', () => {
setIsConnected(false)
})
newSocket.on('connect_error', (error) => {
setConnectionError(`Connection failed: ${error.message}`)
setIsConnected(false)
setIsReconnecting(false)
})
// Listen for message events
newSocket.on('user_message', (data) => {
toast.info(`Message from ${data.from_user_name}`, {
description: data.message,
})
})
newSocket.on('broadcast_message', (data) => {
toast.warning(`Broadcast from ${data.from_user_name}`, {
description: data.message,
})
})
return newSocket
}, [user])
// Handle token refresh - reconnect socket with new token
const handleTokenRefresh = useCallback(() => {
if (!user || !socket) return
setIsReconnecting(true)
// Disconnect current socket
socket.disconnect()
// Create new socket with fresh token
const newSocket = createSocket()
if (newSocket) {
setSocket(newSocket)
}
}, [user, socket, createSocket])
// Listen for token refresh events
useEffect(() => {
authEvents.on(AUTH_EVENTS.TOKEN_REFRESHED, handleTokenRefresh)
return () => {
authEvents.off(AUTH_EVENTS.TOKEN_REFRESHED, handleTokenRefresh)
}
}, [handleTokenRefresh])
// Initial socket connection
useEffect(() => {
if (loading) return
if (!user) {
if (socket) {
socket.disconnect()
setSocket(null)
setIsConnected(false)
}
return
}
const newSocket = createSocket()
if (newSocket) {
setSocket(newSocket)
}
return () => {
if (newSocket) {
newSocket.disconnect()
}
}
}, [loading, user, createSocket])
const value: SocketContextType = {
socket,
isConnected,
connectionError,
isReconnecting,
}
return (
<SocketContext.Provider value={value}>
{children}
</SocketContext.Provider>
)
}
export function useSocket() {
const context = useContext(SocketContext)
if (context === undefined) {
throw new Error('useSocket must be used within a SocketProvider')
}
return context
}