UNPKG

openchain-sdk-yxl-ts

Version:

OpenChain SDK for browser

161 lines (135 loc) 4.69 kB
import { EventEmitter } from 'events'; import protobuf from 'protobufjs'; import { Buffer } from 'buffer'; import WebSocket from './browser-ws.js'; // 导入预编译的protobuf定义 import protoBundle from '../crypto/protobuf/bundle.js'; export default class WebSocketClient extends EventEmitter { constructor(options = {}) { super(); this.url = options.url || 'ws://localhost:17002'; this.autoReconnect = options.autoReconnect !== false; this.reconnectInterval = options.reconnectInterval || 5000; this.maxReconnectAttempts = options.maxReconnectAttempts || 10; this.subscriptions = new Set(); this.reconnectAttempts = 0; this.connected = false; this.ws = null; this.root = null; // 初始化protobuf this.initProtobuf(); } async initProtobuf() { try { // 直接使用导入的JSON定义 this.root = protobuf.Root.fromJSON(protoBundle); } catch (error) { this.emit('error', new Error('Failed to initialize protobuf: ' + error.message)); } } connect() { if (this.ws) { return; } try { this.ws = new WebSocket(this.url); this.bindEvents(); } catch (error) { this.emit('error', new Error('Failed to create WebSocket connection: ' + error.message)); } } bindEvents() { if (!this.ws) return; this.ws.on('open', () => { this.connected = true; this.reconnectAttempts = 0; this.emit('connected'); this.sendHello(); }); this.ws.on('message', (data) => { try { this.handleMessage(data); } catch (error) { this.emit('error', new Error('Failed to handle message: ' + error.message)); } }); this.ws.on('close', () => { this.connected = false; this.emit('disconnected'); this.handleReconnect(); }); this.ws.on('error', (error) => { this.emit('error', error); }); } handleReconnect() { if (!this.autoReconnect || this.reconnectAttempts >= this.maxReconnectAttempts) { return; } setTimeout(() => { this.reconnectAttempts++; this.connect(); }, this.reconnectInterval); } async sendHello() { const WsMessage = this.root.lookupType('protocol.WsMessage'); const message = WsMessage.create({ type: 1, // CHAIN_HELLO data: Buffer.from('Hello') }); this.send(WsMessage.encode(message).finish()); } async subscribeTx(addresses) { if (!Array.isArray(addresses) || addresses.length === 0) { throw new Error('Addresses must be a non-empty array'); } if (addresses.length > 100) { throw new Error('Cannot subscribe to more than 100 addresses'); } const WsMessage = this.root.lookupType('protocol.WsMessage'); const ChainSubscribeTx = this.root.lookupType('protocol.ChainSubscribeTx'); const subscribeMessage = ChainSubscribeTx.create({ address: addresses }); const message = WsMessage.create({ type: 4, // CHAIN_SUBSCRIBE_TX data: ChainSubscribeTx.encode(subscribeMessage).finish() }); addresses.forEach(addr => this.subscriptions.add(addr)); this.send(WsMessage.encode(message).finish()); } async handleMessage(data) { const WsMessage = this.root.lookupType('protocol.WsMessage'); const message = WsMessage.decode(new Uint8Array(data)); switch (message.type) { case 1: // CHAIN_HELLO response this.emit('hello', message); break; case 4: // CHAIN_SUBSCRIBE_TX response this.emit('subscription', message); break; case 5: // CHAIN_TX_ENV_STORE this.emit('transaction', message); break; default: this.emit('message', message); } } send(data) { if (!this.connected) { throw new Error('WebSocket is not connected'); } try { this.ws.send(data); } catch (error) { this.emit('error', new Error('Failed to send message: ' + error.message)); } } close() { if (this.ws) { this.autoReconnect = false; this.ws.close(); this.ws = null; } } }