CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-websocket

Websocket Client & Server Library implementing the WebSocket protocol as specified in RFC 6455

Pending
Overview
Eval results
Files

connection.mddocs/

Connection Management

Active WebSocket connection handling for bidirectional communication between client and server with comprehensive message processing, event handling, and connection lifecycle management.

Capabilities

WebSocketConnection

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 event

Usage 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 Processing

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 State Management

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 closed

State 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 Handling and Recovery

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);
    }
});

Flow Control

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);
    }
}

Connection Close Handling

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);
    }
});

Types

Connection Configuration

interface ConnectionConfig {
    maxReceivedFrameSize?: number;
    maxReceivedMessageSize?: number;
    fragmentOutgoingMessages?: boolean;
    fragmentationThreshold?: number;
    assembleFragments?: boolean;
    disableNagleAlgorithm?: boolean;
    closeTimeout?: number;
}

Connection States

type ConnectionState = 'open' | 'peer_requested_close' | 'ending' | 'closed';

Install with Tessl CLI

npx tessl i tessl/npm-websocket

docs

client.md

connection.md

frame.md

index.md

router-request.md

server.md

utilities.md

tile.json