@waku/core 0.0.36-f911bf8.0 → 0.0.37-7a9850d.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 (55) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bundle/index.js +1147 -613
  3. package/bundle/lib/message/version_0.js +1 -2
  4. package/bundle/{version_0-CiYGrPc2.js → version_0-9DPFjcJG.js} +1586 -10
  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_manager.d.ts +2 -1
  10. package/dist/lib/connection_manager/connection_manager.js +16 -8
  11. package/dist/lib/connection_manager/connection_manager.js.map +1 -1
  12. package/dist/lib/filter/filter.d.ts +8 -5
  13. package/dist/lib/filter/filter.js +33 -10
  14. package/dist/lib/filter/filter.js.map +1 -1
  15. package/dist/lib/light_push/light_push.d.ts +4 -3
  16. package/dist/lib/light_push/light_push.js +6 -4
  17. package/dist/lib/light_push/light_push.js.map +1 -1
  18. package/dist/lib/message/version_0.d.ts +3 -4
  19. package/dist/lib/message/version_0.js +1 -4
  20. package/dist/lib/message/version_0.js.map +1 -1
  21. package/dist/lib/message_hash/index.d.ts +1 -0
  22. package/dist/lib/message_hash/index.js +2 -0
  23. package/dist/lib/message_hash/index.js.map +1 -0
  24. package/dist/lib/message_hash/message_hash.d.ts +52 -0
  25. package/dist/lib/message_hash/message_hash.js +84 -0
  26. package/dist/lib/message_hash/message_hash.js.map +1 -0
  27. package/dist/lib/metadata/metadata.js +6 -4
  28. package/dist/lib/metadata/metadata.js.map +1 -1
  29. package/dist/lib/store/rpc.js +16 -10
  30. package/dist/lib/store/rpc.js.map +1 -1
  31. package/dist/lib/store/store.d.ts +5 -5
  32. package/dist/lib/store/store.js +19 -9
  33. package/dist/lib/store/store.js.map +1 -1
  34. package/dist/lib/stream_manager/stream_manager.d.ts +3 -4
  35. package/dist/lib/stream_manager/stream_manager.js +6 -8
  36. package/dist/lib/stream_manager/stream_manager.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/index.ts +2 -0
  39. package/src/lib/connection_manager/connection_manager.ts +24 -16
  40. package/src/lib/filter/filter.ts +50 -14
  41. package/src/lib/light_push/light_push.ts +8 -5
  42. package/src/lib/message/version_0.ts +3 -7
  43. package/src/lib/message_hash/index.ts +1 -0
  44. package/src/lib/message_hash/message_hash.ts +106 -0
  45. package/src/lib/metadata/metadata.ts +8 -5
  46. package/src/lib/store/rpc.ts +23 -19
  47. package/src/lib/store/store.ts +22 -11
  48. package/src/lib/stream_manager/stream_manager.ts +8 -6
  49. package/bundle/base_protocol-DvQrudwy.js +0 -152
  50. package/bundle/index-CTo1my9M.js +0 -1543
  51. package/bundle/lib/base_protocol.js +0 -2
  52. package/dist/lib/base_protocol.d.ts +0 -18
  53. package/dist/lib/base_protocol.js +0 -25
  54. package/dist/lib/base_protocol.js.map +0 -1
  55. package/src/lib/base_protocol.ts +0 -44
package/bundle/index.js CHANGED
@@ -1,8 +1,5 @@
1
- import { v as version_0, a as allocUnsafe, b as alloc, e as encodingLength$1, c as encode$2, d as decode$4, F as FilterSubscribeRequest, f as FilterSubscribeResponse$1, M as MessagePush, P as PushRpc$1, g as PushResponse, S as StoreQueryRequest$1, h as StoreQueryResponse$1, t as toString$1, i as bases, j as fromString, u as utf8ToBytes, k as createEncoder, p as pubsubTopicToSingleShardInfo, l as bytesToUtf8, s as shardInfoToPubsubTopics, W as WakuMetadataRequest, m as pubsubTopicsToShardInfo, n as WakuMetadataResponse } from './version_0-CiYGrPc2.js';
2
- export { o as createDecoder } from './version_0-CiYGrPc2.js';
3
- import { e as equals$2, c as coerce, b as base32, a as base58btc, d as base36, L as Logger, P as ProtocolError, T as Tags, E as EPeersByDiscoveryEvents, f as EConnectionStateEvents } from './index-CTo1my9M.js';
4
- import { B as BaseProtocol } from './base_protocol-DvQrudwy.js';
5
- export { S as StreamManager } from './base_protocol-DvQrudwy.js';
1
+ import { e as equals$2, c as coerce, b as base32, a as base58btc, d as base36, v as version_0, f as allocUnsafe, g as alloc, h as encodingLength$1, i as encode$2, j as decode$4, L as Logger, F as FilterSubscribeRequest, k as FilterSubscribeResponse$1, M as MessagePush, P as ProtocolError, l as PushRpc$1, m as PushResponse, S as StoreQueryRequest$1, n as StoreQueryResponse$1, o as bases, t as toString, p as fromString, q as base64url, r as encodeUint8Array, u as utf8ToBytes, s as createEncoder, w as pubsubTopicToSingleShardInfo, x as bytesToUtf8, T as Tags, E as EPeersByDiscoveryEvents, y as shardInfoToPubsubTopics, z as EConnectionStateEvents, W as WakuMetadataRequest, A as pubsubTopicsToShardInfo, B as WakuMetadataResponse, C as concat$1, D as sha256, G as bytesToHex, H as numberToBytes } from './version_0-9DPFjcJG.js';
2
+ export { I as createDecoder } from './version_0-9DPFjcJG.js';
6
3
 
7
4
  /* eslint-disable */
8
5
  var encode_1 = encode$1;
@@ -514,6 +511,10 @@ function encodeCID(version, code, multihash) {
514
511
  }
515
512
  const cidSymbol = Symbol.for('@ipld/js-cid/CID');
516
513
 
514
+ function isDefined(value) {
515
+ return Boolean(value);
516
+ }
517
+
517
518
  const MB = 1024 ** 2;
518
519
  const SIZE_CAP_IN_MB = 1;
519
520
  /**
@@ -2008,25 +2009,38 @@ function queuelessPushable() {
2008
2009
  function isAsyncIterable$1(thing) {
2009
2010
  return thing[Symbol.asyncIterator] != null;
2010
2011
  }
2011
- async function addAllToPushable(sources, output) {
2012
+ async function addAllToPushable(sources, output, signal) {
2012
2013
  try {
2013
2014
  await Promise.all(sources.map(async (source) => {
2014
2015
  for await (const item of source) {
2015
- await output.push(item);
2016
+ await output.push(item, {
2017
+ signal
2018
+ });
2019
+ signal.throwIfAborted();
2016
2020
  }
2017
2021
  }));
2018
- await output.end();
2022
+ await output.end(undefined, {
2023
+ signal
2024
+ });
2019
2025
  }
2020
2026
  catch (err) {
2021
- await output.end(err)
2027
+ await output.end(err, {
2028
+ signal
2029
+ })
2022
2030
  .catch(() => { });
2023
2031
  }
2024
2032
  }
2025
2033
  async function* mergeSources(sources) {
2034
+ const controller = new AbortController();
2026
2035
  const output = queuelessPushable();
2027
- addAllToPushable(sources, output)
2036
+ addAllToPushable(sources, output, controller.signal)
2028
2037
  .catch(() => { });
2029
- yield* output;
2038
+ try {
2039
+ yield* output;
2040
+ }
2041
+ finally {
2042
+ controller.abort();
2043
+ }
2030
2044
  }
2031
2045
  function* mergeSyncSources(syncSources) {
2032
2046
  for (const source of syncSources) {
@@ -2132,6 +2146,129 @@ const duplexPipelineFn = (duplex) => {
2132
2146
  };
2133
2147
  };
2134
2148
 
2149
+ function selectOpenConnection(connections) {
2150
+ return connections
2151
+ .filter((c) => c.status === "open")
2152
+ .sort((left, right) => right.timeline.open - left.timeline.open)
2153
+ .at(0);
2154
+ }
2155
+
2156
+ const STREAM_LOCK_KEY = "consumed";
2157
+ class StreamManager {
2158
+ multicodec;
2159
+ libp2p;
2160
+ log;
2161
+ ongoingCreation = new Set();
2162
+ streamPool = new Map();
2163
+ constructor(multicodec, libp2p) {
2164
+ this.multicodec = multicodec;
2165
+ this.libp2p = libp2p;
2166
+ this.log = new Logger(`stream-manager:${multicodec}`);
2167
+ this.libp2p.events.addEventListener("peer:update", this.handlePeerUpdateStreamPool);
2168
+ }
2169
+ async getStream(peerId) {
2170
+ const peerIdStr = peerId.toString();
2171
+ const scheduledStream = this.streamPool.get(peerIdStr);
2172
+ if (scheduledStream) {
2173
+ this.streamPool.delete(peerIdStr);
2174
+ await scheduledStream;
2175
+ }
2176
+ let stream = this.getOpenStreamForCodec(peerId);
2177
+ if (stream) {
2178
+ this.log.info(`Found existing stream peerId=${peerIdStr} multicodec=${this.multicodec}`);
2179
+ this.lockStream(peerIdStr, stream);
2180
+ return stream;
2181
+ }
2182
+ stream = await this.createStream(peerId);
2183
+ this.lockStream(peerIdStr, stream);
2184
+ return stream;
2185
+ }
2186
+ async createStream(peerId, retries = 0) {
2187
+ const connections = this.libp2p.connectionManager.getConnections(peerId);
2188
+ const connection = selectOpenConnection(connections);
2189
+ if (!connection) {
2190
+ throw new Error(`Failed to get a connection to the peer peerId=${peerId.toString()} multicodec=${this.multicodec}`);
2191
+ }
2192
+ let lastError;
2193
+ let stream;
2194
+ for (let i = 0; i < retries + 1; i++) {
2195
+ try {
2196
+ this.log.info(`Attempting to create a stream for peerId=${peerId.toString()} multicodec=${this.multicodec}`);
2197
+ stream = await connection.newStream(this.multicodec);
2198
+ this.log.info(`Created stream for peerId=${peerId.toString()} multicodec=${this.multicodec}`);
2199
+ break;
2200
+ }
2201
+ catch (error) {
2202
+ lastError = error;
2203
+ }
2204
+ }
2205
+ if (!stream) {
2206
+ throw new Error(`Failed to create a new stream for ${peerId.toString()} -- ` + lastError);
2207
+ }
2208
+ return stream;
2209
+ }
2210
+ async createStreamWithLock(peer) {
2211
+ const peerId = peer.id.toString();
2212
+ if (this.ongoingCreation.has(peerId)) {
2213
+ this.log.info(`Skipping creation of a stream due to lock for peerId=${peerId} multicodec=${this.multicodec}`);
2214
+ return;
2215
+ }
2216
+ try {
2217
+ this.ongoingCreation.add(peerId);
2218
+ await this.createStream(peer.id);
2219
+ }
2220
+ catch (error) {
2221
+ this.log.error(`Failed to createStreamWithLock:`, error);
2222
+ }
2223
+ finally {
2224
+ this.ongoingCreation.delete(peerId);
2225
+ }
2226
+ return;
2227
+ }
2228
+ handlePeerUpdateStreamPool = (evt) => {
2229
+ const { peer } = evt.detail;
2230
+ if (!peer.protocols.includes(this.multicodec)) {
2231
+ return;
2232
+ }
2233
+ const stream = this.getOpenStreamForCodec(peer.id);
2234
+ if (stream) {
2235
+ return;
2236
+ }
2237
+ this.scheduleNewStream(peer);
2238
+ };
2239
+ scheduleNewStream(peer) {
2240
+ this.log.info(`Scheduling creation of a stream for peerId=${peer.id.toString()} multicodec=${this.multicodec}`);
2241
+ // abandon previous attempt
2242
+ if (this.streamPool.has(peer.id.toString())) {
2243
+ this.streamPool.delete(peer.id.toString());
2244
+ }
2245
+ this.streamPool.set(peer.id.toString(), this.createStreamWithLock(peer));
2246
+ }
2247
+ getOpenStreamForCodec(peerId) {
2248
+ const connections = this.libp2p.connectionManager.getConnections(peerId);
2249
+ const connection = selectOpenConnection(connections);
2250
+ if (!connection) {
2251
+ return;
2252
+ }
2253
+ const stream = connection.streams.find((s) => s.protocol === this.multicodec);
2254
+ if (!stream) {
2255
+ return;
2256
+ }
2257
+ const isStreamUnusable = ["done", "closed", "closing"].includes(stream.writeStatus || "");
2258
+ if (isStreamUnusable || this.isStreamLocked(stream)) {
2259
+ return;
2260
+ }
2261
+ return stream;
2262
+ }
2263
+ lockStream(peerId, stream) {
2264
+ this.log.info(`Locking stream for peerId:${peerId}\tstreamId:${stream.id}`);
2265
+ stream.metadata[STREAM_LOCK_KEY] = true;
2266
+ }
2267
+ isStreamLocked(stream) {
2268
+ return !!stream.metadata[STREAM_LOCK_KEY];
2269
+ }
2270
+ }
2271
+
2135
2272
  // Unique ID creation requires a high quality random # generator. In the browser we therefore
2136
2273
  // require the crypto API and do not support built-in fallback to lower quality random number
2137
2274
  // generators (like Math.random()).
@@ -2299,13 +2436,33 @@ const FilterCodecs = {
2299
2436
  SUBSCRIBE: "/vac/waku/filter-subscribe/2.0.0-beta1",
2300
2437
  PUSH: "/vac/waku/filter-push/2.0.0-beta1"
2301
2438
  };
2302
- class FilterCore extends BaseProtocol {
2303
- handleIncomingMessage;
2439
+ class FilterCore {
2304
2440
  pubsubTopics;
2441
+ streamManager;
2442
+ static handleIncomingMessage;
2443
+ multicodec = FilterCodecs.SUBSCRIBE;
2305
2444
  constructor(handleIncomingMessage, pubsubTopics, libp2p) {
2306
- super(FilterCodecs.SUBSCRIBE, libp2p.components, pubsubTopics);
2307
- this.handleIncomingMessage = handleIncomingMessage;
2308
2445
  this.pubsubTopics = pubsubTopics;
2446
+ this.streamManager = new StreamManager(FilterCodecs.SUBSCRIBE, libp2p.components);
2447
+ // TODO(weboko): remove when @waku/sdk 0.0.33 is released
2448
+ const prevHandler = FilterCore.handleIncomingMessage;
2449
+ FilterCore.handleIncomingMessage = !prevHandler
2450
+ ? handleIncomingMessage
2451
+ : async (pubsubTopic, message, peerIdStr) => {
2452
+ try {
2453
+ await prevHandler(pubsubTopic, message, peerIdStr);
2454
+ }
2455
+ catch (e) {
2456
+ log$5.error("Previous FilterCore incoming message handler failed ", e);
2457
+ }
2458
+ try {
2459
+ await handleIncomingMessage(pubsubTopic, message, peerIdStr);
2460
+ }
2461
+ catch (e) {
2462
+ log$5.error("Present FilterCore incoming message handler failed ", e);
2463
+ }
2464
+ return;
2465
+ };
2309
2466
  libp2p
2310
2467
  .handle(FilterCodecs.PUSH, this.onRequest.bind(this), {
2311
2468
  maxInboundStreams: 100
@@ -2315,11 +2472,14 @@ class FilterCore extends BaseProtocol {
2315
2472
  });
2316
2473
  }
2317
2474
  async subscribe(pubsubTopic, peerId, contentTopics) {
2318
- const stream = await this.getStream(peerId);
2475
+ const stream = await this.streamManager.getStream(peerId);
2319
2476
  const request = FilterSubscribeRpc.createSubscribeRequest(pubsubTopic, contentTopics);
2320
2477
  let res;
2321
2478
  try {
2322
2479
  res = await pipe([request.encode()], encode, stream, decode, async (source) => await all(source));
2480
+ if (!res?.length) {
2481
+ throw Error("Received no response from subscription request.");
2482
+ }
2323
2483
  }
2324
2484
  catch (error) {
2325
2485
  log$5.error("Failed to send subscribe request", error);
@@ -2350,7 +2510,7 @@ class FilterCore extends BaseProtocol {
2350
2510
  async unsubscribe(pubsubTopic, peerId, contentTopics) {
2351
2511
  let stream;
2352
2512
  try {
2353
- stream = await this.getStream(peerId);
2513
+ stream = await this.streamManager.getStream(peerId);
2354
2514
  }
2355
2515
  catch (error) {
2356
2516
  log$5.error(`Failed to get a stream for remote peer${peerId.toString()}`, error);
@@ -2382,7 +2542,7 @@ class FilterCore extends BaseProtocol {
2382
2542
  };
2383
2543
  }
2384
2544
  async unsubscribeAll(pubsubTopic, peerId) {
2385
- const stream = await this.getStream(peerId);
2545
+ const stream = await this.streamManager.getStream(peerId);
2386
2546
  const request = FilterSubscribeRpc.createUnsubscribeAllRequest(pubsubTopic);
2387
2547
  const res = await pipe([request.encode()], encode, stream, decode, async (source) => await all(source));
2388
2548
  if (!res || !res.length) {
@@ -2413,7 +2573,7 @@ class FilterCore extends BaseProtocol {
2413
2573
  async ping(peerId) {
2414
2574
  let stream;
2415
2575
  try {
2416
- stream = await this.getStream(peerId);
2576
+ stream = await this.streamManager.getStream(peerId);
2417
2577
  }
2418
2578
  catch (error) {
2419
2579
  log$5.error(`Failed to get a stream for remote peer${peerId.toString()}`, error);
@@ -2482,7 +2642,7 @@ class FilterCore extends BaseProtocol {
2482
2642
  log$5.error("Pubsub topic missing from push message");
2483
2643
  return;
2484
2644
  }
2485
- await this.handleIncomingMessage(pubsubTopic, wakuMessage, connection.remotePeer.toString());
2645
+ await FilterCore.handleIncomingMessage?.(pubsubTopic, wakuMessage, connection.remotePeer.toString());
2486
2646
  }
2487
2647
  }).then(() => {
2488
2648
  log$5.info("Receiving pipe closed.");
@@ -2555,11 +2715,13 @@ const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
2555
2715
  /**
2556
2716
  * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
2557
2717
  */
2558
- class LightPushCore extends BaseProtocol {
2718
+ class LightPushCore {
2559
2719
  pubsubTopics;
2720
+ streamManager;
2721
+ multicodec = LightPushCodec;
2560
2722
  constructor(pubsubTopics, libp2p) {
2561
- super(LightPushCodec, libp2p.components, pubsubTopics);
2562
2723
  this.pubsubTopics = pubsubTopics;
2724
+ this.streamManager = new StreamManager(LightPushCodec, libp2p.components);
2563
2725
  }
2564
2726
  async preparePushMessage(encoder, message) {
2565
2727
  try {
@@ -2603,7 +2765,7 @@ class LightPushCore extends BaseProtocol {
2603
2765
  }
2604
2766
  let stream;
2605
2767
  try {
2606
- stream = await this.getStream(peerId);
2768
+ stream = await this.streamManager.getStream(peerId);
2607
2769
  }
2608
2770
  catch (error) {
2609
2771
  log$4.error("Failed to get stream", error);
@@ -2714,6 +2876,7 @@ class StoreQueryRequest {
2714
2876
  static create(params) {
2715
2877
  const request = new StoreQueryRequest({
2716
2878
  ...params,
2879
+ contentTopics: params.contentTopics || [],
2717
2880
  requestId: v4(),
2718
2881
  timeStart: params.timeStart
2719
2882
  ? BigInt(params.timeStart.getTime() * ONE_MILLION)
@@ -2726,17 +2889,22 @@ class StoreQueryRequest {
2726
2889
  ? BigInt(params.paginationLimit)
2727
2890
  : undefined
2728
2891
  });
2729
- // Validate request parameters based on RFC
2730
- if ((params.pubsubTopic && !params.contentTopics) ||
2731
- (!params.pubsubTopic && params.contentTopics)) {
2732
- throw new Error("Both pubsubTopic and contentTopics must be set or unset");
2733
- }
2734
- if (params.messageHashes &&
2735
- (params.pubsubTopic ||
2736
- params.contentTopics ||
2737
- params.timeStart ||
2738
- params.timeEnd)) {
2739
- throw new Error("Message hash lookup queries cannot include content filter criteria");
2892
+ const isHashQuery = params.messageHashes && params.messageHashes.length > 0;
2893
+ const hasContentTopics = params.contentTopics && params.contentTopics.length > 0;
2894
+ const hasTimeFilter = params.timeStart || params.timeEnd;
2895
+ if (isHashQuery) {
2896
+ if (hasContentTopics || hasTimeFilter) {
2897
+ throw new Error("Message hash lookup queries cannot include content filter criteria (contentTopics, timeStart, or timeEnd)");
2898
+ }
2899
+ }
2900
+ else {
2901
+ if ((params.pubsubTopic &&
2902
+ (!params.contentTopics || params.contentTopics.length === 0)) ||
2903
+ (!params.pubsubTopic &&
2904
+ params.contentTopics &&
2905
+ params.contentTopics.length > 0)) {
2906
+ throw new Error("Both pubsubTopic and contentTopics must be set together for content-filtered queries");
2907
+ }
2740
2908
  }
2741
2909
  return request;
2742
2910
  }
@@ -2776,15 +2944,19 @@ class StoreQueryResponse {
2776
2944
 
2777
2945
  const log$3 = new Logger("store");
2778
2946
  const StoreCodec = "/vac/waku/store-query/3.0.0";
2779
- class StoreCore extends BaseProtocol {
2780
- pubsubTopics;
2781
- constructor(pubsubTopics, libp2p) {
2782
- super(StoreCodec, libp2p.components, pubsubTopics);
2783
- this.pubsubTopics = pubsubTopics;
2947
+ class StoreCore {
2948
+ streamManager;
2949
+ multicodec = StoreCodec;
2950
+ constructor(libp2p) {
2951
+ this.streamManager = new StreamManager(StoreCodec, libp2p.components);
2784
2952
  }
2785
2953
  async *queryPerPage(queryOpts, decoders, peerId) {
2786
- if (queryOpts.contentTopics.toString() !==
2787
- Array.from(decoders.keys()).toString()) {
2954
+ // Only validate decoder content topics for content-filtered queries
2955
+ const isHashQuery = queryOpts.messageHashes && queryOpts.messageHashes.length > 0;
2956
+ if (!isHashQuery &&
2957
+ queryOpts.contentTopics &&
2958
+ queryOpts.contentTopics.toString() !==
2959
+ Array.from(decoders.keys()).toString()) {
2788
2960
  throw new Error("Internal error, the decoders should match the query's content topics");
2789
2961
  }
2790
2962
  let currentCursor = queryOpts.paginationCursor;
@@ -2793,9 +2965,15 @@ class StoreCore extends BaseProtocol {
2793
2965
  ...queryOpts,
2794
2966
  paginationCursor: currentCursor
2795
2967
  });
2968
+ log$3.info("Sending store query request:", {
2969
+ hasMessageHashes: !!queryOpts.messageHashes?.length,
2970
+ messageHashCount: queryOpts.messageHashes?.length,
2971
+ pubsubTopic: queryOpts.pubsubTopic,
2972
+ contentTopics: queryOpts.contentTopics
2973
+ });
2796
2974
  let stream;
2797
2975
  try {
2798
- stream = await this.getStream(peerId);
2976
+ stream = await this.streamManager.getStream(peerId);
2799
2977
  }
2800
2978
  catch (e) {
2801
2979
  log$3.error("Failed to get stream", e);
@@ -2867,9 +3045,71 @@ function isPeerId(other) {
2867
3045
  return Boolean(other?.[peerIdSymbol]);
2868
3046
  }
2869
3047
 
3048
+ /**
3049
+ * @packageDocumentation
3050
+ *
3051
+ * Adds types to the EventTarget class.
3052
+ *
3053
+ * Hopefully this won't be necessary
3054
+ * forever:
3055
+ *
3056
+ * - https://github.com/microsoft/TypeScript/issues/28357
3057
+ * - https://github.com/microsoft/TypeScript/issues/43477
3058
+ * - https://github.com/microsoft/TypeScript/issues/299
3059
+ * - https://www.npmjs.com/package/typed-events
3060
+ * - https://www.npmjs.com/package/typed-event-emitter
3061
+ * - https://www.npmjs.com/package/typed-event-target
3062
+ * - etc
3063
+ *
3064
+ * In addition to types, a `safeDispatchEvent` method is available which
3065
+ * prevents dispatching events that aren't in the event map, and a
3066
+ * `listenerCount` method which reports the number of listeners that are
3067
+ * currently registered for a given event.
3068
+ *
3069
+ * @example
3070
+ *
3071
+ * ```ts
3072
+ * import { TypedEventEmitter } from 'main-event'
3073
+ * import type { TypedEventTarget } from 'main-event'
3074
+ *
3075
+ * interface EventTypes {
3076
+ * 'test': CustomEvent<string>
3077
+ * }
3078
+ *
3079
+ * const target = new TypedEventEmitter<EventTypes>()
3080
+ *
3081
+ * // it's a regular EventTarget
3082
+ * console.info(target instanceof EventTarget) // true
3083
+ *
3084
+ * // register listeners normally
3085
+ * target.addEventListener('test', (evt) => {
3086
+ * // evt is CustomEvent<string>
3087
+ * })
3088
+ *
3089
+ * // @ts-expect-error 'derp' is not in the event map
3090
+ * target.addEventListener('derp', () => {})
3091
+ *
3092
+ * // use normal dispatchEvent method
3093
+ * target.dispatchEvent(new CustomEvent('test', {
3094
+ * detail: 'hello'
3095
+ * }))
3096
+ *
3097
+ * // use type safe dispatch method
3098
+ * target.safeDispatchEvent('test', {
3099
+ * detail: 'world'
3100
+ * })
3101
+ *
3102
+ * // report listener count
3103
+ * console.info(target.listenerCount('test')) // 0
3104
+ *
3105
+ * // event emitters can be used purely as interfaces too
3106
+ * function acceptTarget (target: TypedEventTarget<EventTypes>) {
3107
+ * // ...
3108
+ * }
3109
+ * ```
3110
+ */
2870
3111
  /**
2871
3112
  * An implementation of a typed event target
2872
- * etc
2873
3113
  */
2874
3114
  class TypedEventEmitter extends EventTarget {
2875
3115
  #listeners = new Map();
@@ -2919,6 +3159,26 @@ class TypedEventEmitter extends EventTarget {
2919
3159
  }
2920
3160
  }
2921
3161
 
3162
+ /**
3163
+ * Thrown when an invalid multiaddr is encountered
3164
+ */
3165
+ class InvalidMultiaddrError extends Error {
3166
+ static name = 'InvalidMultiaddrError';
3167
+ name = 'InvalidMultiaddrError';
3168
+ }
3169
+ class ValidationError extends Error {
3170
+ static name = 'ValidationError';
3171
+ name = 'ValidationError';
3172
+ }
3173
+ class InvalidParametersError extends Error {
3174
+ static name = 'InvalidParametersError';
3175
+ name = 'InvalidParametersError';
3176
+ }
3177
+ class UnknownProtocolError extends Error {
3178
+ static name = 'UnknownProtocolError';
3179
+ name = 'UnknownProtocolError';
3180
+ }
3181
+
2922
3182
  /* eslint-disable @typescript-eslint/no-unsafe-return */
2923
3183
  class Parser {
2924
3184
  index = 0;
@@ -3142,24 +3402,6 @@ function parseIPv6(input) {
3142
3402
  }
3143
3403
  return parser.new(input).parseWith(() => parser.readIPv6Addr());
3144
3404
  }
3145
- /** Parse `input` into IPv4 or IPv6 bytes. */
3146
- function parseIP(input, mapIPv4ToIPv6 = false) {
3147
- // strip zone index if it is present
3148
- if (input.includes("%")) {
3149
- input = input.split("%")[0];
3150
- }
3151
- if (input.length > MAX_IPV6_LENGTH) {
3152
- return undefined;
3153
- }
3154
- const addr = parser.new(input).parseWith(() => parser.readIPAddr());
3155
- if (!addr) {
3156
- return undefined;
3157
- }
3158
- if (mapIPv4ToIPv6 && addr.length === 4) {
3159
- return Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, addr[0], addr[1], addr[2], addr[3]]);
3160
- }
3161
- return addr;
3162
- }
3163
3405
 
3164
3406
  /** Check if `input` is IPv4. */
3165
3407
  function isIPv4(input) {
@@ -3169,347 +3411,69 @@ function isIPv4(input) {
3169
3411
  function isIPv6(input) {
3170
3412
  return Boolean(parseIPv6(input));
3171
3413
  }
3172
- /** Check if `input` is IPv4 or IPv6. */
3173
- function isIP(input) {
3174
- return Boolean(parseIP(input));
3175
- }
3176
3414
 
3177
- const isV4 = isIPv4;
3178
- const isV6 = isIPv6;
3179
- // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
3180
- // but with buf/offset args removed because we don't use them
3181
- const toBytes = function (ip) {
3182
- let offset = 0;
3183
- ip = ip.toString().trim();
3184
- if (isV4(ip)) {
3185
- const bytes = new Uint8Array(offset + 4);
3186
- ip.split(/\./g).forEach((byte) => {
3187
- bytes[offset++] = parseInt(byte, 10) & 0xff;
3188
- });
3189
- return bytes;
3190
- }
3191
- if (isV6(ip)) {
3192
- const sections = ip.split(':', 8);
3193
- let i;
3194
- for (i = 0; i < sections.length; i++) {
3195
- const isv4 = isV4(sections[i]);
3196
- let v4Buffer;
3197
- if (isv4) {
3198
- v4Buffer = toBytes(sections[i]);
3199
- sections[i] = toString$1(v4Buffer.slice(0, 2), 'base16');
3200
- }
3201
- if (v4Buffer != null && ++i < 8) {
3202
- sections.splice(i, 0, toString$1(v4Buffer.slice(2, 4), 'base16'));
3203
- }
3204
- }
3205
- if (sections[0] === '') {
3206
- while (sections.length < 8)
3207
- sections.unshift('0');
3208
- }
3209
- else if (sections[sections.length - 1] === '') {
3210
- while (sections.length < 8)
3211
- sections.push('0');
3212
- }
3213
- else if (sections.length < 8) {
3214
- for (i = 0; i < sections.length && sections[i] !== ''; i++)
3215
- ;
3216
- const argv = [i, 1];
3217
- for (i = 9 - sections.length; i > 0; i--) {
3218
- argv.push('0');
3219
- }
3220
- sections.splice.apply(sections, argv);
3221
- }
3222
- const bytes = new Uint8Array(offset + 16);
3223
- for (i = 0; i < sections.length; i++) {
3224
- const word = parseInt(sections[i], 16);
3225
- bytes[offset++] = (word >> 8) & 0xff;
3226
- bytes[offset++] = word & 0xff;
3227
- }
3228
- return bytes;
3229
- }
3230
- throw new Error('invalid ip address');
3231
- };
3232
- // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L63
3233
- const toString = function (buf, offset = 0, length) {
3234
- offset = ~~offset;
3235
- length = length ?? (buf.length - offset);
3236
- const view = new DataView(buf.buffer);
3237
- if (length === 4) {
3238
- const result = [];
3239
- // IPv4
3240
- for (let i = 0; i < length; i++) {
3241
- result.push(buf[offset + i]);
3242
- }
3243
- return result.join('.');
3244
- }
3245
- if (length === 16) {
3246
- const result = [];
3247
- // IPv6
3248
- for (let i = 0; i < length; i += 2) {
3249
- result.push(view.getUint16(offset + i).toString(16));
3250
- }
3251
- return result.join(':')
3252
- .replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3')
3253
- .replace(/:{3,4}/, '::');
3254
- }
3255
- return '';
3256
- };
3415
+ // the values here come from https://github.com/multiformats/multiaddr/blob/master/protocols.csv
3416
+ const CODE_IP4 = 4;
3417
+ const CODE_TCP = 6;
3418
+ const CODE_UDP = 273;
3419
+ const CODE_DCCP = 33;
3420
+ const CODE_IP6 = 41;
3421
+ const CODE_IP6ZONE = 42;
3422
+ const CODE_IPCIDR = 43;
3423
+ const CODE_DNS = 53;
3424
+ const CODE_DNS4 = 54;
3425
+ const CODE_DNS6 = 55;
3426
+ const CODE_DNSADDR = 56;
3427
+ const CODE_SCTP = 132;
3428
+ const CODE_UDT = 301;
3429
+ const CODE_UTP = 302;
3430
+ const CODE_UNIX = 400;
3431
+ const CODE_P2P = 421; // also IPFS
3432
+ const CODE_ONION = 444;
3433
+ const CODE_ONION3 = 445;
3434
+ const CODE_GARLIC64 = 446;
3435
+ const CODE_GARLIC32 = 447;
3436
+ const CODE_TLS = 448;
3437
+ const CODE_SNI = 449;
3438
+ const CODE_NOISE = 454;
3439
+ const CODE_QUIC = 460;
3440
+ const CODE_QUIC_V1 = 461;
3441
+ const CODE_WEBTRANSPORT = 465;
3442
+ const CODE_CERTHASH = 466;
3443
+ const CODE_HTTP = 480;
3444
+ const CODE_HTTP_PATH = 481;
3445
+ const CODE_HTTPS = 443;
3446
+ const CODE_WS = 477;
3447
+ const CODE_WSS = 478;
3448
+ const CODE_P2P_WEBSOCKET_STAR = 479;
3449
+ const CODE_P2P_STARDUST = 277;
3450
+ const CODE_P2P_WEBRTC_STAR = 275;
3451
+ const CODE_P2P_WEBRTC_DIRECT = 276;
3452
+ const CODE_WEBRTC_DIRECT = 280;
3453
+ const CODE_WEBRTC = 281;
3454
+ const CODE_P2P_CIRCUIT = 290;
3455
+ const CODE_MEMORY = 777;
3257
3456
 
3258
- const V = -1;
3259
- const names = {};
3260
- const codes = {};
3261
- const table = [
3262
- [4, 32, 'ip4'],
3263
- [6, 16, 'tcp'],
3264
- [33, 16, 'dccp'],
3265
- [41, 128, 'ip6'],
3266
- [42, V, 'ip6zone'],
3267
- [43, 8, 'ipcidr'],
3268
- [53, V, 'dns', true],
3269
- [54, V, 'dns4', true],
3270
- [55, V, 'dns6', true],
3271
- [56, V, 'dnsaddr', true],
3272
- [132, 16, 'sctp'],
3273
- [273, 16, 'udp'],
3274
- [275, 0, 'p2p-webrtc-star'],
3275
- [276, 0, 'p2p-webrtc-direct'],
3276
- [277, 0, 'p2p-stardust'],
3277
- [280, 0, 'webrtc-direct'],
3278
- [281, 0, 'webrtc'],
3279
- [290, 0, 'p2p-circuit'],
3280
- [301, 0, 'udt'],
3281
- [302, 0, 'utp'],
3282
- [400, V, 'unix', false, true],
3283
- // `ipfs` is added before `p2p` for legacy support.
3284
- // All text representations will default to `p2p`, but `ipfs` will
3285
- // still be supported
3286
- [421, V, 'ipfs'],
3287
- // `p2p` is the preferred name for 421, and is now the default
3288
- [421, V, 'p2p'],
3289
- [443, 0, 'https'],
3290
- [444, 96, 'onion'],
3291
- [445, 296, 'onion3'],
3292
- [446, V, 'garlic64'],
3293
- [448, 0, 'tls'],
3294
- [449, V, 'sni'],
3295
- [460, 0, 'quic'],
3296
- [461, 0, 'quic-v1'],
3297
- [465, 0, 'webtransport'],
3298
- [466, V, 'certhash'],
3299
- [477, 0, 'ws'],
3300
- [478, 0, 'wss'],
3301
- [479, 0, 'p2p-websocket-star'],
3302
- [480, 0, 'http'],
3303
- [481, V, 'http-path'],
3304
- [777, V, 'memory']
3305
- ];
3306
- // populate tables
3307
- table.forEach(row => {
3308
- const proto = createProtocol(...row);
3309
- codes[proto.code] = proto;
3310
- names[proto.name] = proto;
3311
- });
3312
- function createProtocol(code, size, name, resolvable, path) {
3313
- return {
3314
- code,
3315
- size,
3316
- name,
3317
- resolvable: Boolean(resolvable),
3318
- path: Boolean(path)
3457
+ function bytesToString(base) {
3458
+ return (buf) => {
3459
+ return toString(buf, base);
3319
3460
  };
3320
3461
  }
3321
- /**
3322
- * For the passed proto string or number, return a {@link Protocol}
3323
- *
3324
- * @example
3325
- *
3326
- * ```js
3327
- * import { protocol } from '@multiformats/multiaddr'
3328
- *
3329
- * console.info(protocol(4))
3330
- * // { code: 4, size: 32, name: 'ip4', resolvable: false, path: false }
3331
- * ```
3332
- */
3333
- function getProtocol(proto) {
3334
- if (typeof proto === 'number') {
3335
- if (codes[proto] != null) {
3336
- return codes[proto];
3337
- }
3338
- throw new Error(`no protocol with code: ${proto}`);
3339
- }
3340
- else if (typeof proto === 'string') {
3341
- if (names[proto] != null) {
3342
- return names[proto];
3343
- }
3344
- throw new Error(`no protocol with name: ${proto}`);
3345
- }
3346
- throw new Error(`invalid protocol id type: ${typeof proto}`);
3347
- }
3348
-
3349
- getProtocol('ip4');
3350
- getProtocol('ip6');
3351
- getProtocol('ipcidr');
3352
- /**
3353
- * Convert [code,Uint8Array] to string
3354
- */
3355
- // eslint-disable-next-line complexity
3356
- function convertToString(proto, buf) {
3357
- const protocol = getProtocol(proto);
3358
- switch (protocol.code) {
3359
- case 4: // ipv4
3360
- case 41: // ipv6
3361
- return bytes2ip(buf);
3362
- case 42: // ipv6zone
3363
- return bytes2str(buf);
3364
- case 43: // ipcidr
3365
- return toString$1(buf, 'base10');
3366
- case 6: // tcp
3367
- case 273: // udp
3368
- case 33: // dccp
3369
- case 132: // sctp
3370
- return bytes2port(buf).toString();
3371
- case 53: // dns
3372
- case 54: // dns4
3373
- case 55: // dns6
3374
- case 56: // dnsaddr
3375
- case 400: // unix
3376
- case 449: // sni
3377
- case 777: // memory
3378
- return bytes2str(buf);
3379
- case 421: // ipfs
3380
- return bytes2mh(buf);
3381
- case 444: // onion
3382
- return bytes2onion(buf);
3383
- case 445: // onion3
3384
- return bytes2onion(buf);
3385
- case 466: // certhash
3386
- return bytes2mb(buf);
3387
- case 481: // http-path
3388
- return globalThis.encodeURIComponent(bytes2str(buf));
3389
- default:
3390
- return toString$1(buf, 'base16'); // no clue. convert to hex
3391
- }
3392
- }
3393
- // eslint-disable-next-line complexity
3394
- function convertToBytes(proto, str) {
3395
- const protocol = getProtocol(proto);
3396
- switch (protocol.code) {
3397
- case 4: // ipv4
3398
- return ip2bytes(str);
3399
- case 41: // ipv6
3400
- return ip2bytes(str);
3401
- case 42: // ipv6zone
3402
- return str2bytes(str);
3403
- case 43: // ipcidr
3404
- return fromString(str, 'base10');
3405
- case 6: // tcp
3406
- case 273: // udp
3407
- case 33: // dccp
3408
- case 132: // sctp
3409
- return port2bytes(parseInt(str, 10));
3410
- case 53: // dns
3411
- case 54: // dns4
3412
- case 55: // dns6
3413
- case 56: // dnsaddr
3414
- case 400: // unix
3415
- case 449: // sni
3416
- case 777: // memory
3417
- return str2bytes(str);
3418
- case 421: // ipfs
3419
- return mh2bytes(str);
3420
- case 444: // onion
3421
- return onion2bytes(str);
3422
- case 445: // onion3
3423
- return onion32bytes(str);
3424
- case 466: // certhash
3425
- return mb2bytes(str);
3426
- case 481: // http-path
3427
- return str2bytes(globalThis.decodeURIComponent(str));
3428
- default:
3429
- return fromString(str, 'base16'); // no clue. convert from hex
3430
- }
3431
- }
3432
- const decoders = Object.values(bases).map((c) => c.decoder);
3433
- const anybaseDecoder = (function () {
3434
- let acc = decoders[0].or(decoders[1]);
3435
- decoders.slice(2).forEach((d) => (acc = acc.or(d)));
3436
- return acc;
3437
- })();
3438
- function ip2bytes(ipString) {
3439
- if (!isIP(ipString)) {
3440
- throw new Error('invalid ip address');
3441
- }
3442
- return toBytes(ipString);
3462
+ function stringToBytes(base) {
3463
+ return (buf) => {
3464
+ return fromString(buf, base);
3465
+ };
3443
3466
  }
3444
- function bytes2ip(ipBuff) {
3445
- const ipString = toString(ipBuff, 0, ipBuff.length);
3446
- if (ipString == null) {
3447
- throw new Error('ipBuff is required');
3448
- }
3449
- if (!isIP(ipString)) {
3450
- throw new Error('invalid ip address');
3451
- }
3452
- return ipString;
3467
+ function bytes2port(buf) {
3468
+ const view = new DataView(buf.buffer);
3469
+ return view.getUint16(buf.byteOffset).toString();
3453
3470
  }
3454
3471
  function port2bytes(port) {
3455
3472
  const buf = new ArrayBuffer(2);
3456
3473
  const view = new DataView(buf);
3457
- view.setUint16(0, port);
3474
+ view.setUint16(0, typeof port === 'string' ? parseInt(port) : port);
3458
3475
  return new Uint8Array(buf);
3459
3476
  }
3460
- function bytes2port(buf) {
3461
- const view = new DataView(buf.buffer);
3462
- return view.getUint16(buf.byteOffset);
3463
- }
3464
- function str2bytes(str) {
3465
- const buf = fromString(str);
3466
- const size = Uint8Array.from(encode$2(buf.length));
3467
- return concat([size, buf], size.length + buf.length);
3468
- }
3469
- function bytes2str(buf) {
3470
- const size = decode$4(buf);
3471
- buf = buf.slice(encodingLength$1(size));
3472
- if (buf.length !== size) {
3473
- throw new Error('inconsistent lengths');
3474
- }
3475
- return toString$1(buf);
3476
- }
3477
- function mh2bytes(hash) {
3478
- let mh;
3479
- if (hash[0] === 'Q' || hash[0] === '1') {
3480
- mh = decode$1(base58btc.decode(`z${hash}`)).bytes;
3481
- }
3482
- else {
3483
- mh = CID.parse(hash).multihash.bytes;
3484
- }
3485
- // the address is a varint prefixed multihash string representation
3486
- const size = Uint8Array.from(encode$2(mh.length));
3487
- return concat([size, mh], size.length + mh.length);
3488
- }
3489
- function mb2bytes(mbstr) {
3490
- const mb = anybaseDecoder.decode(mbstr);
3491
- const size = Uint8Array.from(encode$2(mb.length));
3492
- return concat([size, mb], size.length + mb.length);
3493
- }
3494
- function bytes2mb(buf) {
3495
- const size = decode$4(buf);
3496
- const hash = buf.slice(encodingLength$1(size));
3497
- if (hash.length !== size) {
3498
- throw new Error('inconsistent lengths');
3499
- }
3500
- return 'u' + toString$1(hash, 'base64url');
3501
- }
3502
- /**
3503
- * Converts bytes to bas58btc string
3504
- */
3505
- function bytes2mh(buf) {
3506
- const size = decode$4(buf);
3507
- const address = buf.slice(encodingLength$1(size));
3508
- if (address.length !== size) {
3509
- throw new Error('inconsistent lengths');
3510
- }
3511
- return toString$1(address, 'base58btc');
3512
- }
3513
3477
  function onion2bytes(str) {
3514
3478
  const addr = str.split(':');
3515
3479
  if (addr.length !== 2) {
@@ -3519,7 +3483,7 @@ function onion2bytes(str) {
3519
3483
  throw new Error(`failed to parse onion addr: ${addr[0]} not a Tor onion address.`);
3520
3484
  }
3521
3485
  // onion addresses do not include the multibase prefix, add it before decoding
3522
- const buf = base32.decode('b' + addr[0]);
3486
+ const buf = fromString(addr[0], 'base32');
3523
3487
  // onion port number
3524
3488
  const port = parseInt(addr[1], 10);
3525
3489
  if (port < 1 || port > 65536) {
@@ -3547,167 +3511,558 @@ function onion32bytes(str) {
3547
3511
  return concat([buf, portBuf], buf.length + portBuf.length);
3548
3512
  }
3549
3513
  function bytes2onion(buf) {
3550
- const addrBytes = buf.slice(0, buf.length - 2);
3551
- const portBytes = buf.slice(buf.length - 2);
3552
- const addr = toString$1(addrBytes, 'base32');
3514
+ const addrBytes = buf.subarray(0, buf.length - 2);
3515
+ const portBytes = buf.subarray(buf.length - 2);
3516
+ const addr = toString(addrBytes, 'base32');
3553
3517
  const port = bytes2port(portBytes);
3554
3518
  return `${addr}:${port}`;
3555
3519
  }
3520
+ // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
3521
+ // but with buf/offset args removed because we don't use them
3522
+ const ip4ToBytes = function (ip) {
3523
+ ip = ip.toString().trim();
3524
+ const bytes = new Uint8Array(4);
3525
+ ip.split(/\./g).forEach((byte, index) => {
3526
+ const value = parseInt(byte, 10);
3527
+ if (isNaN(value) || value < 0 || value > 0xff) {
3528
+ throw new InvalidMultiaddrError('Invalid byte value in IP address');
3529
+ }
3530
+ bytes[index] = value;
3531
+ });
3532
+ return bytes;
3533
+ };
3534
+ // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
3535
+ // but with buf/offset args removed because we don't use them
3536
+ const ip6ToBytes = function (ip) {
3537
+ let offset = 0;
3538
+ ip = ip.toString().trim();
3539
+ const sections = ip.split(':', 8);
3540
+ let i;
3541
+ for (i = 0; i < sections.length; i++) {
3542
+ const isv4 = isIPv4(sections[i]);
3543
+ let v4Buffer;
3544
+ if (isv4) {
3545
+ v4Buffer = ip4ToBytes(sections[i]);
3546
+ sections[i] = toString(v4Buffer.subarray(0, 2), 'base16');
3547
+ }
3548
+ if (v4Buffer != null && ++i < 8) {
3549
+ sections.splice(i, 0, toString(v4Buffer.subarray(2, 4), 'base16'));
3550
+ }
3551
+ }
3552
+ if (sections[0] === '') {
3553
+ while (sections.length < 8) {
3554
+ sections.unshift('0');
3555
+ }
3556
+ }
3557
+ else if (sections[sections.length - 1] === '') {
3558
+ while (sections.length < 8) {
3559
+ sections.push('0');
3560
+ }
3561
+ }
3562
+ else if (sections.length < 8) {
3563
+ for (i = 0; i < sections.length && sections[i] !== ''; i++) { }
3564
+ const argv = [i, 1];
3565
+ for (i = 9 - sections.length; i > 0; i--) {
3566
+ argv.push('0');
3567
+ }
3568
+ sections.splice.apply(sections, argv);
3569
+ }
3570
+ const bytes = new Uint8Array(offset + 16);
3571
+ for (i = 0; i < sections.length; i++) {
3572
+ if (sections[i] === '') {
3573
+ sections[i] = '0';
3574
+ }
3575
+ const word = parseInt(sections[i], 16);
3576
+ if (isNaN(word) || word < 0 || word > 0xffff) {
3577
+ throw new InvalidMultiaddrError('Invalid byte value in IP address');
3578
+ }
3579
+ bytes[offset++] = (word >> 8) & 0xff;
3580
+ bytes[offset++] = word & 0xff;
3581
+ }
3582
+ return bytes;
3583
+ };
3584
+ // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L63
3585
+ const ip4ToString = function (buf) {
3586
+ if (buf.byteLength !== 4) {
3587
+ throw new InvalidMultiaddrError('IPv4 address was incorrect length');
3588
+ }
3589
+ const result = [];
3590
+ for (let i = 0; i < buf.byteLength; i++) {
3591
+ result.push(buf[i]);
3592
+ }
3593
+ return result.join('.');
3594
+ };
3595
+ const ip6ToString = function (buf) {
3596
+ if (buf.byteLength !== 16) {
3597
+ throw new InvalidMultiaddrError('IPv6 address was incorrect length');
3598
+ }
3599
+ const result = [];
3600
+ for (let i = 0; i < buf.byteLength; i += 2) {
3601
+ const byte1 = buf[i];
3602
+ const byte2 = buf[i + 1];
3603
+ const tuple = `${byte1.toString(16).padStart(2, '0')}${byte2.toString(16).padStart(2, '0')}`;
3604
+ result.push(tuple);
3605
+ }
3606
+ const ip = result.join(':');
3607
+ try {
3608
+ const url = new URL(`http://[${ip}]`);
3609
+ return url.hostname.substring(1, url.hostname.length - 1);
3610
+ }
3611
+ catch {
3612
+ throw new InvalidMultiaddrError(`Invalid IPv6 address "${ip}"`);
3613
+ }
3614
+ };
3615
+ function ip6StringToValue(str) {
3616
+ try {
3617
+ const url = new URL(`http://[${str}]`);
3618
+ return url.hostname.substring(1, url.hostname.length - 1);
3619
+ }
3620
+ catch {
3621
+ throw new InvalidMultiaddrError(`Invalid IPv6 address "${str}"`);
3622
+ }
3623
+ }
3624
+ const decoders = Object.values(bases).map((c) => c.decoder);
3625
+ const anybaseDecoder = (function () {
3626
+ let acc = decoders[0].or(decoders[1]);
3627
+ decoders.slice(2).forEach((d) => (acc = acc.or(d)));
3628
+ return acc;
3629
+ })();
3630
+ function mb2bytes(mbstr) {
3631
+ return anybaseDecoder.decode(mbstr);
3632
+ }
3633
+ function bytes2mb(base) {
3634
+ return (buf) => {
3635
+ return base.encoder.encode(buf);
3636
+ };
3637
+ }
3556
3638
 
3557
- function stringToMultiaddrParts(str) {
3558
- str = cleanPath(str);
3559
- const tuples = [];
3560
- const stringTuples = [];
3561
- let path = null;
3562
- const parts = str.split('/').slice(1);
3563
- if (parts.length === 1 && parts[0] === '') {
3564
- return {
3565
- bytes: new Uint8Array(),
3566
- string: '/',
3567
- tuples: [],
3568
- stringTuples: [],
3569
- path: null
3570
- };
3639
+ function integer(value) {
3640
+ const int = parseInt(value);
3641
+ if (int.toString() !== value) {
3642
+ throw new ValidationError('Value must be an integer');
3571
3643
  }
3572
- for (let p = 0; p < parts.length; p++) {
3573
- const part = parts[p];
3574
- const proto = getProtocol(part);
3575
- if (proto.size === 0) {
3576
- tuples.push([proto.code]);
3577
- stringTuples.push([proto.code]);
3578
- // eslint-disable-next-line no-continue
3579
- continue;
3580
- }
3581
- p++; // advance addr part
3582
- if (p >= parts.length) {
3583
- throw new ParseError('invalid address: ' + str);
3584
- }
3585
- // if it's a path proto, take the rest
3586
- if (proto.path === true) {
3587
- // should we need to check each path part to see if it's a proto?
3588
- // This would allow for other protocols to be added after a unix path,
3589
- // however it would have issues if the path had a protocol name in the path
3590
- path = cleanPath(parts.slice(p).join('/'));
3591
- tuples.push([proto.code, convertToBytes(proto.code, path)]);
3592
- stringTuples.push([proto.code, path]);
3593
- break;
3594
- }
3595
- const bytes = convertToBytes(proto.code, parts[p]);
3596
- tuples.push([proto.code, bytes]);
3597
- stringTuples.push([proto.code, convertToString(proto.code, bytes)]);
3598
- }
3599
- return {
3600
- string: stringTuplesToString(stringTuples),
3601
- bytes: tuplesToBytes(tuples),
3602
- tuples,
3603
- stringTuples,
3604
- path
3644
+ }
3645
+ function positive(value) {
3646
+ if (value < 0) {
3647
+ throw new ValidationError('Value must be a positive integer, or zero');
3648
+ }
3649
+ }
3650
+ function maxValue(max) {
3651
+ return (value) => {
3652
+ if (value > max) {
3653
+ throw new ValidationError(`Value must be smaller than or equal to ${max}`);
3654
+ }
3655
+ };
3656
+ }
3657
+ function validate$1(...funcs) {
3658
+ return (value) => {
3659
+ for (const fn of funcs) {
3660
+ fn(value);
3661
+ }
3605
3662
  };
3606
3663
  }
3607
- function bytesToMultiaddrParts(bytes) {
3608
- const tuples = [];
3609
- const stringTuples = [];
3610
- let path = null;
3664
+ const validatePort = validate$1(integer, positive, maxValue(65_535));
3665
+
3666
+ const V = -1;
3667
+ class Registry {
3668
+ protocolsByCode = new Map();
3669
+ protocolsByName = new Map();
3670
+ getProtocol(key) {
3671
+ let codec;
3672
+ if (typeof key === 'string') {
3673
+ codec = this.protocolsByName.get(key);
3674
+ }
3675
+ else {
3676
+ codec = this.protocolsByCode.get(key);
3677
+ }
3678
+ if (codec == null) {
3679
+ throw new UnknownProtocolError(`Protocol ${key} was unknown`);
3680
+ }
3681
+ return codec;
3682
+ }
3683
+ addProtocol(codec) {
3684
+ this.protocolsByCode.set(codec.code, codec);
3685
+ this.protocolsByName.set(codec.name, codec);
3686
+ codec.aliases?.forEach(alias => {
3687
+ this.protocolsByName.set(alias, codec);
3688
+ });
3689
+ }
3690
+ removeProtocol(code) {
3691
+ const codec = this.protocolsByCode.get(code);
3692
+ if (codec == null) {
3693
+ return;
3694
+ }
3695
+ this.protocolsByCode.delete(codec.code);
3696
+ this.protocolsByName.delete(codec.name);
3697
+ codec.aliases?.forEach(alias => {
3698
+ this.protocolsByName.delete(alias);
3699
+ });
3700
+ }
3701
+ }
3702
+ const registry = new Registry();
3703
+ const codecs = [{
3704
+ code: CODE_IP4,
3705
+ name: 'ip4',
3706
+ size: 32,
3707
+ valueToBytes: ip4ToBytes,
3708
+ bytesToValue: ip4ToString,
3709
+ validate: (value) => {
3710
+ if (!isIPv4(value)) {
3711
+ throw new ValidationError(`Invalid IPv4 address "${value}"`);
3712
+ }
3713
+ }
3714
+ }, {
3715
+ code: CODE_TCP,
3716
+ name: 'tcp',
3717
+ size: 16,
3718
+ valueToBytes: port2bytes,
3719
+ bytesToValue: bytes2port,
3720
+ validate: validatePort
3721
+ }, {
3722
+ code: CODE_UDP,
3723
+ name: 'udp',
3724
+ size: 16,
3725
+ valueToBytes: port2bytes,
3726
+ bytesToValue: bytes2port,
3727
+ validate: validatePort
3728
+ }, {
3729
+ code: CODE_DCCP,
3730
+ name: 'dccp',
3731
+ size: 16,
3732
+ valueToBytes: port2bytes,
3733
+ bytesToValue: bytes2port,
3734
+ validate: validatePort
3735
+ }, {
3736
+ code: CODE_IP6,
3737
+ name: 'ip6',
3738
+ size: 128,
3739
+ valueToBytes: ip6ToBytes,
3740
+ bytesToValue: ip6ToString,
3741
+ stringToValue: ip6StringToValue,
3742
+ validate: (value) => {
3743
+ if (!isIPv6(value)) {
3744
+ throw new ValidationError(`Invalid IPv6 address "${value}"`);
3745
+ }
3746
+ }
3747
+ }, {
3748
+ code: CODE_IP6ZONE,
3749
+ name: 'ip6zone',
3750
+ size: V
3751
+ }, {
3752
+ code: CODE_IPCIDR,
3753
+ name: 'ipcidr',
3754
+ size: 8,
3755
+ bytesToValue: bytesToString('base10'),
3756
+ valueToBytes: stringToBytes('base10')
3757
+ }, {
3758
+ code: CODE_DNS,
3759
+ name: 'dns',
3760
+ size: V,
3761
+ resolvable: true
3762
+ }, {
3763
+ code: CODE_DNS4,
3764
+ name: 'dns4',
3765
+ size: V,
3766
+ resolvable: true
3767
+ }, {
3768
+ code: CODE_DNS6,
3769
+ name: 'dns6',
3770
+ size: V,
3771
+ resolvable: true
3772
+ }, {
3773
+ code: CODE_DNSADDR,
3774
+ name: 'dnsaddr',
3775
+ size: V,
3776
+ resolvable: true
3777
+ }, {
3778
+ code: CODE_SCTP,
3779
+ name: 'sctp',
3780
+ size: 16,
3781
+ valueToBytes: port2bytes,
3782
+ bytesToValue: bytes2port,
3783
+ validate: validatePort
3784
+ }, {
3785
+ code: CODE_UDT,
3786
+ name: 'udt'
3787
+ }, {
3788
+ code: CODE_UTP,
3789
+ name: 'utp'
3790
+ }, {
3791
+ code: CODE_UNIX,
3792
+ name: 'unix',
3793
+ size: V,
3794
+ path: true,
3795
+ stringToValue: (str) => decodeURIComponent(str),
3796
+ valueToString: (val) => encodeURIComponent(val)
3797
+ }, {
3798
+ code: CODE_P2P,
3799
+ name: 'p2p',
3800
+ aliases: ['ipfs'],
3801
+ size: V,
3802
+ bytesToValue: bytesToString('base58btc'),
3803
+ valueToBytes: (val) => {
3804
+ if (val.startsWith('Q') || val.startsWith('1')) {
3805
+ return stringToBytes('base58btc')(val);
3806
+ }
3807
+ return CID.parse(val).multihash.bytes;
3808
+ }
3809
+ }, {
3810
+ code: CODE_ONION,
3811
+ name: 'onion',
3812
+ size: 96,
3813
+ bytesToValue: bytes2onion,
3814
+ valueToBytes: onion2bytes
3815
+ }, {
3816
+ code: CODE_ONION3,
3817
+ name: 'onion3',
3818
+ size: 296,
3819
+ bytesToValue: bytes2onion,
3820
+ valueToBytes: onion32bytes
3821
+ }, {
3822
+ code: CODE_GARLIC64,
3823
+ name: 'garlic64',
3824
+ size: V
3825
+ }, {
3826
+ code: CODE_GARLIC32,
3827
+ name: 'garlic32',
3828
+ size: V
3829
+ }, {
3830
+ code: CODE_TLS,
3831
+ name: 'tls'
3832
+ }, {
3833
+ code: CODE_SNI,
3834
+ name: 'sni',
3835
+ size: V
3836
+ }, {
3837
+ code: CODE_NOISE,
3838
+ name: 'noise'
3839
+ }, {
3840
+ code: CODE_QUIC,
3841
+ name: 'quic'
3842
+ }, {
3843
+ code: CODE_QUIC_V1,
3844
+ name: 'quic-v1'
3845
+ }, {
3846
+ code: CODE_WEBTRANSPORT,
3847
+ name: 'webtransport'
3848
+ }, {
3849
+ code: CODE_CERTHASH,
3850
+ name: 'certhash',
3851
+ size: V,
3852
+ bytesToValue: bytes2mb(base64url),
3853
+ valueToBytes: mb2bytes
3854
+ }, {
3855
+ code: CODE_HTTP,
3856
+ name: 'http'
3857
+ }, {
3858
+ code: CODE_HTTP_PATH,
3859
+ name: 'http-path',
3860
+ size: V,
3861
+ stringToValue: (str) => `/${decodeURIComponent(str)}`,
3862
+ valueToString: (val) => encodeURIComponent(val.substring(1))
3863
+ }, {
3864
+ code: CODE_HTTPS,
3865
+ name: 'https'
3866
+ }, {
3867
+ code: CODE_WS,
3868
+ name: 'ws'
3869
+ }, {
3870
+ code: CODE_WSS,
3871
+ name: 'wss'
3872
+ }, {
3873
+ code: CODE_P2P_WEBSOCKET_STAR,
3874
+ name: 'p2p-websocket-star'
3875
+ }, {
3876
+ code: CODE_P2P_STARDUST,
3877
+ name: 'p2p-stardust'
3878
+ }, {
3879
+ code: CODE_P2P_WEBRTC_STAR,
3880
+ name: 'p2p-webrtc-star'
3881
+ }, {
3882
+ code: CODE_P2P_WEBRTC_DIRECT,
3883
+ name: 'p2p-webrtc-direct'
3884
+ }, {
3885
+ code: CODE_WEBRTC_DIRECT,
3886
+ name: 'webrtc-direct'
3887
+ }, {
3888
+ code: CODE_WEBRTC,
3889
+ name: 'webrtc'
3890
+ }, {
3891
+ code: CODE_P2P_CIRCUIT,
3892
+ name: 'p2p-circuit'
3893
+ }, {
3894
+ code: CODE_MEMORY,
3895
+ name: 'memory',
3896
+ size: V
3897
+ }];
3898
+ codecs.forEach(codec => {
3899
+ registry.addProtocol(codec);
3900
+ });
3901
+
3902
+ function bytesToComponents(bytes) {
3903
+ const components = [];
3611
3904
  let i = 0;
3612
3905
  while (i < bytes.length) {
3613
3906
  const code = decode$4(bytes, i);
3614
- const n = encodingLength$1(code);
3615
- const p = getProtocol(code);
3616
- const size = sizeForAddr(p, bytes.slice(i + n));
3617
- if (size === 0) {
3618
- tuples.push([code]);
3619
- stringTuples.push([code]);
3620
- i += n;
3621
- // eslint-disable-next-line no-continue
3622
- continue;
3623
- }
3624
- const addr = bytes.slice(i + n, i + n + size);
3625
- i += (size + n);
3626
- if (i > bytes.length) { // did not end _exactly_ at buffer.length
3627
- throw new ParseError('Invalid address Uint8Array: ' + toString$1(bytes, 'base16'));
3628
- }
3629
- // ok, tuple seems good.
3630
- tuples.push([code, addr]);
3631
- const stringAddr = convertToString(code, addr);
3632
- stringTuples.push([code, stringAddr]);
3633
- if (p.path === true) {
3634
- // should we need to check each path part to see if it's a proto?
3635
- // This would allow for other protocols to be added after a unix path,
3636
- // however it would have issues if the path had a protocol name in the path
3637
- path = stringAddr;
3638
- break;
3639
- }
3640
- }
3641
- return {
3642
- bytes: Uint8Array.from(bytes),
3643
- string: stringTuplesToString(stringTuples),
3644
- tuples,
3645
- stringTuples,
3646
- path
3647
- };
3907
+ const codec = registry.getProtocol(code);
3908
+ const codeLength = encodingLength$1(code);
3909
+ const size = sizeForAddr(codec, bytes, i + codeLength);
3910
+ let sizeLength = 0;
3911
+ if (size > 0 && codec.size === V) {
3912
+ sizeLength = encodingLength$1(size);
3913
+ }
3914
+ const componentLength = codeLength + sizeLength + size;
3915
+ const component = {
3916
+ code,
3917
+ name: codec.name,
3918
+ bytes: bytes.subarray(i, i + componentLength)
3919
+ };
3920
+ if (size > 0) {
3921
+ const valueOffset = i + codeLength + sizeLength;
3922
+ const valueBytes = bytes.subarray(valueOffset, valueOffset + size);
3923
+ component.value = codec.bytesToValue?.(valueBytes) ?? toString(valueBytes);
3924
+ }
3925
+ components.push(component);
3926
+ i += componentLength;
3927
+ }
3928
+ return components;
3648
3929
  }
3649
- /**
3650
- * [[num code, str value?]... ] -> string
3651
- */
3652
- function stringTuplesToString(tuples) {
3653
- const parts = [];
3654
- tuples.map((tup) => {
3655
- const proto = getProtocol(tup[0]);
3656
- parts.push(proto.name);
3657
- if (tup.length > 1 && tup[1] != null) {
3658
- parts.push(tup[1]);
3930
+ function componentsToBytes(components) {
3931
+ let length = 0;
3932
+ const bytes = [];
3933
+ for (const component of components) {
3934
+ if (component.bytes == null) {
3935
+ const codec = registry.getProtocol(component.code);
3936
+ const codecLength = encodingLength$1(component.code);
3937
+ let valueBytes;
3938
+ let valueLength = 0;
3939
+ let valueLengthLength = 0;
3940
+ if (component.value != null) {
3941
+ valueBytes = codec.valueToBytes?.(component.value) ?? fromString(component.value);
3942
+ valueLength = valueBytes.byteLength;
3943
+ if (codec.size === V) {
3944
+ valueLengthLength = encodingLength$1(valueLength);
3945
+ }
3946
+ }
3947
+ const bytes = new Uint8Array(codecLength + valueLengthLength + valueLength);
3948
+ // encode the protocol code
3949
+ let offset = 0;
3950
+ encodeUint8Array(component.code, bytes, offset);
3951
+ offset += codecLength;
3952
+ // if there is a value
3953
+ if (valueBytes != null) {
3954
+ // if the value has variable length, encode the length
3955
+ if (codec.size === V) {
3956
+ encodeUint8Array(valueLength, bytes, offset);
3957
+ offset += valueLengthLength;
3958
+ }
3959
+ // finally encode the value
3960
+ bytes.set(valueBytes, offset);
3961
+ }
3962
+ component.bytes = bytes;
3659
3963
  }
3660
- return null;
3661
- });
3662
- return cleanPath(parts.join('/'));
3964
+ bytes.push(component.bytes);
3965
+ length += component.bytes.byteLength;
3966
+ }
3967
+ return concat(bytes, length);
3663
3968
  }
3664
- /**
3665
- * [[int code, Uint8Array ]... ] -> Uint8Array
3666
- */
3667
- function tuplesToBytes(tuples) {
3668
- return concat(tuples.map((tup) => {
3669
- const proto = getProtocol(tup[0]);
3670
- let buf = Uint8Array.from(encode$2(proto.code));
3671
- if (tup.length > 1 && tup[1] != null) {
3672
- buf = concat([buf, tup[1]]); // add address buffer
3673
- }
3674
- return buf;
3675
- }));
3969
+ function stringToComponents(string) {
3970
+ if (string.charAt(0) !== '/') {
3971
+ throw new InvalidMultiaddrError('String multiaddr must start with "/"');
3972
+ }
3973
+ const components = [];
3974
+ let collecting = 'protocol';
3975
+ let value = '';
3976
+ let protocol = '';
3977
+ for (let i = 1; i < string.length; i++) {
3978
+ const char = string.charAt(i);
3979
+ if (char !== '/') {
3980
+ if (collecting === 'protocol') {
3981
+ protocol += string.charAt(i);
3982
+ }
3983
+ else {
3984
+ value += string.charAt(i);
3985
+ }
3986
+ }
3987
+ const ended = i === string.length - 1;
3988
+ if (char === '/' || ended) {
3989
+ const codec = registry.getProtocol(protocol);
3990
+ if (collecting === 'protocol') {
3991
+ if (codec.size == null || codec.size === 0) {
3992
+ // a protocol without an address, eg. `/tls`
3993
+ components.push({
3994
+ code: codec.code,
3995
+ name: codec.name
3996
+ });
3997
+ value = '';
3998
+ protocol = '';
3999
+ collecting = 'protocol';
4000
+ continue;
4001
+ }
4002
+ else if (ended) {
4003
+ throw new InvalidMultiaddrError(`Component ${protocol} was missing value`);
4004
+ }
4005
+ // continue collecting value
4006
+ collecting = 'value';
4007
+ }
4008
+ else if (collecting === 'value') {
4009
+ const component = {
4010
+ code: codec.code,
4011
+ name: codec.name
4012
+ };
4013
+ if (codec.size != null && codec.size !== 0) {
4014
+ if (value === '') {
4015
+ throw new InvalidMultiaddrError(`Component ${protocol} was missing value`);
4016
+ }
4017
+ component.value = codec.stringToValue?.(value) ?? value;
4018
+ }
4019
+ components.push(component);
4020
+ value = '';
4021
+ protocol = '';
4022
+ collecting = 'protocol';
4023
+ }
4024
+ }
4025
+ }
4026
+ if (protocol !== '' && value !== '') {
4027
+ throw new InvalidMultiaddrError('Incomplete multiaddr');
4028
+ }
4029
+ return components;
4030
+ }
4031
+ function componentsToString(components) {
4032
+ return `/${components.flatMap(component => {
4033
+ if (component.value == null) {
4034
+ return component.name;
4035
+ }
4036
+ const codec = registry.getProtocol(component.code);
4037
+ if (codec == null) {
4038
+ throw new InvalidMultiaddrError(`Unknown protocol code ${component.code}`);
4039
+ }
4040
+ return [
4041
+ component.name,
4042
+ codec.valueToString?.(component.value) ?? component.value
4043
+ ];
4044
+ }).join('/')}`;
3676
4045
  }
3677
4046
  /**
3678
4047
  * For the passed address, return the serialized size
3679
4048
  */
3680
- function sizeForAddr(p, addr) {
3681
- if (p.size > 0) {
3682
- return p.size / 8;
3683
- }
3684
- else if (p.size === 0) {
4049
+ function sizeForAddr(codec, bytes, offset) {
4050
+ if (codec.size == null || codec.size === 0) {
3685
4051
  return 0;
3686
4052
  }
3687
- else {
3688
- const size = decode$4(addr instanceof Uint8Array ? addr : Uint8Array.from(addr));
3689
- return size + encodingLength$1(size);
3690
- }
3691
- }
3692
- function cleanPath(str) {
3693
- return '/' + str.trim().split('/').filter((a) => a).join('/');
3694
- }
3695
- class ParseError extends Error {
3696
- static name = 'ParseError';
3697
- name = 'ParseError';
3698
- constructor(str) {
3699
- super(`Error parsing address: ${str}`);
4053
+ if (codec.size > 0) {
4054
+ return codec.size / 8;
3700
4055
  }
4056
+ return decode$4(bytes, offset);
3701
4057
  }
3702
4058
 
3703
- /* eslint-disable complexity */
3704
4059
  const inspect = Symbol.for('nodejs.util.inspect.custom');
3705
- const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr');
4060
+ const symbol = Symbol.for('@multiformats/multiaddr');
3706
4061
  const DNS_CODES = [
3707
- getProtocol('dns').code,
3708
- getProtocol('dns4').code,
3709
- getProtocol('dns6').code,
3710
- getProtocol('dnsaddr').code
4062
+ CODE_DNS,
4063
+ CODE_DNS4,
4064
+ CODE_DNS6,
4065
+ CODE_DNSADDR
3711
4066
  ];
3712
4067
  class NoAvailableResolverError extends Error {
3713
4068
  constructor(message = 'No available resolver') {
@@ -3715,44 +4070,56 @@ class NoAvailableResolverError extends Error {
3715
4070
  this.name = 'NoAvailableResolverError';
3716
4071
  }
3717
4072
  }
4073
+ function toComponents(addr) {
4074
+ if (addr == null) {
4075
+ addr = '/';
4076
+ }
4077
+ if (isMultiaddr(addr)) {
4078
+ return addr.getComponents();
4079
+ }
4080
+ if (addr instanceof Uint8Array) {
4081
+ return bytesToComponents(addr);
4082
+ }
4083
+ if (typeof addr === 'string') {
4084
+ addr = addr
4085
+ .replace(/\/(\/)+/, '/')
4086
+ .replace(/(\/)+$/, '');
4087
+ if (addr === '') {
4088
+ addr = '/';
4089
+ }
4090
+ return stringToComponents(addr);
4091
+ }
4092
+ if (Array.isArray(addr)) {
4093
+ return addr;
4094
+ }
4095
+ throw new InvalidMultiaddrError('Must be a string, Uint8Array, Component[], or another Multiaddr');
4096
+ }
3718
4097
  /**
3719
4098
  * Creates a {@link Multiaddr} from a {@link MultiaddrInput}
3720
4099
  */
3721
4100
  class Multiaddr {
3722
- bytes;
3723
- #string;
3724
- #tuples;
3725
- #stringTuples;
3726
- #path;
3727
4101
  [symbol] = true;
3728
- constructor(addr) {
3729
- // default
3730
- if (addr == null) {
3731
- addr = '';
3732
- }
3733
- let parts;
3734
- if (addr instanceof Uint8Array) {
3735
- parts = bytesToMultiaddrParts(addr);
3736
- }
3737
- else if (typeof addr === 'string') {
3738
- if (addr.length > 0 && addr.charAt(0) !== '/') {
3739
- throw new Error(`multiaddr "${addr}" must start with a "/"`);
3740
- }
3741
- parts = stringToMultiaddrParts(addr);
3742
- }
3743
- else if (isMultiaddr(addr)) { // Multiaddr
3744
- parts = bytesToMultiaddrParts(addr.bytes);
4102
+ #components;
4103
+ // cache string representation
4104
+ #string;
4105
+ // cache byte representation
4106
+ #bytes;
4107
+ constructor(addr = '/', options = {}) {
4108
+ this.#components = toComponents(addr);
4109
+ if (options.validate !== false) {
4110
+ validate(this);
3745
4111
  }
3746
- else {
3747
- throw new Error('addr must be a string, Buffer, or another Multiaddr');
4112
+ }
4113
+ get bytes() {
4114
+ if (this.#bytes == null) {
4115
+ this.#bytes = componentsToBytes(this.#components);
3748
4116
  }
3749
- this.bytes = parts.bytes;
3750
- this.#string = parts.string;
3751
- this.#tuples = parts.tuples;
3752
- this.#stringTuples = parts.stringTuples;
3753
- this.#path = parts.path;
4117
+ return this.#bytes;
3754
4118
  }
3755
4119
  toString() {
4120
+ if (this.#string == null) {
4121
+ this.#string = componentsToString(this.#components);
4122
+ }
3756
4123
  return this.#string;
3757
4124
  }
3758
4125
  toJSON() {
@@ -3764,31 +4131,25 @@ class Multiaddr {
3764
4131
  let host;
3765
4132
  let port;
3766
4133
  let zone = '';
3767
- const tcp = getProtocol('tcp');
3768
- const udp = getProtocol('udp');
3769
- const ip4 = getProtocol('ip4');
3770
- const ip6 = getProtocol('ip6');
3771
- const dns6 = getProtocol('dns6');
3772
- const ip6zone = getProtocol('ip6zone');
3773
- for (const [code, value] of this.stringTuples()) {
3774
- if (code === ip6zone.code) {
4134
+ for (const { code, name, value } of this.#components) {
4135
+ if (code === CODE_IP6ZONE) {
3775
4136
  zone = `%${value ?? ''}`;
3776
4137
  }
3777
4138
  // default to https when protocol & port are omitted from DNS addrs
3778
4139
  if (DNS_CODES.includes(code)) {
3779
- transport = tcp.name === 'tcp' ? 'tcp' : 'udp';
4140
+ transport = 'tcp';
3780
4141
  port = 443;
3781
4142
  host = `${value ?? ''}${zone}`;
3782
- family = code === dns6.code ? 6 : 4;
4143
+ family = code === CODE_DNS6 ? 6 : 4;
3783
4144
  }
3784
- if (code === tcp.code || code === udp.code) {
3785
- transport = getProtocol(code).name === 'tcp' ? 'tcp' : 'udp';
4145
+ if (code === CODE_TCP || code === CODE_UDP) {
4146
+ transport = name === 'tcp' ? 'tcp' : 'udp';
3786
4147
  port = parseInt(value ?? '');
3787
4148
  }
3788
- if (code === ip4.code || code === ip6.code) {
3789
- transport = getProtocol(code).name === 'tcp' ? 'tcp' : 'udp';
4149
+ if (code === CODE_IP4 || code === CODE_IP6) {
4150
+ transport = 'tcp';
3790
4151
  host = `${value ?? ''}${zone}`;
3791
- family = code === ip6.code ? 6 : 4;
4152
+ family = code === CODE_IP6 ? 6 : 4;
3792
4153
  }
3793
4154
  }
3794
4155
  if (family == null || transport == null || host == null || port == null) {
@@ -3802,25 +4163,44 @@ class Multiaddr {
3802
4163
  };
3803
4164
  return opts;
3804
4165
  }
4166
+ getComponents() {
4167
+ return [
4168
+ ...this.#components
4169
+ ];
4170
+ }
3805
4171
  protos() {
3806
- return this.#tuples.map(([code]) => Object.assign({}, getProtocol(code)));
4172
+ return this.#components.map(({ code, value }) => {
4173
+ const codec = registry.getProtocol(code);
4174
+ return {
4175
+ code,
4176
+ size: codec.size ?? 0,
4177
+ name: codec.name,
4178
+ resolvable: Boolean(codec.resolvable),
4179
+ path: Boolean(codec.path)
4180
+ };
4181
+ });
3807
4182
  }
3808
4183
  protoCodes() {
3809
- return this.#tuples.map(([code]) => code);
4184
+ return this.#components.map(({ code }) => code);
3810
4185
  }
3811
4186
  protoNames() {
3812
- return this.#tuples.map(([code]) => getProtocol(code).name);
4187
+ return this.#components.map(({ name }) => name);
3813
4188
  }
3814
4189
  tuples() {
3815
- return this.#tuples.map(([code, value]) => {
4190
+ return this.#components.map(({ code, value }) => {
3816
4191
  if (value == null) {
3817
4192
  return [code];
3818
4193
  }
3819
- return [code, value];
4194
+ const codec = registry.getProtocol(code);
4195
+ const output = [code];
4196
+ if (value != null) {
4197
+ output.push(codec.valueToBytes?.(value) ?? fromString(value));
4198
+ }
4199
+ return output;
3820
4200
  });
3821
4201
  }
3822
4202
  stringTuples() {
3823
- return this.#stringTuples.map(([code, value]) => {
4203
+ return this.#components.map(({ code, value }) => {
3824
4204
  if (value == null) {
3825
4205
  return [code];
3826
4206
  }
@@ -3828,37 +4208,47 @@ class Multiaddr {
3828
4208
  });
3829
4209
  }
3830
4210
  encapsulate(addr) {
3831
- addr = new Multiaddr(addr);
3832
- return new Multiaddr(this.toString() + addr.toString());
4211
+ const ma = new Multiaddr(addr);
4212
+ return new Multiaddr([
4213
+ ...this.#components,
4214
+ ...ma.getComponents()
4215
+ ], {
4216
+ validate: false
4217
+ });
3833
4218
  }
3834
4219
  decapsulate(addr) {
3835
4220
  const addrString = addr.toString();
3836
4221
  const s = this.toString();
3837
4222
  const i = s.lastIndexOf(addrString);
3838
4223
  if (i < 0) {
3839
- throw new Error(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`);
4224
+ throw new InvalidParametersError(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`);
3840
4225
  }
3841
- return new Multiaddr(s.slice(0, i));
4226
+ return new Multiaddr(s.slice(0, i), {
4227
+ validate: false
4228
+ });
3842
4229
  }
3843
4230
  decapsulateCode(code) {
3844
- const tuples = this.tuples();
3845
- for (let i = tuples.length - 1; i >= 0; i--) {
3846
- if (tuples[i][0] === code) {
3847
- return new Multiaddr(tuplesToBytes(tuples.slice(0, i)));
4231
+ let index;
4232
+ for (let i = this.#components.length - 1; i > -1; i--) {
4233
+ if (this.#components[i].code === code) {
4234
+ index = i;
4235
+ break;
3848
4236
  }
3849
4237
  }
3850
- return this;
4238
+ return new Multiaddr(this.#components.slice(0, index), {
4239
+ validate: false
4240
+ });
3851
4241
  }
3852
4242
  getPeerId() {
3853
4243
  try {
3854
4244
  let tuples = [];
3855
- this.stringTuples().forEach(([code, name]) => {
3856
- if (code === names.p2p.code) {
3857
- tuples.push([code, name]);
4245
+ this.#components.forEach(({ code, value }) => {
4246
+ if (code === CODE_P2P) {
4247
+ tuples.push([code, value]);
3858
4248
  }
3859
4249
  // if this is a p2p-circuit address, return the target peer id if present
3860
4250
  // not the peer id of the relay
3861
- if (code === names['p2p-circuit'].code) {
4251
+ if (code === CODE_P2P_CIRCUIT) {
3862
4252
  tuples = [];
3863
4253
  }
3864
4254
  });
@@ -3869,10 +4259,10 @@ class Multiaddr {
3869
4259
  // peer id is base58btc encoded string but not multibase encoded so add the `z`
3870
4260
  // prefix so we can validate that it is correctly encoded
3871
4261
  if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
3872
- return toString$1(base58btc.decode(`z${peerIdStr}`), 'base58btc');
4262
+ return toString(base58btc.decode(`z${peerIdStr}`), 'base58btc');
3873
4263
  }
3874
4264
  // try to parse peer id as CID
3875
- return toString$1(CID.parse(peerIdStr).multihash.bytes, 'base58btc');
4265
+ return toString(CID.parse(peerIdStr).multihash.bytes, 'base58btc');
3876
4266
  }
3877
4267
  return null;
3878
4268
  }
@@ -3881,7 +4271,14 @@ class Multiaddr {
3881
4271
  }
3882
4272
  }
3883
4273
  getPath() {
3884
- return this.#path;
4274
+ for (const component of this.#components) {
4275
+ const codec = registry.getProtocol(component.code);
4276
+ if (!codec.path) {
4277
+ continue;
4278
+ }
4279
+ return component.value ?? null;
4280
+ }
4281
+ return null;
3885
4282
  }
3886
4283
  equals(addr) {
3887
4284
  return equals(this.bytes, addr.bytes);
@@ -3910,15 +4307,14 @@ class Multiaddr {
3910
4307
  port: options.port
3911
4308
  };
3912
4309
  }
3913
- isThinWaistAddress(addr) {
3914
- const protos = (addr ?? this).protos();
3915
- if (protos.length !== 2) {
4310
+ isThinWaistAddress() {
4311
+ if (this.#components.length !== 2) {
3916
4312
  return false;
3917
4313
  }
3918
- if (protos[0].code !== 4 && protos[0].code !== 41) {
4314
+ if (this.#components[0].code !== CODE_IP4 && this.#components[0].code !== CODE_IP6) {
3919
4315
  return false;
3920
4316
  }
3921
- if (protos[1].code !== 6 && protos[1].code !== 273) {
4317
+ if (this.#components[1].code !== CODE_TCP && this.#components[1].code !== CODE_UDP) {
3922
4318
  return false;
3923
4319
  }
3924
4320
  return true;
@@ -3936,9 +4332,23 @@ class Multiaddr {
3936
4332
  * ```
3937
4333
  */
3938
4334
  [inspect]() {
3939
- return `Multiaddr(${this.#string})`;
4335
+ return `Multiaddr(${this.toString()})`;
3940
4336
  }
3941
4337
  }
4338
+ /**
4339
+ * Ensures all multiaddr tuples are correct. Throws if any invalid protocols or
4340
+ * values are encountered.
4341
+ */
4342
+ function validate(addr) {
4343
+ addr.getComponents()
4344
+ .forEach(component => {
4345
+ const codec = registry.getProtocol(component.code);
4346
+ if (component.value == null) {
4347
+ return;
4348
+ }
4349
+ codec.validate?.(component.value);
4350
+ });
4351
+ }
3942
4352
 
3943
4353
  /**
3944
4354
  * @packageDocumentation
@@ -4033,9 +4443,42 @@ class Multiaddr {
4033
4443
  * console.info(resolved)
4034
4444
  * // [Multiaddr('/ip4/147.75...'), Multiaddr('/ip4/147.75...'), Multiaddr('/ip4/147.75...')...]
4035
4445
  * ```
4446
+ *
4447
+ * @example Adding custom protocols
4448
+ *
4449
+ * To add application-specific or experimental protocols, add a protocol codec
4450
+ * to the protocol registry:
4451
+ *
4452
+ * ```ts
4453
+ * import { registry, V, multiaddr } from '@multiformats/multiaddr'
4454
+ * import type { ProtocolCodec } from '@multiformats/multiaddr'
4455
+ *
4456
+ * const maWithCustomTuple = '/custom-protocol/hello'
4457
+ *
4458
+ * // throws UnknownProtocolError
4459
+ * multiaddr(maWithCustomTuple)
4460
+ *
4461
+ * const protocol: ProtocolCodec = {
4462
+ * code: 2059,
4463
+ * name: 'custom-protocol',
4464
+ * size: V
4465
+ * // V means variable length, can also be 0, a positive integer (e.g. a fixed
4466
+ * // length or omitted
4467
+ * }
4468
+ *
4469
+ * registry.addProtocol(protocol)
4470
+ *
4471
+ * // does not throw UnknownProtocolError
4472
+ * multiaddr(maWithCustomTuple)
4473
+ *
4474
+ * // protocols can also be removed
4475
+ * registry.removeProtocol(protocol.code)
4476
+ * ```
4036
4477
  */
4037
4478
  /**
4038
4479
  * All configured {@link Resolver}s
4480
+ *
4481
+ * @deprecated DNS resolving will be removed in a future release
4039
4482
  */
4040
4483
  const resolvers = new Map();
4041
4484
  /**
@@ -4631,9 +5074,9 @@ class ConnectionManager extends TypedEventEmitter {
4631
5074
  log$1.warn(`Already connected to peer ${peerId.toString()}. Not dialing.`);
4632
5075
  return false;
4633
5076
  }
4634
- const isSameShard = await this.isPeerTopicConfigured(peerId);
5077
+ const isSameShard = await this.isPeerOnSameShard(peerId);
4635
5078
  if (!isSameShard) {
4636
- const shardInfo = await this.getPeerShardInfo(peerId, this.libp2p.peerStore);
5079
+ const shardInfo = await this.getPeerShardInfo(peerId);
4637
5080
  log$1.warn(`Discovered peer ${peerId.toString()} with ShardInfo ${shardInfo} is not part of any of the configured pubsub topics (${this.pubsubTopics}).
4638
5081
  Not dialing.`);
4639
5082
  return false;
@@ -4689,17 +5132,25 @@ class ConnectionManager extends TypedEventEmitter {
4689
5132
  return [];
4690
5133
  }
4691
5134
  }
4692
- async isPeerTopicConfigured(peerId) {
4693
- const shardInfo = await this.getPeerShardInfo(peerId, this.libp2p.peerStore);
4694
- // If there's no shard information, simply return true
4695
- if (!shardInfo)
5135
+ async isPeerOnSameShard(peerId) {
5136
+ const shardInfo = await this.getPeerShardInfo(peerId);
5137
+ if (!shardInfo) {
4696
5138
  return true;
5139
+ }
4697
5140
  const pubsubTopics = shardInfoToPubsubTopics(shardInfo);
4698
5141
  const isTopicConfigured = pubsubTopics.some((topic) => this.pubsubTopics.includes(topic));
4699
5142
  return isTopicConfigured;
4700
5143
  }
4701
- async getPeerShardInfo(peerId, peerStore) {
4702
- const peer = await peerStore.get(peerId);
5144
+ async isPeerOnPubsubTopic(peerId, pubsubTopic) {
5145
+ const shardInfo = await this.getPeerShardInfo(peerId);
5146
+ if (!shardInfo) {
5147
+ return true;
5148
+ }
5149
+ const pubsubTopics = shardInfoToPubsubTopics(shardInfo);
5150
+ return pubsubTopics.some((t) => t === pubsubTopic);
5151
+ }
5152
+ async getPeerShardInfo(peerId) {
5153
+ const peer = await this.libp2p.peerStore.get(peerId);
4703
5154
  const shardInfoBytes = peer.metadata.get("shardInfo");
4704
5155
  if (!shardInfoBytes)
4705
5156
  return undefined;
@@ -4745,13 +5196,15 @@ class ConnectionManager extends TypedEventEmitter {
4745
5196
 
4746
5197
  const log = new Logger("metadata");
4747
5198
  const MetadataCodec = "/vac/waku/metadata/1.0.0";
4748
- class Metadata extends BaseProtocol {
5199
+ class Metadata {
4749
5200
  pubsubTopics;
5201
+ streamManager;
4750
5202
  libp2pComponents;
4751
5203
  handshakesConfirmed = new Map();
5204
+ multicodec = MetadataCodec;
4752
5205
  constructor(pubsubTopics, libp2p) {
4753
- super(MetadataCodec, libp2p.components, pubsubTopics);
4754
5206
  this.pubsubTopics = pubsubTopics;
5207
+ this.streamManager = new StreamManager(MetadataCodec, libp2p);
4755
5208
  this.libp2pComponents = libp2p;
4756
5209
  void libp2p.registrar.handle(MetadataCodec, (streamData) => {
4757
5210
  void this.onRequest(streamData);
@@ -4771,7 +5224,7 @@ class Metadata extends BaseProtocol {
4771
5224
  }
4772
5225
  let stream;
4773
5226
  try {
4774
- stream = await this.getStream(peerId);
5227
+ stream = await this.streamManager.getStream(peerId);
4775
5228
  }
4776
5229
  catch (error) {
4777
5230
  log.error("Failed to get stream", error);
@@ -4854,4 +5307,85 @@ function wakuMetadata(pubsubTopics) {
4854
5307
  return (components) => new Metadata(pubsubTopics, components);
4855
5308
  }
4856
5309
 
4857
- export { ConnectionManager, FilterCodecs, FilterCore, LightPushCodec, LightPushCore, MetadataCodec, StoreCodec, StoreCore, createEncoder, index$3 as message, wakuMetadata, index$2 as waku_filter, index$1 as waku_light_push, index as waku_store };
5310
+ /**
5311
+ * Deterministic Message Hashing as defined in
5312
+ * [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/#deterministic-message-hashing)
5313
+ *
5314
+ * Computes a SHA-256 hash of the concatenation of pubsub topic, payload, content topic, meta, and timestamp.
5315
+ *
5316
+ * @param pubsubTopic - The pubsub topic string
5317
+ * @param message - The message to be hashed
5318
+ * @returns A Uint8Array containing the SHA-256 hash
5319
+ *
5320
+ * @example
5321
+ * ```typescript
5322
+ * import { messageHash } from "@waku/core";
5323
+ *
5324
+ * const pubsubTopic = "/waku/2/default-waku/proto";
5325
+ * const message = {
5326
+ * payload: new Uint8Array([1, 2, 3, 4]),
5327
+ * contentTopic: "/waku/2/default-content/proto",
5328
+ * meta: new Uint8Array([5, 6, 7, 8]),
5329
+ * timestamp: new Date()
5330
+ * };
5331
+ *
5332
+ * const hash = messageHash(pubsubTopic, message);
5333
+ * ```
5334
+ */
5335
+ function messageHash(pubsubTopic, message) {
5336
+ const pubsubTopicBytes = utf8ToBytes(pubsubTopic);
5337
+ const contentTopicBytes = utf8ToBytes(message.contentTopic);
5338
+ const timestampBytes = tryConvertTimestampToBytes(message.timestamp);
5339
+ const bytes = concat$1([
5340
+ pubsubTopicBytes,
5341
+ message.payload,
5342
+ contentTopicBytes,
5343
+ message.meta,
5344
+ timestampBytes
5345
+ ].filter(isDefined));
5346
+ return sha256(bytes);
5347
+ }
5348
+ function tryConvertTimestampToBytes(timestamp) {
5349
+ if (!timestamp) {
5350
+ return;
5351
+ }
5352
+ let bigIntTimestamp;
5353
+ if (typeof timestamp === "bigint") {
5354
+ bigIntTimestamp = timestamp;
5355
+ }
5356
+ else {
5357
+ bigIntTimestamp = BigInt(timestamp.valueOf()) * 1000000n;
5358
+ }
5359
+ return numberToBytes(bigIntTimestamp);
5360
+ }
5361
+ /**
5362
+ * Computes a deterministic message hash and returns it as a hexadecimal string.
5363
+ * This is a convenience wrapper around messageHash that converts the result to a hex string.
5364
+ *
5365
+ * @param pubsubTopic - The pubsub topic string
5366
+ * @param message - The message to be hashed
5367
+ * @returns A string containing the hex representation of the SHA-256 hash
5368
+ *
5369
+ * @example
5370
+ * ```typescript
5371
+ * import { messageHashStr } from "@waku/core";
5372
+ *
5373
+ * const pubsubTopic = "/waku/2/default-waku/proto";
5374
+ * const message = {
5375
+ * payload: new Uint8Array([1, 2, 3, 4]),
5376
+ * contentTopic: "/waku/2/default-content/proto",
5377
+ * meta: new Uint8Array([5, 6, 7, 8]),
5378
+ * timestamp: new Date()
5379
+ * };
5380
+ *
5381
+ * const hashString = messageHashStr(pubsubTopic, message);
5382
+ * console.log(hashString); // e.g. "a1b2c3d4..."
5383
+ * ```
5384
+ */
5385
+ function messageHashStr(pubsubTopic, message) {
5386
+ const hash = messageHash(pubsubTopic, message);
5387
+ const hashStr = bytesToHex(hash);
5388
+ return hashStr;
5389
+ }
5390
+
5391
+ export { ConnectionManager, FilterCodecs, FilterCore, LightPushCodec, LightPushCore, MetadataCodec, StoreCodec, StoreCore, StreamManager, createEncoder, index$3 as message, messageHash, messageHashStr, wakuMetadata, index$2 as waku_filter, index$1 as waku_light_push, index as waku_store };