@waku/core 0.0.37-7a9850d.0 → 0.0.37-987c6cd.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/index.js +4745 -899
- package/bundle/lib/message/version_0.js +1 -1
- package/bundle/{version_0-9DPFjcJG.js → version_0-BpF0pNhc.js} +314 -35
- package/dist/.tsbuildinfo +1 -1
- package/dist/lib/connection_manager/connection_limiter.d.ts +37 -0
- package/dist/lib/connection_manager/connection_limiter.js +124 -0
- package/dist/lib/connection_manager/connection_limiter.js.map +1 -0
- package/dist/lib/connection_manager/connection_manager.d.ts +20 -105
- package/dist/lib/connection_manager/connection_manager.js +83 -508
- package/dist/lib/connection_manager/connection_manager.js.map +1 -1
- package/dist/lib/connection_manager/dialer.d.ts +28 -0
- package/dist/lib/connection_manager/dialer.js +100 -0
- package/dist/lib/connection_manager/dialer.js.map +1 -0
- package/dist/lib/connection_manager/discovery_dialer.d.ts +26 -0
- package/dist/lib/connection_manager/discovery_dialer.js +68 -0
- package/dist/lib/connection_manager/discovery_dialer.js.map +1 -0
- package/dist/lib/connection_manager/keep_alive_manager.d.ts +17 -7
- package/dist/lib/connection_manager/keep_alive_manager.js +110 -74
- package/dist/lib/connection_manager/keep_alive_manager.js.map +1 -1
- package/dist/lib/connection_manager/network_monitor.d.ts +36 -0
- package/dist/lib/connection_manager/network_monitor.js +81 -0
- package/dist/lib/connection_manager/network_monitor.js.map +1 -0
- package/dist/lib/connection_manager/shard_reader.d.ts +28 -0
- package/dist/lib/connection_manager/shard_reader.js +70 -0
- package/dist/lib/connection_manager/shard_reader.js.map +1 -0
- package/dist/lib/connection_manager/utils.d.ts +16 -1
- package/dist/lib/connection_manager/utils.js +23 -0
- package/dist/lib/connection_manager/utils.js.map +1 -1
- package/dist/lib/filter/filter.d.ts +2 -3
- package/dist/lib/filter/filter.js +5 -25
- package/dist/lib/filter/filter.js.map +1 -1
- package/dist/lib/light_push/light_push.d.ts +2 -3
- package/dist/lib/light_push/light_push.js +1 -3
- package/dist/lib/light_push/light_push.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/connection_manager/connection_limiter.ts +201 -0
- package/src/lib/connection_manager/connection_manager.ts +104 -669
- package/src/lib/connection_manager/dialer.ts +139 -0
- package/src/lib/connection_manager/discovery_dialer.ts +106 -0
- package/src/lib/connection_manager/keep_alive_manager.ts +154 -87
- package/src/lib/connection_manager/network_monitor.ts +112 -0
- package/src/lib/connection_manager/shard_reader.ts +134 -0
- package/src/lib/connection_manager/utils.ts +27 -1
- package/src/lib/filter/filter.ts +3 -28
- package/src/lib/light_push/light_push.ts +1 -5
@@ -0,0 +1,139 @@
|
|
1
|
+
import type { PeerId } from "@libp2p/interface";
|
2
|
+
import { Libp2p } from "@waku/interfaces";
|
3
|
+
import { Logger } from "@waku/utils";
|
4
|
+
|
5
|
+
import { ShardReader } from "./shard_reader.js";
|
6
|
+
|
7
|
+
const log = new Logger("dialer");
|
8
|
+
|
9
|
+
type DialerConstructorOptions = {
|
10
|
+
libp2p: Libp2p;
|
11
|
+
shardReader: ShardReader;
|
12
|
+
};
|
13
|
+
|
14
|
+
interface IDialer {
|
15
|
+
start(): void;
|
16
|
+
stop(): void;
|
17
|
+
dial(peerId: PeerId): Promise<void>;
|
18
|
+
}
|
19
|
+
|
20
|
+
export class Dialer implements IDialer {
|
21
|
+
private readonly libp2p: Libp2p;
|
22
|
+
private readonly shardReader: ShardReader;
|
23
|
+
|
24
|
+
private dialingQueue: PeerId[] = [];
|
25
|
+
private dialHistory: Map<string, number> = new Map();
|
26
|
+
private dialingInterval: NodeJS.Timeout | null = null;
|
27
|
+
private isProcessing = false;
|
28
|
+
|
29
|
+
public constructor(options: DialerConstructorOptions) {
|
30
|
+
this.libp2p = options.libp2p;
|
31
|
+
this.shardReader = options.shardReader;
|
32
|
+
}
|
33
|
+
|
34
|
+
public start(): void {
|
35
|
+
if (!this.dialingInterval) {
|
36
|
+
this.dialingInterval = setInterval(() => {
|
37
|
+
void this.processQueue();
|
38
|
+
}, 500);
|
39
|
+
}
|
40
|
+
|
41
|
+
this.dialHistory.clear();
|
42
|
+
}
|
43
|
+
|
44
|
+
public stop(): void {
|
45
|
+
if (this.dialingInterval) {
|
46
|
+
clearInterval(this.dialingInterval);
|
47
|
+
this.dialingInterval = null;
|
48
|
+
}
|
49
|
+
|
50
|
+
this.dialHistory.clear();
|
51
|
+
}
|
52
|
+
|
53
|
+
public async dial(peerId: PeerId): Promise<void> {
|
54
|
+
const shouldSkip = await this.shouldSkipPeer(peerId);
|
55
|
+
|
56
|
+
if (shouldSkip) {
|
57
|
+
log.info(`Skipping peer: ${peerId}`);
|
58
|
+
return;
|
59
|
+
}
|
60
|
+
|
61
|
+
// If queue is empty and we're not currently processing, dial immediately
|
62
|
+
if (this.dialingQueue.length === 0 && !this.isProcessing) {
|
63
|
+
await this.dialPeer(peerId);
|
64
|
+
} else {
|
65
|
+
// Add to queue
|
66
|
+
this.dialingQueue.push(peerId);
|
67
|
+
log.info(
|
68
|
+
`Added peer to dialing queue, queue size: ${this.dialingQueue.length}`
|
69
|
+
);
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
private async processQueue(): Promise<void> {
|
74
|
+
if (this.dialingQueue.length === 0 || this.isProcessing) return;
|
75
|
+
|
76
|
+
this.isProcessing = true;
|
77
|
+
|
78
|
+
try {
|
79
|
+
const peersToDial = this.dialingQueue.slice(0, 3);
|
80
|
+
this.dialingQueue = this.dialingQueue.slice(peersToDial.length);
|
81
|
+
|
82
|
+
log.info(
|
83
|
+
`Processing dial queue: dialing ${peersToDial.length} peers, ${this.dialingQueue.length} remaining in queue`
|
84
|
+
);
|
85
|
+
|
86
|
+
await Promise.all(peersToDial.map((peerId) => this.dialPeer(peerId)));
|
87
|
+
} finally {
|
88
|
+
this.isProcessing = false;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
private async dialPeer(peerId: PeerId): Promise<void> {
|
93
|
+
try {
|
94
|
+
log.info(`Dialing peer from queue: ${peerId}`);
|
95
|
+
|
96
|
+
await this.libp2p.dial(peerId);
|
97
|
+
this.dialHistory.set(peerId.toString(), Date.now());
|
98
|
+
|
99
|
+
log.info(`Successfully dialed peer from queue: ${peerId}`);
|
100
|
+
} catch (error) {
|
101
|
+
log.error(`Error dialing peer ${peerId}`, error);
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
private async shouldSkipPeer(peerId: PeerId): Promise<boolean> {
|
106
|
+
const hasConnection = this.libp2p.getPeers().some((p) => p.equals(peerId));
|
107
|
+
if (hasConnection) {
|
108
|
+
log.info(`Skipping peer ${peerId} - already connected`);
|
109
|
+
return true;
|
110
|
+
}
|
111
|
+
|
112
|
+
const lastDialed = this.dialHistory.get(peerId.toString());
|
113
|
+
if (lastDialed && Date.now() - lastDialed < 10_000) {
|
114
|
+
log.info(
|
115
|
+
`Skipping peer ${peerId} - already dialed in the last 10 seconds`
|
116
|
+
);
|
117
|
+
return true;
|
118
|
+
}
|
119
|
+
|
120
|
+
try {
|
121
|
+
const hasShardInfo = await this.shardReader.hasShardInfo(peerId);
|
122
|
+
if (!hasShardInfo) {
|
123
|
+
log.info(`Skipping peer ${peerId} - no shard info`);
|
124
|
+
return false;
|
125
|
+
}
|
126
|
+
|
127
|
+
const isOnSameShard = await this.shardReader.isPeerOnNetwork(peerId);
|
128
|
+
if (!isOnSameShard) {
|
129
|
+
log.info(`Skipping peer ${peerId} - not on same shard`);
|
130
|
+
return true;
|
131
|
+
}
|
132
|
+
|
133
|
+
return false;
|
134
|
+
} catch (error) {
|
135
|
+
log.error(`Error checking shard info for peer ${peerId}`, error);
|
136
|
+
return true; // Skip peer when there's an error
|
137
|
+
}
|
138
|
+
}
|
139
|
+
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import { Peer, PeerId, PeerInfo } from "@libp2p/interface";
|
2
|
+
import { Multiaddr } from "@multiformats/multiaddr";
|
3
|
+
import { Logger } from "@waku/utils";
|
4
|
+
import { Libp2p } from "libp2p";
|
5
|
+
|
6
|
+
import { Dialer } from "./dialer.js";
|
7
|
+
|
8
|
+
type Libp2pEventHandler<T> = (e: CustomEvent<T>) => void;
|
9
|
+
|
10
|
+
type DiscoveryDialerConstructorOptions = {
|
11
|
+
libp2p: Libp2p;
|
12
|
+
dialer: Dialer;
|
13
|
+
};
|
14
|
+
|
15
|
+
interface IDiscoveryDialer {
|
16
|
+
start(): void;
|
17
|
+
stop(): void;
|
18
|
+
}
|
19
|
+
|
20
|
+
const log = new Logger("discovery-dialer");
|
21
|
+
|
22
|
+
/**
|
23
|
+
* This class is responsible for dialing peers that are discovered by the libp2p node.
|
24
|
+
* Managing limits for the peers is out of scope for this class.
|
25
|
+
* Dialing after discovery is needed to identify the peer and get all other information: metadata, protocols, etc.
|
26
|
+
*/
|
27
|
+
export class DiscoveryDialer implements IDiscoveryDialer {
|
28
|
+
private readonly libp2p: Libp2p;
|
29
|
+
private readonly dialer: Dialer;
|
30
|
+
|
31
|
+
public constructor(options: DiscoveryDialerConstructorOptions) {
|
32
|
+
this.libp2p = options.libp2p;
|
33
|
+
this.dialer = options.dialer;
|
34
|
+
|
35
|
+
this.onPeerDiscovery = this.onPeerDiscovery.bind(this);
|
36
|
+
}
|
37
|
+
|
38
|
+
public start(): void {
|
39
|
+
this.libp2p.addEventListener(
|
40
|
+
"peer:discovery",
|
41
|
+
this.onPeerDiscovery as Libp2pEventHandler<PeerInfo>
|
42
|
+
);
|
43
|
+
}
|
44
|
+
|
45
|
+
public stop(): void {
|
46
|
+
this.libp2p.removeEventListener(
|
47
|
+
"peer:discovery",
|
48
|
+
this.onPeerDiscovery as Libp2pEventHandler<PeerInfo>
|
49
|
+
);
|
50
|
+
}
|
51
|
+
|
52
|
+
private async onPeerDiscovery(event: CustomEvent<PeerInfo>): Promise<void> {
|
53
|
+
const peerId = event.detail.id;
|
54
|
+
log.info(`Discovered new peer: ${peerId}`);
|
55
|
+
|
56
|
+
try {
|
57
|
+
await this.updatePeerStore(peerId, event.detail.multiaddrs);
|
58
|
+
await this.dialer.dial(peerId);
|
59
|
+
} catch (error) {
|
60
|
+
log.error(`Error dialing peer ${peerId}`, error);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
private async updatePeerStore(
|
65
|
+
peerId: PeerId,
|
66
|
+
multiaddrs: Multiaddr[]
|
67
|
+
): Promise<void> {
|
68
|
+
try {
|
69
|
+
log.info(`Updating peer store for ${peerId}`);
|
70
|
+
const peer = await this.getPeer(peerId);
|
71
|
+
|
72
|
+
if (!peer) {
|
73
|
+
log.info(`Peer ${peerId} not found in store, saving`);
|
74
|
+
await this.libp2p.peerStore.save(peerId, {
|
75
|
+
multiaddrs: multiaddrs
|
76
|
+
});
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
80
|
+
const hasSameAddr = multiaddrs.every((addr) =>
|
81
|
+
peer.addresses.some((a) => a.multiaddr.equals(addr))
|
82
|
+
);
|
83
|
+
|
84
|
+
if (hasSameAddr) {
|
85
|
+
log.info(`Peer ${peerId} has same addresses in peer store, skipping`);
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
|
89
|
+
log.info(`Merging peer ${peerId} addresses in peer store`);
|
90
|
+
await this.libp2p.peerStore.merge(peerId, {
|
91
|
+
multiaddrs: multiaddrs
|
92
|
+
});
|
93
|
+
} catch (error) {
|
94
|
+
log.error(`Error updating peer store for ${peerId}`, error);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
private async getPeer(peerId: PeerId): Promise<Peer | undefined> {
|
99
|
+
try {
|
100
|
+
return await this.libp2p.peerStore.get(peerId);
|
101
|
+
} catch (error) {
|
102
|
+
log.error(`Error getting peer info for ${peerId}`, error);
|
103
|
+
return undefined;
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import type { PeerId } from "@libp2p/interface";
|
2
|
-
import type { IRelay, Libp2p
|
2
|
+
import type { IEncoder, IRelay, Libp2p } from "@waku/interfaces";
|
3
3
|
import { Logger, pubsubTopicToSingleShardInfo } from "@waku/utils";
|
4
4
|
import { utf8ToBytes } from "@waku/utils/bytes";
|
5
5
|
|
@@ -19,7 +19,12 @@ type CreateKeepAliveManagerOptions = {
|
|
19
19
|
relay?: IRelay;
|
20
20
|
};
|
21
21
|
|
22
|
-
|
22
|
+
interface IKeepAliveManager {
|
23
|
+
start(): void;
|
24
|
+
stop(): void;
|
25
|
+
}
|
26
|
+
|
27
|
+
export class KeepAliveManager implements IKeepAliveManager {
|
23
28
|
private readonly relay?: IRelay;
|
24
29
|
private readonly libp2p: Libp2p;
|
25
30
|
|
@@ -27,7 +32,7 @@ export class KeepAliveManager {
|
|
27
32
|
|
28
33
|
private pingKeepAliveTimers: Map<string, ReturnType<typeof setInterval>> =
|
29
34
|
new Map();
|
30
|
-
private relayKeepAliveTimers: Map<
|
35
|
+
private relayKeepAliveTimers: Map<string, ReturnType<typeof setInterval>[]> =
|
31
36
|
new Map();
|
32
37
|
|
33
38
|
public constructor({
|
@@ -38,122 +43,184 @@ export class KeepAliveManager {
|
|
38
43
|
this.options = options;
|
39
44
|
this.relay = relay;
|
40
45
|
this.libp2p = libp2p;
|
46
|
+
|
47
|
+
this.onPeerConnect = this.onPeerConnect.bind(this);
|
48
|
+
this.onPeerDisconnect = this.onPeerDisconnect.bind(this);
|
41
49
|
}
|
42
50
|
|
43
|
-
public start(
|
44
|
-
|
45
|
-
this.
|
51
|
+
public start(): void {
|
52
|
+
this.libp2p.addEventListener("peer:connect", this.onPeerConnect);
|
53
|
+
this.libp2p.addEventListener("peer:disconnect", this.onPeerDisconnect);
|
54
|
+
}
|
46
55
|
|
47
|
-
|
48
|
-
|
56
|
+
public stop(): void {
|
57
|
+
this.libp2p.removeEventListener("peer:connect", this.onPeerConnect);
|
58
|
+
this.libp2p.removeEventListener("peer:disconnect", this.onPeerDisconnect);
|
49
59
|
|
50
|
-
const
|
60
|
+
for (const timer of this.pingKeepAliveTimers.values()) {
|
61
|
+
clearInterval(timer);
|
62
|
+
}
|
51
63
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
void (async () => {
|
57
|
-
let ping: number;
|
58
|
-
try {
|
59
|
-
// ping the peer for keep alive
|
60
|
-
// also update the peer store with the latency
|
61
|
-
try {
|
62
|
-
ping = await this.libp2p.services.ping.ping(peerId);
|
63
|
-
log.info(`Ping succeeded (${peerIdStr})`, ping);
|
64
|
-
} catch (error) {
|
65
|
-
log.error(`Ping failed for peer (${peerIdStr}).
|
66
|
-
Next ping will be attempted in ${pingPeriodSecs} seconds.
|
67
|
-
`);
|
68
|
-
return;
|
69
|
-
}
|
70
|
-
|
71
|
-
try {
|
72
|
-
await this.libp2p.peerStore.merge(peerId, {
|
73
|
-
metadata: {
|
74
|
-
ping: utf8ToBytes(ping.toString())
|
75
|
-
}
|
76
|
-
});
|
77
|
-
} catch (e) {
|
78
|
-
log.error("Failed to update ping", e);
|
79
|
-
}
|
80
|
-
} catch (e) {
|
81
|
-
log.error(`Ping failed (${peerIdStr})`, e);
|
82
|
-
}
|
83
|
-
})();
|
84
|
-
}, pingPeriodSecs * 1000);
|
85
|
-
|
86
|
-
this.pingKeepAliveTimers.set(peerIdStr, interval);
|
64
|
+
for (const timerArray of this.relayKeepAliveTimers.values()) {
|
65
|
+
for (const timer of timerArray) {
|
66
|
+
clearInterval(timer);
|
67
|
+
}
|
87
68
|
}
|
88
69
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
70
|
+
this.pingKeepAliveTimers.clear();
|
71
|
+
this.relayKeepAliveTimers.clear();
|
72
|
+
}
|
73
|
+
|
74
|
+
private onPeerConnect(evt: CustomEvent<PeerId>): void {
|
75
|
+
const peerId = evt.detail;
|
76
|
+
this.startPingForPeer(peerId);
|
77
|
+
}
|
78
|
+
|
79
|
+
private onPeerDisconnect(evt: CustomEvent<PeerId>): void {
|
80
|
+
const peerId = evt.detail;
|
81
|
+
this.stopPingForPeer(peerId);
|
82
|
+
}
|
83
|
+
|
84
|
+
private startPingForPeer(peerId: PeerId): void {
|
85
|
+
// Just in case a timer already exists for this peer
|
86
|
+
this.stopPingForPeer(peerId);
|
87
|
+
|
88
|
+
this.startLibp2pPing(peerId);
|
89
|
+
this.startRelayPing(peerId);
|
90
|
+
}
|
91
|
+
|
92
|
+
private stopPingForPeer(peerId: PeerId): void {
|
93
|
+
this.stopLibp2pPing(peerId);
|
94
|
+
this.stopRelayPing(peerId);
|
95
|
+
}
|
96
|
+
|
97
|
+
private startLibp2pPing(peerId: PeerId): void {
|
98
|
+
if (this.options.pingKeepAlive === 0) {
|
99
|
+
log.warn(
|
100
|
+
`Ping keep alive is disabled pingKeepAlive:${this.options.pingKeepAlive}, skipping start for libp2p ping`
|
95
101
|
);
|
96
|
-
|
102
|
+
return;
|
97
103
|
}
|
98
|
-
}
|
99
104
|
|
100
|
-
public stop(peerId: PeerId): void {
|
101
105
|
const peerIdStr = peerId.toString();
|
102
106
|
|
103
107
|
if (this.pingKeepAliveTimers.has(peerIdStr)) {
|
104
|
-
|
105
|
-
|
108
|
+
log.warn(
|
109
|
+
`Ping already started for peer: ${peerIdStr}, skipping start for libp2p ping`
|
110
|
+
);
|
111
|
+
return;
|
106
112
|
}
|
107
113
|
|
108
|
-
|
109
|
-
this.
|
110
|
-
|
111
|
-
|
114
|
+
const interval = setInterval(() => {
|
115
|
+
void this.pingLibp2p(peerId);
|
116
|
+
}, this.options.pingKeepAlive * 1000);
|
117
|
+
|
118
|
+
this.pingKeepAliveTimers.set(peerIdStr, interval);
|
112
119
|
}
|
113
120
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
121
|
+
private stopLibp2pPing(peerId: PeerId): void {
|
122
|
+
const peerIdStr = peerId.toString();
|
123
|
+
|
124
|
+
if (!this.pingKeepAliveTimers.has(peerIdStr)) {
|
125
|
+
log.warn(
|
126
|
+
`Ping not started for peer: ${peerIdStr}, skipping stop for ping`
|
127
|
+
);
|
128
|
+
return;
|
120
129
|
}
|
121
130
|
|
122
|
-
this.pingKeepAliveTimers.
|
123
|
-
this.
|
131
|
+
clearInterval(this.pingKeepAliveTimers.get(peerIdStr));
|
132
|
+
this.pingKeepAliveTimers.delete(peerIdStr);
|
124
133
|
}
|
125
134
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
135
|
+
private startRelayPing(peerId: PeerId): void {
|
136
|
+
if (!this.relay) {
|
137
|
+
return;
|
138
|
+
}
|
139
|
+
|
140
|
+
if (this.options.relayKeepAlive === 0) {
|
141
|
+
log.warn(
|
142
|
+
`Relay keep alive is disabled relayKeepAlive:${this.options.relayKeepAlive}, skipping start for relay ping`
|
143
|
+
);
|
144
|
+
return;
|
145
|
+
}
|
146
|
+
|
147
|
+
if (this.relayKeepAliveTimers.has(peerId.toString())) {
|
148
|
+
log.warn(
|
149
|
+
`Relay ping already started for peer: ${peerId.toString()}, skipping start for relay ping`
|
150
|
+
);
|
151
|
+
return;
|
152
|
+
}
|
131
153
|
|
132
|
-
private scheduleRelayPings(
|
133
|
-
relay: IRelay,
|
134
|
-
relayPeriodSecs: number,
|
135
|
-
peerIdStr: PeerIdStr
|
136
|
-
): NodeJS.Timeout[] {
|
137
|
-
// send a ping message to each PubsubTopic the peer is part of
|
138
154
|
const intervals: NodeJS.Timeout[] = [];
|
139
|
-
|
140
|
-
|
141
|
-
|
155
|
+
|
156
|
+
for (const topic of this.relay.pubsubTopics) {
|
157
|
+
const meshPeers = this.relay.getMeshPeers(topic);
|
158
|
+
|
159
|
+
if (!meshPeers.includes(peerId.toString())) {
|
160
|
+
log.warn(
|
161
|
+
`Peer: ${peerId.toString()} is not in the mesh for topic: ${topic}, skipping start for relay ping`
|
162
|
+
);
|
163
|
+
continue;
|
164
|
+
}
|
142
165
|
|
143
166
|
const encoder = createEncoder({
|
144
167
|
pubsubTopicShardInfo: pubsubTopicToSingleShardInfo(topic),
|
145
168
|
contentTopic: RelayPingContentTopic,
|
146
169
|
ephemeral: true
|
147
170
|
});
|
171
|
+
|
148
172
|
const interval = setInterval(() => {
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
.catch((e) => log.error("Failed to send relay ping", e));
|
153
|
-
}, relayPeriodSecs * 1000);
|
173
|
+
void this.pingRelay(encoder);
|
174
|
+
}, this.options.relayKeepAlive * 1000);
|
175
|
+
|
154
176
|
intervals.push(interval);
|
155
177
|
}
|
156
178
|
|
157
|
-
|
179
|
+
this.relayKeepAliveTimers.set(peerId.toString(), intervals);
|
180
|
+
}
|
181
|
+
|
182
|
+
private stopRelayPing(peerId: PeerId): void {
|
183
|
+
if (!this.relay) {
|
184
|
+
return;
|
185
|
+
}
|
186
|
+
|
187
|
+
const peerIdStr = peerId.toString();
|
188
|
+
|
189
|
+
if (!this.relayKeepAliveTimers.has(peerIdStr)) {
|
190
|
+
log.warn(
|
191
|
+
`Relay ping not started for peer: ${peerIdStr}, skipping stop for relay ping`
|
192
|
+
);
|
193
|
+
return;
|
194
|
+
}
|
195
|
+
|
196
|
+
this.relayKeepAliveTimers.get(peerIdStr)?.map(clearInterval);
|
197
|
+
this.relayKeepAliveTimers.delete(peerIdStr);
|
198
|
+
}
|
199
|
+
|
200
|
+
private async pingRelay(encoder: IEncoder): Promise<void> {
|
201
|
+
try {
|
202
|
+
log.info("Sending Waku Relay ping message");
|
203
|
+
await this.relay!.send(encoder, { payload: new Uint8Array([1]) });
|
204
|
+
} catch (e) {
|
205
|
+
log.error("Failed to send relay ping", e);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
private async pingLibp2p(peerId: PeerId): Promise<void> {
|
210
|
+
try {
|
211
|
+
log.info(`Pinging libp2p peer (${peerId.toString()})`);
|
212
|
+
const ping = await this.libp2p.services.ping.ping(peerId);
|
213
|
+
|
214
|
+
log.info(`Ping succeeded (${peerId.toString()})`, ping);
|
215
|
+
|
216
|
+
await this.libp2p.peerStore.merge(peerId, {
|
217
|
+
metadata: {
|
218
|
+
ping: utf8ToBytes(ping.toString())
|
219
|
+
}
|
220
|
+
});
|
221
|
+
log.info(`Ping updated for peer (${peerId.toString()})`);
|
222
|
+
} catch (e) {
|
223
|
+
log.error(`Ping failed for peer (${peerId.toString()})`, e);
|
224
|
+
}
|
158
225
|
}
|
159
226
|
}
|
@@ -0,0 +1,112 @@
|
|
1
|
+
import { IWakuEventEmitter, Libp2p } from "@waku/interfaces";
|
2
|
+
|
3
|
+
type NetworkMonitorConstructorOptions = {
|
4
|
+
libp2p: Libp2p;
|
5
|
+
events: IWakuEventEmitter;
|
6
|
+
};
|
7
|
+
|
8
|
+
interface INetworkMonitor {
|
9
|
+
start(): void;
|
10
|
+
stop(): void;
|
11
|
+
isConnected(): boolean;
|
12
|
+
isP2PConnected(): boolean;
|
13
|
+
isBrowserConnected(): boolean;
|
14
|
+
}
|
15
|
+
|
16
|
+
export class NetworkMonitor implements INetworkMonitor {
|
17
|
+
private readonly libp2p: Libp2p;
|
18
|
+
private readonly events: IWakuEventEmitter;
|
19
|
+
|
20
|
+
private isNetworkConnected: boolean = false;
|
21
|
+
|
22
|
+
public constructor(options: NetworkMonitorConstructorOptions) {
|
23
|
+
this.libp2p = options.libp2p;
|
24
|
+
this.events = options.events;
|
25
|
+
|
26
|
+
this.onConnectedEvent = this.onConnectedEvent.bind(this);
|
27
|
+
this.onDisconnectedEvent = this.onDisconnectedEvent.bind(this);
|
28
|
+
this.dispatchNetworkEvent = this.dispatchNetworkEvent.bind(this);
|
29
|
+
}
|
30
|
+
|
31
|
+
public start(): void {
|
32
|
+
this.libp2p.addEventListener("peer:connect", this.onConnectedEvent);
|
33
|
+
this.libp2p.addEventListener("peer:disconnect", this.onDisconnectedEvent);
|
34
|
+
|
35
|
+
try {
|
36
|
+
globalThis.addEventListener("online", this.dispatchNetworkEvent);
|
37
|
+
globalThis.addEventListener("offline", this.dispatchNetworkEvent);
|
38
|
+
} catch (err) {
|
39
|
+
// ignore
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
public stop(): void {
|
44
|
+
this.libp2p.removeEventListener("peer:connect", this.onConnectedEvent);
|
45
|
+
this.libp2p.removeEventListener(
|
46
|
+
"peer:disconnect",
|
47
|
+
this.onDisconnectedEvent
|
48
|
+
);
|
49
|
+
|
50
|
+
try {
|
51
|
+
globalThis.removeEventListener("online", this.dispatchNetworkEvent);
|
52
|
+
globalThis.removeEventListener("offline", this.dispatchNetworkEvent);
|
53
|
+
} catch (err) {
|
54
|
+
// ignore
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Returns true if the node is connected to the network via libp2p and browser.
|
60
|
+
*/
|
61
|
+
public isConnected(): boolean {
|
62
|
+
if (!this.isBrowserConnected()) {
|
63
|
+
return false;
|
64
|
+
}
|
65
|
+
|
66
|
+
return this.isP2PConnected();
|
67
|
+
}
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Returns true if the node is connected to the network via libp2p.
|
71
|
+
*/
|
72
|
+
public isP2PConnected(): boolean {
|
73
|
+
return this.isNetworkConnected;
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Returns true if the node is connected to the network via browser.
|
78
|
+
*/
|
79
|
+
public isBrowserConnected(): boolean {
|
80
|
+
try {
|
81
|
+
if (globalThis?.navigator && !globalThis?.navigator?.onLine) {
|
82
|
+
return false;
|
83
|
+
}
|
84
|
+
} catch (err) {
|
85
|
+
// ignore
|
86
|
+
}
|
87
|
+
|
88
|
+
return true;
|
89
|
+
}
|
90
|
+
|
91
|
+
private onConnectedEvent(): void {
|
92
|
+
if (!this.isNetworkConnected) {
|
93
|
+
this.isNetworkConnected = true;
|
94
|
+
this.dispatchNetworkEvent();
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
private onDisconnectedEvent(): void {
|
99
|
+
if (this.isNetworkConnected && this.libp2p.getConnections().length === 0) {
|
100
|
+
this.isNetworkConnected = false;
|
101
|
+
this.dispatchNetworkEvent();
|
102
|
+
}
|
103
|
+
}
|
104
|
+
|
105
|
+
private dispatchNetworkEvent(): void {
|
106
|
+
this.events.dispatchEvent(
|
107
|
+
new CustomEvent<boolean>("waku:connection", {
|
108
|
+
detail: this.isConnected()
|
109
|
+
})
|
110
|
+
);
|
111
|
+
}
|
112
|
+
}
|