@le-space/aleph-bootstrap 0.9.2 → 0.9.4

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 (4) hide show
  1. package/README.md +30 -0
  2. package/index.d.ts +53 -1
  3. package/index.js +45 -14
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -12,6 +12,8 @@ It is designed for two complementary jobs:
12
12
 
13
13
  - `discoverAlephBootstrapMultiaddrs(options)`
14
14
  - `createLibp2pAlephBootstrap(options)`
15
+ - `filterRelayBootstrapPostsByProfile(posts, profile?)`
16
+ - `filterRelayBootstrapPostsByRegistration(posts, registrationId?)`
15
17
  - `filterPublicMultiaddrs(addrs, options?)`
16
18
  - `createRelayBootstrapPost(options)`
17
19
  - `signRelayBootstrapAuthorization(args)`
@@ -45,6 +47,34 @@ By default, discovery will:
45
47
  - verify dual-key records when they are present
46
48
  - ignore malformed or invalid dual-key records
47
49
 
50
+ ## Scoping discovery
51
+
52
+ The channel is shared. Several relay implementations register in it, and so
53
+ does every throwaway relay an E2E run starts — under the same profile as the
54
+ production one, with a registration that outlives the machine it describes,
55
+ because guests self-publish with generated keys and no remaining key can
56
+ FORGET the post.
57
+
58
+ Two scopes, and most consumers want both:
59
+
60
+ ```ts
61
+ const list = await discoverAlephBootstrapMultiaddrs({
62
+ profile: 'orbitdb-relay', // not uc-go-peer's relays
63
+ registrationId: 'relay:orbitdb-relay:orbitdb-relay' // and not the E2E ones
64
+ })
65
+ ```
66
+
67
+ `profile` keeps out relays a consumer cannot use at all — an orbitdb app that
68
+ dials a `uc-go-peer` relay never gets a shared circuit and sits at
69
+ `candidates: 0`. `registrationId` keeps out its own corpses: measured
70
+ downstream, a browser probe wave against an unscoped list spent its outbound
71
+ stream budget on dead addresses and wrote off the one healthy relay along with
72
+ them.
73
+
74
+ Use `registrationId` for anything that bakes addresses into a build or dials
75
+ them on start. Omit both to see the whole channel, which is what a dashboard
76
+ wants.
77
+
48
78
  If a consumer wants to require the stronger model:
49
79
 
50
80
  ```ts
package/index.d.ts CHANGED
@@ -85,6 +85,13 @@ interface DiscoverAlephBootstrapOptions {
85
85
  maxAgeMs?: number;
86
86
  compactMultiaddrLimit?: number;
87
87
  browserDialableOnly?: boolean;
88
+ /**
89
+ * Keep plaintext `/ws` relays in a browser-dialable result. Defaults to
90
+ * false so an https page never receives an address the browser will block as
91
+ * mixed content. Set it for consumers that dial from node or a plain-http
92
+ * origin.
93
+ */
94
+ allowInsecureWebSockets?: boolean;
88
95
  requireDualKeyAttestation?: boolean;
89
96
  verifyDualKeyAttestation?: boolean;
90
97
  /**
@@ -96,11 +103,29 @@ interface DiscoverAlephBootstrapOptions {
96
103
  * When omitted, posts of every profile are returned (previous behaviour).
97
104
  */
98
105
  profile?: string | readonly string[];
106
+ /**
107
+ * Restrict discovery further, to the registrations published under this id
108
+ * (e.g. `"relay:orbitdb-relay:orbitdb-relay"`). Profile is not fine enough:
109
+ * an E2E run's throwaway relays register under the same profile as the
110
+ * production one, and a registration outlives the machine it describes. Use
111
+ * this for anything that bakes addresses into a build or dials them on
112
+ * start; omit it to see the whole channel.
113
+ */
114
+ registrationId?: string | readonly string[];
99
115
  fetch?: typeof fetch;
100
116
  }
101
117
  interface FilterPublicMultiaddrsOptions {
102
118
  browserDialableOnly?: boolean;
103
119
  requirePeerId?: boolean;
120
+ /**
121
+ * Keep plaintext `/ws` addresses in the browser-dialable set. Defaults to
122
+ * false: a page served over https cannot open a `ws://` socket, the browser
123
+ * blocks it as mixed content, so a plaintext relay is dead weight in a
124
+ * browser bootstrap list. Publishers pass true — a relay should still
125
+ * announce the plaintext port it really serves, for node peers that can use
126
+ * it.
127
+ */
128
+ allowInsecureWebSockets?: boolean;
104
129
  }
105
130
  interface CreateRelayBootstrapPostOptions {
106
131
  sender: string;
@@ -122,7 +147,14 @@ interface CreateRelayBootstrapPostOptions {
122
147
  }
123
148
  type RelayBootstrapTrustMode = "wallet-signed" | "dual-key-attested";
124
149
  declare function relayBootstrapMultiaddrsHash(addrs: readonly string[]): string;
150
+ /**
151
+ * Publish-side helper: pick the addresses a relay announces to the registry.
152
+ * Plaintext `/ws` is kept here on purpose — announcing the port the relay
153
+ * really serves is honest, and node peers can dial it. Browsers filter it out
154
+ * again on the read side, where the https origin makes it undialable.
155
+ */
125
156
  declare function selectCompactRelayBootstrapMultiaddrs(addrs: readonly string[], limit?: number): string[];
157
+ declare function isBrowserDialableMultiaddr(addr: string, options?: Pick<FilterPublicMultiaddrsOptions, "allowInsecureWebSockets">): boolean;
126
158
  declare function dedupeMultiaddrs(addrs: readonly string[]): string[];
127
159
  declare function isPublicMultiaddr(addr: string): boolean;
128
160
  declare function filterPublicMultiaddrs(addrs: readonly string[], options?: FilterPublicMultiaddrsOptions): string[];
@@ -196,10 +228,30 @@ declare function selectCurrentRelayBootstrapPosts(posts: readonly RelayBootstrap
196
228
  * A missing/empty filter is a no-op so existing callers are unaffected.
197
229
  */
198
230
  declare function filterRelayBootstrapPostsByProfile(posts: readonly RelayBootstrapPostRecord[], profile?: string | readonly string[]): RelayBootstrapPostRecord[];
231
+ /**
232
+ * Keep only the registrations a consumer published for itself.
233
+ *
234
+ * Profile is not fine enough. Every ephemeral relay an E2E run starts
235
+ * registers under the same profile as the production one - as
236
+ * `relay:<profile>:<name>-e2e-*` - and a registration outlives the machine it
237
+ * describes, because guests self-publish with generated keys and no remaining
238
+ * key can FORGET the post. So the channel accumulates registrations that are
239
+ * indistinguishable from the real one by profile alone, and were alive when
240
+ * they were written.
241
+ *
242
+ * Measured downstream: a browser probe wave against that list exhausted its
243
+ * outbound stream budget on dead addresses and wrote off the one healthy relay
244
+ * along with the corpses, leaving both browsers with nothing to dial.
245
+ *
246
+ * Omitting the scope returns every registration, which is the previous
247
+ * behaviour and the right default for a consumer that wants to see the channel
248
+ * rather than its own corner of it.
249
+ */
250
+ declare function filterRelayBootstrapPostsByRegistration(posts: readonly RelayBootstrapPostRecord[], registrationId?: string | readonly string[]): RelayBootstrapPostRecord[];
199
251
  declare function discoverAlephBootstrapMultiaddrs(options?: DiscoverAlephBootstrapOptions): Promise<string[]>;
200
252
  declare function createLibp2pAlephBootstrap(options?: DiscoverAlephBootstrapOptions & {
201
253
  timeout?: number;
202
254
  tagName?: string;
203
255
  }): Promise<ReturnType<typeof bootstrap>>;
204
256
 
205
- export { type CreateRelayBootstrapPostOptions, DEFAULT_ALEPH_API_HOST, DEFAULT_ALEPH_BOOTSTRAP_CHANNEL, DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE, DEFAULT_ALEPH_BOOTSTRAP_REF, DEFAULT_BOOTSTRAP_COMPACT_MULTIADDR_LIMIT, DEFAULT_BOOTSTRAP_MAX_AGE_MS, DEFAULT_BOOTSTRAP_MAX_PAGES, DEFAULT_BOOTSTRAP_PAGINATION, type DiscoverAlephBootstrapOptions, type FilterPublicMultiaddrsOptions, RELAY_BOOTSTRAP_SIGNATURE_SCHEME, type RelayBootstrapAuthorizationPayload, type RelayBootstrapAuthorizationRecord, type RelayBootstrapContent, type RelayBootstrapPostContent, type RelayBootstrapPostRecord, type RelayBootstrapProofPayload, type RelayBootstrapProofRecord, type RelayBootstrapProofSigner, type RelayBootstrapTrustMode, type RelayBootstrapVerificationResult, buildRelayBootstrapPostContent, createLibp2pAlephBootstrap, createRelayBootstrapPost, dedupeMultiaddrs, discoverAlephBootstrapMultiaddrs, fetchAlephBootstrapPosts, filterPublicMultiaddrs, filterRelayBootstrapPostsByProfile, isPublicMultiaddr, relayBootstrapMultiaddrsHash, relayBootstrapTrustMode, selectCompactRelayBootstrapMultiaddrs, selectCurrentRelayBootstrapPosts, signRelayBootstrapAuthorization, signRelayBootstrapProof, verifyRelayBootstrapAuthorization, verifyRelayBootstrapDualKeyContent, verifyRelayBootstrapProof };
257
+ export { type CreateRelayBootstrapPostOptions, DEFAULT_ALEPH_API_HOST, DEFAULT_ALEPH_BOOTSTRAP_CHANNEL, DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE, DEFAULT_ALEPH_BOOTSTRAP_REF, DEFAULT_BOOTSTRAP_COMPACT_MULTIADDR_LIMIT, DEFAULT_BOOTSTRAP_MAX_AGE_MS, DEFAULT_BOOTSTRAP_MAX_PAGES, DEFAULT_BOOTSTRAP_PAGINATION, type DiscoverAlephBootstrapOptions, type FilterPublicMultiaddrsOptions, RELAY_BOOTSTRAP_SIGNATURE_SCHEME, type RelayBootstrapAuthorizationPayload, type RelayBootstrapAuthorizationRecord, type RelayBootstrapContent, type RelayBootstrapPostContent, type RelayBootstrapPostRecord, type RelayBootstrapProofPayload, type RelayBootstrapProofRecord, type RelayBootstrapProofSigner, type RelayBootstrapTrustMode, type RelayBootstrapVerificationResult, buildRelayBootstrapPostContent, createLibp2pAlephBootstrap, createRelayBootstrapPost, dedupeMultiaddrs, discoverAlephBootstrapMultiaddrs, fetchAlephBootstrapPosts, filterPublicMultiaddrs, filterRelayBootstrapPostsByProfile, filterRelayBootstrapPostsByRegistration, isBrowserDialableMultiaddr, isPublicMultiaddr, relayBootstrapMultiaddrsHash, relayBootstrapTrustMode, selectCompactRelayBootstrapMultiaddrs, selectCurrentRelayBootstrapPosts, signRelayBootstrapAuthorization, signRelayBootstrapProof, verifyRelayBootstrapAuthorization, verifyRelayBootstrapDualKeyContent, verifyRelayBootstrapProof };
package/index.js CHANGED
@@ -58,7 +58,8 @@ function browserMultiaddrTransport(addr) {
58
58
  }
59
59
  function selectCompactRelayBootstrapMultiaddrs(addrs, limit = DEFAULT_BOOTSTRAP_COMPACT_MULTIADDR_LIMIT) {
60
60
  const entries = filterPublicMultiaddrs(addrs, {
61
- browserDialableOnly: true
61
+ browserDialableOnly: true,
62
+ allowInsecureWebSockets: true
62
63
  }).map((addr, index) => ({
63
64
  addr,
64
65
  index,
@@ -134,12 +135,23 @@ function isLocalHostname(host) {
134
135
  function hasPeerId(addr) {
135
136
  return splitMultiaddr(addr).includes("p2p");
136
137
  }
137
- function isBrowserDialableMultiaddr(addr) {
138
+ function isSecureWebSocketMultiaddr(addr) {
139
+ const parts = splitMultiaddr(addr.toLowerCase());
140
+ if (parts.includes("wss")) return true;
141
+ const wsIndex = parts.indexOf("ws");
142
+ return wsIndex > 0 && parts.lastIndexOf("tls", wsIndex) >= 0;
143
+ }
144
+ function isWebSocketMultiaddr(addr) {
145
+ const parts = splitMultiaddr(addr.toLowerCase());
146
+ return parts.includes("ws") || parts.includes("wss");
147
+ }
148
+ function isBrowserDialableMultiaddr(addr, options = {}) {
138
149
  const normalized = addr.toLowerCase();
139
150
  if (normalized.includes("/webtransport") || normalized.includes("/webrtc-direct")) {
140
151
  return normalized.includes("/certhash/");
141
152
  }
142
- return normalized.includes("/ws") || normalized.includes("/wss");
153
+ if (!isWebSocketMultiaddr(addr)) return false;
154
+ return (options.allowInsecureWebSockets ?? false) || isSecureWebSocketMultiaddr(addr);
143
155
  }
144
156
  function dedupeMultiaddrs(addrs) {
145
157
  const seen = /* @__PURE__ */ new Set();
@@ -173,7 +185,9 @@ function filterPublicMultiaddrs(addrs, options = {}) {
173
185
  return dedupeMultiaddrs(addrs).filter((addr) => {
174
186
  if (!isPublicMultiaddr(addr)) return false;
175
187
  if (options.requirePeerId !== false && !hasPeerId(addr)) return false;
176
- if (options.browserDialableOnly && !isBrowserDialableMultiaddr(addr)) {
188
+ if (options.browserDialableOnly && !isBrowserDialableMultiaddr(addr, {
189
+ allowInsecureWebSockets: options.allowInsecureWebSockets
190
+ })) {
177
191
  return false;
178
192
  }
179
193
  return true;
@@ -288,7 +302,8 @@ function buildRelayBootstrapPostContent(args) {
288
302
  const updatedAt = args.now ?? Date.now();
289
303
  const fullMultiaddrs = filterPublicMultiaddrs(args.multiaddrs);
290
304
  const fullBrowserMultiaddrs = args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
291
- browserDialableOnly: true
305
+ browserDialableOnly: true,
306
+ allowInsecureWebSockets: true
292
307
  }) : void 0;
293
308
  const compactMultiaddrs = selectCompactRelayBootstrapMultiaddrs(
294
309
  fullBrowserMultiaddrs && fullBrowserMultiaddrs.length > 0 ? fullBrowserMultiaddrs : fullMultiaddrs,
@@ -376,7 +391,8 @@ async function signRelayBootstrapAuthorization(args) {
376
391
  async function signRelayBootstrapProof(args) {
377
392
  const fullMultiaddrs = filterPublicMultiaddrs(args.multiaddrs);
378
393
  const fullBrowserMultiaddrs = args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
379
- browserDialableOnly: true
394
+ browserDialableOnly: true,
395
+ allowInsecureWebSockets: true
380
396
  }) : void 0;
381
397
  const compactMultiaddrs = selectCompactRelayBootstrapMultiaddrs(
382
398
  fullBrowserMultiaddrs && fullBrowserMultiaddrs.length > 0 ? fullBrowserMultiaddrs : fullMultiaddrs,
@@ -637,6 +653,14 @@ function filterRelayBootstrapPostsByProfile(posts, profile) {
637
653
  (post) => post.content != null && allowed.has(String(post.content.profile))
638
654
  );
639
655
  }
656
+ function filterRelayBootstrapPostsByRegistration(posts, registrationId) {
657
+ const wanted = (typeof registrationId === "string" ? [registrationId] : registrationId ?? []).map((entry) => entry.trim()).filter(Boolean);
658
+ if (wanted.length === 0) return [...posts];
659
+ const allowed = new Set(wanted);
660
+ return posts.filter(
661
+ (post) => post.content != null && allowed.has(String(post.content.registrationId))
662
+ );
663
+ }
640
664
  async function filterTrustedRelayBootstrapPosts(posts, options = {}) {
641
665
  const requireDualKeyAttestation = options.requireDualKeyAttestation ?? false;
642
666
  const verifyDualKeyAttestation = options.verifyDualKeyAttestation ?? true;
@@ -676,11 +700,14 @@ async function discoverAlephBootstrapMultiaddrs(options = {}) {
676
700
  pagination
677
701
  });
678
702
  collectedPosts.push(...pagePosts);
679
- const selectedPosts = filterRelayBootstrapPostsByProfile(
680
- selectCurrentRelayBootstrapPosts(collectedPosts, {
681
- maxAgeMs: options.maxAgeMs
682
- }),
683
- options.profile
703
+ const selectedPosts = filterRelayBootstrapPostsByRegistration(
704
+ filterRelayBootstrapPostsByProfile(
705
+ selectCurrentRelayBootstrapPosts(collectedPosts, {
706
+ maxAgeMs: options.maxAgeMs
707
+ }),
708
+ options.profile
709
+ ),
710
+ options.registrationId
684
711
  );
685
712
  const trustedPosts = await filterTrustedRelayBootstrapPosts(selectedPosts, {
686
713
  requireDualKeyAttestation: options.requireDualKeyAttestation,
@@ -688,7 +715,8 @@ async function discoverAlephBootstrapMultiaddrs(options = {}) {
688
715
  });
689
716
  const addrs = relayBootstrapPostsToMultiaddrs(
690
717
  trustedPosts,
691
- browserDialableOnly
718
+ browserDialableOnly,
719
+ options.allowInsecureWebSockets ?? false
692
720
  );
693
721
  if (addrs.length > 0) {
694
722
  return addrs;
@@ -699,7 +727,7 @@ async function discoverAlephBootstrapMultiaddrs(options = {}) {
699
727
  }
700
728
  return [];
701
729
  }
702
- function relayBootstrapPostsToMultiaddrs(posts, browserDialableOnly) {
730
+ function relayBootstrapPostsToMultiaddrs(posts, browserDialableOnly, allowInsecureWebSockets) {
703
731
  const addrs = [];
704
732
  for (const post of posts) {
705
733
  const content = post.content;
@@ -707,7 +735,8 @@ function relayBootstrapPostsToMultiaddrs(posts, browserDialableOnly) {
707
735
  const candidates = browserDialableOnly && Array.isArray(content.browserMultiaddrs) && content.browserMultiaddrs.length > 0 ? content.browserMultiaddrs : content.multiaddrs;
708
736
  addrs.push(
709
737
  ...filterPublicMultiaddrs(candidates, {
710
- browserDialableOnly
738
+ browserDialableOnly,
739
+ allowInsecureWebSockets
711
740
  })
712
741
  );
713
742
  }
@@ -739,6 +768,8 @@ export {
739
768
  fetchAlephBootstrapPosts,
740
769
  filterPublicMultiaddrs,
741
770
  filterRelayBootstrapPostsByProfile,
771
+ filterRelayBootstrapPostsByRegistration,
772
+ isBrowserDialableMultiaddr,
742
773
  isPublicMultiaddr,
743
774
  relayBootstrapMultiaddrsHash,
744
775
  relayBootstrapTrustMode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/aleph-bootstrap",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "Aleph-backed libp2p bootstrap discovery and relay registration helpers.",
5
5
  "license": "MIT",
6
6
  "type": "module",