openchain-sdk-yxl
Version:
openchain sdk
180 lines (152 loc) • 5.34 kB
JavaScript
'use strict';
let WebSocket;
if (typeof window !== 'undefined' && window.WebSocket) {
WebSocket = window.WebSocket;
} else {
WebSocket = require('ws');
}
const protobuf = require('protobufjs');
const path = require('path');
const EventEmitter = require('events');
const { Buffer } = require('buffer');
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 {
const protoPath = path.join(__dirname, '../crypto/protobuf/bundle.json');
this.root = await protobuf.load(protoPath);
} 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;
const handleOpen = () => {
this.connected = true;
this.reconnectAttempts = 0;
this.emit('connected');
this.sendHello();
};
const handleMessage = (event) => {
try {
this.handleMessage(event.data || event);
} catch (error) {
this.emit('error', new Error('Failed to handle message: ' + error.message));
}
};
const handleClose = () => {
this.connected = false;
this.emit('disconnected');
this.handleReconnect();
};
const handleError = (error) => {
this.emit('error', error);
};
if (typeof window !== 'undefined' && window.WebSocket) {
this.ws.addEventListener('open', handleOpen);
this.ws.addEventListener('message', handleMessage);
this.ws.addEventListener('close', handleClose);
this.ws.addEventListener('error', handleError);
} else {
this.ws.on('open', handleOpen);
this.ws.on('message', handleMessage);
this.ws.on('close', handleClose);
this.ws.on('error', handleError);
}
}
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;
}
}
}
module.exports = WebSocketClient;