@waku/core 0.0.22 → 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 (73) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/bundle/base_protocol-2a0c882e.js +1250 -0
  3. package/bundle/{browser-bde977a3.js → browser-90197c87.js} +26 -1
  4. package/bundle/index.js +20048 -3236
  5. package/bundle/lib/base_protocol.js +2 -116
  6. package/bundle/lib/message/version_0.js +2 -2
  7. package/bundle/lib/predefined_bootstrap_nodes.js +6 -6
  8. package/bundle/{version_0-86411fdf.js → version_0-f4afd324.js} +907 -814
  9. package/dist/.tsbuildinfo +1 -0
  10. package/dist/index.d.ts +5 -4
  11. package/dist/index.js +4 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/base_protocol.d.ts +18 -5
  14. package/dist/lib/base_protocol.js +25 -8
  15. package/dist/lib/base_protocol.js.map +1 -1
  16. package/dist/lib/connection_manager.d.ts +15 -3
  17. package/dist/lib/connection_manager.js +92 -34
  18. package/dist/lib/connection_manager.js.map +1 -1
  19. package/dist/lib/filter/filter_rpc.js +4 -4
  20. package/dist/lib/filter/index.d.ts +4 -0
  21. package/dist/lib/filter/index.js +38 -29
  22. package/dist/lib/filter/index.js.map +1 -1
  23. package/dist/lib/filterPeers.d.ts +10 -0
  24. package/dist/lib/filterPeers.js +31 -0
  25. package/dist/lib/filterPeers.js.map +1 -0
  26. package/dist/lib/keep_alive_manager.d.ts +4 -2
  27. package/dist/lib/keep_alive_manager.js +62 -19
  28. package/dist/lib/keep_alive_manager.js.map +1 -1
  29. package/dist/lib/light_push/index.js +85 -38
  30. package/dist/lib/light_push/index.js.map +1 -1
  31. package/dist/lib/light_push/push_rpc.d.ts +1 -1
  32. package/dist/lib/light_push/push_rpc.js +3 -3
  33. package/dist/lib/message/version_0.d.ts +13 -13
  34. package/dist/lib/message/version_0.js +21 -18
  35. package/dist/lib/message/version_0.js.map +1 -1
  36. package/dist/lib/predefined_bootstrap_nodes.js +6 -6
  37. package/dist/lib/store/history_rpc.d.ts +1 -1
  38. package/dist/lib/store/history_rpc.js +4 -4
  39. package/dist/lib/store/index.d.ts +1 -6
  40. package/dist/lib/store/index.js +91 -47
  41. package/dist/lib/store/index.js.map +1 -1
  42. package/dist/lib/stream_manager.d.ts +15 -0
  43. package/dist/lib/stream_manager.js +56 -0
  44. package/dist/lib/stream_manager.js.map +1 -0
  45. package/dist/lib/to_proto_message.js +1 -1
  46. package/dist/lib/wait_for_remote_peer.d.ts +2 -2
  47. package/dist/lib/wait_for_remote_peer.js +10 -7
  48. package/dist/lib/wait_for_remote_peer.js.map +1 -1
  49. package/dist/lib/waku.d.ts +6 -5
  50. package/dist/lib/waku.js +6 -4
  51. package/dist/lib/waku.js.map +1 -1
  52. package/package.json +17 -33
  53. package/src/index.ts +6 -9
  54. package/src/lib/base_protocol.ts +49 -18
  55. package/src/lib/connection_manager.ts +132 -41
  56. package/src/lib/filter/filter_rpc.ts +4 -4
  57. package/src/lib/filter/index.ts +53 -41
  58. package/src/lib/filterPeers.ts +43 -0
  59. package/src/lib/keep_alive_manager.ts +79 -22
  60. package/src/lib/light_push/index.ts +132 -51
  61. package/src/lib/light_push/push_rpc.ts +3 -3
  62. package/src/lib/message/version_0.ts +27 -15
  63. package/src/lib/predefined_bootstrap_nodes.ts +7 -7
  64. package/src/lib/store/history_rpc.ts +6 -6
  65. package/src/lib/store/index.ts +121 -63
  66. package/src/lib/stream_manager.ts +72 -0
  67. package/src/lib/to_proto_message.ts +1 -1
  68. package/src/lib/wait_for_remote_peer.ts +11 -8
  69. package/src/lib/waku.ts +7 -4
  70. package/dist/lib/push_or_init_map.d.ts +0 -1
  71. package/dist/lib/push_or_init_map.js +0 -9
  72. package/dist/lib/push_or_init_map.js.map +0 -1
  73. package/src/lib/push_or_init_map.ts +0 -13
@@ -1,13 +1,12 @@
1
- import type { Stream } from "@libp2p/interface-connection";
2
- import type { Libp2p } from "@libp2p/interface-libp2p";
3
- import type { PeerId } from "@libp2p/interface-peer-id";
4
- import { Peer, PeerStore } from "@libp2p/interface-peer-store";
1
+ import type { Libp2p } from "@libp2p/interface";
2
+ import type { Stream } from "@libp2p/interface/connection";
3
+ import type { PeerId } from "@libp2p/interface/peer-id";
4
+ import { Peer, PeerStore } from "@libp2p/interface/peer-store";
5
5
  import type { IBaseProtocol, Libp2pComponents } from "@waku/interfaces";
6
- import {
7
- getPeersForProtocol,
8
- selectConnection,
9
- selectPeerForProtocol,
10
- } from "@waku/utils/libp2p";
6
+ import { getPeersForProtocol, selectPeerForProtocol } from "@waku/utils/libp2p";
7
+
8
+ import { filterPeers } from "./filterPeers.js";
9
+ import { StreamManager } from "./stream_manager.js";
11
10
 
12
11
  /**
13
12
  * A class with predefined helpers, to be used as a base to implement Waku
@@ -16,14 +15,29 @@ import {
16
15
  export class BaseProtocol implements IBaseProtocol {
17
16
  public readonly addLibp2pEventListener: Libp2p["addEventListener"];
18
17
  public readonly removeLibp2pEventListener: Libp2p["removeEventListener"];
18
+ protected streamManager: StreamManager;
19
19
 
20
- constructor(public multicodec: string, private components: Libp2pComponents) {
20
+ constructor(
21
+ public multicodec: string,
22
+ private components: Libp2pComponents
23
+ ) {
21
24
  this.addLibp2pEventListener = components.events.addEventListener.bind(
22
25
  components.events
23
26
  );
24
27
  this.removeLibp2pEventListener = components.events.removeEventListener.bind(
25
28
  components.events
26
29
  );
30
+
31
+ this.streamManager = new StreamManager(
32
+ multicodec,
33
+ components.connectionManager.getConnections.bind(
34
+ components.connectionManager
35
+ ),
36
+ this.addLibp2pEventListener
37
+ );
38
+ }
39
+ protected async getStream(peer: Peer): Promise<Stream> {
40
+ return this.streamManager.getStream(peer);
27
41
  }
28
42
 
29
43
  public get peerStore(): PeerStore {
@@ -47,15 +61,32 @@ export class BaseProtocol implements IBaseProtocol {
47
61
  );
48
62
  return peer;
49
63
  }
50
- protected async newStream(peer: Peer): Promise<Stream> {
51
- const connections = this.components.connectionManager.getConnections(
52
- peer.id
53
- );
54
- const connection = selectConnection(connections);
55
- if (!connection) {
56
- throw new Error("Failed to get a connection to the peer");
64
+
65
+ /**
66
+ * Retrieves a list of peers based on the specified criteria.
67
+ *
68
+ * @param numPeers - The total number of peers to retrieve. If 0, all peers are returned.
69
+ * @param maxBootstrapPeers - The maximum number of bootstrap peers to retrieve.
70
+ * @returns A Promise that resolves to an array of peers based on the specified criteria.
71
+ */
72
+ protected async getPeers(
73
+ {
74
+ numPeers,
75
+ maxBootstrapPeers
76
+ }: {
77
+ numPeers: number;
78
+ maxBootstrapPeers: number;
79
+ } = {
80
+ maxBootstrapPeers: 1,
81
+ numPeers: 0
57
82
  }
83
+ ): Promise<Peer[]> {
84
+ // Retrieve all peers that support the protocol
85
+ const allPeersForProtocol = await getPeersForProtocol(this.peerStore, [
86
+ this.multicodec
87
+ ]);
58
88
 
59
- return connection.newStream(this.multicodec);
89
+ // Filter the peers based on the specified criteria
90
+ return filterPeers(allPeersForProtocol, numPeers, maxBootstrapPeers);
60
91
  }
61
92
  }
@@ -1,7 +1,9 @@
1
- import type { PeerId } from "@libp2p/interface-peer-id";
2
- import type { PeerInfo } from "@libp2p/interface-peer-info";
3
- import type { Peer } from "@libp2p/interface-peer-store";
1
+ import type { PeerId } from "@libp2p/interface/peer-id";
2
+ import type { PeerInfo } from "@libp2p/interface/peer-info";
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,
@@ -10,8 +12,11 @@ import {
10
12
  IRelay,
11
13
  KeepAliveOptions,
12
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
  );
@@ -92,28 +99,30 @@ export class ConnectionManager
92
99
  return {
93
100
  DISCOVERED: {
94
101
  [Tags.BOOTSTRAP]: peersDiscoveredByBootstrap,
95
- [Tags.PEER_EXCHANGE]: peersDiscoveredByPeerExchange,
102
+ [Tags.PEER_EXCHANGE]: peersDiscoveredByPeerExchange
96
103
  },
97
104
  CONNECTED: {
98
105
  [Tags.BOOTSTRAP]: peersConnectedByBootstrap,
99
- [Tags.PEER_EXCHANGE]: peersConnectedByPeerExchange,
100
- },
106
+ [Tags.PEER_EXCHANGE]: peersConnectedByPeerExchange
107
+ }
101
108
  };
102
109
  }
103
110
 
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,
115
124
  maxParallelDials: DEFAULT_MAX_PARALLEL_DIALS,
116
- ...options,
125
+ ...options
117
126
  };
118
127
 
119
128
  this.keepAliveManager = new KeepAliveManager(keepAliveOptions, relay);
@@ -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);
@@ -340,7 +344,11 @@ export class ConnectionManager
340
344
  void (async () => {
341
345
  const peerId = evt.detail;
342
346
 
343
- this.keepAliveManager.start(peerId, this.libp2p.services.ping);
347
+ this.keepAliveManager.start(
348
+ peerId,
349
+ this.libp2p.services.ping,
350
+ this.libp2p.peerStore
351
+ );
344
352
 
345
353
  const isBootstrap = (await this.getTagNamesForPeer(peerId)).includes(
346
354
  Tags.BOOTSTRAP
@@ -361,7 +369,7 @@ export class ConnectionManager
361
369
  new CustomEvent<PeerId>(
362
370
  EPeersByDiscoveryEvents.PEER_CONNECT_BOOTSTRAP,
363
371
  {
364
- detail: peerId,
372
+ detail: peerId
365
373
  }
366
374
  )
367
375
  );
@@ -371,7 +379,7 @@ export class ConnectionManager
371
379
  new CustomEvent<PeerId>(
372
380
  EPeersByDiscoveryEvents.PEER_CONNECT_PEER_EXCHANGE,
373
381
  {
374
- detail: peerId,
382
+ detail: peerId
375
383
  }
376
384
  )
377
385
  );
@@ -382,19 +390,58 @@ export class ConnectionManager
382
390
  return (evt: CustomEvent<PeerId>): void => {
383
391
  this.keepAliveManager.stop(evt.detail);
384
392
  };
385
- },
393
+ }
386
394
  };
387
395
 
388
396
  /**
389
- * Checks if the peer is dialable based on the following conditions:
390
- * 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.
391
- * 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
392
402
  */
393
403
  private async shouldDialPeer(peerId: PeerId): Promise<boolean> {
404
+ // if we're already connected to the peer, don't dial
394
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
+ }
410
+
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
+ }
395
425
 
396
- if (isConnected) return false;
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
+ }
397
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> {
398
445
  const tagNames = await this.getTagNamesForPeer(peerId);
399
446
 
400
447
  const isBootstrap = tagNames.some((tagName) => tagName === Tags.BOOTSTRAP);
@@ -414,6 +461,23 @@ export class ConnectionManager
414
461
  return false;
415
462
  }
416
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
+
417
481
  /**
418
482
  * Fetches the tag names for a given peer
419
483
  */
@@ -426,4 +490,31 @@ export class ConnectionManager
426
490
  return [];
427
491
  }
428
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
+ }
429
520
  }
@@ -42,7 +42,7 @@ export class FilterSubscribeRpc {
42
42
  filterSubscribeType:
43
43
  proto.FilterSubscribeRequest.FilterSubscribeType.SUBSCRIBE,
44
44
  pubsubTopic,
45
- contentTopics,
45
+ contentTopics
46
46
  });
47
47
  }
48
48
 
@@ -55,7 +55,7 @@ export class FilterSubscribeRpc {
55
55
  filterSubscribeType:
56
56
  proto.FilterSubscribeRequest.FilterSubscribeType.UNSUBSCRIBE,
57
57
  pubsubTopic,
58
- contentTopics,
58
+ contentTopics
59
59
  });
60
60
  }
61
61
 
@@ -65,7 +65,7 @@ export class FilterSubscribeRpc {
65
65
  filterSubscribeType:
66
66
  proto.FilterSubscribeRequest.FilterSubscribeType.UNSUBSCRIBE_ALL,
67
67
  pubsubTopic,
68
- contentTopics: [],
68
+ contentTopics: []
69
69
  });
70
70
  }
71
71
 
@@ -75,7 +75,7 @@ export class FilterSubscribeRpc {
75
75
  filterSubscribeType:
76
76
  proto.FilterSubscribeRequest.FilterSubscribeType.SUBSCRIBER_PING,
77
77
  pubsubTopic: "",
78
- contentTopics: [],
78
+ contentTopics: []
79
79
  });
80
80
  }
81
81
 
@@ -1,7 +1,6 @@
1
- import { Stream } from "@libp2p/interface-connection";
2
- import type { PeerId } from "@libp2p/interface-peer-id";
3
- import type { Peer } from "@libp2p/interface-peer-store";
4
- import type { IncomingStreamData } from "@libp2p/interface-registrar";
1
+ import { Stream } from "@libp2p/interface/connection";
2
+ import type { Peer } from "@libp2p/interface/peer-store";
3
+ import type { IncomingStreamData } from "@libp2p/interface-internal/registrar";
5
4
  import type {
6
5
  Callback,
7
6
  ContentTopic,
@@ -14,12 +13,15 @@ import type {
14
13
  Libp2p,
15
14
  PeerIdStr,
16
15
  ProtocolCreateOptions,
17
- ProtocolOptions,
18
16
  PubSubTopic,
19
- Unsubscribe,
17
+ Unsubscribe
20
18
  } from "@waku/interfaces";
21
19
  import { WakuMessage } from "@waku/proto";
22
- import { groupByContentTopic, toAsyncIterator } from "@waku/utils";
20
+ import {
21
+ ensurePubsubTopicIsConfigured,
22
+ groupByContentTopic,
23
+ toAsyncIterator
24
+ } from "@waku/utils";
23
25
  import debug from "debug";
24
26
  import all from "it-all";
25
27
  import * as lp from "it-length-prefixed";
@@ -31,7 +33,7 @@ import { DefaultPubSubTopic } from "../constants.js";
31
33
  import {
32
34
  FilterPushRpc,
33
35
  FilterSubscribeResponse,
34
- FilterSubscribeRpc,
36
+ FilterSubscribeRpc
35
37
  } from "./filter_rpc.js";
36
38
 
37
39
  const log = debug("waku:filter:v2");
@@ -41,14 +43,14 @@ type SubscriptionCallback<T extends IDecodedMessage> = {
41
43
  callback: Callback<T>;
42
44
  };
43
45
 
44
- const FilterCodecs = {
46
+ export const FilterCodecs = {
45
47
  SUBSCRIBE: "/vac/waku/filter-subscribe/2.0.0-beta1",
46
- PUSH: "/vac/waku/filter-push/2.0.0-beta1",
48
+ PUSH: "/vac/waku/filter-push/2.0.0-beta1"
47
49
  };
48
50
 
49
51
  class Subscription {
50
52
  private readonly peer: Peer;
51
- private readonly pubSubTopic: PubSubTopic;
53
+ private readonly pubsubTopic: PubSubTopic;
52
54
  private newStream: (peer: Peer) => Promise<Stream>;
53
55
 
54
56
  private subscriptionCallbacks: Map<
@@ -57,12 +59,12 @@ class Subscription {
57
59
  >;
58
60
 
59
61
  constructor(
60
- pubSubTopic: PubSubTopic,
62
+ pubsubTopic: PubSubTopic,
61
63
  remotePeer: Peer,
62
64
  newStream: (peer: Peer) => Promise<Stream>
63
65
  ) {
64
66
  this.peer = remotePeer;
65
- this.pubSubTopic = pubSubTopic;
67
+ this.pubsubTopic = pubsubTopic;
66
68
  this.newStream = newStream;
67
69
  this.subscriptionCallbacks = new Map();
68
70
  }
@@ -78,7 +80,7 @@ class Subscription {
78
80
  const stream = await this.newStream(this.peer);
79
81
 
80
82
  const request = FilterSubscribeRpc.createSubscribeRequest(
81
- this.pubSubTopic,
83
+ this.pubsubTopic,
82
84
  contentTopics
83
85
  );
84
86
 
@@ -91,6 +93,12 @@ class Subscription {
91
93
  async (source) => await all(source)
92
94
  );
93
95
 
96
+ if (!res || !res.length) {
97
+ throw Error(
98
+ `No response received for request ${request.requestId}: ${res}`
99
+ );
100
+ }
101
+
94
102
  const { statusCode, requestId, statusDesc } =
95
103
  FilterSubscribeResponse.decode(res[0].slice());
96
104
 
@@ -125,7 +133,7 @@ class Subscription {
125
133
  // Decoder that decode to different implementations of `IDecodedMessage`
126
134
  const subscriptionCallback = {
127
135
  decoders,
128
- callback,
136
+ callback
129
137
  } as unknown as SubscriptionCallback<IDecodedMessage>;
130
138
 
131
139
  // The callback and decoder may override previous values, this is on
@@ -137,7 +145,7 @@ class Subscription {
137
145
  async unsubscribe(contentTopics: ContentTopic[]): Promise<void> {
138
146
  const stream = await this.newStream(this.peer);
139
147
  const unsubscribeRequest = FilterSubscribeRpc.createUnsubscribeRequest(
140
- this.pubSubTopic,
148
+ this.pubsubTopic,
141
149
  contentTopics
142
150
  );
143
151
 
@@ -186,7 +194,7 @@ class Subscription {
186
194
  const stream = await this.newStream(this.peer);
187
195
 
188
196
  const request = FilterSubscribeRpc.createUnsubscribeAllRequest(
189
- this.pubSubTopic
197
+ this.pubsubTopic
190
198
  );
191
199
 
192
200
  try {
@@ -221,67 +229,72 @@ class Subscription {
221
229
  log("No subscription callback available for ", contentTopic);
222
230
  return;
223
231
  }
224
- await pushMessage(subscriptionCallback, this.pubSubTopic, message);
232
+ await pushMessage(subscriptionCallback, this.pubsubTopic, message);
225
233
  }
226
234
  }
227
235
 
228
236
  class Filter extends BaseProtocol implements IReceiver {
229
- private readonly options: ProtocolCreateOptions;
237
+ private readonly pubsubTopics: PubSubTopic[] = [];
230
238
  private activeSubscriptions = new Map<string, Subscription>();
239
+ private readonly NUM_PEERS_PROTOCOL = 1;
231
240
 
232
241
  private getActiveSubscription(
233
- pubSubTopic: PubSubTopic,
242
+ pubsubTopic: PubSubTopic,
234
243
  peerIdStr: PeerIdStr
235
244
  ): Subscription | undefined {
236
- return this.activeSubscriptions.get(`${pubSubTopic}_${peerIdStr}`);
245
+ return this.activeSubscriptions.get(`${pubsubTopic}_${peerIdStr}`);
237
246
  }
238
247
 
239
248
  private setActiveSubscription(
240
- pubSubTopic: PubSubTopic,
249
+ pubsubTopic: PubSubTopic,
241
250
  peerIdStr: PeerIdStr,
242
251
  subscription: Subscription
243
252
  ): Subscription {
244
- this.activeSubscriptions.set(`${pubSubTopic}_${peerIdStr}`, subscription);
253
+ this.activeSubscriptions.set(`${pubsubTopic}_${peerIdStr}`, subscription);
245
254
  return subscription;
246
255
  }
247
256
 
248
257
  constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
249
258
  super(FilterCodecs.SUBSCRIBE, libp2p.components);
250
259
 
260
+ this.pubsubTopics = options?.pubsubTopics || [DefaultPubSubTopic];
261
+
251
262
  libp2p.handle(FilterCodecs.PUSH, this.onRequest.bind(this)).catch((e) => {
252
263
  log("Failed to register ", FilterCodecs.PUSH, e);
253
264
  });
254
265
 
255
266
  this.activeSubscriptions = new Map();
256
-
257
- this.options = options ?? {};
258
267
  }
259
268
 
260
269
  async createSubscription(
261
- pubSubTopic?: string,
262
- peerId?: PeerId
270
+ pubsubTopic: string = DefaultPubSubTopic
263
271
  ): Promise<Subscription> {
264
- const _pubSubTopic =
265
- pubSubTopic ?? this.options.pubSubTopic ?? DefaultPubSubTopic;
272
+ ensurePubsubTopicIsConfigured(pubsubTopic, this.pubsubTopics);
266
273
 
267
- const peer = await this.getPeer(peerId);
274
+ //TODO: get a relevant peer for the topic/shard
275
+ // https://github.com/waku-org/js-waku/pull/1586#discussion_r1336428230
276
+ const peer = (
277
+ await this.getPeers({
278
+ maxBootstrapPeers: 1,
279
+ numPeers: this.NUM_PEERS_PROTOCOL
280
+ })
281
+ )[0];
268
282
 
269
283
  const subscription =
270
- this.getActiveSubscription(_pubSubTopic, peer.id.toString()) ??
284
+ this.getActiveSubscription(pubsubTopic, peer.id.toString()) ??
271
285
  this.setActiveSubscription(
272
- _pubSubTopic,
286
+ pubsubTopic,
273
287
  peer.id.toString(),
274
- new Subscription(_pubSubTopic, peer, this.newStream.bind(this, peer))
288
+ new Subscription(pubsubTopic, peer, this.getStream.bind(this, peer))
275
289
  );
276
290
 
277
291
  return subscription;
278
292
  }
279
293
 
280
294
  public toSubscriptionIterator<T extends IDecodedMessage>(
281
- decoders: IDecoder<T> | IDecoder<T>[],
282
- opts?: ProtocolOptions | undefined
295
+ decoders: IDecoder<T> | IDecoder<T>[]
283
296
  ): Promise<IAsyncIterator<T>> {
284
- return toAsyncIterator(this, decoders, opts);
297
+ return toAsyncIterator(this, decoders);
285
298
  }
286
299
 
287
300
  /**
@@ -301,10 +314,9 @@ class Filter extends BaseProtocol implements IReceiver {
301
314
  */
302
315
  async subscribe<T extends IDecodedMessage>(
303
316
  decoders: IDecoder<T> | IDecoder<T>[],
304
- callback: Callback<T>,
305
- opts?: ProtocolOptions
317
+ callback: Callback<T>
306
318
  ): Promise<Unsubscribe> {
307
- const subscription = await this.createSubscription(undefined, opts?.peerId);
319
+ const subscription = await this.createSubscription();
308
320
 
309
321
  await subscription.subscribe(decoders, callback);
310
322
 
@@ -373,7 +385,7 @@ export function wakuFilter(
373
385
 
374
386
  async function pushMessage<T extends IDecodedMessage>(
375
387
  subscriptionCallback: SubscriptionCallback<T>,
376
- pubSubTopic: PubSubTopic,
388
+ pubsubTopic: PubSubTopic,
377
389
  message: WakuMessage
378
390
  ): Promise<void> {
379
391
  const { decoders, callback } = subscriptionCallback;
@@ -387,7 +399,7 @@ async function pushMessage<T extends IDecodedMessage>(
387
399
  try {
388
400
  const decodePromises = decoders.map((dec) =>
389
401
  dec
390
- .fromProtoObj(pubSubTopic, message as IProtoMessage)
402
+ .fromProtoObj(pubsubTopic, message as IProtoMessage)
391
403
  .then((decoded) => decoded || Promise.reject("Decoding failed"))
392
404
  );
393
405