diff --git a/client/src/Services/socketClient.ts b/client/src/Services/socketClient.ts index 7c3ba96f4..f0eb8e812 100644 --- a/client/src/Services/socketClient.ts +++ b/client/src/Services/socketClient.ts @@ -15,10 +15,11 @@ class SocketClient { private setupDefaultEvents() { this.socket.on('connect', () => { - console.log('Socket handler Built / Connected!'); + console.log('Socket Connected!'); }); - this.socket.on('disconnect', () => { - console.log('Socket handler Removed / Disconnected!'); + + this.socket.off('disconnect', () => { + console.log('Socket Disconnected!'); }); // TODO: This section leaves for debugging purpose @@ -37,6 +38,7 @@ class SocketClient { this.socket = io(URL, { path: '/dapi' }); this.setupDefaultEvents(); this.socket.connect(); + console.log('created a new socket client!'); } setupEventHandlers(handlers: ServerToClientEvent) { @@ -82,11 +84,27 @@ class SocketClient { } interface SocketStore { - socketClient: SocketClient; + socketClient: SocketClient | null; + initialise: () => void; + disconnect: () => void; } -const useSocketClientStore = create(() => ({ - socketClient: new SocketClient(), +const useSocketClientStore = create((set, get) => ({ + socketClient: null, + initialise: () => { + if (!get().socketClient) { + console.log('intialising socket client'); + set({ socketClient: new SocketClient() }); + } + }, + disconnect: () => { + const client = get().socketClient; + if (client) { + client.socket.close(); + console.log('closing socket client '); + set({ socketClient: null }); + } + }, })); export default useSocketClientStore; diff --git a/client/src/Services/useSocketCommunication.ts b/client/src/Services/useSocketCommunication.ts index 2b12764c5..4f6dde103 100644 --- a/client/src/Services/useSocketCommunication.ts +++ b/client/src/Services/useSocketCommunication.ts @@ -21,24 +21,33 @@ export const useSocketCommunication = () => { resetConsoleChunks, appendConsoleChunks, } = useGlobalStore(); + const { setActive, clearFrontendState } = useFrontendStateStore(); const { socketClient } = useSocketClientStore(); - const { setToastMessage: setMessage } = useToastStateStore(); + const { setToastMessage } = useToastStateStore(); const queue = useQueue(); + // Setup socket event handlers on mount useEffect(() => { - const eventHandler = buildSocketEventHandler({ + console.log('usesocketcom is mounted'); + + if (!socketClient) { + return; + } + + const handlers = buildSocketEventHandler({ setActive, updateNextFrame, updateTypeDeclaration, appendConsoleChunks, updateCurrFocusedTab, - setMessage, + setMessage: setToastMessage, }); - socketClient.setupEventHandlers(eventHandler); - }, []); + socketClient.setupEventHandlers(handlers); + }, [socketClient]); + // reset entire debuggin session const resetDebugSession = useCallback(() => { updateNextFrame(INITIAL_BACKEND_STATE); clearFrontendState(); @@ -46,82 +55,102 @@ export const useSocketCommunication = () => { clearTypeDeclarations(); clearUserAnnotation(); resetConsoleChunks(); - }, []); + }, [ + updateNextFrame, + clearFrontendState, + setActive, + clearTypeDeclarations, + clearUserAnnotation, + resetConsoleChunks, + ]); + // error handler for sending code + const handleSendCodeError = () => { + setToastMessage({ + content: 'No file being selected', + colorTheme: 'warning', + durationMs: DEFAULT_MESSAGE_DURATION, + }); + }; + + // send code to backend to start debugging session const sendCode = useCallback(() => { + if (!socketClient) return; + resetDebugSession(); + const { fileSystem, currFocusFilePath } = useUserFsStateStore.getState(); const file = fileSystem.getFileFromPath(currFocusFilePath); if (!file || file.path === 'root') { - setMessage({ - content: 'No file being selected.', - colorTheme: 'warning', - durationMs: DEFAULT_MESSAGE_DURATION, - }); + handleSendCodeError(); return; } + socketClient.serverAction.initializeDebugSession(file.data); - }, [socketClient]); + }, [socketClient, resetDebugSession]); - const executeNextWithRetry = useCallback(() => { - const addEventListenerWithTimeout = ( - listener: (state: BackendState | null) => void, - timeout: number - ) => { - let resolved = false; - - const wrappedListener = (state: BackendState) => { - if (!resolved) { - resolved = true; - listener(state); - socketClient.socket.off('sendBackendStateToUser', wrappedListener); - } - }; - - socketClient.socket.on('sendBackendStateToUser', wrappedListener); - - setTimeout(() => { - if (!resolved) { - resolved = true; - listener(null); - socketClient.socket.off('sendBackendStateToUser', wrappedListener); - } - }, timeout); + // add event listener with timeout + const addEventListenerWithTimeout = ( + listener: (state: BackendState | null) => void, + timeout: number + ) => { + if (!socketClient) return; + + let resolved = false; + + const wrappedListener = (state: BackendState) => { + if (!resolved) { + resolved = true; + listener(state); + socketClient.socket.off('sendBackendStateToUser', wrappedListener); + } }; + socketClient.socket.on('sendBackendStateToUser', wrappedListener); + + setTimeout(() => { + if (!resolved) { + resolved = true; + listener(null); + socketClient.socket.off('sendBackendStateToUser', wrappedListener); + } + }, timeout); + }; + + // step debugger and wait for backend to respond + const executeNextWithRetry = useCallback(() => { + if (!socketClient) return Promise.resolve(false); + return queue(() => { return new Promise((resolve) => { const handleBackendState = (state: BackendState | null) => { - if (state) { - resolve(true); // Resolve as success - } else { - resolve(false); // Resolve as failure due to timeout - } + resolve(!!state); }; - // Add the event listener with a timeout addEventListenerWithTimeout(handleBackendState, 5000); socketClient.serverAction.executeNext(); }); }); - }, [socketClient]); + }, [socketClient, queue]); + // to call multiple next states in bulk const bulkSendNextStates = useCallback( async (count: number) => { - const results = await Promise.all(Array.from({ length: count }, executeNextWithRetry)); - const successfulCount = results.filter((result) => result).length; - return successfulCount; + const results = await Promise.all( + Array.from({ length: count }, () => executeNextWithRetry()) + ); + return results.filter(Boolean).length; }, [executeNextWithRetry] ); return { - resetConsoleChunks, - appendConsoleChunks, - sendCode, - getNextState: executeNextWithRetry, - bulkSendNextStates, - resetDebugSession, + resetConsoleChunks, // Clear console logs + appendConsoleChunks, // Append logs + sendCode, // Start session with file + getNextState: executeNextWithRetry, // Step once + bulkSendNextStates, // Step multiple times + resetDebugSession, // Full reset }; }; diff --git a/client/src/visualiser-debugger/Component/Console/useCursor.ts b/client/src/visualiser-debugger/Component/Console/useCursor.ts index a5f3c6959..2e0856564 100644 --- a/client/src/visualiser-debugger/Component/Console/useCursor.ts +++ b/client/src/visualiser-debugger/Component/Console/useCursor.ts @@ -11,10 +11,9 @@ function useCursor( // eslint-disable-next-line @typescript-eslint/no-unused-vars isCompiled: boolean ) { - const socket = useSocketClientStore((state) => state.socketClient); + const socketClient = useSocketClientStore((state) => state.socketClient); // i dont understand whats happening and need to clarify const appendConsoleChunk = useGlobalStore((state) => state.appendConsoleChunks); const setActive = useFrontendStateStore((state) => state.setActive); - const { socketClient } = useSocketClientStore(); const [shifts, setShifts] = useState(0); const [paused, setPaused] = useState(true); @@ -65,7 +64,8 @@ function useCursor( // TODO: Ensure that it's okay to send the PREFIX to the backend // because if we remove the PREFIX, we can't tell which command // is input while the program is running and while the program is not running - socket.serverAction.sendStdin(content); + if (!socketClient) return; + socketClient.serverAction.sendStdin(content); appendConsoleChunk(`${content}\n`); clearInput(); scrollToBottom(); @@ -82,6 +82,7 @@ function useCursor( default: break; } + if (!socketClient) return; if (isCtrlPressed && key === 'd') { socketClient.serverAction.sendEOF(); diff --git a/client/src/visualiser-debugger/DevelopmentMode.tsx b/client/src/visualiser-debugger/DevelopmentMode.tsx index 07785585c..1733d3990 100644 --- a/client/src/visualiser-debugger/DevelopmentMode.tsx +++ b/client/src/visualiser-debugger/DevelopmentMode.tsx @@ -6,6 +6,7 @@ import { Tabs, Tab } from 'components/Tabs'; import Console from 'visualiser-debugger/Component/Console/Console'; import Joyride from 'react-joyride'; import DynamicTabs from 'components/TabResize/DynamicTabs'; +import useSocketClientStore from 'Services/socketClient'; import { ThemeProvider as MuiThemeProvider } from '@mui/material'; import DevelopmentModeNavbar from '../components/Navbars/DevelopmentModeNavbar'; import Configuration from './Component/Configuration/Configuration'; @@ -35,6 +36,9 @@ const DevelopmentModeContent = () => { } }; + const initialise = useSocketClientStore((state) => state.initialise); + const disconnect = useSocketClientStore((state) => state.disconnect); + // Onboarding Code useEffect(() => { if (onboardingCurrFile) { @@ -45,6 +49,19 @@ const DevelopmentModeContent = () => { } }, [onboardingCurrFile]); + // initialising socket cycle + useEffect(() => { + // intialise socket Connection + console.log('DevelopmentMode is mounted'); + initialise(); + + // disconnect socket connection + return () => { + console.log('DevelopmentMode is unmounted'); + disconnect(); + }; + }, [initialise, disconnect]); + const handleClickStart = (event: React.MouseEvent) => { event.preventDefault(); resetRootPaths();