@logux/core
Version:
Logux core components
154 lines (138 loc) • 3.95 kB
JavaScript
const DEFAULT_OPTIONS = {
attempts: Infinity,
maxDelay: 5000,
minDelay: 1000
}
const FATAL_ERRORS = [
'wrong-protocol',
'wrong-subprotocol',
'wrong-credentials'
]
export class Reconnect {
get connected() {
return this.connection.connected
}
get emitter() {
return this.connection.emitter
}
constructor(
connection,
{
attempts = DEFAULT_OPTIONS.attempts,
maxDelay = DEFAULT_OPTIONS.maxDelay,
minDelay = DEFAULT_OPTIONS.minDelay
} = {}
) {
this.connection = connection
this.options = { attempts, maxDelay, minDelay }
this.reconnecting = connection.connected
this.beforeFreeze = null
this.connecting = false
this.attempts = 0
this.unbind = [
this.connection.on('message', msg => {
if (msg[0] === 'error' && FATAL_ERRORS.includes(msg[1])) {
this.reconnecting = false
}
}),
this.connection.on('connecting', () => {
this.connecting = true
}),
this.connection.on('connect', () => {
this.attempts = 0
this.connecting = false
}),
this.connection.on('disconnect', () => {
this.connecting = false
if (this.reconnecting) this.reconnect()
}),
() => {
clearTimeout(this.timer)
}
]
let visibility = () => {
if (this.reconnecting && !this.connected && !this.connecting) {
if (typeof document !== 'undefined' && !document.hidden) this.connect()
}
}
let connect = () => {
if (this.reconnecting && !this.connected && !this.connecting) {
if (navigator.onLine) this.connect()
}
}
let resume = () => {
if (this.beforeFreeze !== null) {
this.reconnecting = this.beforeFreeze
this.beforeFreeze = null
}
connect()
}
let freeze = () => {
if (this.beforeFreeze === null) {
this.beforeFreeze = this.reconnecting
this.reconnecting = false
}
this.disconnect('freeze')
}
if (
typeof document !== 'undefined' &&
typeof window !== 'undefined' &&
document.addEventListener &&
window.addEventListener
) {
document.addEventListener('visibilitychange', visibility, false)
window.addEventListener('focus', connect, false)
window.addEventListener('online', connect, false)
window.addEventListener('resume', resume, false)
window.addEventListener('freeze', freeze, false)
this.unbind.push(() => {
document.removeEventListener('visibilitychange', visibility, false)
window.removeEventListener('focus', connect, false)
window.removeEventListener('online', connect, false)
window.removeEventListener('resume', resume, false)
window.removeEventListener('freeze', freeze, false)
})
}
}
connect() {
this.attempts += 1
this.reconnecting = true
return this.connection.connect()
}
destroy() {
for (let i of this.unbind) i()
this.disconnect('destroy')
}
disconnect(reason) {
if (reason !== 'timeout' && reason !== 'error' && reason !== 'freeze') {
this.reconnecting = false
}
return this.connection.disconnect(reason)
}
nextDelay() {
let base = this.options.minDelay * 2 ** this.attempts
let rand = Math.random()
let deviation = rand * 0.5 * base
if (Math.floor(rand * 10) === 1) deviation = -deviation
return Math.min(base + deviation, this.options.maxDelay) || 0
}
on(...args) {
return this.connection.on(...args)
}
reconnect() {
if (this.attempts > this.options.attempts - 1) {
this.reconnecting = false
this.attempts = 0
return
}
let delay = this.nextDelay()
this.timer = setTimeout(() => {
if (this.reconnecting && !this.connecting && !this.connected) {
this.connect()
}
}, delay)
}
send(...args) {
return this.connection.send(...args)
}
}