@electrum-cash/network 4.3.2-development.16680478005 → 4.4.0-development.16683964237
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +18 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +24 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -9,13 +9,15 @@ import { ElectrumSocket, ElectrumSocketOptions } from "@electrum-cash/socket";
|
|
|
9
9
|
* @property {2} DISCONNECTING The connection is disconnecting.
|
|
10
10
|
* @property {3} CONNECTING The connection is connecting.
|
|
11
11
|
* @property {4} RECONNECTING The connection is restarting.
|
|
12
|
+
* @property {5} UNRECOVERABLE The connection has stopped reconnecting, as it cannot recover on its own.
|
|
12
13
|
*/
|
|
13
14
|
export declare enum ConnectionStatus {
|
|
14
15
|
DISCONNECTED = 0,
|
|
15
16
|
CONNECTED = 1,
|
|
16
17
|
DISCONNECTING = 2,
|
|
17
18
|
CONNECTING = 3,
|
|
18
|
-
RECONNECTING = 4
|
|
19
|
+
RECONNECTING = 4,
|
|
20
|
+
UNRECOVERABLE = 5
|
|
19
21
|
}
|
|
20
22
|
//#endregion
|
|
21
23
|
//#region source/rpc-interfaces.d.ts
|
|
@@ -132,6 +134,11 @@ export interface ElectrumConnectionEvents {
|
|
|
132
134
|
* @eventProperty
|
|
133
135
|
*/
|
|
134
136
|
'reconnecting': [];
|
|
137
|
+
/**
|
|
138
|
+
* Emitted when the connection stops reconnecting, as it cannot recover on its own.
|
|
139
|
+
* @eventProperty
|
|
140
|
+
*/
|
|
141
|
+
'unrecoverable': [];
|
|
135
142
|
/**
|
|
136
143
|
* Emitted when the network has failed in some way.
|
|
137
144
|
* @eventProperty
|
|
@@ -174,6 +181,11 @@ export interface ElectrumClientEvents {
|
|
|
174
181
|
* @eventProperty
|
|
175
182
|
*/
|
|
176
183
|
'reconnecting': [];
|
|
184
|
+
/**
|
|
185
|
+
* Emitted when the connection stops reconnecting, as it cannot recover on its own.
|
|
186
|
+
* @eventProperty
|
|
187
|
+
*/
|
|
188
|
+
'unrecoverable': [];
|
|
177
189
|
/**
|
|
178
190
|
* Emitted when the network has failed in some way.
|
|
179
191
|
* @eventProperty
|
|
@@ -265,6 +277,10 @@ declare class ElectrumClient<ElectrumEvents extends ElectrumClientEvents> extend
|
|
|
265
277
|
private attachConnectionHandlers;
|
|
266
278
|
get hostIdentifier(): string;
|
|
267
279
|
get encrypted(): boolean;
|
|
280
|
+
/**
|
|
281
|
+
* Timestamp of the pending automatic reconnection attempt, or undefined if none is scheduled.
|
|
282
|
+
*/
|
|
283
|
+
get nextReconnectTimestamp(): number | undefined;
|
|
268
284
|
/**
|
|
269
285
|
* Connects to the remote server.
|
|
270
286
|
*
|
|
@@ -385,6 +401,7 @@ declare class ElectrumClient<ElectrumEvents extends ElectrumClientEvents> extend
|
|
|
385
401
|
readonly disconnecting: [];
|
|
386
402
|
readonly disconnected: [];
|
|
387
403
|
readonly reconnecting: [];
|
|
404
|
+
readonly unrecoverable: [];
|
|
388
405
|
readonly notification: [RPCNotification];
|
|
389
406
|
readonly error: [Error];
|
|
390
407
|
}
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../source/enums.ts","../source/rpc-interfaces.ts","../source/interfaces.ts","../source/electrum-client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../source/enums.ts","../source/rpc-interfaces.ts","../source/interfaces.ts","../source/electrum-client.ts"],"mappings":";;;;;;;;;;;;;oBAUY;EAEX;EACA;EACA;EACA;EACA;EACA;;;;YChBW;YAGA;iBAGK;EAEhB;;iBAIgB,wBAAwB;EAExC;EACA,SAAS;;iBAIO,mBAAmB;EAEnC,IAAI;EACJ;EACA,SAAS;;iBAIO,qBAAqB;EAErC,IAAI;EACJ;;iBAGgB;EAEhB;EACA;EACA;;iBAIgB,yBAAyB;EAEzC,IAAI;EACJ,OAAO;;YAII,cAAc,mBAAmB,eAAe;YAGhD,aAAa,kBAAkB,aAAa;YAG5C,mBAAmB;YACnB,kBAAkB;qBAEjB,qBAAkB,SAAqB,YAAU,WAAW;qBAK5D,iBAAc,SAAqB,YAAU,WAAW;qBAKxD,oBAAiB,SAAqB,YAAU,WAAW;qBAK3D,eAAY,SAAqB,YAAU,WAAW;;;;;;iBCnElD,+BAA+B,QAAQ;;EAGvD;;EAGA;;EAGA;;EAGA;;;;;iBAMgB;EAEhB,OAAO;;;;;iBAMS;EAEhB;EACA;;;;;YAMW,6BAA6B,oBAAoB;;;;;;iBAO5C;;;;;EAMhB;;;;;EAMA,aAAc;;;;;EAMd,YAAa;;;;;EAMb;;;;;EAMA;;;;;EAMA;;;;;EAMA;;;;;EAMA;;;;;EAMA;;;;;EAMA,UAAW;;;;;;;iBAQK;;;;;EAMhB,iBAAkB;;;;;EAMlB;;;;;EAMA;;;;;EAMA;;;;;EAMA;;;;;EAMA;;;;;EAMA;;;;;EAMA,UAAW;;;;;;YAOA,kBAAkB,eAAe;;;;;;YAOjC,mBAAmB,QAAQ,OAAO;;;;;YAMlC,gBAAgB,MAAM,OAAO,IAAI,YAAY;;;;;YAM7C,kBAAkB;;;;qBAKjB,oBAAiB,QAAoB,+BAA6B,UAAU;;;;qBAQ5E,sBAAmB,QAAoB,+BAA6B,UAAU;;;;;;cC5LrF,eAAe,uBAAuB,8BAA8B,aAAa,uBAAuB,2BAA2B;EA0DhI;EACA;EACA,kBAAkB;EAClB,SAAS,QAAQ;;;;EAxDlB;;;;;EAMA;;;;;EAMA;;;;EAKA;;;;MAKI,UAAU;UAMb;UAGA;UAGA;UAGA;UAGA;;;;;;;;;;;EAaA,YAAA,qBACA,iBACA,kBAAkB,yBAClB,UAAS,QAAQ;;;;;;UAsBjB;MAwBJ;MAMA;;;;MAQA;;;;;;;EAWE,WAAW;;;;;;;;;EAwBX,WAAW,iBAAwB,gCAAuC;;;;;;;;;;EA4B1E,QAAQ,mBAAmB,YAAY,iBAAiB,QAAQ,QAAQ;;;;;;;;;UAoEtE;;;;;;;;;;;;EA6BF,UAAU,mBAAmB,YAAY,iBAAiB;;;;;;;;;;;;EAqD1D,YAAY,mBAAmB,YAAY,iBAAiB;;;;;;;;;;UA6CpD;;;;;;;;;EA2Cd,SAAS,SAAS;;;;;;;EAgEZ,0BAA0B;;;;;;EAwB1B,qBAAqB,wBAAmB;;;;;;EAkBxC,+BAA+B;;;;;;;EAY/B,0CAA0C,eAAU;;;;;;;EAgBpD,qCAAqC,eAAU;;;;EAoB/C,8BAA8B,iBAAY;WAOhC;WACA;WACA;WACA;WACA;WACA;WACA,eAAgB;WAChB,QAAS"}
|
package/dist/index.mjs
CHANGED
|
@@ -67,6 +67,7 @@ const isRPCRequest = function(message) {
|
|
|
67
67
|
* @property {2} DISCONNECTING The connection is disconnecting.
|
|
68
68
|
* @property {3} CONNECTING The connection is connecting.
|
|
69
69
|
* @property {4} RECONNECTING The connection is restarting.
|
|
70
|
+
* @property {5} UNRECOVERABLE The connection has stopped reconnecting, as it cannot recover on its own.
|
|
70
71
|
*/
|
|
71
72
|
let ConnectionStatus = /* @__PURE__ */ function(ConnectionStatus) {
|
|
72
73
|
ConnectionStatus[ConnectionStatus["DISCONNECTED"] = 0] = "DISCONNECTED";
|
|
@@ -74,6 +75,7 @@ let ConnectionStatus = /* @__PURE__ */ function(ConnectionStatus) {
|
|
|
74
75
|
ConnectionStatus[ConnectionStatus["DISCONNECTING"] = 2] = "DISCONNECTING";
|
|
75
76
|
ConnectionStatus[ConnectionStatus["CONNECTING"] = 3] = "CONNECTING";
|
|
76
77
|
ConnectionStatus[ConnectionStatus["RECONNECTING"] = 4] = "RECONNECTING";
|
|
78
|
+
ConnectionStatus[ConnectionStatus["UNRECOVERABLE"] = 5] = "UNRECOVERABLE";
|
|
77
79
|
return ConnectionStatus;
|
|
78
80
|
}({});
|
|
79
81
|
//#endregion
|
|
@@ -114,10 +116,12 @@ var ElectrumConnection = class extends EventEmitter {
|
|
|
114
116
|
options;
|
|
115
117
|
status = 0;
|
|
116
118
|
lastReceivedTimestamp;
|
|
119
|
+
nextReconnectTimestamp;
|
|
117
120
|
socket;
|
|
118
121
|
keepAliveTimer;
|
|
119
122
|
reconnectTimer;
|
|
120
123
|
isAutomaticReconnectAttempt = false;
|
|
124
|
+
isUnrecoverable = false;
|
|
121
125
|
verifications = [];
|
|
122
126
|
messageBuffer = "";
|
|
123
127
|
/**
|
|
@@ -265,6 +269,7 @@ var ElectrumConnection = class extends EventEmitter {
|
|
|
265
269
|
clearReconnectTimer() {
|
|
266
270
|
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
267
271
|
this.reconnectTimer = void 0;
|
|
272
|
+
this.nextReconnectTimestamp = void 0;
|
|
268
273
|
}
|
|
269
274
|
/**
|
|
270
275
|
* Removes the current keep-alive timer.
|
|
@@ -291,7 +296,7 @@ var ElectrumConnection = class extends EventEmitter {
|
|
|
291
296
|
* @returns true if successfully disconnected, or false if there was no connection.
|
|
292
297
|
*/
|
|
293
298
|
async disconnect(force = false, intentional = true) {
|
|
294
|
-
if (this.status === 0 && !force) {
|
|
299
|
+
if ((this.status === 0 || this.status === 5) && !force) {
|
|
295
300
|
if (intentional) {
|
|
296
301
|
this.clearReconnectTimer();
|
|
297
302
|
await this.closeSocket();
|
|
@@ -387,11 +392,14 @@ var ElectrumConnection = class extends EventEmitter {
|
|
|
387
392
|
* Updates the connection status when a connection is ended.
|
|
388
393
|
*/
|
|
389
394
|
onSocketDisconnect() {
|
|
395
|
+
const isUnrecoverable = this.isUnrecoverable;
|
|
396
|
+
this.isUnrecoverable = false;
|
|
390
397
|
this.clearKeepAliveTimer();
|
|
391
398
|
this.clearVerificationTimers();
|
|
392
399
|
if (this.status === 2) {
|
|
393
|
-
this.status = 0;
|
|
400
|
+
this.status = isUnrecoverable ? 5 : 0;
|
|
394
401
|
this.emit("disconnected");
|
|
402
|
+
if (isUnrecoverable) this.emit("unrecoverable");
|
|
395
403
|
this.clearReconnectTimer();
|
|
396
404
|
debug.network(`Disconnected from '${this.hostIdentifier}'.`);
|
|
397
405
|
} else {
|
|
@@ -399,7 +407,10 @@ var ElectrumConnection = class extends EventEmitter {
|
|
|
399
407
|
else if (this.isAutomaticReconnectAttempt) debug.network(`Reconnection attempt to '${this.hostIdentifier}' failed, trying again in ${this.options.reconnectAfterMilliSeconds / 1e3} seconds.`);
|
|
400
408
|
this.status = 0;
|
|
401
409
|
if (!this.isAutomaticReconnectAttempt) this.emit("disconnected");
|
|
402
|
-
if (!this.reconnectTimer)
|
|
410
|
+
if (!this.reconnectTimer) {
|
|
411
|
+
this.reconnectTimer = setTimeout(this.reconnect.bind(this), this.options.reconnectAfterMilliSeconds);
|
|
412
|
+
this.nextReconnectTimestamp = Date.now() + this.options.reconnectAfterMilliSeconds;
|
|
413
|
+
}
|
|
403
414
|
}
|
|
404
415
|
}
|
|
405
416
|
/**
|
|
@@ -427,11 +438,13 @@ var ElectrumConnection = class extends EventEmitter {
|
|
|
427
438
|
};
|
|
428
439
|
const versionValidator = (version) => {
|
|
429
440
|
if (isVersionRejected(version)) {
|
|
441
|
+
this.isUnrecoverable = true;
|
|
430
442
|
this.disconnect(true);
|
|
431
443
|
const errorMessage = "unsupported protocol version.";
|
|
432
444
|
debug.errors(`Failed to connect with ${this.hostIdentifier} due to ${errorMessage}`);
|
|
433
445
|
rejectNegotiation(/* @__PURE__ */ new Error(errorMessage));
|
|
434
446
|
} else if (version.protocol !== this.version && `${version.protocol}.0` !== this.version && `${version.protocol}.0.0` !== this.version) {
|
|
447
|
+
this.isUnrecoverable = true;
|
|
435
448
|
this.disconnect(true);
|
|
436
449
|
const errorMessage = `incompatible protocol version negotiated (${version.protocol} !== ${this.version}).`;
|
|
437
450
|
debug.errors(`Failed to connect with ${this.hostIdentifier} due to ${errorMessage}`);
|
|
@@ -531,6 +544,7 @@ var ElectrumClient = class extends EventEmitter {
|
|
|
531
544
|
this.connection.on("connecting", this.handleConnectionStatusChanges.bind(this, "connecting"));
|
|
532
545
|
this.connection.on("disconnecting", this.handleConnectionStatusChanges.bind(this, "disconnecting"));
|
|
533
546
|
this.connection.on("reconnecting", this.handleConnectionStatusChanges.bind(this, "reconnecting"));
|
|
547
|
+
this.connection.on("unrecoverable", this.handleConnectionStatusChanges.bind(this, "unrecoverable"));
|
|
534
548
|
this.connection.on("version", this.storeSoftwareVersion.bind(this));
|
|
535
549
|
this.connection.on("received", this.updateLastReceivedTimestamp.bind(this));
|
|
536
550
|
this.connection.on("error", this.emit.bind(this, "error"));
|
|
@@ -542,6 +556,12 @@ var ElectrumClient = class extends EventEmitter {
|
|
|
542
556
|
return this.connection.encrypted;
|
|
543
557
|
}
|
|
544
558
|
/**
|
|
559
|
+
* Timestamp of the pending automatic reconnection attempt, or undefined if none is scheduled.
|
|
560
|
+
*/
|
|
561
|
+
get nextReconnectTimestamp() {
|
|
562
|
+
return this.connection.nextReconnectTimestamp;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
545
565
|
* Connects to the remote server.
|
|
546
566
|
*
|
|
547
567
|
* @throws {Error} if the socket connection fails.
|
|
@@ -770,6 +790,7 @@ var ElectrumClient = class extends EventEmitter {
|
|
|
770
790
|
disconnecting;
|
|
771
791
|
disconnected;
|
|
772
792
|
reconnecting;
|
|
793
|
+
unrecoverable;
|
|
773
794
|
notification;
|
|
774
795
|
error;
|
|
775
796
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../source/electrum-protocol.ts","../source/rpc-interfaces.ts","../source/enums.ts","../source/interfaces.ts","../source/constants.ts","../source/electrum-connection.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 { ElectrumSocketOptions } from '@electrum-cash/socket';\n\n/**\n * Optional settings that change the default behavior of the network connection.\n */\nexport interface ElectrumNetworkOptions extends Partial<ElectrumSocketOptions>\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 * @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 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\n/**\n * The `automatic` argument of connect() for a reconnection attempt the connection starts on its own,\n * as opposed to a connect() the application asked for.\n */\nexport const automaticAttempt = true;\n\n/**\n * The `force` argument of disconnect() for a disconnect that tears the connection down whatever state it is in,\n * even when it is already marked as disconnected, such as between reconnection attempts.\n */\nexport const doForce = true;\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 { automaticAttempt, doForce } from './constants.ts';\nimport type { ElectrumNetworkOptions, ElectrumConnectionEvents, ResolveFunction, RejectFunction, VersionNegotiationResponse } from './interfaces.ts';\nimport type { RPCResponse } from './rpc-interfaces.ts';\nimport type { ElectrumSocket } from '@electrum-cash/socket';\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// Whether the current connection attempt is an automatic reconnection after a lost connection.\n\tprivate isAutomaticReconnectAttempt = false;\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, 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.host;\n\t}\n\n\t// Expose port from the socket.\n\tget encrypted(): boolean\n\t{\n\t\treturn this.socket.options.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, as the data proves the connection is alive.\n\t\tthis.clearVerificationTimers();\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\tasync ping(): Promise<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\ttry\n\t\t{\n\t\t\t// Send the keep-alive message and return the send status.\n\t\t\treturn await this.send(message);\n\t\t}\n\t\tcatch (_ignored)\n\t\t{\n\t\t\t// Return false to indicate that the ping was not sent.\n\t\t\t// NOTE: A closed socket is reported separately through the 'disconnected' event.\n\t\t\treturn false;\n\t\t}\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 * @param automatic - whether this is an automatic reconnection attempt rather than a request by the application; a failed automatic attempt is logged instead of reported.\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(automatic: boolean = false): 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// Remember whether this attempt is automatic, so that its failure is logged rather than reported as another loss.\n\t\tthis.isAutomaticReconnectAttempt = automatic;\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// Declare the functions that settle the connection promise.\n\t\tlet resolveConnection: ResolveFunction<void>;\n\t\tlet rejectConnection: RejectFunction;\n\n\t\t// Create a promise that is settled by the events below, once the connection is established or lost.\n\t\tconst connectionPromise = new Promise<void>((resolve, reject) =>\n\t\t{\n\t\t\tresolveConnection = resolve;\n\t\t\trejectConnection = reject;\n\t\t});\n\n\t\t// Define a function to resolve the connection promise once the connection is established.\n\t\t// NOTE: This event is emitted by onSocketConnect after successful version negotiation.\n\t\tconst onConnected = (): void =>\n\t\t{\n\t\t\tresolveConnection();\n\t\t};\n\n\t\t// Declare the error reported by the socket while connecting, if any.\n\t\tlet socketError: unknown;\n\n\t\t// Define a function to reject the connection promise if the connection is lost before it is established.\n\t\t// NOTE: This listens on the socket rather than on the connection, as the connection does not report a failed\n\t\t// automatic reconnection attempt. It runs after onSocketDisconnect has updated the connection state,\n\t\t// so callers see a settled state.\n\t\tconst onDisconnected = (): void =>\n\t\t{\n\t\t\trejectConnection(socketError ?? new Error(`Failed to connect to '${this.hostIdentifier}'.`));\n\t\t};\n\n\t\t// Settle the connection promise on whichever event happens first.\n\t\tthis.once('connected', onConnected);\n\t\tthis.socket.once('disconnected', onDisconnected);\n\n\t\ttry\n\t\t{\n\t\t\t// Start the socket connection process.\n\t\t\tawait this.socket.connect();\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Keep the error as the reason for the failed connection.\n\t\t\t// NOTE: A failed socket connection is reported both by rejecting this promise and through the 'disconnected'\n\t\t\t// event. The event is what settles the connection promise, so that the connection state is already\n\t\t\t// updated when the caller is told; the error is only kept to give the caller the reason.\n\t\t\tsocketError = error;\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\t// Wait until connection is established and version negotiation succeeds.\n\t\t\tawait connectionPromise;\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\t// Remove whichever listener did not fire, so that it cannot linger until the next event.\n\t\t\tthis.removeListener('connected', onConnected);\n\t\t\tthis.socket.removeListener('disconnected', onDisconnected);\n\t\t}\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.closeSocket();\n\n\t\ttry\n\t\t{\n\t\t\t// Try to connect again, as an automatic attempt.\n\t\t\tawait this.connect(automaticAttempt);\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.\n\t *\n\t * @remarks The event listeners are left in place, as they are owned by the client and stay attached for its lifetime.\n\t * @remarks An intentional disconnect also cancels a pending reconnection and closes the socket, whether or not there was a connection to close.\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// Close the socket and cancel a pending reconnection if the disconnect is intentional.\n\t\t\tif(intentional)\n\t\t\t{\n\t\t\t\t// Cancel a pending reconnection before closing the socket.\n\t\t\t\t// NOTE: Closing can take a while, as a socket that is reconnecting on its own must finish that first.\n\t\t\t\t// A reconnection timer firing meanwhile would queue a connect behind our close, which nothing\n\t\t\t\t// afterwards can undo, and the client would reconnect after being asked to disconnect.\n\t\t\t\tthis.clearReconnectTimer();\n\n\t\t\t\t// Close the socket, so that it stops reconnecting on its own and removes its browser listeners.\n\t\t\t\tawait this.closeSocket();\n\n\t\t\t\t// Cancel a reconnection that closing the socket may have scheduled.\n\t\t\t\t// NOTE: A socket that had reconnected on its own reports the closure through onSocketDisconnect(),\n\t\t\t\t// which schedules a new reconnection.\n\t\t\t\tthis.clearReconnectTimer();\n\n\t\t\t\t// The cancelled reconnection ends any automatic reconnection.\n\t\t\t\tthis.isAutomaticReconnectAttempt = false;\n\t\t\t}\n\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\n\t\t\t// The tear-down ends any automatic reconnection.\n\t\t\tthis.isAutomaticReconnectAttempt = false;\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// If the socket is already disconnected it will not emit a 'disconnected' event,\n\t\t\t// so we finalize the disconnection directly instead of waiting for the event.\n\t\t\t// NOTE: This happens when disconnecting before a connection was made, or after a connection was lost.\n\t\t\tif(this.socket.isDisconnected)\n\t\t\t{\n\t\t\t\t// Update the connection state as if the socket had reported the disconnection.\n\t\t\t\tthis.onSocketDisconnect();\n\n\t\t\t\t// Resolve to true as the connection has been torn down.\n\t\t\t\tresolve(true);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Resolve to true after the connection emits a disconnect\n\t\t\t// NOTE: This listener is consumed by the 'disconnected' event that closing the socket triggers, and calls\n\t\t\t// to disconnect() are serialized by the client, so at most one such listener is pending at any time.\n\t\t\tthis.once('disconnected', () => resolve(true));\n\n\t\t\t// Close the connection on the socket level.\n\t\t\tthis.closeSocket();\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\tasync send(message: string): Promise<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.options.timeoutInMilliSeconds) 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/**\n\t * Closes the underlying socket.\n\t *\n\t * @remarks The socket reports the closed connection through its 'disconnected' event, which is what drives\n\t * our connection state. Any promise returned by the socket is consumed here so that a failure to\n\t * close cannot surface as an unhandled rejection.\n\t */\n\tasync closeSocket(): Promise<void>\n\t{\n\t\ttry\n\t\t{\n\t\t\t// Close the connection on the socket level.\n\t\t\tawait this.socket.disconnect();\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Log a warning, as there is nothing we can do to actually handle it.\n\t\t\tdebug.warning(`Failed to close the socket for '${this.hostIdentifier}': `, error);\n\t\t}\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.closeSocket();\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\n\t\ttry\n\t\t{\n\t\t\t// Wait for the version to be negotiated.\n\t\t\tawait this.negotiateVersion();\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Log the failure, as the connection has already been torn down by the version negotiation.\n\t\t\tdebug.errors(`Aborted connection with '${this.hostIdentifier}': `, error);\n\n\t\t\t// Do not emit the connected event, since the connection is not usable.\n\t\t\treturn;\n\t\t}\n\n\t\t// The connection is restored, so the next loss is a new one to report.\n\t\tthis.isAutomaticReconnectAttempt = false;\n\n\t\t// Emit a connect event now that the connection is established and version negotiated.\n\t\tthis.emit('connected');\n\t}\n\n\t/**\n\t * Removes the pending verification timers.\n\t */\n\tclearVerificationTimers(): void\n\t{\n\t\t// Clear and remove all verification timers.\n\t\tthis.verifications.forEach((timer) => clearTimeout(timer));\n\t\tthis.verifications.length = 0;\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// Remove the verification timers of the lost connection, so that they cannot close the next one.\n\t\t// NOTE: A timer armed by a message sent before the loss would otherwise fire while the next\n\t\t// connection is being established, see that no data has arrived since, and close it.\n\t\tthis.clearVerificationTimers();\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// 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 failed automatic reconnection attempt..\n\t\t\telse if(this.isAutomaticReconnectAttempt)\n\t\t\t{\n\t\t\t\t// Write a notification to the logs.\n\t\t\t\tdebug.network(`Reconnection attempt to '${this.hostIdentifier}' failed, trying again in ${this.options.reconnectAfterMilliSeconds / 1000} seconds.`);\n\t\t\t}\n\t\t\t// If this is a connection that is currently connecting 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 connecting 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, unless this is a failed automatic reconnection attempt:\n\t\t\t// the loss was already reported when it happened, and the attempt changed nothing for the layers above.\n\t\t\tif(!this.isAutomaticReconnectAttempt)\n\t\t\t{\n\t\t\t\tthis.emit('disconnected');\n\t\t\t}\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 * Negotiates the protocol version with the server, and disconnects if the negotiation fails.\n\t *\n\t * @throws {Error} if the server rejects or negotiates an incompatible protocol version, or the connection is lost.\n\t * @returns a promise resolving when the protocol version has been negotiated.\n\t */\n\tasync negotiateVersion(): Promise<void>\n\t{\n\t\t// Declare the functions that settle the negotiation promise.\n\t\tlet resolveNegotiation: ResolveFunction<void>;\n\t\tlet rejectNegotiation: RejectFunction;\n\n\t\t// Create a promise that is settled by the handlers below, once the negotiation has completed or failed.\n\t\tconst negotiationPromise = new Promise<void>((resolve, reject) =>\n\t\t{\n\t\t\tresolveNegotiation = resolve;\n\t\t\trejectNegotiation = reject;\n\t\t});\n\n\t\t// Define a function to reject the negotiation if the connection is lost while negotiating.\n\t\t// NOTE: The connection state is updated by onSocketDisconnect, which handles the same event.\n\t\tconst rejector = (): void =>\n\t\t{\n\t\t\t// Reject with the error as reason\n\t\t\trejectNegotiation(new Error(`Version negotiation with '${this.hostIdentifier}' failed due to lost connection.`));\n\t\t};\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(doForce);\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 negotiation since the server refused the protocol version.\n\t\t\t\trejectNegotiation(new Error(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(doForce);\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 negotiation since the protocol versions are incompatible.\n\t\t\t\trejectNegotiation(new Error(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 negotiation since the protocol version was accepted.\n\t\t\t\tresolveNegotiation();\n\t\t\t}\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\n\t\t// Settle the negotiation promise on whichever event happens first.\n\t\tthis.socket.once('disconnected', rejector);\n\t\tthis.once('version', versionValidator);\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\ttry\n\t\t{\n\t\t\t// Send the version negotiation message.\n\t\t\tawait this.send(versionMessage);\n\t\t}\n\t\tcatch (_ignored)\n\t\t{\n\t\t\t// Do nothing, as a failed send means the socket has closed, which is reported through the 'disconnected' event.\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\t// Wait until the server has answered the version negotiation.\n\t\t\tawait negotiationPromise;\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\t// Remove whichever listener did not fire, so that it cannot linger until the next event.\n\t\t\tthis.socket.removeListener('disconnected', rejector);\n\t\t\tthis.removeListener('version', versionValidator);\n\t\t}\n\t}\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, ResolveFunction, RequestResolver, RequestResponse } from './interfaces.ts';\nimport type { ElectrumSocket } from '@electrum-cash/socket';\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: Partial<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\n\t\t// Attach the handlers for events emitted by the connection.\n\t\t// NOTE: This is done once, as the client owns the connection for its entire lifetime and the connection never removes them.\n\t\tthis.attachConnectionHandlers();\n\t}\n\n\t/**\n\t * Attaches the handlers for events emitted by the underlying connection.\n\t *\n\t * @ignore\n\t */\n\tprivate attachConnectionHandlers(): void\n\t{\n\t\t// Listen for parsed statements.\n\t\tthis.connection.on('response', this.response.bind(this));\n\n\t\t// Hook up handles for the connected and disconnected events.\n\t\tthis.connection.on('connected', this.resubscribeOnConnect.bind(this));\n\t\tthis.connection.on('disconnected', this.onConnectionDisconnect.bind(this));\n\n\t\t// Relay connecting and reconnecting events.\n\t\tthis.connection.on('connecting', this.handleConnectionStatusChanges.bind(this, 'connecting'));\n\t\tthis.connection.on('disconnecting', this.handleConnectionStatusChanges.bind(this, 'disconnecting'));\n\t\tthis.connection.on('reconnecting', this.handleConnectionStatusChanges.bind(this, 'reconnecting'));\n\n\t\t// Hook up client metadata gathering functions.\n\t\tthis.connection.on('version', this.storeSoftwareVersion.bind(this));\n\t\tthis.connection.on('received', this.updateLastReceivedTimestamp.bind(this));\n\n\t\t// Relay error events.\n\t\tthis.connection.on('error', this.emit.bind(this, 'error'));\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// Force concurrent calls to this function to be run serially.\n\t\treturn this.connectionLock.runExclusive(async () =>\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// Connect with the server.\n\t\t\tawait this.connection.connect();\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\t// Force concurrent calls to this function to be run serially.\n\t\treturn this.connectionLock.runExclusive(async () =>\n\t\t{\n\t\t\tif(!retainSubscriptions)\n\t\t\t{\n\t\t\t\t// Cancel all event listeners.\n\t\t\t\tthis.removeAllListeners();\n\n\t\t\t\t// Remove all subscription data\n\t\t\t\tthis.subscriptionMethods = {};\n\t\t\t}\n\n\t\t\t// Disconnect from the remote server.\n\t\t\treturn this.connection.disconnect(force);\n\t\t});\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};\n\n\t\t// Create the promise that delivers the result once the response arrives.\n\t\tconst responsePromise = new Promise<Error | RequestResponse>(requestResolver);\n\n\t\t// Write a log message.\n\t\tdebug.network(`Sending request '${method}' to '${this.hostIdentifier}'`);\n\n\t\ttry\n\t\t{\n\t\t\t// Send the request message to the remote server.\n\t\t\tawait this.connection.send(message);\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Settle the request with the error instead of leaving it pending, unless a lost connection already settled it.\n\t\t\tthis.abortRequest(id, error);\n\t\t}\n\n\t\t// return the promise to deliver results later.\n\t\treturn responsePromise;\n\t}\n\n\t/**\n\t * Settles a pending request with an error, for instance when its message could not be sent.\n\t *\n\t * @param id - the id of the request to settle.\n\t * @param error - the reason the request failed.\n\t *\n\t * @ignore\n\t */\n\tprivate abortRequest(id: number, error: unknown): void\n\t{\n\t\t// Look up the request resolver, as the request may already have been settled by a lost connection.\n\t\tconst requestResolver = this.requestResolvers[id];\n\n\t\t// Do nothing if the request has already been settled.\n\t\tif(!requestResolver)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove the promise from the request list.\n\t\tdelete this.requestResolvers[id];\n\n\t\t// Resolve the request with the error for the application to handle.\n\t\trequestResolver(error instanceof Error ? error : new Error(String(error)));\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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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../source/electrum-protocol.ts","../source/rpc-interfaces.ts","../source/enums.ts","../source/interfaces.ts","../source/constants.ts","../source/electrum-connection.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 * @property {5} UNRECOVERABLE The connection has stopped reconnecting, as it cannot recover on its own.\n */\nexport enum ConnectionStatus\n{\n\tDISCONNECTED = 0,\n\tCONNECTED = 1,\n\tDISCONNECTING = 2,\n\tCONNECTING = 3,\n\tRECONNECTING = 4,\n\tUNRECOVERABLE = 5,\n}\n","import type { RPCError, RPCParameter, RPCResponse, RPCNotification } from './rpc-interfaces';\nimport type { ElectrumSocketOptions } from '@electrum-cash/socket';\n\n/**\n * Optional settings that change the default behavior of the network connection.\n */\nexport interface ElectrumNetworkOptions extends Partial<ElectrumSocketOptions>\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 * @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 connection stops reconnecting, as it cannot recover on its own.\n\t * @eventProperty\n\t */\n\t'unrecoverable': [];\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 connection stops reconnecting, as it cannot recover on its own.\n\t * @eventProperty\n\t */\n\t'unrecoverable': [];\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 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\n/**\n * The `automatic` argument of connect() for a reconnection attempt the connection starts on its own,\n * as opposed to a connect() the application asked for.\n */\nexport const automaticAttempt = true;\n\n/**\n * The `force` argument of disconnect() for a disconnect that tears the connection down whatever state it is in,\n * even when it is already marked as disconnected, such as between reconnection attempts.\n */\nexport const doForce = true;\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 { automaticAttempt, doForce } from './constants.ts';\nimport type { ElectrumNetworkOptions, ElectrumConnectionEvents, ResolveFunction, RejectFunction, VersionNegotiationResponse } from './interfaces.ts';\nimport type { RPCResponse } from './rpc-interfaces.ts';\nimport type { ElectrumSocket } from '@electrum-cash/socket';\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// Timestamp of the pending automatic reconnection attempt, or undefined if none is scheduled.\n\tpublic nextReconnectTimestamp?: 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// Whether the current connection attempt is an automatic reconnection after a lost connection.\n\tprivate isAutomaticReconnectAttempt = false;\n\n\t// Whether the connection cannot recover on its own, so that it stops reconnecting once it is torn down.\n\tprivate isUnrecoverable = false;\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, 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.host;\n\t}\n\n\t// Expose port from the socket.\n\tget encrypted(): boolean\n\t{\n\t\treturn this.socket.options.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, as the data proves the connection is alive.\n\t\tthis.clearVerificationTimers();\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\tasync ping(): Promise<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\ttry\n\t\t{\n\t\t\t// Send the keep-alive message and return the send status.\n\t\t\treturn await this.send(message);\n\t\t}\n\t\tcatch (_ignored)\n\t\t{\n\t\t\t// Return false to indicate that the ping was not sent.\n\t\t\t// NOTE: A closed socket is reported separately through the 'disconnected' event.\n\t\t\treturn false;\n\t\t}\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 * @param automatic - whether this is an automatic reconnection attempt rather than a request by the application; a failed automatic attempt is logged instead of reported.\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(automatic: boolean = false): 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// Remember whether this attempt is automatic, so that its failure is logged rather than reported as another loss.\n\t\tthis.isAutomaticReconnectAttempt = automatic;\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// Declare the functions that settle the connection promise.\n\t\tlet resolveConnection: ResolveFunction<void>;\n\t\tlet rejectConnection: RejectFunction;\n\n\t\t// Create a promise that is settled by the events below, once the connection is established or lost.\n\t\tconst connectionPromise = new Promise<void>((resolve, reject) =>\n\t\t{\n\t\t\tresolveConnection = resolve;\n\t\t\trejectConnection = reject;\n\t\t});\n\n\t\t// Define a function to resolve the connection promise once the connection is established.\n\t\t// NOTE: This event is emitted by onSocketConnect after successful version negotiation.\n\t\tconst onConnected = (): void =>\n\t\t{\n\t\t\tresolveConnection();\n\t\t};\n\n\t\t// Declare the error reported by the socket while connecting, if any.\n\t\tlet socketError: unknown;\n\n\t\t// Define a function to reject the connection promise if the connection is lost before it is established.\n\t\t// NOTE: This listens on the socket rather than on the connection, as the connection does not report a failed\n\t\t// automatic reconnection attempt. It runs after onSocketDisconnect has updated the connection state,\n\t\t// so callers see a settled state.\n\t\tconst onDisconnected = (): void =>\n\t\t{\n\t\t\trejectConnection(socketError ?? new Error(`Failed to connect to '${this.hostIdentifier}'.`));\n\t\t};\n\n\t\t// Settle the connection promise on whichever event happens first.\n\t\tthis.once('connected', onConnected);\n\t\tthis.socket.once('disconnected', onDisconnected);\n\n\t\ttry\n\t\t{\n\t\t\t// Start the socket connection process.\n\t\t\tawait this.socket.connect();\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Keep the error as the reason for the failed connection.\n\t\t\t// NOTE: A failed socket connection is reported both by rejecting this promise and through the 'disconnected'\n\t\t\t// event. The event is what settles the connection promise, so that the connection state is already\n\t\t\t// updated when the caller is told; the error is only kept to give the caller the reason.\n\t\t\tsocketError = error;\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\t// Wait until connection is established and version negotiation succeeds.\n\t\t\tawait connectionPromise;\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\t// Remove whichever listener did not fire, so that it cannot linger until the next event.\n\t\t\tthis.removeListener('connected', onConnected);\n\t\t\tthis.socket.removeListener('disconnected', onDisconnected);\n\t\t}\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.closeSocket();\n\n\t\ttry\n\t\t{\n\t\t\t// Try to connect again, as an automatic attempt.\n\t\t\tawait this.connect(automaticAttempt);\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 and its timestamp.\n\t\tthis.reconnectTimer = undefined;\n\t\tthis.nextReconnectTimestamp = 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.\n\t *\n\t * @remarks The event listeners are left in place, as they are owned by the client and stay attached for its lifetime.\n\t * @remarks An intentional disconnect also cancels a pending reconnection and closes the socket, whether or not there was a connection to close.\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 || this.status === ConnectionStatus.UNRECOVERABLE) && !force)\n\t\t{\n\t\t\t// Close the socket and cancel a pending reconnection if the disconnect is intentional.\n\t\t\tif(intentional)\n\t\t\t{\n\t\t\t\t// Cancel a pending reconnection before closing the socket.\n\t\t\t\t// NOTE: Closing can take a while, as a socket that is reconnecting on its own must finish that first.\n\t\t\t\t// A reconnection timer firing meanwhile would queue a connect behind our close, which nothing\n\t\t\t\t// afterwards can undo, and the client would reconnect after being asked to disconnect.\n\t\t\t\tthis.clearReconnectTimer();\n\n\t\t\t\t// Close the socket, so that it stops reconnecting on its own and removes its browser listeners.\n\t\t\t\tawait this.closeSocket();\n\n\t\t\t\t// Cancel a reconnection that closing the socket may have scheduled.\n\t\t\t\t// NOTE: A socket that had reconnected on its own reports the closure through onSocketDisconnect(),\n\t\t\t\t// which schedules a new reconnection.\n\t\t\t\tthis.clearReconnectTimer();\n\n\t\t\t\t// The cancelled reconnection ends any automatic reconnection.\n\t\t\t\tthis.isAutomaticReconnectAttempt = false;\n\t\t\t}\n\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\n\t\t\t// The tear-down ends any automatic reconnection.\n\t\t\tthis.isAutomaticReconnectAttempt = false;\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// If the socket is already disconnected it will not emit a 'disconnected' event,\n\t\t\t// so we finalize the disconnection directly instead of waiting for the event.\n\t\t\t// NOTE: This happens when disconnecting before a connection was made, or after a connection was lost.\n\t\t\tif(this.socket.isDisconnected)\n\t\t\t{\n\t\t\t\t// Update the connection state as if the socket had reported the disconnection.\n\t\t\t\tthis.onSocketDisconnect();\n\n\t\t\t\t// Resolve to true as the connection has been torn down.\n\t\t\t\tresolve(true);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Resolve to true after the connection emits a disconnect\n\t\t\t// NOTE: This listener is consumed by the 'disconnected' event that closing the socket triggers, and calls\n\t\t\t// to disconnect() are serialized by the client, so at most one such listener is pending at any time.\n\t\t\tthis.once('disconnected', () => resolve(true));\n\n\t\t\t// Close the connection on the socket level.\n\t\t\tthis.closeSocket();\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\tasync send(message: string): Promise<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.options.timeoutInMilliSeconds) 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/**\n\t * Closes the underlying socket.\n\t *\n\t * @remarks The socket reports the closed connection through its 'disconnected' event, which is what drives\n\t * our connection state. Any promise returned by the socket is consumed here so that a failure to\n\t * close cannot surface as an unhandled rejection.\n\t */\n\tasync closeSocket(): Promise<void>\n\t{\n\t\ttry\n\t\t{\n\t\t\t// Close the connection on the socket level.\n\t\t\tawait this.socket.disconnect();\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Log a warning, as there is nothing we can do to actually handle it.\n\t\t\tdebug.warning(`Failed to close the socket for '${this.hostIdentifier}': `, error);\n\t\t}\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.closeSocket();\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\n\t\ttry\n\t\t{\n\t\t\t// Wait for the version to be negotiated.\n\t\t\tawait this.negotiateVersion();\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Log the failure, as the connection has already been torn down by the version negotiation.\n\t\t\tdebug.errors(`Aborted connection with '${this.hostIdentifier}': `, error);\n\n\t\t\t// Do not emit the connected event, since the connection is not usable.\n\t\t\treturn;\n\t\t}\n\n\t\t// The connection is restored, so the next loss is a new one to report.\n\t\tthis.isAutomaticReconnectAttempt = false;\n\n\t\t// Emit a connect event now that the connection is established and version negotiated.\n\t\tthis.emit('connected');\n\t}\n\n\t/**\n\t * Removes the pending verification timers.\n\t */\n\tclearVerificationTimers(): void\n\t{\n\t\t// Clear and remove all verification timers.\n\t\tthis.verifications.forEach((timer) => clearTimeout(timer));\n\t\tthis.verifications.length = 0;\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// Take the unrecoverable flag and reset it, so that it applies only to the disconnect that follows it.\n\t\t// NOTE: The flag is set before the connection is torn down, but the application can call connect() before the\n\t\t// socket reports the disconnect, which then arrives as a lost connection rather than a teardown. Resetting\n\t\t// the flag only when marking the connection unrecoverable would leave it set, and a later ordinary\n\t\t// disconnect would wrongly be marked unrecoverable.\n\t\tconst isUnrecoverable = this.isUnrecoverable;\n\t\tthis.isUnrecoverable = false;\n\n\t\t// Remove the current keep-alive timer if it exists.\n\t\tthis.clearKeepAliveTimer();\n\n\t\t// Remove the verification timers of the lost connection, so that they cannot close the next one.\n\t\t// NOTE: A timer armed by a message sent before the loss would otherwise fire while the next\n\t\t// connection is being established, see that no data has arrived since, and close it.\n\t\tthis.clearVerificationTimers();\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, or as unrecoverable if it cannot recover on its own.\n\t\t\tthis.status = (isUnrecoverable ? ConnectionStatus.UNRECOVERABLE : 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// Signal that the connection will not reconnect, if it cannot recover on its own.\n\t\t\tif(isUnrecoverable)\n\t\t\t{\n\t\t\t\tthis.emit('unrecoverable');\n\t\t\t}\n\n\t\t\t// If a reconnect timer is set, remove it.\n\t\t\tthis.clearReconnectTimer();\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 failed automatic reconnection attempt..\n\t\t\telse if(this.isAutomaticReconnectAttempt)\n\t\t\t{\n\t\t\t\t// Write a notification to the logs.\n\t\t\t\tdebug.network(`Reconnection attempt to '${this.hostIdentifier}' failed, trying again in ${this.options.reconnectAfterMilliSeconds / 1000} seconds.`);\n\t\t\t}\n\t\t\t// If this is a connection that is currently connecting 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 connecting 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, unless this is a failed automatic reconnection attempt:\n\t\t\t// the loss was already reported when it happened, and the attempt changed nothing for the layers above.\n\t\t\tif(!this.isAutomaticReconnectAttempt)\n\t\t\t{\n\t\t\t\tthis.emit('disconnected');\n\t\t\t}\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\tthis.nextReconnectTimestamp = Date.now() + this.options.reconnectAfterMilliSeconds;\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 * Negotiates the protocol version with the server, and disconnects if the negotiation fails.\n\t *\n\t * @throws {Error} if the server rejects or negotiates an incompatible protocol version, or the connection is lost.\n\t * @returns a promise resolving when the protocol version has been negotiated.\n\t */\n\tasync negotiateVersion(): Promise<void>\n\t{\n\t\t// Declare the functions that settle the negotiation promise.\n\t\tlet resolveNegotiation: ResolveFunction<void>;\n\t\tlet rejectNegotiation: RejectFunction;\n\n\t\t// Create a promise that is settled by the handlers below, once the negotiation has completed or failed.\n\t\tconst negotiationPromise = new Promise<void>((resolve, reject) =>\n\t\t{\n\t\t\tresolveNegotiation = resolve;\n\t\t\trejectNegotiation = reject;\n\t\t});\n\n\t\t// Define a function to reject the negotiation if the connection is lost while negotiating.\n\t\t// NOTE: The connection state is updated by onSocketDisconnect, which handles the same event.\n\t\tconst rejector = (): void =>\n\t\t{\n\t\t\t// Reject with the error as reason\n\t\t\trejectNegotiation(new Error(`Version negotiation with '${this.hostIdentifier}' failed due to lost connection.`));\n\t\t};\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, and stop reconnecting to it, as it will keep refusing our protocol version.\n\t\t\t\tthis.isUnrecoverable = true;\n\t\t\t\tthis.disconnect(doForce);\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 negotiation since the server refused the protocol version.\n\t\t\t\trejectNegotiation(new Error(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, and stop reconnecting to it, as it will keep answering with an incompatible protocol version.\n\t\t\t\tthis.isUnrecoverable = true;\n\t\t\t\tthis.disconnect(doForce);\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 negotiation since the protocol versions are incompatible.\n\t\t\t\trejectNegotiation(new Error(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 negotiation since the protocol version was accepted.\n\t\t\t\tresolveNegotiation();\n\t\t\t}\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\n\t\t// Settle the negotiation promise on whichever event happens first.\n\t\tthis.socket.once('disconnected', rejector);\n\t\tthis.once('version', versionValidator);\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\ttry\n\t\t{\n\t\t\t// Send the version negotiation message.\n\t\t\tawait this.send(versionMessage);\n\t\t}\n\t\tcatch (_ignored)\n\t\t{\n\t\t\t// Do nothing, as a failed send means the socket has closed, which is reported through the 'disconnected' event.\n\t\t}\n\n\t\ttry\n\t\t{\n\t\t\t// Wait until the server has answered the version negotiation.\n\t\t\tawait negotiationPromise;\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\t// Remove whichever listener did not fire, so that it cannot linger until the next event.\n\t\t\tthis.socket.removeListener('disconnected', rejector);\n\t\t\tthis.removeListener('version', versionValidator);\n\t\t}\n\t}\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, ResolveFunction, RequestResolver, RequestResponse } from './interfaces.ts';\nimport type { ElectrumSocket } from '@electrum-cash/socket';\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: Partial<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\n\t\t// Attach the handlers for events emitted by the connection.\n\t\t// NOTE: This is done once, as the client owns the connection for its entire lifetime and the connection never removes them.\n\t\tthis.attachConnectionHandlers();\n\t}\n\n\t/**\n\t * Attaches the handlers for events emitted by the underlying connection.\n\t *\n\t * @ignore\n\t */\n\tprivate attachConnectionHandlers(): void\n\t{\n\t\t// Listen for parsed statements.\n\t\tthis.connection.on('response', this.response.bind(this));\n\n\t\t// Hook up handles for the connected and disconnected events.\n\t\tthis.connection.on('connected', this.resubscribeOnConnect.bind(this));\n\t\tthis.connection.on('disconnected', this.onConnectionDisconnect.bind(this));\n\n\t\t// Relay connection status events.\n\t\tthis.connection.on('connecting', this.handleConnectionStatusChanges.bind(this, 'connecting'));\n\t\tthis.connection.on('disconnecting', this.handleConnectionStatusChanges.bind(this, 'disconnecting'));\n\t\tthis.connection.on('reconnecting', this.handleConnectionStatusChanges.bind(this, 'reconnecting'));\n\t\tthis.connection.on('unrecoverable', this.handleConnectionStatusChanges.bind(this, 'unrecoverable'));\n\n\t\t// Hook up client metadata gathering functions.\n\t\tthis.connection.on('version', this.storeSoftwareVersion.bind(this));\n\t\tthis.connection.on('received', this.updateLastReceivedTimestamp.bind(this));\n\n\t\t// Relay error events.\n\t\tthis.connection.on('error', this.emit.bind(this, 'error'));\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 * Timestamp of the pending automatic reconnection attempt, or undefined if none is scheduled.\n\t */\n\tget nextReconnectTimestamp(): number | undefined\n\t{\n\t\treturn this.connection.nextReconnectTimestamp;\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// Force concurrent calls to this function to be run serially.\n\t\treturn this.connectionLock.runExclusive(async () =>\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// Connect with the server.\n\t\t\tawait this.connection.connect();\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\t// Force concurrent calls to this function to be run serially.\n\t\treturn this.connectionLock.runExclusive(async () =>\n\t\t{\n\t\t\tif(!retainSubscriptions)\n\t\t\t{\n\t\t\t\t// Cancel all event listeners.\n\t\t\t\tthis.removeAllListeners();\n\n\t\t\t\t// Remove all subscription data\n\t\t\t\tthis.subscriptionMethods = {};\n\t\t\t}\n\n\t\t\t// Disconnect from the remote server.\n\t\t\treturn this.connection.disconnect(force);\n\t\t});\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};\n\n\t\t// Create the promise that delivers the result once the response arrives.\n\t\tconst responsePromise = new Promise<Error | RequestResponse>(requestResolver);\n\n\t\t// Write a log message.\n\t\tdebug.network(`Sending request '${method}' to '${this.hostIdentifier}'`);\n\n\t\ttry\n\t\t{\n\t\t\t// Send the request message to the remote server.\n\t\t\tawait this.connection.send(message);\n\t\t}\n\t\tcatch (error)\n\t\t{\n\t\t\t// Settle the request with the error instead of leaving it pending, unless a lost connection already settled it.\n\t\t\tthis.abortRequest(id, error);\n\t\t}\n\n\t\t// return the promise to deliver results later.\n\t\treturn responsePromise;\n\t}\n\n\t/**\n\t * Settles a pending request with an error, for instance when its message could not be sent.\n\t *\n\t * @param id - the id of the request to settle.\n\t * @param error - the reason the request failed.\n\t *\n\t * @ignore\n\t */\n\tprivate abortRequest(id: number, error: unknown): void\n\t{\n\t\t// Look up the request resolver, as the request may already have been settled by a lost connection.\n\t\tconst requestResolver = this.requestResolvers[id];\n\n\t\t// Do nothing if the request has already been settled.\n\t\tif(!requestResolver)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove the promise from the request list.\n\t\tdelete this.requestResolvers[id];\n\n\t\t// Resolve the request with the error for the application to handle.\n\t\trequestResolver(error instanceof Error ? error : new Error(String(error)));\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 unrecoverable: [];\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electrum-cash/network",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0-development.16683964237",
|
|
4
4
|
"description": "@electrum-cash/network is a lightweight JavaScript library that lets you connect with one or more Electrum servers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"electrum",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@electrum-cash/eslint-config": "gitlab:electrum-cash/eslint-config",
|
|
49
|
-
"@electrum-cash/tcp-socket": "^4.1.
|
|
49
|
+
"@electrum-cash/tcp-socket": "^4.1.3",
|
|
50
50
|
"@generalprotocols/cspell-dictionary": "git+https://gitlab.com/GeneralProtocols/cspell-dictionary.git",
|
|
51
51
|
"@stylistic/eslint-plugin": "^5.7.0",
|
|
52
52
|
"@types/debug": "^4.1.6",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@electrum-cash/debug-logs": "^1.0.0",
|
|
70
|
-
"@electrum-cash/socket": "^4.0
|
|
71
|
-
"@electrum-cash/web-socket": "^4.0
|
|
70
|
+
"@electrum-cash/socket": "^4.1.0",
|
|
71
|
+
"@electrum-cash/web-socket": "^4.1.0",
|
|
72
72
|
"async-mutex": "^0.5.0",
|
|
73
73
|
"debug": "^4.3.2",
|
|
74
74
|
"eventemitter3": "^5.0.1",
|