UNPKG

chia-network-scanner

Version:
133 lines (114 loc) 4.03 kB
import WebSocket from 'ws'; import { log } from './log'; interface MessageChannelOptions { networkId: string; protocolVersion: string; softwareVersion: string; nodeType: number; hostname: string; port: number; cert: Buffer; key: Buffer; onMessage: (message: Buffer) => void; connectionTimeout: number; } // Todo: proper message types interface Message { f: string; d: any; } class MessageChannel { private readonly onMessage: (message: Buffer) => void; private ws: WebSocket | null = null; private inboundDataBuffer: Buffer = Buffer.from([]); public readonly hostname: string; public readonly port: number; public readonly connectionTimeout: number; public readonly networkId: string; public readonly protocolVersion: string; public readonly softwareVersion: string; public readonly nodeType: number; public readonly cert: Buffer; public readonly key: Buffer; public constructor({ networkId, protocolVersion, softwareVersion, nodeType, hostname, port, onMessage, connectionTimeout, cert, key }: MessageChannelOptions) { this.networkId = networkId; this.protocolVersion = protocolVersion; this.softwareVersion = softwareVersion; this.nodeType = nodeType; this.hostname = hostname; this.port = port; this.onMessage = onMessage; this.connectionTimeout = connectionTimeout; this.cert = cert; this.key = key; } public async connect(): Promise<void> { return new Promise(resolve => { log.info(`Attempting websocket connection to wss://${this.hostname}:${this.port}/ws`); const ipv6 = this.hostname.includes(':'); const url = ipv6 ? `wss://[${this.hostname}]:${this.port}/ws` : `wss://${this.hostname}:${this.port}/ws`; this.ws = new WebSocket(url, { rejectUnauthorized: false, cert: this.cert, key: this.key }); this.ws.on('message', (data: Buffer): void => this.messageHandler(data)); this.ws.on('error', (err: Error): void => this.onClose(err)); this.ws.on('close', (_, reason) => this.onClose(new Error(reason))); this.ws.on('open', () => { this.onConnected(); resolve(); }); }); } public sendMessage(message: Buffer): void { this.ws?.send(message); } public close(): void { this.ws?.close(); } private messageHandler(data: Buffer): void { this.inboundDataBuffer = Buffer.concat([this.inboundDataBuffer, data]); // Buffer is big enough to contain the length if (this.inboundDataBuffer.byteLength >= 5) { const messageType = data[0]; const haveMessageId = data.readUInt8(1); const messageLength = haveMessageId > 0 ? data.readUInt32BE(4) : data.readUInt32BE(2); const messageReady = data.byteLength === messageLength + 6; const bufferOverflow = data.byteLength > messageLength + 6; if (messageReady) { this.onMessage(this.inboundDataBuffer); this.inboundDataBuffer = Buffer.from([]); } else if (bufferOverflow) { // Very basic protection against badly developed or malicious peers // Depending on what they are doing this could happen many times in a row but should eventually recover this.inboundDataBuffer = Buffer.from([]); } } } private onConnected(): void { log.info(`Established websocket connection to Chia node ${this.hostname}:${this.port}`); } private onClose(err?: Error): void { log.info(err || {}, 'Connection closed') } } export { MessageChannel, Message };