const s = require("ready-resource"),
e = require("hypercore-id-encoding"),
t = require("scope-lock"),
{
local: {
hyperschema: { getEnum: i },
},
} = require("../../../schema/spec"),
r = i("@local/keet-state"),
a = 8,
o = 1e3,
n = 3e4,
c = 6e4,
l = 1e4,
h = 5e3,
m = 5e3
async function d(s) {
;(await s.swarm.flushed(),
await s.swarm.destroy(),
(s.swarmStatus.isClient = !1),
(s.swarmStatus.isServer = !1),
(s.swarm = null))
}
module.exports = class extends s {
constructor(s, e = {}) {
super()
const {
gcDefaultDiscoveryTimeout: i = c,
gcUpdatedDiscoveryTimeout: r = l,
gcStartDiscoverySwarmingDelay: d = h,
gcStartFallbackSwarmingDelay: u = m,
refreshInterval: w = o,
fallbackSwarmingLimit: S = a,
gcIdleTime: g = n,
} = e
;((this.keet = s),
(this.swarm = null),
(this.sessions = new Map()),
(this.sessionsByRoomId = new Map()),
(this._refreshLock = new t({ debounce: !0 })),
(this._refreshTimer = null),
(this._gcEnabled = !1 !== e.gc),
(this._deferSwarming = !1 !== e.deferSwarming),
(this._fallbackSwarmingEnabled = !1 !== e.fallbackSwarming),
(this._fallbackSwarmingLimit = S),
(this._gcIdleTime = g),
(this._refreshInterval = w),
(this._gcDefaultDiscoveryTimeout = i),
(this._gcUpdatedDiscoveryTimeout = r),
(this._gcStartDiscoverySwarmingDelay = d),
(this._gcStartFallbackSwarmingDelay = u),
(this._serverModeAllowed = !0),
(this._backgrounded = !1))
}
_runOpen() {
;(this._refreshTimer ||
(this._refreshTimer = setInterval(this.refresh.bind(this), this._refreshInterval)),
this.refresh().catch(this.keet.onerror))
}
async _runClose() {
this._refreshTimer && (clearInterval(this._refreshTimer), (this._refreshTimer = null))
const s = []
for (const e of this.sessions.values()) e.swarm && s.push(e.swarm.destroy())
;(this.sessions.clear(), await Promise.allSettled(s))
}
_open() {
;((this.swarm = this.keet.swarm), this._runOpen())
}
_close() {
return this._runClose()
}
async _refreshFallbackSwarming() {
if (!this._fallbackSwarmingEnabled) return
const s = await this.keet.local
.find("@local/rooms-by-timestamp", { limit: this._fallbackSwarmingLimit, reverse: !0 })
.toArray(),
t = new Set()
for (const i of s) {
const s = e.encode(i.capabilities.swarmTopic),
r = this._createSession(s, i.capabilities.swarmTopic)
if (!r.status.isFallback) {
r.status.isFallback = !0
const s = Math.floor(0.5 * Math.random() * this._gcStartFallbackSwarmingDelay),
e = this._gcStartFallbackSwarmingDelay + s
r.fallbackSwarmingStartTime = Date.now() + e
}
t.add(s)
}
for (const s of this.sessions.values()) t.has(s.id) || (s.status.isFallback = !1)
}
async _refreshSessions() {
const s = [],
e = []
for (const t of this.sessions.values())
this._checkInactivity(t) ? s.push(t) : e.push(this._refreshSession(t))
await Promise.allSettled(e)
const t = []
for (const e of s)
(this.sessions.delete(e.id),
e.roomId && this.sessionsByRoomId.delete(e.roomId),
e.swarm && t.push(e.swarm.destroy()))
await Promise.allSettled(t)
}
_checkInactivity(s) {
if (!this._gcEnabled) return !1
let e = !1
s.status.isDiscovering && (e = Date.now() - s.discoveryStartTime > s.discoveryTimeout)
const t = s.status.isFallback || s.status.isActive
let i = s.status.isDiscovering && !e
return (
s.status.isDiscovering &&
e &&
(this.keet.internalDiscoveryCompleted(s.discoveryKey, s.roomId),
(s.status.isDiscovering = !1),
(i = !1)),
!(
t ||
i ||
(0 === s.idleStartTime && (s.idleStartTime = Date.now()),
(s.idleDuration = Date.now() - s.idleStartTime),
s.idleDuration < this._gcIdleTime)
)
)
}
async _refreshSession(s) {
let e = !1,
t = !1
;(s.status.isFallback &&
(t = Date.now() >= s.fallbackSwarmingStartTime || s.swarmStatus.isServer),
s.status.isDiscovering &&
(e = Date.now() >= s.discoverySwarmingStartTime || s.swarmStatus.isClient),
s.status.isActive && ((e = !0), (t = !0)))
const i = e === s.swarmStatus.isClient,
a = t === s.swarmStatus.isServer
if (i && a) return
const o = s.swarm
;(this._serverModeAllowed || (t = !1),
(s.swarmStatus.isClient = e),
(s.swarmStatus.isServer = t),
this.keet.debug.disableSwarming ||
this.keet.state === r.WAKEUP ||
((s.swarm = this.swarm.join(s.discoveryKey, { client: e, server: t, limit: s.limit })),
o && (await o.destroy())))
}
async refresh() {
if (!this.keet.closing && (await this._refreshLock.lock()))
try {
;(await this._refreshFallbackSwarming(), await this._refreshSessions())
} catch (s) {
this.keet.onerror(s)
} finally {
this._refreshLock.unlock()
}
}
_createSession(s, e, { roomId: t, limit: i } = {}) {
let r = this.sessions.get(s)
return r
? (t && !this.sessionsByRoomId.has(t) && ((r.roomId = t), this.sessionsByRoomId.set(t, r)), r)
: ((r = {
id: s,
roomId: t,
discoveryKey: e,
swarm: null,
lastActiveTime: 0,
discoveryStartTime: 0,
discoveryTimeout: 0,
discoverySwarmingStartTime: 0,
fallbackSwarmingStartTime: 0,
idleStartTime: 0,
idleDuration: 0,
status: { isActive: !1, isFallback: !1, isDiscovering: !1 },
limit: i,
swarmStatus: { isClient: !1, isServer: !1 },
}),
this.sessions.set(s, r),
t && this.sessionsByRoomId.set(t, r),
r)
}
setServerMode(s) {
this._serverModeAllowed = s
}
startActiveSwarming(s) {
const t = e.encode(s),
i = this._createSession(t, s)
;((i.status.isActive = !0), (i.lastActiveTime = Date.now()), this.refresh())
}
stopActiveSwarming(s) {
const t = e.encode(s),
i = this.sessions.get(t)
i && ((i.status.isActive = !1), this.refresh())
}
startDiscoverySwarming(s, t) {
const i = e.encode(s),
r = this._createSession(i, s, t)
if (
((r.status.isDiscovering = !0),
(r.discoveryStartTime = Date.now()),
(r.discoveryTimeout = this._gcDefaultDiscoveryTimeout),
!1 !== t.deferSwarming && this._deferSwarming)
) {
const s = Math.floor(0.5 * Math.random() * this._gcStartDiscoverySwarmingDelay),
e = this._gcStartDiscoverySwarmingDelay + s
r.discoverySwarmingStartTime = Date.now() + e
} else r.discoverySwarmingStartTime = r.discoveryStartTime
this.refresh()
}
notifyDiscoveryUpdate(s) {
const e = this.sessionsByRoomId.get(s)
e &&
e.status.isDiscovering &&
((e.discoveryStartTime = Date.now()),
(e.discoveryTimeout = this._gcUpdatedDiscoveryTimeout),
this.refresh())
}
async background() {
if (this._backgrounded) return
this._backgrounded = !0
const s = []
for (const e of this.sessions.values()) e.swarm && s.push(d(e))
await Promise.allSettled(s)
}
async unbackground() {
this._backgrounded && ((this._backgrounded = !1), await this._runOpen())
}
async suspend() {
await this._runClose()
}
async resume() {
await this._runOpen()
}
async flush() {
;(await this.refresh(), await this.swarm.flush())
}
}