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

frame.mddocs/

Frame-Level Operations

Low-level WebSocket frame parsing and construction for advanced use cases requiring direct protocol frame manipulation and custom frame processing.

Capabilities

WebSocketFrame

Low-level WebSocket frame parsing and construction class.

/**
 * WebSocket frame for low-level protocol operations
 * @param maskBytes - Optional mask bytes for frame masking
 * @param frameHeader - Optional frame header configuration
 * @param config - Frame processing configuration
 */
class WebSocketFrame {
    constructor(maskBytes?: Buffer, frameHeader?: FrameHeader, config?: FrameConfig);
    
    /**
     * Add data for frame parsing
     * @param bufferList - Buffer containing frame data
     * @returns true when frame is complete, false if more data needed
     */
    addData(bufferList: Buffer): boolean;
    
    /**
     * Discard payload data without processing
     * @param bufferList - Buffer to discard data from
     */
    throwAwayPayload(bufferList: Buffer): void;
    
    /**
     * Convert frame to Buffer for transmission
     * @param nullMask - Use null mask instead of random mask
     * @returns Buffer containing complete frame
     */
    toBuffer(nullMask?: boolean): Buffer;
    
    /** String representation for debugging */
    toString(): string;
    
    /** Final fragment flag (true if this is the last fragment) */
    fin: boolean;
    
    /** Reserved flag 1 (must be false unless extension defines it) */
    rsv1: boolean;
    
    /** Reserved flag 2 (must be false unless extension defines it) */
    rsv2: boolean;
    
    /** Reserved flag 3 (must be false unless extension defines it) */
    rsv3: boolean;
    
    /** Mask flag (true if payload is masked) */
    mask: boolean;
    
    /** Frame opcode (0x0-0xF indicating frame type) */
    opcode: number;
    
    /** Payload length in bytes */
    length: number;
    
    /** Frame payload data as Buffer */
    binaryPayload: Buffer;
    
    /** Close frame status code (for close frames) */
    closeStatus: number;
    
    /** Protocol error flag */
    protocolError: boolean;
    
    /** Frame too large flag */
    frameTooLarge: boolean;
    
    /** Invalid close frame length flag */
    invalidCloseFrameLength: boolean;
}

Frame Processing Configuration:

interface FrameConfig {
    /** Maximum frame size in bytes */
    maxReceivedFrameSize?: number;
    
    /** Whether frames should be masked */
    maskOutgoingPackets?: boolean;
    
    /** Assemble fragmented frames */
    assembleFragments?: boolean;
}

interface FrameHeader {
    fin?: boolean;
    rsv1?: boolean;
    rsv2?: boolean;
    rsv3?: boolean;
    mask?: boolean;
    opcode?: number;
    length?: number;
}

Frame Opcodes:

const OPCODES = {
    CONTINUATION: 0x0,    // Continuation frame
    TEXT: 0x1,           // Text frame
    BINARY: 0x2,         // Binary frame
    // 0x3-0x7 reserved for future non-control frames
    CLOSE: 0x8,          // Close frame
    PING: 0x9,           // Ping frame
    PONG: 0xA            // Pong frame
    // 0xB-0xF reserved for future control frames
};

Usage Example:

const WebSocket = require('websocket');

// Create a text frame
const textFrame = new WebSocket.frame();
textFrame.opcode = 0x1; // Text frame
textFrame.fin = true;   // Final fragment
textFrame.binaryPayload = Buffer.from('Hello, World!', 'utf8');
textFrame.length = textFrame.binaryPayload.length;

// Convert to buffer for transmission
const frameBuffer = textFrame.toBuffer();
console.log('Frame buffer length:', frameBuffer.length);

// Parse incoming frame data
const incomingFrame = new WebSocket.frame();
const buffer = Buffer.from(/* frame data */);

// Add data and check if complete
const isComplete = incomingFrame.addData(buffer);
if (isComplete) {
    console.log('Frame complete');
    console.log('Opcode:', incomingFrame.opcode);
    console.log('Payload:', incomingFrame.binaryPayload.toString());
}

Frame Parsing Process

Frame Parsing States:

const FRAME_STATE = {
    DECODE_HEADER: 1,
    WAITING_FOR_16_BIT_LENGTH: 2,
    WAITING_FOR_64_BIT_LENGTH: 3,
    WAITING_FOR_MASK_KEY: 4,
    WAITING_FOR_PAYLOAD: 5,
    COMPLETE: 6
};

Manual Frame Processing:

function processFrameData(frameData) {
    const frame = new WebSocket.frame();
    
    let offset = 0;
    while (offset < frameData.length) {
        const chunk = frameData.slice(offset);
        const complete = frame.addData(chunk);
        
        if (complete) {
            console.log('Frame completed:');
            console.log('  Opcode:', frame.opcode);
            console.log('  FIN:', frame.fin);
            console.log('  Masked:', frame.mask);
            console.log('  Length:', frame.length);
            
            if (frame.protocolError) {
                console.error('Protocol error in frame');
                break;
            }
            
            if (frame.frameTooLarge) {
                console.error('Frame too large');
                break;
            }
            
            // Process payload based on opcode
            switch (frame.opcode) {
                case 0x1: // Text
                    const text = frame.binaryPayload.toString('utf8');
                    console.log('Text payload:', text);
                    break;
                    
                case 0x2: // Binary
                    console.log('Binary payload length:', frame.binaryPayload.length);
                    break;
                    
                case 0x8: // Close
                    console.log('Close frame, status:', frame.closeStatus);
                    break;
                    
                case 0x9: // Ping
                    console.log('Ping frame');
                    // Should respond with pong
                    break;
                    
                case 0xA: // Pong
                    console.log('Pong frame');
                    break;
            }
            
            break;
        }
        
        // If not complete, we need more data
        console.log('Frame incomplete, need more data');
        break;
    }
}

Frame Construction

Creating Different Frame Types:

// Text frame
function createTextFrame(text, isFinal = true) {
    const frame = new WebSocket.frame();
    frame.opcode = 0x1;
    frame.fin = isFinal;
    frame.binaryPayload = Buffer.from(text, 'utf8');
    frame.length = frame.binaryPayload.length;
    return frame;
}

// Binary frame
function createBinaryFrame(data, isFinal = true) {
    const frame = new WebSocket.frame();
    frame.opcode = 0x2;
    frame.fin = isFinal;
    frame.binaryPayload = Buffer.isBuffer(data) ? data : Buffer.from(data);
    frame.length = frame.binaryPayload.length;
    return frame;
}

// Close frame
function createCloseFrame(code = 1000, reason = '') {
    const frame = new WebSocket.frame();
    frame.opcode = 0x8;
    frame.fin = true;
    
    if (code || reason) {
        const reasonBuffer = Buffer.from(reason, 'utf8');
        const payload = Buffer.allocUnsafe(2 + reasonBuffer.length);
        payload.writeUInt16BE(code, 0);
        reasonBuffer.copy(payload, 2);
        frame.binaryPayload = payload;
        frame.length = payload.length;
        frame.closeStatus = code;
    } else {
        frame.binaryPayload = Buffer.allocUnsafe(0);
        frame.length = 0;
    }
    
    return frame;
}

// Ping frame
function createPingFrame(data = Buffer.allocUnsafe(0)) {
    const frame = new WebSocket.frame();
    frame.opcode = 0x9;
    frame.fin = true;
    frame.binaryPayload = Buffer.isBuffer(data) ? data : Buffer.from(data);
    frame.length = frame.binaryPayload.length;
    return frame;
}

// Pong frame
function createPongFrame(data = Buffer.allocUnsafe(0)) {
    const frame = new WebSocket.frame();
    frame.opcode = 0xA;
    frame.fin = true;
    frame.binaryPayload = Buffer.isBuffer(data) ? data : Buffer.from(data);
    frame.length = frame.binaryPayload.length;
    return frame;
}

Message Fragmentation

Creating Fragmented Messages:

function createFragmentedMessage(data, maxFrameSize = 1024) {
    const frames = [];
    const totalLength = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data, 'utf8');
    const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
    const isText = !Buffer.isBuffer(data);
    
    let offset = 0;
    let isFirst = true;
    
    while (offset < totalLength) {
        const remainingLength = totalLength - offset;
        const frameSize = Math.min(maxFrameSize, remainingLength);
        const isLast = offset + frameSize >= totalLength;
        
        const frame = new WebSocket.frame();
        
        if (isFirst) {
            // First frame uses TEXT or BINARY opcode
            frame.opcode = isText ? 0x1 : 0x2;
            isFirst = false;
        } else {
            // Continuation frames use CONTINUATION opcode
            frame.opcode = 0x0;
        }
        
        frame.fin = isLast;
        frame.binaryPayload = buffer.slice(offset, offset + frameSize);
        frame.length = frame.binaryPayload.length;
        
        frames.push(frame);
        offset += frameSize;
    }
    
    return frames;
}

// Usage
const largeMessage = 'A'.repeat(5000);
const fragments = createFragmentedMessage(largeMessage, 1024);

console.log(`Message fragmented into ${fragments.length} frames`);
fragments.forEach((frame, index) => {
    console.log(`Frame ${index}: opcode=${frame.opcode}, fin=${frame.fin}, length=${frame.length}`);
});

Advanced Frame Handling

Custom Frame Processing:

class CustomFrameProcessor {
    constructor() {
        this.pendingFragments = [];
    }
    
    processFrame(frame) {
        // Validate frame
        if (frame.protocolError) {
            throw new Error('Protocol error in frame');
        }
        
        if (frame.frameTooLarge) {
            throw new Error('Frame too large');
        }
        
        switch (frame.opcode) {
            case 0x0: // Continuation
                return this.handleContinuation(frame);
                
            case 0x1: // Text
                return this.handleText(frame);
                
            case 0x2: // Binary
                return this.handleBinary(frame);
                
            case 0x8: // Close
                return this.handleClose(frame);
                
            case 0x9: // Ping
                return this.handlePing(frame);
                
            case 0xA: // Pong
                return this.handlePong(frame);
                
            default:
                throw new Error('Unknown opcode: ' + frame.opcode);
        }
    }
    
    handleText(frame) {
        if (frame.fin) {
            // Complete text message
            return {
                type: 'text',
                data: frame.binaryPayload.toString('utf8')
            };
        } else {
            // Start of fragmented text message
            this.pendingFragments = [frame];
            return null; // Not complete yet
        }
    }
    
    handleContinuation(frame) {
        if (this.pendingFragments.length === 0) {
            throw new Error('Continuation frame without initial frame');
        }
        
        this.pendingFragments.push(frame);
        
        if (frame.fin) {
            // Reassemble message
            const firstFrame = this.pendingFragments[0];
            const totalLength = this.pendingFragments.reduce((sum, f) => sum + f.length, 0);
            const assembled = Buffer.allocUnsafe(totalLength);
            
            let offset = 0;
            this.pendingFragments.forEach(f => {
                f.binaryPayload.copy(assembled, offset);
                offset += f.length;
            });
            
            this.pendingFragments = [];
            
            return {
                type: firstFrame.opcode === 0x1 ? 'text' : 'binary',
                data: firstFrame.opcode === 0x1 ? assembled.toString('utf8') : assembled
            };
        }
        
        return null; // Still assembling
    }
    
    handleClose(frame) {
        let code = 1005; // No status code
        let reason = '';
        
        if (frame.length >= 2) {
            code = frame.binaryPayload.readUInt16BE(0);
            if (frame.length > 2) {
                reason = frame.binaryPayload.slice(2).toString('utf8');
            }
        }
        
        return {
            type: 'close',
            code: code,
            reason: reason
        };
    }
    
    handlePing(frame) {
        return {
            type: 'ping',
            data: frame.binaryPayload
        };
    }
    
    handlePong(frame) {
        return {
            type: 'pong',
            data: frame.binaryPayload
        };
    }
}

Types

Frame Structure

interface FrameStructure {
    fin: boolean;       // Final fragment flag
    rsv1: boolean;      // Reserved bit 1
    rsv2: boolean;      // Reserved bit 2  
    rsv3: boolean;      // Reserved bit 3
    mask: boolean;      // Mask flag
    opcode: number;     // Frame type (0x0-0xF)
    length: number;     // Payload length
    maskingKey?: Buffer; // 4-byte masking key (if masked)
    payload: Buffer;    // Frame payload
}

Opcode Types

type FrameOpcode = 
    | 0x0  // Continuation
    | 0x1  // Text
    | 0x2  // Binary
    | 0x8  // Close
    | 0x9  // Ping
    | 0xA; // Pong

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