diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c385cdd44..d70c7f80ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `WakuRelay.deleteObserver` to allow removal of observers, useful when a React component add observers when mounting and needs to delete it when unmounting. +- Keep alive feature that pings host regularly, reducing the chance of connections being dropped due to idle. + Can be disabled or default frequency (10s) can be changed when calling `Waku.create`. ### Changed - **Breaking**: Auto select peer if none provided for store and light push protocols. diff --git a/src/lib/waku.ts b/src/lib/waku.ts index f46330dfde..19e8309d9b 100644 --- a/src/lib/waku.ts +++ b/src/lib/waku.ts @@ -1,9 +1,10 @@ -import Libp2p, { Libp2pModules, Libp2pOptions } from 'libp2p'; +import Libp2p, { Connection, Libp2pModules, Libp2pOptions } from 'libp2p'; import Mplex from 'libp2p-mplex'; import { bytes } from 'libp2p-noise/dist/src/@types/basic'; import { Noise } from 'libp2p-noise/dist/src/noise'; import Websockets from 'libp2p-websockets'; import filters from 'libp2p-websockets/src/filters'; +import Ping from 'libp2p/src/ping'; import { Multiaddr, multiaddr } from 'multiaddr'; import PeerId from 'peer-id'; @@ -28,6 +29,13 @@ export interface CreateOptions { * @default {@link DefaultPubsubTopic} */ pubsubTopic?: string; + /** + * Set keep alive frequency in seconds: Waku will send a ping request to each peer + * after the set number of seconds. Set to 0 to disable the keep alive feature + * + * @default 10 + */ + keepAlive?: number; /** * You can pass options to the `Libp2p` instance used by {@link Waku} using the {@link CreateOptions.libp2p} property. * This property is the same type than the one passed to [`Libp2p.create`](https://github.com/libp2p/js-libp2p/blob/master/doc/API.md#create) @@ -52,7 +60,12 @@ export class Waku { public store: WakuStore; public lightPush: WakuLightPush; + private keepAliveTimers: { + [peer: string]: ReturnType; + }; + private constructor( + options: CreateOptions, libp2p: Libp2p, store: WakuStore, lightPush: WakuLightPush @@ -61,6 +74,17 @@ export class Waku { this.relay = (libp2p.pubsub as unknown) as WakuRelay; this.store = store; this.lightPush = lightPush; + this.keepAliveTimers = {}; + + const keepAlive = options.keepAlive ? options.keepAlive : 10; + + libp2p.connectionManager.on('peer:connect', (connection: Connection) => { + this.startKeepAlive(connection.remotePeer, keepAlive); + }); + + libp2p.connectionManager.on('peer:disconnect', (connection: Connection) => { + this.stopKeepAlive(connection.remotePeer); + }); } /** @@ -122,7 +146,7 @@ export class Waku { await libp2p.start(); - return new Waku(libp2p, wakuStore, wakuLightPush); + return new Waku(options ? options : {}, libp2p, wakuStore, wakuLightPush); } /** @@ -179,4 +203,22 @@ export class Waku { } return localMultiaddr + '/p2p/' + this.libp2p.peerId.toB58String(); } + + private startKeepAlive(peerId: PeerId, periodSecs: number): void { + // Just in case a timer already exist for this peer + this.stopKeepAlive(peerId); + + const peerIdStr = peerId.toB58String(); + this.keepAliveTimers[peerIdStr] = setInterval(() => { + Ping(this.libp2p, peerId); + }, periodSecs * 1000); + } + + private stopKeepAlive(peerId: PeerId): void { + const peerIdStr = peerId.toB58String(); + if (this.keepAliveTimers[peerIdStr]) { + clearInterval(this.keepAliveTimers[peerIdStr]); + delete this.keepAliveTimers[peerIdStr]; + } + } }