@waku/core 0.0.36-383e0b2.0 → 0.0.36-4c18ca2.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 (50) hide show
  1. package/bundle/index.js +1098 -593
  2. package/bundle/lib/message/version_0.js +1 -2
  3. package/bundle/{version_0-4TwtF5aQ.js → version_0-9DPFjcJG.js} +1661 -65
  4. package/dist/.tsbuildinfo +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/filter/filter.d.ts +8 -5
  9. package/dist/lib/filter/filter.js +30 -10
  10. package/dist/lib/filter/filter.js.map +1 -1
  11. package/dist/lib/light_push/light_push.d.ts +4 -3
  12. package/dist/lib/light_push/light_push.js +6 -4
  13. package/dist/lib/light_push/light_push.js.map +1 -1
  14. package/dist/lib/message/version_0.d.ts +3 -4
  15. package/dist/lib/message/version_0.js +1 -4
  16. package/dist/lib/message/version_0.js.map +1 -1
  17. package/dist/lib/message_hash/index.d.ts +1 -0
  18. package/dist/lib/message_hash/index.js +2 -0
  19. package/dist/lib/message_hash/index.js.map +1 -0
  20. package/dist/lib/message_hash/message_hash.d.ts +52 -0
  21. package/dist/lib/message_hash/message_hash.js +84 -0
  22. package/dist/lib/message_hash/message_hash.js.map +1 -0
  23. package/dist/lib/metadata/metadata.js +6 -4
  24. package/dist/lib/metadata/metadata.js.map +1 -1
  25. package/dist/lib/store/rpc.js +0 -4
  26. package/dist/lib/store/rpc.js.map +1 -1
  27. package/dist/lib/store/store.d.ts +4 -3
  28. package/dist/lib/store/store.js +6 -4
  29. package/dist/lib/store/store.js.map +1 -1
  30. package/dist/lib/stream_manager/stream_manager.d.ts +3 -4
  31. package/dist/lib/stream_manager/stream_manager.js +6 -8
  32. package/dist/lib/stream_manager/stream_manager.js.map +1 -1
  33. package/package.json +1 -1
  34. package/src/index.ts +2 -0
  35. package/src/lib/filter/filter.ts +46 -14
  36. package/src/lib/light_push/light_push.ts +8 -5
  37. package/src/lib/message/version_0.ts +3 -7
  38. package/src/lib/message_hash/index.ts +1 -0
  39. package/src/lib/message_hash/message_hash.ts +106 -0
  40. package/src/lib/metadata/metadata.ts +8 -5
  41. package/src/lib/store/rpc.ts +0 -4
  42. package/src/lib/store/store.ts +8 -5
  43. package/src/lib/stream_manager/stream_manager.ts +8 -6
  44. package/bundle/base_protocol-DvQrudwy.js +0 -152
  45. package/bundle/index-CTo1my9M.js +0 -1543
  46. package/bundle/lib/base_protocol.js +0 -2
  47. package/dist/lib/base_protocol.d.ts +0 -18
  48. package/dist/lib/base_protocol.js +0 -25
  49. package/dist/lib/base_protocol.js.map +0 -1
  50. 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-4TwtF5aQ.js';
2
- export { o as createDecoder } from './version_0-4TwtF5aQ.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,7 +2472,7 @@ 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 {
@@ -2350,7 +2507,7 @@ class FilterCore extends BaseProtocol {
2350
2507
  async unsubscribe(pubsubTopic, peerId, contentTopics) {
2351
2508
  let stream;
2352
2509
  try {
2353
- stream = await this.getStream(peerId);
2510
+ stream = await this.streamManager.getStream(peerId);
2354
2511
  }
2355
2512
  catch (error) {
2356
2513
  log$5.error(`Failed to get a stream for remote peer${peerId.toString()}`, error);
@@ -2382,7 +2539,7 @@ class FilterCore extends BaseProtocol {
2382
2539
  };
2383
2540
  }
2384
2541
  async unsubscribeAll(pubsubTopic, peerId) {
2385
- const stream = await this.getStream(peerId);
2542
+ const stream = await this.streamManager.getStream(peerId);
2386
2543
  const request = FilterSubscribeRpc.createUnsubscribeAllRequest(pubsubTopic);
2387
2544
  const res = await pipe([request.encode()], encode, stream, decode, async (source) => await all(source));
2388
2545
  if (!res || !res.length) {
@@ -2413,7 +2570,7 @@ class FilterCore extends BaseProtocol {
2413
2570
  async ping(peerId) {
2414
2571
  let stream;
2415
2572
  try {
2416
- stream = await this.getStream(peerId);
2573
+ stream = await this.streamManager.getStream(peerId);
2417
2574
  }
2418
2575
  catch (error) {
2419
2576
  log$5.error(`Failed to get a stream for remote peer${peerId.toString()}`, error);
@@ -2482,7 +2639,7 @@ class FilterCore extends BaseProtocol {
2482
2639
  log$5.error("Pubsub topic missing from push message");
2483
2640
  return;
2484
2641
  }
2485
- await this.handleIncomingMessage(pubsubTopic, wakuMessage, connection.remotePeer.toString());
2642
+ await FilterCore.handleIncomingMessage?.(pubsubTopic, wakuMessage, connection.remotePeer.toString());
2486
2643
  }
2487
2644
  }).then(() => {
2488
2645
  log$5.info("Receiving pipe closed.");
@@ -2555,11 +2712,13 @@ const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
2555
2712
  /**
2556
2713
  * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
2557
2714
  */
2558
- class LightPushCore extends BaseProtocol {
2715
+ class LightPushCore {
2559
2716
  pubsubTopics;
2717
+ streamManager;
2718
+ multicodec = LightPushCodec;
2560
2719
  constructor(pubsubTopics, libp2p) {
2561
- super(LightPushCodec, libp2p.components, pubsubTopics);
2562
2720
  this.pubsubTopics = pubsubTopics;
2721
+ this.streamManager = new StreamManager(LightPushCodec, libp2p.components);
2563
2722
  }
2564
2723
  async preparePushMessage(encoder, message) {
2565
2724
  try {
@@ -2603,7 +2762,7 @@ class LightPushCore extends BaseProtocol {
2603
2762
  }
2604
2763
  let stream;
2605
2764
  try {
2606
- stream = await this.getStream(peerId);
2765
+ stream = await this.streamManager.getStream(peerId);
2607
2766
  }
2608
2767
  catch (error) {
2609
2768
  log$4.error("Failed to get stream", error);
@@ -2727,19 +2886,15 @@ class StoreQueryRequest {
2727
2886
  ? BigInt(params.paginationLimit)
2728
2887
  : undefined
2729
2888
  });
2730
- // Validate request parameters based on RFC
2731
2889
  const isHashQuery = params.messageHashes && params.messageHashes.length > 0;
2732
2890
  const hasContentTopics = params.contentTopics && params.contentTopics.length > 0;
2733
2891
  const hasTimeFilter = params.timeStart || params.timeEnd;
2734
2892
  if (isHashQuery) {
2735
- // Message hash lookup queries cannot include content topics or time filters
2736
- // but pubsubTopic is allowed/required
2737
2893
  if (hasContentTopics || hasTimeFilter) {
2738
2894
  throw new Error("Message hash lookup queries cannot include content filter criteria (contentTopics, timeStart, or timeEnd)");
2739
2895
  }
2740
2896
  }
2741
2897
  else {
2742
- // Content-filtered queries require both pubsubTopic and contentTopics to be set together
2743
2898
  if ((params.pubsubTopic &&
2744
2899
  (!params.contentTopics || params.contentTopics.length === 0)) ||
2745
2900
  (!params.pubsubTopic &&
@@ -2786,11 +2941,13 @@ class StoreQueryResponse {
2786
2941
 
2787
2942
  const log$3 = new Logger("store");
2788
2943
  const StoreCodec = "/vac/waku/store-query/3.0.0";
2789
- class StoreCore extends BaseProtocol {
2944
+ class StoreCore {
2790
2945
  pubsubTopics;
2946
+ streamManager;
2947
+ multicodec = StoreCodec;
2791
2948
  constructor(pubsubTopics, libp2p) {
2792
- super(StoreCodec, libp2p.components, pubsubTopics);
2793
2949
  this.pubsubTopics = pubsubTopics;
2950
+ this.streamManager = new StreamManager(StoreCodec, libp2p.components);
2794
2951
  }
2795
2952
  async *queryPerPage(queryOpts, decoders, peerId) {
2796
2953
  // Only validate decoder content topics for content-filtered queries
@@ -2815,7 +2972,7 @@ class StoreCore extends BaseProtocol {
2815
2972
  });
2816
2973
  let stream;
2817
2974
  try {
2818
- stream = await this.getStream(peerId);
2975
+ stream = await this.streamManager.getStream(peerId);
2819
2976
  }
2820
2977
  catch (e) {
2821
2978
  log$3.error("Failed to get stream", e);
@@ -2887,9 +3044,71 @@ function isPeerId(other) {
2887
3044
  return Boolean(other?.[peerIdSymbol]);
2888
3045
  }
2889
3046
 
3047
+ /**
3048
+ * @packageDocumentation
3049
+ *
3050
+ * Adds types to the EventTarget class.
3051
+ *
3052
+ * Hopefully this won't be necessary
3053
+ * forever:
3054
+ *
3055
+ * - https://github.com/microsoft/TypeScript/issues/28357
3056
+ * - https://github.com/microsoft/TypeScript/issues/43477
3057
+ * - https://github.com/microsoft/TypeScript/issues/299
3058
+ * - https://www.npmjs.com/package/typed-events
3059
+ * - https://www.npmjs.com/package/typed-event-emitter
3060
+ * - https://www.npmjs.com/package/typed-event-target
3061
+ * - etc
3062
+ *
3063
+ * In addition to types, a `safeDispatchEvent` method is available which
3064
+ * prevents dispatching events that aren't in the event map, and a
3065
+ * `listenerCount` method which reports the number of listeners that are
3066
+ * currently registered for a given event.
3067
+ *
3068
+ * @example
3069
+ *
3070
+ * ```ts
3071
+ * import { TypedEventEmitter } from 'main-event'
3072
+ * import type { TypedEventTarget } from 'main-event'
3073
+ *
3074
+ * interface EventTypes {
3075
+ * 'test': CustomEvent<string>
3076
+ * }
3077
+ *
3078
+ * const target = new TypedEventEmitter<EventTypes>()
3079
+ *
3080
+ * // it's a regular EventTarget
3081
+ * console.info(target instanceof EventTarget) // true
3082
+ *
3083
+ * // register listeners normally
3084
+ * target.addEventListener('test', (evt) => {
3085
+ * // evt is CustomEvent<string>
3086
+ * })
3087
+ *
3088
+ * // @ts-expect-error 'derp' is not in the event map
3089
+ * target.addEventListener('derp', () => {})
3090
+ *
3091
+ * // use normal dispatchEvent method
3092
+ * target.dispatchEvent(new CustomEvent('test', {
3093
+ * detail: 'hello'
3094
+ * }))
3095
+ *
3096
+ * // use type safe dispatch method
3097
+ * target.safeDispatchEvent('test', {
3098
+ * detail: 'world'
3099
+ * })
3100
+ *
3101
+ * // report listener count
3102
+ * console.info(target.listenerCount('test')) // 0
3103
+ *
3104
+ * // event emitters can be used purely as interfaces too
3105
+ * function acceptTarget (target: TypedEventTarget<EventTypes>) {
3106
+ * // ...
3107
+ * }
3108
+ * ```
3109
+ */
2890
3110
  /**
2891
3111
  * An implementation of a typed event target
2892
- * etc
2893
3112
  */
2894
3113
  class TypedEventEmitter extends EventTarget {
2895
3114
  #listeners = new Map();
@@ -2939,6 +3158,26 @@ class TypedEventEmitter extends EventTarget {
2939
3158
  }
2940
3159
  }
2941
3160
 
3161
+ /**
3162
+ * Thrown when an invalid multiaddr is encountered
3163
+ */
3164
+ class InvalidMultiaddrError extends Error {
3165
+ static name = 'InvalidMultiaddrError';
3166
+ name = 'InvalidMultiaddrError';
3167
+ }
3168
+ class ValidationError extends Error {
3169
+ static name = 'ValidationError';
3170
+ name = 'ValidationError';
3171
+ }
3172
+ class InvalidParametersError extends Error {
3173
+ static name = 'InvalidParametersError';
3174
+ name = 'InvalidParametersError';
3175
+ }
3176
+ class UnknownProtocolError extends Error {
3177
+ static name = 'UnknownProtocolError';
3178
+ name = 'UnknownProtocolError';
3179
+ }
3180
+
2942
3181
  /* eslint-disable @typescript-eslint/no-unsafe-return */
2943
3182
  class Parser {
2944
3183
  index = 0;
@@ -3162,24 +3401,6 @@ function parseIPv6(input) {
3162
3401
  }
3163
3402
  return parser.new(input).parseWith(() => parser.readIPv6Addr());
3164
3403
  }
3165
- /** Parse `input` into IPv4 or IPv6 bytes. */
3166
- function parseIP(input, mapIPv4ToIPv6 = false) {
3167
- // strip zone index if it is present
3168
- if (input.includes("%")) {
3169
- input = input.split("%")[0];
3170
- }
3171
- if (input.length > MAX_IPV6_LENGTH) {
3172
- return undefined;
3173
- }
3174
- const addr = parser.new(input).parseWith(() => parser.readIPAddr());
3175
- if (!addr) {
3176
- return undefined;
3177
- }
3178
- if (mapIPv4ToIPv6 && addr.length === 4) {
3179
- return Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, addr[0], addr[1], addr[2], addr[3]]);
3180
- }
3181
- return addr;
3182
- }
3183
3404
 
3184
3405
  /** Check if `input` is IPv4. */
3185
3406
  function isIPv4(input) {
@@ -3189,347 +3410,69 @@ function isIPv4(input) {
3189
3410
  function isIPv6(input) {
3190
3411
  return Boolean(parseIPv6(input));
3191
3412
  }
3192
- /** Check if `input` is IPv4 or IPv6. */
3193
- function isIP(input) {
3194
- return Boolean(parseIP(input));
3195
- }
3196
3413
 
3197
- const isV4 = isIPv4;
3198
- const isV6 = isIPv6;
3199
- // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
3200
- // but with buf/offset args removed because we don't use them
3201
- const toBytes = function (ip) {
3202
- let offset = 0;
3203
- ip = ip.toString().trim();
3204
- if (isV4(ip)) {
3205
- const bytes = new Uint8Array(offset + 4);
3206
- ip.split(/\./g).forEach((byte) => {
3207
- bytes[offset++] = parseInt(byte, 10) & 0xff;
3208
- });
3209
- return bytes;
3210
- }
3211
- if (isV6(ip)) {
3212
- const sections = ip.split(':', 8);
3213
- let i;
3214
- for (i = 0; i < sections.length; i++) {
3215
- const isv4 = isV4(sections[i]);
3216
- let v4Buffer;
3217
- if (isv4) {
3218
- v4Buffer = toBytes(sections[i]);
3219
- sections[i] = toString$1(v4Buffer.slice(0, 2), 'base16');
3220
- }
3221
- if (v4Buffer != null && ++i < 8) {
3222
- sections.splice(i, 0, toString$1(v4Buffer.slice(2, 4), 'base16'));
3223
- }
3224
- }
3225
- if (sections[0] === '') {
3226
- while (sections.length < 8)
3227
- sections.unshift('0');
3228
- }
3229
- else if (sections[sections.length - 1] === '') {
3230
- while (sections.length < 8)
3231
- sections.push('0');
3232
- }
3233
- else if (sections.length < 8) {
3234
- for (i = 0; i < sections.length && sections[i] !== ''; i++)
3235
- ;
3236
- const argv = [i, 1];
3237
- for (i = 9 - sections.length; i > 0; i--) {
3238
- argv.push('0');
3239
- }
3240
- sections.splice.apply(sections, argv);
3241
- }
3242
- const bytes = new Uint8Array(offset + 16);
3243
- for (i = 0; i < sections.length; i++) {
3244
- const word = parseInt(sections[i], 16);
3245
- bytes[offset++] = (word >> 8) & 0xff;
3246
- bytes[offset++] = word & 0xff;
3247
- }
3248
- return bytes;
3249
- }
3250
- throw new Error('invalid ip address');
3251
- };
3252
- // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L63
3253
- const toString = function (buf, offset = 0, length) {
3254
- offset = ~~offset;
3255
- length = length ?? (buf.length - offset);
3256
- const view = new DataView(buf.buffer);
3257
- if (length === 4) {
3258
- const result = [];
3259
- // IPv4
3260
- for (let i = 0; i < length; i++) {
3261
- result.push(buf[offset + i]);
3262
- }
3263
- return result.join('.');
3264
- }
3265
- if (length === 16) {
3266
- const result = [];
3267
- // IPv6
3268
- for (let i = 0; i < length; i += 2) {
3269
- result.push(view.getUint16(offset + i).toString(16));
3270
- }
3271
- return result.join(':')
3272
- .replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3')
3273
- .replace(/:{3,4}/, '::');
3274
- }
3275
- return '';
3276
- };
3414
+ // the values here come from https://github.com/multiformats/multiaddr/blob/master/protocols.csv
3415
+ const CODE_IP4 = 4;
3416
+ const CODE_TCP = 6;
3417
+ const CODE_UDP = 273;
3418
+ const CODE_DCCP = 33;
3419
+ const CODE_IP6 = 41;
3420
+ const CODE_IP6ZONE = 42;
3421
+ const CODE_IPCIDR = 43;
3422
+ const CODE_DNS = 53;
3423
+ const CODE_DNS4 = 54;
3424
+ const CODE_DNS6 = 55;
3425
+ const CODE_DNSADDR = 56;
3426
+ const CODE_SCTP = 132;
3427
+ const CODE_UDT = 301;
3428
+ const CODE_UTP = 302;
3429
+ const CODE_UNIX = 400;
3430
+ const CODE_P2P = 421; // also IPFS
3431
+ const CODE_ONION = 444;
3432
+ const CODE_ONION3 = 445;
3433
+ const CODE_GARLIC64 = 446;
3434
+ const CODE_GARLIC32 = 447;
3435
+ const CODE_TLS = 448;
3436
+ const CODE_SNI = 449;
3437
+ const CODE_NOISE = 454;
3438
+ const CODE_QUIC = 460;
3439
+ const CODE_QUIC_V1 = 461;
3440
+ const CODE_WEBTRANSPORT = 465;
3441
+ const CODE_CERTHASH = 466;
3442
+ const CODE_HTTP = 480;
3443
+ const CODE_HTTP_PATH = 481;
3444
+ const CODE_HTTPS = 443;
3445
+ const CODE_WS = 477;
3446
+ const CODE_WSS = 478;
3447
+ const CODE_P2P_WEBSOCKET_STAR = 479;
3448
+ const CODE_P2P_STARDUST = 277;
3449
+ const CODE_P2P_WEBRTC_STAR = 275;
3450
+ const CODE_P2P_WEBRTC_DIRECT = 276;
3451
+ const CODE_WEBRTC_DIRECT = 280;
3452
+ const CODE_WEBRTC = 281;
3453
+ const CODE_P2P_CIRCUIT = 290;
3454
+ const CODE_MEMORY = 777;
3277
3455
 
3278
- const V = -1;
3279
- const names = {};
3280
- const codes = {};
3281
- const table = [
3282
- [4, 32, 'ip4'],
3283
- [6, 16, 'tcp'],
3284
- [33, 16, 'dccp'],
3285
- [41, 128, 'ip6'],
3286
- [42, V, 'ip6zone'],
3287
- [43, 8, 'ipcidr'],
3288
- [53, V, 'dns', true],
3289
- [54, V, 'dns4', true],
3290
- [55, V, 'dns6', true],
3291
- [56, V, 'dnsaddr', true],
3292
- [132, 16, 'sctp'],
3293
- [273, 16, 'udp'],
3294
- [275, 0, 'p2p-webrtc-star'],
3295
- [276, 0, 'p2p-webrtc-direct'],
3296
- [277, 0, 'p2p-stardust'],
3297
- [280, 0, 'webrtc-direct'],
3298
- [281, 0, 'webrtc'],
3299
- [290, 0, 'p2p-circuit'],
3300
- [301, 0, 'udt'],
3301
- [302, 0, 'utp'],
3302
- [400, V, 'unix', false, true],
3303
- // `ipfs` is added before `p2p` for legacy support.
3304
- // All text representations will default to `p2p`, but `ipfs` will
3305
- // still be supported
3306
- [421, V, 'ipfs'],
3307
- // `p2p` is the preferred name for 421, and is now the default
3308
- [421, V, 'p2p'],
3309
- [443, 0, 'https'],
3310
- [444, 96, 'onion'],
3311
- [445, 296, 'onion3'],
3312
- [446, V, 'garlic64'],
3313
- [448, 0, 'tls'],
3314
- [449, V, 'sni'],
3315
- [460, 0, 'quic'],
3316
- [461, 0, 'quic-v1'],
3317
- [465, 0, 'webtransport'],
3318
- [466, V, 'certhash'],
3319
- [477, 0, 'ws'],
3320
- [478, 0, 'wss'],
3321
- [479, 0, 'p2p-websocket-star'],
3322
- [480, 0, 'http'],
3323
- [481, V, 'http-path'],
3324
- [777, V, 'memory']
3325
- ];
3326
- // populate tables
3327
- table.forEach(row => {
3328
- const proto = createProtocol(...row);
3329
- codes[proto.code] = proto;
3330
- names[proto.name] = proto;
3331
- });
3332
- function createProtocol(code, size, name, resolvable, path) {
3333
- return {
3334
- code,
3335
- size,
3336
- name,
3337
- resolvable: Boolean(resolvable),
3338
- path: Boolean(path)
3456
+ function bytesToString(base) {
3457
+ return (buf) => {
3458
+ return toString(buf, base);
3339
3459
  };
3340
3460
  }
3341
- /**
3342
- * For the passed proto string or number, return a {@link Protocol}
3343
- *
3344
- * @example
3345
- *
3346
- * ```js
3347
- * import { protocol } from '@multiformats/multiaddr'
3348
- *
3349
- * console.info(protocol(4))
3350
- * // { code: 4, size: 32, name: 'ip4', resolvable: false, path: false }
3351
- * ```
3352
- */
3353
- function getProtocol(proto) {
3354
- if (typeof proto === 'number') {
3355
- if (codes[proto] != null) {
3356
- return codes[proto];
3357
- }
3358
- throw new Error(`no protocol with code: ${proto}`);
3359
- }
3360
- else if (typeof proto === 'string') {
3361
- if (names[proto] != null) {
3362
- return names[proto];
3363
- }
3364
- throw new Error(`no protocol with name: ${proto}`);
3365
- }
3366
- throw new Error(`invalid protocol id type: ${typeof proto}`);
3367
- }
3368
-
3369
- getProtocol('ip4');
3370
- getProtocol('ip6');
3371
- getProtocol('ipcidr');
3372
- /**
3373
- * Convert [code,Uint8Array] to string
3374
- */
3375
- // eslint-disable-next-line complexity
3376
- function convertToString(proto, buf) {
3377
- const protocol = getProtocol(proto);
3378
- switch (protocol.code) {
3379
- case 4: // ipv4
3380
- case 41: // ipv6
3381
- return bytes2ip(buf);
3382
- case 42: // ipv6zone
3383
- return bytes2str(buf);
3384
- case 43: // ipcidr
3385
- return toString$1(buf, 'base10');
3386
- case 6: // tcp
3387
- case 273: // udp
3388
- case 33: // dccp
3389
- case 132: // sctp
3390
- return bytes2port(buf).toString();
3391
- case 53: // dns
3392
- case 54: // dns4
3393
- case 55: // dns6
3394
- case 56: // dnsaddr
3395
- case 400: // unix
3396
- case 449: // sni
3397
- case 777: // memory
3398
- return bytes2str(buf);
3399
- case 421: // ipfs
3400
- return bytes2mh(buf);
3401
- case 444: // onion
3402
- return bytes2onion(buf);
3403
- case 445: // onion3
3404
- return bytes2onion(buf);
3405
- case 466: // certhash
3406
- return bytes2mb(buf);
3407
- case 481: // http-path
3408
- return globalThis.encodeURIComponent(bytes2str(buf));
3409
- default:
3410
- return toString$1(buf, 'base16'); // no clue. convert to hex
3411
- }
3412
- }
3413
- // eslint-disable-next-line complexity
3414
- function convertToBytes(proto, str) {
3415
- const protocol = getProtocol(proto);
3416
- switch (protocol.code) {
3417
- case 4: // ipv4
3418
- return ip2bytes(str);
3419
- case 41: // ipv6
3420
- return ip2bytes(str);
3421
- case 42: // ipv6zone
3422
- return str2bytes(str);
3423
- case 43: // ipcidr
3424
- return fromString(str, 'base10');
3425
- case 6: // tcp
3426
- case 273: // udp
3427
- case 33: // dccp
3428
- case 132: // sctp
3429
- return port2bytes(parseInt(str, 10));
3430
- case 53: // dns
3431
- case 54: // dns4
3432
- case 55: // dns6
3433
- case 56: // dnsaddr
3434
- case 400: // unix
3435
- case 449: // sni
3436
- case 777: // memory
3437
- return str2bytes(str);
3438
- case 421: // ipfs
3439
- return mh2bytes(str);
3440
- case 444: // onion
3441
- return onion2bytes(str);
3442
- case 445: // onion3
3443
- return onion32bytes(str);
3444
- case 466: // certhash
3445
- return mb2bytes(str);
3446
- case 481: // http-path
3447
- return str2bytes(globalThis.decodeURIComponent(str));
3448
- default:
3449
- return fromString(str, 'base16'); // no clue. convert from hex
3450
- }
3451
- }
3452
- const decoders = Object.values(bases).map((c) => c.decoder);
3453
- const anybaseDecoder = (function () {
3454
- let acc = decoders[0].or(decoders[1]);
3455
- decoders.slice(2).forEach((d) => (acc = acc.or(d)));
3456
- return acc;
3457
- })();
3458
- function ip2bytes(ipString) {
3459
- if (!isIP(ipString)) {
3460
- throw new Error('invalid ip address');
3461
- }
3462
- return toBytes(ipString);
3461
+ function stringToBytes(base) {
3462
+ return (buf) => {
3463
+ return fromString(buf, base);
3464
+ };
3463
3465
  }
3464
- function bytes2ip(ipBuff) {
3465
- const ipString = toString(ipBuff, 0, ipBuff.length);
3466
- if (ipString == null) {
3467
- throw new Error('ipBuff is required');
3468
- }
3469
- if (!isIP(ipString)) {
3470
- throw new Error('invalid ip address');
3471
- }
3472
- return ipString;
3466
+ function bytes2port(buf) {
3467
+ const view = new DataView(buf.buffer);
3468
+ return view.getUint16(buf.byteOffset).toString();
3473
3469
  }
3474
3470
  function port2bytes(port) {
3475
3471
  const buf = new ArrayBuffer(2);
3476
3472
  const view = new DataView(buf);
3477
- view.setUint16(0, port);
3473
+ view.setUint16(0, typeof port === 'string' ? parseInt(port) : port);
3478
3474
  return new Uint8Array(buf);
3479
3475
  }
3480
- function bytes2port(buf) {
3481
- const view = new DataView(buf.buffer);
3482
- return view.getUint16(buf.byteOffset);
3483
- }
3484
- function str2bytes(str) {
3485
- const buf = fromString(str);
3486
- const size = Uint8Array.from(encode$2(buf.length));
3487
- return concat([size, buf], size.length + buf.length);
3488
- }
3489
- function bytes2str(buf) {
3490
- const size = decode$4(buf);
3491
- buf = buf.slice(encodingLength$1(size));
3492
- if (buf.length !== size) {
3493
- throw new Error('inconsistent lengths');
3494
- }
3495
- return toString$1(buf);
3496
- }
3497
- function mh2bytes(hash) {
3498
- let mh;
3499
- if (hash[0] === 'Q' || hash[0] === '1') {
3500
- mh = decode$1(base58btc.decode(`z${hash}`)).bytes;
3501
- }
3502
- else {
3503
- mh = CID.parse(hash).multihash.bytes;
3504
- }
3505
- // the address is a varint prefixed multihash string representation
3506
- const size = Uint8Array.from(encode$2(mh.length));
3507
- return concat([size, mh], size.length + mh.length);
3508
- }
3509
- function mb2bytes(mbstr) {
3510
- const mb = anybaseDecoder.decode(mbstr);
3511
- const size = Uint8Array.from(encode$2(mb.length));
3512
- return concat([size, mb], size.length + mb.length);
3513
- }
3514
- function bytes2mb(buf) {
3515
- const size = decode$4(buf);
3516
- const hash = buf.slice(encodingLength$1(size));
3517
- if (hash.length !== size) {
3518
- throw new Error('inconsistent lengths');
3519
- }
3520
- return 'u' + toString$1(hash, 'base64url');
3521
- }
3522
- /**
3523
- * Converts bytes to bas58btc string
3524
- */
3525
- function bytes2mh(buf) {
3526
- const size = decode$4(buf);
3527
- const address = buf.slice(encodingLength$1(size));
3528
- if (address.length !== size) {
3529
- throw new Error('inconsistent lengths');
3530
- }
3531
- return toString$1(address, 'base58btc');
3532
- }
3533
3476
  function onion2bytes(str) {
3534
3477
  const addr = str.split(':');
3535
3478
  if (addr.length !== 2) {
@@ -3539,7 +3482,7 @@ function onion2bytes(str) {
3539
3482
  throw new Error(`failed to parse onion addr: ${addr[0]} not a Tor onion address.`);
3540
3483
  }
3541
3484
  // onion addresses do not include the multibase prefix, add it before decoding
3542
- const buf = base32.decode('b' + addr[0]);
3485
+ const buf = fromString(addr[0], 'base32');
3543
3486
  // onion port number
3544
3487
  const port = parseInt(addr[1], 10);
3545
3488
  if (port < 1 || port > 65536) {
@@ -3567,167 +3510,558 @@ function onion32bytes(str) {
3567
3510
  return concat([buf, portBuf], buf.length + portBuf.length);
3568
3511
  }
3569
3512
  function bytes2onion(buf) {
3570
- const addrBytes = buf.slice(0, buf.length - 2);
3571
- const portBytes = buf.slice(buf.length - 2);
3572
- const addr = toString$1(addrBytes, 'base32');
3513
+ const addrBytes = buf.subarray(0, buf.length - 2);
3514
+ const portBytes = buf.subarray(buf.length - 2);
3515
+ const addr = toString(addrBytes, 'base32');
3573
3516
  const port = bytes2port(portBytes);
3574
3517
  return `${addr}:${port}`;
3575
3518
  }
3519
+ // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
3520
+ // but with buf/offset args removed because we don't use them
3521
+ const ip4ToBytes = function (ip) {
3522
+ ip = ip.toString().trim();
3523
+ const bytes = new Uint8Array(4);
3524
+ ip.split(/\./g).forEach((byte, index) => {
3525
+ const value = parseInt(byte, 10);
3526
+ if (isNaN(value) || value < 0 || value > 0xff) {
3527
+ throw new InvalidMultiaddrError('Invalid byte value in IP address');
3528
+ }
3529
+ bytes[index] = value;
3530
+ });
3531
+ return bytes;
3532
+ };
3533
+ // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
3534
+ // but with buf/offset args removed because we don't use them
3535
+ const ip6ToBytes = function (ip) {
3536
+ let offset = 0;
3537
+ ip = ip.toString().trim();
3538
+ const sections = ip.split(':', 8);
3539
+ let i;
3540
+ for (i = 0; i < sections.length; i++) {
3541
+ const isv4 = isIPv4(sections[i]);
3542
+ let v4Buffer;
3543
+ if (isv4) {
3544
+ v4Buffer = ip4ToBytes(sections[i]);
3545
+ sections[i] = toString(v4Buffer.subarray(0, 2), 'base16');
3546
+ }
3547
+ if (v4Buffer != null && ++i < 8) {
3548
+ sections.splice(i, 0, toString(v4Buffer.subarray(2, 4), 'base16'));
3549
+ }
3550
+ }
3551
+ if (sections[0] === '') {
3552
+ while (sections.length < 8) {
3553
+ sections.unshift('0');
3554
+ }
3555
+ }
3556
+ else if (sections[sections.length - 1] === '') {
3557
+ while (sections.length < 8) {
3558
+ sections.push('0');
3559
+ }
3560
+ }
3561
+ else if (sections.length < 8) {
3562
+ for (i = 0; i < sections.length && sections[i] !== ''; i++) { }
3563
+ const argv = [i, 1];
3564
+ for (i = 9 - sections.length; i > 0; i--) {
3565
+ argv.push('0');
3566
+ }
3567
+ sections.splice.apply(sections, argv);
3568
+ }
3569
+ const bytes = new Uint8Array(offset + 16);
3570
+ for (i = 0; i < sections.length; i++) {
3571
+ if (sections[i] === '') {
3572
+ sections[i] = '0';
3573
+ }
3574
+ const word = parseInt(sections[i], 16);
3575
+ if (isNaN(word) || word < 0 || word > 0xffff) {
3576
+ throw new InvalidMultiaddrError('Invalid byte value in IP address');
3577
+ }
3578
+ bytes[offset++] = (word >> 8) & 0xff;
3579
+ bytes[offset++] = word & 0xff;
3580
+ }
3581
+ return bytes;
3582
+ };
3583
+ // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L63
3584
+ const ip4ToString = function (buf) {
3585
+ if (buf.byteLength !== 4) {
3586
+ throw new InvalidMultiaddrError('IPv4 address was incorrect length');
3587
+ }
3588
+ const result = [];
3589
+ for (let i = 0; i < buf.byteLength; i++) {
3590
+ result.push(buf[i]);
3591
+ }
3592
+ return result.join('.');
3593
+ };
3594
+ const ip6ToString = function (buf) {
3595
+ if (buf.byteLength !== 16) {
3596
+ throw new InvalidMultiaddrError('IPv6 address was incorrect length');
3597
+ }
3598
+ const result = [];
3599
+ for (let i = 0; i < buf.byteLength; i += 2) {
3600
+ const byte1 = buf[i];
3601
+ const byte2 = buf[i + 1];
3602
+ const tuple = `${byte1.toString(16).padStart(2, '0')}${byte2.toString(16).padStart(2, '0')}`;
3603
+ result.push(tuple);
3604
+ }
3605
+ const ip = result.join(':');
3606
+ try {
3607
+ const url = new URL(`http://[${ip}]`);
3608
+ return url.hostname.substring(1, url.hostname.length - 1);
3609
+ }
3610
+ catch {
3611
+ throw new InvalidMultiaddrError(`Invalid IPv6 address "${ip}"`);
3612
+ }
3613
+ };
3614
+ function ip6StringToValue(str) {
3615
+ try {
3616
+ const url = new URL(`http://[${str}]`);
3617
+ return url.hostname.substring(1, url.hostname.length - 1);
3618
+ }
3619
+ catch {
3620
+ throw new InvalidMultiaddrError(`Invalid IPv6 address "${str}"`);
3621
+ }
3622
+ }
3623
+ const decoders = Object.values(bases).map((c) => c.decoder);
3624
+ const anybaseDecoder = (function () {
3625
+ let acc = decoders[0].or(decoders[1]);
3626
+ decoders.slice(2).forEach((d) => (acc = acc.or(d)));
3627
+ return acc;
3628
+ })();
3629
+ function mb2bytes(mbstr) {
3630
+ return anybaseDecoder.decode(mbstr);
3631
+ }
3632
+ function bytes2mb(base) {
3633
+ return (buf) => {
3634
+ return base.encoder.encode(buf);
3635
+ };
3636
+ }
3576
3637
 
3577
- function stringToMultiaddrParts(str) {
3578
- str = cleanPath(str);
3579
- const tuples = [];
3580
- const stringTuples = [];
3581
- let path = null;
3582
- const parts = str.split('/').slice(1);
3583
- if (parts.length === 1 && parts[0] === '') {
3584
- return {
3585
- bytes: new Uint8Array(),
3586
- string: '/',
3587
- tuples: [],
3588
- stringTuples: [],
3589
- path: null
3590
- };
3638
+ function integer(value) {
3639
+ const int = parseInt(value);
3640
+ if (int.toString() !== value) {
3641
+ throw new ValidationError('Value must be an integer');
3591
3642
  }
3592
- for (let p = 0; p < parts.length; p++) {
3593
- const part = parts[p];
3594
- const proto = getProtocol(part);
3595
- if (proto.size === 0) {
3596
- tuples.push([proto.code]);
3597
- stringTuples.push([proto.code]);
3598
- // eslint-disable-next-line no-continue
3599
- continue;
3600
- }
3601
- p++; // advance addr part
3602
- if (p >= parts.length) {
3603
- throw new ParseError('invalid address: ' + str);
3604
- }
3605
- // if it's a path proto, take the rest
3606
- if (proto.path === true) {
3607
- // should we need to check each path part to see if it's a proto?
3608
- // This would allow for other protocols to be added after a unix path,
3609
- // however it would have issues if the path had a protocol name in the path
3610
- path = cleanPath(parts.slice(p).join('/'));
3611
- tuples.push([proto.code, convertToBytes(proto.code, path)]);
3612
- stringTuples.push([proto.code, path]);
3613
- break;
3614
- }
3615
- const bytes = convertToBytes(proto.code, parts[p]);
3616
- tuples.push([proto.code, bytes]);
3617
- stringTuples.push([proto.code, convertToString(proto.code, bytes)]);
3618
- }
3619
- return {
3620
- string: stringTuplesToString(stringTuples),
3621
- bytes: tuplesToBytes(tuples),
3622
- tuples,
3623
- stringTuples,
3624
- path
3643
+ }
3644
+ function positive(value) {
3645
+ if (value < 0) {
3646
+ throw new ValidationError('Value must be a positive integer, or zero');
3647
+ }
3648
+ }
3649
+ function maxValue(max) {
3650
+ return (value) => {
3651
+ if (value > max) {
3652
+ throw new ValidationError(`Value must be smaller than or equal to ${max}`);
3653
+ }
3654
+ };
3655
+ }
3656
+ function validate$1(...funcs) {
3657
+ return (value) => {
3658
+ for (const fn of funcs) {
3659
+ fn(value);
3660
+ }
3625
3661
  };
3626
3662
  }
3627
- function bytesToMultiaddrParts(bytes) {
3628
- const tuples = [];
3629
- const stringTuples = [];
3630
- let path = null;
3663
+ const validatePort = validate$1(integer, positive, maxValue(65_535));
3664
+
3665
+ const V = -1;
3666
+ class Registry {
3667
+ protocolsByCode = new Map();
3668
+ protocolsByName = new Map();
3669
+ getProtocol(key) {
3670
+ let codec;
3671
+ if (typeof key === 'string') {
3672
+ codec = this.protocolsByName.get(key);
3673
+ }
3674
+ else {
3675
+ codec = this.protocolsByCode.get(key);
3676
+ }
3677
+ if (codec == null) {
3678
+ throw new UnknownProtocolError(`Protocol ${key} was unknown`);
3679
+ }
3680
+ return codec;
3681
+ }
3682
+ addProtocol(codec) {
3683
+ this.protocolsByCode.set(codec.code, codec);
3684
+ this.protocolsByName.set(codec.name, codec);
3685
+ codec.aliases?.forEach(alias => {
3686
+ this.protocolsByName.set(alias, codec);
3687
+ });
3688
+ }
3689
+ removeProtocol(code) {
3690
+ const codec = this.protocolsByCode.get(code);
3691
+ if (codec == null) {
3692
+ return;
3693
+ }
3694
+ this.protocolsByCode.delete(codec.code);
3695
+ this.protocolsByName.delete(codec.name);
3696
+ codec.aliases?.forEach(alias => {
3697
+ this.protocolsByName.delete(alias);
3698
+ });
3699
+ }
3700
+ }
3701
+ const registry = new Registry();
3702
+ const codecs = [{
3703
+ code: CODE_IP4,
3704
+ name: 'ip4',
3705
+ size: 32,
3706
+ valueToBytes: ip4ToBytes,
3707
+ bytesToValue: ip4ToString,
3708
+ validate: (value) => {
3709
+ if (!isIPv4(value)) {
3710
+ throw new ValidationError(`Invalid IPv4 address "${value}"`);
3711
+ }
3712
+ }
3713
+ }, {
3714
+ code: CODE_TCP,
3715
+ name: 'tcp',
3716
+ size: 16,
3717
+ valueToBytes: port2bytes,
3718
+ bytesToValue: bytes2port,
3719
+ validate: validatePort
3720
+ }, {
3721
+ code: CODE_UDP,
3722
+ name: 'udp',
3723
+ size: 16,
3724
+ valueToBytes: port2bytes,
3725
+ bytesToValue: bytes2port,
3726
+ validate: validatePort
3727
+ }, {
3728
+ code: CODE_DCCP,
3729
+ name: 'dccp',
3730
+ size: 16,
3731
+ valueToBytes: port2bytes,
3732
+ bytesToValue: bytes2port,
3733
+ validate: validatePort
3734
+ }, {
3735
+ code: CODE_IP6,
3736
+ name: 'ip6',
3737
+ size: 128,
3738
+ valueToBytes: ip6ToBytes,
3739
+ bytesToValue: ip6ToString,
3740
+ stringToValue: ip6StringToValue,
3741
+ validate: (value) => {
3742
+ if (!isIPv6(value)) {
3743
+ throw new ValidationError(`Invalid IPv6 address "${value}"`);
3744
+ }
3745
+ }
3746
+ }, {
3747
+ code: CODE_IP6ZONE,
3748
+ name: 'ip6zone',
3749
+ size: V
3750
+ }, {
3751
+ code: CODE_IPCIDR,
3752
+ name: 'ipcidr',
3753
+ size: 8,
3754
+ bytesToValue: bytesToString('base10'),
3755
+ valueToBytes: stringToBytes('base10')
3756
+ }, {
3757
+ code: CODE_DNS,
3758
+ name: 'dns',
3759
+ size: V,
3760
+ resolvable: true
3761
+ }, {
3762
+ code: CODE_DNS4,
3763
+ name: 'dns4',
3764
+ size: V,
3765
+ resolvable: true
3766
+ }, {
3767
+ code: CODE_DNS6,
3768
+ name: 'dns6',
3769
+ size: V,
3770
+ resolvable: true
3771
+ }, {
3772
+ code: CODE_DNSADDR,
3773
+ name: 'dnsaddr',
3774
+ size: V,
3775
+ resolvable: true
3776
+ }, {
3777
+ code: CODE_SCTP,
3778
+ name: 'sctp',
3779
+ size: 16,
3780
+ valueToBytes: port2bytes,
3781
+ bytesToValue: bytes2port,
3782
+ validate: validatePort
3783
+ }, {
3784
+ code: CODE_UDT,
3785
+ name: 'udt'
3786
+ }, {
3787
+ code: CODE_UTP,
3788
+ name: 'utp'
3789
+ }, {
3790
+ code: CODE_UNIX,
3791
+ name: 'unix',
3792
+ size: V,
3793
+ path: true,
3794
+ stringToValue: (str) => decodeURIComponent(str),
3795
+ valueToString: (val) => encodeURIComponent(val)
3796
+ }, {
3797
+ code: CODE_P2P,
3798
+ name: 'p2p',
3799
+ aliases: ['ipfs'],
3800
+ size: V,
3801
+ bytesToValue: bytesToString('base58btc'),
3802
+ valueToBytes: (val) => {
3803
+ if (val.startsWith('Q') || val.startsWith('1')) {
3804
+ return stringToBytes('base58btc')(val);
3805
+ }
3806
+ return CID.parse(val).multihash.bytes;
3807
+ }
3808
+ }, {
3809
+ code: CODE_ONION,
3810
+ name: 'onion',
3811
+ size: 96,
3812
+ bytesToValue: bytes2onion,
3813
+ valueToBytes: onion2bytes
3814
+ }, {
3815
+ code: CODE_ONION3,
3816
+ name: 'onion3',
3817
+ size: 296,
3818
+ bytesToValue: bytes2onion,
3819
+ valueToBytes: onion32bytes
3820
+ }, {
3821
+ code: CODE_GARLIC64,
3822
+ name: 'garlic64',
3823
+ size: V
3824
+ }, {
3825
+ code: CODE_GARLIC32,
3826
+ name: 'garlic32',
3827
+ size: V
3828
+ }, {
3829
+ code: CODE_TLS,
3830
+ name: 'tls'
3831
+ }, {
3832
+ code: CODE_SNI,
3833
+ name: 'sni',
3834
+ size: V
3835
+ }, {
3836
+ code: CODE_NOISE,
3837
+ name: 'noise'
3838
+ }, {
3839
+ code: CODE_QUIC,
3840
+ name: 'quic'
3841
+ }, {
3842
+ code: CODE_QUIC_V1,
3843
+ name: 'quic-v1'
3844
+ }, {
3845
+ code: CODE_WEBTRANSPORT,
3846
+ name: 'webtransport'
3847
+ }, {
3848
+ code: CODE_CERTHASH,
3849
+ name: 'certhash',
3850
+ size: V,
3851
+ bytesToValue: bytes2mb(base64url),
3852
+ valueToBytes: mb2bytes
3853
+ }, {
3854
+ code: CODE_HTTP,
3855
+ name: 'http'
3856
+ }, {
3857
+ code: CODE_HTTP_PATH,
3858
+ name: 'http-path',
3859
+ size: V,
3860
+ stringToValue: (str) => `/${decodeURIComponent(str)}`,
3861
+ valueToString: (val) => encodeURIComponent(val.substring(1))
3862
+ }, {
3863
+ code: CODE_HTTPS,
3864
+ name: 'https'
3865
+ }, {
3866
+ code: CODE_WS,
3867
+ name: 'ws'
3868
+ }, {
3869
+ code: CODE_WSS,
3870
+ name: 'wss'
3871
+ }, {
3872
+ code: CODE_P2P_WEBSOCKET_STAR,
3873
+ name: 'p2p-websocket-star'
3874
+ }, {
3875
+ code: CODE_P2P_STARDUST,
3876
+ name: 'p2p-stardust'
3877
+ }, {
3878
+ code: CODE_P2P_WEBRTC_STAR,
3879
+ name: 'p2p-webrtc-star'
3880
+ }, {
3881
+ code: CODE_P2P_WEBRTC_DIRECT,
3882
+ name: 'p2p-webrtc-direct'
3883
+ }, {
3884
+ code: CODE_WEBRTC_DIRECT,
3885
+ name: 'webrtc-direct'
3886
+ }, {
3887
+ code: CODE_WEBRTC,
3888
+ name: 'webrtc'
3889
+ }, {
3890
+ code: CODE_P2P_CIRCUIT,
3891
+ name: 'p2p-circuit'
3892
+ }, {
3893
+ code: CODE_MEMORY,
3894
+ name: 'memory',
3895
+ size: V
3896
+ }];
3897
+ codecs.forEach(codec => {
3898
+ registry.addProtocol(codec);
3899
+ });
3900
+
3901
+ function bytesToComponents(bytes) {
3902
+ const components = [];
3631
3903
  let i = 0;
3632
3904
  while (i < bytes.length) {
3633
3905
  const code = decode$4(bytes, i);
3634
- const n = encodingLength$1(code);
3635
- const p = getProtocol(code);
3636
- const size = sizeForAddr(p, bytes.slice(i + n));
3637
- if (size === 0) {
3638
- tuples.push([code]);
3639
- stringTuples.push([code]);
3640
- i += n;
3641
- // eslint-disable-next-line no-continue
3642
- continue;
3643
- }
3644
- const addr = bytes.slice(i + n, i + n + size);
3645
- i += (size + n);
3646
- if (i > bytes.length) { // did not end _exactly_ at buffer.length
3647
- throw new ParseError('Invalid address Uint8Array: ' + toString$1(bytes, 'base16'));
3648
- }
3649
- // ok, tuple seems good.
3650
- tuples.push([code, addr]);
3651
- const stringAddr = convertToString(code, addr);
3652
- stringTuples.push([code, stringAddr]);
3653
- if (p.path === true) {
3654
- // should we need to check each path part to see if it's a proto?
3655
- // This would allow for other protocols to be added after a unix path,
3656
- // however it would have issues if the path had a protocol name in the path
3657
- path = stringAddr;
3658
- break;
3659
- }
3660
- }
3661
- return {
3662
- bytes: Uint8Array.from(bytes),
3663
- string: stringTuplesToString(stringTuples),
3664
- tuples,
3665
- stringTuples,
3666
- path
3667
- };
3906
+ const codec = registry.getProtocol(code);
3907
+ const codeLength = encodingLength$1(code);
3908
+ const size = sizeForAddr(codec, bytes, i + codeLength);
3909
+ let sizeLength = 0;
3910
+ if (size > 0 && codec.size === V) {
3911
+ sizeLength = encodingLength$1(size);
3912
+ }
3913
+ const componentLength = codeLength + sizeLength + size;
3914
+ const component = {
3915
+ code,
3916
+ name: codec.name,
3917
+ bytes: bytes.subarray(i, i + componentLength)
3918
+ };
3919
+ if (size > 0) {
3920
+ const valueOffset = i + codeLength + sizeLength;
3921
+ const valueBytes = bytes.subarray(valueOffset, valueOffset + size);
3922
+ component.value = codec.bytesToValue?.(valueBytes) ?? toString(valueBytes);
3923
+ }
3924
+ components.push(component);
3925
+ i += componentLength;
3926
+ }
3927
+ return components;
3668
3928
  }
3669
- /**
3670
- * [[num code, str value?]... ] -> string
3671
- */
3672
- function stringTuplesToString(tuples) {
3673
- const parts = [];
3674
- tuples.map((tup) => {
3675
- const proto = getProtocol(tup[0]);
3676
- parts.push(proto.name);
3677
- if (tup.length > 1 && tup[1] != null) {
3678
- parts.push(tup[1]);
3929
+ function componentsToBytes(components) {
3930
+ let length = 0;
3931
+ const bytes = [];
3932
+ for (const component of components) {
3933
+ if (component.bytes == null) {
3934
+ const codec = registry.getProtocol(component.code);
3935
+ const codecLength = encodingLength$1(component.code);
3936
+ let valueBytes;
3937
+ let valueLength = 0;
3938
+ let valueLengthLength = 0;
3939
+ if (component.value != null) {
3940
+ valueBytes = codec.valueToBytes?.(component.value) ?? fromString(component.value);
3941
+ valueLength = valueBytes.byteLength;
3942
+ if (codec.size === V) {
3943
+ valueLengthLength = encodingLength$1(valueLength);
3944
+ }
3945
+ }
3946
+ const bytes = new Uint8Array(codecLength + valueLengthLength + valueLength);
3947
+ // encode the protocol code
3948
+ let offset = 0;
3949
+ encodeUint8Array(component.code, bytes, offset);
3950
+ offset += codecLength;
3951
+ // if there is a value
3952
+ if (valueBytes != null) {
3953
+ // if the value has variable length, encode the length
3954
+ if (codec.size === V) {
3955
+ encodeUint8Array(valueLength, bytes, offset);
3956
+ offset += valueLengthLength;
3957
+ }
3958
+ // finally encode the value
3959
+ bytes.set(valueBytes, offset);
3960
+ }
3961
+ component.bytes = bytes;
3679
3962
  }
3680
- return null;
3681
- });
3682
- return cleanPath(parts.join('/'));
3963
+ bytes.push(component.bytes);
3964
+ length += component.bytes.byteLength;
3965
+ }
3966
+ return concat(bytes, length);
3683
3967
  }
3684
- /**
3685
- * [[int code, Uint8Array ]... ] -> Uint8Array
3686
- */
3687
- function tuplesToBytes(tuples) {
3688
- return concat(tuples.map((tup) => {
3689
- const proto = getProtocol(tup[0]);
3690
- let buf = Uint8Array.from(encode$2(proto.code));
3691
- if (tup.length > 1 && tup[1] != null) {
3692
- buf = concat([buf, tup[1]]); // add address buffer
3693
- }
3694
- return buf;
3695
- }));
3968
+ function stringToComponents(string) {
3969
+ if (string.charAt(0) !== '/') {
3970
+ throw new InvalidMultiaddrError('String multiaddr must start with "/"');
3971
+ }
3972
+ const components = [];
3973
+ let collecting = 'protocol';
3974
+ let value = '';
3975
+ let protocol = '';
3976
+ for (let i = 1; i < string.length; i++) {
3977
+ const char = string.charAt(i);
3978
+ if (char !== '/') {
3979
+ if (collecting === 'protocol') {
3980
+ protocol += string.charAt(i);
3981
+ }
3982
+ else {
3983
+ value += string.charAt(i);
3984
+ }
3985
+ }
3986
+ const ended = i === string.length - 1;
3987
+ if (char === '/' || ended) {
3988
+ const codec = registry.getProtocol(protocol);
3989
+ if (collecting === 'protocol') {
3990
+ if (codec.size == null || codec.size === 0) {
3991
+ // a protocol without an address, eg. `/tls`
3992
+ components.push({
3993
+ code: codec.code,
3994
+ name: codec.name
3995
+ });
3996
+ value = '';
3997
+ protocol = '';
3998
+ collecting = 'protocol';
3999
+ continue;
4000
+ }
4001
+ else if (ended) {
4002
+ throw new InvalidMultiaddrError(`Component ${protocol} was missing value`);
4003
+ }
4004
+ // continue collecting value
4005
+ collecting = 'value';
4006
+ }
4007
+ else if (collecting === 'value') {
4008
+ const component = {
4009
+ code: codec.code,
4010
+ name: codec.name
4011
+ };
4012
+ if (codec.size != null && codec.size !== 0) {
4013
+ if (value === '') {
4014
+ throw new InvalidMultiaddrError(`Component ${protocol} was missing value`);
4015
+ }
4016
+ component.value = codec.stringToValue?.(value) ?? value;
4017
+ }
4018
+ components.push(component);
4019
+ value = '';
4020
+ protocol = '';
4021
+ collecting = 'protocol';
4022
+ }
4023
+ }
4024
+ }
4025
+ if (protocol !== '' && value !== '') {
4026
+ throw new InvalidMultiaddrError('Incomplete multiaddr');
4027
+ }
4028
+ return components;
4029
+ }
4030
+ function componentsToString(components) {
4031
+ return `/${components.flatMap(component => {
4032
+ if (component.value == null) {
4033
+ return component.name;
4034
+ }
4035
+ const codec = registry.getProtocol(component.code);
4036
+ if (codec == null) {
4037
+ throw new InvalidMultiaddrError(`Unknown protocol code ${component.code}`);
4038
+ }
4039
+ return [
4040
+ component.name,
4041
+ codec.valueToString?.(component.value) ?? component.value
4042
+ ];
4043
+ }).join('/')}`;
3696
4044
  }
3697
4045
  /**
3698
4046
  * For the passed address, return the serialized size
3699
4047
  */
3700
- function sizeForAddr(p, addr) {
3701
- if (p.size > 0) {
3702
- return p.size / 8;
3703
- }
3704
- else if (p.size === 0) {
4048
+ function sizeForAddr(codec, bytes, offset) {
4049
+ if (codec.size == null || codec.size === 0) {
3705
4050
  return 0;
3706
4051
  }
3707
- else {
3708
- const size = decode$4(addr instanceof Uint8Array ? addr : Uint8Array.from(addr));
3709
- return size + encodingLength$1(size);
3710
- }
3711
- }
3712
- function cleanPath(str) {
3713
- return '/' + str.trim().split('/').filter((a) => a).join('/');
3714
- }
3715
- class ParseError extends Error {
3716
- static name = 'ParseError';
3717
- name = 'ParseError';
3718
- constructor(str) {
3719
- super(`Error parsing address: ${str}`);
4052
+ if (codec.size > 0) {
4053
+ return codec.size / 8;
3720
4054
  }
4055
+ return decode$4(bytes, offset);
3721
4056
  }
3722
4057
 
3723
- /* eslint-disable complexity */
3724
4058
  const inspect = Symbol.for('nodejs.util.inspect.custom');
3725
- const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr');
4059
+ const symbol = Symbol.for('@multiformats/multiaddr');
3726
4060
  const DNS_CODES = [
3727
- getProtocol('dns').code,
3728
- getProtocol('dns4').code,
3729
- getProtocol('dns6').code,
3730
- getProtocol('dnsaddr').code
4061
+ CODE_DNS,
4062
+ CODE_DNS4,
4063
+ CODE_DNS6,
4064
+ CODE_DNSADDR
3731
4065
  ];
3732
4066
  class NoAvailableResolverError extends Error {
3733
4067
  constructor(message = 'No available resolver') {
@@ -3735,44 +4069,56 @@ class NoAvailableResolverError extends Error {
3735
4069
  this.name = 'NoAvailableResolverError';
3736
4070
  }
3737
4071
  }
4072
+ function toComponents(addr) {
4073
+ if (addr == null) {
4074
+ addr = '/';
4075
+ }
4076
+ if (isMultiaddr(addr)) {
4077
+ return addr.getComponents();
4078
+ }
4079
+ if (addr instanceof Uint8Array) {
4080
+ return bytesToComponents(addr);
4081
+ }
4082
+ if (typeof addr === 'string') {
4083
+ addr = addr
4084
+ .replace(/\/(\/)+/, '/')
4085
+ .replace(/(\/)+$/, '');
4086
+ if (addr === '') {
4087
+ addr = '/';
4088
+ }
4089
+ return stringToComponents(addr);
4090
+ }
4091
+ if (Array.isArray(addr)) {
4092
+ return addr;
4093
+ }
4094
+ throw new InvalidMultiaddrError('Must be a string, Uint8Array, Component[], or another Multiaddr');
4095
+ }
3738
4096
  /**
3739
4097
  * Creates a {@link Multiaddr} from a {@link MultiaddrInput}
3740
4098
  */
3741
4099
  class Multiaddr {
3742
- bytes;
3743
- #string;
3744
- #tuples;
3745
- #stringTuples;
3746
- #path;
3747
4100
  [symbol] = true;
3748
- constructor(addr) {
3749
- // default
3750
- if (addr == null) {
3751
- addr = '';
3752
- }
3753
- let parts;
3754
- if (addr instanceof Uint8Array) {
3755
- parts = bytesToMultiaddrParts(addr);
3756
- }
3757
- else if (typeof addr === 'string') {
3758
- if (addr.length > 0 && addr.charAt(0) !== '/') {
3759
- throw new Error(`multiaddr "${addr}" must start with a "/"`);
3760
- }
3761
- parts = stringToMultiaddrParts(addr);
3762
- }
3763
- else if (isMultiaddr(addr)) { // Multiaddr
3764
- parts = bytesToMultiaddrParts(addr.bytes);
4101
+ #components;
4102
+ // cache string representation
4103
+ #string;
4104
+ // cache byte representation
4105
+ #bytes;
4106
+ constructor(addr = '/', options = {}) {
4107
+ this.#components = toComponents(addr);
4108
+ if (options.validate !== false) {
4109
+ validate(this);
3765
4110
  }
3766
- else {
3767
- throw new Error('addr must be a string, Buffer, or another Multiaddr');
4111
+ }
4112
+ get bytes() {
4113
+ if (this.#bytes == null) {
4114
+ this.#bytes = componentsToBytes(this.#components);
3768
4115
  }
3769
- this.bytes = parts.bytes;
3770
- this.#string = parts.string;
3771
- this.#tuples = parts.tuples;
3772
- this.#stringTuples = parts.stringTuples;
3773
- this.#path = parts.path;
4116
+ return this.#bytes;
3774
4117
  }
3775
4118
  toString() {
4119
+ if (this.#string == null) {
4120
+ this.#string = componentsToString(this.#components);
4121
+ }
3776
4122
  return this.#string;
3777
4123
  }
3778
4124
  toJSON() {
@@ -3784,31 +4130,25 @@ class Multiaddr {
3784
4130
  let host;
3785
4131
  let port;
3786
4132
  let zone = '';
3787
- const tcp = getProtocol('tcp');
3788
- const udp = getProtocol('udp');
3789
- const ip4 = getProtocol('ip4');
3790
- const ip6 = getProtocol('ip6');
3791
- const dns6 = getProtocol('dns6');
3792
- const ip6zone = getProtocol('ip6zone');
3793
- for (const [code, value] of this.stringTuples()) {
3794
- if (code === ip6zone.code) {
4133
+ for (const { code, name, value } of this.#components) {
4134
+ if (code === CODE_IP6ZONE) {
3795
4135
  zone = `%${value ?? ''}`;
3796
4136
  }
3797
4137
  // default to https when protocol & port are omitted from DNS addrs
3798
4138
  if (DNS_CODES.includes(code)) {
3799
- transport = tcp.name === 'tcp' ? 'tcp' : 'udp';
4139
+ transport = 'tcp';
3800
4140
  port = 443;
3801
4141
  host = `${value ?? ''}${zone}`;
3802
- family = code === dns6.code ? 6 : 4;
4142
+ family = code === CODE_DNS6 ? 6 : 4;
3803
4143
  }
3804
- if (code === tcp.code || code === udp.code) {
3805
- transport = getProtocol(code).name === 'tcp' ? 'tcp' : 'udp';
4144
+ if (code === CODE_TCP || code === CODE_UDP) {
4145
+ transport = name === 'tcp' ? 'tcp' : 'udp';
3806
4146
  port = parseInt(value ?? '');
3807
4147
  }
3808
- if (code === ip4.code || code === ip6.code) {
3809
- transport = getProtocol(code).name === 'tcp' ? 'tcp' : 'udp';
4148
+ if (code === CODE_IP4 || code === CODE_IP6) {
4149
+ transport = 'tcp';
3810
4150
  host = `${value ?? ''}${zone}`;
3811
- family = code === ip6.code ? 6 : 4;
4151
+ family = code === CODE_IP6 ? 6 : 4;
3812
4152
  }
3813
4153
  }
3814
4154
  if (family == null || transport == null || host == null || port == null) {
@@ -3822,25 +4162,44 @@ class Multiaddr {
3822
4162
  };
3823
4163
  return opts;
3824
4164
  }
4165
+ getComponents() {
4166
+ return [
4167
+ ...this.#components
4168
+ ];
4169
+ }
3825
4170
  protos() {
3826
- return this.#tuples.map(([code]) => Object.assign({}, getProtocol(code)));
4171
+ return this.#components.map(({ code, value }) => {
4172
+ const codec = registry.getProtocol(code);
4173
+ return {
4174
+ code,
4175
+ size: codec.size ?? 0,
4176
+ name: codec.name,
4177
+ resolvable: Boolean(codec.resolvable),
4178
+ path: Boolean(codec.path)
4179
+ };
4180
+ });
3827
4181
  }
3828
4182
  protoCodes() {
3829
- return this.#tuples.map(([code]) => code);
4183
+ return this.#components.map(({ code }) => code);
3830
4184
  }
3831
4185
  protoNames() {
3832
- return this.#tuples.map(([code]) => getProtocol(code).name);
4186
+ return this.#components.map(({ name }) => name);
3833
4187
  }
3834
4188
  tuples() {
3835
- return this.#tuples.map(([code, value]) => {
4189
+ return this.#components.map(({ code, value }) => {
3836
4190
  if (value == null) {
3837
4191
  return [code];
3838
4192
  }
3839
- return [code, value];
4193
+ const codec = registry.getProtocol(code);
4194
+ const output = [code];
4195
+ if (value != null) {
4196
+ output.push(codec.valueToBytes?.(value) ?? fromString(value));
4197
+ }
4198
+ return output;
3840
4199
  });
3841
4200
  }
3842
4201
  stringTuples() {
3843
- return this.#stringTuples.map(([code, value]) => {
4202
+ return this.#components.map(({ code, value }) => {
3844
4203
  if (value == null) {
3845
4204
  return [code];
3846
4205
  }
@@ -3848,37 +4207,47 @@ class Multiaddr {
3848
4207
  });
3849
4208
  }
3850
4209
  encapsulate(addr) {
3851
- addr = new Multiaddr(addr);
3852
- return new Multiaddr(this.toString() + addr.toString());
4210
+ const ma = new Multiaddr(addr);
4211
+ return new Multiaddr([
4212
+ ...this.#components,
4213
+ ...ma.getComponents()
4214
+ ], {
4215
+ validate: false
4216
+ });
3853
4217
  }
3854
4218
  decapsulate(addr) {
3855
4219
  const addrString = addr.toString();
3856
4220
  const s = this.toString();
3857
4221
  const i = s.lastIndexOf(addrString);
3858
4222
  if (i < 0) {
3859
- throw new Error(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`);
4223
+ throw new InvalidParametersError(`Address ${this.toString()} does not contain subaddress: ${addr.toString()}`);
3860
4224
  }
3861
- return new Multiaddr(s.slice(0, i));
4225
+ return new Multiaddr(s.slice(0, i), {
4226
+ validate: false
4227
+ });
3862
4228
  }
3863
4229
  decapsulateCode(code) {
3864
- const tuples = this.tuples();
3865
- for (let i = tuples.length - 1; i >= 0; i--) {
3866
- if (tuples[i][0] === code) {
3867
- return new Multiaddr(tuplesToBytes(tuples.slice(0, i)));
4230
+ let index;
4231
+ for (let i = this.#components.length - 1; i > -1; i--) {
4232
+ if (this.#components[i].code === code) {
4233
+ index = i;
4234
+ break;
3868
4235
  }
3869
4236
  }
3870
- return this;
4237
+ return new Multiaddr(this.#components.slice(0, index), {
4238
+ validate: false
4239
+ });
3871
4240
  }
3872
4241
  getPeerId() {
3873
4242
  try {
3874
4243
  let tuples = [];
3875
- this.stringTuples().forEach(([code, name]) => {
3876
- if (code === names.p2p.code) {
3877
- tuples.push([code, name]);
4244
+ this.#components.forEach(({ code, value }) => {
4245
+ if (code === CODE_P2P) {
4246
+ tuples.push([code, value]);
3878
4247
  }
3879
4248
  // if this is a p2p-circuit address, return the target peer id if present
3880
4249
  // not the peer id of the relay
3881
- if (code === names['p2p-circuit'].code) {
4250
+ if (code === CODE_P2P_CIRCUIT) {
3882
4251
  tuples = [];
3883
4252
  }
3884
4253
  });
@@ -3889,10 +4258,10 @@ class Multiaddr {
3889
4258
  // peer id is base58btc encoded string but not multibase encoded so add the `z`
3890
4259
  // prefix so we can validate that it is correctly encoded
3891
4260
  if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
3892
- return toString$1(base58btc.decode(`z${peerIdStr}`), 'base58btc');
4261
+ return toString(base58btc.decode(`z${peerIdStr}`), 'base58btc');
3893
4262
  }
3894
4263
  // try to parse peer id as CID
3895
- return toString$1(CID.parse(peerIdStr).multihash.bytes, 'base58btc');
4264
+ return toString(CID.parse(peerIdStr).multihash.bytes, 'base58btc');
3896
4265
  }
3897
4266
  return null;
3898
4267
  }
@@ -3901,7 +4270,14 @@ class Multiaddr {
3901
4270
  }
3902
4271
  }
3903
4272
  getPath() {
3904
- return this.#path;
4273
+ for (const component of this.#components) {
4274
+ const codec = registry.getProtocol(component.code);
4275
+ if (!codec.path) {
4276
+ continue;
4277
+ }
4278
+ return component.value ?? null;
4279
+ }
4280
+ return null;
3905
4281
  }
3906
4282
  equals(addr) {
3907
4283
  return equals(this.bytes, addr.bytes);
@@ -3930,15 +4306,14 @@ class Multiaddr {
3930
4306
  port: options.port
3931
4307
  };
3932
4308
  }
3933
- isThinWaistAddress(addr) {
3934
- const protos = (addr ?? this).protos();
3935
- if (protos.length !== 2) {
4309
+ isThinWaistAddress() {
4310
+ if (this.#components.length !== 2) {
3936
4311
  return false;
3937
4312
  }
3938
- if (protos[0].code !== 4 && protos[0].code !== 41) {
4313
+ if (this.#components[0].code !== CODE_IP4 && this.#components[0].code !== CODE_IP6) {
3939
4314
  return false;
3940
4315
  }
3941
- if (protos[1].code !== 6 && protos[1].code !== 273) {
4316
+ if (this.#components[1].code !== CODE_TCP && this.#components[1].code !== CODE_UDP) {
3942
4317
  return false;
3943
4318
  }
3944
4319
  return true;
@@ -3956,9 +4331,23 @@ class Multiaddr {
3956
4331
  * ```
3957
4332
  */
3958
4333
  [inspect]() {
3959
- return `Multiaddr(${this.#string})`;
4334
+ return `Multiaddr(${this.toString()})`;
3960
4335
  }
3961
4336
  }
4337
+ /**
4338
+ * Ensures all multiaddr tuples are correct. Throws if any invalid protocols or
4339
+ * values are encountered.
4340
+ */
4341
+ function validate(addr) {
4342
+ addr.getComponents()
4343
+ .forEach(component => {
4344
+ const codec = registry.getProtocol(component.code);
4345
+ if (component.value == null) {
4346
+ return;
4347
+ }
4348
+ codec.validate?.(component.value);
4349
+ });
4350
+ }
3962
4351
 
3963
4352
  /**
3964
4353
  * @packageDocumentation
@@ -4053,9 +4442,42 @@ class Multiaddr {
4053
4442
  * console.info(resolved)
4054
4443
  * // [Multiaddr('/ip4/147.75...'), Multiaddr('/ip4/147.75...'), Multiaddr('/ip4/147.75...')...]
4055
4444
  * ```
4445
+ *
4446
+ * @example Adding custom protocols
4447
+ *
4448
+ * To add application-specific or experimental protocols, add a protocol codec
4449
+ * to the protocol registry:
4450
+ *
4451
+ * ```ts
4452
+ * import { registry, V, multiaddr } from '@multiformats/multiaddr'
4453
+ * import type { ProtocolCodec } from '@multiformats/multiaddr'
4454
+ *
4455
+ * const maWithCustomTuple = '/custom-protocol/hello'
4456
+ *
4457
+ * // throws UnknownProtocolError
4458
+ * multiaddr(maWithCustomTuple)
4459
+ *
4460
+ * const protocol: ProtocolCodec = {
4461
+ * code: 2059,
4462
+ * name: 'custom-protocol',
4463
+ * size: V
4464
+ * // V means variable length, can also be 0, a positive integer (e.g. a fixed
4465
+ * // length or omitted
4466
+ * }
4467
+ *
4468
+ * registry.addProtocol(protocol)
4469
+ *
4470
+ * // does not throw UnknownProtocolError
4471
+ * multiaddr(maWithCustomTuple)
4472
+ *
4473
+ * // protocols can also be removed
4474
+ * registry.removeProtocol(protocol.code)
4475
+ * ```
4056
4476
  */
4057
4477
  /**
4058
4478
  * All configured {@link Resolver}s
4479
+ *
4480
+ * @deprecated DNS resolving will be removed in a future release
4059
4481
  */
4060
4482
  const resolvers = new Map();
4061
4483
  /**
@@ -4765,13 +5187,15 @@ class ConnectionManager extends TypedEventEmitter {
4765
5187
 
4766
5188
  const log = new Logger("metadata");
4767
5189
  const MetadataCodec = "/vac/waku/metadata/1.0.0";
4768
- class Metadata extends BaseProtocol {
5190
+ class Metadata {
4769
5191
  pubsubTopics;
5192
+ streamManager;
4770
5193
  libp2pComponents;
4771
5194
  handshakesConfirmed = new Map();
5195
+ multicodec = MetadataCodec;
4772
5196
  constructor(pubsubTopics, libp2p) {
4773
- super(MetadataCodec, libp2p.components, pubsubTopics);
4774
5197
  this.pubsubTopics = pubsubTopics;
5198
+ this.streamManager = new StreamManager(MetadataCodec, libp2p);
4775
5199
  this.libp2pComponents = libp2p;
4776
5200
  void libp2p.registrar.handle(MetadataCodec, (streamData) => {
4777
5201
  void this.onRequest(streamData);
@@ -4791,7 +5215,7 @@ class Metadata extends BaseProtocol {
4791
5215
  }
4792
5216
  let stream;
4793
5217
  try {
4794
- stream = await this.getStream(peerId);
5218
+ stream = await this.streamManager.getStream(peerId);
4795
5219
  }
4796
5220
  catch (error) {
4797
5221
  log.error("Failed to get stream", error);
@@ -4874,4 +5298,85 @@ function wakuMetadata(pubsubTopics) {
4874
5298
  return (components) => new Metadata(pubsubTopics, components);
4875
5299
  }
4876
5300
 
4877
- 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 };
5301
+ /**
5302
+ * Deterministic Message Hashing as defined in
5303
+ * [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/#deterministic-message-hashing)
5304
+ *
5305
+ * Computes a SHA-256 hash of the concatenation of pubsub topic, payload, content topic, meta, and timestamp.
5306
+ *
5307
+ * @param pubsubTopic - The pubsub topic string
5308
+ * @param message - The message to be hashed
5309
+ * @returns A Uint8Array containing the SHA-256 hash
5310
+ *
5311
+ * @example
5312
+ * ```typescript
5313
+ * import { messageHash } from "@waku/core";
5314
+ *
5315
+ * const pubsubTopic = "/waku/2/default-waku/proto";
5316
+ * const message = {
5317
+ * payload: new Uint8Array([1, 2, 3, 4]),
5318
+ * contentTopic: "/waku/2/default-content/proto",
5319
+ * meta: new Uint8Array([5, 6, 7, 8]),
5320
+ * timestamp: new Date()
5321
+ * };
5322
+ *
5323
+ * const hash = messageHash(pubsubTopic, message);
5324
+ * ```
5325
+ */
5326
+ function messageHash(pubsubTopic, message) {
5327
+ const pubsubTopicBytes = utf8ToBytes(pubsubTopic);
5328
+ const contentTopicBytes = utf8ToBytes(message.contentTopic);
5329
+ const timestampBytes = tryConvertTimestampToBytes(message.timestamp);
5330
+ const bytes = concat$1([
5331
+ pubsubTopicBytes,
5332
+ message.payload,
5333
+ contentTopicBytes,
5334
+ message.meta,
5335
+ timestampBytes
5336
+ ].filter(isDefined));
5337
+ return sha256(bytes);
5338
+ }
5339
+ function tryConvertTimestampToBytes(timestamp) {
5340
+ if (!timestamp) {
5341
+ return;
5342
+ }
5343
+ let bigIntTimestamp;
5344
+ if (typeof timestamp === "bigint") {
5345
+ bigIntTimestamp = timestamp;
5346
+ }
5347
+ else {
5348
+ bigIntTimestamp = BigInt(timestamp.valueOf()) * 1000000n;
5349
+ }
5350
+ return numberToBytes(bigIntTimestamp);
5351
+ }
5352
+ /**
5353
+ * Computes a deterministic message hash and returns it as a hexadecimal string.
5354
+ * This is a convenience wrapper around messageHash that converts the result to a hex string.
5355
+ *
5356
+ * @param pubsubTopic - The pubsub topic string
5357
+ * @param message - The message to be hashed
5358
+ * @returns A string containing the hex representation of the SHA-256 hash
5359
+ *
5360
+ * @example
5361
+ * ```typescript
5362
+ * import { messageHashStr } from "@waku/core";
5363
+ *
5364
+ * const pubsubTopic = "/waku/2/default-waku/proto";
5365
+ * const message = {
5366
+ * payload: new Uint8Array([1, 2, 3, 4]),
5367
+ * contentTopic: "/waku/2/default-content/proto",
5368
+ * meta: new Uint8Array([5, 6, 7, 8]),
5369
+ * timestamp: new Date()
5370
+ * };
5371
+ *
5372
+ * const hashString = messageHashStr(pubsubTopic, message);
5373
+ * console.log(hashString); // e.g. "a1b2c3d4..."
5374
+ * ```
5375
+ */
5376
+ function messageHashStr(pubsubTopic, message) {
5377
+ const hash = messageHash(pubsubTopic, message);
5378
+ const hashStr = bytesToHex(hash);
5379
+ return hashStr;
5380
+ }
5381
+
5382
+ 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 };