Build a production-ready WebSocket client for Cocos Creator with a connection state machine, exponential backoff, heartbeat timeout, queued messages and safe foreground reconnection.
WebSocket code often looks correct in a browser preview but fails in a real game build. Put the app in the background for a few minutes, switch networks, or let a router silently drop an idle TCP connection and the common result is the same: the socket object still exists, but the player can no longer receive game messages.
The fix is not to call new WebSocket() from every error callback. A reliable client needs a small state machine that owns the socket lifecycle:
only one active connection attempt;
a heartbeat that detects half-open connections;
exponential backoff so a server outage does not cause a reconnect storm;
a bounded queue for messages produced while offline;
a fresh connection when the game returns to the foreground.
This article uses Cocos Creator 3.x and TypeScript. The browser WebSocket API is also used by Cocos Creator’s native JavaScript environment, so the overall design works for Web, Android and iOS builds. Test your target platform, because proxy, TLS and background-network behavior can differ.
The failure mode: an open socket is not proof of a live connection
socket.readyState === WebSocket.OPEN only means the local runtime believes the WebSocket is open. A mobile device can lose connectivity without immediately receiving a TCP close event. The server may already have discarded the connection while the client continues to show OPEN.
That is why reconnecting only in onclose is incomplete. The client must periodically send an application-level ping and require a response within a deadline. If the deadline expires, close the stale socket locally and let the normal reconnect path take over.
A reconnecting WebSocket client
Create ReconnectWebSocket.ts in your project scripts folder. This example uses JSON frames with ping and pong messages; use the equivalent protocol messages if your server already has them.
// Do not allow a long outage to grow memory forever. if (this.pendingMessages.length >= this.maxQueuedMessages) { this.pendingMessages.shift(); } this.pendingMessages.push(serialized); this.connect(); }
The sample expects the server to return a pong frame when it receives ping:
1
{"type":"ping","time":1760000000000}
1
{"type":"pong"}
Do not treat an arbitrary incoming message as a heartbeat response unless your protocol guarantees frequent server traffic. An explicit pong makes a stalled connection detectable even when the game is otherwise idle.
Connect the client to a Cocos Creator component
Keep the socket owner alive for as long as the game session needs it. A boot scene node marked as persistent is a simple option.
privateonGameShow() { // The OS may have suspended networking in the background. Reconnect instead // of trusting the old readyState. this.client.reconnectNow(); } }
EVENT_SHOW is a useful recovery point, but it should not be the only one. The heartbeat still protects a player who changes Wi-Fi or loses mobile data while the app remains visible.
Avoid these common reconnection bugs
Creating multiple sockets
If onerror, onclose, a button handler and EVENT_SHOW all call connect() without a guard, several live sockets can exist at once. That causes duplicated messages, duplicated login requests and race conditions. The Connecting and Open checks above ensure one connection owner.
Retrying immediately in a loop
An instant retry loop drains battery and overloads a server exactly when it is least healthy. Exponential backoff with a small random jitter spreads clients across time. Cap the delay so recovery after a long outage is still reasonably quick.
Replaying unsafe messages
Queuing every outbound packet is dangerous. A chat message may be safe to retry; a purchase, reward claim or “use item” command may not be. Add a message identifier and make important server actions idempotent, or mark those messages as non-retryable and show a retry UI instead.
Treating a reconnect as a resumed session
A new WebSocket is a new transport connection. Authenticate again if your protocol requires it, then request a fresh game snapshot or synchronization point. Never assume events missed while offline can be reconstructed from the local client alone.
Test on real devices
Before shipping, test at least these cases on Android and iOS:
Start the game with no network, then restore Wi-Fi.
Switch between Wi-Fi and cellular data while connected.
Background the app for several minutes, then return to it.
Stop the WebSocket server briefly and confirm retry intervals increase.
Send messages while offline and verify only retry-safe frames are replayed.
Force a delayed or missing pong and verify the stale socket is replaced.
The essential rule is simple: the socket object is not your connection state. Your state machine, heartbeat and server synchronization protocol are. Once those responsibilities are explicit, WebSocket behavior in Cocos Creator becomes predictable even on unreliable mobile networks.