diff --git a/lib.esm/providers/provider-websocket.js b/lib.esm/providers/provider-websocket.js index 2f4fb2e557..c1a0b8b2cb 100644 --- a/lib.esm/providers/provider-websocket.js +++ b/lib.esm/providers/provider-websocket.js @@ -13,6 +13,8 @@ import { SocketProvider } from "./provider-socket.js"; export class WebSocketProvider extends SocketProvider { #connect; #websocket; + #pingTimeout; + #keepAliveInterval; get websocket() { if (this.#websocket == null) { throw new Error("websocket closed"); @@ -37,6 +39,7 @@ export class WebSocketProvider extends SocketProvider { try { await this._start(); this.resume(); + this.#startKeepAlive(); } catch (error) { console.log("failed to start WebsocketProvider", error); @@ -46,26 +49,59 @@ export class WebSocketProvider extends SocketProvider { this.websocket.onmessage = (message) => { this._processMessage(message.data); }; - /* - this.websocket.onclose = (event) => { - // @TODO: What event.code should we reconnect on? - const reconnect = false; - if (reconnect) { - this.pause(true); - if (this.#connect) { - this.#websocket = this.#connect(); - this.#websocket.onopen = ... - // @TODO: this requires the super class to rebroadcast; move it there - } - this._reconnect(); + this.websocket.onclose = (event) => { + this.#stopKeepAlive(); + this.pause(true); + if (this.#connect) { + this.#websocket = this.#connect(); + this.#websocket.onopen = this.websocket.onopen; + this.#websocket.onmessage = this.websocket.onmessage; + this.#websocket.onclose = this.websocket.onclose; + this.#websocket.onping = this.websocket.onping; + this.#websocket.onpong = this.websocket.onpong; + } + }; + this.websocket.onping = () => { + if (this.#pingTimeout) { + clearTimeout(this.#pingTimeout); + this.#pingTimeout = null; + } + this.websocket.pong(); + }; + this.websocket.onpong = () => { + if (this.#pingTimeout) { + clearTimeout(this.#pingTimeout); + this.#pingTimeout = null; + } + }; + } + #startKeepAlive() { + this.#keepAliveInterval = setInterval(() => { + if (this.#websocket && this.#websocket.readyState === 1) { + this.#websocket.ping(); + this.#pingTimeout = setTimeout(() => { + if (this.#websocket) { + this.#websocket.close(); } - }; - */ + }, 15000); + } + }, 7500); + } + #stopKeepAlive() { + if (this.#keepAliveInterval) { + clearInterval(this.#keepAliveInterval); + this.#keepAliveInterval = null; + } + if (this.#pingTimeout) { + clearTimeout(this.#pingTimeout); + this.#pingTimeout = null; + } } async _write(message) { this.websocket.send(message); } async destroy() { + this.#stopKeepAlive(); if (this.#websocket != null) { this.#websocket.close(); this.#websocket = null; @@ -73,4 +109,3 @@ export class WebSocketProvider extends SocketProvider { super.destroy(); } } -//# sourceMappingURL=provider-websocket.js.map \ No newline at end of file diff --git a/src.ts/providers/provider-websocket.ts b/src.ts/providers/provider-websocket.ts index ee1cf847fa..a4093b0c59 100644 --- a/src.ts/providers/provider-websocket.ts +++ b/src.ts/providers/provider-websocket.ts @@ -1,5 +1,3 @@ - - import { WebSocket as _WebSocket } from "./ws.js"; /*-browser*/ import { SocketProvider } from "./provider-socket.js"; @@ -14,11 +12,15 @@ export interface WebSocketLike { onopen: null | ((...args: Array) => any); onmessage: null | ((...args: Array) => any); onerror: null | ((...args: Array) => any); + onclose: null | ((...args: Array) => any); + onping: null | ((...args: Array) => any); + onpong: null | ((...args: Array) => any); readyState: number; send(payload: any): void; close(code?: number, reason?: string): void; + ping(): void; } /** @@ -46,6 +48,9 @@ export class WebSocketProvider extends SocketProvider { return this.#websocket; } + #pingTimeout: null | ReturnType; + #keepAliveInterval: null | ReturnType; + constructor(url: string | WebSocketLike | WebSocketCreator, network?: Networkish, options?: JsonRpcApiProviderOptions) { super(network, options); if (typeof(url) === "string") { @@ -63,6 +68,7 @@ export class WebSocketProvider extends SocketProvider { try { await this._start() this.resume(); + this.#startKeepAlive(); } catch (error) { console.log("failed to start WebsocketProvider", error); // @TODO: now what? Attempt reconnect? @@ -72,21 +78,58 @@ export class WebSocketProvider extends SocketProvider { this.websocket.onmessage = (message: { data: string }) => { this._processMessage(message.data); }; -/* + this.websocket.onclose = (event) => { - // @TODO: What event.code should we reconnect on? - const reconnect = false; - if (reconnect) { - this.pause(true); - if (this.#connect) { - this.#websocket = this.#connect(); - this.#websocket.onopen = ... - // @TODO: this requires the super class to rebroadcast; move it there - } - this._reconnect(); + this.#stopKeepAlive(); + this.pause(true); + if (this.#connect) { + this.#websocket = this.#connect(); + this.#websocket.onopen = this.websocket.onopen; + this.#websocket.onmessage = this.websocket.onmessage; + this.#websocket.onclose = this.websocket.onclose; + this.#websocket.onping = this.websocket.onping; + this.#websocket.onpong = this.websocket.onpong; } }; -*/ + + this.websocket.onping = () => { + if (this.#pingTimeout) { + clearTimeout(this.#pingTimeout); + this.#pingTimeout = null; + } + this.websocket.pong(); + }; + + this.websocket.onpong = () => { + if (this.#pingTimeout) { + clearTimeout(this.#pingTimeout); + this.#pingTimeout = null; + } + }; + } + + #startKeepAlive() { + this.#keepAliveInterval = setInterval(() => { + if (this.#websocket && this.#websocket.readyState === 1) { + this.#websocket.ping(); + this.#pingTimeout = setTimeout(() => { + if (this.#websocket) { + this.#websocket.close(); + } + }, 15000); + } + }, 7500); + } + + #stopKeepAlive() { + if (this.#keepAliveInterval) { + clearInterval(this.#keepAliveInterval); + this.#keepAliveInterval = null; + } + if (this.#pingTimeout) { + clearTimeout(this.#pingTimeout); + this.#pingTimeout = null; + } } async _write(message: string): Promise { @@ -94,6 +137,7 @@ export class WebSocketProvider extends SocketProvider { } async destroy(): Promise { + this.#stopKeepAlive(); if (this.#websocket != null) { this.#websocket.close(); this.#websocket = null;