UNPKG

@lily-technologies/electrum-client

Version:

Electrum protocol client for React Native & Node.js

247 lines (215 loc) 5.85 kB
'use strict'; let net = require('net'); let tls = require('tls'); const TIMEOUT = 5000; const socks = require('socks'); const TlsSocketWrapper = require('./TlsSocketWrapper.js'); const EventEmitter = require('events').EventEmitter; const util = require('./util'); class Client { constructor(port, host, protocol, options, callbacks) { this.id = 0; this.port = port; this.host = host; this.callback_message_queue = {}; this.subscribe = new EventEmitter(); this.mp = new util.MessageParser((body, n) => { this.onMessage(body, n); }); this._protocol = protocol; // saving defaults this._options = options; this.onErrorCallback = (callbacks && callbacks.onError) ? callbacks.onError : null; this.initSocket(protocol, options); } initSocket(protocol, options) { protocol = protocol || this._protocol; options = options || this._options; switch (protocol) { case 'tcp': this.conn = new net.Socket(); break; case 'tls': case 'ssl': if (!tls) { throw new Error("Package 'tls' not available"); } this.conn = new TlsSocketWrapper(tls); break; default: throw new Error('unknown protocol'); } this.conn.setTimeout(TIMEOUT); this.conn.setEncoding('utf8'); this.conn.setKeepAlive(true, 0); this.conn.setNoDelay(true); this.conn.on('connect', () => { this.conn.setTimeout(0); this.onConnect(); }); this.conn.on('close', e => { this.onClose(e); }); this.conn.on('data', chunk => { this.conn.setTimeout(0); this.onRecv(chunk); }); this.conn.on('error', e => { this.onError(e); }); this.status = 0; } connect() { if (this.status === 1) { return Promise.resolve(); } this.status = 1; if (this._options && this._options.proxy && this._options.proxy.port) { this.conn = socks.SocksClient.createConnection({command: "connect", destination: {port: this.port, host: this.host }, ...this._options}).then(client => { return this.initSocksProxy(client) }); return this.conn; } else { return this.connectSocket(this.conn, this.port, this.host); } } connectSocket(conn, port, host) { return new Promise((resolve, reject) => { const errorHandler = e => reject(e); conn.on('error', errorHandler); conn.connect(port, host, () => { conn.removeListener('error', errorHandler); resolve(); }); }); } initSocksProxy(client) { client.socket.setTimeout(TIMEOUT); client.socket.setEncoding('utf8'); client.socket.setKeepAlive(true, 0); client.socket.setNoDelay(true); client.socket.on('connect', () => { client.socket.setTimeout(0); this.onConnect(); }); client.socket.on('close', e => { this.onClose(e); }); client.socket.on('data', chunk => { client.socket.setTimeout(0); this.onRecv(chunk); }); client.socket.on('error', e => { this.onError(e); }); return client.socket; } close() { if (this.status === 0) { return; } this.conn.end(); this.conn.destroy(); this.status = 0; } request(method, params) { if (this.status === 0) { return Promise.reject(new Error('Connection to server lost, please retry')); } return new Promise((resolve, reject) => { const id = ++this.id; const content = util.makeRequest(method, params, id); this.callback_message_queue[id] = util.createPromiseResult(resolve, reject); if (this._options && this._options.proxy && this._options.proxy.port) { this.conn.then(socket => { socket.write(content + '\n'); socket.resume(); }); } else { this.conn.write(content + '\n'); } }); } requestBatch(method, params, secondParam) { if (this.status === 0) { return Promise.reject(new Error('Connection to server lost, please retry')); } return new Promise((resolve, reject) => { let arguments_far_calls = {}; let contents = []; for (let param of params) { const id = ++this.id; if (secondParam !== undefined) { contents.push(util.makeRequest(method, [param, secondParam], id)); } else { contents.push(util.makeRequest(method, [param], id)); } arguments_far_calls[id] = param; } const content = '[' + contents.join(',') + ']'; this.callback_message_queue[this.id] = util.createPromiseResultBatch(resolve, reject, arguments_far_calls); if (this._options && this._options.proxy && this._options.proxy.port) { this.conn.then(socket => { socket.write(content + '\n'); socket.resume(); }); } else { // callback will exist only for max id this.conn.write(content + '\n'); } }); } response(msg) { let callback; if (!msg.id && msg[0] && msg[0].id) { // this is a response from batch request for (let m of msg) { if (m.id && this.callback_message_queue[m.id]) { callback = this.callback_message_queue[m.id]; delete this.callback_message_queue[m.id]; } } } else { callback = this.callback_message_queue[msg.id]; } if (callback) { delete this.callback_message_queue[msg.id]; if (msg.error) { callback(msg.error); } else { callback(null, msg.result || msg); } } else { throw new Error("Error getting callback while handling response"); } } onMessage(body, n) { const msg = JSON.parse(body); if (msg instanceof Array) { this.response(msg); } else { if (msg.id !== void 0) { this.response(msg); } else { this.subscribe.emit(msg.method, msg.params); } } } onConnect() { } onClose(e) { this.status = 0; Object.keys(this.callback_message_queue).forEach(key => { this.callback_message_queue[key](new Error('close connect')); delete this.callback_message_queue[key]; }); } onRecv(chunk) { this.mp.run(chunk); } onError(e) { if (this.onErrorCallback != null) { this.onErrorCallback(e); } } } module.exports = Client;