feat: initial commit at project root

This commit is contained in:
Hamza-Ayed
2026-05-29 01:06:47 +03:00
commit 87ec54bbd7
22 changed files with 1737 additions and 0 deletions

151
internal/ws/client.js Normal file
View File

@@ -0,0 +1,151 @@
import { logger } from '../logger/logger.js';
import * as protocol from '../protocol/messages.js';
/**
* Client represents a single active WebSocket connection wrapper.
* It encapsulates the raw connection, handles ping/pong heartbeat,
* parses messages, and routes events back to the coordinator Hub.
*/
export class Client {
/**
* Initializes a new client instance and sets up connection event listeners and heartbeat.
* @param {object} conn The raw WS socket connection instance from the 'ws' library.
* @param {object} hub The central Hub coordinator to process and route messages.
*/
constructor(conn, hub) {
this.conn = conn; // The underlying raw WebSocket connection
this.hub = hub; // Reference to the main coordinator hub
this.userID = ''; // Authenticated user ID (driver_id or passenger_id)
this.rideID = ''; // Associated ride ID
this.role = ''; // User's role: 'driver' or 'passenger'
this.isAlive = true; // Connection status flag updated by heartbeat pong responses
this.pongTimeout = null; // Timer waiting for a pong reply after sending a ping
/**
* Listener for incoming WebSocket message payloads.
* Enforces a hard read limit of 4096 bytes (4KB) to prevent Denial of Service (DoS) memory consumption.
*/
this.conn.on('message', (data) => {
if (data.length > 4096) {
logger.warn('payload_too_large', { remote_ip: this.remoteIP() });
this.send(protocol.newError(protocol.ErrPayloadTooLarge, 'Payload too large'));
this.close();
return;
}
// Pass the message to the hub for authentication and signaling routing
this.hub.handleMessage(this, data);
});
/**
* Listener for the 'pong' response frame.
* Indicates that the client is responsive. Clears the timeout timer.
*/
this.conn.on('pong', () => {
this.isAlive = true;
if (this.pongTimeout) {
clearTimeout(this.pongTimeout);
this.pongTimeout = null;
}
});
/**
* Listener for connection close event.
* Triggers cleanup of heartbeat timers and unregisters the client from the hub.
*/
this.conn.on('close', () => {
this.cleanup();
this.hub.unregister(this);
});
/**
* Listener for socket connection errors.
* Logs the error details and safely terminates the connection.
*/
this.conn.on('error', (err) => {
logger.error('websocket_error', {
user_id: this.userID,
ride_id: this.rideID,
error: err.message
});
this.close();
});
/**
* Heartbeat loop: Pings the client every 20 seconds.
* If the client does not respond with a pong within 10 seconds, the connection is considered dead and closed.
* This prevents resource leaks from half-open TCP connections.
*/
this.pingInterval = setInterval(() => {
this.isAlive = false;
try {
this.conn.ping();
} catch (err) {
this.close();
return;
}
// Schedule a timeout check for 10 seconds. Close connection if isAlive is still false.
this.pongTimeout = setTimeout(() => {
if (!this.isAlive) {
logger.warn('heartbeat_timeout', {
user_id: this.userID,
ride_id: this.rideID,
remote_ip: this.remoteIP()
});
this.close();
}
}, 10000);
}, 20000);
}
/**
* Sends a serialized JSON message down the WebSocket connection wire.
* Checks the connection readyState to ensure it is in the OPEN state before writing.
* @param {string} msg Serialized JSON string message payload.
*/
send(msg) {
if (this.conn.readyState === 1) { // WebSocket.OPEN state
try {
this.conn.send(msg);
} catch (err) {
logger.error('websocket_send_failed', { error: err.message });
}
}
}
/**
* Resolves the remote IP address of the connected client.
* Falls back to 'unknown' if the connection socket is already closed or unavailable.
* @returns {string} The remote client's IP address.
*/
remoteIP() {
return this.conn._socket ? this.conn._socket.remoteAddress : 'unknown';
}
/**
* Safely terminates the WebSocket connection and releases local memory and timers.
*/
close() {
try {
this.conn.close();
} catch (err) {
// Ignored: connection might already be closed or unreachable
}
this.cleanup();
}
/**
* Cleans up and clears all scheduled interval pings and timeout check timers.
* This is critical to prevent CPU timer memory leaks when client closes.
*/
cleanup() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
if (this.pongTimeout) {
clearTimeout(this.pongTimeout);
this.pongTimeout = null;
}
}
}