My React application has a home page where a user can create a “room” and connect to a socket. •The user chooses a username and clicks “create room”. •This action navigates them to /game-page. The /game-page is wrapped in a SocketContext component, which connects to the Socket.IO server and performs the handshake. Once inside /game-page, the UI handles triggering the create room event, and if successful, it displays the room UI. ⸻ Desktop behaviour •If the desktop browser loses connection due to network issues, reconnection is automatically triggered. •If the socket is successfully recovered (socket.recovered within 'handshake' server listener), the same user session continues and the application retains the pre-existing state. •If reconnection ultimately fails, the user sees a modal and is redirected back to the home page. This part works fine. ⸻ Mobile behaviour On mobile, the flow is different: •If the user minimises the browser and reopens it shortly after, the socket reconnection succeeds within 'handshake' server listener, the app state is preserved, and socket.recovered works correctly. •However, if the user returns after a longer delay (past the maximum reconnection attempts), the socket cannot be recovered. Here’s where the problem occurs: 1.When the browser is reopened, a new reconnection attempt is triggered automatically — even though it should be past the reconnectionAttempts limit. So I believe this listener is re triggered even if should not ?! /** Connection / reconnection listeners */ socket.io.on('reconnect', (attempt) => { console.info('Reconnected on attempt: ' + attempt); SendHandshake(); });
The app state is lost, but the SocketContext seems to reconnect and handshake successfully. I believe due to the listener it tries to handshake again. As a result, the user remains on /game-page, connected to the socket but not actually inside any room. Normally what would happen is.. I am handshaking and then joining/creating a room, or I am handshaking because managed to reconnect via the client reconnection attempts which worked! cos within the allowed time! But in this case we reconnecting when "we should not" and the user ends up stuck in a limbo state.
⸻ My confusion I’m not sure if this behaviour is caused by a mistake in my event/architecture design or if it’s just how browsers handle backgrounded tabs and sockets on mobile. One potential workaround I considered is: •On /game-page load, save a flag in localStorage indicating that the user is in a game. •If the socket cannot be recovered when the page is reopened, use this flag to detect the mismatch and kick the user back to the home page. I’m not sure if this would introduce edge cases, and I’d like to find a more robust solution. Another idea was.. In the socket.io.on('reconnect', (attempt) => { console.info('Reconnected on attempt: ' + attempt); SendHandshake(); }); emit another event which checks if user/socket is within any room. If not kick him.. hope this won't create any edge cases too ⸻ Looking forward to hear anyidea and how generally you would solve this scenario. Thanks! Socket connection settings: reconnectionAttempts: 8, // Maximum number of reconnection attempts before giving up // 8 attempts = exactly 8 seconds total (1s each) reconnectionDelay: 1000, // Time to wait before first reconnect attempt (in ms) // Each attempt will take exactly 1 second reconnectionDelayMax: 1000, // Maximum time between reconnection attempts (in ms) // Kept same as reconnectionDelay for consistent timing // timeout: 20000, // How long to wait for a connection to establish (in ms) // If server doesn't respond within 20s, connection fails // Don't change, or game breaks. Believe cos we doing socket.connect() in useEffect autoConnect: false, // Don't connect automatically when socket is created // Allows manual control with socket.connect() randomizationFactor: 0 // No random variation in reconnection delays // Makes timing predictable for testing and debugging ```Versions used: socket.io-client: ^4.7.4, socket.io: ^4.7.4
|