@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.
- package/README.md +30 -0
- package/index.d.ts +53 -1
- package/index.js +45 -14
- 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
|
|
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
|
-
|
|
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 =
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
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,
|