UNPKG

@electrum-cash/web-socket

Version:

@electrum-cash/web-socket implements the ElectrumSocket interface using web sockets.

169 lines (156 loc) 8.14 kB
import {WebSocket as $dvphU$WebSocket} from "@monsterbitar/isomorphic-ws"; import {EventEmitter as $dvphU$EventEmitter} from "eventemitter3"; import $dvphU$electrumcashdebuglogs from "@electrum-cash/debug-logs"; function $parcel$export(e, n, v, s) { Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true}); } var $05743633fea447d4$exports = {}; $parcel$export($05743633fea447d4$exports, "ElectrumWebSocket", () => $05743633fea447d4$export$25b4633f61498e1); // Export a default timeout value of 30 seconds. const $d801b1f9b7fc3074$export$1bddf2b96e25d075 = 30000; class $05743633fea447d4$export$25b4633f61498e1 extends (0, $dvphU$EventEmitter) { host; port; encrypted; timeout; // Declare an empty WebSocket. webSocket; // Used to disconnect after some time if initial connection is too slow. disconnectTimer; // Initialize boolean that indicates whether the onConnect function has run (initialize to false). onConnectHasRun; // Initialize event forwarding functions. eventForwarders; /** * Creates a socket configured with connection information for a given Electrum server. * * @param host Fully qualified domain name or IP address of the host * @param port Network port for the host to connect to, defaults to the standard TLS port * @param encrypted If false, uses an unencrypted connection instead of the default on TLS * @param timeout If no connection is established after `timeout` ms, the connection is terminated */ constructor(host, port = 50004, encrypted = true, timeout = (0, $d801b1f9b7fc3074$export$1bddf2b96e25d075)){ // Initialize the event emitter. super(), this.host = host, this.port = port, this.encrypted = encrypted, this.timeout = timeout, this.onConnectHasRun = false, this.eventForwarders = { disconnect: ()=>this.emit('disconnected'), wsData: (event)=>this.emit('data', `${event.data}\n`), wsError: (event)=>this.emit('error', new Error(event.error)) }; } /** * Returns a string for the host identifier for usage in debug messages. */ get hostIdentifier() { return `${this.host}:${this.port}`; } /** * Connect to host:port using the specified transport */ connect() { // Check that no existing socket exists before initiating a new connection. if (this.webSocket) throw new Error('Cannot initiate a new socket connection when an existing connection exists'); // Set a timer to force disconnect after `timeout` seconds this.disconnectTimer = setTimeout(()=>this.disconnectOnTimeout(), this.timeout); // Remove the timer if a connection is successfully established this.once('connected', this.clearDisconnectTimerOnTimeout); // Set a named connection type for logging purposes. const connectionType = this.encrypted ? 'an encrypted WebSocket' : 'a WebSocket'; // Log that we are trying to establish a connection. (0, $dvphU$electrumcashdebuglogs).network(`Initiating ${connectionType} connection to '${this.host}:${this.port}'.`); if (this.encrypted) // Initialize this.webSocket (rejecting self-signed certificates). // We reject self-signed certificates to match functionality of browsers. this.webSocket = new (0, $dvphU$WebSocket)(`wss://${this.host}:${this.port}`); else // Initialize this.webSocket. this.webSocket = new (0, $dvphU$WebSocket)(`ws://${this.host}:${this.port}`); // Trigger successful connection events. this.webSocket.addEventListener('open', this.onConnect.bind(this)); // Forward the encountered errors. this.webSocket.addEventListener('error', this.eventForwarders.wsError); } /** * Sets up forwarding of events related to the connection. */ onConnect() { // If the onConnect function has already run, do not execute it again. if (this.onConnectHasRun) return; // Set a named connection type for logging purposes. const connectionType = this.encrypted ? 'an encrypted WebSocket' : 'a WebSocket'; // Log that the connection has been established. (0, $dvphU$electrumcashdebuglogs).network(`Established ${connectionType} connection with '${this.host}:${this.port}'.`); // Forward the socket events this.webSocket.addEventListener('close', this.eventForwarders.disconnect); this.webSocket.addEventListener('message', this.eventForwarders.wsData); // Indicate that the onConnect function has run. this.onConnectHasRun = true; // Emit the connect event. this.emit('connected'); } /** * Clears the disconnect timer if it is still active. */ clearDisconnectTimerOnTimeout() { // Clear the retry timer if it is still active. if (this.disconnectTimer) clearTimeout(this.disconnectTimer); } /** * Forcibly terminate the connection. * * @throws {Error} if no connection was found */ disconnect() { // Clear the disconnect timer so that the socket does not try to disconnect again later. this.clearDisconnectTimerOnTimeout(); try { // Remove all event forwarders. this.webSocket.removeEventListener('close', this.eventForwarders.disconnect); this.webSocket.removeEventListener('message', this.eventForwarders.wsData); this.webSocket.removeEventListener('error', this.eventForwarders.wsError); // Add a one-time event listener for potential error events after closing the socket. // NOTE: This is needed since the close() call might not result in an error itself, but the // underlying network packets that are sent in order to do a graceful termination can fail. this.webSocket.addEventListener('error', (ignored)=>{}, { once: true }); // Gracefully terminate the connection this.webSocket.close(); } catch (ignored) { // close() will throw an error if the connection has not been established yet. // We ignore this error, since no similar error gets thrown in the TLS Socket. } finally{ // Remove the stored socket regardless of any thrown errors. this.webSocket = undefined; } // Indicate that the onConnect function has not run and it has to be run again. this.onConnectHasRun = false; // Emit a disconnect event this.emit('disconnected'); } /** * Write data to the socket * * @param data Data to be written to the socket * @param callback Callback function to be called when the write has completed * * @throws {Error} if no connection was found * @returns true if the message was fully flushed to the socket, false if part of the message * is queued in the user memory */ write(data, callback) { // Throw an error if no active connection is found if (!this.webSocket) throw new Error('Cannot write to socket when there is no active connection'); // Write data to the WebSocket this.webSocket.send(data, callback); // WebSockets always fit everything in a single request, so we return true return true; } /** * Force a disconnection if no connection is established after `timeout` milliseconds. */ disconnectOnTimeout() { // Remove the connect listener. this.removeListener('connected', this.clearDisconnectTimerOnTimeout); // Emit an error event so that connect is rejected upstream. this.emit('error', new Error(`Connection to '${this.host}:${this.port}' timed out after ${this.timeout} milliseconds`)); // Forcibly disconnect to clean up the connection on timeout this.disconnect(); } // Add magic glue that makes typedoc happy so that we can have the events listed on the class. connected; disconnected; data; error; } export {$05743633fea447d4$export$25b4633f61498e1 as ElectrumWebSocket}; //# sourceMappingURL=index.mjs.map