How I Fix the Error While Sending Request From Web Server to Game Server

While integrating a Node.js web server with a Pomelo-based game server, I ran into a frustrating error that stopped my data from reaching the game server.

Everything looked fine. My code connected, logged messages, and didn’t complain until it did. Here’s the infamous runtime error I saw:

Error: not opened
at WebSocket.send (/home/digvijay/node_modules/ws/lib/WebSocket.js:181:16)

This was thrown right after pomelo.init() successfully connected and printed “websocket connected!” to the console. The weird part? I didn’t think I was doing anything wrong. Here’s the code I was using:

pomelo.init({
host: rootTools.connectorHost,
port: rootTools.connectorPort,
log: true
}, function () {
var playerData = { /* any key-value pairs */ };

pomelo.request('connector.entryHandler.broadcastPlayer', {
data: playerData,
route: 'update',
playerId: data.playerId
}, function (err, data) {
console.log(err, data);
pomelo.disconnect();
});
});

Define the Error

What does “Error not opened”

That error is coming from the WebSocket client used internally by Pomelo. It’s triggered when you try to send a message before the socket is fully open.

Here’s the culprit in the WebSocket source:

if (this.readyState !== WebSocket.OPEN) {
throw new Error('not opened');
}

So even though pomelo.init() called my callback and logged “websocket connected!”, the internal WebSocket may not have completed the full handshake yet.

Root Cause

The core issue is timing.

  • pomelo.init() triggers the callback too early before the WebSocket is truly ready for sending.
  • As a result, my .request() fired on a socket that wasn’t fully open.
  • This is a known behavior in some versions of pomelo-node-client-websocket.

Improve and Safer Code

I decided to revise the logic and make it wait for a true “ready” event. Pomelo emits an onHandshakeOver event once the WebSocket handshake is finalized this is the right place to send data.

Here’s the corrected and safer version of my code:

const pomelo = require('pomelo-node-client-websocket').create();

const connectToGameServer = () => {
pomelo.init({
host: rootTools.connectorHost,
port: rootTools.connectorPort,
log: true
});

pomelo.on('io-error', (err) => {
console.error('IO Error:', err);
});

pomelo.on('error', (err) => {
console.error('General Error:', err);
});

pomelo.on('disconnect', () => {
console.log('Disconnected from game server.');
});

// Wait for full handshake
pomelo.on('onHandshakeOver', () => {
console.log('Handshake complete! Connection is now open.');

const playerData = {
username: 'Player1',
level: 10,
position: { x: 100, y: 200 }
};

pomelo.request('connector.entryHandler.broadcastPlayer', {
data: playerData,
route: 'update',
playerId: playerData.username
}, (err, response) => {
if (err) {
console.error('Request failed:', err);
} else {
console.log('Server response:', response);
}
pomelo.disconnect();
});
});
};

connectToGameServer();

Add Retry Logic

Production environments aren’t always predictable. If the connection fails, it’s good practice to retry. Here’s how I added simple retry logic:

let retryAttempts = 0;
const maxRetries = 3;

pomelo.on('io-error', () => {
if (retryAttempts < maxRetries) {
retryAttempts++;
console.log(`Retrying connection (${retryAttempts}/${maxRetries})...`);
setTimeout(connectToGameServer, 1000 * retryAttempts);
} else {
console.error('Max retries reached. Could not connect.');
}
});

Now if the first attempt fails due to a temporary issue, the script will automatically retry a few times before giving up.

Summary

Problem Fix
Request sent before socket openedWait for onHandshakeOver or on('open')
Silent connection issuesAdd pomelo.on('error') and logging
No fallback for failureImplement retry logic

Final Thoughts

What caught me off guard was that pomelo.init() gave me false confidence that the socket was ready. In reality, WebSocket connections require a handshake and verification process before they’re fully open and usable. To avoid this issue, always wait for the onHandshakeOver event before making any requests. Don’t rely on the init() callback as a signal that the connection is ready. In production, be sure to include solid error handling and retry logic. This small fix saved me a lot of debugging tim and hopefully it helps you too.

Related blog posts