@waku/core 0.0.36-f911bf8.0 → 0.0.37-2ed5ddc.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 (79) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bundle/index.js +5647 -1359
  3. package/bundle/lib/message/version_0.js +1 -2
  4. package/bundle/{version_0-CiYGrPc2.js → version_0-Bc0h7ah2.js} +1886 -31
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/connection_manager/connection_limiter.d.ts +35 -0
  10. package/dist/lib/connection_manager/connection_limiter.js +103 -0
  11. package/dist/lib/connection_manager/connection_limiter.js.map +1 -0
  12. package/dist/lib/connection_manager/connection_manager.d.ts +19 -104
  13. package/dist/lib/connection_manager/connection_manager.js +64 -499
  14. package/dist/lib/connection_manager/connection_manager.js.map +1 -1
  15. package/dist/lib/connection_manager/discovery_dialer.d.ts +32 -0
  16. package/dist/lib/connection_manager/discovery_dialer.js +131 -0
  17. package/dist/lib/connection_manager/discovery_dialer.js.map +1 -0
  18. package/dist/lib/connection_manager/keep_alive_manager.d.ts +17 -7
  19. package/dist/lib/connection_manager/keep_alive_manager.js +110 -74
  20. package/dist/lib/connection_manager/keep_alive_manager.js.map +1 -1
  21. package/dist/lib/connection_manager/network_monitor.d.ts +22 -0
  22. package/dist/lib/connection_manager/network_monitor.js +63 -0
  23. package/dist/lib/connection_manager/network_monitor.js.map +1 -0
  24. package/dist/lib/connection_manager/shard_reader.d.ts +28 -0
  25. package/dist/lib/connection_manager/shard_reader.js +70 -0
  26. package/dist/lib/connection_manager/shard_reader.js.map +1 -0
  27. package/dist/lib/connection_manager/utils.d.ts +16 -1
  28. package/dist/lib/connection_manager/utils.js +23 -0
  29. package/dist/lib/connection_manager/utils.js.map +1 -1
  30. package/dist/lib/filter/filter.d.ts +7 -5
  31. package/dist/lib/filter/filter.js +14 -11
  32. package/dist/lib/filter/filter.js.map +1 -1
  33. package/dist/lib/light_push/light_push.d.ts +5 -5
  34. package/dist/lib/light_push/light_push.js +7 -7
  35. package/dist/lib/light_push/light_push.js.map +1 -1
  36. package/dist/lib/message/version_0.d.ts +3 -4
  37. package/dist/lib/message/version_0.js +1 -4
  38. package/dist/lib/message/version_0.js.map +1 -1
  39. package/dist/lib/message_hash/index.d.ts +1 -0
  40. package/dist/lib/message_hash/index.js +2 -0
  41. package/dist/lib/message_hash/index.js.map +1 -0
  42. package/dist/lib/message_hash/message_hash.d.ts +52 -0
  43. package/dist/lib/message_hash/message_hash.js +84 -0
  44. package/dist/lib/message_hash/message_hash.js.map +1 -0
  45. package/dist/lib/metadata/metadata.js +6 -4
  46. package/dist/lib/metadata/metadata.js.map +1 -1
  47. package/dist/lib/store/rpc.js +16 -10
  48. package/dist/lib/store/rpc.js.map +1 -1
  49. package/dist/lib/store/store.d.ts +5 -5
  50. package/dist/lib/store/store.js +19 -9
  51. package/dist/lib/store/store.js.map +1 -1
  52. package/dist/lib/stream_manager/stream_manager.d.ts +3 -4
  53. package/dist/lib/stream_manager/stream_manager.js +6 -8
  54. package/dist/lib/stream_manager/stream_manager.js.map +1 -1
  55. package/package.json +1 -1
  56. package/src/index.ts +2 -0
  57. package/src/lib/connection_manager/connection_limiter.ts +161 -0
  58. package/src/lib/connection_manager/connection_manager.ts +87 -668
  59. package/src/lib/connection_manager/discovery_dialer.ts +195 -0
  60. package/src/lib/connection_manager/keep_alive_manager.ts +154 -87
  61. package/src/lib/connection_manager/network_monitor.ts +88 -0
  62. package/src/lib/connection_manager/shard_reader.ts +134 -0
  63. package/src/lib/connection_manager/utils.ts +27 -1
  64. package/src/lib/filter/filter.ts +26 -15
  65. package/src/lib/light_push/light_push.ts +9 -10
  66. package/src/lib/message/version_0.ts +3 -7
  67. package/src/lib/message_hash/index.ts +1 -0
  68. package/src/lib/message_hash/message_hash.ts +106 -0
  69. package/src/lib/metadata/metadata.ts +8 -5
  70. package/src/lib/store/rpc.ts +23 -19
  71. package/src/lib/store/store.ts +22 -11
  72. package/src/lib/stream_manager/stream_manager.ts +8 -6
  73. package/bundle/base_protocol-DvQrudwy.js +0 -152
  74. package/bundle/index-CTo1my9M.js +0 -1543
  75. package/bundle/lib/base_protocol.js +0 -2
  76. package/dist/lib/base_protocol.d.ts +0 -18
  77. package/dist/lib/base_protocol.js +0 -25
  78. package/dist/lib/base_protocol.js.map +0 -1
  79. package/src/lib/base_protocol.ts +0 -44
@@ -1,167 +1,56 @@
1
- import {
2
- type Connection,
3
- isPeerId,
4
- type Peer,
5
- type PeerId,
6
- type PeerInfo,
7
- type PeerStore,
8
- type Stream,
9
- TypedEventEmitter
10
- } from "@libp2p/interface";
11
- import { Multiaddr, multiaddr, MultiaddrInput } from "@multiformats/multiaddr";
1
+ import { type Peer, type PeerId, type Stream } from "@libp2p/interface";
2
+ import { MultiaddrInput } from "@multiformats/multiaddr";
12
3
  import {
13
4
  ConnectionManagerOptions,
14
- DiscoveryTrigger,
15
- DNS_DISCOVERY_TAG,
16
- EConnectionStateEvents,
17
- EPeersByDiscoveryEvents,
18
5
  IConnectionManager,
19
- IConnectionStateEvents,
20
- IPeersByDiscoveryEvents,
21
6
  IRelay,
22
- PeersByDiscoveryResult,
23
- PubsubTopic,
24
- ShardInfo
7
+ IWakuEventEmitter,
8
+ NetworkConfig,
9
+ PubsubTopic
25
10
  } from "@waku/interfaces";
26
- import { Libp2p, Tags } from "@waku/interfaces";
27
- import { decodeRelayShard, shardInfoToPubsubTopics } from "@waku/utils";
11
+ import { Libp2p } from "@waku/interfaces";
28
12
  import { Logger } from "@waku/utils";
29
13
 
14
+ import { ConnectionLimiter } from "./connection_limiter.js";
15
+ import { DiscoveryDialer } from "./discovery_dialer.js";
30
16
  import { KeepAliveManager } from "./keep_alive_manager.js";
31
- import { getPeerPing } from "./utils.js";
17
+ import { NetworkMonitor } from "./network_monitor.js";
18
+ import { ShardReader } from "./shard_reader.js";
19
+ import { getPeerPing, mapToPeerId, mapToPeerIdOrMultiaddr } from "./utils.js";
32
20
 
33
21
  const log = new Logger("connection-manager");
34
22
 
35
23
  const DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED = 1;
36
- const DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER = 3;
37
- const DEFAULT_MAX_PARALLEL_DIALS = 3;
38
-
39
24
  const DEFAULT_PING_KEEP_ALIVE_SEC = 5 * 60;
40
25
  const DEFAULT_RELAY_KEEP_ALIVE_SEC = 5 * 60;
41
26
 
42
27
  type ConnectionManagerConstructorOptions = {
43
28
  libp2p: Libp2p;
29
+ events: IWakuEventEmitter;
44
30
  pubsubTopics: PubsubTopic[];
31
+ networkConfig: NetworkConfig;
45
32
  relay?: IRelay;
46
33
  config?: Partial<ConnectionManagerOptions>;
47
34
  };
48
35
 
49
- export class ConnectionManager
50
- extends TypedEventEmitter<IPeersByDiscoveryEvents & IConnectionStateEvents>
51
- implements IConnectionManager
52
- {
53
- // TODO(weboko): make it private
54
- public readonly pubsubTopics: PubsubTopic[];
36
+ export class ConnectionManager implements IConnectionManager {
37
+ private readonly pubsubTopics: PubsubTopic[];
38
+
39
+ private readonly keepAliveManager: KeepAliveManager;
40
+ private readonly discoveryDialer: DiscoveryDialer;
41
+ private readonly shardReader: ShardReader;
42
+ private readonly networkMonitor: NetworkMonitor;
43
+ private readonly connectionLimiter: ConnectionLimiter;
55
44
 
56
- private keepAliveManager: KeepAliveManager;
57
45
  private options: ConnectionManagerOptions;
58
46
  private libp2p: Libp2p;
59
- private dialAttemptsForPeer: Map<string, number> = new Map();
60
- private dialErrorsForPeer: Map<string, any> = new Map();
61
-
62
- private currentActiveParallelDialCount = 0;
63
- private pendingPeerDialQueue: Array<PeerId> = [];
64
-
65
- private isP2PNetworkConnected: boolean = false;
66
-
67
- public isConnected(): boolean {
68
- if (globalThis?.navigator && !globalThis?.navigator?.onLine) {
69
- return false;
70
- }
71
-
72
- return this.isP2PNetworkConnected;
73
- }
74
-
75
- public stop(): void {
76
- this.keepAliveManager.stopAll();
77
- this.libp2p.removeEventListener(
78
- "peer:connect",
79
- this.onEventHandlers["peer:connect"]
80
- );
81
- this.libp2p.removeEventListener(
82
- "peer:disconnect",
83
- this.onEventHandlers["peer:disconnect"]
84
- );
85
- this.libp2p.removeEventListener(
86
- "peer:discovery",
87
- this.onEventHandlers["peer:discovery"]
88
- );
89
- this.stopNetworkStatusListener();
90
- }
91
-
92
- public async dropConnection(peerId: PeerId): Promise<void> {
93
- try {
94
- this.keepAliveManager.stop(peerId);
95
- await this.libp2p.hangUp(peerId);
96
- log.info(`Dropped connection with peer ${peerId.toString()}`);
97
- } catch (error) {
98
- log.error(
99
- `Error dropping connection with peer ${peerId.toString()} - ${error}`
100
- );
101
- }
102
- }
103
-
104
- public async getPeersByDiscovery(): Promise<PeersByDiscoveryResult> {
105
- const peersDiscovered = await this.libp2p.peerStore.all();
106
- const peersConnected = this.libp2p
107
- .getConnections()
108
- .map((conn) => conn.remotePeer);
109
-
110
- const peersDiscoveredByBootstrap: Peer[] = [];
111
- const peersDiscoveredByPeerExchange: Peer[] = [];
112
- const peersDiscoveredByLocal: Peer[] = [];
113
-
114
- const peersConnectedByBootstrap: Peer[] = [];
115
- const peersConnectedByPeerExchange: Peer[] = [];
116
- const peersConnectedByLocal: Peer[] = [];
117
-
118
- for (const peer of peersDiscovered) {
119
- const tags = await this.getTagNamesForPeer(peer.id);
120
-
121
- if (tags.includes(Tags.BOOTSTRAP)) {
122
- peersDiscoveredByBootstrap.push(peer);
123
- } else if (tags.includes(Tags.PEER_EXCHANGE)) {
124
- peersDiscoveredByPeerExchange.push(peer);
125
- } else if (tags.includes(Tags.LOCAL)) {
126
- peersDiscoveredByLocal.push(peer);
127
- }
128
- }
129
-
130
- for (const peerId of peersConnected) {
131
- const peer = await this.libp2p.peerStore.get(peerId);
132
- const tags = await this.getTagNamesForPeer(peerId);
133
-
134
- if (tags.includes(Tags.BOOTSTRAP)) {
135
- peersConnectedByBootstrap.push(peer);
136
- } else if (tags.includes(Tags.PEER_EXCHANGE)) {
137
- peersConnectedByPeerExchange.push(peer);
138
- } else if (tags.includes(Tags.LOCAL)) {
139
- peersConnectedByLocal.push(peer);
140
- }
141
- }
142
-
143
- return {
144
- DISCOVERED: {
145
- [Tags.BOOTSTRAP]: peersDiscoveredByBootstrap,
146
- [Tags.PEER_EXCHANGE]: peersDiscoveredByPeerExchange,
147
- [Tags.LOCAL]: peersDiscoveredByLocal
148
- },
149
- CONNECTED: {
150
- [Tags.BOOTSTRAP]: peersConnectedByBootstrap,
151
- [Tags.PEER_EXCHANGE]: peersConnectedByPeerExchange,
152
- [Tags.LOCAL]: peersConnectedByLocal
153
- }
154
- };
155
- }
156
47
 
157
48
  public constructor(options: ConnectionManagerConstructorOptions) {
158
- super();
159
49
  this.libp2p = options.libp2p;
160
50
  this.pubsubTopics = options.pubsubTopics;
51
+
161
52
  this.options = {
162
- maxDialAttemptsForPeer: DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER,
163
- maxBootstrapPeersAllowed: DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED,
164
- maxParallelDials: DEFAULT_MAX_PARALLEL_DIALS,
53
+ maxBootstrapPeers: DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED,
165
54
  pingKeepAlive: DEFAULT_PING_KEEP_ALIVE_SEC,
166
55
  relayKeepAlive: DEFAULT_RELAY_KEEP_ALIVE_SEC,
167
56
  ...options.config
@@ -176,575 +65,105 @@ export class ConnectionManager
176
65
  }
177
66
  });
178
67
 
179
- this.startEventListeners()
180
- .then(() => log.info(`Connection Manager is now running`))
181
- .catch((error) =>
182
- log.error(`Unexpected error while running service`, error)
183
- );
184
-
185
- // libp2p emits `peer:discovery` events during its initialization
186
- // which means that before the ConnectionManager is initialized, some peers may have been discovered
187
- // we will dial the peers in peerStore ONCE before we start to listen to the `peer:discovery` events within the ConnectionManager
188
- this.dialPeerStorePeers().catch((error) =>
189
- log.error(`Unexpected error while dialing peer store peers`, error)
190
- );
191
- }
192
-
193
- public async getConnectedPeers(codec?: string): Promise<Peer[]> {
194
- const peerIDs = this.libp2p.getPeers();
68
+ this.shardReader = new ShardReader({
69
+ libp2p: options.libp2p,
70
+ networkConfig: options.networkConfig
71
+ });
195
72
 
196
- if (peerIDs.length === 0) {
197
- return [];
198
- }
73
+ this.discoveryDialer = new DiscoveryDialer({
74
+ libp2p: options.libp2p,
75
+ shardReader: this.shardReader
76
+ });
199
77
 
200
- const peers = await Promise.all(
201
- peerIDs.map(async (id) => {
202
- try {
203
- return await this.libp2p.peerStore.get(id);
204
- } catch (e) {
205
- return null;
206
- }
207
- })
208
- );
78
+ this.networkMonitor = new NetworkMonitor({
79
+ libp2p: options.libp2p,
80
+ events: options.events
81
+ });
209
82
 
210
- return peers
211
- .filter((p) => !!p)
212
- .filter((p) => (codec ? (p as Peer).protocols.includes(codec) : true))
213
- .sort((left, right) => getPeerPing(left) - getPeerPing(right)) as Peer[];
83
+ this.connectionLimiter = new ConnectionLimiter({
84
+ libp2p: options.libp2p,
85
+ options: this.options
86
+ });
214
87
  }
215
88
 
216
- private async dialPeerStorePeers(): Promise<void> {
217
- const peerInfos = await this.libp2p.peerStore.all();
218
- const dialPromises = [];
219
- for (const peerInfo of peerInfos) {
220
- if (
221
- this.libp2p.getConnections().find((c) => c.remotePeer === peerInfo.id)
222
- )
223
- continue;
224
-
225
- dialPromises.push(this.attemptDial(peerInfo.id));
226
- }
227
- try {
228
- await Promise.all(dialPromises);
229
- } catch (error) {
230
- log.error(`Unexpected error while dialing peer store peers`, error);
231
- }
89
+ public start(): void {
90
+ this.networkMonitor.start();
91
+ this.discoveryDialer.start();
92
+ this.keepAliveManager.start();
93
+ this.connectionLimiter.start();
232
94
  }
233
95
 
234
- private async startEventListeners(): Promise<void> {
235
- this.startPeerDiscoveryListener();
236
- this.startPeerConnectionListener();
237
- this.startPeerDisconnectionListener();
238
-
239
- this.startNetworkStatusListener();
96
+ public stop(): void {
97
+ this.networkMonitor.stop();
98
+ this.discoveryDialer.stop();
99
+ this.keepAliveManager.stop();
100
+ this.connectionLimiter.stop();
240
101
  }
241
102
 
242
- /**
243
- * Attempts to establish a connection with a peer and set up specified protocols.
244
- * The method handles both PeerId and Multiaddr inputs, manages connection attempts,
245
- * and maintains the connection state.
246
- *
247
- * The dialing process includes:
248
- * 1. Converting input to dialable peer info
249
- * 2. Managing parallel dial attempts
250
- * 3. Attempting to establish protocol-specific connections
251
- * 4. Handling connection failures and retries
252
- * 5. Updating the peer store and connection state
253
- *
254
- * @param {PeerId | MultiaddrInput} peer - The peer to connect to, either as a PeerId or multiaddr
255
- * @param {string[]} [protocolCodecs] - Optional array of protocol-specific codec strings to establish
256
- * (e.g., for LightPush, Filter, Store protocols)
257
- *
258
- * @throws {Error} If the multiaddr is missing a peer ID
259
- * @throws {Error} If the maximum dial attempts are reached and the peer cannot be dialed
260
- * @throws {Error} If there's an error deleting an undialable peer from the peer store
261
- *
262
- * @example
263
- * ```typescript
264
- * // Dial using PeerId
265
- * await connectionManager.dialPeer(peerId);
266
- *
267
- * // Dial using multiaddr with specific protocols
268
- * await connectionManager.dialPeer(multiaddr, [
269
- * "/vac/waku/relay/2.0.0",
270
- * "/vac/waku/lightpush/2.0.0-beta1"
271
- * ]);
272
- * ```
273
- *
274
- * @remarks
275
- * - The method implements exponential backoff through multiple dial attempts
276
- * - Maintains a queue for parallel dial attempts (limited by maxParallelDials)
277
- * - Integrates with the KeepAliveManager for connection maintenance
278
- * - Updates the peer store and connection state after successful/failed attempts
279
- * - If all dial attempts fail, triggers DNS discovery as a fallback
280
- */
281
- public async dialPeer(peer: PeerId | MultiaddrInput): Promise<Connection> {
282
- let connection: Connection | undefined;
283
- let peerId: PeerId | undefined;
284
- const peerDialInfo = this.getDialablePeerInfo(peer);
285
- const peerIdStr = isPeerId(peerDialInfo)
286
- ? peerDialInfo.toString()
287
- : peerDialInfo.getPeerId()!;
288
-
289
- this.currentActiveParallelDialCount += 1;
290
- let dialAttempt = 0;
291
- while (dialAttempt < this.options.maxDialAttemptsForPeer) {
292
- try {
293
- log.info(`Dialing peer ${peerDialInfo} on attempt ${dialAttempt + 1}`);
294
- connection = await this.libp2p.dial(peerDialInfo);
295
- peerId = connection.remotePeer;
296
-
297
- const tags = await this.getTagNamesForPeer(peerId);
298
- // add tag to connection describing discovery mechanism
299
- // don't add duplicate tags
300
- this.libp2p.getConnections(peerId).forEach((conn) => {
301
- conn.tags = Array.from(new Set([...conn.tags, ...tags]));
302
- });
303
-
304
- // instead of deleting the peer from the peer store, we set the dial attempt to -1
305
- // this helps us keep track of peers that have been dialed before
306
- this.dialAttemptsForPeer.set(peerId.toString(), -1);
307
-
308
- // Dialing succeeded, break the loop
309
- this.keepAliveManager.start(peerId);
310
- break;
311
- } catch (error) {
312
- if (error instanceof AggregateError) {
313
- // Handle AggregateError
314
- log.error(`Error dialing peer ${peerIdStr} - ${error.errors}`);
315
- } else {
316
- // Handle generic error
317
- log.error(
318
- `Error dialing peer ${peerIdStr} - ${(error as any).message}`
319
- );
320
- }
321
- this.dialErrorsForPeer.set(peerIdStr, error);
322
-
323
- dialAttempt++;
324
- this.dialAttemptsForPeer.set(peerIdStr, dialAttempt);
325
- }
326
- }
327
-
328
- // Always decrease the active dial count and process the dial queue
329
- this.currentActiveParallelDialCount--;
330
- this.processDialQueue();
331
-
332
- // If max dial attempts reached and dialing failed, delete the peer
333
- if (dialAttempt === this.options.maxDialAttemptsForPeer) {
334
- try {
335
- const error = this.dialErrorsForPeer.get(peerIdStr);
336
-
337
- if (error) {
338
- let errorMessage;
339
- if (error instanceof AggregateError) {
340
- if (!error.errors) {
341
- log.warn(`No errors array found for AggregateError`);
342
- } else if (error.errors.length === 0) {
343
- log.warn(`Errors array is empty for AggregateError`);
344
- } else {
345
- errorMessage = JSON.stringify(error.errors[0]);
346
- }
347
- } else {
348
- errorMessage = error.message;
349
- }
350
-
351
- log.info(
352
- `Deleting undialable peer ${peerIdStr} from peer store. Reason: ${errorMessage}`
353
- );
354
- }
355
-
356
- this.dialErrorsForPeer.delete(peerIdStr);
357
- if (peerId) {
358
- await this.libp2p.peerStore.delete(peerId);
359
- }
360
-
361
- // if it was last available peer - attempt DNS discovery
362
- await this.attemptDnsDiscovery();
363
- } catch (error) {
364
- throw new Error(
365
- `Error deleting undialable peer ${peerIdStr} from peer store - ${error}`
366
- );
367
- }
368
- }
369
-
370
- if (!connection) {
371
- throw new Error(`Failed to dial peer ${peerDialInfo}`);
372
- }
373
-
374
- return connection;
103
+ public isConnected(): boolean {
104
+ return this.networkMonitor.isConnected();
375
105
  }
376
106
 
377
- /**
378
- * Dial a peer with specific protocols.
379
- * This method is a raw proxy to the libp2p dialProtocol method.
380
- * @param peer - The peer to connect to, either as a PeerId or multiaddr
381
- * @param protocolCodecs - Optional array of protocol-specific codec strings to establish
382
- * @returns A stream to the peer
383
- */
384
- public async rawDialPeerWithProtocols(
107
+ public async dial(
385
108
  peer: PeerId | MultiaddrInput,
386
109
  protocolCodecs: string[]
387
110
  ): Promise<Stream> {
388
- const peerDialInfo = this.getDialablePeerInfo(peer);
389
- return await this.libp2p.dialProtocol(peerDialInfo, protocolCodecs);
111
+ const ma = mapToPeerIdOrMultiaddr(peer);
112
+ return this.libp2p.dialProtocol(ma, protocolCodecs);
390
113
  }
391
114
 
392
- /**
393
- * Internal utility to extract a PeerId or Multiaddr from a peer input.
394
- * This is used internally by the connection manager to handle different peer input formats.
395
- * @internal
396
- */
397
- private getDialablePeerInfo(
398
- peer: PeerId | MultiaddrInput
399
- ): PeerId | Multiaddr {
400
- if (isPeerId(peer)) {
401
- return peer;
402
- } else {
403
- // peer is of MultiaddrInput type
404
- const ma = multiaddr(peer);
405
- const peerIdStr = ma.getPeerId();
406
- if (!peerIdStr) {
407
- throw new Error("Failed to dial multiaddr: missing peer ID");
408
- }
409
- return ma;
410
- }
411
- }
412
-
413
- private async attemptDnsDiscovery(): Promise<void> {
414
- if (this.libp2p.getConnections().length > 0) return;
415
- if ((await this.libp2p.peerStore.all()).length > 0) return;
416
-
417
- log.info("Attempting to trigger DNS discovery.");
115
+ public async hangUp(peer: PeerId | MultiaddrInput): Promise<boolean> {
116
+ const peerId = mapToPeerId(peer);
418
117
 
419
- const dnsDiscovery = Object.values(this.libp2p.components.components).find(
420
- (v: unknown) => {
421
- if (v && v.toString) {
422
- return v.toString().includes(DNS_DISCOVERY_TAG);
423
- }
424
-
425
- return false;
426
- }
427
- ) as DiscoveryTrigger;
428
-
429
- if (!dnsDiscovery) return;
430
-
431
- await dnsDiscovery.findPeers();
432
- }
433
-
434
- private processDialQueue(): void {
435
- if (
436
- this.pendingPeerDialQueue.length > 0 &&
437
- this.currentActiveParallelDialCount < this.options.maxParallelDials
438
- ) {
439
- const peerId = this.pendingPeerDialQueue.shift();
440
- if (!peerId) return;
441
- this.attemptDial(peerId).catch((error) => {
442
- log.error(error);
443
- });
444
- }
445
- }
446
-
447
- private startPeerDiscoveryListener(): void {
448
- this.libp2p.addEventListener(
449
- "peer:discovery",
450
- this.onEventHandlers["peer:discovery"]
451
- );
452
- }
453
-
454
- private startPeerConnectionListener(): void {
455
- this.libp2p.addEventListener(
456
- "peer:connect",
457
- this.onEventHandlers["peer:connect"]
458
- );
459
- }
460
-
461
- private startPeerDisconnectionListener(): void {
462
- // TODO: ensure that these following issues are updated and confirmed
463
- /**
464
- * NOTE: Event is not being emitted on closing nor losing a connection.
465
- * @see https://github.com/libp2p/js-libp2p/issues/939
466
- * @see https://github.com/status-im/js-waku/issues/252
467
- *
468
- * >This event will be triggered anytime we are disconnected from another peer,
469
- * >regardless of the circumstances of that disconnection.
470
- * >If we happen to have multiple connections to a peer,
471
- * >this event will **only** be triggered when the last connection is closed.
472
- * @see https://github.com/libp2p/js-libp2p/blob/bad9e8c0ff58d60a78314077720c82ae331cc55b/doc/API.md?plain=1#L2100
473
- */
474
- this.libp2p.addEventListener(
475
- "peer:disconnect",
476
- this.onEventHandlers["peer:disconnect"]
477
- );
478
- }
479
-
480
- public async attemptDial(peerId: PeerId): Promise<void> {
481
- if (!(await this.shouldDialPeer(peerId))) return;
482
-
483
- if (this.currentActiveParallelDialCount >= this.options.maxParallelDials) {
484
- this.pendingPeerDialQueue.push(peerId);
485
- return;
486
- }
487
-
488
- await this.dialPeer(peerId);
489
- }
490
-
491
- private onEventHandlers = {
492
- "peer:discovery": (evt: CustomEvent<PeerInfo>): void => {
493
- void (async () => {
494
- const { id: peerId } = evt.detail;
495
-
496
- await this.dispatchDiscoveryEvent(peerId);
497
-
498
- try {
499
- await this.attemptDial(peerId);
500
- } catch (error) {
501
- log.error(`Error dialing peer ${peerId.toString()} : ${error}`);
502
- }
503
- })();
504
- },
505
- "peer:connect": (evt: CustomEvent<PeerId>): void => {
506
- void (async () => {
507
- log.info(`Connected to peer ${evt.detail.toString()}`);
508
-
509
- const peerId = evt.detail;
510
-
511
- this.keepAliveManager.start(peerId);
512
-
513
- const isBootstrap = (await this.getTagNamesForPeer(peerId)).includes(
514
- Tags.BOOTSTRAP
515
- );
516
-
517
- if (isBootstrap) {
518
- const bootstrapConnections = this.libp2p
519
- .getConnections()
520
- .filter((conn) => conn.tags.includes(Tags.BOOTSTRAP));
521
-
522
- // If we have too many bootstrap connections, drop one
523
- if (
524
- bootstrapConnections.length > this.options.maxBootstrapPeersAllowed
525
- ) {
526
- await this.dropConnection(peerId);
527
- } else {
528
- this.dispatchEvent(
529
- new CustomEvent<PeerId>(
530
- EPeersByDiscoveryEvents.PEER_CONNECT_BOOTSTRAP,
531
- {
532
- detail: peerId
533
- }
534
- )
535
- );
536
- }
537
- } else {
538
- this.dispatchEvent(
539
- new CustomEvent<PeerId>(
540
- EPeersByDiscoveryEvents.PEER_CONNECT_PEER_EXCHANGE,
541
- {
542
- detail: peerId
543
- }
544
- )
545
- );
546
- }
547
-
548
- this.setP2PNetworkConnected();
549
- })();
550
- },
551
- "peer:disconnect": (evt: CustomEvent<PeerId>): void => {
552
- void (async () => {
553
- this.keepAliveManager.stop(evt.detail);
554
- this.setP2PNetworkDisconnected();
555
- })();
556
- },
557
- "browser:network": (): void => {
558
- this.dispatchWakuConnectionEvent();
559
- }
560
- };
561
-
562
- /**
563
- * Checks if the peer should be dialed based on the following conditions:
564
- * 1. If the peer is already connected, don't dial
565
- * 2. If the peer is not part of any of the configured pubsub topics, don't dial
566
- * 3. If the peer is not dialable based on bootstrap status, don't dial
567
- * 4. If the peer is already has an active dial attempt, or has been dialed before, don't dial it
568
- * @returns true if the peer should be dialed, false otherwise
569
- */
570
- private async shouldDialPeer(peerId: PeerId): Promise<boolean> {
571
- const isConnected = this.libp2p.getConnections(peerId).length > 0;
572
- if (isConnected) {
573
- log.warn(`Already connected to peer ${peerId.toString()}. Not dialing.`);
574
- return false;
575
- }
576
-
577
- const isSameShard = await this.isPeerTopicConfigured(peerId);
578
- if (!isSameShard) {
579
- const shardInfo = await this.getPeerShardInfo(
580
- peerId,
581
- this.libp2p.peerStore
582
- );
583
-
584
- log.warn(
585
- `Discovered peer ${peerId.toString()} with ShardInfo ${shardInfo} is not part of any of the configured pubsub topics (${
586
- this.pubsubTopics
587
- }).
588
- Not dialing.`
589
- );
590
-
591
- return false;
592
- }
118
+ try {
119
+ await this.libp2p.hangUp(peerId);
593
120
 
594
- const isPreferredBasedOnBootstrap =
595
- await this.isPeerDialableBasedOnBootstrapStatus(peerId);
596
- if (!isPreferredBasedOnBootstrap) {
597
- log.warn(
598
- `Peer ${peerId.toString()} is not dialable based on bootstrap status. Not dialing.`
121
+ log.info(`Dropped connection with peer ${peerId.toString()}`);
122
+ return true;
123
+ } catch (error) {
124
+ log.error(
125
+ `Error dropping connection with peer ${peerId.toString()} - ${error}`
599
126
  );
600
- return false;
601
- }
602
127
 
603
- const hasBeenDialed = this.dialAttemptsForPeer.has(peerId.toString());
604
- if (hasBeenDialed) {
605
- log.warn(
606
- `Peer ${peerId.toString()} has already been attempted dial before, or already has a dial attempt in progress, skipping dial`
607
- );
608
128
  return false;
609
129
  }
610
-
611
- return true;
612
- }
613
-
614
- /**
615
- * Checks if the peer is dialable based on the following conditions:
616
- * 1. If the peer is a bootstrap peer, it is only dialable if the number of current bootstrap connections is less than the max allowed.
617
- * 2. If the peer is not a bootstrap peer
618
- */
619
- private async isPeerDialableBasedOnBootstrapStatus(
620
- peerId: PeerId
621
- ): Promise<boolean> {
622
- const tagNames = await this.getTagNamesForPeer(peerId);
623
-
624
- const isBootstrap = tagNames.some((tagName) => tagName === Tags.BOOTSTRAP);
625
-
626
- if (!isBootstrap) {
627
- return true;
628
- }
629
-
630
- const currentBootstrapConnections = this.libp2p
631
- .getConnections()
632
- .filter((conn) => {
633
- return conn.tags.find((name) => name === Tags.BOOTSTRAP);
634
- }).length;
635
-
636
- return currentBootstrapConnections < this.options.maxBootstrapPeersAllowed;
637
130
  }
638
131
 
639
- private async dispatchDiscoveryEvent(peerId: PeerId): Promise<void> {
640
- const isBootstrap = (await this.getTagNamesForPeer(peerId)).includes(
641
- Tags.BOOTSTRAP
642
- );
643
-
644
- this.dispatchEvent(
645
- new CustomEvent<PeerId>(
646
- isBootstrap
647
- ? EPeersByDiscoveryEvents.PEER_DISCOVERY_BOOTSTRAP
648
- : EPeersByDiscoveryEvents.PEER_DISCOVERY_PEER_EXCHANGE,
649
- {
650
- detail: peerId
651
- }
652
- )
653
- );
654
- }
132
+ public async getConnectedPeers(codec?: string): Promise<Peer[]> {
133
+ const peerIDs = this.libp2p.getPeers();
655
134
 
656
- /**
657
- * Fetches the tag names for a given peer
658
- */
659
- private async getTagNamesForPeer(peerId: PeerId): Promise<string[]> {
660
- try {
661
- const peer = await this.libp2p.peerStore.get(peerId);
662
- return Array.from(peer.tags.keys());
663
- } catch (error) {
664
- log.error(`Failed to get peer ${peerId}, error: ${error}`);
135
+ if (peerIDs.length === 0) {
665
136
  return [];
666
137
  }
667
- }
668
-
669
- private async isPeerTopicConfigured(peerId: PeerId): Promise<boolean> {
670
- const shardInfo = await this.getPeerShardInfo(
671
- peerId,
672
- this.libp2p.peerStore
673
- );
674
138
 
675
- // If there's no shard information, simply return true
676
- if (!shardInfo) return true;
677
-
678
- const pubsubTopics = shardInfoToPubsubTopics(shardInfo);
679
-
680
- const isTopicConfigured = pubsubTopics.some((topic) =>
681
- this.pubsubTopics.includes(topic)
139
+ const peers = await Promise.all(
140
+ peerIDs.map(async (id) => {
141
+ try {
142
+ return await this.libp2p.peerStore.get(id);
143
+ } catch (e) {
144
+ return null;
145
+ }
146
+ })
682
147
  );
683
- return isTopicConfigured;
684
- }
685
-
686
- private async getPeerShardInfo(
687
- peerId: PeerId,
688
- peerStore: PeerStore
689
- ): Promise<ShardInfo | undefined> {
690
- const peer = await peerStore.get(peerId);
691
- const shardInfoBytes = peer.metadata.get("shardInfo");
692
- if (!shardInfoBytes) return undefined;
693
- return decodeRelayShard(shardInfoBytes);
694
- }
695
148
 
696
- private startNetworkStatusListener(): void {
697
- try {
698
- globalThis.addEventListener(
699
- "online",
700
- this.onEventHandlers["browser:network"]
701
- );
702
- globalThis.addEventListener(
703
- "offline",
704
- this.onEventHandlers["browser:network"]
705
- );
706
- } catch (err) {
707
- log.error(`Failed to start network listener: ${err}`);
708
- }
709
- }
710
-
711
- private stopNetworkStatusListener(): void {
712
- try {
713
- globalThis.removeEventListener(
714
- "online",
715
- this.onEventHandlers["browser:network"]
716
- );
717
- globalThis.removeEventListener(
718
- "offline",
719
- this.onEventHandlers["browser:network"]
720
- );
721
- } catch (err) {
722
- log.error(`Failed to stop network listener: ${err}`);
723
- }
149
+ return peers
150
+ .filter((p) => !!p)
151
+ .filter((p) => (codec ? (p as Peer).protocols.includes(codec) : true))
152
+ .sort((left, right) => getPeerPing(left) - getPeerPing(right)) as Peer[];
724
153
  }
725
154
 
726
- private setP2PNetworkConnected(): void {
727
- if (!this.isP2PNetworkConnected) {
728
- this.isP2PNetworkConnected = true;
729
- this.dispatchWakuConnectionEvent();
730
- }
155
+ public isTopicConfigured(pubsubTopic: PubsubTopic): boolean {
156
+ return this.pubsubTopics.includes(pubsubTopic);
731
157
  }
732
158
 
733
- private setP2PNetworkDisconnected(): void {
734
- if (
735
- this.isP2PNetworkConnected &&
736
- this.libp2p.getConnections().length === 0
737
- ) {
738
- this.isP2PNetworkConnected = false;
739
- this.dispatchWakuConnectionEvent();
740
- }
159
+ public async hasShardInfo(peerId: PeerId): Promise<boolean> {
160
+ return this.shardReader.hasShardInfo(peerId);
741
161
  }
742
162
 
743
- private dispatchWakuConnectionEvent(): void {
744
- this.dispatchEvent(
745
- new CustomEvent<boolean>(EConnectionStateEvents.CONNECTION_STATUS, {
746
- detail: this.isConnected()
747
- })
748
- );
163
+ public async isPeerOnTopic(
164
+ peerId: PeerId,
165
+ pubsubTopic: string
166
+ ): Promise<boolean> {
167
+ return this.shardReader.isPeerOnTopic(peerId, pubsubTopic);
749
168
  }
750
169
  }