@waku/core 0.0.26 → 0.0.28-070b625.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 (66) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/bundle/{base_protocol-pDODy0G6.js → base_protocol-D0Zdzb-v.js} +134 -89
  3. package/bundle/{browser-mTOOnVZp.js → browser-DoQRY-an.js} +518 -712
  4. package/bundle/{index-cmONXM-V.js → index-BJwgMx4y.js} +116 -88
  5. package/bundle/index.js +2967 -21667
  6. package/bundle/lib/base_protocol.js +3 -3
  7. package/bundle/lib/message/version_0.js +3 -3
  8. package/bundle/lib/predefined_bootstrap_nodes.js +17 -17
  9. package/bundle/version_0-C6o0DvNW.js +4055 -0
  10. package/dist/.tsbuildinfo +1 -1
  11. package/dist/index.d.ts +3 -6
  12. package/dist/index.js +3 -6
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/base_protocol.d.ts +15 -13
  15. package/dist/lib/base_protocol.js +35 -22
  16. package/dist/lib/base_protocol.js.map +1 -1
  17. package/dist/lib/connection_manager.d.ts +2 -2
  18. package/dist/lib/connection_manager.js +16 -6
  19. package/dist/lib/connection_manager.js.map +1 -1
  20. package/dist/lib/filter/index.d.ts +1 -1
  21. package/dist/lib/filter/index.js +144 -82
  22. package/dist/lib/filter/index.js.map +1 -1
  23. package/dist/lib/filterPeers.d.ts +8 -5
  24. package/dist/lib/filterPeers.js +12 -5
  25. package/dist/lib/filterPeers.js.map +1 -1
  26. package/dist/lib/keep_alive_manager.d.ts +2 -3
  27. package/dist/lib/keep_alive_manager.js.map +1 -1
  28. package/dist/lib/light_push/index.d.ts +12 -2
  29. package/dist/lib/light_push/index.js +80 -80
  30. package/dist/lib/light_push/index.js.map +1 -1
  31. package/dist/lib/message/version_0.js +1 -1
  32. package/dist/lib/message/version_0.js.map +1 -1
  33. package/dist/lib/metadata/index.d.ts +2 -2
  34. package/dist/lib/metadata/index.js +58 -16
  35. package/dist/lib/metadata/index.js.map +1 -1
  36. package/dist/lib/predefined_bootstrap_nodes.d.ts +11 -11
  37. package/dist/lib/predefined_bootstrap_nodes.js +16 -16
  38. package/dist/lib/predefined_bootstrap_nodes.js.map +1 -1
  39. package/dist/lib/store/history_rpc.js +1 -1
  40. package/dist/lib/store/history_rpc.js.map +1 -1
  41. package/dist/lib/store/index.d.ts +14 -6
  42. package/dist/lib/store/index.js +51 -235
  43. package/dist/lib/store/index.js.map +1 -1
  44. package/dist/lib/stream_manager.d.ts +2 -2
  45. package/dist/lib/stream_manager.js.map +1 -1
  46. package/dist/lib/wait_for_remote_peer.d.ts +1 -1
  47. package/dist/lib/wait_for_remote_peer.js +42 -10
  48. package/dist/lib/wait_for_remote_peer.js.map +1 -1
  49. package/package.json +1 -127
  50. package/src/index.ts +3 -7
  51. package/src/lib/base_protocol.ts +57 -37
  52. package/src/lib/connection_manager.ts +17 -10
  53. package/src/lib/filter/index.ts +234 -136
  54. package/src/lib/filterPeers.ts +15 -7
  55. package/src/lib/keep_alive_manager.ts +2 -3
  56. package/src/lib/light_push/index.ts +104 -124
  57. package/src/lib/metadata/index.ts +92 -30
  58. package/src/lib/predefined_bootstrap_nodes.ts +22 -22
  59. package/src/lib/store/index.ts +79 -344
  60. package/src/lib/stream_manager.ts +2 -3
  61. package/src/lib/wait_for_remote_peer.ts +68 -12
  62. package/bundle/version_0-LQTFNC7k.js +0 -5008
  63. package/dist/lib/waku.d.ts +0 -57
  64. package/dist/lib/waku.js +0 -130
  65. package/dist/lib/waku.js.map +0 -1
  66. package/src/lib/waku.ts +0 -214
@@ -1,18 +1,14 @@
1
- import type { Stream } from "@libp2p/interface/connection";
2
- import { sha256 } from "@noble/hashes/sha256";
1
+ import type { Peer } from "@libp2p/interface";
3
2
  import {
4
3
  Cursor,
5
4
  IDecodedMessage,
6
5
  IDecoder,
7
- IStore,
6
+ IStoreCore,
8
7
  Libp2p,
9
- ProtocolCreateOptions,
10
- PubsubTopic
8
+ ProtocolCreateOptions
11
9
  } from "@waku/interfaces";
12
10
  import { proto_store as proto } from "@waku/proto";
13
- import { ensurePubsubTopicIsConfigured, isDefined } from "@waku/utils";
14
11
  import { Logger } from "@waku/utils";
15
- import { concat, utf8ToBytes } from "@waku/utils/bytes";
16
12
  import all from "it-all";
17
13
  import * as lp from "it-length-prefixed";
18
14
  import { pipe } from "it-pipe";
@@ -29,9 +25,7 @@ const log = new Logger("store");
29
25
 
30
26
  export const StoreCodec = "/vac/waku/store/2.0.0-beta4";
31
27
 
32
- export const DefaultPageSize = 10;
33
-
34
- export { PageDirection };
28
+ export { PageDirection, Params };
35
29
 
36
30
  export interface TimeFilter {
37
31
  startTime: Date;
@@ -73,363 +67,104 @@ export interface QueryOptions {
73
67
  *
74
68
  * The Waku Store protocol can be used to retrieved historical messages.
75
69
  */
76
- class Store extends BaseProtocol implements IStore {
77
- private readonly pubsubTopics: PubsubTopic[];
78
- private readonly NUM_PEERS_PROTOCOL = 1;
79
-
70
+ export class StoreCore extends BaseProtocol implements IStoreCore {
80
71
  constructor(libp2p: Libp2p, options?: ProtocolCreateOptions) {
81
- super(StoreCodec, libp2p.components);
82
- this.pubsubTopics = this.initializePubsubTopic(options);
83
- }
84
-
85
- /**
86
- * Processes messages based on the provided callback and options.
87
- * @private
88
- */
89
- private async processMessages<T extends IDecodedMessage>(
90
- messages: Promise<T | undefined>[],
91
- callback: (message: T) => Promise<void | boolean> | boolean | void,
92
- options?: QueryOptions
93
- ): Promise<boolean> {
94
- let abort = false;
95
- const messagesOrUndef: Array<T | undefined> = await Promise.all(messages);
96
- let processedMessages: Array<T> = messagesOrUndef.filter(isDefined);
97
-
98
- if (this.shouldReverseOrder(options)) {
99
- processedMessages = processedMessages.reverse();
100
- }
101
-
102
- await Promise.all(
103
- processedMessages.map(async (msg) => {
104
- if (msg && !abort) {
105
- abort = Boolean(await callback(msg));
106
- }
107
- })
108
- );
109
-
110
- return abort;
111
- }
112
-
113
- /**
114
- * Determines whether to reverse the order of messages based on the provided options.
115
- *
116
- * Messages in pages are ordered from oldest (first) to most recent (last).
117
- * https://github.com/vacp2p/rfc/issues/533
118
- *
119
- * @private
120
- */
121
- private shouldReverseOrder(options?: QueryOptions): boolean {
122
- return (
123
- typeof options?.pageDirection === "undefined" ||
124
- options?.pageDirection === PageDirection.BACKWARD
125
- );
126
- }
127
-
128
- /**
129
- * @deprecated Use `queryWithOrderedCallback` instead
130
- **/
131
- queryOrderedCallback = this.queryWithOrderedCallback;
132
-
133
- /**
134
- * Do a query to a Waku Store to retrieve historical/missed messages.
135
- *
136
- * The callback function takes a `WakuMessage` in input,
137
- * messages are processed in order:
138
- * - oldest to latest if `options.pageDirection` == { @link PageDirection.FORWARD }
139
- * - latest to oldest if `options.pageDirection` == { @link PageDirection.BACKWARD }
140
- *
141
- * The ordering may affect performance.
142
- * The ordering depends on the behavior of the remote store node.
143
- * If strong ordering is needed, you may need to handle this at application level
144
- * and set your own timestamps too (the WakuMessage timestamps are not certified).
145
- *
146
- * @throws If not able to reach a Waku Store peer to query,
147
- * or if an error is encountered when processing the reply,
148
- * or if two decoders with the same content topic are passed.
149
- */
150
- async queryWithOrderedCallback<T extends IDecodedMessage>(
151
- decoders: IDecoder<T>[],
152
- callback: (message: T) => Promise<void | boolean> | boolean | void,
153
- options?: QueryOptions
154
- ): Promise<void> {
155
- for await (const promises of this.queryGenerator(decoders, options)) {
156
- if (await this.processMessages(promises, callback, options)) break;
157
- }
72
+ super(StoreCodec, libp2p.components, log, options!.pubsubTopics!, options);
158
73
  }
159
74
 
160
- /**
161
- * Do a query to a Waku Store to retrieve historical/missed messages.
162
- * The callback function takes a `Promise<WakuMessage>` in input,
163
- * useful if messages need to be decrypted and performance matters.
164
- *
165
- * The order of the messages passed to the callback is as follows:
166
- * - within a page, messages are expected to be ordered from oldest to most recent
167
- * - pages direction depends on { @link QueryOptions.pageDirection }
168
- *
169
- * Do note that the resolution of the `Promise<WakuMessage | undefined` may
170
- * break the order as it may rely on the browser decryption API, which in turn,
171
- * may have a different speed depending on the type of decryption.
172
- *
173
- * @throws If not able to reach a Waku Store peer to query,
174
- * or if an error is encountered when processing the reply,
175
- * or if two decoders with the same content topic are passed.
176
- */
177
- async queryWithPromiseCallback<T extends IDecodedMessage>(
178
- decoders: IDecoder<T>[],
179
- callback: (
180
- message: Promise<T | undefined>
181
- ) => Promise<void | boolean> | boolean | void,
182
- options?: QueryOptions
183
- ): Promise<void> {
184
- let abort = false;
185
- for await (const page of this.queryGenerator(decoders, options)) {
186
- const _promises = page.map(async (msgPromise) => {
187
- if (abort) return;
188
- abort = Boolean(await callback(msgPromise));
189
- });
190
-
191
- await Promise.all(_promises);
192
- if (abort) break;
193
- }
194
- }
195
-
196
- /**
197
- * Do a query to a Waku Store to retrieve historical/missed messages.
198
- *
199
- * This is a generator, useful if you want most control on how messages
200
- * are processed.
201
- *
202
- * The order of the messages returned by the remote Waku node SHOULD BE
203
- * as follows:
204
- * - within a page, messages SHOULD be ordered from oldest to most recent
205
- * - pages direction depends on { @link QueryOptions.pageDirection }
206
- * @throws If not able to reach a Waku Store peer to query,
207
- * or if an error is encountered when processing the reply,
208
- * or if two decoders with the same content topic are passed.
209
- *
210
- * This API only supports querying a single pubsub topic at a time.
211
- * If multiple decoders are provided, they must all have the same pubsub topic.
212
- * @throws If multiple decoders with different pubsub topics are provided.
213
- * @throws If no decoders are provided.
214
- * @throws If no decoders are found for the provided pubsub topic.
215
- */
216
- async *queryGenerator<T extends IDecodedMessage>(
217
- decoders: IDecoder<T>[],
218
- options?: QueryOptions
75
+ async *queryPerPage<T extends IDecodedMessage>(
76
+ queryOpts: Params,
77
+ decoders: Map<string, IDecoder<T>>,
78
+ peer: Peer
219
79
  ): AsyncGenerator<Promise<T | undefined>[]> {
220
- if (decoders.length === 0) {
221
- throw new Error("No decoders provided");
222
- }
223
-
224
- let startTime, endTime;
225
-
226
- if (options?.timeFilter) {
227
- startTime = options.timeFilter.startTime;
228
- endTime = options.timeFilter.endTime;
229
- }
230
-
231
- // convert array to set to remove duplicates
232
- const uniquePubsubTopicsInQuery = Array.from(
233
- new Set(decoders.map((decoder) => decoder.pubsubTopic))
234
- );
235
-
236
- // If multiple pubsub topics are provided, throw an error
237
- if (uniquePubsubTopicsInQuery.length > 1) {
238
- throw new Error(
239
- "API does not support querying multiple pubsub topics at once"
240
- );
241
- }
242
-
243
- // we can be certain that there is only one pubsub topic in the query
244
- const pubsubTopicForQuery = uniquePubsubTopicsInQuery[0];
245
-
246
- ensurePubsubTopicIsConfigured(pubsubTopicForQuery, this.pubsubTopics);
247
-
248
- // check that the pubsubTopic from the Cursor and Decoder match
249
80
  if (
250
- options?.cursor?.pubsubTopic &&
251
- options.cursor.pubsubTopic !== pubsubTopicForQuery
81
+ queryOpts.contentTopics.toString() !==
82
+ Array.from(decoders.keys()).toString()
252
83
  ) {
253
84
  throw new Error(
254
- `Cursor pubsub topic (${options?.cursor?.pubsubTopic}) does not match decoder pubsub topic (${pubsubTopicForQuery})`
85
+ "Internal error, the decoders should match the query's content topics"
255
86
  );
256
87
  }
257
88
 
258
- const decodersAsMap = new Map();
259
- decoders.forEach((dec) => {
260
- if (decodersAsMap.has(dec.contentTopic)) {
261
- throw new Error(
262
- "API does not support different decoder per content topic"
263
- );
264
- }
265
- decodersAsMap.set(dec.contentTopic, dec);
266
- });
89
+ let currentCursor = queryOpts.cursor;
90
+ while (true) {
91
+ queryOpts.cursor = currentCursor;
267
92
 
268
- const contentTopics = decoders
269
- .filter((decoder) => decoder.pubsubTopic === pubsubTopicForQuery)
270
- .map((dec) => dec.contentTopic);
93
+ const historyRpcQuery = HistoryRpc.createQuery(queryOpts);
271
94
 
272
- if (contentTopics.length === 0) {
273
- throw new Error("No decoders found for topic " + pubsubTopicForQuery);
274
- }
275
-
276
- const queryOpts = Object.assign(
277
- {
278
- pubsubTopic: pubsubTopicForQuery,
279
- pageDirection: PageDirection.BACKWARD,
280
- pageSize: DefaultPageSize
281
- },
282
- options,
283
- { contentTopics, startTime, endTime }
284
- );
285
-
286
- const peer = (
287
- await this.getPeers({
288
- numPeers: this.NUM_PEERS_PROTOCOL,
289
- maxBootstrapPeers: 1
290
- })
291
- )[0];
292
-
293
- for await (const messages of paginate<T>(
294
- this.getStream.bind(this, peer),
295
- queryOpts,
296
- decodersAsMap,
297
- options?.cursor
298
- )) {
299
- yield messages;
300
- }
301
- }
302
- }
95
+ const stream = await this.getStream(peer);
303
96
 
304
- async function* paginate<T extends IDecodedMessage>(
305
- streamFactory: () => Promise<Stream>,
306
- queryOpts: Params,
307
- decoders: Map<string, IDecoder<T>>,
308
- cursor?: Cursor
309
- ): AsyncGenerator<Promise<T | undefined>[]> {
310
- if (
311
- queryOpts.contentTopics.toString() !==
312
- Array.from(decoders.keys()).toString()
313
- ) {
314
- throw new Error(
315
- "Internal error, the decoders should match the query's content topics"
316
- );
317
- }
318
-
319
- let currentCursor = cursor;
320
- while (true) {
321
- queryOpts.cursor = currentCursor;
322
-
323
- const historyRpcQuery = HistoryRpc.createQuery(queryOpts);
324
-
325
- log.info(
326
- "Querying store peer",
327
- `for (${queryOpts.pubsubTopic})`,
328
- queryOpts.contentTopics
329
- );
330
-
331
- const stream = await streamFactory();
332
-
333
- const res = await pipe(
334
- [historyRpcQuery.encode()],
335
- lp.encode,
336
- stream,
337
- lp.decode,
338
- async (source) => await all(source)
339
- );
340
-
341
- const bytes = new Uint8ArrayList();
342
- res.forEach((chunk) => {
343
- bytes.append(chunk);
344
- });
345
-
346
- const reply = historyRpcQuery.decode(bytes);
97
+ const res = await pipe(
98
+ [historyRpcQuery.encode()],
99
+ lp.encode,
100
+ stream,
101
+ lp.decode,
102
+ async (source) => await all(source)
103
+ );
347
104
 
348
- if (!reply.response) {
349
- log.warn("Stopping pagination due to store `response` field missing");
350
- break;
351
- }
105
+ const bytes = new Uint8ArrayList();
106
+ res.forEach((chunk) => {
107
+ bytes.append(chunk);
108
+ });
352
109
 
353
- const response = reply.response as proto.HistoryResponse;
110
+ const reply = historyRpcQuery.decode(bytes);
354
111
 
355
- if (response.error && response.error !== HistoryError.NONE) {
356
- throw "History response contains an Error: " + response.error;
357
- }
112
+ if (!reply.response) {
113
+ log.warn("Stopping pagination due to store `response` field missing");
114
+ break;
115
+ }
358
116
 
359
- if (!response.messages || !response.messages.length) {
360
- log.warn(
361
- "Stopping pagination due to store `response.messages` field missing or empty"
362
- );
363
- break;
364
- }
117
+ const response = reply.response as proto.HistoryResponse;
365
118
 
366
- log.error(`${response.messages.length} messages retrieved from store`);
119
+ if (response.error && response.error !== HistoryError.NONE) {
120
+ throw "History response contains an Error: " + response.error;
121
+ }
367
122
 
368
- yield response.messages.map((protoMsg) => {
369
- const contentTopic = protoMsg.contentTopic;
370
- if (typeof contentTopic !== "undefined") {
371
- const decoder = decoders.get(contentTopic);
372
- if (decoder) {
373
- return decoder.fromProtoObj(
374
- queryOpts.pubsubTopic,
375
- toProtoMessage(protoMsg)
376
- );
377
- }
123
+ if (!response.messages || !response.messages.length) {
124
+ log.warn(
125
+ "Stopping pagination due to store `response.messages` field missing or empty"
126
+ );
127
+ break;
378
128
  }
379
- return Promise.resolve(undefined);
380
- });
381
129
 
382
- const nextCursor = response.pagingInfo?.cursor;
383
- if (typeof nextCursor === "undefined") {
384
- // If the server does not return cursor then there is an issue,
385
- // Need to abort, or we end up in an infinite loop
386
- log.warn(
387
- "Stopping pagination due to `response.pagingInfo.cursor` missing from store response"
388
- );
389
- break;
390
- }
130
+ log.error(`${response.messages.length} messages retrieved from store`);
131
+
132
+ yield response.messages.map((protoMsg) => {
133
+ const contentTopic = protoMsg.contentTopic;
134
+ if (typeof contentTopic !== "undefined") {
135
+ const decoder = decoders.get(contentTopic);
136
+ if (decoder) {
137
+ return decoder.fromProtoObj(
138
+ queryOpts.pubsubTopic,
139
+ toProtoMessage(protoMsg)
140
+ );
141
+ }
142
+ }
143
+ return Promise.resolve(undefined);
144
+ });
391
145
 
392
- currentCursor = nextCursor;
146
+ const nextCursor = response.pagingInfo?.cursor;
147
+ if (typeof nextCursor === "undefined") {
148
+ // If the server does not return cursor then there is an issue,
149
+ // Need to abort, or we end up in an infinite loop
150
+ log.warn(
151
+ "Stopping pagination due to `response.pagingInfo.cursor` missing from store response"
152
+ );
153
+ break;
154
+ }
393
155
 
394
- const responsePageSize = response.pagingInfo?.pageSize;
395
- const queryPageSize = historyRpcQuery.query?.pagingInfo?.pageSize;
396
- if (
397
- // Response page size smaller than query, meaning this is the last page
398
- responsePageSize &&
399
- queryPageSize &&
400
- responsePageSize < queryPageSize
401
- ) {
402
- break;
156
+ currentCursor = nextCursor;
157
+
158
+ const responsePageSize = response.pagingInfo?.pageSize;
159
+ const queryPageSize = historyRpcQuery.query?.pagingInfo?.pageSize;
160
+ if (
161
+ // Response page size smaller than query, meaning this is the last page
162
+ responsePageSize &&
163
+ queryPageSize &&
164
+ responsePageSize < queryPageSize
165
+ ) {
166
+ break;
167
+ }
403
168
  }
404
169
  }
405
170
  }
406
-
407
- export async function createCursor(message: IDecodedMessage): Promise<Cursor> {
408
- if (
409
- !message ||
410
- !message.timestamp ||
411
- !message.payload ||
412
- !message.contentTopic
413
- ) {
414
- throw new Error("Message is missing required fields");
415
- }
416
-
417
- const contentTopicBytes = utf8ToBytes(message.contentTopic);
418
-
419
- const digest = sha256(concat([contentTopicBytes, message.payload]));
420
-
421
- const messageTime = BigInt(message.timestamp.getTime()) * BigInt(1000000);
422
-
423
- return {
424
- digest,
425
- pubsubTopic: message.pubsubTopic,
426
- senderTime: messageTime,
427
- receiverTime: messageTime
428
- };
429
- }
430
-
431
- export function wakuStore(
432
- init: Partial<ProtocolCreateOptions> = {}
433
- ): (libp2p: Libp2p) => IStore {
434
- return (libp2p: Libp2p) => new Store(libp2p, init);
435
- }
@@ -1,6 +1,5 @@
1
- import type { PeerUpdate } from "@libp2p/interface";
2
- import type { Stream } from "@libp2p/interface/connection";
3
- import { Peer } from "@libp2p/interface/peer-store";
1
+ import type { PeerUpdate, Stream } from "@libp2p/interface";
2
+ import { Peer } from "@libp2p/interface";
4
3
  import { Libp2p } from "@waku/interfaces";
5
4
  import { Logger } from "@waku/utils";
6
5
  import { selectConnection } from "@waku/utils/libp2p";
@@ -1,15 +1,20 @@
1
1
  import type { IdentifyResult } from "@libp2p/interface";
2
- import type { IBaseProtocol, IRelay, Waku } from "@waku/interfaces";
2
+ import type {
3
+ IBaseProtocolCore,
4
+ IMetadata,
5
+ IRelay,
6
+ Waku
7
+ } from "@waku/interfaces";
3
8
  import { Protocols } from "@waku/interfaces";
4
9
  import { Logger } from "@waku/utils";
5
10
  import { pEvent } from "p-event";
6
-
7
11
  const log = new Logger("wait-for-remote-peer");
8
12
 
13
+ //TODO: move this function within the Waku class: https://github.com/waku-org/js-waku/issues/1761
9
14
  /**
10
15
  * Wait for a remote peer to be ready given the passed protocols.
11
16
  * Must be used after attempting to connect to nodes, using
12
- * {@link @waku/core!WakuNode.dial} or a bootstrap method with
17
+ * {@link @waku/sdk!WakuNode.dial} or a bootstrap method with
13
18
  * {@link @waku/sdk!createLightNode}.
14
19
  *
15
20
  * If the passed protocols is a GossipSub protocol, then it resolves only once
@@ -45,19 +50,28 @@ export async function waitForRemotePeer(
45
50
  if (protocols.includes(Protocols.Store)) {
46
51
  if (!waku.store)
47
52
  throw new Error("Cannot wait for Store peer: protocol not mounted");
48
- promises.push(waitForConnectedPeer(waku.store));
53
+ promises.push(
54
+ waitForConnectedPeer(waku.store.protocol, waku.libp2p.services.metadata)
55
+ );
49
56
  }
50
57
 
51
58
  if (protocols.includes(Protocols.LightPush)) {
52
59
  if (!waku.lightPush)
53
60
  throw new Error("Cannot wait for LightPush peer: protocol not mounted");
54
- promises.push(waitForConnectedPeer(waku.lightPush));
61
+ promises.push(
62
+ waitForConnectedPeer(
63
+ waku.lightPush.protocol,
64
+ waku.libp2p.services.metadata
65
+ )
66
+ );
55
67
  }
56
68
 
57
69
  if (protocols.includes(Protocols.Filter)) {
58
70
  if (!waku.filter)
59
71
  throw new Error("Cannot wait for Filter peer: protocol not mounted");
60
- promises.push(waitForConnectedPeer(waku.filter));
72
+ promises.push(
73
+ waitForConnectedPeer(waku.filter, waku.libp2p.services.metadata)
74
+ );
61
75
  }
62
76
 
63
77
  if (timeoutMs) {
@@ -71,23 +85,65 @@ export async function waitForRemotePeer(
71
85
  }
72
86
  }
73
87
 
88
+ //TODO: move this function within protocol SDK class: https://github.com/waku-org/js-waku/issues/1761
74
89
  /**
75
90
  * Wait for a peer with the given protocol to be connected.
91
+ * If sharding is enabled on the node, it will also wait for the peer to be confirmed by the metadata service.
76
92
  */
77
- async function waitForConnectedPeer(protocol: IBaseProtocol): Promise<void> {
93
+ async function waitForConnectedPeer(
94
+ protocol: IBaseProtocolCore,
95
+ metadataService?: IMetadata
96
+ ): Promise<void> {
78
97
  const codec = protocol.multicodec;
79
- const peers = await protocol.peers();
98
+ const peers = await protocol.connectedPeers();
80
99
 
81
100
  if (peers.length) {
82
- log.info(`${codec} peer found: `, peers[0].id.toString());
83
- return;
101
+ if (!metadataService) {
102
+ log.info(`${codec} peer found: `, peers[0].id.toString());
103
+ return;
104
+ }
105
+
106
+ // once a peer is connected, we need to confirm the metadata handshake with at least one of those peers if sharding is enabled
107
+ try {
108
+ await Promise.any(
109
+ peers.map((peer) => metadataService.confirmOrAttemptHandshake(peer.id))
110
+ );
111
+ return;
112
+ } catch (e) {
113
+ if ((e as any).code === "ERR_CONNECTION_BEING_CLOSED")
114
+ log.error(
115
+ `Connection with the peer was closed and possibly because it's on a different shard. Error: ${e}`
116
+ );
117
+
118
+ log.error(`Error waiting for handshake confirmation: ${e}`);
119
+ }
84
120
  }
85
121
 
122
+ log.info(`Waiting for ${codec} peer`);
123
+
124
+ // else we'll just wait for the next peer to connect
86
125
  await new Promise<void>((resolve) => {
87
126
  const cb = (evt: CustomEvent<IdentifyResult>): void => {
88
127
  if (evt.detail?.protocols?.includes(codec)) {
89
- protocol.removeLibp2pEventListener("peer:identify", cb);
90
- resolve();
128
+ if (metadataService) {
129
+ metadataService
130
+ .confirmOrAttemptHandshake(evt.detail.peerId)
131
+ .then(() => {
132
+ protocol.removeLibp2pEventListener("peer:identify", cb);
133
+ resolve();
134
+ })
135
+ .catch((e) => {
136
+ if (e.code === "ERR_CONNECTION_BEING_CLOSED")
137
+ log.error(
138
+ `Connection with the peer was closed and possibly because it's on a different shard. Error: ${e}`
139
+ );
140
+
141
+ log.error(`Error waiting for handshake confirmation: ${e}`);
142
+ });
143
+ } else {
144
+ protocol.removeLibp2pEventListener("peer:identify", cb);
145
+ resolve();
146
+ }
91
147
  }
92
148
  };
93
149
  protocol.addLibp2pEventListener("peer:identify", cb);