From aa73cd8bb3f2d6aefd7c054cb08d5bf5151d1cbb Mon Sep 17 00:00:00 2001 From: Overtorment Date: Thu, 26 Mar 2020 15:04:02 +0000 Subject: [PATCH] added TLS support --- index.js | 10 +++- lib/TlsSocket.js | 3 - lib/TlsSocketWrapper.js | 126 ++++++++++++++++++++++++++++++++++++++++ lib/client.js | 14 +++-- package.json | 2 +- 5 files changed, 144 insertions(+), 11 deletions(-) delete mode 100644 lib/TlsSocket.js create mode 100644 lib/TlsSocketWrapper.js diff --git a/index.js b/index.js index 9ba4373..15cc300 100644 --- a/index.js +++ b/index.js @@ -67,8 +67,16 @@ class ElectrumClient extends Client { }, 10000); } + close() { + super.close(); + if (this.timeout != null) { + clearTimeout(this.timeout); + } + this.reconnect = this.onClose = this.keepAlive = () => {}; // dirty hack to make it stop reconnecting + } + reconnect() { - this.initSocketConnection(); + this.initSocket(); return this.initElectrum(this.electrumConfig); } diff --git a/lib/TlsSocket.js b/lib/TlsSocket.js deleted file mode 100644 index ec9a00b..0000000 --- a/lib/TlsSocket.js +++ /dev/null @@ -1,3 +0,0 @@ -class TlsSocket { - constructor() {} -} diff --git a/lib/TlsSocketWrapper.js b/lib/TlsSocketWrapper.js new file mode 100644 index 0000000..e8182f1 --- /dev/null +++ b/lib/TlsSocketWrapper.js @@ -0,0 +1,126 @@ +/** + * Simple wrapper to mimick Socket class from NET package, since TLS package havs slightly different API. + * We implement several methods that TCP sockets are expected to have. We will proxy call them as soon as + * realt TLS socket will be created (TLS socket created after connection). + */ +class TlsSocketWrapper { + constructor(tls) { + this._tls = tls; // dependency injection lol + this._socket = false; + // defaults: + this._timeout = 10000; + this._encoding = 'utf8'; + this._keepAliveEneblad = true; + this._keepAliveinitialDelay = 0; + this._noDelay = true; + this._listeners = {}; + } + + setTimeout(timeout) { + this._timeout = timeout; + } + + setEncoding(encoding) { + this._encoding = encoding; + } + + setKeepAlive(enabled, initialDelay) { + this._keepAliveEneblad = enabled; + this._keepAliveinitialDelay = initialDelay; + } + + setNoDelay(noDelay) { + this._noDelay = noDelay; + } + + on(event, listener) { + this._listeners[event] = this._listeners[event] || []; + this._listeners[event].push(listener); + } + + removeListener(event, listener) { + this._listeners[event] = this._listeners[event] || []; + let newListeners = []; + + let found = false; + for (let savedListener of this._listeners[event]) { + if (savedListener == listener) { + // found our listener + found = true; + // we just skip it + } else { + // other listeners should go back to original array + newListeners.push(savedListener); + } + } + + if (found) { + this._listeners[event] = newListeners; + } else { + // something went wrong, lets just cleanup all listeners + this._listeners[event] = []; + } + } + + connect(port, host, callback) { + // resulting TLSSocket extends + this._socket = tls.connect({ port: port, host: host, rejectUnauthorized: false }, () => { + // socket.write('{ "id": 5, "method": "blockchain.estimatefee", "params": [2] }\n') + console.log('TLS Connected to ', host, port); + return callback(); + }); + + // setting everything that was set to this proxy class + + this._socket.setTimeout(this._timeout); + this._socket.setEncoding(this._encoding); + this._socket.setKeepAlive(this._keepAliveEneblad, this._keepAliveinitialDelay); + this._socket.setNoDelay(this._noDelay); + + // resubscribing to events on newly created socket so we could proxy them to already established listeners + + this._socket.on('data', data => { + this._passOnEvent('data', data); + }); + this._socket.on('end', data => { + this._passOnEvent('end', data); + }); + this._socket.on('timeout', () => { + this._passOnEvent('timeout'); + }); + this._socket.on('error', data => { + this._passOnEvent('error', data); + }); + this._socket.on('close', data => { + this._passOnEvent('close', data); + }); + this._socket.on('connect', data => { + this._passOnEvent('connect', data); + }); + } + + _passOnEvent(event, data) { + this._listeners[event] = this._listeners[event] || []; + for (let savedListener of this._listeners[event]) { + savedListener(data); + } + } + + emit(event, data) { + this._socket.emit(event, data); + } + + end() { + this._socket.end(); + } + + destroy() { + this._socket.destroy(); + } + + write(data) { + this._socket.write(data); + } +} + +module.exports = TlsSocketWrapper; diff --git a/lib/client.js b/lib/client.js index 4716da7..cb00397 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1,17 +1,19 @@ 'use strict'; /** - * expecting NET to be injected from outside: + * expecting NET & TLS to be injected from outside: * for RN it should be in shim.js: * global.net = require('react-native-tcp'); + * global.tls = require('react-native-tcp/tls'); * * for nodejs tests it should be provided before tests: * global.net = require('net'); + * global.tls = require('tls'); * */ let net = global.net; - +let tls = global.tls; const TIMEOUT = 10000; -const TlsSocket = require('./TlsSocket.js'); +const TlsSocketWrapper = require('./TlsSocketWrapper.js'); const EventEmitter = require('events').EventEmitter; const util = require('./util'); @@ -25,10 +27,10 @@ class Client { this.mp = new util.MessageParser((body, n) => { this.onMessage(body, n); }); - this.initSocketConnection(protocol, options); + this.initSocket(protocol, options); } - initSocketConnection(protocol, options) { + initSocket(protocol, options) { switch (protocol) { case 'tcp': this.conn = new net.Socket(); @@ -38,7 +40,7 @@ class Client { if (!tls) { throw new Error('tls package could not be loaded'); } - this.conn = {}; // TODO + this.conn = new TlsSocketWrapper(tls); break; default: throw new Error('unknown protocol'); diff --git a/package.json b/package.json index a28dd4d..36ac629 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "electrum-client", - "version": "1.0.0", + "version": "1.1.0", "description": "Electrum protocol client for React Native & Node.js", "main": "index.js", "scripts": {