@waku/core 0.0.37-7a9850d.0 → 0.0.37-c7682ea.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.
Files changed (45) hide show
  1. package/bundle/index.js +4745 -899
  2. package/bundle/lib/message/version_0.js +1 -1
  3. package/bundle/{version_0-9DPFjcJG.js → version_0-Bc0h7ah2.js} +312 -33
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/lib/connection_manager/connection_limiter.d.ts +37 -0
  6. package/dist/lib/connection_manager/connection_limiter.js +124 -0
  7. package/dist/lib/connection_manager/connection_limiter.js.map +1 -0
  8. package/dist/lib/connection_manager/connection_manager.d.ts +20 -105
  9. package/dist/lib/connection_manager/connection_manager.js +83 -508
  10. package/dist/lib/connection_manager/connection_manager.js.map +1 -1
  11. package/dist/lib/connection_manager/dialer.d.ts +28 -0
  12. package/dist/lib/connection_manager/dialer.js +100 -0
  13. package/dist/lib/connection_manager/dialer.js.map +1 -0
  14. package/dist/lib/connection_manager/discovery_dialer.d.ts +26 -0
  15. package/dist/lib/connection_manager/discovery_dialer.js +68 -0
  16. package/dist/lib/connection_manager/discovery_dialer.js.map +1 -0
  17. package/dist/lib/connection_manager/keep_alive_manager.d.ts +17 -7
  18. package/dist/lib/connection_manager/keep_alive_manager.js +110 -74
  19. package/dist/lib/connection_manager/keep_alive_manager.js.map +1 -1
  20. package/dist/lib/connection_manager/network_monitor.d.ts +36 -0
  21. package/dist/lib/connection_manager/network_monitor.js +81 -0
  22. package/dist/lib/connection_manager/network_monitor.js.map +1 -0
  23. package/dist/lib/connection_manager/shard_reader.d.ts +28 -0
  24. package/dist/lib/connection_manager/shard_reader.js +70 -0
  25. package/dist/lib/connection_manager/shard_reader.js.map +1 -0
  26. package/dist/lib/connection_manager/utils.d.ts +16 -1
  27. package/dist/lib/connection_manager/utils.js +23 -0
  28. package/dist/lib/connection_manager/utils.js.map +1 -1
  29. package/dist/lib/filter/filter.d.ts +2 -3
  30. package/dist/lib/filter/filter.js +5 -25
  31. package/dist/lib/filter/filter.js.map +1 -1
  32. package/dist/lib/light_push/light_push.d.ts +2 -3
  33. package/dist/lib/light_push/light_push.js +1 -3
  34. package/dist/lib/light_push/light_push.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/lib/connection_manager/connection_limiter.ts +201 -0
  37. package/src/lib/connection_manager/connection_manager.ts +104 -669
  38. package/src/lib/connection_manager/dialer.ts +139 -0
  39. package/src/lib/connection_manager/discovery_dialer.ts +106 -0
  40. package/src/lib/connection_manager/keep_alive_manager.ts +154 -87
  41. package/src/lib/connection_manager/network_monitor.ts +112 -0
  42. package/src/lib/connection_manager/shard_reader.ts +134 -0
  43. package/src/lib/connection_manager/utils.ts +27 -1
  44. package/src/lib/filter/filter.ts +3 -28
  45. 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, PeerIdStr } from "@waku/interfaces";
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
- export class KeepAliveManager {
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<PeerId, ReturnType<typeof setInterval>[]> =
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(peerId: PeerId): void {
44
- // Just in case a timer already exists for this peer
45
- this.stop(peerId);
51
+ public start(): void {
52
+ this.libp2p.addEventListener("peer:connect", this.onPeerConnect);
53
+ this.libp2p.addEventListener("peer:disconnect", this.onPeerDisconnect);
54
+ }
46
55
 
47
- const { pingKeepAlive: pingPeriodSecs, relayKeepAlive: relayPeriodSecs } =
48
- this.options;
56
+ public stop(): void {
57
+ this.libp2p.removeEventListener("peer:connect", this.onPeerConnect);
58
+ this.libp2p.removeEventListener("peer:disconnect", this.onPeerDisconnect);
49
59
 
50
- const peerIdStr = peerId.toString();
60
+ for (const timer of this.pingKeepAliveTimers.values()) {
61
+ clearInterval(timer);
62
+ }
51
63
 
52
- // Ping the peer every pingPeriodSecs seconds
53
- // if pingPeriodSecs is 0, don't ping the peer
54
- if (pingPeriodSecs !== 0) {
55
- const interval = setInterval(() => {
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
- const relay = this.relay;
90
- if (relay && relayPeriodSecs !== 0) {
91
- const intervals = this.scheduleRelayPings(
92
- relay,
93
- relayPeriodSecs,
94
- peerId.toString()
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
- this.relayKeepAliveTimers.set(peerId, intervals);
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
- clearInterval(this.pingKeepAliveTimers.get(peerIdStr));
105
- this.pingKeepAliveTimers.delete(peerIdStr);
108
+ log.warn(
109
+ `Ping already started for peer: ${peerIdStr}, skipping start for libp2p ping`
110
+ );
111
+ return;
106
112
  }
107
113
 
108
- if (this.relayKeepAliveTimers.has(peerId)) {
109
- this.relayKeepAliveTimers.get(peerId)?.map(clearInterval);
110
- this.relayKeepAliveTimers.delete(peerId);
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
- public stopAll(): void {
115
- for (const timer of [
116
- ...Object.values(this.pingKeepAliveTimers),
117
- ...Object.values(this.relayKeepAliveTimers)
118
- ]) {
119
- clearInterval(timer);
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.clear();
123
- this.relayKeepAliveTimers.clear();
131
+ clearInterval(this.pingKeepAliveTimers.get(peerIdStr));
132
+ this.pingKeepAliveTimers.delete(peerIdStr);
124
133
  }
125
134
 
126
- public connectionsExist(): boolean {
127
- return (
128
- this.pingKeepAliveTimers.size > 0 || this.relayKeepAliveTimers.size > 0
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
- for (const topic of relay.pubsubTopics) {
140
- const meshPeers = relay.getMeshPeers(topic);
141
- if (!meshPeers.includes(peerIdStr)) continue;
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
- log.info("Sending Waku Relay ping message");
150
- relay
151
- .send(encoder, { payload: new Uint8Array([1]) })
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
- return intervals;
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
+ }