@electrum-cash/web-socket
Version:
@electrum-cash/web-socket implements the ElectrumSocket interface using web sockets.
169 lines (156 loc) • 8.14 kB
JavaScript
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