{"version":3,"file":"index.mjs","names":[],"sources":["../source/electrum-protocol.ts","../source/rpc-interfaces.ts","../source/enums.ts","../source/interfaces.ts","../source/electrum-connection.ts","../source/constants.ts","../source/electrum-client.ts"],"sourcesContent":["import type { RPCParameter } from './rpc-interfaces.ts';\n\n/**\n * Grouping of utilities that simplifies implementation of the Electrum protocol.\n *\n * @ignore\n */\nexport class ElectrumProtocol\n{\n\t/**\n\t * Helper function that builds an Electrum request object.\n\t *\n\t * @param method     - method to call.\n\t * @param parameters - method parameters for the call.\n\t * @param requestId  - unique string or number referencing this request.\n\t *\n\t * @returns a properly formatted Electrum request string.\n\t */\n\tstatic buildRequestObject(method: string, parameters: RPCParameter[], requestId: string | number): string\n\t{\n\t\t// Return the formatted request object.\n\t\t// NOTE: Electrum either uses JsonRPC strictly or loosely.\n\t\t//       If we specify protocol identifier without being 100% compliant, we risk being disconnected/blacklisted.\n\t\t//       For this reason, we omit the protocol identifier to avoid issues.\n\t\treturn JSON.stringify({ method: method, params: parameters, id: requestId });\n\t}\n\n\t/**\n\t * Constant used to verify if a provided string is a valid version number.\n\t *\n\t * @returns a regular expression that matches valid version numbers.\n\t */\n\tstatic get versionRegexp(): RegExp\n\t{\n\t\treturn /^\\d+(\\.\\d+)+$/;\n\t}\n\n\t/**\n\t * Constant used to separate statements/messages in a stream of data.\n\t *\n\t * @returns the delimiter used by Electrum to separate statements.\n\t */\n\tstatic get statementDelimiter(): string\n\t{\n\t\treturn '\\n';\n\t}\n}\n","// Acceptable parameter types for RPC messages\nexport type RPCParameter = string | number | boolean | object | null;\n\n// Acceptable identifier types for RCP messages.\nexport type RCPIdentifier = number | string | null;\n\n// The base type for all RPC messages\nexport interface RPCBase\n{\n\tjsonrpc: string;\n}\n\n// An RPC message that sends a notification requiring no response\nexport interface RPCNotification extends RPCBase\n{\n\tmethod: string;\n\tparams?: RPCParameter[];\n}\n\n// An RPC message that sends a request requiring a response\nexport interface RPCRequest extends RPCBase\n{\n\tid: RCPIdentifier;\n\tmethod: string;\n\tparams?: RPCParameter[];\n}\n\n// An RPC message that returns the response to a successful request\nexport interface RPCStatement extends RPCBase\n{\n\tid: RCPIdentifier;\n\tresult: string;\n}\n\nexport interface RPCError\n{\n\tcode: number;\n\tmessage: string;\n\tdata?: unknown;\n}\n\n// An RPC message that returns the error to an unsuccessful request\nexport interface RPCErrorResponse extends RPCBase\n{\n\tid: RCPIdentifier;\n\terror: RPCError;\n}\n\n// A response to a request is either a statement (successful) or an error (unsuccessful)\nexport type RPCResponse = RPCErrorResponse | RPCStatement | RPCNotification;\n\n// RPC messages are notifications, requests, or responses\nexport type RPCMessage = RPCNotification | RPCRequest | RPCResponse;\n\n// Requests and responses can also be sent in batches\nexport type RPCResponseBatch = RPCResponse[];\nexport type RPCRequestBatch = RPCRequest[];\n\nexport const isRPCErrorResponse = function(message: RPCBase): message is RPCErrorResponse\n{\n\treturn 'id' in message && 'error' in message;\n};\n\nexport const isRPCStatement = function(message: RPCBase): message is RPCStatement\n{\n\treturn 'id' in message && 'result' in message;\n};\n\nexport const isRPCNotification = function(message: RPCBase): message is RPCNotification\n{\n\treturn !('id' in message) && 'method' in message;\n};\n\nexport const isRPCRequest = function(message: RPCBase): message is RPCRequest\n{\n\treturn 'id' in message && 'method' in message;\n};\n","/**\n * Enum that denotes the connection status of an ElectrumConnection.\n * @enum {number}\n * @property {0} DISCONNECTED    The connection is disconnected.\n * @property {1} AVAILABLE       The connection is connected.\n * @property {2} DISCONNECTING   The connection is disconnecting.\n * @property {3} CONNECTING      The connection is connecting.\n * @property {4} RECONNECTING    The connection is restarting.\n */\nexport enum ConnectionStatus\n{\n\tDISCONNECTED = 0,\n\tCONNECTED = 1,\n\tDISCONNECTING = 2,\n\tCONNECTING = 3,\n\tRECONNECTING = 4,\n}\n","import type { RPCError, RPCParameter, RPCResponse, RPCNotification } from './rpc-interfaces';\nimport type { ElectrumWebSocketReconnectionOptions } from '@electrum-cash/web-socket';\nimport type { EventEmitter } from 'eventemitter3';\n\n/**\n * Optional settings that change the default behavior of the network connection.\n */\nexport interface ElectrumNetworkOptions extends Partial<ElectrumWebSocketReconnectionOptions>\n{\n\t/** If set to true, numbers that can safely be parsed as integers will be `BigInt` rather than `Number`. */\n\tuseBigInt?: boolean;\n\n\t/** When connected, send a keep-alive Ping message this often. */\n\tsendKeepAliveIntervalInMilliSeconds?: number;\n\n\t/** When disconnected, attempt to reconnect after this amount of time. */\n\treconnectAfterMilliSeconds?: number;\n\n\t/** After every send, verify that we have received data after this amount of time. */\n\tverifyConnectionTimeoutInMilliSeconds?: number;\n}\n\n/**\n * List of events emitted by the ElectrumSocket.\n * @event\n * @ignore\n */\nexport interface ElectrumSocketEvents\n{\n\t/**\n\t * Emitted when data has been received over the socket.\n\t * @eventProperty\n\t */\n\t'data': [ string ];\n\n\t/**\n\t * Emitted when a socket connects.\n\t * @eventProperty\n\t */\n\t'connected': [];\n\n\t/**\n\t * Emitted when a socket disconnects.\n\t * @eventProperty\n\t */\n\t'disconnected': [];\n\n\t/**\n\t * Emitted when the socket has failed in some way.\n\t * @eventProperty\n\t */\n\t'error': [ Error ];\n}\n\n/**\n * Abstract socket used when communicating with Electrum servers.\n */\nexport interface ElectrumSocket extends EventEmitter<ElectrumSocketEvents>, ElectrumSocketEvents\n{\n\t/**\n\t * Utility function to provide a human accessible host identifier.\n\t */\n\tget hostIdentifier(): string;\n\n\t/**\n\t * Fully qualified domain name or IP address of the host\n\t */\n\thost: string;\n\n\t/**\n\t * Network port for the host to connect to, defaults to the standard TLS port\n\t */\n\tport: number;\n\n\t/**\n\t * If false, uses an unencrypted connection instead of the default on TLS\n\t */\n\tencrypted: boolean;\n\n\t/**\n\t * If no connection is established after `timeout` ms, the connection is terminated\n\t */\n\ttimeout: number;\n\n\t/**\n\t * Connects to an Electrum server using the socket.\n\t */\n\tconnect(): void;\n\n\t/**\n\t * Disconnects from the Electrum server from the socket.\n\t */\n\tdisconnect(): void;\n\n\t/**\n\t * Write data to the Electrum server on the socket.\n\t *\n\t * @param data     - Data to be written to the socket\n\t * @param callback - Callback function to be called when the write has completed\n\t */\n\twrite(data: Uint8Array | string, callback?: (err?: Error) => void): boolean;\n}\n\n/**\n * @ignore\n */\nexport interface VersionRejected\n{\n\terror: RPCError;\n}\n\n/**\n * @ignore\n */\nexport interface VersionNegotiated\n{\n\tsoftware: string;\n\tprotocol: string;\n}\n\n/**\n * @ignore\n */\nexport type VersionNegotiationResponse = VersionNegotiated | VersionRejected;\n\n/**\n * List of events emitted by the ElectrumConnection.\n * @event\n * @ignore\n */\nexport interface ElectrumConnectionEvents\n{\n\t/**\n\t * Emitted when any data has been received over the network.\n\t * @eventProperty\n\t */\n\t'received': [];\n\n\t/**\n\t * Emitted when a complete electrum message has been received over the network.\n\t * @eventProperty\n\t */\n\t'response': [ RPCResponse ];\n\n\t/**\n\t * Emitted when the connection has completed version negotiation.\n\t * @eventProperty\n\t */\n\t'version': [ VersionNegotiationResponse ];\n\n\t/**\n\t * Emitted when a network connection is initiated.\n\t * @eventProperty\n\t */\n\t'connecting': [];\n\n\t/**\n\t * Emitted when a network connection is successful.\n\t * @eventProperty\n\t */\n\t'connected': [];\n\n\t/**\n\t * Emitted when a network disconnection is initiated.\n\t * @eventProperty\n\t */\n\t'disconnecting': [];\n\n\t/**\n\t * Emitted when a network disconnection is successful.\n\t * @eventProperty\n\t */\n\t'disconnected': [];\n\n\t/**\n\t * Emitted when a network connect attempts to automatically reconnect.\n\t * @eventProperty\n\t */\n\t'reconnecting': [];\n\n\t/**\n\t * Emitted when the network has failed in some way.\n\t * @eventProperty\n\t */\n\t'error': [ Error ];\n}\n\n/**\n * List of events emitted by the ElectrumClient.\n * @event\n * @ignore\n */\nexport interface ElectrumClientEvents\n{\n\t/**\n\t * Emitted when an electrum subscription statement has been received over the network.\n\t * @eventProperty\n\t */\n\t'notification': [ RPCNotification ];\n\n\t/**\n\t * Emitted when a network connection is initiated.\n\t * @eventProperty\n\t */\n\t'connecting': [];\n\n\t/**\n\t * Emitted when a network connection is successful.\n\t * @eventProperty\n\t */\n\t'connected': [];\n\n\t/**\n\t * Emitted when a network disconnection is initiated.\n\t * @eventProperty\n\t */\n\t'disconnecting': [];\n\n\t/**\n\t * Emitted when a network disconnection is successful.\n\t * @eventProperty\n\t */\n\t'disconnected': [];\n\n\t/**\n\t * Emitted when a network connect attempts to automatically reconnect.\n\t * @eventProperty\n\t */\n\t'reconnecting': [];\n\n\t/**\n\t * Emitted when the network has failed in some way.\n\t * @eventProperty\n\t */\n\t'error': [ Error ];\n}\n\n/**\n * A list of possible responses to requests.\n * @ignore\n */\nexport type RequestResponse = RPCParameter | RPCParameter[];\n\n/**\n * Request resolvers are used to process the response of a request. This takes either\n * an error object or any stringified data, while the other parameter is omitted.\n * @ignore\n */\nexport type RequestResolver = (error?: Error, data?: string) => void;\n\n/**\n * Typing for promise resolution.\n * @ignore\n */\nexport type ResolveFunction<T> = (value: T | PromiseLike<T>) => void;\n\n/**\n * Typing for promise rejection.\n * @ignore\n */\nexport type RejectFunction = (reason?: unknown) => void;\n\n/**\n * @ignore\n */\nexport const isVersionRejected = function(object: VersionNegotiationResponse): object is VersionRejected\n{\n\treturn 'error' in object;\n};\n\n/**\n * @ignore\n */\nexport const isVersionNegotiated = function(object: VersionNegotiationResponse): object is VersionNegotiated\n{\n\treturn 'software' in object && 'protocol' in object;\n};\n","import debug from '@electrum-cash/debug-logs';\nimport { ElectrumWebSocket } from '@electrum-cash/web-socket';\nimport { ElectrumProtocol } from './electrum-protocol.ts';\nimport { isRPCNotification, isRPCErrorResponse } from './rpc-interfaces.ts';\nimport { EventEmitter } from 'eventemitter3';\nimport { ConnectionStatus } from './enums.ts';\nimport { parse, parseNumberAndBigInt } from 'lossless-json';\nimport { isVersionRejected } from './interfaces.ts';\nimport type { ElectrumNetworkOptions, ElectrumConnectionEvents, ElectrumSocket, ResolveFunction, RejectFunction, VersionNegotiationResponse } from './interfaces.ts';\nimport type { RPCResponse } from './rpc-interfaces.ts';\n\n/**\n * Wrapper around TLS/WSS sockets that gracefully separates a network stream into Electrum protocol messages.\n */\nexport class ElectrumConnection extends EventEmitter<ElectrumConnectionEvents>\n{\n\t// Initialize the connected flag to false to indicate that there is no connection\n\tpublic status: ConnectionStatus = ConnectionStatus.DISCONNECTED;\n\n\t// Declare empty timestamps\n\tprivate lastReceivedTimestamp: number;\n\n\t// Declare an empty socket.\n\tprivate socket: ElectrumSocket;\n\n\t// Declare timers for keep-alive pings and reconnection\n\tprivate keepAliveTimer?: number;\n\tprivate reconnectTimer?: number;\n\n\t// Initialize an empty array of connection verification timers.\n\tprivate verifications: Array<number> = [];\n\n\t// Initialize messageBuffer to an empty string\n\tprivate messageBuffer = '';\n\n\t/**\n\t * Sets up network configuration for an Electrum client connection.\n\t *\n\t * @param application       - your application name, used to identify to the electrum host.\n\t * @param version           - protocol version to use with the host.\n\t * @param socketOrHostname  - pre-configured electrum socket or fully qualified domain name or IP number of the host\n\t * @param options           - ...\n\t *\n\t * @throws {Error} if `version` is not a valid version string.\n\t */\n\tconstructor(\n\t\tprivate application: string,\n\t\tprivate version: string,\n\t\tprivate socketOrHostname: ElectrumSocket | string,\n\t\tprivate options: ElectrumNetworkOptions,\n\t)\n\t{\n\t\t// Initialize the event emitter.\n\t\tsuper();\n\n\t\t// Check if the provided version is a valid version number.\n\t\tif(!ElectrumProtocol.versionRegexp.test(version))\n\t\t{\n\t\t\t// Throw an error since the version number was not valid.\n\t\t\tthrow(new Error(`Provided version string (${version}) is not a valid protocol version number.`));\n\t\t}\n\n\t\t// If a hostname was provided..\n\t\tif(typeof socketOrHostname === 'string')\n\t\t{\n\t\t\t// Use a web socket with default parameters.\n\t\t\tthis.socket = new ElectrumWebSocket(socketOrHostname, undefined, undefined, undefined, this.options);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Use the provided socket.\n\t\t\tthis.socket = socketOrHostname;\n\t\t}\n\n\t\t// Set up handlers for connection and disconnection.\n\t\tthis.socket.on('connected', this.onSocketConnect.bind(this));\n\t\tthis.socket.on('disconnected', this.onSocketDisconnect.bind(this));\n\n\t\t// Set up handler for incoming data.\n\t\tthis.socket.on('data', this.parseMessageChunk.bind(this));\n\t}\n\n\t// Expose hostIdentifier from the socket.\n\tget hostIdentifier(): string\n\t{\n\t\treturn this.socket.hostIdentifier;\n\t}\n\n\t// Expose port from the socket.\n\tget encrypted(): boolean\n\t{\n\t\treturn this.socket.encrypted;\n\t}\n\n\t/**\n\t * Assembles incoming data into statements and hands them off to the message parser.\n\t *\n\t * @param data - data to append to the current message buffer, as a string.\n\t *\n\t * @throws {SyntaxError} if the passed statement parts are not valid JSON.\n\t */\n\tparseMessageChunk(data: string): void\n\t{\n\t\t// Update the timestamp for when we last received data.\n\t\tthis.lastReceivedTimestamp = Date.now();\n\n\t\t// Emit a notification indicating that the connection has received data.\n\t\tthis.emit('received');\n\n\t\t// Clear and remove all verification timers.\n\t\tthis.verifications.forEach((timer) => clearTimeout(timer));\n\t\tthis.verifications.length = 0;\n\n\t\t// Add the message to the current message buffer.\n\t\tthis.messageBuffer += data;\n\n\t\t// Check if the new message buffer contains the statement delimiter.\n\t\twhile(this.messageBuffer.includes(ElectrumProtocol.statementDelimiter))\n\t\t{\n\t\t\t// Split message buffer into statements.\n\t\t\tconst statementParts = this.messageBuffer.split(ElectrumProtocol.statementDelimiter);\n\n\t\t\t// For as long as we still have statements to parse..\n\t\t\twhile(statementParts.length > 1)\n\t\t\t{\n\t\t\t\t// Move the first statement to its own variable.\n\t\t\t\tconst currentStatementList = String(statementParts.shift());\n\n\t\t\t\t// Parse the statement into an object or list of objects.\n\t\t\t\tlet statementList = parse(currentStatementList, null, this.options.useBigInt ? parseNumberAndBigInt : parseFloat) as RPCResponse | RPCResponse[];\n\n\t\t\t\t// Wrap the statement in an array if it is not already a batched statement list.\n\t\t\t\tif(!Array.isArray(statementList))\n\t\t\t\t{\n\t\t\t\t\tstatementList = [ statementList ];\n\t\t\t\t}\n\n\t\t\t\t// For as long as there is statements in the result set..\n\t\t\t\twhile(statementList.length > 0)\n\t\t\t\t{\n\t\t\t\t\t// Move the first statement from the batch to its own variable.\n\t\t\t\t\tconst currentStatement = statementList.shift();\n\n\t\t\t\t\t// If the current statement is a subscription notification..\n\t\t\t\t\tif(isRPCNotification(currentStatement))\n\t\t\t\t\t{\n\t\t\t\t\t\t// Emit the notification for handling higher up in the stack.\n\t\t\t\t\t\tthis.emit('response', currentStatement);\n\n\t\t\t\t\t\t// Consider this statement handled.\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// If the current statement is a version negotiation response..\n\t\t\t\t\tif(currentStatement.id === 'versionNegotiation')\n\t\t\t\t\t{\n\t\t\t\t\t\tif(isRPCErrorResponse(currentStatement))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Then emit a failed version negotiation response signal.\n\t\t\t\t\t\t\tthis.emit('version', { error: currentStatement.error });\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t// Extract the software and protocol version reported.\n\t\t\t\t\t\t\tconst [ software, protocol ] = currentStatement.result;\n\n\t\t\t\t\t\t\t// Emit a successful version negotiation response signal.\n\t\t\t\t\t\t\tthis.emit('version', { software, protocol });\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Consider this statement handled.\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// If the current statement is a keep-alive response..\n\t\t\t\t\tif(currentStatement.id === 'keepAlive')\n\t\t\t\t\t{\n\t\t\t\t\t\t// Do nothing and consider this statement handled.\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Emit the statements for handling higher up in the stack.\n\t\t\t\t\tthis.emit('response', currentStatement);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Store the remaining statement as the current message buffer.\n\t\t\tthis.messageBuffer = statementParts.shift() || '';\n\t\t}\n\t}\n\n\t/**\n\t * Sends a keep-alive message to the host.\n\t *\n\t * @returns true if the ping message was fully flushed to the socket, false if\n\t * part of the message is queued in the user memory\n\t */\n\tping(): boolean\n\t{\n\t\t// Write a log message.\n\t\tdebug.ping(`Sending keep-alive ping to '${this.hostIdentifier}'`);\n\n\t\t// Craft a keep-alive message.\n\t\tconst message = ElectrumProtocol.buildRequestObject('server.ping', [], 'keepAlive');\n\n\t\t// Send the keep-alive message.\n\t\tconst status = this.send(message);\n\n\t\t// Return the ping status.\n\t\treturn status;\n\t}\n\n\t/**\n\t * Initiates the network connection negotiates a protocol version. Also emits the 'connect' signal if successful.\n\t *\n\t * @throws {Error} if the socket connection fails.\n\t * @returns a promise resolving when the connection is established\n\t */\n\tasync connect(): Promise<void>\n\t{\n\t\t// If we are already connected return true.\n\t\tif(this.status === ConnectionStatus.CONNECTED)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Indicate that the connection is connecting\n\t\tthis.status = ConnectionStatus.CONNECTING;\n\n\t\t// Emit a connect event now that the connection is being set up.\n\t\tthis.emit('connecting');\n\n\t\t// Create a function that will resolve once the connection is established.\n\t\t// The connection is established through onSocketConnect\n\t\tconst connectionResolver = (resolve: ResolveFunction<void>, reject: RejectFunction): void =>\n\t\t{\n\t\t\t// Resolve the connection promise once the connection is established. This event is emitted by onSocketConnect.\n\t\t\tthis.once('connected', () =>\n\t\t\t{\n\t\t\t\t// Remove the listener for the disconnected event.\n\t\t\t\tthis.removeListener('disconnected', reject);\n\n\t\t\t\t// Resolve the connection promise.\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\t// Reject the connection promise if the connection is disconnected.\n\t\t\tthis.once('disconnected', () =>\n\t\t\t{\n\t\t\t\t// Remove the listener for the connected event.\n\t\t\t\tthis.removeListener('connected', resolve);\n\n\t\t\t\t// Reject the connection promise.\n\t\t\t\treject();\n\t\t\t});\n\n\t\t\t// Start the socket connection process.\n\t\t\tthis.socket.connect();\n\t\t};\n\n\t\t// Wait until connection is established and version negotiation succeeds.\n\t\tawait new Promise<void>(connectionResolver);\n\t}\n\n\t/**\n\t * Restores the network connection.\n\t */\n\tasync reconnect(): Promise<void>\n\t{\n\t\t// If a reconnect timer is set, remove it\n\t\tawait this.clearReconnectTimer();\n\n\t\t// Write a log message.\n\t\tdebug.network(`Trying to reconnect to '${this.hostIdentifier}'..`);\n\n\t\t// Set the status to reconnecting for more accurate log messages.\n\t\tthis.status = ConnectionStatus.RECONNECTING;\n\n\t\t// Emit a connect event now that the connection is usable.\n\t\tthis.emit('reconnecting');\n\n\t\t// Disconnect the underlying socket\n\t\tthis.socket.disconnect();\n\n\t\ttry\n\t\t{\n\t\t\t// Try to connect again.\n\t\t\tawait this.connect();\n\t\t}\n\t\tcatch (_error)\n\t\t{\n\t\t\t// Do nothing as the error should be handled via the disconnect and error signals.\n\t\t}\n\t}\n\n\t/**\n\t * Removes the current reconnect timer.\n\t */\n\tclearReconnectTimer(): void\n\t{\n\t\t// If a reconnect timer is set, remove it\n\t\tif(this.reconnectTimer)\n\t\t{\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t}\n\n\t\t// Reset the timer reference.\n\t\tthis.reconnectTimer = undefined;\n\t}\n\n\t/**\n\t * Removes the current keep-alive timer.\n\t */\n\tclearKeepAliveTimer(): void\n\t{\n\t\t// If a keep-alive timer is set, remove it\n\t\tif(this.keepAliveTimer)\n\t\t{\n\t\t\tclearTimeout(this.keepAliveTimer);\n\t\t}\n\n\t\t// Reset the timer reference.\n\t\tthis.keepAliveTimer = undefined;\n\t}\n\n\t/**\n\t * Initializes the keep alive timer loop.\n\t */\n\tsetupKeepAliveTimer(): void\n\t{\n\t\t// If the keep-alive timer loop is not currently set up..\n\t\tif(!this.keepAliveTimer)\n\t\t{\n\t\t\t// Set a new keep-alive timer.\n\t\t\tthis.keepAliveTimer = setTimeout(this.ping.bind(this), this.options.sendKeepAliveIntervalInMilliSeconds) as unknown as number;\n\t\t}\n\t}\n\n\t/**\n\t * Tears down the current connection and removes all event listeners on disconnect.\n\t *\n\t * @param force       - disconnect even if the connection has not been fully established yet.\n\t * @param intentional - update connection state if disconnect is intentional.\n\t *\n\t * @returns true if successfully disconnected, or false if there was no connection.\n\t */\n\tasync disconnect(force: boolean = false, intentional: boolean = true): Promise<boolean>\n\t{\n\t\t// Return early when there is nothing to disconnect from\n\t\tif(this.status === ConnectionStatus.DISCONNECTED && !force)\n\t\t{\n\t\t\t// Return false to indicate that there was nothing to disconnect from.\n\t\t\treturn false;\n\t\t}\n\n\t\t// Update connection state if the disconnection is intentional.\n\t\t// NOTE: The state is meant to represent what the client is requesting, but\n\t\t//       is used internally to handle visibility changes in browsers to ensure functional reconnection.\n\t\tif(intentional)\n\t\t{\n\t\t\t// Set connection status to null to indicate tear-down is currently happening.\n\t\t\tthis.status = ConnectionStatus.DISCONNECTING;\n\t\t}\n\n\t\t// Emit a connect event to indicate that we are disconnecting.\n\t\tthis.emit('disconnecting');\n\n\t\t// If a keep-alive timer is set, remove it.\n\t\tawait this.clearKeepAliveTimer();\n\n\t\t// If a reconnect timer is set, remove it\n\t\tawait this.clearReconnectTimer();\n\n\t\tconst disconnectResolver = (resolve: ResolveFunction<boolean>): void =>\n\t\t{\n\t\t\t// Resolve to true after the connection emits a disconnect\n\t\t\tthis.once('disconnected', () => resolve(true));\n\n\t\t\t// Close the connection on the socket level.\n\t\t\tthis.socket.disconnect();\n\t\t};\n\n\t\t// Return true to indicate that we disconnected.\n\t\treturn new Promise<boolean>(disconnectResolver);\n\t}\n\n\t/**\n\t * Sends an arbitrary message to the server.\n\t *\n\t * @param message - json encoded request object to send to the server, as a string.\n\t *\n\t * @returns true if the message was fully flushed to the socket, false if part of the message\n\t * is queued in the user memory\n\t */\n\tsend(message: string): boolean\n\t{\n\t\t// Remove the current keep-alive timer if it exists.\n\t\tthis.clearKeepAliveTimer();\n\n\t\t// Get the current timestamp in milliseconds.\n\t\tconst currentTime = Date.now();\n\n\t\t// Follow up and verify that the message got sent..\n\t\tconst verificationTimer = setTimeout(this.verifySend.bind(this, currentTime), this.socket.timeout) as unknown as number;\n\n\t\t// Store the verification timer locally so that it can be cleared when data has been received.\n\t\tthis.verifications.push(verificationTimer);\n\n\t\t// Set a new keep-alive timer.\n\t\tthis.setupKeepAliveTimer();\n\n\t\t// Write the message to the network socket.\n\t\treturn this.socket.write(message + ElectrumProtocol.statementDelimiter);\n\t}\n\n\t// --- Event managers. --- //\n\n\t/**\n\t * Marks the connection as timed out and schedules reconnection if we have not\n\t * received data within the expected time frame.\n\t */\n\tverifySend(sentTimestamp: number): void\n\t{\n\t\t// If we haven't received any data since we last sent data out..\n\t\tif(Number(this.lastReceivedTimestamp) < sentTimestamp)\n\t\t{\n\t\t\t// If this connection is already disconnected, we do not change anything\n\t\t\tif((this.status === ConnectionStatus.DISCONNECTED) || (this.status === ConnectionStatus.DISCONNECTING))\n\t\t\t{\n\t\t\t\t// debug.warning(`Tried to verify already disconnected connection to '${this.hostIdentifier}'`);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Remove the current keep-alive timer if it exists.\n\t\t\tthis.clearKeepAliveTimer();\n\n\t\t\t// Write a notification to the logs.\n\t\t\tdebug.network(`Connection to '${this.hostIdentifier}' timed out.`);\n\n\t\t\t// Close the connection to avoid re-use.\n\t\t\t// NOTE: This initiates reconnection routines if the connection has not\n\t\t\t//       been marked as intentionally disconnected.\n\t\t\tthis.socket.disconnect();\n\t\t}\n\t}\n\n\t/**\n\t * Updates the connection status when a connection is confirmed.\n\t */\n\tasync onSocketConnect(): Promise<void>\n\t{\n\t\t// If a reconnect timer is set, remove it.\n\t\tthis.clearReconnectTimer();\n\n\t\t// Set up the initial timestamp for when we last received data from the server.\n\t\tthis.lastReceivedTimestamp = Date.now();\n\n\t\t// Set up the initial keep-alive timer.\n\t\tthis.setupKeepAliveTimer();\n\t\t\n\t\t// Wait for the version to be negotiated.\n\t\tawait new Promise<void>(this.negotiateVersion.bind(this));\n\n\t\t// Emit a connect event now that the connection is established and version negotiated.\n\t\tthis.emit('connected');\n\n\t\t// Clear all temporary error listeners.\n\t\tthis.socket.removeAllListeners('error');\n\n\t\t// Set up handler for network errors.\n\t\tthis.socket.on('error', this.onSocketError.bind(this));\n\t}\n\n\t/**\n\t * Updates the connection status when a connection is ended.\n\t */\n\tonSocketDisconnect(): void\n\t{\n\t\t// Remove the current keep-alive timer if it exists.\n\t\tthis.clearKeepAliveTimer();\n\n\t\t// If this is a connection we're trying to tear down..\n\t\tif(this.status === ConnectionStatus.DISCONNECTING)\n\t\t{\n\t\t\t// Mark the connection as disconnected.\n\t\t\tthis.status = ConnectionStatus.DISCONNECTED;\n\n\t\t\t// Send a disconnect signal higher up the stack.\n\t\t\tthis.emit('disconnected');\n\n\t\t\t// If a reconnect timer is set, remove it.\n\t\t\tthis.clearReconnectTimer();\n\n\t\t\t// Remove all event listeners\n\t\t\tthis.removeAllListeners();\n\n\t\t\t// Write a log message.\n\t\t\tdebug.network(`Disconnected from '${this.hostIdentifier}'.`);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// If this is for an established connection..\n\t\t\tif(this.status === ConnectionStatus.CONNECTED)\n\t\t\t{\n\t\t\t\t// Write a notification to the logs.\n\t\t\t\tdebug.errors(`Connection with '${this.hostIdentifier}' was closed, trying to reconnect in ${this.options.reconnectAfterMilliSeconds / 1000} seconds.`);\n\t\t\t}\n\t\t\t// If this is a connection that is currently connecting, reconnecting or already disconnected..\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Do nothing\n\n\t\t\t\t// NOTE: This error message is useful during manual debugging of reconnections.\n\t\t\t\t// debug.errors(`Lost connection with reconnecting or already disconnected server '${this.hostIdentifier}'.`);\n\t\t\t}\n\n\t\t\t// Mark the connection as disconnected for now..\n\t\t\tthis.status = ConnectionStatus.DISCONNECTED;\n\n\t\t\t// Send a disconnect signal higher up the stack.\n\t\t\tthis.emit('disconnected');\n\n\t\t\t// If we don't have a pending reconnection timer..\n\t\t\tif(!this.reconnectTimer)\n\t\t\t{\n\t\t\t\t// Attempt to reconnect after one keep-alive duration.\n\t\t\t\tthis.reconnectTimer = setTimeout(this.reconnect.bind(this), this.options.reconnectAfterMilliSeconds) as unknown as number;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Notify administrator of any unexpected errors.\n\t */\n\tonSocketError(error: unknown | undefined): void\n\t{\n\t\t// Report a generic error if no error information is present.\n\t\t// NOTE: When using WSS, the error event explicitly\n\t\t//       only allows to send a \"simple\" event without data.\n\t\t//       https://stackoverflow.com/a/18804298\n\t\tif(typeof error === 'undefined')\n\t\t{\n\t\t\t// Do nothing, and instead rely on the socket disconnect event for further information.\n\t\t\treturn;\n\t\t}\n\n\t\t// Log the error, as there is nothing we can do to actually handle it.\n\t\tdebug.errors(`Network error ('${this.hostIdentifier}'): `, error);\n\t}\n\n\t/**\n\t * Negotiate the protocol version with the server.\n\t * Disconnect the connection if the version negotiation fails.\n\t * @param resolve \n\t * @param reject \n\t */\n\tasync negotiateVersion(resolve: ResolveFunction<void>, reject: RejectFunction): Promise<void>\n\t{\n\t\tconst rejector = (error: Error): void =>\n\t\t{\n\t\t\t// Set the status back to disconnected\n\t\t\tthis.status = ConnectionStatus.DISCONNECTED;\n\n\t\t\t// Emit a connect event indicating that we failed to connect.\n\t\t\tthis.emit('disconnected');\n\n\t\t\t// Reject with the error as reason\n\t\t\treject(error);\n\t\t};\n\n\t\t// Write a log message to show that we have started version negotiation.\n\t\tdebug.network(`Requesting protocol version ${this.version} with '${this.hostIdentifier}'.`);\n\t\t\t\t\n\t\t// Add error handler for one-time error.\n\t\tthis.socket.once('error', rejector);\n\n\t\t// Build a version negotiation message.\n\t\tconst versionMessage = ElectrumProtocol.buildRequestObject('server.version', [ this.application, this.version ], 'versionNegotiation');\n\n\t\t// Define a function to wrap version validation as a function.\n\t\tconst versionValidator = (version: VersionNegotiationResponse): void =>\n\t\t{\n\t\t\t// Check if version negotiation failed.\n\t\t\tif(isVersionRejected(version))\n\t\t\t{\n\t\t\t\t// Disconnect from the host.\n\t\t\t\tthis.disconnect(true);\n\n\t\t\t\t// Declare an error message.\n\t\t\t\tconst errorMessage = 'unsupported protocol version.';\n\n\t\t\t\t// Log the error.\n\t\t\t\tdebug.errors(`Failed to connect with ${this.hostIdentifier} due to ${errorMessage}`);\n\n\t\t\t\t// Reject the connection with false since version negotiation failed.\n\t\t\t\treject(errorMessage);\n\t\t\t}\n\t\t\t// Check if the host supports our requested protocol version.\n\t\t\t// NOTE: the server responds with version numbers that truncate 0's, so 1.5.0 turns into 1.5.\n\t\t\telse if((version.protocol !== this.version) && (`${version.protocol}.0` !== this.version) && (`${version.protocol}.0.0` !== this.version))\n\t\t\t{\n\t\t\t\t// Disconnect from the host.\n\t\t\t\tthis.disconnect(true);\n\n\t\t\t\t// Declare an error message.\n\t\t\t\tconst errorMessage = `incompatible protocol version negotiated (${version.protocol} !== ${this.version}).`;\n\n\t\t\t\t// Log the error.\n\t\t\t\tdebug.errors(`Failed to connect with ${this.hostIdentifier} due to ${errorMessage}`);\n\n\t\t\t\t// Reject the connection with false since version negotiation failed.\n\t\t\t\treject(errorMessage);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t// Write a log message.\n\t\t\t\tdebug.network(`Negotiated protocol version ${version.protocol} with '${this.hostIdentifier}', powered by ${version.software}.`);\n\n\t\t\t\t// Set connection status to connected\n\t\t\t\tthis.status = ConnectionStatus.CONNECTED;\n\n\t\t\t\t// Resolve the connection promise since we successfully connected and negotiated protocol version.\n\t\t\t\tresolve();\n\t\t\t}\n\t\t};\n\n\t\t// Listen for version negotiation once.\n\t\tthis.once('version', versionValidator);\n\n\t\t// Send the version negotiation message.\n\t\tthis.send(versionMessage);\n\t}\n}\n","import type { ElectrumNetworkOptions } from './interfaces.ts';\n\n// Define number of milliseconds per second for legibility.\nconst MILLI_SECONDS_PER_SECOND = 1000;\n\n/**\n * Configure default options.\n */\nexport const defaultNetworkOptions: ElectrumNetworkOptions =\n{\n\t// By default, all numbers including integers are parsed as regular JavaScript numbers.\n\tuseBigInt: false,\n\n\t// Send a ping message every seconds, to detect network problem as early as possible.\n\tsendKeepAliveIntervalInMilliSeconds: 1 * MILLI_SECONDS_PER_SECOND,\n\n\t// Try to reconnect 5 seconds after unintentional disconnects.\n\treconnectAfterMilliSeconds: 5 * MILLI_SECONDS_PER_SECOND,\n\n\t// Try to detect stale connections 5 seconds after every send.\n\tverifyConnectionTimeoutInMilliSeconds: 5 * MILLI_SECONDS_PER_SECOND,\n\n\t// Automatically manage the connection for a consistent behavior across browsers and devices.\n\tdisableBrowserVisibilityHandling: false,\n\tdisableBrowserConnectivityHandling: false,\n};\n","import debug from '@electrum-cash/debug-logs';\nimport { ElectrumConnection } from './electrum-connection.ts';\nimport { ElectrumProtocol } from './electrum-protocol.ts';\nimport { defaultNetworkOptions } from './constants.ts';\nimport { ConnectionStatus } from './enums.ts';\nimport { EventEmitter } from 'eventemitter3';\nimport { Mutex } from 'async-mutex';\nimport { isRPCNotification, isRPCErrorResponse } from './rpc-interfaces.ts';\nimport type { RPCParameter, RPCNotification, RPCResponse } from './rpc-interfaces.ts';\nimport type { ElectrumNetworkOptions, ElectrumClientEvents, ElectrumSocket, ResolveFunction, RequestResolver, RequestResponse } from './interfaces.ts';\n\n/**\n * High-level Electrum client that lets applications send requests and subscribe to notification events from a server.\n */\nclass ElectrumClient<ElectrumEvents extends ElectrumClientEvents> extends EventEmitter<ElectrumClientEvents | ElectrumEvents> implements ElectrumClientEvents\n{\n\t/**\n\t * The name and version of the server software indexing the blockchain.\n\t */\n\tpublic software: string;\n\n\t/**\n\t * The genesis hash of the blockchain indexed by the server.\n\t * @remarks This is only available after a 'server.features' call.\n\t */\n\tpublic genesisHash: string;\n\n\t/**\n\t * The chain height of the blockchain indexed by the server.\n\t * @remarks This is only available after a 'blockchain.headers.subscribe' call.\n\t */\n\tpublic chainHeight: number;\n\n\t/**\n\t * Timestamp of when we last received data from the server indexing the blockchain.\n\t */\n\tpublic lastReceivedTimestamp: number;\n\n\t/**\n\t * Number corresponding to the underlying connection status.\n\t */\n\tpublic get status(): ConnectionStatus\n\t{\n\t\treturn this.connection.status;\n\t}\n\n\t// Declare instance variables\n\tprivate connection: ElectrumConnection;\n\n\t// Initialize an empty list of subscription metadata.\n\tprivate subscriptionMethods: Record<string, Set<string>> = {};\n\n\t// Start counting the request IDs from 0\n\tprivate requestId = 0;\n\n\t// Initialize an empty dictionary for keeping track of request resolvers\n\tprivate requestResolvers: { [index: number]: RequestResolver } = {};\n\n\t// Mutex lock used to prevent simultaneous connect() and disconnect() calls.\n\tprivate connectionLock = new Mutex();\n\n\t/**\n\t * Initializes an Electrum client.\n\t *\n\t * @param application       - your application name, used to identify to the electrum host.\n\t * @param version           - protocol version to use with the host.\n\t * @param socketOrHostname  - pre-configured electrum socket or fully qualified domain name or IP number of the host\n\t * @param options           - ...\n\t *\n\t * @throws {Error} if `version` is not a valid version string.\n\t */\n\tconstructor(\n\t\tpublic application: string,\n\t\tpublic version: string,\n\t\tpublic socketOrHostname: ElectrumSocket | string,\n\t\tpublic options: ElectrumNetworkOptions = {},\n\t)\n\t{\n\t\t// Initialize the event emitter.\n\t\tsuper();\n\n\t\t// Update default options with the provided values.\n\t\tconst networkOptions: ElectrumNetworkOptions = { ...defaultNetworkOptions, ...options };\n\n\t\t// Set up a connection to an electrum server.\n\t\tthis.connection = new ElectrumConnection(application, version, socketOrHostname, networkOptions);\n\t}\n\n\t// Expose hostIdentifier from the connection.\n\tget hostIdentifier(): string\n\t{\n\t\treturn this.connection.hostIdentifier;\n\t}\n\n\t// Expose port from the connection.\n\tget encrypted(): boolean\n\t{\n\t\treturn this.connection.encrypted;\n\t}\n\n\t/**\n\t * Connects to the remote server.\n\t *\n\t * @throws {Error} if the socket connection fails.\n\t * @returns a promise resolving when the connection is established.\n\t */\n\tasync connect(): Promise<void>\n\t{\n\t\t// Create a lock so that multiple connects/disconnects cannot race each other.\n\t\tconst unlock = await this.connectionLock.acquire();\n\n\t\ttry\n\t\t{\n\t\t\t// If we are already connected, do not attempt to connect again.\n\t\t\tif(this.connection.status === ConnectionStatus.CONNECTED)\n\t\t\t{\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Listen for parsed statements.\n\t\t\tthis.connection.on('response', this.response.bind(this));\n\n\t\t\t// Hook up handles for the connected and disconnected events.\n\t\t\tthis.connection.on('connected', this.resubscribeOnConnect.bind(this));\n\t\t\tthis.connection.on('disconnected', this.onConnectionDisconnect.bind(this));\n\n\t\t\t// Relay connecting and reconnecting events.\n\t\t\tthis.connection.on('connecting', this.handleConnectionStatusChanges.bind(this, 'connecting'));\n\t\t\tthis.connection.on('disconnecting', this.handleConnectionStatusChanges.bind(this, 'disconnecting'));\n\t\t\tthis.connection.on('reconnecting', this.handleConnectionStatusChanges.bind(this, 'reconnecting'));\n\n\t\t\t// Hook up client metadata gathering functions.\n\t\t\tthis.connection.on('version', this.storeSoftwareVersion.bind(this));\n\t\t\tthis.connection.on('received', this.updateLastReceivedTimestamp.bind(this));\n\n\t\t\t// Relay error events.\n\t\t\tthis.connection.on('error', this.emit.bind(this, 'error'));\n\n\t\t\t// Connect with the server.\n\t\t\tawait this.connection.connect();\n\t\t}\n\t\t// Always release our lock so that we do not end up in a stuck-state.\n\t\tfinally\n\t\t{\n\t\t\tunlock();\n\t\t}\n\t}\n\n\t/**\n\t * Disconnects from the remote server and removes all event listeners/subscriptions and open requests.\n\t *\n\t * @param force               - disconnect even if the connection has not been fully established yet.\n\t * @param retainSubscriptions - retain subscription data so they will be restored on reconnection.\n\t *\n\t * @returns true if successfully disconnected, or false if there was no connection.\n\t */\n\tasync disconnect(force: boolean = false, retainSubscriptions: boolean = false): Promise<boolean>\n\t{\n\t\tif(!retainSubscriptions)\n\t\t{\n\t\t\t// Cancel all event listeners.\n\t\t\tthis.removeAllListeners();\n\n\t\t\t// Remove all subscription data\n\t\t\tthis.subscriptionMethods = {};\n\t\t}\n\n\t\t// Disconnect from the remote server.\n\t\treturn this.connection.disconnect(force);\n\t}\n\n\t/**\n\t * Calls a method on the remote server with the supplied parameters.\n\t *\n\t * @param method     - name of the method to call.\n\t * @param parameters - one or more parameters for the method.\n\t *\n\t * @throws {Error} if the client is disconnected.\n\t * @returns a promise that resolves with the result of the method or an Error.\n\t */\n\tasync request(method: string, ...parameters: RPCParameter[]): Promise<Error | RequestResponse>\n\t{\n\t\t// If we are not connected to a server..\n\t\tif(this.connection.status !== ConnectionStatus.CONNECTED)\n\t\t{\n\t\t\t// Reject the request with a disconnected error message.\n\t\t\tthrow(new Error(`Unable to send request to a disconnected server '${this.hostIdentifier}'.`));\n\t\t}\n\n\t\t// Increase the request ID by one.\n\t\tthis.requestId += 1;\n\n\t\t// Store a copy of the request id.\n\t\tconst id = this.requestId;\n\n\t\t// Format the arguments as an electrum request object.\n\t\tconst message = ElectrumProtocol.buildRequestObject(method, parameters, id);\n\n\t\t// Define a function to wrap the request in a promise.\n\t\tconst requestResolver = (resolve: ResolveFunction<Error | RequestResponse>): void =>\n\t\t{\n\t\t\t// Add a request resolver for this promise to the list of requests.\n\t\t\tthis.requestResolvers[id] = (error?: Error, data?: RequestResponse) =>\n\t\t\t{\n\t\t\t\t// If the resolution failed..\n\t\t\t\tif(error)\n\t\t\t\t{\n\t\t\t\t\t// Resolve the promise with the error for the application to handle.\n\t\t\t\t\tresolve(error);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t// Resolve the promise with the request results.\n\t\t\t\t\tresolve(data);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Send the request message to the remote server.\n\t\t\tthis.connection.send(message);\n\t\t};\n\n\t\t// Write a log message.\n\t\tdebug.network(`Sending request '${method}' to '${this.hostIdentifier}'`);\n\n\t\t// return a promise to deliver results later.\n\t\treturn new Promise<Error | RequestResponse>(requestResolver);\n\t}\n\n\t/**\n\t * Subscribes to the method and payload at the server.\n\t *\n\t * @remarks the response for the subscription request is issued as a notification event.\n\t *\n\t * @param method     - one of the subscribable methods the server supports.\n\t * @param parameters - one or more parameters for the method.\n\t *\n\t * @throws {Error} if the client is disconnected.\n\t * @returns a promise resolving when the subscription is established.\n\t */\n\tasync subscribe(method: string, ...parameters: RPCParameter[]): Promise<void>\n\t{\n\t\t// Initialize an empty list of subscription payloads, if needed.\n\t\tif(!this.subscriptionMethods[method])\n\t\t{\n\t\t\tthis.subscriptionMethods[method] = new Set<string>();\n\t\t}\n\n\t\t// Store the subscription parameters to track what data we have subscribed to.\n\t\tthis.subscriptionMethods[method].add(JSON.stringify(parameters));\n\n\t\t// Send initial subscription request.\n\t\tconst requestData = await this.request(method, ...parameters);\n\n\t\t// If the request failed, throw it as an error.\n\t\tif(requestData instanceof Error)\n\t\t{\n\t\t\tthrow(requestData);\n\t\t}\n\n\t\t// If the request returned more than one data point..\n\t\tif(Array.isArray(requestData))\n\t\t{\n\t\t\t// .. throw an error, as this breaks our expectation for subscriptions.\n\t\t\tthrow(new Error('Subscription request returned an more than one data point.'));\n\t\t}\n\n\t\t// Construct a notification structure to package the initial result as a notification.\n\t\tconst notification: RPCNotification =\n\t\t{\n\t\t\tjsonrpc: '2.0',\n\t\t\tmethod: method,\n\t\t\tparams: [ ...parameters, requestData ],\n\t\t};\n\n\t\t// Manually emit an event for the initial response.\n\t\tthis.emit('notification', notification);\n\n\t\t// Try to update the chain height.\n\t\tthis.updateChainHeightFromHeadersNotifications(notification);\n\t}\n\n\t/**\n\t * Unsubscribes to the method at the server and removes any callback functions\n\t * when there are no more subscriptions for the method.\n\t *\n\t * @param method     - a previously subscribed to method.\n\t * @param parameters - one or more parameters for the method.\n\t *\n\t * @throws {Error} if no subscriptions exist for the combination of the provided `method` and `parameters.\n\t * @throws {Error} if the client is disconnected.\n\t * @returns a promise resolving when the subscription is removed.\n\t */\n\tasync unsubscribe(method: string, ...parameters: RPCParameter[]): Promise<void>\n\t{\n\t\t// Throw an error if the client is disconnected.\n\t\tif(this.connection.status !== ConnectionStatus.CONNECTED)\n\t\t{\n\t\t\tthrow(new Error(`Unable to send unsubscribe request to a disconnected server '${this.hostIdentifier}'.`));\n\t\t}\n\n\t\t// If this method has no subscriptions..\n\t\tif(!this.subscriptionMethods[method])\n\t\t{\n\t\t\t// Reject this promise with an explanation.\n\t\t\tthrow(new Error(`Cannot unsubscribe from '${method}' since the method has no subscriptions.`));\n\t\t}\n\n\t\t// Pack up the parameters as a long string.\n\t\tconst subscriptionParameters = JSON.stringify(parameters);\n\n\t\t// If the method payload could not be located..\n\t\tif(!this.subscriptionMethods[method].has(subscriptionParameters))\n\t\t{\n\t\t\t// Reject this promise with an explanation.\n\t\t\tthrow(new Error(`Cannot unsubscribe from '${method}' since it has no subscription with the given parameters.`));\n\t\t}\n\n\t\t// Remove this specific subscription payload from internal tracking.\n\t\tthis.subscriptionMethods[method].delete(subscriptionParameters);\n\n\t\t// Send unsubscription request to the server\n\t\t// NOTE: As a convenience we allow users to define the method as the subscribe or unsubscribe version.\n\t\tawait this.request(method.replace('.subscribe', '.unsubscribe'), ...parameters);\n\n\t\t// Write a log message.\n\t\tdebug.client(`Unsubscribed from '${String(method)}' for the '${subscriptionParameters}' parameters.`);\n\t}\n\n\t/**\n\t * Restores existing subscriptions without updating status or triggering manual callbacks.\n\t *\n\t * @throws {Error} if subscription data cannot be found for all stored event names.\n\t * @throws {Error} if the client is disconnected.\n\t * @returns a promise resolving to true when the subscriptions are restored.\n\t *\n\t * @ignore\n\t */\n\tprivate async resubscribeOnConnect(): Promise<void>\n\t{\n\t\t// Write a log message.\n\t\tdebug.client(`Connected to '${this.hostIdentifier}'.`);\n\n\t\t// Synchronize with the underlying connection status.\n\t\tthis.handleConnectionStatusChanges('connected');\n\n\t\t// Initialize an empty list of resubscription promises.\n\t\tconst resubscriptionPromises = [];\n\n\t\t// For each method we have a subscription for..\n\t\tfor(const method in this.subscriptionMethods)\n\t\t{\n\t\t\t// .. and for each parameter we have previously been subscribed to..\n\t\t\tfor(const parameterJSON of this.subscriptionMethods[method].values())\n\t\t\t{\n\t\t\t\t// restore the parameters from JSON.\n\t\t\t\tconst parameters = JSON.parse(parameterJSON);\n\n\t\t\t\t// Send a subscription request.\n\t\t\t\tresubscriptionPromises.push(this.subscribe(method, ...parameters));\n\t\t\t}\n\n\t\t\t// Wait for all re-subscriptions to complete.\n\t\t\tawait Promise.all(resubscriptionPromises);\n\t\t}\n\n\t\t// Write a log message if there was any subscriptions to restore.\n\t\tif(resubscriptionPromises.length > 0)\n\t\t{\n\t\t\tdebug.client(`Restored ${resubscriptionPromises.length} previous subscriptions for '${this.hostIdentifier}'`);\n\t\t}\n\t}\n\n\t/**\n\t * Parser messages from the remote server to resolve request promises and emit subscription events.\n\t *\n\t * @param message - the response message\n\t *\n\t * @throws {Error} if the message ID does not match an existing request.\n\t * @ignore\n\t */\n\tresponse(message: RPCResponse): void\n\t{\n\t\t// If the received message is a notification, we forward it to all event listeners\n\t\tif(isRPCNotification(message))\n\t\t{\n\t\t\t// Write a log message.\n\t\t\tdebug.client(`Received notification for '${message.method}' from '${this.hostIdentifier}'`);\n\n\t\t\t// Forward the message content to all event listeners.\n\t\t\tthis.emit('notification', message);\n\n\t\t\t// Try to update the chain height.\n\t\t\tthis.updateChainHeightFromHeadersNotifications(message);\n\n\t\t\t// Return since it does not have an associated request resolver\n\t\t\treturn;\n\t\t}\n\n\t\t// If the response ID is null we cannot use it to index our request resolvers\n\t\tif(message.id === null)\n\t\t{\n\t\t\t// Throw an internal error, this should not happen.\n\t\t\tthrow(new Error('Internal error: Received an RPC response with ID null.'));\n\t\t}\n\n\t\t// Look up which request promise we should resolve this.\n\t\tconst requestResolver = this.requestResolvers[message.id];\n\n\t\t// If we do not have a request resolver for this response message..\n\t\tif(!requestResolver)\n\t\t{\n\t\t\t// Log that a message was ignored since the request has already been rejected.\n\t\t\tdebug.warning(`Ignoring response #${message.id} as the request has already been rejected.`);\n\n\t\t\t// Return as this has now been fully handled.\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove the promise from the request list.\n\t\tdelete this.requestResolvers[message.id];\n\n\t\t// If the message contains an error..\n\t\tif(isRPCErrorResponse(message))\n\t\t{\n\t\t\t// Forward the message error to the request resolver and omit the `result` parameter.\n\t\t\trequestResolver(new Error(message.error.message));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Forward the message content to the request resolver and omit the `error` parameter\n\t\t\t// (by setting it to undefined).\n\t\t\trequestResolver(undefined, message.result);\n\n\t\t\t// Attempt to extract genesis hash from feature requests.\n\t\t\tthis.storeGenesisHashFromFeaturesResponse(message);\n\t\t}\n\t}\n\n\t/**\n\t * Callback function that is called when connection to the Electrum server is lost.\n\t * Aborts all active requests with an error message indicating that connection was lost.\n\t *\n\t * @ignore\n\t */\n\tasync onConnectionDisconnect(): Promise<void>\n\t{\n\t\t// Loop over active requests\n\t\tfor(const resolverId in this.requestResolvers)\n\t\t{\n\t\t\t// Extract request resolver for readability\n\t\t\tconst requestResolver = this.requestResolvers[resolverId];\n\n\t\t\t// Resolve the active request with an error indicating that the connection was lost.\n\t\t\trequestResolver(new Error('Connection lost'));\n\n\t\t\t// Remove the promise from the request list.\n\t\t\tdelete this.requestResolvers[resolverId];\n\t\t}\n\n\t\t// Synchronize with the underlying connection status.\n\t\tthis.handleConnectionStatusChanges('disconnected');\n\t}\n\n\t/**\n\t * Stores the server provider software version field on successful version negotiation.\n\t *\n\t * @ignore\n\t */\n\tasync storeSoftwareVersion(versionStatement): Promise<void>\n\t{\n\t\t// TODO: handle failed version negotiation better.\n\t\tif(versionStatement.error)\n\t\t{\n\t\t\t// Do nothing.\n\t\t\treturn;\n\t\t}\n\n\t\t// Store the software version.\n\t\tthis.software = versionStatement.software;\n\t}\n\n\t/**\n\t * Updates the last received timestamp.\n\t *\n\t * @ignore\n\t */\n\tasync updateLastReceivedTimestamp(): Promise<void>\n\t{\n\t\t// Update the timestamp for when we last received data.\n\t\tthis.lastReceivedTimestamp = Date.now();\n\t}\n\n\t/**\n\t * Checks if the provided message is a response to a headers subscription,\n\t * and if so updates the locally stored chain height value for this client.\n\t *\n\t * @ignore\n\t */\n\tasync updateChainHeightFromHeadersNotifications(message): Promise<void>\n\t{\n\t\t// If the message is a notification for a new chain height..\n\t\tif(message.method === 'blockchain.headers.subscribe')\n\t\t{\n\t\t\t// ..also store the updated chain height locally.\n\t\t\tthis.chainHeight = message.params[0].height;\n\t\t}\n\t}\n\n\t/**\n\t * Checks if the provided message is a response to a server.features request,\n\t * and if so stores the genesis hash for this client locally.\n\t *\n\t * @ignore\n\t */\n\tasync storeGenesisHashFromFeaturesResponse(message): Promise<void>\n\t{\n\t\ttry\n\t\t{\n\t\t\t// If the message is a response to a features request..\n\t\t\tif(typeof message.result.genesis_hash !== 'undefined')\n\t\t\t{\n\t\t\t\t// ..store the genesis hash locally.\n\t\t\t\tthis.genesisHash = message.result.genesis_hash;\n\t\t\t}\n\t\t}\n\t\tcatch (_ignored)\n\t\t{\n\t\t\t// Do nothing.\n\t\t}\n\t}\n\n\t/**\n\t * Helper function to synchronize state and events with the underlying connection.\n\t */\n\tasync handleConnectionStatusChanges(eventName): Promise<void>\n\t{\n\t\t// Re-emit the event.\n\t\tthis.emit(eventName);\n\t}\n\n\t// Add magic glue that makes typedoc happy so that we can have the events listed on the class.\n\tpublic readonly connecting: [];\n\tpublic readonly connected: [];\n\tpublic readonly disconnecting: [];\n\tpublic readonly disconnected: [];\n\tpublic readonly reconnecting: [];\n\tpublic readonly notification: [ RPCNotification ];\n\tpublic readonly error: [ Error ];\n}\n\n// Export the client.\nexport default ElectrumClient;\n"],"mappings":"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"}