Websocket Client & Server Library implementing the WebSocket protocol as specified in RFC 6455
—
Active WebSocket connection handling for bidirectional communication between client and server with comprehensive message processing, event handling, and connection lifecycle management.
Represents an active WebSocket connection for both client and server sides.
/**
* Active WebSocket connection for bidirectional communication
* @param socket - TCP socket
* @param extensions - Negotiated extensions array
* @param protocol - Negotiated protocol string
* @param maskOutgoingPackets - Whether to mask outgoing packets
* @param config - Connection configuration
*/
class WebSocketConnection extends EventEmitter {
/**
* Send text or binary data (auto-detects type)
* @param data - String or Buffer to send
* @param callback - Optional callback for completion/error
*/
send(data: string | Buffer, callback?: (error?: Error) => void): void;
/**
* Send UTF-8 text data
* @param data - UTF-8 string to send
* @param callback - Optional callback for completion/error
*/
sendUTF(data: string, callback?: (error?: Error) => void): void;
/**
* Send binary data
* @param data - Buffer containing binary data
* @param callback - Optional callback for completion/error
*/
sendBytes(data: Buffer, callback?: (error?: Error) => void): void;
/**
* Send ping frame with optional payload
* @param data - Optional Buffer payload for ping frame
*/
ping(data?: Buffer): void;
/**
* Send pong frame (usually sent automatically in response to ping)
* @param binaryPayload - Buffer payload for pong frame
*/
pong(binaryPayload: Buffer): void;
/**
* Initiate clean connection close
* @param reasonCode - Close reason code (default: 1000)
* @param description - Human-readable close reason
*/
close(reasonCode?: number, description?: string): void;
/**
* Force close connection immediately
* @param reasonCode - Close reason code
* @param description - Human-readable close reason
* @param skipCloseFrame - Skip sending close frame
*/
drop(reasonCode?: number, description?: string, skipCloseFrame?: boolean): void;
/** Pause reading from socket */
pause(): void;
/** Resume reading from socket */
resume(): void;
/** Boolean connection status */
readonly connected: boolean;
/** Connection state: 'open', 'peer_requested_close', 'ending', 'closed' */
readonly state: string;
/** Negotiated WebSocket protocol */
readonly protocol: string;
/** Negotiated extensions (currently always empty array) */
readonly extensions: any[];
/** Remote IP address */
readonly remoteAddress: string;
/** Array of addresses including X-Forwarded-For */
readonly remoteAddresses: string[];
/** Close reason code (set after close) */
readonly closeReasonCode: number;
/** Close reason description (set after close) */
readonly closeDescription: string;
/** WebSocket protocol version (8 or 13) */
readonly webSocketVersion: number;
/** Underlying TCP socket */
readonly socket: object;
}Events:
'message' - (WebSocketMessage) - Message received'frame' - (WebSocketFrame) - Raw frame received (if assembleFragments is false)'close' - (reasonCode, description) - Connection closed'error' - (error) - Connection error'ping' - (cancel, data) - Ping frame received'pong' - (data) - Pong frame received'drain' - Socket drain event (outgoing buffer emptied)'pause' - Socket pause event'resume' - Socket resume eventUsage Example:
// Server-side: handling connection from request
wsServer.on('request', function(request) {
const connection = request.accept('echo-protocol', request.origin);
// Connection event handlers
connection.on('message', function(message) {
console.log('Received Message:', message.type);
if (message.type === 'utf8') {
console.log('UTF-8 Data: ' + message.utf8Data);
// Echo back
connection.sendUTF('Echo: ' + message.utf8Data);
} else if (message.type === 'binary') {
console.log('Binary Data: ' + message.binaryData.length + ' bytes');
// Echo back binary
connection.sendBytes(message.binaryData);
}
});
connection.on('ping', function(cancel, data) {
console.log('Received ping');
// Pong is sent automatically, but you can cancel it
// cancel.cancel = true;
});
connection.on('pong', function(data) {
console.log('Received pong');
});
connection.on('close', function(reasonCode, description) {
console.log('Connection closed: ' + reasonCode + ' - ' + description);
});
connection.on('error', function(error) {
console.log('Connection error: ' + error.toString());
});
// Send periodic pings
const pingInterval = setInterval(function() {
if (connection.connected) {
connection.ping(Buffer.from('ping-data'));
} else {
clearInterval(pingInterval);
}
}, 30000);
});
// Client-side: handling connection
client.on('connect', function(connection) {
// Send different types of data
connection.sendUTF('Hello Server!');
connection.sendBytes(Buffer.from([0x01, 0x02, 0x03, 0x04]));
// Auto-detect send method
connection.send('Text message'); // Uses sendUTF
connection.send(Buffer.from('Binary data')); // Uses sendBytes
// Handle responses
connection.on('message', function(message) {
if (message.type === 'utf8') {
console.log('Server says: ' + message.utf8Data);
}
});
// Graceful close after 5 seconds
setTimeout(function() {
connection.close(1000, 'Normal closure');
}, 5000);
});Message Object Structure:
interface WebSocketMessage {
/** Message type: 'utf8' for text, 'binary' for binary data */
type: 'utf8' | 'binary';
/** UTF-8 string data (present when type is 'utf8') */
utf8Data?: string;
/** Binary data as Buffer (present when type is 'binary') */
binaryData?: Buffer;
}Message Handling Patterns:
connection.on('message', function(message) {
switch (message.type) {
case 'utf8':
// Handle text message
const textData = message.utf8Data;
console.log('Text:', textData);
// Parse JSON if applicable
try {
const jsonData = JSON.parse(textData);
handleJsonMessage(jsonData);
} catch (e) {
handleTextMessage(textData);
}
break;
case 'binary':
// Handle binary message
const binaryData = message.binaryData;
console.log('Binary length:', binaryData.length);
// Process binary data
if (binaryData.length > 0) {
const firstByte = binaryData[0];
console.log('First byte:', firstByte);
}
break;
}
});Connection States:
'open' - Connection is active and ready for communication'peer_requested_close' - Remote peer initiated close handshake'ending' - Local close initiated, waiting for peer response'closed' - Connection is fully closedState Monitoring:
connection.on('close', function(reasonCode, description) {
console.log('Connection state:', connection.state); // 'closed'
console.log('Close reason:', reasonCode, description);
console.log('Connected status:', connection.connected); // false
});
// Monitor state changes
const originalState = connection.state;
setInterval(function() {
if (connection.state !== originalState) {
console.log('State changed from', originalState, 'to', connection.state);
}
}, 1000);Error Types and Handling:
connection.on('error', function(error) {
console.error('WebSocket Error:', error.message);
// Check if connection is still viable
if (connection.connected) {
console.log('Connection still active, error was recoverable');
} else {
console.log('Connection lost due to error');
// Attempt reconnection logic here
attemptReconnection();
}
});
// Handle different types of errors
connection.on('error', function(error) {
if (error.code === 'ECONNRESET') {
console.log('Connection reset by peer');
} else if (error.code === 'ETIMEDOUT') {
console.log('Connection timed out');
} else {
console.log('Other error:', error.code, error.message);
}
});Backpressure Handling:
// Monitor drain events for flow control
let canSendMore = true;
connection.on('drain', function() {
console.log('Output buffer drained, can send more data');
canSendMore = true;
// Resume sending queued messages
sendQueuedMessages();
});
function sendMessage(data) {
if (canSendMore && connection.connected) {
const result = connection.sendUTF(data, function(error) {
if (error) {
console.error('Send error:', error);
canSendMore = false;
}
});
// Check if we should pause sending
if (!result) {
canSendMore = false;
console.log('Output buffer full, waiting for drain');
}
} else {
// Queue message for later
messageQueue.push(data);
}
}Close Reason Constants:
// Close reason constants (accessible as WebSocketConnection static properties)
WebSocketConnection.CLOSE_REASON_NORMAL = 1000;
WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001;
WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002;
WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003;
WebSocketConnection.CLOSE_REASON_RESERVED = 1004;
WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005;
WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006;
WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007;
WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008;
WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009;
WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010;
WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011;
WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015;
// Close descriptions mapping
WebSocketConnection.CLOSE_DESCRIPTIONS = {
1000: 'Normal connection closure',
1001: 'Remote end going away',
1002: 'Protocol error',
1003: 'Unprocessable input',
1004: 'Reserved',
1005: 'Reason not provided',
1006: 'Abnormal closure, no close frame',
1007: 'Invalid frame payload data',
1008: 'Policy violation',
1009: 'Message too big',
1010: 'Extension required',
1011: 'Internal server error',
1015: 'TLS handshake failed'
};Graceful Shutdown:
function gracefulShutdown(connection) {
if (connection.connected) {
// Send any final messages
connection.sendUTF(JSON.stringify({ type: 'goodbye' }));
// Initiate clean close
connection.close(WebSocketConnection.CLOSE_REASON_NORMAL, 'Server shutting down');
// Force close after timeout
setTimeout(function() {
if (connection.state !== 'closed') {
console.log('Forcing connection close');
connection.drop(WebSocketConnection.CLOSE_REASON_NORMAL, 'Timeout');
}
}, 5000);
}
}
// Handle different close scenarios
connection.on('close', function(reasonCode, description) {
switch (reasonCode) {
case WebSocketConnection.CLOSE_REASON_NORMAL:
console.log('Clean shutdown');
break;
case WebSocketConnection.CLOSE_REASON_GOING_AWAY:
console.log('Peer is going away');
break;
case WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR:
console.log('Protocol error occurred');
break;
default:
console.log('Connection closed with code:', reasonCode);
}
});interface ConnectionConfig {
maxReceivedFrameSize?: number;
maxReceivedMessageSize?: number;
fragmentOutgoingMessages?: boolean;
fragmentationThreshold?: number;
assembleFragments?: boolean;
disableNagleAlgorithm?: boolean;
closeTimeout?: number;
}type ConnectionState = 'open' | 'peer_requested_close' | 'ending' | 'closed';Install with Tessl CLI
npx tessl i tessl/npm-websocket