@waku/core 0.0.36-f7c290d.0 → 0.0.36

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 (37) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/bundle/index.js +1008 -594
  3. package/bundle/lib/message/version_0.js +1 -2
  4. package/bundle/{version_0-CyeTW0Vr.js → version_0-9DPFjcJG.js} +1570 -6
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/lib/connection_manager/connection_manager.d.ts +2 -1
  7. package/dist/lib/connection_manager/connection_manager.js +16 -8
  8. package/dist/lib/connection_manager/connection_manager.js.map +1 -1
  9. package/dist/lib/filter/filter.d.ts +4 -3
  10. package/dist/lib/filter/filter.js +9 -7
  11. package/dist/lib/filter/filter.js.map +1 -1
  12. package/dist/lib/light_push/light_push.d.ts +4 -3
  13. package/dist/lib/light_push/light_push.js +6 -4
  14. package/dist/lib/light_push/light_push.js.map +1 -1
  15. package/dist/lib/message/version_0.d.ts +1 -1
  16. package/dist/lib/metadata/metadata.js +6 -4
  17. package/dist/lib/metadata/metadata.js.map +1 -1
  18. package/dist/lib/store/store.d.ts +4 -3
  19. package/dist/lib/store/store.js +6 -4
  20. package/dist/lib/store/store.js.map +1 -1
  21. package/dist/lib/stream_manager/stream_manager.d.ts +3 -4
  22. package/dist/lib/stream_manager/stream_manager.js +6 -8
  23. package/dist/lib/stream_manager/stream_manager.js.map +1 -1
  24. package/package.json +125 -1
  25. package/src/lib/connection_manager/connection_manager.ts +24 -16
  26. package/src/lib/filter/filter.ts +13 -8
  27. package/src/lib/light_push/light_push.ts +8 -5
  28. package/src/lib/metadata/metadata.ts +8 -5
  29. package/src/lib/store/store.ts +8 -5
  30. package/src/lib/stream_manager/stream_manager.ts +8 -6
  31. package/bundle/base_protocol-DvQrudwy.js +0 -152
  32. package/bundle/index-CTo1my9M.js +0 -1543
  33. package/bundle/lib/base_protocol.js +0 -2
  34. package/dist/lib/base_protocol.d.ts +0 -18
  35. package/dist/lib/base_protocol.js +0 -25
  36. package/dist/lib/base_protocol.js.map +0 -1
  37. 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, o as concat$1, q as sha256, r as bytesToHex, w as numberToBytes } from './version_0-CyeTW0Vr.js';
2
- export { x as createDecoder } from './version_0-CyeTW0Vr.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;
@@ -2012,25 +2009,38 @@ function queuelessPushable() {
2012
2009
  function isAsyncIterable$1(thing) {
2013
2010
  return thing[Symbol.asyncIterator] != null;
2014
2011
  }
2015
- async function addAllToPushable(sources, output) {
2012
+ async function addAllToPushable(sources, output, signal) {
2016
2013
  try {
2017
2014
  await Promise.all(sources.map(async (source) => {
2018
2015
  for await (const item of source) {
2019
- await output.push(item);
2016
+ await output.push(item, {
2017
+ signal
2018
+ });
2019
+ signal.throwIfAborted();
2020
2020
  }
2021
2021
  }));
2022
- await output.end();
2022
+ await output.end(undefined, {
2023
+ signal
2024
+ });
2023
2025
  }
2024
2026
  catch (err) {
2025
- await output.end(err)
2027
+ await output.end(err, {
2028
+ signal
2029
+ })
2026
2030
  .catch(() => { });
2027
2031
  }
2028
2032
  }
2029
2033
  async function* mergeSources(sources) {
2034
+ const controller = new AbortController();
2030
2035
  const output = queuelessPushable();
2031
- addAllToPushable(sources, output)
2036
+ addAllToPushable(sources, output, controller.signal)
2032
2037
  .catch(() => { });
2033
- yield* output;
2038
+ try {
2039
+ yield* output;
2040
+ }
2041
+ finally {
2042
+ controller.abort();
2043
+ }
2034
2044
  }
2035
2045
  function* mergeSyncSources(syncSources) {
2036
2046
  for (const source of syncSources) {
@@ -2136,6 +2146,129 @@ const duplexPipelineFn = (duplex) => {
2136
2146
  };
2137
2147
  };
2138
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
+
2139
2272
  // Unique ID creation requires a high quality random # generator. In the browser we therefore
2140
2273
  // require the crypto API and do not support built-in fallback to lower quality random number
2141
2274
  // generators (like Math.random()).
@@ -2303,12 +2436,14 @@ const FilterCodecs = {
2303
2436
  SUBSCRIBE: "/vac/waku/filter-subscribe/2.0.0-beta1",
2304
2437
  PUSH: "/vac/waku/filter-push/2.0.0-beta1"
2305
2438
  };
2306
- class FilterCore extends BaseProtocol {
2439
+ class FilterCore {
2307
2440
  pubsubTopics;
2441
+ streamManager;
2308
2442
  static handleIncomingMessage;
2443
+ multicodec = FilterCodecs.SUBSCRIBE;
2309
2444
  constructor(handleIncomingMessage, pubsubTopics, libp2p) {
2310
- super(FilterCodecs.SUBSCRIBE, libp2p.components, pubsubTopics);
2311
2445
  this.pubsubTopics = pubsubTopics;
2446
+ this.streamManager = new StreamManager(FilterCodecs.SUBSCRIBE, libp2p.components);
2312
2447
  // TODO(weboko): remove when @waku/sdk 0.0.33 is released
2313
2448
  const prevHandler = FilterCore.handleIncomingMessage;
2314
2449
  FilterCore.handleIncomingMessage = !prevHandler
@@ -2337,7 +2472,7 @@ class FilterCore extends BaseProtocol {
2337
2472
  });
2338
2473
  }
2339
2474
  async subscribe(pubsubTopic, peerId, contentTopics) {
2340
- const stream = await this.getStream(peerId);
2475
+ const stream = await this.streamManager.getStream(peerId);
2341
2476
  const request = FilterSubscribeRpc.createSubscribeRequest(pubsubTopic, contentTopics);
2342
2477
  let res;
2343
2478
  try {
@@ -2372,7 +2507,7 @@ class FilterCore extends BaseProtocol {
2372
2507
  async unsubscribe(pubsubTopic, peerId, contentTopics) {
2373
2508
  let stream;
2374
2509
  try {
2375
- stream = await this.getStream(peerId);
2510
+ stream = await this.streamManager.getStream(peerId);
2376
2511
  }
2377
2512
  catch (error) {
2378
2513
  log$5.error(`Failed to get a stream for remote peer${peerId.toString()}`, error);
@@ -2404,7 +2539,7 @@ class FilterCore extends BaseProtocol {
2404
2539
  };
2405
2540
  }
2406
2541
  async unsubscribeAll(pubsubTopic, peerId) {
2407
- const stream = await this.getStream(peerId);
2542
+ const stream = await this.streamManager.getStream(peerId);
2408
2543
  const request = FilterSubscribeRpc.createUnsubscribeAllRequest(pubsubTopic);
2409
2544
  const res = await pipe([request.encode()], encode, stream, decode, async (source) => await all(source));
2410
2545
  if (!res || !res.length) {
@@ -2435,7 +2570,7 @@ class FilterCore extends BaseProtocol {
2435
2570
  async ping(peerId) {
2436
2571
  let stream;
2437
2572
  try {
2438
- stream = await this.getStream(peerId);
2573
+ stream = await this.streamManager.getStream(peerId);
2439
2574
  }
2440
2575
  catch (error) {
2441
2576
  log$5.error(`Failed to get a stream for remote peer${peerId.toString()}`, error);
@@ -2577,11 +2712,13 @@ const LightPushCodec = "/vac/waku/lightpush/2.0.0-beta1";
2577
2712
  /**
2578
2713
  * Implements the [Waku v2 Light Push protocol](https://rfc.vac.dev/spec/19/).
2579
2714
  */
2580
- class LightPushCore extends BaseProtocol {
2715
+ class LightPushCore {
2581
2716
  pubsubTopics;
2717
+ streamManager;
2718
+ multicodec = LightPushCodec;
2582
2719
  constructor(pubsubTopics, libp2p) {
2583
- super(LightPushCodec, libp2p.components, pubsubTopics);
2584
2720
  this.pubsubTopics = pubsubTopics;
2721
+ this.streamManager = new StreamManager(LightPushCodec, libp2p.components);
2585
2722
  }
2586
2723
  async preparePushMessage(encoder, message) {
2587
2724
  try {
@@ -2625,7 +2762,7 @@ class LightPushCore extends BaseProtocol {
2625
2762
  }
2626
2763
  let stream;
2627
2764
  try {
2628
- stream = await this.getStream(peerId);
2765
+ stream = await this.streamManager.getStream(peerId);
2629
2766
  }
2630
2767
  catch (error) {
2631
2768
  log$4.error("Failed to get stream", error);
@@ -2804,11 +2941,13 @@ class StoreQueryResponse {
2804
2941
 
2805
2942
  const log$3 = new Logger("store");
2806
2943
  const StoreCodec = "/vac/waku/store-query/3.0.0";
2807
- class StoreCore extends BaseProtocol {
2944
+ class StoreCore {
2808
2945
  pubsubTopics;
2946
+ streamManager;
2947
+ multicodec = StoreCodec;
2809
2948
  constructor(pubsubTopics, libp2p) {
2810
- super(StoreCodec, libp2p.components, pubsubTopics);
2811
2949
  this.pubsubTopics = pubsubTopics;
2950
+ this.streamManager = new StreamManager(StoreCodec, libp2p.components);
2812
2951
  }
2813
2952
  async *queryPerPage(queryOpts, decoders, peerId) {
2814
2953
  // Only validate decoder content topics for content-filtered queries
@@ -2833,7 +2972,7 @@ class StoreCore extends BaseProtocol {
2833
2972
  });
2834
2973
  let stream;
2835
2974
  try {
2836
- stream = await this.getStream(peerId);
2975
+ stream = await this.streamManager.getStream(peerId);
2837
2976
  }
2838
2977
  catch (e) {
2839
2978
  log$3.error("Failed to get stream", e);
@@ -2905,9 +3044,71 @@ function isPeerId(other) {
2905
3044
  return Boolean(other?.[peerIdSymbol]);
2906
3045
  }
2907
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
+ */
2908
3110
  /**
2909
3111
  * An implementation of a typed event target
2910
- * etc
2911
3112
  */
2912
3113
  class TypedEventEmitter extends EventTarget {
2913
3114
  #listeners = new Map();
@@ -2957,6 +3158,26 @@ class TypedEventEmitter extends EventTarget {
2957
3158
  }
2958
3159
  }
2959
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
+
2960
3181
  /* eslint-disable @typescript-eslint/no-unsafe-return */
2961
3182
  class Parser {
2962
3183
  index = 0;
@@ -3180,24 +3401,6 @@ function parseIPv6(input) {
3180
3401
  }
3181
3402
  return parser.new(input).parseWith(() => parser.readIPv6Addr());
3182
3403
  }
3183
- /** Parse `input` into IPv4 or IPv6 bytes. */
3184
- function parseIP(input, mapIPv4ToIPv6 = false) {
3185
- // strip zone index if it is present
3186
- if (input.includes("%")) {
3187
- input = input.split("%")[0];
3188
- }
3189
- if (input.length > MAX_IPV6_LENGTH) {
3190
- return undefined;
3191
- }
3192
- const addr = parser.new(input).parseWith(() => parser.readIPAddr());
3193
- if (!addr) {
3194
- return undefined;
3195
- }
3196
- if (mapIPv4ToIPv6 && addr.length === 4) {
3197
- return Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, addr[0], addr[1], addr[2], addr[3]]);
3198
- }
3199
- return addr;
3200
- }
3201
3404
 
3202
3405
  /** Check if `input` is IPv4. */
3203
3406
  function isIPv4(input) {
@@ -3207,347 +3410,69 @@ function isIPv4(input) {
3207
3410
  function isIPv6(input) {
3208
3411
  return Boolean(parseIPv6(input));
3209
3412
  }
3210
- /** Check if `input` is IPv4 or IPv6. */
3211
- function isIP(input) {
3212
- return Boolean(parseIP(input));
3213
- }
3214
3413
 
3215
- const isV4 = isIPv4;
3216
- const isV6 = isIPv6;
3217
- // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L7
3218
- // but with buf/offset args removed because we don't use them
3219
- const toBytes = function (ip) {
3220
- let offset = 0;
3221
- ip = ip.toString().trim();
3222
- if (isV4(ip)) {
3223
- const bytes = new Uint8Array(offset + 4);
3224
- ip.split(/\./g).forEach((byte) => {
3225
- bytes[offset++] = parseInt(byte, 10) & 0xff;
3226
- });
3227
- return bytes;
3228
- }
3229
- if (isV6(ip)) {
3230
- const sections = ip.split(':', 8);
3231
- let i;
3232
- for (i = 0; i < sections.length; i++) {
3233
- const isv4 = isV4(sections[i]);
3234
- let v4Buffer;
3235
- if (isv4) {
3236
- v4Buffer = toBytes(sections[i]);
3237
- sections[i] = toString$1(v4Buffer.slice(0, 2), 'base16');
3238
- }
3239
- if (v4Buffer != null && ++i < 8) {
3240
- sections.splice(i, 0, toString$1(v4Buffer.slice(2, 4), 'base16'));
3241
- }
3242
- }
3243
- if (sections[0] === '') {
3244
- while (sections.length < 8)
3245
- sections.unshift('0');
3246
- }
3247
- else if (sections[sections.length - 1] === '') {
3248
- while (sections.length < 8)
3249
- sections.push('0');
3250
- }
3251
- else if (sections.length < 8) {
3252
- for (i = 0; i < sections.length && sections[i] !== ''; i++)
3253
- ;
3254
- const argv = [i, 1];
3255
- for (i = 9 - sections.length; i > 0; i--) {
3256
- argv.push('0');
3257
- }
3258
- sections.splice.apply(sections, argv);
3259
- }
3260
- const bytes = new Uint8Array(offset + 16);
3261
- for (i = 0; i < sections.length; i++) {
3262
- const word = parseInt(sections[i], 16);
3263
- bytes[offset++] = (word >> 8) & 0xff;
3264
- bytes[offset++] = word & 0xff;
3265
- }
3266
- return bytes;
3267
- }
3268
- throw new Error('invalid ip address');
3269
- };
3270
- // Copied from https://github.com/indutny/node-ip/blob/master/lib/ip.js#L63
3271
- const toString = function (buf, offset = 0, length) {
3272
- offset = ~~offset;
3273
- length = length ?? (buf.length - offset);
3274
- const view = new DataView(buf.buffer);
3275
- if (length === 4) {
3276
- const result = [];
3277
- // IPv4
3278
- for (let i = 0; i < length; i++) {
3279
- result.push(buf[offset + i]);
3280
- }
3281
- return result.join('.');
3282
- }
3283
- if (length === 16) {
3284
- const result = [];
3285
- // IPv6
3286
- for (let i = 0; i < length; i += 2) {
3287
- result.push(view.getUint16(offset + i).toString(16));
3288
- }
3289
- return result.join(':')
3290
- .replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3')
3291
- .replace(/:{3,4}/, '::');
3292
- }
3293
- return '';
3294
- };
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;
3295
3455
 
3296
- const V = -1;
3297
- const names = {};
3298
- const codes = {};
3299
- const table = [
3300
- [4, 32, 'ip4'],
3301
- [6, 16, 'tcp'],
3302
- [33, 16, 'dccp'],
3303
- [41, 128, 'ip6'],
3304
- [42, V, 'ip6zone'],
3305
- [43, 8, 'ipcidr'],
3306
- [53, V, 'dns', true],
3307
- [54, V, 'dns4', true],
3308
- [55, V, 'dns6', true],
3309
- [56, V, 'dnsaddr', true],
3310
- [132, 16, 'sctp'],
3311
- [273, 16, 'udp'],
3312
- [275, 0, 'p2p-webrtc-star'],
3313
- [276, 0, 'p2p-webrtc-direct'],
3314
- [277, 0, 'p2p-stardust'],
3315
- [280, 0, 'webrtc-direct'],
3316
- [281, 0, 'webrtc'],
3317
- [290, 0, 'p2p-circuit'],
3318
- [301, 0, 'udt'],
3319
- [302, 0, 'utp'],
3320
- [400, V, 'unix', false, true],
3321
- // `ipfs` is added before `p2p` for legacy support.
3322
- // All text representations will default to `p2p`, but `ipfs` will
3323
- // still be supported
3324
- [421, V, 'ipfs'],
3325
- // `p2p` is the preferred name for 421, and is now the default
3326
- [421, V, 'p2p'],
3327
- [443, 0, 'https'],
3328
- [444, 96, 'onion'],
3329
- [445, 296, 'onion3'],
3330
- [446, V, 'garlic64'],
3331
- [448, 0, 'tls'],
3332
- [449, V, 'sni'],
3333
- [460, 0, 'quic'],
3334
- [461, 0, 'quic-v1'],
3335
- [465, 0, 'webtransport'],
3336
- [466, V, 'certhash'],
3337
- [477, 0, 'ws'],
3338
- [478, 0, 'wss'],
3339
- [479, 0, 'p2p-websocket-star'],
3340
- [480, 0, 'http'],
3341
- [481, V, 'http-path'],
3342
- [777, V, 'memory']
3343
- ];
3344
- // populate tables
3345
- table.forEach(row => {
3346
- const proto = createProtocol(...row);
3347
- codes[proto.code] = proto;
3348
- names[proto.name] = proto;
3349
- });
3350
- function createProtocol(code, size, name, resolvable, path) {
3351
- return {
3352
- code,
3353
- size,
3354
- name,
3355
- resolvable: Boolean(resolvable),
3356
- path: Boolean(path)
3456
+ function bytesToString(base) {
3457
+ return (buf) => {
3458
+ return toString(buf, base);
3357
3459
  };
3358
3460
  }
3359
- /**
3360
- * For the passed proto string or number, return a {@link Protocol}
3361
- *
3362
- * @example
3363
- *
3364
- * ```js
3365
- * import { protocol } from '@multiformats/multiaddr'
3366
- *
3367
- * console.info(protocol(4))
3368
- * // { code: 4, size: 32, name: 'ip4', resolvable: false, path: false }
3369
- * ```
3370
- */
3371
- function getProtocol(proto) {
3372
- if (typeof proto === 'number') {
3373
- if (codes[proto] != null) {
3374
- return codes[proto];
3375
- }
3376
- throw new Error(`no protocol with code: ${proto}`);
3377
- }
3378
- else if (typeof proto === 'string') {
3379
- if (names[proto] != null) {
3380
- return names[proto];
3381
- }
3382
- throw new Error(`no protocol with name: ${proto}`);
3383
- }
3384
- throw new Error(`invalid protocol id type: ${typeof proto}`);
3385
- }
3386
-
3387
- getProtocol('ip4');
3388
- getProtocol('ip6');
3389
- getProtocol('ipcidr');
3390
- /**
3391
- * Convert [code,Uint8Array] to string
3392
- */
3393
- // eslint-disable-next-line complexity
3394
- function convertToString(proto, buf) {
3395
- const protocol = getProtocol(proto);
3396
- switch (protocol.code) {
3397
- case 4: // ipv4
3398
- case 41: // ipv6
3399
- return bytes2ip(buf);
3400
- case 42: // ipv6zone
3401
- return bytes2str(buf);
3402
- case 43: // ipcidr
3403
- return toString$1(buf, 'base10');
3404
- case 6: // tcp
3405
- case 273: // udp
3406
- case 33: // dccp
3407
- case 132: // sctp
3408
- return bytes2port(buf).toString();
3409
- case 53: // dns
3410
- case 54: // dns4
3411
- case 55: // dns6
3412
- case 56: // dnsaddr
3413
- case 400: // unix
3414
- case 449: // sni
3415
- case 777: // memory
3416
- return bytes2str(buf);
3417
- case 421: // ipfs
3418
- return bytes2mh(buf);
3419
- case 444: // onion
3420
- return bytes2onion(buf);
3421
- case 445: // onion3
3422
- return bytes2onion(buf);
3423
- case 466: // certhash
3424
- return bytes2mb(buf);
3425
- case 481: // http-path
3426
- return globalThis.encodeURIComponent(bytes2str(buf));
3427
- default:
3428
- return toString$1(buf, 'base16'); // no clue. convert to hex
3429
- }
3430
- }
3431
- // eslint-disable-next-line complexity
3432
- function convertToBytes(proto, str) {
3433
- const protocol = getProtocol(proto);
3434
- switch (protocol.code) {
3435
- case 4: // ipv4
3436
- return ip2bytes(str);
3437
- case 41: // ipv6
3438
- return ip2bytes(str);
3439
- case 42: // ipv6zone
3440
- return str2bytes(str);
3441
- case 43: // ipcidr
3442
- return fromString(str, 'base10');
3443
- case 6: // tcp
3444
- case 273: // udp
3445
- case 33: // dccp
3446
- case 132: // sctp
3447
- return port2bytes(parseInt(str, 10));
3448
- case 53: // dns
3449
- case 54: // dns4
3450
- case 55: // dns6
3451
- case 56: // dnsaddr
3452
- case 400: // unix
3453
- case 449: // sni
3454
- case 777: // memory
3455
- return str2bytes(str);
3456
- case 421: // ipfs
3457
- return mh2bytes(str);
3458
- case 444: // onion
3459
- return onion2bytes(str);
3460
- case 445: // onion3
3461
- return onion32bytes(str);
3462
- case 466: // certhash
3463
- return mb2bytes(str);
3464
- case 481: // http-path
3465
- return str2bytes(globalThis.decodeURIComponent(str));
3466
- default:
3467
- return fromString(str, 'base16'); // no clue. convert from hex
3468
- }
3469
- }
3470
- const decoders = Object.values(bases).map((c) => c.decoder);
3471
- const anybaseDecoder = (function () {
3472
- let acc = decoders[0].or(decoders[1]);
3473
- decoders.slice(2).forEach((d) => (acc = acc.or(d)));
3474
- return acc;
3475
- })();
3476
- function ip2bytes(ipString) {
3477
- if (!isIP(ipString)) {
3478
- throw new Error('invalid ip address');
3479
- }
3480
- return toBytes(ipString);
3461
+ function stringToBytes(base) {
3462
+ return (buf) => {
3463
+ return fromString(buf, base);
3464
+ };
3481
3465
  }
3482
- function bytes2ip(ipBuff) {
3483
- const ipString = toString(ipBuff, 0, ipBuff.length);
3484
- if (ipString == null) {
3485
- throw new Error('ipBuff is required');
3486
- }
3487
- if (!isIP(ipString)) {
3488
- throw new Error('invalid ip address');
3489
- }
3490
- return ipString;
3466
+ function bytes2port(buf) {
3467
+ const view = new DataView(buf.buffer);
3468
+ return view.getUint16(buf.byteOffset).toString();
3491
3469
  }
3492
3470
  function port2bytes(port) {
3493
3471
  const buf = new ArrayBuffer(2);
3494
3472
  const view = new DataView(buf);
3495
- view.setUint16(0, port);
3473
+ view.setUint16(0, typeof port === 'string' ? parseInt(port) : port);
3496
3474
  return new Uint8Array(buf);
3497
3475
  }
3498
- function bytes2port(buf) {
3499
- const view = new DataView(buf.buffer);
3500
- return view.getUint16(buf.byteOffset);
3501
- }
3502
- function str2bytes(str) {
3503
- const buf = fromString(str);
3504
- const size = Uint8Array.from(encode$2(buf.length));
3505
- return concat([size, buf], size.length + buf.length);
3506
- }
3507
- function bytes2str(buf) {
3508
- const size = decode$4(buf);
3509
- buf = buf.slice(encodingLength$1(size));
3510
- if (buf.length !== size) {
3511
- throw new Error('inconsistent lengths');
3512
- }
3513
- return toString$1(buf);
3514
- }
3515
- function mh2bytes(hash) {
3516
- let mh;
3517
- if (hash[0] === 'Q' || hash[0] === '1') {
3518
- mh = decode$1(base58btc.decode(`z${hash}`)).bytes;
3519
- }
3520
- else {
3521
- mh = CID.parse(hash).multihash.bytes;
3522
- }
3523
- // the address is a varint prefixed multihash string representation
3524
- const size = Uint8Array.from(encode$2(mh.length));
3525
- return concat([size, mh], size.length + mh.length);
3526
- }
3527
- function mb2bytes(mbstr) {
3528
- const mb = anybaseDecoder.decode(mbstr);
3529
- const size = Uint8Array.from(encode$2(mb.length));
3530
- return concat([size, mb], size.length + mb.length);
3531
- }
3532
- function bytes2mb(buf) {
3533
- const size = decode$4(buf);
3534
- const hash = buf.slice(encodingLength$1(size));
3535
- if (hash.length !== size) {
3536
- throw new Error('inconsistent lengths');
3537
- }
3538
- return 'u' + toString$1(hash, 'base64url');
3539
- }
3540
- /**
3541
- * Converts bytes to bas58btc string
3542
- */
3543
- function bytes2mh(buf) {
3544
- const size = decode$4(buf);
3545
- const address = buf.slice(encodingLength$1(size));
3546
- if (address.length !== size) {
3547
- throw new Error('inconsistent lengths');
3548
- }
3549
- return toString$1(address, 'base58btc');
3550
- }
3551
3476
  function onion2bytes(str) {
3552
3477
  const addr = str.split(':');
3553
3478
  if (addr.length !== 2) {
@@ -3557,7 +3482,7 @@ function onion2bytes(str) {
3557
3482
  throw new Error(`failed to parse onion addr: ${addr[0]} not a Tor onion address.`);
3558
3483
  }
3559
3484
  // onion addresses do not include the multibase prefix, add it before decoding
3560
- const buf = base32.decode('b' + addr[0]);
3485
+ const buf = fromString(addr[0], 'base32');
3561
3486
  // onion port number
3562
3487
  const port = parseInt(addr[1], 10);
3563
3488
  if (port < 1 || port > 65536) {
@@ -3585,167 +3510,558 @@ function onion32bytes(str) {
3585
3510
  return concat([buf, portBuf], buf.length + portBuf.length);
3586
3511
  }
3587
3512
  function bytes2onion(buf) {
3588
- const addrBytes = buf.slice(0, buf.length - 2);
3589
- const portBytes = buf.slice(buf.length - 2);
3590
- 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');
3591
3516
  const port = bytes2port(portBytes);
3592
3517
  return `${addr}:${port}`;
3593
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
+ }
3594
3637
 
3595
- function stringToMultiaddrParts(str) {
3596
- str = cleanPath(str);
3597
- const tuples = [];
3598
- const stringTuples = [];
3599
- let path = null;
3600
- const parts = str.split('/').slice(1);
3601
- if (parts.length === 1 && parts[0] === '') {
3602
- return {
3603
- bytes: new Uint8Array(),
3604
- string: '/',
3605
- tuples: [],
3606
- stringTuples: [],
3607
- path: null
3608
- };
3638
+ function integer(value) {
3639
+ const int = parseInt(value);
3640
+ if (int.toString() !== value) {
3641
+ throw new ValidationError('Value must be an integer');
3642
+ }
3643
+ }
3644
+ function positive(value) {
3645
+ if (value < 0) {
3646
+ throw new ValidationError('Value must be a positive integer, or zero');
3609
3647
  }
3610
- for (let p = 0; p < parts.length; p++) {
3611
- const part = parts[p];
3612
- const proto = getProtocol(part);
3613
- if (proto.size === 0) {
3614
- tuples.push([proto.code]);
3615
- stringTuples.push([proto.code]);
3616
- // eslint-disable-next-line no-continue
3617
- continue;
3618
- }
3619
- p++; // advance addr part
3620
- if (p >= parts.length) {
3621
- throw new ParseError('invalid address: ' + str);
3622
- }
3623
- // if it's a path proto, take the rest
3624
- if (proto.path === true) {
3625
- // should we need to check each path part to see if it's a proto?
3626
- // This would allow for other protocols to be added after a unix path,
3627
- // however it would have issues if the path had a protocol name in the path
3628
- path = cleanPath(parts.slice(p).join('/'));
3629
- tuples.push([proto.code, convertToBytes(proto.code, path)]);
3630
- stringTuples.push([proto.code, path]);
3631
- break;
3632
- }
3633
- const bytes = convertToBytes(proto.code, parts[p]);
3634
- tuples.push([proto.code, bytes]);
3635
- stringTuples.push([proto.code, convertToString(proto.code, bytes)]);
3636
- }
3637
- return {
3638
- string: stringTuplesToString(stringTuples),
3639
- bytes: tuplesToBytes(tuples),
3640
- tuples,
3641
- stringTuples,
3642
- path
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
+ }
3643
3661
  };
3644
3662
  }
3645
- function bytesToMultiaddrParts(bytes) {
3646
- const tuples = [];
3647
- const stringTuples = [];
3648
- 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 = [];
3649
3903
  let i = 0;
3650
3904
  while (i < bytes.length) {
3651
3905
  const code = decode$4(bytes, i);
3652
- const n = encodingLength$1(code);
3653
- const p = getProtocol(code);
3654
- const size = sizeForAddr(p, bytes.slice(i + n));
3655
- if (size === 0) {
3656
- tuples.push([code]);
3657
- stringTuples.push([code]);
3658
- i += n;
3659
- // eslint-disable-next-line no-continue
3660
- continue;
3661
- }
3662
- const addr = bytes.slice(i + n, i + n + size);
3663
- i += (size + n);
3664
- if (i > bytes.length) { // did not end _exactly_ at buffer.length
3665
- throw new ParseError('Invalid address Uint8Array: ' + toString$1(bytes, 'base16'));
3666
- }
3667
- // ok, tuple seems good.
3668
- tuples.push([code, addr]);
3669
- const stringAddr = convertToString(code, addr);
3670
- stringTuples.push([code, stringAddr]);
3671
- if (p.path === true) {
3672
- // should we need to check each path part to see if it's a proto?
3673
- // This would allow for other protocols to be added after a unix path,
3674
- // however it would have issues if the path had a protocol name in the path
3675
- path = stringAddr;
3676
- break;
3677
- }
3678
- }
3679
- return {
3680
- bytes: Uint8Array.from(bytes),
3681
- string: stringTuplesToString(stringTuples),
3682
- tuples,
3683
- stringTuples,
3684
- path
3685
- };
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;
3686
3928
  }
3687
- /**
3688
- * [[num code, str value?]... ] -> string
3689
- */
3690
- function stringTuplesToString(tuples) {
3691
- const parts = [];
3692
- tuples.map((tup) => {
3693
- const proto = getProtocol(tup[0]);
3694
- parts.push(proto.name);
3695
- if (tup.length > 1 && tup[1] != null) {
3696
- 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;
3697
3962
  }
3698
- return null;
3699
- });
3700
- return cleanPath(parts.join('/'));
3963
+ bytes.push(component.bytes);
3964
+ length += component.bytes.byteLength;
3965
+ }
3966
+ return concat(bytes, length);
3701
3967
  }
3702
- /**
3703
- * [[int code, Uint8Array ]... ] -> Uint8Array
3704
- */
3705
- function tuplesToBytes(tuples) {
3706
- return concat(tuples.map((tup) => {
3707
- const proto = getProtocol(tup[0]);
3708
- let buf = Uint8Array.from(encode$2(proto.code));
3709
- if (tup.length > 1 && tup[1] != null) {
3710
- buf = concat([buf, tup[1]]); // add address buffer
3711
- }
3712
- return buf;
3713
- }));
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('/')}`;
3714
4044
  }
3715
4045
  /**
3716
4046
  * For the passed address, return the serialized size
3717
4047
  */
3718
- function sizeForAddr(p, addr) {
3719
- if (p.size > 0) {
3720
- return p.size / 8;
3721
- }
3722
- else if (p.size === 0) {
4048
+ function sizeForAddr(codec, bytes, offset) {
4049
+ if (codec.size == null || codec.size === 0) {
3723
4050
  return 0;
3724
4051
  }
3725
- else {
3726
- const size = decode$4(addr instanceof Uint8Array ? addr : Uint8Array.from(addr));
3727
- return size + encodingLength$1(size);
3728
- }
3729
- }
3730
- function cleanPath(str) {
3731
- return '/' + str.trim().split('/').filter((a) => a).join('/');
3732
- }
3733
- class ParseError extends Error {
3734
- static name = 'ParseError';
3735
- name = 'ParseError';
3736
- constructor(str) {
3737
- super(`Error parsing address: ${str}`);
4052
+ if (codec.size > 0) {
4053
+ return codec.size / 8;
3738
4054
  }
4055
+ return decode$4(bytes, offset);
3739
4056
  }
3740
4057
 
3741
- /* eslint-disable complexity */
3742
4058
  const inspect = Symbol.for('nodejs.util.inspect.custom');
3743
- const symbol = Symbol.for('@multiformats/js-multiaddr/multiaddr');
4059
+ const symbol = Symbol.for('@multiformats/multiaddr');
3744
4060
  const DNS_CODES = [
3745
- getProtocol('dns').code,
3746
- getProtocol('dns4').code,
3747
- getProtocol('dns6').code,
3748
- getProtocol('dnsaddr').code
4061
+ CODE_DNS,
4062
+ CODE_DNS4,
4063
+ CODE_DNS6,
4064
+ CODE_DNSADDR
3749
4065
  ];
3750
4066
  class NoAvailableResolverError extends Error {
3751
4067
  constructor(message = 'No available resolver') {
@@ -3753,44 +4069,56 @@ class NoAvailableResolverError extends Error {
3753
4069
  this.name = 'NoAvailableResolverError';
3754
4070
  }
3755
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
+ }
3756
4096
  /**
3757
4097
  * Creates a {@link Multiaddr} from a {@link MultiaddrInput}
3758
4098
  */
3759
4099
  class Multiaddr {
3760
- bytes;
3761
- #string;
3762
- #tuples;
3763
- #stringTuples;
3764
- #path;
3765
4100
  [symbol] = true;
3766
- constructor(addr) {
3767
- // default
3768
- if (addr == null) {
3769
- addr = '';
3770
- }
3771
- let parts;
3772
- if (addr instanceof Uint8Array) {
3773
- parts = bytesToMultiaddrParts(addr);
3774
- }
3775
- else if (typeof addr === 'string') {
3776
- if (addr.length > 0 && addr.charAt(0) !== '/') {
3777
- throw new Error(`multiaddr "${addr}" must start with a "/"`);
3778
- }
3779
- parts = stringToMultiaddrParts(addr);
3780
- }
3781
- else if (isMultiaddr(addr)) { // Multiaddr
3782
- 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);
3783
4110
  }
3784
- else {
3785
- 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);
3786
4115
  }
3787
- this.bytes = parts.bytes;
3788
- this.#string = parts.string;
3789
- this.#tuples = parts.tuples;
3790
- this.#stringTuples = parts.stringTuples;
3791
- this.#path = parts.path;
4116
+ return this.#bytes;
3792
4117
  }
3793
4118
  toString() {
4119
+ if (this.#string == null) {
4120
+ this.#string = componentsToString(this.#components);
4121
+ }
3794
4122
  return this.#string;
3795
4123
  }
3796
4124
  toJSON() {
@@ -3802,31 +4130,25 @@ class Multiaddr {
3802
4130
  let host;
3803
4131
  let port;
3804
4132
  let zone = '';
3805
- const tcp = getProtocol('tcp');
3806
- const udp = getProtocol('udp');
3807
- const ip4 = getProtocol('ip4');
3808
- const ip6 = getProtocol('ip6');
3809
- const dns6 = getProtocol('dns6');
3810
- const ip6zone = getProtocol('ip6zone');
3811
- for (const [code, value] of this.stringTuples()) {
3812
- if (code === ip6zone.code) {
4133
+ for (const { code, name, value } of this.#components) {
4134
+ if (code === CODE_IP6ZONE) {
3813
4135
  zone = `%${value ?? ''}`;
3814
4136
  }
3815
4137
  // default to https when protocol & port are omitted from DNS addrs
3816
4138
  if (DNS_CODES.includes(code)) {
3817
- transport = tcp.name === 'tcp' ? 'tcp' : 'udp';
4139
+ transport = 'tcp';
3818
4140
  port = 443;
3819
4141
  host = `${value ?? ''}${zone}`;
3820
- family = code === dns6.code ? 6 : 4;
4142
+ family = code === CODE_DNS6 ? 6 : 4;
3821
4143
  }
3822
- if (code === tcp.code || code === udp.code) {
3823
- transport = getProtocol(code).name === 'tcp' ? 'tcp' : 'udp';
4144
+ if (code === CODE_TCP || code === CODE_UDP) {
4145
+ transport = name === 'tcp' ? 'tcp' : 'udp';
3824
4146
  port = parseInt(value ?? '');
3825
4147
  }
3826
- if (code === ip4.code || code === ip6.code) {
3827
- transport = getProtocol(code).name === 'tcp' ? 'tcp' : 'udp';
4148
+ if (code === CODE_IP4 || code === CODE_IP6) {
4149
+ transport = 'tcp';
3828
4150
  host = `${value ?? ''}${zone}`;
3829
- family = code === ip6.code ? 6 : 4;
4151
+ family = code === CODE_IP6 ? 6 : 4;
3830
4152
  }
3831
4153
  }
3832
4154
  if (family == null || transport == null || host == null || port == null) {
@@ -3840,25 +4162,44 @@ class Multiaddr {
3840
4162
  };
3841
4163
  return opts;
3842
4164
  }
4165
+ getComponents() {
4166
+ return [
4167
+ ...this.#components
4168
+ ];
4169
+ }
3843
4170
  protos() {
3844
- 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
+ });
3845
4181
  }
3846
4182
  protoCodes() {
3847
- return this.#tuples.map(([code]) => code);
4183
+ return this.#components.map(({ code }) => code);
3848
4184
  }
3849
4185
  protoNames() {
3850
- return this.#tuples.map(([code]) => getProtocol(code).name);
4186
+ return this.#components.map(({ name }) => name);
3851
4187
  }
3852
4188
  tuples() {
3853
- return this.#tuples.map(([code, value]) => {
4189
+ return this.#components.map(({ code, value }) => {
3854
4190
  if (value == null) {
3855
4191
  return [code];
3856
4192
  }
3857
- 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;
3858
4199
  });
3859
4200
  }
3860
4201
  stringTuples() {
3861
- return this.#stringTuples.map(([code, value]) => {
4202
+ return this.#components.map(({ code, value }) => {
3862
4203
  if (value == null) {
3863
4204
  return [code];
3864
4205
  }
@@ -3866,37 +4207,47 @@ class Multiaddr {
3866
4207
  });
3867
4208
  }
3868
4209
  encapsulate(addr) {
3869
- addr = new Multiaddr(addr);
3870
- 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
+ });
3871
4217
  }
3872
4218
  decapsulate(addr) {
3873
4219
  const addrString = addr.toString();
3874
4220
  const s = this.toString();
3875
4221
  const i = s.lastIndexOf(addrString);
3876
4222
  if (i < 0) {
3877
- 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()}`);
3878
4224
  }
3879
- return new Multiaddr(s.slice(0, i));
4225
+ return new Multiaddr(s.slice(0, i), {
4226
+ validate: false
4227
+ });
3880
4228
  }
3881
4229
  decapsulateCode(code) {
3882
- const tuples = this.tuples();
3883
- for (let i = tuples.length - 1; i >= 0; i--) {
3884
- if (tuples[i][0] === code) {
3885
- 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;
3886
4235
  }
3887
4236
  }
3888
- return this;
4237
+ return new Multiaddr(this.#components.slice(0, index), {
4238
+ validate: false
4239
+ });
3889
4240
  }
3890
4241
  getPeerId() {
3891
4242
  try {
3892
4243
  let tuples = [];
3893
- this.stringTuples().forEach(([code, name]) => {
3894
- if (code === names.p2p.code) {
3895
- tuples.push([code, name]);
4244
+ this.#components.forEach(({ code, value }) => {
4245
+ if (code === CODE_P2P) {
4246
+ tuples.push([code, value]);
3896
4247
  }
3897
4248
  // if this is a p2p-circuit address, return the target peer id if present
3898
4249
  // not the peer id of the relay
3899
- if (code === names['p2p-circuit'].code) {
4250
+ if (code === CODE_P2P_CIRCUIT) {
3900
4251
  tuples = [];
3901
4252
  }
3902
4253
  });
@@ -3907,10 +4258,10 @@ class Multiaddr {
3907
4258
  // peer id is base58btc encoded string but not multibase encoded so add the `z`
3908
4259
  // prefix so we can validate that it is correctly encoded
3909
4260
  if (peerIdStr[0] === 'Q' || peerIdStr[0] === '1') {
3910
- return toString$1(base58btc.decode(`z${peerIdStr}`), 'base58btc');
4261
+ return toString(base58btc.decode(`z${peerIdStr}`), 'base58btc');
3911
4262
  }
3912
4263
  // try to parse peer id as CID
3913
- return toString$1(CID.parse(peerIdStr).multihash.bytes, 'base58btc');
4264
+ return toString(CID.parse(peerIdStr).multihash.bytes, 'base58btc');
3914
4265
  }
3915
4266
  return null;
3916
4267
  }
@@ -3919,7 +4270,14 @@ class Multiaddr {
3919
4270
  }
3920
4271
  }
3921
4272
  getPath() {
3922
- 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;
3923
4281
  }
3924
4282
  equals(addr) {
3925
4283
  return equals(this.bytes, addr.bytes);
@@ -3948,15 +4306,14 @@ class Multiaddr {
3948
4306
  port: options.port
3949
4307
  };
3950
4308
  }
3951
- isThinWaistAddress(addr) {
3952
- const protos = (addr ?? this).protos();
3953
- if (protos.length !== 2) {
4309
+ isThinWaistAddress() {
4310
+ if (this.#components.length !== 2) {
3954
4311
  return false;
3955
4312
  }
3956
- if (protos[0].code !== 4 && protos[0].code !== 41) {
4313
+ if (this.#components[0].code !== CODE_IP4 && this.#components[0].code !== CODE_IP6) {
3957
4314
  return false;
3958
4315
  }
3959
- if (protos[1].code !== 6 && protos[1].code !== 273) {
4316
+ if (this.#components[1].code !== CODE_TCP && this.#components[1].code !== CODE_UDP) {
3960
4317
  return false;
3961
4318
  }
3962
4319
  return true;
@@ -3974,9 +4331,23 @@ class Multiaddr {
3974
4331
  * ```
3975
4332
  */
3976
4333
  [inspect]() {
3977
- return `Multiaddr(${this.#string})`;
4334
+ return `Multiaddr(${this.toString()})`;
3978
4335
  }
3979
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
+ }
3980
4351
 
3981
4352
  /**
3982
4353
  * @packageDocumentation
@@ -4071,9 +4442,42 @@ class Multiaddr {
4071
4442
  * console.info(resolved)
4072
4443
  * // [Multiaddr('/ip4/147.75...'), Multiaddr('/ip4/147.75...'), Multiaddr('/ip4/147.75...')...]
4073
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
+ * ```
4074
4476
  */
4075
4477
  /**
4076
4478
  * All configured {@link Resolver}s
4479
+ *
4480
+ * @deprecated DNS resolving will be removed in a future release
4077
4481
  */
4078
4482
  const resolvers = new Map();
4079
4483
  /**
@@ -4669,9 +5073,9 @@ class ConnectionManager extends TypedEventEmitter {
4669
5073
  log$1.warn(`Already connected to peer ${peerId.toString()}. Not dialing.`);
4670
5074
  return false;
4671
5075
  }
4672
- const isSameShard = await this.isPeerTopicConfigured(peerId);
5076
+ const isSameShard = await this.isPeerOnSameShard(peerId);
4673
5077
  if (!isSameShard) {
4674
- const shardInfo = await this.getPeerShardInfo(peerId, this.libp2p.peerStore);
5078
+ const shardInfo = await this.getPeerShardInfo(peerId);
4675
5079
  log$1.warn(`Discovered peer ${peerId.toString()} with ShardInfo ${shardInfo} is not part of any of the configured pubsub topics (${this.pubsubTopics}).
4676
5080
  Not dialing.`);
4677
5081
  return false;
@@ -4727,17 +5131,25 @@ class ConnectionManager extends TypedEventEmitter {
4727
5131
  return [];
4728
5132
  }
4729
5133
  }
4730
- async isPeerTopicConfigured(peerId) {
4731
- const shardInfo = await this.getPeerShardInfo(peerId, this.libp2p.peerStore);
4732
- // If there's no shard information, simply return true
4733
- if (!shardInfo)
5134
+ async isPeerOnSameShard(peerId) {
5135
+ const shardInfo = await this.getPeerShardInfo(peerId);
5136
+ if (!shardInfo) {
4734
5137
  return true;
5138
+ }
4735
5139
  const pubsubTopics = shardInfoToPubsubTopics(shardInfo);
4736
5140
  const isTopicConfigured = pubsubTopics.some((topic) => this.pubsubTopics.includes(topic));
4737
5141
  return isTopicConfigured;
4738
5142
  }
4739
- async getPeerShardInfo(peerId, peerStore) {
4740
- const peer = await peerStore.get(peerId);
5143
+ async isPeerOnPubsubTopic(peerId, pubsubTopic) {
5144
+ const shardInfo = await this.getPeerShardInfo(peerId);
5145
+ if (!shardInfo) {
5146
+ return true;
5147
+ }
5148
+ const pubsubTopics = shardInfoToPubsubTopics(shardInfo);
5149
+ return pubsubTopics.some((t) => t === pubsubTopic);
5150
+ }
5151
+ async getPeerShardInfo(peerId) {
5152
+ const peer = await this.libp2p.peerStore.get(peerId);
4741
5153
  const shardInfoBytes = peer.metadata.get("shardInfo");
4742
5154
  if (!shardInfoBytes)
4743
5155
  return undefined;
@@ -4783,13 +5195,15 @@ class ConnectionManager extends TypedEventEmitter {
4783
5195
 
4784
5196
  const log = new Logger("metadata");
4785
5197
  const MetadataCodec = "/vac/waku/metadata/1.0.0";
4786
- class Metadata extends BaseProtocol {
5198
+ class Metadata {
4787
5199
  pubsubTopics;
5200
+ streamManager;
4788
5201
  libp2pComponents;
4789
5202
  handshakesConfirmed = new Map();
5203
+ multicodec = MetadataCodec;
4790
5204
  constructor(pubsubTopics, libp2p) {
4791
- super(MetadataCodec, libp2p.components, pubsubTopics);
4792
5205
  this.pubsubTopics = pubsubTopics;
5206
+ this.streamManager = new StreamManager(MetadataCodec, libp2p);
4793
5207
  this.libp2pComponents = libp2p;
4794
5208
  void libp2p.registrar.handle(MetadataCodec, (streamData) => {
4795
5209
  void this.onRequest(streamData);
@@ -4809,7 +5223,7 @@ class Metadata extends BaseProtocol {
4809
5223
  }
4810
5224
  let stream;
4811
5225
  try {
4812
- stream = await this.getStream(peerId);
5226
+ stream = await this.streamManager.getStream(peerId);
4813
5227
  }
4814
5228
  catch (error) {
4815
5229
  log.error("Failed to get stream", error);
@@ -4973,4 +5387,4 @@ function messageHashStr(pubsubTopic, message) {
4973
5387
  return hashStr;
4974
5388
  }
4975
5389
 
4976
- export { ConnectionManager, FilterCodecs, FilterCore, LightPushCodec, LightPushCore, MetadataCodec, StoreCodec, StoreCore, createEncoder, index$3 as message, messageHash, messageHashStr, wakuMetadata, index$2 as waku_filter, index$1 as waku_light_push, index as waku_store };
5390
+ 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 };