Remove Websocket code since we are only using SSE
This commit is contained in:
@@ -1,106 +0,0 @@
|
|||||||
import { Circle, SignalWifi4Bar, SignalWifiOff } from '@mui/icons-material'
|
|
||||||
import { Box, Chip, Tooltip, Typography } from '@mui/joy'
|
|
||||||
import { useWebSocketContext } from '../contexts/WebSocketContext'
|
|
||||||
|
|
||||||
const WebSocketConnectionStatus = ({
|
|
||||||
variant = 'minimal',
|
|
||||||
showError = false,
|
|
||||||
sx = {},
|
|
||||||
}) => {
|
|
||||||
const { isConnected, isConnecting, error, getConnectionStatus } =
|
|
||||||
useWebSocketContext()
|
|
||||||
|
|
||||||
const getStatusColor = () => {
|
|
||||||
if (isConnected) return 'success'
|
|
||||||
if (isConnecting) return 'warning'
|
|
||||||
return 'danger'
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusIcon = () => {
|
|
||||||
if (isConnected) return <SignalWifi4Bar />
|
|
||||||
if (isConnecting) return <Circle />
|
|
||||||
return <SignalWifiOff />
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusText = () => {
|
|
||||||
if (isConnected) return 'Connected'
|
|
||||||
if (isConnecting) return 'Connecting...'
|
|
||||||
return 'Disconnected'
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTooltipText = () => {
|
|
||||||
const status = getConnectionStatus()
|
|
||||||
if (error) return `Real-time updates: ${status} - ${error}`
|
|
||||||
if (!isConnected && !isConnecting) {
|
|
||||||
return `Real-time updates: ${status} - Join a circle to enable real-time updates`
|
|
||||||
}
|
|
||||||
return `Real-time updates: ${status}`
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variant === 'minimal') {
|
|
||||||
return (
|
|
||||||
<Tooltip title={getTooltipText()} size='sm'>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: 0.5,
|
|
||||||
...sx,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Circle
|
|
||||||
sx={{
|
|
||||||
fontSize: 8,
|
|
||||||
color:
|
|
||||||
getStatusColor() === 'success'
|
|
||||||
? 'success.main'
|
|
||||||
: getStatusColor() === 'warning'
|
|
||||||
? 'warning.main'
|
|
||||||
: 'danger.main',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{showError && error && (
|
|
||||||
<Typography level='body-xs' color='danger'>
|
|
||||||
{error}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (variant === 'chip') {
|
|
||||||
return (
|
|
||||||
<Tooltip title={getTooltipText()} size='sm'>
|
|
||||||
<Chip
|
|
||||||
color={getStatusColor()}
|
|
||||||
size='sm'
|
|
||||||
variant='soft'
|
|
||||||
startDecorator={getStatusIcon()}
|
|
||||||
sx={sx}
|
|
||||||
>
|
|
||||||
{getStatusText()}
|
|
||||||
</Chip>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Full variant
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, ...sx }}>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
||||||
{getStatusIcon()}
|
|
||||||
<Typography level='body-sm' color={getStatusColor()}>
|
|
||||||
{getStatusText()}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
{showError && error && (
|
|
||||||
<Typography level='body-xs' color='danger'>
|
|
||||||
{error}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WebSocketConnectionStatus
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
import { Sync, SyncDisabled } from '@mui/icons-material'
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Card,
|
|
||||||
Chip,
|
|
||||||
FormControl,
|
|
||||||
FormHelperText,
|
|
||||||
FormLabel,
|
|
||||||
Switch,
|
|
||||||
Typography,
|
|
||||||
} from '@mui/joy'
|
|
||||||
import { useWebSocketContext } from '../contexts/WebSocketContext'
|
|
||||||
import { useUserProfile } from '../queries/UserQueries'
|
|
||||||
import { isPlusAccount } from '../utils/Helpers'
|
|
||||||
import WebSocketConnectionStatus from './WebSocketConnectionStatus'
|
|
||||||
|
|
||||||
const WebSocketSettings = () => {
|
|
||||||
const { data: userProfile } = useUserProfile()
|
|
||||||
const {
|
|
||||||
isConnected,
|
|
||||||
isConnecting,
|
|
||||||
error,
|
|
||||||
getConnectionStatus,
|
|
||||||
toggleWebSocketEnabled,
|
|
||||||
isWebSocketEnabled,
|
|
||||||
} = useWebSocketContext()
|
|
||||||
|
|
||||||
const handleToggle = () => {
|
|
||||||
if (!isPlusAccount(userProfile)) {
|
|
||||||
return // Don't allow toggle for non-Plus users
|
|
||||||
}
|
|
||||||
const currentlyEnabled = isWebSocketEnabled()
|
|
||||||
toggleWebSocketEnabled(!currentlyEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusDescription = () => {
|
|
||||||
if (!isPlusAccount(userProfile)) {
|
|
||||||
return 'Real-time updates are not available in the Basic plan. Upgrade to Plus to receive instant notifications when chores are updated.'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isWebSocketEnabled()) {
|
|
||||||
return 'Real-time updates are disabled. Enable to see live changes when you or other circle members complete, skip, or modify chores.'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isConnected) {
|
|
||||||
return "Real-time updates are working. You'll see live changes when you or other circle members complete, skip, or modify chores."
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isConnecting) {
|
|
||||||
return 'Connecting to real-time updates...'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return `Real-time updates are enabled but not working: ${error}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Real-time updates are enabled but not currently connected.'
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card sx={{ mt: 2, p: 3 }}>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
|
|
||||||
{isWebSocketEnabled() && isPlusAccount(userProfile) ? (
|
|
||||||
<Sync color={isConnected ? 'success' : 'disabled'} />
|
|
||||||
) : (
|
|
||||||
<SyncDisabled color='disabled' />
|
|
||||||
)}
|
|
||||||
<Box sx={{ flex: 1 }}>
|
|
||||||
<Typography level='title-md'>
|
|
||||||
Real-time Updates
|
|
||||||
{!isPlusAccount(userProfile) && (
|
|
||||||
<Chip variant='soft' color='warning' sx={{ ml: 1 }}>
|
|
||||||
Plus Feature
|
|
||||||
</Chip>
|
|
||||||
)}
|
|
||||||
</Typography>
|
|
||||||
<Typography level='body-sm' color='neutral'>
|
|
||||||
Get instant notifications when chores are updated
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
{isWebSocketEnabled() && isPlusAccount(userProfile) && (
|
|
||||||
<WebSocketConnectionStatus variant='chip' />
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FormControl orientation='horizontal' sx={{ mb: 2 }}>
|
|
||||||
<Box sx={{ flex: 1 }}>
|
|
||||||
<FormLabel>Enable Real-time Updates</FormLabel>
|
|
||||||
<FormHelperText sx={{ mt: 0 }}>
|
|
||||||
{getStatusDescription()}
|
|
||||||
</FormHelperText>
|
|
||||||
</Box>
|
|
||||||
<Switch
|
|
||||||
checked={Boolean(isWebSocketEnabled() && isPlusAccount(userProfile))}
|
|
||||||
onChange={handleToggle}
|
|
||||||
disabled={!isPlusAccount(userProfile)}
|
|
||||||
color={
|
|
||||||
isWebSocketEnabled() && isPlusAccount(userProfile)
|
|
||||||
? 'success'
|
|
||||||
: 'neutral'
|
|
||||||
}
|
|
||||||
variant='solid'
|
|
||||||
endDecorator={
|
|
||||||
isWebSocketEnabled() && isPlusAccount(userProfile) ? 'On' : 'Off'
|
|
||||||
}
|
|
||||||
slotProps={{ endDecorator: { sx: { minWidth: 24 } } }}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
{isWebSocketEnabled() && isPlusAccount(userProfile) && (
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 1 }}>
|
|
||||||
<Typography level='body-xs' color='neutral'>
|
|
||||||
Status:
|
|
||||||
</Typography>
|
|
||||||
<Chip
|
|
||||||
size='sm'
|
|
||||||
variant='soft'
|
|
||||||
color={
|
|
||||||
isConnected ? 'success' : isConnecting ? 'warning' : 'danger'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{getConnectionStatus()}
|
|
||||||
</Chip>
|
|
||||||
{error && (
|
|
||||||
<Typography level='body-xs' color='danger'>
|
|
||||||
{error}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isPlusAccount(userProfile) && (
|
|
||||||
<Typography level='body-sm' color='warning' sx={{ mt: 1 }}>
|
|
||||||
Real-time updates are not available in the Basic plan. Upgrade to Plus
|
|
||||||
to receive instant notifications when you or other circle members
|
|
||||||
complete, skip, or modify chores.
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WebSocketSettings
|
|
||||||
@@ -4,7 +4,6 @@ import QueryContext from './QueryContext'
|
|||||||
import RouterContext from './RouterContext'
|
import RouterContext from './RouterContext'
|
||||||
import SSEProvider from './SSEContext'
|
import SSEProvider from './SSEContext'
|
||||||
import ThemeContext from './ThemeContext'
|
import ThemeContext from './ThemeContext'
|
||||||
import WebSocketProvider from './WebSocketContext'
|
|
||||||
|
|
||||||
const Contexts = ({ children }) => {
|
const Contexts = ({ children }) => {
|
||||||
const contexts = [
|
const contexts = [
|
||||||
@@ -13,7 +12,6 @@ const Contexts = ({ children }) => {
|
|||||||
QueryContext,
|
QueryContext,
|
||||||
NotificationProvider,
|
NotificationProvider,
|
||||||
SSEProvider,
|
SSEProvider,
|
||||||
WebSocketProvider,
|
|
||||||
RouterContext,
|
RouterContext,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { createContext, useContext } from 'react'
|
|
||||||
import { useWebSocket } from '../hooks/useWebSocket'
|
|
||||||
|
|
||||||
const WebSocketContext = createContext({
|
|
||||||
connectionState: 3, // CLOSED
|
|
||||||
isConnected: false,
|
|
||||||
isConnecting: false,
|
|
||||||
lastEvent: null,
|
|
||||||
error: null,
|
|
||||||
connect: () => {},
|
|
||||||
disconnect: () => {},
|
|
||||||
getConnectionStatus: () => 'disconnected',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const useWebSocketContext = () => {
|
|
||||||
return useContext(WebSocketContext)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WebSocketProvider = ({ children }) => {
|
|
||||||
const webSocketState = useWebSocket()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WebSocketContext.Provider value={webSocketState}>
|
|
||||||
{children}
|
|
||||||
</WebSocketContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WebSocketProvider
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
import { useQueryClient } from '@tanstack/react-query'
|
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
||||||
import { apiManager, isTokenValid } from '../utils/TokenManager'
|
|
||||||
|
|
||||||
const WEBSOCKET_STATES = {
|
|
||||||
CONNECTING: 0,
|
|
||||||
OPEN: 1,
|
|
||||||
CLOSING: 2,
|
|
||||||
CLOSED: 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
const RECONNECT_INTERVALS = [1000, 2000, 5000, 10000, 30000] // Progressive backoff
|
|
||||||
|
|
||||||
export const useWebSocket = () => {
|
|
||||||
const [connectionState, setConnectionState] = useState(
|
|
||||||
WEBSOCKET_STATES.CLOSED,
|
|
||||||
)
|
|
||||||
const [lastEvent, setLastEvent] = useState(null)
|
|
||||||
const [error, setError] = useState(null)
|
|
||||||
|
|
||||||
const wsRef = useRef(null)
|
|
||||||
const reconnectTimeoutRef = useRef(null)
|
|
||||||
const reconnectAttemptsRef = useRef(0)
|
|
||||||
const isManuallyClosedRef = useRef(false)
|
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
|
|
||||||
const getWebSocketUrl = useCallback(() => {
|
|
||||||
const token = localStorage.getItem('ca_token')
|
|
||||||
if (!token || !isTokenValid()) {
|
|
||||||
console.log('WebSocket: No valid authentication token')
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiUrl = apiManager.getApiURL()
|
|
||||||
|
|
||||||
// Convert HTTP/HTTPS to WebSocket protocol and remove /api/v1 suffix
|
|
||||||
let wsUrl = apiUrl.replace(/\/api\/v1$/, '')
|
|
||||||
if (wsUrl.startsWith('http://')) {
|
|
||||||
wsUrl = wsUrl.replace('http://', 'ws://')
|
|
||||||
} else if (wsUrl.startsWith('https://')) {
|
|
||||||
wsUrl = wsUrl.replace('https://', 'wss://')
|
|
||||||
} else {
|
|
||||||
const isHttps = window.location.protocol === 'https:'
|
|
||||||
wsUrl = `${isHttps ? 'wss:' : 'ws:'}//${wsUrl}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let backend determine circle from authenticated user
|
|
||||||
wsUrl = `${wsUrl}/api/v1/realtime/ws?token=${token}`
|
|
||||||
|
|
||||||
return wsUrl
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleWebSocketMessage = useCallback(
|
|
||||||
event => {
|
|
||||||
try {
|
|
||||||
const eventData = JSON.parse(event.data)
|
|
||||||
setLastEvent(eventData)
|
|
||||||
|
|
||||||
console.debug('WebSocket event received:', eventData.type, eventData)
|
|
||||||
|
|
||||||
// Handle different event types and update React Query cache accordingly
|
|
||||||
switch (eventData.type) {
|
|
||||||
case 'chore.created':
|
|
||||||
case 'chore.updated':
|
|
||||||
case 'chore.completed':
|
|
||||||
case 'chore.skipped':
|
|
||||||
case 'chore.deleted':
|
|
||||||
// Invalidate chores queries to refetch data
|
|
||||||
queryClient.invalidateQueries(['chores'])
|
|
||||||
|
|
||||||
// If it's a specific chore event, also invalidate that chore's details
|
|
||||||
if (eventData.data.chore?.id) {
|
|
||||||
queryClient.invalidateQueries(['chore', eventData.data.chore.id])
|
|
||||||
queryClient.invalidateQueries([
|
|
||||||
'choreDetails',
|
|
||||||
eventData.data.chore.id,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
// expire the history so feed on dashboard gert updated :
|
|
||||||
// need to find a better way to do this as we don't need to do it with every single update for anything
|
|
||||||
// but not sure if i can do it with the fall-through switch case in javascript :)
|
|
||||||
queryClient.invalidateQueries(['choresHistory', 7])
|
|
||||||
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'subtask.updated':
|
|
||||||
case 'subtask.completed':
|
|
||||||
// Invalidate the specific chore that contains this subtask
|
|
||||||
if (eventData.data.choreId) {
|
|
||||||
queryClient.invalidateQueries(['chore', eventData.data.choreId])
|
|
||||||
queryClient.invalidateQueries([
|
|
||||||
'choreDetails',
|
|
||||||
eventData.data.choreId,
|
|
||||||
])
|
|
||||||
}
|
|
||||||
// Also invalidate general chores list
|
|
||||||
queryClient.invalidateQueries(['chores'])
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'heartbeat':
|
|
||||||
// Heartbeat events don't need cache invalidation
|
|
||||||
console.debug('Heartbeat!')
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'connection.established':
|
|
||||||
console.log('WebSocket connection established')
|
|
||||||
setError(null)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'error':
|
|
||||||
console.error('WebSocket error event:', eventData.data)
|
|
||||||
setError(eventData.data.message || 'WebSocket error occurred')
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.log('Unknown WebSocket event type:', eventData.type)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to parse WebSocket message:', err)
|
|
||||||
setError('Failed to parse server message')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[queryClient],
|
|
||||||
)
|
|
||||||
|
|
||||||
const createWebSocketConnection = useCallback(
|
|
||||||
wsUrl => {
|
|
||||||
try {
|
|
||||||
setConnectionState(WEBSOCKET_STATES.CONNECTING)
|
|
||||||
isManuallyClosedRef.current = false
|
|
||||||
|
|
||||||
// Use query parameter authentication (token already included in URL)
|
|
||||||
wsRef.current = new WebSocket(wsUrl)
|
|
||||||
|
|
||||||
wsRef.current.onopen = () => {
|
|
||||||
setConnectionState(WEBSOCKET_STATES.OPEN)
|
|
||||||
setError(null)
|
|
||||||
reconnectAttemptsRef.current = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
wsRef.current.onmessage = handleWebSocketMessage
|
|
||||||
|
|
||||||
wsRef.current.onerror = error => {
|
|
||||||
console.error('WebSocket error:', error)
|
|
||||||
setError('Connection error occurred')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to create WebSocket connection:', err)
|
|
||||||
setError('Failed to establish connection')
|
|
||||||
setConnectionState(WEBSOCKET_STATES.CLOSED)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[handleWebSocketMessage],
|
|
||||||
)
|
|
||||||
|
|
||||||
const scheduleReconnect = useCallback(() => {
|
|
||||||
if (reconnectTimeoutRef.current) {
|
|
||||||
clearTimeout(reconnectTimeoutRef.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
const attemptIndex = Math.min(
|
|
||||||
reconnectAttemptsRef.current,
|
|
||||||
RECONNECT_INTERVALS.length - 1,
|
|
||||||
)
|
|
||||||
const delay = RECONNECT_INTERVALS[attemptIndex]
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Scheduling WebSocket reconnect in ${delay}ms (attempt ${reconnectAttemptsRef.current + 1})`,
|
|
||||||
)
|
|
||||||
|
|
||||||
reconnectTimeoutRef.current = setTimeout(() => {
|
|
||||||
reconnectAttemptsRef.current++
|
|
||||||
// Trigger reconnection
|
|
||||||
const wsUrl = getWebSocketUrl()
|
|
||||||
if (wsUrl && wsRef.current?.readyState !== WEBSOCKET_STATES.OPEN) {
|
|
||||||
createWebSocketConnection(wsUrl)
|
|
||||||
}
|
|
||||||
}, delay)
|
|
||||||
}, [getWebSocketUrl, createWebSocketConnection])
|
|
||||||
|
|
||||||
// Set up the onclose handler separately to avoid circular dependency
|
|
||||||
useEffect(() => {
|
|
||||||
if (wsRef.current) {
|
|
||||||
wsRef.current.onclose = event => {
|
|
||||||
console.log('WebSocket connection closed:', event.code, event.reason)
|
|
||||||
setConnectionState(WEBSOCKET_STATES.CLOSED)
|
|
||||||
|
|
||||||
// Handle different close codes
|
|
||||||
if (event.code === 4000) {
|
|
||||||
setError('Authentication failed - please refresh the page')
|
|
||||||
return // Don't attempt to reconnect for auth failures
|
|
||||||
} else if (event.code === 4001) {
|
|
||||||
setError('Authorization failed - check circle access')
|
|
||||||
return // Don't attempt to reconnect for auth failures
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to reconnect if not manually closed
|
|
||||||
if (!isManuallyClosedRef.current && event.code !== 1000) {
|
|
||||||
scheduleReconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [scheduleReconnect])
|
|
||||||
|
|
||||||
const connect = useCallback(() => {
|
|
||||||
if (wsRef.current?.readyState === WEBSOCKET_STATES.OPEN) {
|
|
||||||
console.log('WebSocket: Already connected')
|
|
||||||
return // Already connected
|
|
||||||
}
|
|
||||||
|
|
||||||
const wsUrl = getWebSocketUrl()
|
|
||||||
console.log('WebSocket connect - URL:', wsUrl)
|
|
||||||
|
|
||||||
if (!wsUrl) {
|
|
||||||
console.log(
|
|
||||||
'Cannot connect to WebSocket: missing URL, token, or user profile',
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
createWebSocketConnection(wsUrl)
|
|
||||||
}, [getWebSocketUrl, createWebSocketConnection])
|
|
||||||
|
|
||||||
const disconnect = useCallback(() => {
|
|
||||||
isManuallyClosedRef.current = true
|
|
||||||
|
|
||||||
if (reconnectTimeoutRef.current) {
|
|
||||||
clearTimeout(reconnectTimeoutRef.current)
|
|
||||||
reconnectTimeoutRef.current = null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wsRef.current) {
|
|
||||||
wsRef.current.close(1000, 'Manual disconnect')
|
|
||||||
wsRef.current = null
|
|
||||||
}
|
|
||||||
|
|
||||||
setConnectionState(WEBSOCKET_STATES.CLOSED)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const toggleWebSocketEnabled = useCallback(
|
|
||||||
enabled => {
|
|
||||||
localStorage.setItem('websocket_enabled', enabled.toString())
|
|
||||||
if (enabled && isTokenValid()) {
|
|
||||||
connect()
|
|
||||||
} else {
|
|
||||||
disconnect()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[connect, disconnect],
|
|
||||||
)
|
|
||||||
|
|
||||||
const isWebSocketEnabled = useCallback(() => {
|
|
||||||
return localStorage.getItem('websocket_enabled') !== 'false'
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Auto-connect when WebSocket is enabled and token is valid
|
|
||||||
useEffect(() => {
|
|
||||||
// Check if WebSocket is enabled in settings
|
|
||||||
const isWebSocketEnabledSetting =
|
|
||||||
localStorage.getItem('websocket_enabled') !== 'false'
|
|
||||||
console.log('WebSocket enabled in settings:', isWebSocketEnabledSetting)
|
|
||||||
|
|
||||||
if (isTokenValid() && isWebSocketEnabledSetting) {
|
|
||||||
console.log('WebSocket: Conditions met, attempting to connect')
|
|
||||||
connect()
|
|
||||||
} else {
|
|
||||||
console.log('WebSocket: Conditions not met, disconnecting')
|
|
||||||
disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
return () => {
|
|
||||||
disconnect()
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []) // Only run once on mount
|
|
||||||
|
|
||||||
// Cleanup timeouts on unmount
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (reconnectTimeoutRef.current) {
|
|
||||||
clearTimeout(reconnectTimeoutRef.current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return {
|
|
||||||
connectionState,
|
|
||||||
isConnected: connectionState === WEBSOCKET_STATES.OPEN,
|
|
||||||
isConnecting: connectionState === WEBSOCKET_STATES.CONNECTING,
|
|
||||||
lastEvent,
|
|
||||||
error,
|
|
||||||
connect,
|
|
||||||
disconnect,
|
|
||||||
toggleWebSocketEnabled,
|
|
||||||
isWebSocketEnabled,
|
|
||||||
// Helper function to check connection status
|
|
||||||
getConnectionStatus: () => {
|
|
||||||
switch (connectionState) {
|
|
||||||
case WEBSOCKET_STATES.CONNECTING:
|
|
||||||
return 'connecting'
|
|
||||||
case WEBSOCKET_STATES.OPEN:
|
|
||||||
return 'connected'
|
|
||||||
case WEBSOCKET_STATES.CLOSING:
|
|
||||||
return 'disconnecting'
|
|
||||||
case WEBSOCKET_STATES.CLOSED:
|
|
||||||
default:
|
|
||||||
return 'disconnected'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user