rittenhop-dev/versions/5.94.2/node_modules/lib0/websocket.js
2024-09-23 19:40:12 -04:00

155 lines
4.2 KiB
JavaScript

/* eslint-env browser */
/**
* Tiny websocket connection handler.
*
* Implements exponential backoff reconnects, ping/pong, and a nice event system using [lib0/observable].
*
* @module websocket
*/
import { Observable } from './observable.js'
import * as time from './time.js'
import * as math from './math.js'
const reconnectTimeoutBase = 1200
const maxReconnectTimeout = 2500
// @todo - this should depend on awareness.outdatedTime
const messageReconnectTimeout = 30000
/**
* @param {WebsocketClient} wsclient
*/
const setupWS = (wsclient) => {
if (wsclient.shouldConnect && wsclient.ws === null) {
const websocket = new WebSocket(wsclient.url)
const binaryType = wsclient.binaryType
/**
* @type {any}
*/
let pingTimeout = null
if (binaryType) {
websocket.binaryType = binaryType
}
wsclient.ws = websocket
wsclient.connecting = true
wsclient.connected = false
websocket.onmessage = event => {
wsclient.lastMessageReceived = time.getUnixTime()
const data = event.data
const message = typeof data === 'string' ? JSON.parse(data) : data
if (message && message.type === 'pong') {
clearTimeout(pingTimeout)
pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2)
}
wsclient.emit('message', [message, wsclient])
}
/**
* @param {any} error
*/
const onclose = error => {
if (wsclient.ws !== null) {
wsclient.ws = null
wsclient.connecting = false
if (wsclient.connected) {
wsclient.connected = false
wsclient.emit('disconnect', [{ type: 'disconnect', error }, wsclient])
} else {
wsclient.unsuccessfulReconnects++
}
// Start with no reconnect timeout and increase timeout by
// log10(wsUnsuccessfulReconnects).
// The idea is to increase reconnect timeout slowly and have no reconnect
// timeout at the beginning (log(1) = 0)
setTimeout(setupWS, math.min(math.log10(wsclient.unsuccessfulReconnects + 1) * reconnectTimeoutBase, maxReconnectTimeout), wsclient)
}
clearTimeout(pingTimeout)
}
const sendPing = () => {
if (wsclient.ws === websocket) {
wsclient.send({
type: 'ping'
})
}
}
websocket.onclose = () => onclose(null)
websocket.onerror = error => onclose(error)
websocket.onopen = () => {
wsclient.lastMessageReceived = time.getUnixTime()
wsclient.connecting = false
wsclient.connected = true
wsclient.unsuccessfulReconnects = 0
wsclient.emit('connect', [{ type: 'connect' }, wsclient])
// set ping
pingTimeout = setTimeout(sendPing, messageReconnectTimeout / 2)
}
}
}
/**
* @deprecated
* @extends Observable<string>
*/
export class WebsocketClient extends Observable {
/**
* @param {string} url
* @param {object} opts
* @param {'arraybuffer' | 'blob' | null} [opts.binaryType] Set `ws.binaryType`
*/
constructor (url, { binaryType } = {}) {
super()
this.url = url
/**
* @type {WebSocket?}
*/
this.ws = null
this.binaryType = binaryType || null
this.connected = false
this.connecting = false
this.unsuccessfulReconnects = 0
this.lastMessageReceived = 0
/**
* Whether to connect to other peers or not
* @type {boolean}
*/
this.shouldConnect = true
this._checkInterval = setInterval(() => {
if (this.connected && messageReconnectTimeout < time.getUnixTime() - this.lastMessageReceived) {
// no message received in a long time - not even your own awareness
// updates (which are updated every 15 seconds)
/** @type {WebSocket} */ (this.ws).close()
}
}, messageReconnectTimeout / 2)
setupWS(this)
}
/**
* @param {any} message
*/
send (message) {
if (this.ws) {
this.ws.send(JSON.stringify(message))
}
}
destroy () {
clearInterval(this._checkInterval)
this.disconnect()
super.destroy()
}
disconnect () {
this.shouldConnect = false
if (this.ws !== null) {
this.ws.close()
}
}
connect () {
this.shouldConnect = true
if (!this.connected && this.ws === null) {
setupWS(this)
}
}
}