@le-space/aleph-bootstrap 0.9.4 → 0.9.6

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.
package/README.md CHANGED
@@ -13,6 +13,7 @@ It is designed for two complementary jobs:
13
13
  - `discoverAlephBootstrapMultiaddrs(options)`
14
14
  - `createLibp2pAlephBootstrap(options)`
15
15
  - `filterRelayBootstrapPostsByProfile(posts, profile?)`
16
+ - `unsupportedRelayBootstrapReason(post)`
16
17
  - `filterRelayBootstrapPostsByRegistration(posts, registrationId?)`
17
18
  - `filterPublicMultiaddrs(addrs, options?)`
18
19
  - `createRelayBootstrapPost(options)`
@@ -71,6 +72,12 @@ downstream, a browser probe wave against an unscoped list spent its outbound
71
72
  stream budget on dead addresses and wrote off the one healthy relay along with
72
73
  them.
73
74
 
75
+ A record this package cannot use — a legacy v1 post, or a type it did not ask
76
+ for — is **skipped, not fatal**. One such record on a public, append-only
77
+ channel would otherwise blind discovery for every consumer, permanently, and
78
+ nobody can FORGET somebody else's post. Pass `onUnsupportedPost` to see what
79
+ was skipped and why.
80
+
74
81
  Use `registrationId` for anything that bakes addresses into a build or dials
75
82
  them on start. Omit both to see the whole channel, which is what a dashboard
76
83
  wants.
package/index.d.ts CHANGED
@@ -112,6 +112,15 @@ interface DiscoverAlephBootstrapOptions {
112
112
  * start; omit it to see the whole channel.
113
113
  */
114
114
  registrationId?: string | readonly string[];
115
+ /**
116
+ * Called for each record discovery had to skip. Optional, and the default of
117
+ * doing nothing is deliberate — a skipped record is not an error for the
118
+ * consumer — but a consumer whose relay went missing wants to see this.
119
+ */
120
+ onUnsupportedPost?: (skipped: {
121
+ hash: string | null | undefined;
122
+ reason: string;
123
+ }) => void;
115
124
  fetch?: typeof fetch;
116
125
  }
117
126
  interface FilterPublicMultiaddrsOptions {
@@ -220,7 +229,24 @@ declare function verifyRelayBootstrapDualKeyContent(content: RelayBootstrapConte
220
229
  now?: number;
221
230
  }): Promise<RelayBootstrapVerificationResult>;
222
231
  declare function fetchAlephBootstrapPosts(options?: DiscoverAlephBootstrapOptions): Promise<RelayBootstrapPostRecord[]>;
223
- declare function selectCurrentRelayBootstrapPosts(posts: readonly RelayBootstrapPostRecord[], options?: Pick<DiscoverAlephBootstrapOptions, "maxAgeMs"> & {
232
+ /**
233
+ * Why this post cannot be used, or `null` if it can.
234
+ *
235
+ * This used to throw, and one record of the wrong type took the whole page
236
+ * with it. The channel is public and append-only: anybody with an ETH key can
237
+ * publish into it, nobody can FORGET somebody else's post, and every consumer
238
+ * polls the same page. So a single legacy record — not ours, not removable —
239
+ * would blind discovery for everyone, permanently.
240
+ *
241
+ * The Rust client learned this the same way and changed the same shape: after
242
+ * a production incident on 2026-08-30, `get_messages_iterator` was page-strict
243
+ * and one message the validator rejected failed every poll, leaving the
244
+ * scheduler unable to see new v-programs at all (aleph-im/aleph-rs#386). The
245
+ * answer there was to yield per message and carry on; the answer here is to
246
+ * skip the record and say which one it was.
247
+ */
248
+ declare function unsupportedRelayBootstrapReason(post: RelayBootstrapPostRecord): string | null;
249
+ declare function selectCurrentRelayBootstrapPosts(posts: readonly RelayBootstrapPostRecord[], options?: Pick<DiscoverAlephBootstrapOptions, "maxAgeMs" | "onUnsupportedPost"> & {
224
250
  now?: number;
225
251
  }): RelayBootstrapPostRecord[];
226
252
  /**
@@ -254,4 +280,4 @@ declare function createLibp2pAlephBootstrap(options?: DiscoverAlephBootstrapOpti
254
280
  tagName?: string;
255
281
  }): Promise<ReturnType<typeof bootstrap>>;
256
282
 
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 };
283
+ 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, unsupportedRelayBootstrapReason, verifyRelayBootstrapAuthorization, verifyRelayBootstrapDualKeyContent, verifyRelayBootstrapProof };
package/index.js CHANGED
@@ -595,20 +595,24 @@ async function fetchAlephBootstrapPosts(options = {}) {
595
595
  }
596
596
  const payload = await response.json();
597
597
  const posts = (payload.posts ?? []).map((entry) => normalizeRelayBootstrapPostRecord(entry)).filter((entry) => entry != null).sort((left, right) => compareRelayBootstrapPostRecency(right, left));
598
- posts.forEach(assertSupportedRelayBootstrapPost);
599
- return posts;
598
+ return usableRelayBootstrapPosts(posts, options.onUnsupportedPost);
600
599
  }
601
- function assertSupportedRelayBootstrapPost(post) {
600
+ function unsupportedRelayBootstrapReason(post) {
602
601
  if (post.type === "relay-bootstrap") {
603
- throw new Error(
604
- "Legacy relay-bootstrap record encountered. Only relay-bootstrap-v2 is supported."
605
- );
602
+ return "legacy relay-bootstrap record; only relay-bootstrap-v2 is supported";
606
603
  }
607
604
  if (post.type && post.type !== DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE) {
608
- throw new Error(
609
- `Unsupported relay bootstrap post type: ${post.type}. Expected ${DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE}.`
610
- );
605
+ return `unsupported post type ${post.type}; expected ${DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE}`;
611
606
  }
607
+ return null;
608
+ }
609
+ function usableRelayBootstrapPosts(posts, onUnsupportedPost) {
610
+ return posts.filter((post) => {
611
+ const reason = unsupportedRelayBootstrapReason(post);
612
+ if (!reason) return true;
613
+ onUnsupportedPost?.({ hash: post.itemHash ?? post.hash, reason });
614
+ return false;
615
+ });
612
616
  }
613
617
  function compareRelayBootstrapPostRecency(left, right) {
614
618
  const leftUpdatedAt = left.content?.updatedAt ?? 0;
@@ -629,8 +633,7 @@ function selectCurrentRelayBootstrapPosts(posts, options = {}) {
629
633
  const maxAgeMs = options.maxAgeMs ?? DEFAULT_BOOTSTRAP_MAX_AGE_MS;
630
634
  const now = options.now ?? Date.now();
631
635
  const selected = /* @__PURE__ */ new Map();
632
- for (const post of posts) {
633
- assertSupportedRelayBootstrapPost(post);
636
+ for (const post of usableRelayBootstrapPosts(posts, options.onUnsupportedPost)) {
634
637
  const content = post.content;
635
638
  if (!content) continue;
636
639
  if (now - content.updatedAt > maxAgeMs) continue;
@@ -777,6 +780,7 @@ export {
777
780
  selectCurrentRelayBootstrapPosts,
778
781
  signRelayBootstrapAuthorization,
779
782
  signRelayBootstrapProof,
783
+ unsupportedRelayBootstrapReason,
780
784
  verifyRelayBootstrapAuthorization,
781
785
  verifyRelayBootstrapDualKeyContent,
782
786
  verifyRelayBootstrapProof
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/aleph-bootstrap",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "Aleph-backed libp2p bootstrap discovery and relay registration helpers.",
5
5
  "license": "MIT",
6
6
  "type": "module",