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