feat(protocols): refactor protocol utilities into centralized protocols module
Some checks failed
Default (tags) / security (push) Successful in 55s
Default (tags) / test (push) Failing after 30m45s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped

This commit is contained in:
Juergen Kunz
2025-07-21 22:37:45 +00:00
parent d47b048517
commit 36068a6d92
32 changed files with 1155 additions and 394 deletions

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