Websocket Client & Server Library implementing the WebSocket protocol as specified in RFC 6455
—
Low-level WebSocket frame parsing and construction for advanced use cases requiring direct protocol frame manipulation and custom frame processing.
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 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;
}
}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;
}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}`);
});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
};
}
}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
}type FrameOpcode =
| 0x0 // Continuation
| 0x1 // Text
| 0x2 // Binary
| 0x8 // Close
| 0x9 // Ping
| 0xA; // PongInstall with Tessl CLI
npx tessl i tessl/npm-websocket