diff --git a/src/components/WebSocketConnectionStatus.jsx b/src/components/WebSocketConnectionStatus.jsx
deleted file mode 100644
index cf44aea..0000000
--- a/src/components/WebSocketConnectionStatus.jsx
+++ /dev/null
@@ -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
- if (isConnecting) return
- return
- }
-
- 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 (
-
-
-
- {showError && error && (
-
- {error}
-
- )}
-
-
- )
- }
-
- if (variant === 'chip') {
- return (
-
-
- {getStatusText()}
-
-
- )
- }
-
- // Full variant
- return (
-
-
- {getStatusIcon()}
-
- {getStatusText()}
-
-
- {showError && error && (
-
- {error}
-
- )}
-
- )
-}
-
-export default WebSocketConnectionStatus
diff --git a/src/components/WebSocketSettings.jsx b/src/components/WebSocketSettings.jsx
deleted file mode 100644
index 2c37cfa..0000000
--- a/src/components/WebSocketSettings.jsx
+++ /dev/null
@@ -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 (
-
-
- {isWebSocketEnabled() && isPlusAccount(userProfile) ? (
-
- ) : (
-
- )}
-
-
- Real-time Updates
- {!isPlusAccount(userProfile) && (
-
- Plus Feature
-
- )}
-
-
- Get instant notifications when chores are updated
-
-
- {isWebSocketEnabled() && isPlusAccount(userProfile) && (
-
- )}
-
-
-
-
- Enable Real-time Updates
-
- {getStatusDescription()}
-
-
-
-
-
- {isWebSocketEnabled() && isPlusAccount(userProfile) && (
-
-
- Status:
-
-
- {getConnectionStatus()}
-
- {error && (
-
- {error}
-
- )}
-
- )}
-
- {!isPlusAccount(userProfile) && (
-
- 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.
-
- )}
-
- )
-}
-
-export default WebSocketSettings
diff --git a/src/contexts/Contexts.jsx b/src/contexts/Contexts.jsx
index 542795a..1b42ef1 100644
--- a/src/contexts/Contexts.jsx
+++ b/src/contexts/Contexts.jsx
@@ -4,7 +4,6 @@ import QueryContext from './QueryContext'
import RouterContext from './RouterContext'
import SSEProvider from './SSEContext'
import ThemeContext from './ThemeContext'
-import WebSocketProvider from './WebSocketContext'
const Contexts = ({ children }) => {
const contexts = [
@@ -13,7 +12,6 @@ const Contexts = ({ children }) => {
QueryContext,
NotificationProvider,
SSEProvider,
- WebSocketProvider,
RouterContext,
]
diff --git a/src/contexts/WebSocketContext.jsx b/src/contexts/WebSocketContext.jsx
deleted file mode 100644
index 4a9835e..0000000
--- a/src/contexts/WebSocketContext.jsx
+++ /dev/null
@@ -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 (
-
- {children}
-
- )
-}
-
-export default WebSocketProvider
diff --git a/src/hooks/useWebSocket.js b/src/hooks/useWebSocket.js
deleted file mode 100644
index 2850258..0000000
--- a/src/hooks/useWebSocket.js
+++ /dev/null
@@ -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'
- }
- },
- }
-}