2022-07-22 15:26:43 +10:00
|
|
|
import type { Stream } from "@libp2p/interface-connection";
|
2023-02-09 13:21:03 +11:00
|
|
|
import type { Libp2p } from "@libp2p/interface-libp2p";
|
2022-06-20 16:48:30 +10:00
|
|
|
import type { PeerId } from "@libp2p/interface-peer-id";
|
2022-09-07 16:51:43 +10:00
|
|
|
import type { PubSub } from "@libp2p/interface-pubsub";
|
2022-08-01 15:43:43 +10:00
|
|
|
import type { Multiaddr } from "@multiformats/multiaddr";
|
2022-12-07 11:35:30 +05:30
|
|
|
import type {
|
2022-12-06 12:36:29 +11:00
|
|
|
IFilter,
|
|
|
|
|
ILightPush,
|
|
|
|
|
IRelay,
|
|
|
|
|
IStore,
|
2022-12-07 11:35:30 +05:30
|
|
|
Waku,
|
|
|
|
|
} from "@waku/interfaces";
|
2022-11-01 16:30:24 +11:00
|
|
|
import { Protocols } from "@waku/interfaces";
|
2022-02-04 14:12:00 +11:00
|
|
|
import debug from "debug";
|
|
|
|
|
|
2022-12-06 12:49:02 +11:00
|
|
|
import { createEncoder } from "./message/version_0.js";
|
|
|
|
|
import * as relayConstants from "./relay/constants.js";
|
2023-01-25 17:20:33 +11:00
|
|
|
import { RelayPingContentTopic } from "./relay/constants.js";
|
2021-03-19 14:40:16 +11:00
|
|
|
|
2021-08-11 10:34:42 +10:00
|
|
|
export const DefaultPingKeepAliveValueSecs = 0;
|
|
|
|
|
export const DefaultRelayKeepAliveValueSecs = 5 * 60;
|
2022-11-11 19:51:27 +05:30
|
|
|
export const DefaultUserAgent = "js-waku";
|
2021-07-27 15:55:11 +10:00
|
|
|
|
2022-06-23 16:38:14 +10:00
|
|
|
const log = debug("waku:waku");
|
2021-08-10 11:32:14 +10:00
|
|
|
|
2022-08-01 15:43:43 +10:00
|
|
|
export interface WakuOptions {
|
2021-06-18 16:48:16 +10:00
|
|
|
/**
|
2021-07-22 16:34:27 +10:00
|
|
|
* Set keep alive frequency in seconds: Waku will send a `/ipfs/ping/1.0.0`
|
|
|
|
|
* request to each peer after the set number of seconds. Set to 0 to disable.
|
2021-06-18 16:48:16 +10:00
|
|
|
*
|
2023-02-09 14:45:32 +11:00
|
|
|
* @default {@link @waku/core.DefaultPingKeepAliveValueSecs}
|
2021-06-18 16:48:16 +10:00
|
|
|
*/
|
2021-07-22 16:34:27 +10:00
|
|
|
pingKeepAlive?: number;
|
2021-07-27 15:55:11 +10:00
|
|
|
/**
|
|
|
|
|
* Set keep alive frequency in seconds: Waku will send a ping message over
|
|
|
|
|
* relay to each peer after the set number of seconds. Set to 0 to disable.
|
|
|
|
|
*
|
2023-02-09 14:45:32 +11:00
|
|
|
* @default {@link @waku/core.DefaultRelayKeepAliveValueSecs}
|
2021-07-27 15:55:11 +10:00
|
|
|
*/
|
|
|
|
|
relayKeepAlive?: number;
|
2022-11-10 22:44:53 +05:30
|
|
|
/**
|
|
|
|
|
* Set the user agent string to be used in identification of the node.
|
2023-02-09 14:45:32 +11:00
|
|
|
* @default {@link @waku/core.DefaultUserAgent}
|
2022-11-10 22:44:53 +05:30
|
|
|
*/
|
|
|
|
|
userAgent?: string;
|
2021-06-08 22:01:48 +10:00
|
|
|
}
|
2021-03-29 13:56:17 +11:00
|
|
|
|
2022-09-06 12:25:28 +10:00
|
|
|
export class WakuNode implements Waku {
|
2021-04-16 11:25:08 +10:00
|
|
|
public libp2p: Libp2p;
|
2022-12-06 12:36:29 +11:00
|
|
|
public relay?: IRelay;
|
|
|
|
|
public store?: IStore;
|
|
|
|
|
public filter?: IFilter;
|
|
|
|
|
public lightPush?: ILightPush;
|
2021-04-16 11:25:08 +10:00
|
|
|
|
2021-07-22 16:34:27 +10:00
|
|
|
private pingKeepAliveTimers: {
|
2021-06-18 16:48:16 +10:00
|
|
|
[peer: string]: ReturnType<typeof setInterval>;
|
|
|
|
|
};
|
2021-07-27 15:55:11 +10:00
|
|
|
private relayKeepAliveTimers: {
|
|
|
|
|
[peer: string]: ReturnType<typeof setInterval>;
|
|
|
|
|
};
|
2021-06-18 16:48:16 +10:00
|
|
|
|
2022-06-22 15:52:02 +10:00
|
|
|
constructor(
|
2022-08-01 15:43:43 +10:00
|
|
|
options: WakuOptions,
|
2021-05-19 11:00:43 +10:00
|
|
|
libp2p: Libp2p,
|
2023-02-09 13:21:03 +11:00
|
|
|
store?: (libp2p: Libp2p) => IStore,
|
|
|
|
|
lightPush?: (libp2p: Libp2p) => ILightPush,
|
2023-02-17 13:27:37 +05:30
|
|
|
filter?: (libp2p: Libp2p) => IFilter
|
2021-05-19 11:00:43 +10:00
|
|
|
) {
|
2021-04-16 11:25:08 +10:00
|
|
|
this.libp2p = libp2p;
|
2022-09-07 16:51:43 +10:00
|
|
|
|
2022-11-17 11:01:27 +11:00
|
|
|
if (store) {
|
2023-02-09 13:21:03 +11:00
|
|
|
this.store = store(libp2p);
|
2022-11-17 11:01:27 +11:00
|
|
|
}
|
|
|
|
|
if (filter) {
|
2023-02-09 13:21:03 +11:00
|
|
|
this.filter = filter(libp2p);
|
2022-11-17 11:01:27 +11:00
|
|
|
}
|
|
|
|
|
if (lightPush) {
|
2023-02-09 13:21:03 +11:00
|
|
|
this.lightPush = lightPush(libp2p);
|
2022-11-17 11:01:27 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isRelay(libp2p.pubsub)) {
|
2022-09-07 16:51:43 +10:00
|
|
|
this.relay = libp2p.pubsub;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-13 20:25:40 +10:00
|
|
|
log(
|
|
|
|
|
"Waku node created",
|
|
|
|
|
this.libp2p.peerId.toString(),
|
|
|
|
|
`relay: ${!!this.relay}, store: ${!!this.store}, light push: ${!!this
|
2023-02-17 13:27:37 +05:30
|
|
|
.lightPush}, filter: ${!!this.filter}} `
|
2022-09-13 20:25:40 +10:00
|
|
|
);
|
|
|
|
|
|
2021-07-22 16:34:27 +10:00
|
|
|
this.pingKeepAliveTimers = {};
|
2021-07-27 15:55:11 +10:00
|
|
|
this.relayKeepAliveTimers = {};
|
2021-06-18 16:48:16 +10:00
|
|
|
|
2021-07-27 15:55:11 +10:00
|
|
|
const pingKeepAlive =
|
|
|
|
|
options.pingKeepAlive || DefaultPingKeepAliveValueSecs;
|
2022-09-07 16:51:43 +10:00
|
|
|
const relayKeepAlive = this.relay
|
|
|
|
|
? options.relayKeepAlive || DefaultRelayKeepAliveValueSecs
|
|
|
|
|
: 0;
|
2021-06-18 16:48:16 +10:00
|
|
|
|
2023-02-09 13:21:03 +11:00
|
|
|
libp2p.addEventListener("peer:connect", (evt) => {
|
2022-06-21 13:23:42 +10:00
|
|
|
this.startKeepAlive(evt.detail.remotePeer, pingKeepAlive, relayKeepAlive);
|
2021-07-27 15:55:11 +10:00
|
|
|
});
|
2021-06-18 16:48:16 +10:00
|
|
|
|
2022-05-29 17:39:51 +02:00
|
|
|
/**
|
2022-05-29 17:46:26 +02:00
|
|
|
* NOTE: Event is not being emitted on closing nor losing a connection.
|
2022-05-29 17:39:51 +02:00
|
|
|
* @see https://github.com/libp2p/js-libp2p/issues/939
|
2022-05-29 17:41:55 +02:00
|
|
|
* @see https://github.com/status-im/js-waku/issues/252
|
2022-05-29 17:39:51 +02:00
|
|
|
*
|
|
|
|
|
* >This event will be triggered anytime we are disconnected from another peer,
|
|
|
|
|
* >regardless of the circumstances of that disconnection.
|
|
|
|
|
* >If we happen to have multiple connections to a peer,
|
|
|
|
|
* >this event will **only** be triggered when the last connection is closed.
|
|
|
|
|
* @see https://github.com/libp2p/js-libp2p/blob/bad9e8c0ff58d60a78314077720c82ae331cc55b/doc/API.md?plain=1#L2100
|
|
|
|
|
*/
|
2023-02-09 13:21:03 +11:00
|
|
|
libp2p.addEventListener("peer:disconnect", (evt) => {
|
2022-06-21 13:23:42 +10:00
|
|
|
this.stopKeepAlive(evt.detail.remotePeer);
|
2021-07-27 15:55:11 +10:00
|
|
|
});
|
2022-11-16 20:30:48 +11:00
|
|
|
|
|
|
|
|
// Trivial handling of discovered peers, to be refined.
|
|
|
|
|
libp2p.addEventListener("peer:discovery", (evt) => {
|
|
|
|
|
const peerId = evt.detail.id;
|
|
|
|
|
log(`Found peer ${peerId.toString()}, dialing.`);
|
|
|
|
|
libp2p.dial(peerId).catch((err) => {
|
|
|
|
|
log(`Fail to dial ${peerId}`, err);
|
|
|
|
|
});
|
|
|
|
|
});
|
2021-04-16 11:25:08 +10:00
|
|
|
}
|
2021-03-19 14:40:16 +11:00
|
|
|
|
2021-03-23 11:14:51 +11:00
|
|
|
/**
|
2021-04-07 11:04:30 +10:00
|
|
|
* Dials to the provided peer.
|
2021-05-13 11:47:03 +10:00
|
|
|
*
|
2021-03-23 11:14:51 +11:00
|
|
|
* @param peer The peer to dial
|
2022-11-15 21:23:27 +11:00
|
|
|
* @param protocols Waku protocols we expect from the peer; Defaults to mounted protocols
|
2021-03-23 11:14:51 +11:00
|
|
|
*/
|
2022-07-22 15:26:43 +10:00
|
|
|
async dial(
|
|
|
|
|
peer: PeerId | Multiaddr,
|
|
|
|
|
protocols?: Protocols[]
|
|
|
|
|
): Promise<Stream> {
|
2022-11-15 21:23:27 +11:00
|
|
|
const _protocols = protocols ?? [];
|
|
|
|
|
|
|
|
|
|
if (typeof protocols === "undefined") {
|
|
|
|
|
this.relay && _protocols.push(Protocols.Relay);
|
|
|
|
|
this.store && _protocols.push(Protocols.Store);
|
|
|
|
|
this.filter && _protocols.push(Protocols.Filter);
|
|
|
|
|
this.lightPush && _protocols.push(Protocols.LightPush);
|
|
|
|
|
}
|
2022-02-13 19:04:50 +11:00
|
|
|
|
|
|
|
|
const codecs: string[] = [];
|
|
|
|
|
if (_protocols.includes(Protocols.Relay)) {
|
2023-01-25 17:20:33 +11:00
|
|
|
if (this.relay) {
|
|
|
|
|
this.relay.multicodecs.forEach((codec) => codecs.push(codec));
|
|
|
|
|
} else {
|
|
|
|
|
log(
|
|
|
|
|
"Relay codec not included in dial codec: protocol not mounted locally"
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-02-13 19:04:50 +11:00
|
|
|
}
|
|
|
|
|
if (_protocols.includes(Protocols.Store)) {
|
2023-01-25 17:20:33 +11:00
|
|
|
if (this.store) {
|
|
|
|
|
codecs.push(this.store.multicodec);
|
|
|
|
|
} else {
|
|
|
|
|
log(
|
|
|
|
|
"Store codec not included in dial codec: protocol not mounted locally"
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-02-13 19:04:50 +11:00
|
|
|
}
|
|
|
|
|
if (_protocols.includes(Protocols.LightPush)) {
|
2023-01-25 17:20:33 +11:00
|
|
|
if (this.lightPush) {
|
|
|
|
|
codecs.push(this.lightPush.multicodec);
|
|
|
|
|
} else {
|
|
|
|
|
log(
|
|
|
|
|
"Light Push codec not included in dial codec: protocol not mounted locally"
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-05-26 14:48:33 +10:00
|
|
|
}
|
|
|
|
|
if (_protocols.includes(Protocols.Filter)) {
|
2023-01-25 17:20:33 +11:00
|
|
|
if (this.filter) {
|
|
|
|
|
codecs.push(this.filter.multicodec);
|
|
|
|
|
} else {
|
|
|
|
|
log(
|
|
|
|
|
"Filter codec not included in dial codec: protocol not mounted locally"
|
|
|
|
|
);
|
|
|
|
|
}
|
2022-02-13 19:04:50 +11:00
|
|
|
}
|
|
|
|
|
|
2022-12-07 11:35:30 +05:30
|
|
|
log(`Dialing to ${peer.toString()} with protocols ${_protocols}`);
|
|
|
|
|
|
2022-02-13 19:04:50 +11:00
|
|
|
return this.libp2p.dialProtocol(peer, codecs);
|
2021-03-23 11:14:51 +11:00
|
|
|
}
|
|
|
|
|
|
2022-06-23 14:03:52 +10:00
|
|
|
async start(): Promise<void> {
|
|
|
|
|
await this.libp2p.start();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-03 15:52:38 +10:00
|
|
|
async stop(): Promise<void> {
|
2022-05-29 17:39:51 +02:00
|
|
|
this.stopAllKeepAlives();
|
2022-05-29 13:46:12 +02:00
|
|
|
await this.libp2p.stop();
|
2021-03-19 16:07:56 +11:00
|
|
|
}
|
2021-04-06 11:06:10 +10:00
|
|
|
|
2022-07-27 12:54:02 +10:00
|
|
|
isStarted(): boolean {
|
|
|
|
|
return this.libp2p.isStarted();
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-06 11:06:10 +10:00
|
|
|
/**
|
|
|
|
|
* Return the local multiaddr with peer id on which libp2p is listening.
|
2022-02-11 17:27:15 +11:00
|
|
|
*
|
|
|
|
|
* @throws if libp2p is not listening on localhost.
|
2021-04-06 11:06:10 +10:00
|
|
|
*/
|
|
|
|
|
getLocalMultiaddrWithID(): string {
|
2022-06-22 15:52:02 +10:00
|
|
|
const localMultiaddr = this.libp2p
|
|
|
|
|
.getMultiaddrs()
|
|
|
|
|
.find((addr) => addr.toString().match(/127\.0\.0\.1/));
|
2022-02-04 14:12:00 +11:00
|
|
|
if (!localMultiaddr || localMultiaddr.toString() === "") {
|
|
|
|
|
throw "Not listening on localhost";
|
2021-04-06 11:06:10 +10:00
|
|
|
}
|
2022-06-14 16:12:37 +10:00
|
|
|
return localMultiaddr + "/p2p/" + this.libp2p.peerId.toString();
|
2021-04-06 11:06:10 +10:00
|
|
|
}
|
2021-06-18 16:48:16 +10:00
|
|
|
|
2021-07-27 15:55:11 +10:00
|
|
|
private startKeepAlive(
|
|
|
|
|
peerId: PeerId,
|
|
|
|
|
pingPeriodSecs: number,
|
|
|
|
|
relayPeriodSecs: number
|
|
|
|
|
): void {
|
2021-06-21 09:46:41 +10:00
|
|
|
// Just in case a timer already exist for this peer
|
2021-07-27 15:55:11 +10:00
|
|
|
this.stopKeepAlive(peerId);
|
2021-06-18 16:48:16 +10:00
|
|
|
|
2022-06-14 16:12:37 +10:00
|
|
|
const peerIdStr = peerId.toString();
|
2021-07-27 15:55:11 +10:00
|
|
|
|
|
|
|
|
if (pingPeriodSecs !== 0) {
|
|
|
|
|
this.pingKeepAliveTimers[peerIdStr] = setInterval(() => {
|
2022-06-22 15:59:18 +10:00
|
|
|
this.libp2p.ping(peerId).catch((e) => {
|
2022-06-23 16:38:14 +10:00
|
|
|
log(`Ping failed (${peerIdStr})`, e);
|
2022-02-02 15:12:08 +11:00
|
|
|
});
|
2021-07-27 15:55:11 +10:00
|
|
|
}, pingPeriodSecs * 1000);
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-07 16:51:43 +10:00
|
|
|
const relay = this.relay;
|
|
|
|
|
if (relay && relayPeriodSecs !== 0) {
|
2023-02-02 11:37:28 +05:30
|
|
|
const encoder = createEncoder({
|
|
|
|
|
contentTopic: RelayPingContentTopic,
|
|
|
|
|
ephemeral: true,
|
|
|
|
|
});
|
2021-07-27 15:55:11 +10:00
|
|
|
this.relayKeepAliveTimers[peerIdStr] = setInterval(() => {
|
2022-08-31 13:14:52 +10:00
|
|
|
log("Sending Waku Relay ping message");
|
2022-09-19 13:50:29 +10:00
|
|
|
relay
|
|
|
|
|
.send(encoder, { payload: new Uint8Array() })
|
|
|
|
|
.catch((e) => log("Failed to send relay ping", e));
|
2021-07-27 15:55:11 +10:00
|
|
|
}, relayPeriodSecs * 1000);
|
|
|
|
|
}
|
2021-06-18 16:48:16 +10:00
|
|
|
}
|
|
|
|
|
|
2021-07-27 15:55:11 +10:00
|
|
|
private stopKeepAlive(peerId: PeerId): void {
|
2022-06-14 16:12:37 +10:00
|
|
|
const peerIdStr = peerId.toString();
|
2021-07-27 15:55:11 +10:00
|
|
|
|
2021-07-22 16:34:27 +10:00
|
|
|
if (this.pingKeepAliveTimers[peerIdStr]) {
|
|
|
|
|
clearInterval(this.pingKeepAliveTimers[peerIdStr]);
|
|
|
|
|
delete this.pingKeepAliveTimers[peerIdStr];
|
2021-06-18 16:48:16 +10:00
|
|
|
}
|
2021-07-27 15:55:11 +10:00
|
|
|
|
|
|
|
|
if (this.relayKeepAliveTimers[peerIdStr]) {
|
|
|
|
|
clearInterval(this.relayKeepAliveTimers[peerIdStr]);
|
|
|
|
|
delete this.relayKeepAliveTimers[peerIdStr];
|
|
|
|
|
}
|
2021-06-18 16:48:16 +10:00
|
|
|
}
|
2022-05-29 17:39:51 +02:00
|
|
|
|
|
|
|
|
private stopAllKeepAlives(): void {
|
|
|
|
|
for (const timer of [
|
|
|
|
|
...Object.values(this.pingKeepAliveTimers),
|
|
|
|
|
...Object.values(this.relayKeepAliveTimers),
|
|
|
|
|
]) {
|
|
|
|
|
clearInterval(timer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.pingKeepAliveTimers = {};
|
|
|
|
|
this.relayKeepAliveTimers = {};
|
|
|
|
|
}
|
2021-03-19 14:40:16 +11:00
|
|
|
}
|
2022-09-07 16:51:43 +10:00
|
|
|
|
2022-12-06 12:36:29 +11:00
|
|
|
function isRelay(pubsub: PubSub): pubsub is IRelay {
|
2022-09-07 16:51:43 +10:00
|
|
|
if (pubsub) {
|
|
|
|
|
try {
|
|
|
|
|
return pubsub.multicodecs.includes(
|
|
|
|
|
relayConstants.RelayCodecs[relayConstants.RelayCodecs.length - 1]
|
|
|
|
|
);
|
|
|
|
|
// Exception is expected if `libp2p` was not instantiated with pubsub
|
|
|
|
|
// eslint-disable-next-line no-empty
|
|
|
|
|
} catch (e) {}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|