@electrum-cash/web-socket 1.1.1-development.12712257157 → 1.1.1-development.12942990149
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 +21 -3
- package/dist/index.mjs +43 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -71,6 +71,12 @@ interface ElectrumSocket extends EventEmitter<ElectrumSocketEvents>, ElectrumSoc
|
|
|
71
71
|
}
|
|
72
72
|
//#endregion
|
|
73
73
|
//#region source/web.d.ts
|
|
74
|
+
type ElectrumWebSocketReconnectionOptions = {
|
|
75
|
+
/** If true, will not handle visibility changes when run in a browser environment. */
|
|
76
|
+
disableBrowserVisibilityHandling: boolean;
|
|
77
|
+
/** If true, will not handle connectivity changes when run in a browser environment. */
|
|
78
|
+
disableBrowserConnectivityHandling: boolean;
|
|
79
|
+
};
|
|
74
80
|
/**
|
|
75
81
|
* Web Socket used when communicating with Electrum servers.
|
|
76
82
|
*/
|
|
@@ -79,10 +85,13 @@ declare class ElectrumWebSocket extends EventEmitter<ElectrumSocketEvents> imple
|
|
|
79
85
|
port: number;
|
|
80
86
|
encrypted: boolean;
|
|
81
87
|
timeout: number;
|
|
88
|
+
reconnectionOptions: ElectrumWebSocketReconnectionOptions;
|
|
82
89
|
private webSocket;
|
|
83
90
|
private disconnectTimer?;
|
|
84
91
|
private onConnectHasRun;
|
|
85
92
|
private eventForwarders;
|
|
93
|
+
private boundHandleVisibilityChange;
|
|
94
|
+
private boundHandleConnectivityChange;
|
|
86
95
|
/**
|
|
87
96
|
* Creates a socket configured with connection information for a given Electrum server.
|
|
88
97
|
*
|
|
@@ -90,8 +99,9 @@ declare class ElectrumWebSocket extends EventEmitter<ElectrumSocketEvents> imple
|
|
|
90
99
|
* @param port Network port for the host to connect to, defaults to the standard TLS port
|
|
91
100
|
* @param encrypted If false, uses an unencrypted connection instead of the default on TLS
|
|
92
101
|
* @param timeout If no connection is established after `timeout` ms, the connection is terminated
|
|
102
|
+
* @param reconnectionOptions Options to disable the automatic reconnection behavior when browser visibility or connectivity changes
|
|
93
103
|
*/
|
|
94
|
-
constructor(host: string, port?: number, encrypted?: boolean, timeout?: number);
|
|
104
|
+
constructor(host: string, port?: number, encrypted?: boolean, timeout?: number, reconnectionOptions?: ElectrumWebSocketReconnectionOptions);
|
|
95
105
|
/**
|
|
96
106
|
* Returns a string for the host identifier for usage in debug messages.
|
|
97
107
|
*/
|
|
@@ -113,7 +123,7 @@ declare class ElectrumWebSocket extends EventEmitter<ElectrumSocketEvents> imple
|
|
|
113
123
|
*
|
|
114
124
|
* @throws {Error} if no connection was found
|
|
115
125
|
*/
|
|
116
|
-
disconnect(): void;
|
|
126
|
+
disconnect(suspend?: boolean): void;
|
|
117
127
|
/**
|
|
118
128
|
* Write data to the socket
|
|
119
129
|
*
|
|
@@ -129,11 +139,19 @@ declare class ElectrumWebSocket extends EventEmitter<ElectrumSocketEvents> imple
|
|
|
129
139
|
* Force a disconnection if no connection is established after `timeout` milliseconds.
|
|
130
140
|
*/
|
|
131
141
|
private disconnectOnTimeout;
|
|
142
|
+
/**
|
|
143
|
+
* Handles visibility changes when run in a browser environment.
|
|
144
|
+
*/
|
|
145
|
+
private handleVisibilityChange;
|
|
146
|
+
/**
|
|
147
|
+
* Handles connectivity changes when run in a browser environment.
|
|
148
|
+
*/
|
|
149
|
+
private handleConnectivityChange;
|
|
132
150
|
readonly connected: [];
|
|
133
151
|
readonly disconnected: [];
|
|
134
152
|
readonly data: [string];
|
|
135
153
|
readonly error: [Error];
|
|
136
154
|
}
|
|
137
155
|
//#endregion
|
|
138
|
-
export { ElectrumWebSocket };
|
|
156
|
+
export { ElectrumWebSocket, ElectrumWebSocketReconnectionOptions };
|
|
139
157
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -19,6 +19,8 @@ var ElectrumWebSocket = class extends EventEmitter {
|
|
|
19
19
|
wsData: (event) => this.emit("data", `${event.data}\n`),
|
|
20
20
|
wsError: (event) => this.emit("error", new Error(event.error))
|
|
21
21
|
};
|
|
22
|
+
boundHandleVisibilityChange = this.handleVisibilityChange.bind(this);
|
|
23
|
+
boundHandleConnectivityChange = this.handleConnectivityChange.bind(this);
|
|
22
24
|
/**
|
|
23
25
|
* Creates a socket configured with connection information for a given Electrum server.
|
|
24
26
|
*
|
|
@@ -26,13 +28,18 @@ var ElectrumWebSocket = class extends EventEmitter {
|
|
|
26
28
|
* @param port Network port for the host to connect to, defaults to the standard TLS port
|
|
27
29
|
* @param encrypted If false, uses an unencrypted connection instead of the default on TLS
|
|
28
30
|
* @param timeout If no connection is established after `timeout` ms, the connection is terminated
|
|
31
|
+
* @param reconnectionOptions Options to disable the automatic reconnection behavior when browser visibility or connectivity changes
|
|
29
32
|
*/
|
|
30
|
-
constructor(host, port = 50004, encrypted = true, timeout = defaultTimeout
|
|
33
|
+
constructor(host, port = 50004, encrypted = true, timeout = defaultTimeout, reconnectionOptions = {
|
|
34
|
+
disableBrowserVisibilityHandling: false,
|
|
35
|
+
disableBrowserConnectivityHandling: false
|
|
36
|
+
}) {
|
|
31
37
|
super();
|
|
32
38
|
this.host = host;
|
|
33
39
|
this.port = port;
|
|
34
40
|
this.encrypted = encrypted;
|
|
35
41
|
this.timeout = timeout;
|
|
42
|
+
this.reconnectionOptions = reconnectionOptions;
|
|
36
43
|
}
|
|
37
44
|
/**
|
|
38
45
|
* Returns a string for the host identifier for usage in debug messages.
|
|
@@ -53,6 +60,17 @@ var ElectrumWebSocket = class extends EventEmitter {
|
|
|
53
60
|
else this.webSocket = new WebSocket(`ws://${this.host}:${this.port}`);
|
|
54
61
|
this.webSocket.addEventListener("open", this.onConnect.bind(this));
|
|
55
62
|
this.webSocket.addEventListener("error", this.eventForwarders.wsError);
|
|
63
|
+
const { disableBrowserVisibilityHandling, disableBrowserConnectivityHandling } = this.reconnectionOptions;
|
|
64
|
+
if (typeof document !== "undefined" && !disableBrowserVisibilityHandling) {
|
|
65
|
+
document.removeEventListener("visibilitychange", this.boundHandleVisibilityChange);
|
|
66
|
+
document.addEventListener("visibilitychange", this.boundHandleVisibilityChange);
|
|
67
|
+
}
|
|
68
|
+
if (typeof window !== "undefined" && !disableBrowserConnectivityHandling) {
|
|
69
|
+
window.removeEventListener("online", this.boundHandleConnectivityChange);
|
|
70
|
+
window.removeEventListener("offline", this.boundHandleConnectivityChange);
|
|
71
|
+
window.addEventListener("online", this.boundHandleConnectivityChange);
|
|
72
|
+
window.addEventListener("offline", this.boundHandleConnectivityChange);
|
|
73
|
+
}
|
|
56
74
|
}
|
|
57
75
|
/**
|
|
58
76
|
* Sets up forwarding of events related to the connection.
|
|
@@ -77,7 +95,7 @@ var ElectrumWebSocket = class extends EventEmitter {
|
|
|
77
95
|
*
|
|
78
96
|
* @throws {Error} if no connection was found
|
|
79
97
|
*/
|
|
80
|
-
disconnect() {
|
|
98
|
+
disconnect(suspend = false) {
|
|
81
99
|
this.clearDisconnectTimerOnTimeout();
|
|
82
100
|
try {
|
|
83
101
|
this.webSocket.removeEventListener("close", this.eventForwarders.disconnect);
|
|
@@ -89,6 +107,13 @@ var ElectrumWebSocket = class extends EventEmitter {
|
|
|
89
107
|
this.webSocket = void 0;
|
|
90
108
|
}
|
|
91
109
|
this.onConnectHasRun = false;
|
|
110
|
+
if (!suspend) {
|
|
111
|
+
if (typeof document !== "undefined") document.removeEventListener("visibilitychange", this.boundHandleVisibilityChange);
|
|
112
|
+
if (typeof window !== "undefined") {
|
|
113
|
+
window.removeEventListener("online", this.boundHandleConnectivityChange);
|
|
114
|
+
window.removeEventListener("offline", this.boundHandleConnectivityChange);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
92
117
|
this.emit("disconnected");
|
|
93
118
|
}
|
|
94
119
|
/**
|
|
@@ -114,6 +139,22 @@ var ElectrumWebSocket = class extends EventEmitter {
|
|
|
114
139
|
this.emit("error", /* @__PURE__ */ new Error(`Connection to '${this.host}:${this.port}' timed out after ${this.timeout} milliseconds`));
|
|
115
140
|
this.disconnect();
|
|
116
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* Handles visibility changes when run in a browser environment.
|
|
144
|
+
*/
|
|
145
|
+
handleVisibilityChange() {
|
|
146
|
+
if (document?.visibilityState === "hidden") return this.disconnect(true);
|
|
147
|
+
if (this.webSocket) return;
|
|
148
|
+
return this.connect();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Handles connectivity changes when run in a browser environment.
|
|
152
|
+
*/
|
|
153
|
+
handleConnectivityChange() {
|
|
154
|
+
if (window.navigator.onLine === false) return this.disconnect(true);
|
|
155
|
+
if (this.webSocket) return;
|
|
156
|
+
return this.connect();
|
|
157
|
+
}
|
|
117
158
|
connected;
|
|
118
159
|
disconnected;
|
|
119
160
|
data;
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../source/constants.ts","../source/web.ts"],"sourcesContent":["// Export a default timeout value of 30 seconds.\nexport const defaultTimeout = 30 * 1000;\n","import { WebSocket } from '@monsterbitar/isomorphic-ws';\nimport { EventEmitter } from 'eventemitter3';\nimport debug from '@electrum-cash/debug-logs';\nimport { defaultTimeout } from './constants.ts';\n\nimport type { MessageEvent, ErrorEvent } from '@monsterbitar/isomorphic-ws';\nimport type { ElectrumSocket, ElectrumSocketEvents } from '@electrum-cash/network';\n\n/**\n * Web Socket used when communicating with Electrum servers.\n */\nexport class ElectrumWebSocket extends EventEmitter<ElectrumSocketEvents> implements ElectrumSocket\n{\n\t// Declare an empty WebSocket.\n\tprivate webSocket: WebSocket;\n\n\t// Used to disconnect after some time if initial connection is too slow.\n\tprivate disconnectTimer?: number;\n\n\t// Initialize boolean that indicates whether the onConnect function has run (initialize to false).\n\tprivate onConnectHasRun = false;\n\n\t// Initialize event forwarding functions.\n\tprivate eventForwarders =\n\t{\n\t\tdisconnect: () => this.emit('disconnected'),\n\t\twsData: (event: MessageEvent) => this.emit('data', `${event.data}\\n`),\n\t\twsError: (event: ErrorEvent) => this.emit('error', new Error(event.error)),\n\t};\n\n\t/**\n\t * Creates a socket configured with connection information for a given Electrum server.\n\t *\n\t * @param host Fully qualified domain name or IP address of the host\n\t * @param port Network port for the host to connect to, defaults to the standard TLS port\n\t * @param encrypted If false, uses an unencrypted connection instead of the default on TLS\n\t * @param timeout If no connection is established after `timeout` ms, the connection is terminated\n\t */\n\tpublic constructor\n\t(\n\t\tpublic host: string,\n\t\tpublic port: number = 50004,\n\t\tpublic encrypted: boolean = true,\n\t\tpublic timeout: number = defaultTimeout,\n\t)\n\t{\n\t\t// Initialize the event emitter.\n\t\tsuper();\n\t}\n\n\t/**\n\t * Returns a string for the host identifier for usage in debug messages.\n\t */\n\tget hostIdentifier(): string\n\t{\n\t\treturn `${this.host}:${this.port}`;\n\t}\n\n\t/**\n\t * Connect to host:port using the specified transport\n\t */\n\tconnect(): void\n\t{\n\t\t// Check that no existing socket exists before initiating a new connection.\n\t\tif(this.webSocket)\n\t\t{\n\t\t\tthrow(new Error('Cannot initiate a new socket connection when an existing connection exists'));\n\t\t}\n\n\t\t// Set a timer to force disconnect after `timeout` seconds\n\t\tthis.disconnectTimer = setTimeout(() => this.disconnectOnTimeout(), this.timeout) as unknown as number;\n\n\t\t// Remove the timer if a connection is successfully established\n\t\tthis.once('connected', this.clearDisconnectTimerOnTimeout);\n\n\t\t// Set a named connection type for logging purposes.\n\t\tconst connectionType = (this.encrypted ? 'an encrypted WebSocket' : 'a WebSocket');\n\n\t\t// Log that we are trying to establish a connection.\n\t\tdebug.network(`Initiating ${connectionType} connection to '${this.host}:${this.port}'.`);\n\n\t\tif(this.encrypted)\n\t\t{\n\t\t\t// Initialize this.webSocket (rejecting self-signed certificates).\n\t\t\t// We reject self-signed certificates to match functionality of browsers.\n\t\t\tthis.webSocket = new WebSocket(`wss://${this.host}:${this.port}`);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Initialize this.webSocket.\n\t\t\tthis.webSocket = new WebSocket(`ws://${this.host}:${this.port}`);\n\t\t}\n\n\t\t// Trigger successful connection events.\n\t\tthis.webSocket.addEventListener('open', this.onConnect.bind(this));\n\n\t\t// Forward the encountered errors.\n\t\tthis.webSocket.addEventListener('error', this.eventForwarders.wsError);\n\t}\n\n\t/**\n\t * Sets up forwarding of events related to the connection.\n\t */\n\tprivate onConnect(): void\n\t{\n\t\t// If the onConnect function has already run, do not execute it again.\n\t\tif(this.onConnectHasRun) return;\n\n\t\t// Set a named connection type for logging purposes.\n\t\tconst connectionType = (this.encrypted ? 'an encrypted WebSocket' : 'a WebSocket');\n\n\t\t// Log that the connection has been established.\n\t\tdebug.network(`Established ${connectionType} connection with '${this.host}:${this.port}'.`);\n\n\t\t// Forward the socket events\n\t\tthis.webSocket.addEventListener('close', this.eventForwarders.disconnect);\n\t\tthis.webSocket.addEventListener('message', this.eventForwarders.wsData);\n\n\t\t// Indicate that the onConnect function has run.\n\t\tthis.onConnectHasRun = true;\n\n\t\t// Emit the connect event.\n\t\tthis.emit('connected');\n\t}\n\n\t/**\n\t * Clears the disconnect timer if it is still active.\n\t */\n\tprivate clearDisconnectTimerOnTimeout(): void\n\t{\n\t\t// Clear the retry timer if it is still active.\n\t\tif(this.disconnectTimer)\n\t\t{\n\t\t\tclearTimeout(this.disconnectTimer);\n\t\t}\n\t}\n\n\t/**\n\t * Forcibly terminate the connection.\n\t *\n\t * @throws {Error} if no connection was found\n\t */\n\tpublic disconnect(): void\n\t{\n\t\t// Clear the disconnect timer so that the socket does not try to disconnect again later.\n\t\tthis.clearDisconnectTimerOnTimeout();\n\n\t\ttry\n\t\t{\n\t\t\t// Remove all event forwarders.\n\t\t\tthis.webSocket.removeEventListener('close', this.eventForwarders.disconnect);\n\t\t\tthis.webSocket.removeEventListener('message', this.eventForwarders.wsData);\n\t\t\tthis.webSocket.removeEventListener('error', this.eventForwarders.wsError);\n\n\t\t\t// Add a one-time event listener for potential error events after closing the socket.\n\t\t\t// NOTE: This is needed since the close() call might not result in an error itself, but the\n\t\t\t// underlying network packets that are sent in order to do a graceful termination can fail.\n\t\t\tthis.webSocket.addEventListener('error', (_ignored) => {}, { once: true });\n\n\t\t\t// Gracefully terminate the connection\n\t\t\tthis.webSocket.close();\n\t\t}\n\t\tcatch (_ignored)\n\t\t{\n\t\t\t// close() will throw an error if the connection has not been established yet.\n\t\t\t// We ignore this error, since no similar error gets thrown in the TLS Socket.\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\t// Remove the stored socket regardless of any thrown errors.\n\t\t\tthis.webSocket = undefined;\n\t\t}\n\n\t\t// Indicate that the onConnect function has not run and it has to be run again.\n\t\tthis.onConnectHasRun = false;\n\n\t\t// Emit a disconnect event\n\t\tthis.emit('disconnected');\n\t}\n\n\t/**\n\t * Write data to the socket\n\t *\n\t * @param data Data to be written to the socket\n\t * @param callback Callback function to be called when the write has completed\n\t *\n\t * @throws {Error} if no connection was found\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\tpublic write(data: Uint8Array | string, callback?: (err?: Error) => void): boolean\n\t{\n\t\t// Throw an error if no active connection is found\n\t\tif(!this.webSocket)\n\t\t{\n\t\t\tthrow(new Error('Cannot write to socket when there is no active connection'));\n\t\t}\n\n\t\t// Write data to the WebSocket\n\t\tthis.webSocket.send(data, callback);\n\n\t\t// WebSockets always fit everything in a single request, so we return true\n\t\treturn true;\n\t}\n\n\t/**\n\t * Force a disconnection if no connection is established after `timeout` milliseconds.\n\t */\n\tprivate disconnectOnTimeout(): void\n\t{\n\t\t// Remove the connect listener.\n\t\tthis.removeListener('connected', this.clearDisconnectTimerOnTimeout);\n\n\t\t// Emit an error event so that connect is rejected upstream.\n\t\tthis.emit('error', new Error(`Connection to '${this.host}:${this.port}' timed out after ${this.timeout} milliseconds`));\n\n\t\t// Forcibly disconnect to clean up the connection on timeout\n\t\tthis.disconnect();\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 connected: [];\n\tpublic readonly disconnected: [];\n\tpublic readonly data: [ string ];\n\tpublic readonly error: [ Error ];\n}\n"],"mappings":";;;;;AACA,MAAa,iBAAiB,KAAK;;;;;;;ACUnC,IAAa,oBAAb,cAAuC,aACvC;CAEC,AAAQ;CAGR,AAAQ;CAGR,AAAQ,kBAAkB;CAG1B,AAAQ,kBACR;EACC,kBAAiC,KAAK,KAAK,eAAe;EAC1D,SAAS,UAAwB,KAAK,KAAK,QAAQ,GAAG,MAAM,KAAK,IAAI;EACrE,UAAU,UAAuB,KAAK,KAAK,SAAS,IAAI,MAAM,MAAM,MAAM,CAAC;EAC3E;;;;;;;;;CAUD,AAAO,YAEN,AAAO,MACP,AAAO,OAAe,OACtB,AAAO,YAAqB,MAC5B,AAAO,UAAkB,gBAE1B;AAEC,SAAO;EAPA;EACA;EACA;EACA;;;;;CAUR,IAAI,iBACJ;AACC,SAAO,GAAG,KAAK,KAAK,GAAG,KAAK;;;;;CAM7B,UACA;AAEC,MAAG,KAAK,UAEP,uBAAM,IAAI,MAAM,6EAA6E;AAI9F,OAAK,kBAAkB,iBAAiB,KAAK,qBAAqB,EAAE,KAAK,QAAQ;AAGjF,OAAK,KAAK,aAAa,KAAK,8BAA8B;EAG1D,MAAM,iBAAkB,KAAK,YAAY,2BAA2B;AAGpE,QAAM,QAAQ,cAAc,eAAe,kBAAkB,KAAK,KAAK,GAAG,KAAK,KAAK,IAAI;AAExF,MAAG,KAAK,UAIP,MAAK,YAAY,IAAI,UAAU,SAAS,KAAK,KAAK,GAAG,KAAK,OAAO;MAKjE,MAAK,YAAY,IAAI,UAAU,QAAQ,KAAK,KAAK,GAAG,KAAK,OAAO;AAIjE,OAAK,UAAU,iBAAiB,QAAQ,KAAK,UAAU,KAAK,KAAK,CAAC;AAGlE,OAAK,UAAU,iBAAiB,SAAS,KAAK,gBAAgB,QAAQ;;;;;CAMvE,AAAQ,YACR;AAEC,MAAG,KAAK,gBAAiB;EAGzB,MAAM,iBAAkB,KAAK,YAAY,2BAA2B;AAGpE,QAAM,QAAQ,eAAe,eAAe,oBAAoB,KAAK,KAAK,GAAG,KAAK,KAAK,IAAI;AAG3F,OAAK,UAAU,iBAAiB,SAAS,KAAK,gBAAgB,WAAW;AACzE,OAAK,UAAU,iBAAiB,WAAW,KAAK,gBAAgB,OAAO;AAGvE,OAAK,kBAAkB;AAGvB,OAAK,KAAK,YAAY;;;;;CAMvB,AAAQ,gCACR;AAEC,MAAG,KAAK,gBAEP,cAAa,KAAK,gBAAgB;;;;;;;CASpC,AAAO,aACP;AAEC,OAAK,+BAA+B;AAEpC,MACA;AAEC,QAAK,UAAU,oBAAoB,SAAS,KAAK,gBAAgB,WAAW;AAC5E,QAAK,UAAU,oBAAoB,WAAW,KAAK,gBAAgB,OAAO;AAC1E,QAAK,UAAU,oBAAoB,SAAS,KAAK,gBAAgB,QAAQ;AAKzE,QAAK,UAAU,iBAAiB,UAAU,aAAa,IAAI,EAAE,MAAM,MAAM,CAAC;AAG1E,QAAK,UAAU,OAAO;WAEhB,UACP,WAKA;AAEC,QAAK,YAAY;;AAIlB,OAAK,kBAAkB;AAGvB,OAAK,KAAK,eAAe;;;;;;;;;;;;CAa1B,AAAO,MAAM,MAA2B,UACxC;AAEC,MAAG,CAAC,KAAK,UAER,uBAAM,IAAI,MAAM,4DAA4D;AAI7E,OAAK,UAAU,KAAK,MAAM,SAAS;AAGnC,SAAO;;;;;CAMR,AAAQ,sBACR;AAEC,OAAK,eAAe,aAAa,KAAK,8BAA8B;AAGpE,OAAK,KAAK,yBAAS,IAAI,MAAM,kBAAkB,KAAK,KAAK,GAAG,KAAK,KAAK,oBAAoB,KAAK,QAAQ,eAAe,CAAC;AAGvH,OAAK,YAAY;;CAIlB,AAAgB;CAChB,AAAgB;CAChB,AAAgB;CAChB,AAAgB"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../source/constants.ts","../source/web.ts"],"sourcesContent":["// Export a default timeout value of 30 seconds.\nexport const defaultTimeout = 30 * 1000;\n","import { WebSocket } from '@monsterbitar/isomorphic-ws';\nimport { EventEmitter } from 'eventemitter3';\nimport debug from '@electrum-cash/debug-logs';\nimport { defaultTimeout } from './constants.ts';\n\nimport type { MessageEvent, ErrorEvent } from '@monsterbitar/isomorphic-ws';\nimport type { ElectrumSocket, ElectrumSocketEvents } from '@electrum-cash/network';\n\nexport type ElectrumWebSocketReconnectionOptions = {\n\t/** If true, will not handle visibility changes when run in a browser environment. */\n\tdisableBrowserVisibilityHandling: boolean,\n\n\t/** If true, will not handle connectivity changes when run in a browser environment. */\n\tdisableBrowserConnectivityHandling: boolean,\n};\n\n/**\n * Web Socket used when communicating with Electrum servers.\n */\nexport class ElectrumWebSocket extends EventEmitter<ElectrumSocketEvents> implements ElectrumSocket\n{\n\t// Declare an empty WebSocket.\n\tprivate webSocket: WebSocket;\n\n\t// Used to disconnect after some time if initial connection is too slow.\n\tprivate disconnectTimer?: number;\n\n\t// Initialize boolean that indicates whether the onConnect function has run (initialize to false).\n\tprivate onConnectHasRun = false;\n\n\t// Initialize event forwarding functions.\n\tprivate eventForwarders =\n\t{\n\t\tdisconnect: () => this.emit('disconnected'),\n\t\twsData: (event: MessageEvent) => this.emit('data', `${event.data}\\n`),\n\t\twsError: (event: ErrorEvent) => this.emit('error', new Error(event.error)),\n\t};\n\n\t// Store bound visibility change handler so we can properly remove it later.\n\t// NOTE: .bind() creates a new function each time, so we need to store the reference.\n\tprivate boundHandleVisibilityChange = this.handleVisibilityChange.bind(this);\n\n\tprivate boundHandleConnectivityChange = this.handleConnectivityChange.bind(this);\n\n\t/**\n\t * Creates a socket configured with connection information for a given Electrum server.\n\t *\n\t * @param host Fully qualified domain name or IP address of the host\n\t * @param port Network port for the host to connect to, defaults to the standard TLS port\n\t * @param encrypted If false, uses an unencrypted connection instead of the default on TLS\n\t * @param timeout If no connection is established after `timeout` ms, the connection is terminated\n\t * @param reconnectionOptions Options to disable the automatic reconnection behavior when browser visibility or connectivity changes\n\t */\n\tpublic constructor\n\t(\n\t\tpublic host: string,\n\t\tpublic port: number = 50004,\n\t\tpublic encrypted: boolean = true,\n\t\tpublic timeout: number = defaultTimeout,\n\t\tpublic reconnectionOptions: ElectrumWebSocketReconnectionOptions = {\n\t\t\tdisableBrowserVisibilityHandling: false,\n\t\t\tdisableBrowserConnectivityHandling: false,\n\t\t},\n\t)\n\t{\n\t\t// Initialize the event emitter.\n\t\tsuper();\n\t}\n\n\t/**\n\t * Returns a string for the host identifier for usage in debug messages.\n\t */\n\tget hostIdentifier(): string\n\t{\n\t\treturn `${this.host}:${this.port}`;\n\t}\n\n\t/**\n\t * Connect to host:port using the specified transport\n\t */\n\tconnect(): void\n\t{\n\t\t// Check that no existing socket exists before initiating a new connection.\n\t\tif(this.webSocket)\n\t\t{\n\t\t\tthrow(new Error('Cannot initiate a new socket connection when an existing connection exists'));\n\t\t}\n\n\t\t// Set a timer to force disconnect after `timeout` seconds\n\t\tthis.disconnectTimer = setTimeout(() => this.disconnectOnTimeout(), this.timeout) as unknown as number;\n\n\t\t// Remove the timer if a connection is successfully established\n\t\tthis.once('connected', this.clearDisconnectTimerOnTimeout);\n\n\t\t// Set a named connection type for logging purposes.\n\t\tconst connectionType = (this.encrypted ? 'an encrypted WebSocket' : 'a WebSocket');\n\n\t\t// Log that we are trying to establish a connection.\n\t\tdebug.network(`Initiating ${connectionType} connection to '${this.host}:${this.port}'.`);\n\n\t\tif(this.encrypted)\n\t\t{\n\t\t\t// Initialize this.webSocket (rejecting self-signed certificates).\n\t\t\t// We reject self-signed certificates to match functionality of browsers.\n\t\t\tthis.webSocket = new WebSocket(`wss://${this.host}:${this.port}`);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t// Initialize this.webSocket.\n\t\t\tthis.webSocket = new WebSocket(`ws://${this.host}:${this.port}`);\n\t\t}\n\n\t\t// Trigger successful connection events.\n\t\tthis.webSocket.addEventListener('open', this.onConnect.bind(this));\n\n\t\t// Forward the encountered errors.\n\t\tthis.webSocket.addEventListener('error', this.eventForwarders.wsError);\n\n\t\tconst { disableBrowserVisibilityHandling, disableBrowserConnectivityHandling } = this.reconnectionOptions;\n\n\t\t// Handle visibility changes when run in a browser environment.\n\t\t// Only add listener if the document is currently visible to avoid immediate disconnect loops.\n\t\tif(typeof document !== 'undefined' && !disableBrowserVisibilityHandling)\n\t\t{\n\t\t\t// Remove the listener if it exists so we don't create multiple listeners.\n\t\t\tdocument.removeEventListener('visibilitychange', this.boundHandleVisibilityChange);\n\t\t\t\n\t\t\t// Add the listener again.\n\t\t\tdocument.addEventListener('visibilitychange', this.boundHandleVisibilityChange);\n\t\t}\n\n\t\t// Handle connectivity changes when run in a browser environment.\n\t\tif(typeof window !== 'undefined' && !disableBrowserConnectivityHandling)\n\t\t{\n\t\t\t// Remove the listeners if it exists so we don't create multiple listeners.\n\t\t\twindow.removeEventListener('online', this.boundHandleConnectivityChange);\n\t\t\twindow.removeEventListener('offline', this.boundHandleConnectivityChange);\n\t\t\t\n\t\t\t// Add the listeners again.\n\t\t\twindow.addEventListener('online', this.boundHandleConnectivityChange);\n\t\t\twindow.addEventListener('offline', this.boundHandleConnectivityChange);\n\t\t}\n\t}\n\n\t/**\n\t * Sets up forwarding of events related to the connection.\n\t */\n\tprivate onConnect(): void\n\t{\n\t\t// If the onConnect function has already run, do not execute it again.\n\t\tif(this.onConnectHasRun) return;\n\n\t\t// Set a named connection type for logging purposes.\n\t\tconst connectionType = (this.encrypted ? 'an encrypted WebSocket' : 'a WebSocket');\n\n\t\t// Log that the connection has been established.\n\t\tdebug.network(`Established ${connectionType} connection with '${this.host}:${this.port}'.`);\n\n\t\t// Forward the socket events\n\t\tthis.webSocket.addEventListener('close', this.eventForwarders.disconnect);\n\t\tthis.webSocket.addEventListener('message', this.eventForwarders.wsData);\n\n\t\t// Indicate that the onConnect function has run.\n\t\tthis.onConnectHasRun = true;\n\n\t\t// Emit the connect event.\n\t\tthis.emit('connected');\n\t}\n\n\t/**\n\t * Clears the disconnect timer if it is still active.\n\t */\n\tprivate clearDisconnectTimerOnTimeout(): void\n\t{\n\t\t// Clear the retry timer if it is still active.\n\t\tif(this.disconnectTimer)\n\t\t{\n\t\t\tclearTimeout(this.disconnectTimer);\n\t\t}\n\t}\n\n\t/**\n\t * Forcibly terminate the connection.\n\t *\n\t * @throws {Error} if no connection was found\n\t */\n\tpublic disconnect(suspend: boolean = false): void\n\t{\n\t\t// Clear the disconnect timer so that the socket does not try to disconnect again later.\n\t\tthis.clearDisconnectTimerOnTimeout();\n\n\t\ttry\n\t\t{\n\t\t\t// Remove all event forwarders.\n\t\t\tthis.webSocket.removeEventListener('close', this.eventForwarders.disconnect);\n\t\t\tthis.webSocket.removeEventListener('message', this.eventForwarders.wsData);\n\t\t\tthis.webSocket.removeEventListener('error', this.eventForwarders.wsError);\n\n\t\t\t// Add a one-time event listener for potential error events after closing the socket.\n\t\t\t// NOTE: This is needed since the close() call might not result in an error itself, but the\n\t\t\t// underlying network packets that are sent in order to do a graceful termination can fail.\n\t\t\tthis.webSocket.addEventListener('error', (_ignored) => {}, { once: true });\n\n\t\t\t// Gracefully terminate the connection\n\t\t\tthis.webSocket.close();\n\t\t}\n\t\tcatch (_ignored)\n\t\t{\n\t\t\t// close() will throw an error if the connection has not been established yet.\n\t\t\t// We ignore this error, since no similar error gets thrown in the TLS Socket.\n\t\t}\n\t\tfinally\n\t\t{\n\t\t\t// Remove the stored socket regardless of any thrown errors.\n\t\t\tthis.webSocket = undefined;\n\t\t}\n\n\t\tthis.onConnectHasRun = false;\n\n\t\t// If we are doing a complete disconnect, and not just suspending the connection, we need to remove the listeners.\n\t\tif(!suspend)\n\t\t{\n\t\t\t// Remove the visibility change listener\n\t\t\tif(typeof document !== 'undefined')\n\t\t\t{\n\t\t\t\tdocument.removeEventListener('visibilitychange', this.boundHandleVisibilityChange);\n\t\t\t}\n\n\t\t\t// Remove the connectivity change listeners\n\t\t\tif(typeof window !== 'undefined')\n\t\t\t{\n\t\t\t\twindow.removeEventListener('online', this.boundHandleConnectivityChange);\n\t\t\t\twindow.removeEventListener('offline', this.boundHandleConnectivityChange);\n\t\t\t}\n\t\t}\n\n\t\tthis.emit('disconnected');\n\t}\n\n\t/**\n\t * Write data to the socket\n\t *\n\t * @param data Data to be written to the socket\n\t * @param callback Callback function to be called when the write has completed\n\t *\n\t * @throws {Error} if no connection was found\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\tpublic write(data: Uint8Array | string, callback?: (err?: Error) => void): boolean\n\t{\n\t\t// Throw an error if no active connection is found\n\t\tif(!this.webSocket)\n\t\t{\n\t\t\tthrow(new Error('Cannot write to socket when there is no active connection'));\n\t\t}\n\n\t\t// Write data to the WebSocket\n\t\tthis.webSocket.send(data, callback);\n\n\t\t// WebSockets always fit everything in a single request, so we return true\n\t\treturn true;\n\t}\n\n\t/**\n\t * Force a disconnection if no connection is established after `timeout` milliseconds.\n\t */\n\tprivate disconnectOnTimeout(): void\n\t{\n\t\t// Remove the connect listener.\n\t\tthis.removeListener('connected', this.clearDisconnectTimerOnTimeout);\n\n\t\t// Emit an error event so that connect is rejected upstream.\n\t\tthis.emit('error', new Error(`Connection to '${this.host}:${this.port}' timed out after ${this.timeout} milliseconds`));\n\n\t\t// Forcibly disconnect to clean up the connection on timeout\n\t\tthis.disconnect();\n\t}\n\n\t/**\n\t * Handles visibility changes when run in a browser environment.\n\t */\n\tprivate handleVisibilityChange(): void\n\t{\n\t\t// If the document is hiding, we should disconnect the connection.\n\t\tif(document?.visibilityState === 'hidden')\n\t\t{\n\t\t\t// Suspend is used to indicate that we should not remove the handleVisibilityChange listener.\n\t\t\tconst suspend = true;\n\n\t\t\t// Disconnect the connection.\n\t\t\treturn this.disconnect(suspend);\n\t\t}\n\n\t\t// If the webSocket has already been connected, we don't need to connect again.\n\t\t// This could occur from a higher layer manually reconnecting\n\t\tif(this.webSocket)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Connect the connection.\n\t\treturn this.connect();\n\t}\n\n\t/**\n\t * Handles connectivity changes when run in a browser environment.\n\t */\n\tprivate handleConnectivityChange(): void\n\t{\n\t\t// If we are offline, we should disconnect the connection.\n\t\tif(window.navigator.onLine === false)\n\t\t{\n\t\t\t// Suspend is used to indicate that we should not remove the handleConnectivityChange listener.\n\t\t\tconst suspend = true;\n\n\t\t\t// disconnect the connection.\n\t\t\treturn this.disconnect(suspend);\n\t\t}\n\n\t\t// If the webSocket has already been connected, we don't need to connect again.\n\t\t// This could occur from a higher layer manually reconnecting\n\t\tif(this.webSocket)\n\t\t{\n\t\t\treturn;\n\t\t}\n\n\t\t// Connect the connection.\n\t\treturn this.connect();\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 connected: [];\n\tpublic readonly disconnected: [];\n\tpublic readonly data: [ string ];\n\tpublic readonly error: [ Error ];\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electrum-cash/web-socket",
|
|
3
|
-
"version": "1.1.1-development.
|
|
3
|
+
"version": "1.1.1-development.12942990149",
|
|
4
4
|
"description": "@electrum-cash/web-socket implements the ElectrumSocket interface using web sockets.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"electrum",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"build": "tsdown --clean --sourcemap source/index.ts",
|
|
33
33
|
"analyze": "tsdown --clean --no-fixed-extension --sourcemap source/index.ts && esbuild-analyzer dist/",
|
|
34
34
|
"docs": "typedoc --hideGenerator --categorizeByGroup",
|
|
35
|
-
"
|
|
35
|
+
"style": "eslint",
|
|
36
36
|
"syntax": "tsc --noEmit",
|
|
37
37
|
"spellcheck": "cspell 'source/**' 'test/**' 'examples/**'",
|
|
38
38
|
"test": "vitest --dir test/ --test-timeout=15000 --passWithNoTests --run --coverage"
|