mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-04-25 10:32:14 +00:00
155 lines
4.1 KiB
TypeScript
155 lines
4.1 KiB
TypeScript
import { logger } from '@libp2p/logger'
|
|
import mergeOptions from 'merge-options'
|
|
// @ts-expect-error retimer does not have types
|
|
import retimer from 'retimer'
|
|
import all from 'it-all'
|
|
import { pipe } from 'it-pipe'
|
|
import filter from 'it-filter'
|
|
import sort from 'it-sort'
|
|
import type { Startable } from '@libp2p/interfaces/startable'
|
|
import type { Components } from '@libp2p/components'
|
|
|
|
const log = logger('libp2p:connection-manager:auto-dialler')
|
|
|
|
export interface AutoDiallerInit {
|
|
/**
|
|
* Should preemptively guarantee connections are above the low watermark
|
|
*/
|
|
enabled?: boolean
|
|
|
|
/**
|
|
* The minimum number of connections to avoid pruning
|
|
*/
|
|
minConnections?: number
|
|
|
|
/**
|
|
* How often, in milliseconds, it should preemptively guarantee connections are above the low watermark
|
|
*/
|
|
autoDialInterval?: number
|
|
}
|
|
|
|
const defaultOptions: Partial<AutoDiallerInit> = {
|
|
enabled: true,
|
|
minConnections: 0,
|
|
autoDialInterval: 10000
|
|
}
|
|
|
|
export class AutoDialler implements Startable {
|
|
private readonly components: Components
|
|
private readonly options: Required<AutoDiallerInit>
|
|
private running: boolean
|
|
private autoDialTimeout?: ReturnType<retimer>
|
|
|
|
/**
|
|
* Proactively tries to connect to known peers stored in the PeerStore.
|
|
* It will keep the number of connections below the upper limit and sort
|
|
* the peers to connect based on wether we know their keys and protocols.
|
|
*/
|
|
constructor (components: Components, init: AutoDiallerInit) {
|
|
this.components = components
|
|
this.options = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init)
|
|
this.running = false
|
|
this._autoDial = this._autoDial.bind(this)
|
|
|
|
log('options: %j', this.options)
|
|
}
|
|
|
|
isStarted () {
|
|
return this.running
|
|
}
|
|
|
|
/**
|
|
* Starts the auto dialer
|
|
*/
|
|
async start () {
|
|
if (!this.options.enabled) {
|
|
log('not enabled')
|
|
return
|
|
}
|
|
|
|
this.running = true
|
|
|
|
void this._autoDial().catch(err => {
|
|
log.error('could start autodial', err)
|
|
})
|
|
|
|
log('started')
|
|
}
|
|
|
|
/**
|
|
* Stops the auto dialler
|
|
*/
|
|
async stop () {
|
|
if (!this.options.enabled) {
|
|
log('not enabled')
|
|
return
|
|
}
|
|
|
|
this.running = false
|
|
|
|
if (this.autoDialTimeout != null) {
|
|
this.autoDialTimeout.clear()
|
|
}
|
|
|
|
log('stopped')
|
|
}
|
|
|
|
async _autoDial () {
|
|
if (this.autoDialTimeout != null) {
|
|
this.autoDialTimeout.clear()
|
|
}
|
|
|
|
const minConnections = this.options.minConnections
|
|
|
|
// Already has enough connections
|
|
if (this.components.getConnectionManager().getConnections().length >= minConnections) {
|
|
this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval)
|
|
|
|
return
|
|
}
|
|
|
|
// Sort peers on whether we know protocols or public keys for them
|
|
const allPeers = await this.components.getPeerStore().all()
|
|
|
|
const peers = await pipe(
|
|
// shuffle the peers
|
|
allPeers.sort(() => Math.random() > 0.5 ? 1 : -1),
|
|
(source) => filter(source, (peer) => !peer.id.equals(this.components.getPeerId())),
|
|
(source) => sort(source, (a, b) => {
|
|
if (b.protocols.length > a.protocols.length) {
|
|
return 1
|
|
} else if (b.id.publicKey != null && a.id.publicKey == null) {
|
|
return 1
|
|
}
|
|
return -1
|
|
}),
|
|
async (source) => await all(source)
|
|
)
|
|
|
|
for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnections().length < minConnections; i++) {
|
|
// Connection Manager was stopped during async dial
|
|
if (!this.running) {
|
|
return
|
|
}
|
|
|
|
const peer = peers[i]
|
|
|
|
if (this.components.getConnectionManager().getConnections(peer.id).length === 0) {
|
|
log('connecting to a peerStore stored peer %p', peer.id)
|
|
try {
|
|
await this.components.getConnectionManager().openConnection(peer.id)
|
|
} catch (err: any) {
|
|
log.error('could not connect to peerStore stored peer', err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Connection Manager was stopped
|
|
if (!this.running) {
|
|
return
|
|
}
|
|
|
|
this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval)
|
|
}
|
|
}
|