From 9550229a11a66be320e8218b52210d76a9e3fe40 Mon Sep 17 00:00:00 2001 From: Patrick Tang Truong Date: Mon, 21 Jul 2025 16:30:43 +1000 Subject: [PATCH 1/3] refactor: update timing of socket connection --- client/src/Services/socketClient.ts | 21 ++- client/src/Services/useSocketCommunication.ts | 141 +++++++++++------- .../Component/Console/useCursor.ts | 7 +- 3 files changed, 107 insertions(+), 62 deletions(-) diff --git a/client/src/Services/socketClient.ts b/client/src/Services/socketClient.ts index 7c3ba96f4..8bbfc9338 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,18 @@ class SocketClient { } interface SocketStore { - socketClient: SocketClient; + socketClient: SocketClient | null; + initialise: () => 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() }); + } + }, })); export default useSocketClientStore; diff --git a/client/src/Services/useSocketCommunication.ts b/client/src/Services/useSocketCommunication.ts index 2b12764c5..77ad94215 100644 --- a/client/src/Services/useSocketCommunication.ts +++ b/client/src/Services/useSocketCommunication.ts @@ -21,23 +21,31 @@ export const useSocketCommunication = () => { resetConsoleChunks, appendConsoleChunks, } = useGlobalStore(); + const { setActive, clearFrontendState } = useFrontendStateStore(); - const { socketClient } = useSocketClientStore(); - const { setToastMessage: setMessage } = useToastStateStore(); + const { socketClient, initialise } = useSocketClientStore(); + const { setToastMessage } = useToastStateStore(); const queue = useQueue(); + // Setup socket event handlers on mount useEffect(() => { - const eventHandler = buildSocketEventHandler({ + console.log('usesocketcom is mounted'); + if (!socketClient) { + initialise(); + return; + } + + const handlers = buildSocketEventHandler({ setActive, updateNextFrame, updateTypeDeclaration, appendConsoleChunks, updateCurrFocusedTab, - setMessage, + setMessage: setToastMessage, }); - socketClient.setupEventHandlers(eventHandler); - }, []); + socketClient.setupEventHandlers(handlers); + }, [socketClient, initialise]); // ask what this means i think before was only socketClient dependency const resetDebugSession = useCallback(() => { updateNextFrame(INITIAL_BACKEND_STATE); @@ -46,82 +54,109 @@ export const useSocketCommunication = () => { clearTypeDeclarations(); clearUserAnnotation(); resetConsoleChunks(); - }, []); + }, [ + updateNextFrame, + clearFrontendState, + setActive, + clearTypeDeclarations, + clearUserAnnotation, + resetConsoleChunks, + ]); // ask why you do this? before was empty dependency + + // error hadnler 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]); // ask why you put resetDebugSession in here as dependency - 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 - } + // if (state) { + // resolve(true); // Resolve as success + // } else { + // resolve(false); // Resolve as failure due to timeout + // } before was this + resolve(!!state); // what this means? }; - // Add the event listener with a timeout addEventListenerWithTimeout(handleBackendState, 5000); socketClient.serverAction.executeNext(); }); }); - }, [socketClient]); + }, [socketClient, queue]); // why do i add queue here? + // 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((result) => result).length; + // return successfulCount; + return results.filter(Boolean).length; // whats happening? }, [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(); From f6c9a12b9998db4ba8bb817f841d3ae105f23caf Mon Sep 17 00:00:00 2001 From: Patrick Tang Truong Date: Fri, 29 Aug 2025 15:03:58 +1000 Subject: [PATCH 2/3] feat: add websocket close function --- client/src/App.tsx | 4 ++++ client/src/Services/socketClient.ts | 15 ++++++++++++--- client/src/Services/useSocketCommunication.ts | 9 ++++++--- .../src/visualiser-debugger/DevelopmentMode.tsx | 17 +++++++++++++++++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 27456b353..33ee239ea 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -19,6 +19,10 @@ const App = () => { window.globalStore = useGlobalStore; // @ts-ignore window.frontendStore = useFrontendStateStore; + // console.log('app is mounted'); + // return () => { + // console.log('app is unmounted'); + // }; }, [useGlobalStore]); return ( diff --git a/client/src/Services/socketClient.ts b/client/src/Services/socketClient.ts index 8bbfc9338..d63a5f61f 100644 --- a/client/src/Services/socketClient.ts +++ b/client/src/Services/socketClient.ts @@ -18,9 +18,9 @@ class SocketClient { console.log('Socket Connected!'); }); - this.socket.off('disconnect', () => { - console.log('Socket Disconnected!'); - }); + // this.socket.off('disconnect', () => { + // console.log('Socket Disconnected!'); + // }); // TODO: This section leaves for debugging purpose /* @@ -86,6 +86,7 @@ class SocketClient { interface SocketStore { socketClient: SocketClient | null; initialise: () => void; + disconnect: () => void; } const useSocketClientStore = create((set, get) => ({ @@ -96,6 +97,14 @@ const useSocketClientStore = create((set, get) => ({ 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 77ad94215..180b86c66 100644 --- a/client/src/Services/useSocketCommunication.ts +++ b/client/src/Services/useSocketCommunication.ts @@ -23,15 +23,17 @@ export const useSocketCommunication = () => { } = useGlobalStore(); const { setActive, clearFrontendState } = useFrontendStateStore(); - const { socketClient, initialise } = useSocketClientStore(); + const { socketClient } = useSocketClientStore(); const { setToastMessage } = useToastStateStore(); const queue = useQueue(); // Setup socket event handlers on mount useEffect(() => { console.log('usesocketcom is mounted'); + + // can remove the intialise() from below or leave it if (!socketClient) { - initialise(); + // initialise(); return; } @@ -45,8 +47,9 @@ export const useSocketCommunication = () => { }); socketClient.setupEventHandlers(handlers); - }, [socketClient, initialise]); // ask what this means i think before was only socketClient dependency + }, [socketClient]); // ask what this means i think before was only socketClient dependency + // reset entire debuggin session const resetDebugSession = useCallback(() => { updateNextFrame(INITIAL_BACKEND_STATE); clearFrontendState(); diff --git a/client/src/visualiser-debugger/DevelopmentMode.tsx b/client/src/visualiser-debugger/DevelopmentMode.tsx index c385044a6..c8179cb21 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 DevelopmentModeNavbar from '../components/Navbars/DevelopmentModeNavbar'; import Configuration from './Component/Configuration/Configuration'; import Controls from './Component/Control/Controls'; @@ -32,6 +33,9 @@ const DevelopmentModeContent = () => { } }; + const initialise = useSocketClientStore((state) => state.initialise); + const disconnect = useSocketClientStore((state) => state.disconnect); + // Onboarding Code useEffect(() => { if (onboardingCurrFile) { @@ -42,6 +46,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(); From f4801777dae6350af56619e83c9afdfed87efec1 Mon Sep 17 00:00:00 2001 From: Patrick Tang Truong Date: Fri, 29 Aug 2025 15:23:49 +1000 Subject: [PATCH 3/3] style: remove comments --- client/src/App.tsx | 4 ---- client/src/Services/socketClient.ts | 6 ++--- client/src/Services/useSocketCommunication.ts | 23 ++++++------------- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 33ee239ea..27456b353 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -19,10 +19,6 @@ const App = () => { window.globalStore = useGlobalStore; // @ts-ignore window.frontendStore = useFrontendStateStore; - // console.log('app is mounted'); - // return () => { - // console.log('app is unmounted'); - // }; }, [useGlobalStore]); return ( diff --git a/client/src/Services/socketClient.ts b/client/src/Services/socketClient.ts index d63a5f61f..f0eb8e812 100644 --- a/client/src/Services/socketClient.ts +++ b/client/src/Services/socketClient.ts @@ -18,9 +18,9 @@ class SocketClient { console.log('Socket Connected!'); }); - // this.socket.off('disconnect', () => { - // console.log('Socket Disconnected!'); - // }); + this.socket.off('disconnect', () => { + console.log('Socket Disconnected!'); + }); // TODO: This section leaves for debugging purpose /* diff --git a/client/src/Services/useSocketCommunication.ts b/client/src/Services/useSocketCommunication.ts index 180b86c66..4f6dde103 100644 --- a/client/src/Services/useSocketCommunication.ts +++ b/client/src/Services/useSocketCommunication.ts @@ -31,9 +31,7 @@ export const useSocketCommunication = () => { useEffect(() => { console.log('usesocketcom is mounted'); - // can remove the intialise() from below or leave it if (!socketClient) { - // initialise(); return; } @@ -47,7 +45,7 @@ export const useSocketCommunication = () => { }); socketClient.setupEventHandlers(handlers); - }, [socketClient]); // ask what this means i think before was only socketClient dependency + }, [socketClient]); // reset entire debuggin session const resetDebugSession = useCallback(() => { @@ -64,9 +62,9 @@ export const useSocketCommunication = () => { clearTypeDeclarations, clearUserAnnotation, resetConsoleChunks, - ]); // ask why you do this? before was empty dependency + ]); - // error hadnler for sending code + // error handler for sending code const handleSendCodeError = () => { setToastMessage({ content: 'No file being selected', @@ -90,7 +88,7 @@ export const useSocketCommunication = () => { } socketClient.serverAction.initializeDebugSession(file.data); - }, [socketClient, resetDebugSession]); // ask why you put resetDebugSession in here as dependency + }, [socketClient, resetDebugSession]); // add event listener with timeout const addEventListenerWithTimeout = ( @@ -127,19 +125,14 @@ export const useSocketCommunication = () => { 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 - // } before was this - resolve(!!state); // what this means? + resolve(!!state); }; addEventListenerWithTimeout(handleBackendState, 5000); socketClient.serverAction.executeNext(); }); }); - }, [socketClient, queue]); // why do i add queue here? + }, [socketClient, queue]); // to call multiple next states in bulk const bulkSendNextStates = useCallback( @@ -147,9 +140,7 @@ export const useSocketCommunication = () => { const results = await Promise.all( Array.from({ length: count }, () => executeNextWithRetry()) ); - // return results.filter((result) => result).length; - // return successfulCount; - return results.filter(Boolean).length; // whats happening? + return results.filter(Boolean).length; }, [executeNextWithRetry] );