mirror of
https://github.com/coleam00/Archon.git
synced 2026-01-01 12:18:41 -05:00
199 lines
6.8 KiB
TypeScript
199 lines
6.8 KiB
TypeScript
/**
|
|
* Task Socket Hook - Simplified real-time task synchronization
|
|
*
|
|
* This hook provides a clean interface to the task socket service,
|
|
* replacing the complex useOptimisticUpdates pattern with a simpler
|
|
* approach that avoids conflicts and connection issues.
|
|
*/
|
|
|
|
import { useEffect, useRef, useCallback, useState } from 'react';
|
|
import { taskSocketService, TaskSocketEvents } from '../services/taskSocketService';
|
|
import { WebSocketState } from '../services/socketIOService';
|
|
|
|
export interface UseTaskSocketOptions {
|
|
projectId: string;
|
|
onTaskCreated?: (task: any) => void;
|
|
onTaskUpdated?: (task: any) => void;
|
|
onTaskDeleted?: (task: any) => void;
|
|
onTaskArchived?: (task: any) => void;
|
|
onTasksReordered?: (data: any) => void;
|
|
onInitialTasks?: (tasks: any[]) => void;
|
|
onConnectionStateChange?: (state: WebSocketState) => void;
|
|
}
|
|
|
|
export function useTaskSocket(options: UseTaskSocketOptions) {
|
|
const {
|
|
projectId,
|
|
onTaskCreated,
|
|
onTaskUpdated,
|
|
onTaskDeleted,
|
|
onTaskArchived,
|
|
onTasksReordered,
|
|
onInitialTasks,
|
|
onConnectionStateChange
|
|
} = options;
|
|
|
|
const componentIdRef = useRef<string>(`task-socket-${Math.random().toString(36).substring(7)}`);
|
|
const currentProjectIdRef = useRef<string | null>(null);
|
|
const isInitializedRef = useRef<boolean>(false);
|
|
|
|
// Add reactive state for connection status
|
|
const [isConnected, setIsConnected] = useState<boolean>(false);
|
|
const [connectionState, setConnectionState] = useState<WebSocketState>(WebSocketState.DISCONNECTED);
|
|
|
|
// Memoized handlers to prevent unnecessary re-registrations
|
|
const memoizedHandlers = useCallback((): Partial<TaskSocketEvents> => {
|
|
return {
|
|
onTaskCreated,
|
|
onTaskUpdated,
|
|
onTaskDeleted,
|
|
onTaskArchived,
|
|
onTasksReordered,
|
|
onInitialTasks,
|
|
onConnectionStateChange
|
|
};
|
|
}, [
|
|
onTaskCreated,
|
|
onTaskUpdated,
|
|
onTaskDeleted,
|
|
onTaskArchived,
|
|
onTasksReordered,
|
|
onInitialTasks,
|
|
onConnectionStateChange
|
|
]);
|
|
|
|
// Subscribe to connection state changes
|
|
useEffect(() => {
|
|
const checkConnection = () => {
|
|
const connected = taskSocketService.isConnected();
|
|
const state = taskSocketService.getConnectionState();
|
|
setIsConnected(connected);
|
|
setConnectionState(state);
|
|
};
|
|
|
|
// Check initial state
|
|
checkConnection();
|
|
|
|
// Poll for connection state changes (since the service doesn't expose event emitters)
|
|
const interval = setInterval(checkConnection, 500);
|
|
|
|
// Also trigger when connection state handler is called
|
|
const wrappedOnConnectionStateChange = onConnectionStateChange ? (state: WebSocketState) => {
|
|
setConnectionState(state);
|
|
setIsConnected(state === WebSocketState.CONNECTED);
|
|
onConnectionStateChange(state);
|
|
} : (state: WebSocketState) => {
|
|
setConnectionState(state);
|
|
setIsConnected(state === WebSocketState.CONNECTED);
|
|
};
|
|
|
|
// Update the handler
|
|
if (componentIdRef.current && taskSocketService) {
|
|
taskSocketService.registerHandlers(componentIdRef.current, {
|
|
...memoizedHandlers(),
|
|
onConnectionStateChange: wrappedOnConnectionStateChange
|
|
});
|
|
}
|
|
|
|
return () => {
|
|
clearInterval(interval);
|
|
};
|
|
}, [onConnectionStateChange, memoizedHandlers]);
|
|
|
|
// Initialize connection once and register handlers
|
|
useEffect(() => {
|
|
if (!projectId || isInitializedRef.current) return;
|
|
|
|
const initializeConnection = async () => {
|
|
try {
|
|
console.log(`[USE_TASK_SOCKET] Initializing connection for project: ${projectId}`);
|
|
setConnectionState(WebSocketState.CONNECTING);
|
|
|
|
// Register handlers first
|
|
taskSocketService.registerHandlers(componentIdRef.current, memoizedHandlers());
|
|
|
|
// Connect to project (singleton service will handle deduplication)
|
|
await taskSocketService.connectToProject(projectId);
|
|
|
|
currentProjectIdRef.current = projectId;
|
|
isInitializedRef.current = true;
|
|
console.log(`[USE_TASK_SOCKET] Successfully initialized for project: ${projectId}`);
|
|
|
|
// Update connection state after successful connection
|
|
setIsConnected(taskSocketService.isConnected());
|
|
setConnectionState(taskSocketService.getConnectionState());
|
|
|
|
} catch (error) {
|
|
console.error(`[USE_TASK_SOCKET] Failed to initialize for project ${projectId}:`, error);
|
|
setConnectionState(WebSocketState.DISCONNECTED);
|
|
setIsConnected(false);
|
|
}
|
|
};
|
|
|
|
initializeConnection();
|
|
|
|
}, [projectId, memoizedHandlers]);
|
|
|
|
// Update handlers when they change (without reconnecting)
|
|
useEffect(() => {
|
|
if (isInitializedRef.current && currentProjectIdRef.current === projectId) {
|
|
console.log(`[USE_TASK_SOCKET] Updating handlers for component: ${componentIdRef.current}`);
|
|
taskSocketService.registerHandlers(componentIdRef.current, memoizedHandlers());
|
|
}
|
|
}, [memoizedHandlers, projectId]);
|
|
|
|
// Handle project change (different project)
|
|
useEffect(() => {
|
|
if (!projectId) return;
|
|
|
|
// If project changed, reconnect
|
|
if (isInitializedRef.current && currentProjectIdRef.current !== projectId) {
|
|
console.log(`[USE_TASK_SOCKET] Project changed from ${currentProjectIdRef.current} to ${projectId}`);
|
|
|
|
const switchProject = async () => {
|
|
try {
|
|
setConnectionState(WebSocketState.CONNECTING);
|
|
|
|
// Update handlers for new project
|
|
taskSocketService.registerHandlers(componentIdRef.current, memoizedHandlers());
|
|
|
|
// Connect to new project (service handles disconnecting from old)
|
|
await taskSocketService.connectToProject(projectId);
|
|
|
|
currentProjectIdRef.current = projectId;
|
|
console.log(`[USE_TASK_SOCKET] Successfully switched to project: ${projectId}`);
|
|
|
|
// Update connection state
|
|
setIsConnected(taskSocketService.isConnected());
|
|
setConnectionState(taskSocketService.getConnectionState());
|
|
|
|
} catch (error) {
|
|
console.error(`[USE_TASK_SOCKET] Failed to switch to project ${projectId}:`, error);
|
|
setConnectionState(WebSocketState.DISCONNECTED);
|
|
setIsConnected(false);
|
|
}
|
|
};
|
|
|
|
switchProject();
|
|
}
|
|
}, [projectId, memoizedHandlers]);
|
|
|
|
// Cleanup on unmount
|
|
useEffect(() => {
|
|
const componentId = componentIdRef.current;
|
|
|
|
return () => {
|
|
console.log(`[USE_TASK_SOCKET] Cleaning up component: ${componentId}`);
|
|
taskSocketService.unregisterHandlers(componentId);
|
|
isInitializedRef.current = false;
|
|
};
|
|
}, []);
|
|
|
|
// Return reactive state and utility functions
|
|
return {
|
|
isConnected, // Now reactive!
|
|
connectionState, // Now reactive!
|
|
reconnect: taskSocketService.reconnect.bind(taskSocketService),
|
|
getCurrentProjectId: taskSocketService.getCurrentProjectId.bind(taskSocketService)
|
|
};
|
|
} |