feat(protocols): refactor protocol utilities into centralized protocols module
This commit is contained in:
60
ts/protocols/websocket/constants.ts
Normal file
60
ts/protocols/websocket/constants.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* WebSocket Protocol Constants
|
||||
* Based on RFC 6455
|
||||
*/
|
||||
|
||||
/**
|
||||
* WebSocket opcode types
|
||||
*/
|
||||
export enum WebSocketOpcode {
|
||||
CONTINUATION = 0x0,
|
||||
TEXT = 0x1,
|
||||
BINARY = 0x2,
|
||||
CLOSE = 0x8,
|
||||
PING = 0x9,
|
||||
PONG = 0xa,
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket close codes
|
||||
*/
|
||||
export enum WebSocketCloseCode {
|
||||
NORMAL_CLOSURE = 1000,
|
||||
GOING_AWAY = 1001,
|
||||
PROTOCOL_ERROR = 1002,
|
||||
UNSUPPORTED_DATA = 1003,
|
||||
NO_STATUS_RECEIVED = 1005,
|
||||
ABNORMAL_CLOSURE = 1006,
|
||||
INVALID_FRAME_PAYLOAD_DATA = 1007,
|
||||
POLICY_VIOLATION = 1008,
|
||||
MESSAGE_TOO_BIG = 1009,
|
||||
MISSING_EXTENSION = 1010,
|
||||
INTERNAL_ERROR = 1011,
|
||||
SERVICE_RESTART = 1012,
|
||||
TRY_AGAIN_LATER = 1013,
|
||||
BAD_GATEWAY = 1014,
|
||||
TLS_HANDSHAKE = 1015,
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket protocol version
|
||||
*/
|
||||
export const WEBSOCKET_VERSION = 13;
|
||||
|
||||
/**
|
||||
* WebSocket magic string for handshake
|
||||
*/
|
||||
export const WEBSOCKET_MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||
|
||||
/**
|
||||
* WebSocket headers
|
||||
*/
|
||||
export const WEBSOCKET_HEADERS = {
|
||||
UPGRADE: 'upgrade',
|
||||
CONNECTION: 'connection',
|
||||
SEC_WEBSOCKET_KEY: 'sec-websocket-key',
|
||||
SEC_WEBSOCKET_VERSION: 'sec-websocket-version',
|
||||
SEC_WEBSOCKET_ACCEPT: 'sec-websocket-accept',
|
||||
SEC_WEBSOCKET_PROTOCOL: 'sec-websocket-protocol',
|
||||
SEC_WEBSOCKET_EXTENSIONS: 'sec-websocket-extensions',
|
||||
} as const;
|
8
ts/protocols/websocket/index.ts
Normal file
8
ts/protocols/websocket/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* WebSocket Protocol Module
|
||||
* WebSocket protocol utilities and constants
|
||||
*/
|
||||
|
||||
export * from './constants.js';
|
||||
export * from './types.js';
|
||||
export * from './utils.js';
|
53
ts/protocols/websocket/types.ts
Normal file
53
ts/protocols/websocket/types.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* WebSocket Protocol Type Definitions
|
||||
*/
|
||||
|
||||
import type { WebSocketOpcode, WebSocketCloseCode } from './constants.js';
|
||||
|
||||
/**
|
||||
* WebSocket frame header
|
||||
*/
|
||||
export interface IWebSocketFrameHeader {
|
||||
fin: boolean;
|
||||
rsv1: boolean;
|
||||
rsv2: boolean;
|
||||
rsv3: boolean;
|
||||
opcode: WebSocketOpcode;
|
||||
masked: boolean;
|
||||
payloadLength: number;
|
||||
maskingKey?: Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket frame
|
||||
*/
|
||||
export interface IWebSocketFrame {
|
||||
header: IWebSocketFrameHeader;
|
||||
payload: Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket close frame payload
|
||||
*/
|
||||
export interface IWebSocketClosePayload {
|
||||
code: WebSocketCloseCode;
|
||||
reason?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket handshake request headers
|
||||
*/
|
||||
export interface IWebSocketHandshakeHeaders {
|
||||
upgrade: string;
|
||||
connection: string;
|
||||
'sec-websocket-key': string;
|
||||
'sec-websocket-version': string;
|
||||
'sec-websocket-protocol'?: string;
|
||||
'sec-websocket-extensions'?: string;
|
||||
[key: string]: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type for WebSocket raw data (matching ws library)
|
||||
*/
|
||||
export type RawData = Buffer | ArrayBuffer | Buffer[] | any;
|
98
ts/protocols/websocket/utils.ts
Normal file
98
ts/protocols/websocket/utils.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* WebSocket Protocol Utilities
|
||||
*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import { WEBSOCKET_MAGIC_STRING } from './constants.js';
|
||||
import type { RawData } from './types.js';
|
||||
|
||||
/**
|
||||
* Get the length of a WebSocket message regardless of its type
|
||||
* (handles all possible WebSocket message data types)
|
||||
*/
|
||||
export function getMessageSize(data: RawData): number {
|
||||
if (typeof data === 'string') {
|
||||
// For string data, get the byte length
|
||||
return Buffer.from(data, 'utf8').length;
|
||||
} else if (data instanceof Buffer) {
|
||||
// For Node.js Buffer
|
||||
return data.length;
|
||||
} else if (data instanceof ArrayBuffer) {
|
||||
// For ArrayBuffer
|
||||
return data.byteLength;
|
||||
} else if (Array.isArray(data)) {
|
||||
// For array of buffers, sum their lengths
|
||||
return data.reduce((sum, chunk) => {
|
||||
if (chunk instanceof Buffer) {
|
||||
return sum + chunk.length;
|
||||
} else if (chunk instanceof ArrayBuffer) {
|
||||
return sum + chunk.byteLength;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
} else {
|
||||
// For other types, try to determine the size or return 0
|
||||
try {
|
||||
return Buffer.from(data).length;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert any raw WebSocket data to Buffer for consistent handling
|
||||
*/
|
||||
export function toBuffer(data: RawData): Buffer {
|
||||
if (typeof data === 'string') {
|
||||
return Buffer.from(data, 'utf8');
|
||||
} else if (data instanceof Buffer) {
|
||||
return data;
|
||||
} else if (data instanceof ArrayBuffer) {
|
||||
return Buffer.from(data);
|
||||
} else if (Array.isArray(data)) {
|
||||
// For array of buffers, concatenate them
|
||||
return Buffer.concat(data.map(chunk => {
|
||||
if (chunk instanceof Buffer) {
|
||||
return chunk;
|
||||
} else if (chunk instanceof ArrayBuffer) {
|
||||
return Buffer.from(chunk);
|
||||
}
|
||||
return Buffer.from(chunk);
|
||||
}));
|
||||
} else {
|
||||
// For other types, try to convert to Buffer or return empty Buffer
|
||||
try {
|
||||
return Buffer.from(data);
|
||||
} catch (e) {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate WebSocket accept key from client key
|
||||
*/
|
||||
export function generateAcceptKey(clientKey: string): string {
|
||||
const hash = crypto.createHash('sha1');
|
||||
hash.update(clientKey + WEBSOCKET_MAGIC_STRING);
|
||||
return hash.digest('base64');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate WebSocket upgrade request
|
||||
*/
|
||||
export function isWebSocketUpgrade(headers: Record<string, string>): boolean {
|
||||
const upgrade = headers['upgrade'];
|
||||
const connection = headers['connection'];
|
||||
|
||||
return upgrade?.toLowerCase() === 'websocket' &&
|
||||
connection?.toLowerCase().includes('upgrade');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random WebSocket key for client handshake
|
||||
*/
|
||||
export function generateWebSocketKey(): string {
|
||||
return crypto.randomBytes(16).toString('base64');
|
||||
}
|
Reference in New Issue
Block a user