Remove unused class
I managed to lose this deletion in a merge.
This commit is contained in:
parent
a96606e589
commit
4cfd7c50ad
@ -1,204 +0,0 @@
|
||||
import { field, logger, Logger } from "@coder/logger"
|
||||
import { Emitter } from "../common/emitter"
|
||||
import { generateUuid } from "../common/util"
|
||||
|
||||
const decoder = new TextDecoder("utf8")
|
||||
export const decode = (buffer: string | ArrayBuffer): string => {
|
||||
return typeof buffer !== "string" ? decoder.decode(buffer) : buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* A web socket that reconnects itself when it closes. Sending messages while
|
||||
* disconnected will throw an error.
|
||||
*/
|
||||
export class ReconnectingSocket {
|
||||
protected readonly _onMessage = new Emitter<string | ArrayBuffer>()
|
||||
public readonly onMessage = this._onMessage.event
|
||||
protected readonly _onDisconnect = new Emitter<number | undefined>()
|
||||
public readonly onDisconnect = this._onDisconnect.event
|
||||
protected readonly _onClose = new Emitter<number | undefined>()
|
||||
public readonly onClose = this._onClose.event
|
||||
protected readonly _onConnect = new Emitter<void>()
|
||||
public readonly onConnect = this._onConnect.event
|
||||
|
||||
// This helps distinguish messages between sockets.
|
||||
private readonly logger: Logger
|
||||
|
||||
private socket?: WebSocket
|
||||
private connecting?: Promise<void>
|
||||
private closed = false
|
||||
private readonly openTimeout = 10000
|
||||
|
||||
// Every time the socket fails to connect, the retry will be increasingly
|
||||
// delayed up to a maximum.
|
||||
private readonly retryBaseDelay = 1000
|
||||
private readonly retryMaxDelay = 10000
|
||||
private retryDelay?: number
|
||||
private readonly retryDelayFactor = 1.5
|
||||
|
||||
// The socket must be connected for this amount of time before resetting the
|
||||
// retry delay. This prevents rapid retries when the socket does connect but
|
||||
// is closed shortly after.
|
||||
private resetRetryTimeout?: NodeJS.Timeout
|
||||
private readonly resetRetryDelay = 10000
|
||||
|
||||
private _binaryType: typeof WebSocket.prototype.binaryType = "arraybuffer"
|
||||
|
||||
public constructor(private path: string, public readonly id: string = generateUuid(4)) {
|
||||
// On Firefox the socket seems to somehow persist a page reload so the close
|
||||
// event runs and we see "attempting to reconnect".
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("beforeunload", () => this.close())
|
||||
}
|
||||
this.logger = logger.named(this.id)
|
||||
}
|
||||
|
||||
public set binaryType(b: typeof WebSocket.prototype.binaryType) {
|
||||
this._binaryType = b
|
||||
if (this.socket) {
|
||||
this.socket.binaryType = b
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently close the connection. Will not attempt to reconnect. Will
|
||||
* remove event listeners.
|
||||
*/
|
||||
public close(code?: number): void {
|
||||
if (this.closed) {
|
||||
return
|
||||
}
|
||||
|
||||
if (code) {
|
||||
this.logger.info(`closing with code ${code}`)
|
||||
}
|
||||
|
||||
if (this.resetRetryTimeout) {
|
||||
clearTimeout(this.resetRetryTimeout)
|
||||
}
|
||||
|
||||
this.closed = true
|
||||
|
||||
if (this.socket) {
|
||||
this.socket.close()
|
||||
} else {
|
||||
this._onClose.emit(code)
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._onMessage.dispose()
|
||||
this._onDisconnect.dispose()
|
||||
this._onClose.dispose()
|
||||
this._onConnect.dispose()
|
||||
this.logger.debug("disposed handlers")
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message on the socket. Logs an error if currently disconnected.
|
||||
*/
|
||||
public send(message: string | ArrayBuffer): void {
|
||||
this.logger.trace(() => ["sending message", field("message", decode(message))])
|
||||
if (!this.socket) {
|
||||
return logger.error("tried to send message on closed socket")
|
||||
}
|
||||
this.socket.send(message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the socket. Can also be called to wait until the connection is
|
||||
* established in the case of disconnections. Multiple calls will be handled
|
||||
* correctly.
|
||||
*/
|
||||
public async connect(): Promise<void> {
|
||||
if (!this.connecting) {
|
||||
this.connecting = new Promise((resolve, reject) => {
|
||||
const tryConnect = (): void => {
|
||||
if (this.closed) {
|
||||
return reject(new Error("disconnected")) // Don't keep trying if we've closed permanently.
|
||||
}
|
||||
if (typeof this.retryDelay === "undefined") {
|
||||
this.retryDelay = 0
|
||||
} else {
|
||||
this.retryDelay = this.retryDelay * this.retryDelayFactor || this.retryBaseDelay
|
||||
if (this.retryDelay > this.retryMaxDelay) {
|
||||
this.retryDelay = this.retryMaxDelay
|
||||
}
|
||||
}
|
||||
this._connect()
|
||||
.then((socket) => {
|
||||
this.logger.info("connected")
|
||||
this.socket = socket
|
||||
this.socket.binaryType = this._binaryType
|
||||
if (this.resetRetryTimeout) {
|
||||
clearTimeout(this.resetRetryTimeout)
|
||||
}
|
||||
this.resetRetryTimeout = setTimeout(() => (this.retryDelay = undefined), this.resetRetryDelay)
|
||||
this.connecting = undefined
|
||||
this._onConnect.emit()
|
||||
resolve()
|
||||
})
|
||||
.catch((error) => {
|
||||
this.logger.error(`failed to connect: ${error.message}`)
|
||||
tryConnect()
|
||||
})
|
||||
}
|
||||
tryConnect()
|
||||
})
|
||||
}
|
||||
return this.connecting
|
||||
}
|
||||
|
||||
private async _connect(): Promise<WebSocket> {
|
||||
const socket = await new Promise<WebSocket>((resolve, _reject) => {
|
||||
if (this.retryDelay) {
|
||||
this.logger.info(`retrying in ${this.retryDelay}ms...`)
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.logger.info("connecting...", field("path", this.path))
|
||||
const socket = new WebSocket(this.path)
|
||||
|
||||
const reject = (): void => {
|
||||
_reject(new Error("socket closed"))
|
||||
}
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
socket.removeEventListener("open", open)
|
||||
socket.removeEventListener("close", reject)
|
||||
_reject(new Error("timeout"))
|
||||
}, this.openTimeout)
|
||||
|
||||
const open = (): void => {
|
||||
clearTimeout(timeout)
|
||||
socket.removeEventListener("close", reject)
|
||||
resolve(socket)
|
||||
}
|
||||
|
||||
socket.addEventListener("open", open)
|
||||
socket.addEventListener("close", reject)
|
||||
}, this.retryDelay)
|
||||
})
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
this.logger.trace(() => ["got message", field("message", decode(event.data))])
|
||||
this._onMessage.emit(event.data)
|
||||
})
|
||||
socket.addEventListener("close", (event) => {
|
||||
this.socket = undefined
|
||||
if (!this.closed) {
|
||||
this._onDisconnect.emit(event.code)
|
||||
// It might be closed in the event handler.
|
||||
if (!this.closed) {
|
||||
this.logger.info("connection closed; attempting to reconnect")
|
||||
this.connect()
|
||||
}
|
||||
} else {
|
||||
this._onClose.emit(event.code)
|
||||
this.logger.info("connection closed permanently")
|
||||
}
|
||||
})
|
||||
|
||||
return socket
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user