@waku/core 0.0.23 → 0.0.24

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 (53) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/bundle/{base_protocol-84d9b670.js → base_protocol-2a0c882e.js} +182 -130
  3. package/bundle/{browser-bde977a3.js → browser-90197c87.js} +26 -1
  4. package/bundle/index.js +19159 -2228
  5. package/bundle/lib/base_protocol.js +2 -2
  6. package/bundle/lib/message/version_0.js +2 -2
  7. package/bundle/{version_0-74b4b9db.js → version_0-f4afd324.js} +33 -21
  8. package/dist/.tsbuildinfo +1 -1
  9. package/dist/lib/connection_manager.d.ts +15 -3
  10. package/dist/lib/connection_manager.js +83 -25
  11. package/dist/lib/connection_manager.js.map +1 -1
  12. package/dist/lib/filter/index.js +25 -20
  13. package/dist/lib/filter/index.js.map +1 -1
  14. package/dist/lib/keep_alive_manager.d.ts +1 -0
  15. package/dist/lib/keep_alive_manager.js +38 -14
  16. package/dist/lib/keep_alive_manager.js.map +1 -1
  17. package/dist/lib/light_push/index.js +49 -31
  18. package/dist/lib/light_push/index.js.map +1 -1
  19. package/dist/lib/light_push/push_rpc.d.ts +1 -1
  20. package/dist/lib/light_push/push_rpc.js +2 -2
  21. package/dist/lib/message/version_0.d.ts +13 -13
  22. package/dist/lib/message/version_0.js +19 -16
  23. package/dist/lib/message/version_0.js.map +1 -1
  24. package/dist/lib/store/history_rpc.d.ts +1 -1
  25. package/dist/lib/store/history_rpc.js +1 -1
  26. package/dist/lib/store/index.d.ts +1 -1
  27. package/dist/lib/store/index.js +37 -10
  28. package/dist/lib/store/index.js.map +1 -1
  29. package/dist/lib/stream_manager.d.ts +1 -1
  30. package/dist/lib/stream_manager.js +5 -2
  31. package/dist/lib/stream_manager.js.map +1 -1
  32. package/dist/lib/wait_for_remote_peer.d.ts +2 -2
  33. package/dist/lib/wait_for_remote_peer.js +10 -7
  34. package/dist/lib/wait_for_remote_peer.js.map +1 -1
  35. package/dist/lib/waku.d.ts +4 -3
  36. package/dist/lib/waku.js +5 -3
  37. package/dist/lib/waku.js.map +1 -1
  38. package/package.json +8 -18
  39. package/src/lib/connection_manager.ts +118 -31
  40. package/src/lib/filter/index.ts +36 -23
  41. package/src/lib/keep_alive_manager.ts +49 -16
  42. package/src/lib/light_push/index.ts +56 -31
  43. package/src/lib/light_push/push_rpc.ts +2 -2
  44. package/src/lib/message/version_0.ts +22 -13
  45. package/src/lib/store/history_rpc.ts +2 -2
  46. package/src/lib/store/index.ts +53 -14
  47. package/src/lib/stream_manager.ts +7 -4
  48. package/src/lib/wait_for_remote_peer.ts +10 -7
  49. package/src/lib/waku.ts +4 -1
  50. package/dist/lib/push_or_init_map.d.ts +0 -1
  51. package/dist/lib/push_or_init_map.js +0 -9
  52. package/dist/lib/push_or_init_map.js.map +0 -1
  53. package/src/lib/push_or_init_map.ts +0 -13
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waku/core",
3
- "version": "0.0.23",
3
+ "version": "0.0.24",
4
4
  "description": "TypeScript implementation of the Waku v2 protocol",
5
5
  "types": "./dist/index.d.ts",
6
6
  "module": "./dist/index.js",
@@ -69,18 +69,19 @@
69
69
  "reset-hard": "git clean -dfx -e .idea && git reset --hard && npm i && npm run build"
70
70
  },
71
71
  "engines": {
72
- "node": ">=16"
72
+ "node": ">=18"
73
73
  },
74
74
  "dependencies": {
75
75
  "@noble/hashes": "^1.3.2",
76
- "@waku/interfaces": "0.0.18",
76
+ "@waku/enr": "^0.0.18",
77
+ "@waku/interfaces": "0.0.19",
77
78
  "@waku/proto": "0.0.5",
78
- "@waku/utils": "0.0.11",
79
+ "@waku/utils": "0.0.12",
79
80
  "debug": "^4.3.4",
80
81
  "it-all": "^3.0.3",
81
82
  "it-length-prefixed": "^9.0.1",
82
83
  "it-pipe": "^3.0.1",
83
- "p-event": "^5.0.1",
84
+ "p-event": "^6.0.0",
84
85
  "uint8arraylist": "^2.4.3",
85
86
  "uuid": "^9.0.0"
86
87
  },
@@ -96,21 +97,13 @@
96
97
  "@waku/build-utils": "*",
97
98
  "chai": "^4.3.7",
98
99
  "cspell": "^7.3.2",
99
- "fast-check": "^3.12.0",
100
+ "fast-check": "^3.13.1",
100
101
  "ignore-loader": "^0.1.2",
101
102
  "isomorphic-fetch": "^3.0.0",
102
- "karma": "^6.4.1",
103
- "karma-chrome-launcher": "^3.2.0",
104
- "karma-mocha": "^2.0.1",
105
- "karma-webpack": "^5.0.0",
106
103
  "mocha": "^10.2.0",
107
104
  "npm-run-all": "^4.1.5",
108
105
  "process": "^0.11.10",
109
- "puppeteer": "^21.1.1",
110
- "rollup": "^3.29.0",
111
- "ts-loader": "^9.4.2",
112
- "ts-node": "^10.9.1",
113
- "typescript": "^5.0.4"
106
+ "rollup": "^3.29.2"
114
107
  },
115
108
  "peerDependencies": {
116
109
  "@multiformats/multiaddr": "^12.0.0",
@@ -121,9 +114,6 @@
121
114
  "optional": true
122
115
  }
123
116
  },
124
- "typedoc": {
125
- "entryPoint": "./src/index.ts"
126
- },
127
117
  "files": [
128
118
  "dist",
129
119
  "bundle",
@@ -1,7 +1,9 @@
1
1
  import type { PeerId } from "@libp2p/interface/peer-id";
2
2
  import type { PeerInfo } from "@libp2p/interface/peer-info";
3
3
  import type { Peer } from "@libp2p/interface/peer-store";
4
+ import type { PeerStore } from "@libp2p/interface/peer-store";
4
5
  import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events";
6
+ import { decodeRelayShard } from "@waku/enr";
5
7
  import {
6
8
  ConnectionManagerOptions,
7
9
  EPeersByDiscoveryEvents,
@@ -9,9 +11,12 @@ import {
9
11
  IPeersByDiscoveryEvents,
10
12
  IRelay,
11
13
  KeepAliveOptions,
12
- PeersByDiscoveryResult
14
+ PeersByDiscoveryResult,
15
+ PubSubTopic,
16
+ ShardInfo
13
17
  } from "@waku/interfaces";
14
18
  import { Libp2p, Tags } from "@waku/interfaces";
19
+ import { shardInfoToPubSubTopics } from "@waku/utils";
15
20
  import debug from "debug";
16
21
 
17
22
  import { KeepAliveManager } from "./keep_alive_manager.js";
@@ -40,6 +45,7 @@ export class ConnectionManager
40
45
  peerId: string,
41
46
  libp2p: Libp2p,
42
47
  keepAliveOptions: KeepAliveOptions,
48
+ pubsubTopics: PubSubTopic[],
43
49
  relay?: IRelay,
44
50
  options?: ConnectionManagerOptions
45
51
  ): ConnectionManager {
@@ -48,6 +54,7 @@ export class ConnectionManager
48
54
  instance = new ConnectionManager(
49
55
  libp2p,
50
56
  keepAliveOptions,
57
+ pubsubTopics,
51
58
  relay,
52
59
  options
53
60
  );
@@ -104,11 +111,13 @@ export class ConnectionManager
104
111
  private constructor(
105
112
  libp2p: Libp2p,
106
113
  keepAliveOptions: KeepAliveOptions,
114
+ private configuredPubSubTopics: PubSubTopic[],
107
115
  relay?: IRelay,
108
116
  options?: Partial<ConnectionManagerOptions>
109
117
  ) {
110
118
  super();
111
119
  this.libp2p = libp2p;
120
+ this.configuredPubSubTopics = configuredPubSubTopics;
112
121
  this.options = {
113
122
  maxDialAttemptsForPeer: DEFAULT_MAX_DIAL_ATTEMPTS_FOR_PEER,
114
123
  maxBootstrapPeersAllowed: DEFAULT_MAX_BOOTSTRAP_PEERS_ALLOWED,
@@ -217,16 +226,24 @@ export class ConnectionManager
217
226
  try {
218
227
  const error = this.dialErrorsForPeer.get(peerId.toString());
219
228
 
220
- let errorMessage;
221
- if (error instanceof AggregateError) {
222
- errorMessage = JSON.stringify(error.errors[0]);
223
- } else {
224
- errorMessage = error.message;
225
- }
229
+ if (error) {
230
+ let errorMessage;
231
+ if (error instanceof AggregateError) {
232
+ if (!error.errors) {
233
+ log(`No errors array found for AggregateError`);
234
+ } else if (error.errors.length === 0) {
235
+ log(`Errors array is empty for AggregateError`);
236
+ } else {
237
+ errorMessage = JSON.stringify(error.errors[0]);
238
+ }
239
+ } else {
240
+ errorMessage = error.message;
241
+ }
226
242
 
227
- log(
228
- `Deleting undialable peer ${peerId.toString()} from peer store. Error: ${errorMessage}`
229
- );
243
+ log(
244
+ `Deleting undialable peer ${peerId.toString()} from peer store. Error: ${errorMessage}`
245
+ );
246
+ }
230
247
 
231
248
  this.dialErrorsForPeer.delete(peerId.toString());
232
249
  await this.libp2p.peerStore.delete(peerId);
@@ -297,15 +314,15 @@ export class ConnectionManager
297
314
  }
298
315
 
299
316
  private async attemptDial(peerId: PeerId): Promise<void> {
317
+ if (!(await this.shouldDialPeer(peerId))) return;
318
+
300
319
  if (this.currentActiveDialCount >= this.options.maxParallelDials) {
301
320
  this.pendingPeerDialQueue.push(peerId);
302
321
  return;
303
322
  }
304
323
 
305
- if (!(await this.shouldDialPeer(peerId))) return;
306
-
307
324
  this.dialPeer(peerId).catch((err) => {
308
- throw `Error dialing peer ${peerId.toString()} : ${err}`;
325
+ log(`Error dialing peer ${peerId.toString()} : ${err}`);
309
326
  });
310
327
  }
311
328
 
@@ -314,20 +331,7 @@ export class ConnectionManager
314
331
  void (async () => {
315
332
  const { id: peerId } = evt.detail;
316
333
 
317
- const isBootstrap = (await this.getTagNamesForPeer(peerId)).includes(
318
- Tags.BOOTSTRAP
319
- );
320
-
321
- this.dispatchEvent(
322
- new CustomEvent<PeerId>(
323
- isBootstrap
324
- ? EPeersByDiscoveryEvents.PEER_DISCOVERY_BOOTSTRAP
325
- : EPeersByDiscoveryEvents.PEER_DISCOVERY_PEER_EXCHANGE,
326
- {
327
- detail: peerId
328
- }
329
- )
330
- );
334
+ await this.dispatchDiscoveryEvent(peerId);
331
335
 
332
336
  try {
333
337
  await this.attemptDial(peerId);
@@ -390,15 +394,54 @@ export class ConnectionManager
390
394
  };
391
395
 
392
396
  /**
393
- * Checks if the peer is dialable based on the following conditions:
394
- * 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.
395
- * 2. If the peer is not a bootstrap peer
397
+ * Checks if the peer should be dialed based on the following conditions:
398
+ * 1. If the peer is already connected, don't dial
399
+ * 2. If the peer is not part of any of the configured pubsub topics, don't dial
400
+ * 3. If the peer is not dialable based on bootstrap status, don't dial
401
+ * @returns true if the peer should be dialed, false otherwise
396
402
  */
397
403
  private async shouldDialPeer(peerId: PeerId): Promise<boolean> {
404
+ // if we're already connected to the peer, don't dial
398
405
  const isConnected = this.libp2p.getConnections(peerId).length > 0;
406
+ if (isConnected) {
407
+ log(`Already connected to peer ${peerId.toString()}. Not dialing.`);
408
+ return false;
409
+ }
399
410
 
400
- if (isConnected) return false;
411
+ // if the peer is not part of any of the configured pubsub topics, don't dial
412
+ if (!(await this.isPeerTopicConfigured(peerId))) {
413
+ const shardInfo = await this.getPeerShardInfo(
414
+ peerId,
415
+ this.libp2p.peerStore
416
+ );
417
+ log(
418
+ `Discovered peer ${peerId.toString()} with ShardInfo ${shardInfo} is not part of any of the configured pubsub topics (${
419
+ this.configuredPubSubTopics
420
+ }).
421
+ Not dialing.`
422
+ );
423
+ return false;
424
+ }
401
425
 
426
+ // if the peer is not dialable based on bootstrap status, don't dial
427
+ if (!(await this.isPeerDialableBasedOnBootstrapStatus(peerId))) {
428
+ log(
429
+ `Peer ${peerId.toString()} is not dialable based on bootstrap status. Not dialing.`
430
+ );
431
+ return false;
432
+ }
433
+
434
+ return true;
435
+ }
436
+
437
+ /**
438
+ * Checks if the peer is dialable based on the following conditions:
439
+ * 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.
440
+ * 2. If the peer is not a bootstrap peer
441
+ */
442
+ private async isPeerDialableBasedOnBootstrapStatus(
443
+ peerId: PeerId
444
+ ): Promise<boolean> {
402
445
  const tagNames = await this.getTagNamesForPeer(peerId);
403
446
 
404
447
  const isBootstrap = tagNames.some((tagName) => tagName === Tags.BOOTSTRAP);
@@ -418,6 +461,23 @@ export class ConnectionManager
418
461
  return false;
419
462
  }
420
463
 
464
+ private async dispatchDiscoveryEvent(peerId: PeerId): Promise<void> {
465
+ const isBootstrap = (await this.getTagNamesForPeer(peerId)).includes(
466
+ Tags.BOOTSTRAP
467
+ );
468
+
469
+ this.dispatchEvent(
470
+ new CustomEvent<PeerId>(
471
+ isBootstrap
472
+ ? EPeersByDiscoveryEvents.PEER_DISCOVERY_BOOTSTRAP
473
+ : EPeersByDiscoveryEvents.PEER_DISCOVERY_PEER_EXCHANGE,
474
+ {
475
+ detail: peerId
476
+ }
477
+ )
478
+ );
479
+ }
480
+
421
481
  /**
422
482
  * Fetches the tag names for a given peer
423
483
  */
@@ -430,4 +490,31 @@ export class ConnectionManager
430
490
  return [];
431
491
  }
432
492
  }
493
+
494
+ private async isPeerTopicConfigured(peerId: PeerId): Promise<boolean> {
495
+ const shardInfo = await this.getPeerShardInfo(
496
+ peerId,
497
+ this.libp2p.peerStore
498
+ );
499
+
500
+ // If there's no shard information, simply return true
501
+ if (!shardInfo) return true;
502
+
503
+ const pubsubTopics = shardInfoToPubSubTopics(shardInfo);
504
+
505
+ const isTopicConfigured = pubsubTopics.some((topic) =>
506
+ this.configuredPubSubTopics.includes(topic)
507
+ );
508
+ return isTopicConfigured;
509
+ }
510
+
511
+ private async getPeerShardInfo(
512
+ peerId: PeerId,
513
+ peerStore: PeerStore
514
+ ): Promise<ShardInfo | undefined> {
515
+ const peer = await peerStore.get(peerId);
516
+ const shardInfoBytes = peer.metadata.get("shardInfo");
517
+ if (!shardInfoBytes) return undefined;
518
+ return decodeRelayShard(shardInfoBytes);
519
+ }
433
520
  }
@@ -17,7 +17,11 @@ import type {
17
17
  Unsubscribe
18
18
  } from "@waku/interfaces";
19
19
  import { WakuMessage } from "@waku/proto";
20
- import { groupByContentTopic, toAsyncIterator } from "@waku/utils";
20
+ import {
21
+ ensurePubsubTopicIsConfigured,
22
+ groupByContentTopic,
23
+ toAsyncIterator
24
+ } from "@waku/utils";
21
25
  import debug from "debug";
22
26
  import all from "it-all";
23
27
  import * as lp from "it-length-prefixed";
@@ -46,7 +50,7 @@ export const FilterCodecs = {
46
50
 
47
51
  class Subscription {
48
52
  private readonly peer: Peer;
49
- private readonly pubSubTopic: PubSubTopic;
53
+ private readonly pubsubTopic: PubSubTopic;
50
54
  private newStream: (peer: Peer) => Promise<Stream>;
51
55
 
52
56
  private subscriptionCallbacks: Map<
@@ -55,12 +59,12 @@ class Subscription {
55
59
  >;
56
60
 
57
61
  constructor(
58
- pubSubTopic: PubSubTopic,
62
+ pubsubTopic: PubSubTopic,
59
63
  remotePeer: Peer,
60
64
  newStream: (peer: Peer) => Promise<Stream>
61
65
  ) {
62
66
  this.peer = remotePeer;
63
- this.pubSubTopic = pubSubTopic;
67
+ this.pubsubTopic = pubsubTopic;
64
68
  this.newStream = newStream;
65
69
  this.subscriptionCallbacks = new Map();
66
70
  }
@@ -76,7 +80,7 @@ class Subscription {
76
80
  const stream = await this.newStream(this.peer);
77
81
 
78
82
  const request = FilterSubscribeRpc.createSubscribeRequest(
79
- this.pubSubTopic,
83
+ this.pubsubTopic,
80
84
  contentTopics
81
85
  );
82
86
 
@@ -89,6 +93,12 @@ class Subscription {
89
93
  async (source) => await all(source)
90
94
  );
91
95
 
96
+ if (!res || !res.length) {
97
+ throw Error(
98
+ `No response received for request ${request.requestId}: ${res}`
99
+ );
100
+ }
101
+
92
102
  const { statusCode, requestId, statusDesc } =
93
103
  FilterSubscribeResponse.decode(res[0].slice());
94
104
 
@@ -135,7 +145,7 @@ class Subscription {
135
145
  async unsubscribe(contentTopics: ContentTopic[]): Promise<void> {
136
146
  const stream = await this.newStream(this.peer);
137
147
  const unsubscribeRequest = FilterSubscribeRpc.createUnsubscribeRequest(
138
- this.pubSubTopic,
148
+ this.pubsubTopic,
139
149
  contentTopics
140
150
  );
141
151
 
@@ -184,7 +194,7 @@ class Subscription {
184
194
  const stream = await this.newStream(this.peer);
185
195
 
186
196
  const request = FilterSubscribeRpc.createUnsubscribeAllRequest(
187
- this.pubSubTopic
197
+ this.pubsubTopic
188
198
  );
189
199
 
190
200
  try {
@@ -219,47 +229,50 @@ class Subscription {
219
229
  log("No subscription callback available for ", contentTopic);
220
230
  return;
221
231
  }
222
- await pushMessage(subscriptionCallback, this.pubSubTopic, message);
232
+ await pushMessage(subscriptionCallback, this.pubsubTopic, message);
223
233
  }
224
234
  }
225
235
 
226
236
  class Filter extends BaseProtocol implements IReceiver {
227
- private readonly options: ProtocolCreateOptions;
237
+ private readonly pubsubTopics: PubSubTopic[] = [];
228
238
  private activeSubscriptions = new Map<string, Subscription>();
229
239
  private readonly NUM_PEERS_PROTOCOL = 1;
230
240
 
231
241
  private getActiveSubscription(
232
- pubSubTopic: PubSubTopic,
242
+ pubsubTopic: PubSubTopic,
233
243
  peerIdStr: PeerIdStr
234
244
  ): Subscription | undefined {
235
- return this.activeSubscriptions.get(`${pubSubTopic}_${peerIdStr}`);
245
+ return this.activeSubscriptions.get(`${pubsubTopic}_${peerIdStr}`);
236
246
  }
237
247
 
238
248
  private setActiveSubscription(
239
- pubSubTopic: PubSubTopic,
249
+ pubsubTopic: PubSubTopic,
240
250
  peerIdStr: PeerIdStr,
241
251
  subscription: Subscription
242
252
  ): Subscription {
243
- this.activeSubscriptions.set(`${pubSubTopic}_${peerIdStr}`, subscription);
253
+ this.activeSubscriptions.set(`${pubsubTopic}_${peerIdStr}`, subscription);
244
254
  return subscription;
245
255
  }
246
256
 
247
257
  constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
248
258
  super(FilterCodecs.SUBSCRIBE, libp2p.components);
249
259
 
260
+ this.pubsubTopics = options?.pubsubTopics || [DefaultPubSubTopic];
261
+
250
262
  libp2p.handle(FilterCodecs.PUSH, this.onRequest.bind(this)).catch((e) => {
251
263
  log("Failed to register ", FilterCodecs.PUSH, e);
252
264
  });
253
265
 
254
266
  this.activeSubscriptions = new Map();
255
-
256
- this.options = options ?? {};
257
267
  }
258
268
 
259
- async createSubscription(pubSubTopic?: string): Promise<Subscription> {
260
- const _pubSubTopic =
261
- pubSubTopic ?? this.options.pubSubTopic ?? DefaultPubSubTopic;
269
+ async createSubscription(
270
+ pubsubTopic: string = DefaultPubSubTopic
271
+ ): Promise<Subscription> {
272
+ ensurePubsubTopicIsConfigured(pubsubTopic, this.pubsubTopics);
262
273
 
274
+ //TODO: get a relevant peer for the topic/shard
275
+ // https://github.com/waku-org/js-waku/pull/1586#discussion_r1336428230
263
276
  const peer = (
264
277
  await this.getPeers({
265
278
  maxBootstrapPeers: 1,
@@ -268,11 +281,11 @@ class Filter extends BaseProtocol implements IReceiver {
268
281
  )[0];
269
282
 
270
283
  const subscription =
271
- this.getActiveSubscription(_pubSubTopic, peer.id.toString()) ??
284
+ this.getActiveSubscription(pubsubTopic, peer.id.toString()) ??
272
285
  this.setActiveSubscription(
273
- _pubSubTopic,
286
+ pubsubTopic,
274
287
  peer.id.toString(),
275
- new Subscription(_pubSubTopic, peer, this.getStream.bind(this, peer))
288
+ new Subscription(pubsubTopic, peer, this.getStream.bind(this, peer))
276
289
  );
277
290
 
278
291
  return subscription;
@@ -372,7 +385,7 @@ export function wakuFilter(
372
385
 
373
386
  async function pushMessage<T extends IDecodedMessage>(
374
387
  subscriptionCallback: SubscriptionCallback<T>,
375
- pubSubTopic: PubSubTopic,
388
+ pubsubTopic: PubSubTopic,
376
389
  message: WakuMessage
377
390
  ): Promise<void> {
378
391
  const { decoders, callback } = subscriptionCallback;
@@ -386,7 +399,7 @@ async function pushMessage<T extends IDecodedMessage>(
386
399
  try {
387
400
  const decodePromises = decoders.map((dec) =>
388
401
  dec
389
- .fromProtoObj(pubSubTopic, message as IProtoMessage)
402
+ .fromProtoObj(pubsubTopic, message as IProtoMessage)
390
403
  .then((decoded) => decoded || Promise.reject("Decoding failed"))
391
404
  );
392
405
 
@@ -1,6 +1,6 @@
1
1
  import type { PeerId } from "@libp2p/interface/peer-id";
2
2
  import type { PeerStore } from "@libp2p/interface/peer-store";
3
- import type { IRelay } from "@waku/interfaces";
3
+ import type { IRelay, PeerIdStr } from "@waku/interfaces";
4
4
  import type { KeepAliveOptions } from "@waku/interfaces";
5
5
  import { utf8ToBytes } from "@waku/utils/bytes";
6
6
  import debug from "debug";
@@ -13,7 +13,7 @@ const log = debug("waku:keep-alive");
13
13
 
14
14
  export class KeepAliveManager {
15
15
  private pingKeepAliveTimers: Map<string, ReturnType<typeof setInterval>>;
16
- private relayKeepAliveTimers: Map<PeerId, ReturnType<typeof setInterval>>;
16
+ private relayKeepAliveTimers: Map<PeerId, ReturnType<typeof setInterval>[]>;
17
17
  private options: KeepAliveOptions;
18
18
  private relay?: IRelay;
19
19
 
@@ -37,14 +37,24 @@ export class KeepAliveManager {
37
37
 
38
38
  const peerIdStr = peerId.toString();
39
39
 
40
+ // Ping the peer every pingPeriodSecs seconds
41
+ // if pingPeriodSecs is 0, don't ping the peer
40
42
  if (pingPeriodSecs !== 0) {
41
43
  const interval = setInterval(() => {
42
44
  void (async () => {
45
+ let ping: number;
43
46
  try {
44
47
  // ping the peer for keep alive
45
48
  // also update the peer store with the latency
46
- const ping = await libp2pPing.ping(peerId);
47
- log(`Ping succeeded (${peerIdStr})`, ping);
49
+ try {
50
+ ping = await libp2pPing.ping(peerId);
51
+ log(`Ping succeeded (${peerIdStr})`, ping);
52
+ } catch (error) {
53
+ log(`Ping failed for peer (${peerIdStr}).
54
+ Next ping will be attempted in ${pingPeriodSecs} seconds.
55
+ `);
56
+ return;
57
+ }
48
58
 
49
59
  try {
50
60
  await peerStore.patch(peerId, {
@@ -66,17 +76,12 @@ export class KeepAliveManager {
66
76
 
67
77
  const relay = this.relay;
68
78
  if (relay && relayPeriodSecs !== 0) {
69
- const encoder = createEncoder({
70
- contentTopic: RelayPingContentTopic,
71
- ephemeral: true
72
- });
73
- const interval = setInterval(() => {
74
- log("Sending Waku Relay ping message");
75
- relay
76
- .send(encoder, { payload: new Uint8Array([1]) })
77
- .catch((e) => log("Failed to send relay ping", e));
78
- }, relayPeriodSecs * 1000);
79
- this.relayKeepAliveTimers.set(peerId, interval);
79
+ const intervals = this.scheduleRelayPings(
80
+ relay,
81
+ relayPeriodSecs,
82
+ peerId.toString()
83
+ );
84
+ this.relayKeepAliveTimers.set(peerId, intervals);
80
85
  }
81
86
  }
82
87
 
@@ -89,7 +94,7 @@ export class KeepAliveManager {
89
94
  }
90
95
 
91
96
  if (this.relayKeepAliveTimers.has(peerId)) {
92
- clearInterval(this.relayKeepAliveTimers.get(peerId));
97
+ this.relayKeepAliveTimers.get(peerId)?.map(clearInterval);
93
98
  this.relayKeepAliveTimers.delete(peerId);
94
99
  }
95
100
  }
@@ -105,4 +110,32 @@ export class KeepAliveManager {
105
110
  this.pingKeepAliveTimers.clear();
106
111
  this.relayKeepAliveTimers.clear();
107
112
  }
113
+
114
+ private scheduleRelayPings(
115
+ relay: IRelay,
116
+ relayPeriodSecs: number,
117
+ peerIdStr: PeerIdStr
118
+ ): NodeJS.Timeout[] {
119
+ // send a ping message to each PubSubTopic the peer is part of
120
+ const intervals: NodeJS.Timeout[] = [];
121
+ for (const topic of relay.pubsubTopics) {
122
+ const meshPeers = relay.getMeshPeers(topic);
123
+ if (!meshPeers.includes(peerIdStr)) continue;
124
+
125
+ const encoder = createEncoder({
126
+ pubsubTopic: topic,
127
+ contentTopic: RelayPingContentTopic,
128
+ ephemeral: true
129
+ });
130
+ const interval = setInterval(() => {
131
+ log("Sending Waku Relay ping message");
132
+ relay
133
+ .send(encoder, { payload: new Uint8Array([1]) })
134
+ .catch((e) => log("Failed to send relay ping", e));
135
+ }, relayPeriodSecs * 1000);
136
+ intervals.push(interval);
137
+ }
138
+
139
+ return intervals;
140
+ }
108
141
  }