@statewalker/httpeers-libp2p 0.1.3

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/dist/browser.d.ts +33 -0
  4. package/dist/browser.d.ts.map +1 -0
  5. package/dist/browser.js +77 -0
  6. package/dist/duplex.d.ts +86 -0
  7. package/dist/duplex.d.ts.map +1 -0
  8. package/dist/duplex.js +101 -0
  9. package/dist/hop-reserve.d.ts +105 -0
  10. package/dist/hop-reserve.d.ts.map +1 -0
  11. package/dist/hop-reserve.js +269 -0
  12. package/dist/hub-link.d.ts +131 -0
  13. package/dist/hub-link.d.ts.map +1 -0
  14. package/dist/hub-link.js +217 -0
  15. package/dist/hub-relay.d.ts +129 -0
  16. package/dist/hub-relay.d.ts.map +1 -0
  17. package/dist/hub-relay.js +154 -0
  18. package/dist/identity.d.ts +84 -0
  19. package/dist/identity.d.ts.map +1 -0
  20. package/dist/identity.js +91 -0
  21. package/dist/index.d.ts +28 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +26 -0
  24. package/dist/link.d.ts +49 -0
  25. package/dist/link.d.ts.map +1 -0
  26. package/dist/link.js +88 -0
  27. package/dist/multiaddr-parts.d.ts +21 -0
  28. package/dist/multiaddr-parts.d.ts.map +1 -0
  29. package/dist/multiaddr-parts.js +27 -0
  30. package/dist/node.d.ts +33 -0
  31. package/dist/node.d.ts.map +1 -0
  32. package/dist/node.js +60 -0
  33. package/dist/reservation.d.ts +245 -0
  34. package/dist/reservation.d.ts.map +1 -0
  35. package/dist/reservation.js +396 -0
  36. package/dist/serve-peer.d.ts +91 -0
  37. package/dist/serve-peer.d.ts.map +1 -0
  38. package/dist/serve-peer.js +68 -0
  39. package/dist/timers.d.ts +36 -0
  40. package/dist/timers.d.ts.map +1 -0
  41. package/dist/timers.js +38 -0
  42. package/dist/transport.d.ts +314 -0
  43. package/dist/transport.d.ts.map +1 -0
  44. package/dist/transport.js +410 -0
  45. package/package.json +73 -0
  46. package/src/browser.ts +118 -0
  47. package/src/duplex.ts +172 -0
  48. package/src/hop-reserve.ts +295 -0
  49. package/src/hub-link.ts +281 -0
  50. package/src/hub-relay.ts +188 -0
  51. package/src/identity.ts +155 -0
  52. package/src/index.ts +114 -0
  53. package/src/link.ts +143 -0
  54. package/src/multiaddr-parts.ts +29 -0
  55. package/src/node.ts +63 -0
  56. package/src/reservation.ts +564 -0
  57. package/src/serve-peer.ts +141 -0
  58. package/src/timers.ts +47 -0
  59. package/src/transport.ts +538 -0
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The hub as a relay for its own members — signalling only, members only.
3
+ *
4
+ * WHY A HUB RELAYS AT ALL. Members reach each other through their hub rather
5
+ * than through the public relay: the hub is the one peer every member is
6
+ * already connected to and heartbeating against, so it can carry the few
7
+ * kilobytes of WebRTC offer/answer between two members, after which they talk
8
+ * directly. The public relay is then needed only to reach the hub itself.
9
+ *
10
+ * MEMBERS ONLY. `membershipGater` refuses a reservation from a non-member and
11
+ * refuses to relay unless BOTH ends are members. A non-member can still
12
+ * *connect* to a hub — it must, to redeem an invitation — it simply cannot use
13
+ * the hub to reach anyone else. The public relay, by contrast, connects anyone
14
+ * to any reserved peer whose id they know.
15
+ *
16
+ * SIGNALLING ONLY, ENFORCED RATHER THAN HOPED FOR. `hubRelayService` keeps
17
+ * libp2p's default per-circuit limits (128 KiB, 2 min), sized for an SDP
18
+ * exchange. A circuit carrying limits is a LIMITED connection, and libp2p
19
+ * refuses to open an application protocol on one — so application data cannot
20
+ * cross a hub by accident. When two members cannot connect directly, WebRTC
21
+ * falls back to TURN, never to the hub.
22
+ *
23
+ * Opening a protocol with `runOnLimitedConnection` would bypass that, and the
24
+ * prototype measured the result: a 1 MiB transfer silently cut at 112 KiB.
25
+ * **Nothing may set it for application traffic.**
26
+ *
27
+ * HOW MANY, AND FOR HOW LONG -- a different question from how much. The data
28
+ * limits above bound what one CIRCUIT may carry. The reservation store bounds
29
+ * how many members may be REACHABLE through the hub at once, and libp2p's
30
+ * default for it (15, each held for two hours, kept after its holder hangs
31
+ * up) is sized for a public relay, not for a hub. A hub on that default
32
+ * filled after fifteen distinct peers in two hours -- test runs, revoked
33
+ * members, a member whose page was reopened -- and then refused every member
34
+ * with `RESERVATION_REFUSED`, which a member reports as a failed join. So:
35
+ *
36
+ * - the store is sized for a mesh (`HUB_MAX_RESERVATIONS`), which is safe
37
+ * because only members can take a slot;
38
+ * - a reservation is released when its holder's last connection closes
39
+ * (it is useless from then on: libp2p relays only to a CONNECTED
40
+ * reservation holder, and a member re-reserves when it comes back);
41
+ * - `releaseReservation` lets the hub release a REVOKED member's
42
+ * reservation at once, rather than leave the slot held until expiry.
43
+ */
44
+ import { type CircuitRelayServerComponents, type CircuitRelayService } from "@libp2p/circuit-relay-v2";
45
+ import type { ConnectionGater, Libp2pEvents, PeerId, TypedEventTarget } from "@libp2p/interface";
46
+ import type { PeerIdStr } from "@statewalker/httpeers-core";
47
+ /** Is this peer a member of our mesh? Read LIVE — see `membershipGater`. */
48
+ export type IsMember = (peerId: PeerIdStr) => boolean;
49
+ /**
50
+ * A gater that admits only members to the relay.
51
+ *
52
+ * TAKES A THUNK, and that is not a style preference — it breaks a genuine
53
+ * ordering cycle. The gater is needed to CREATE the node; the answer to "is
54
+ * this peer a member" comes from the hub's member store; and the hub cannot be
55
+ * built until the node exists. Passing `isMember` directly forces a caller to
56
+ * resolve that cycle themselves, and the prototype's two profiles each did it
57
+ * differently.
58
+ *
59
+ * A thunk defers the lookup to the moment a decision is actually needed, by
60
+ * which time the hub exists. It also means a revocation takes effect on the
61
+ * next relay request rather than at the next restart, because nothing is
62
+ * captured.
63
+ */
64
+ export declare function membershipGater(isMember: () => IsMember): ConnectionGater;
65
+ /**
66
+ * How many members may hold a reservation on a hub at once: one per member
67
+ * currently connected to it.
68
+ *
69
+ * WHY THIS NUMBER. It is a ceiling, not an allocation -- an empty slot costs
70
+ * nothing, and a held one is a map entry of a few hundred bytes, so 4096 of
71
+ * them is about a megabyte. Only members can take a slot (`membershipGater`),
72
+ * and a slot is given back when its holder disconnects, so what this really
73
+ * bounds is simultaneously connected members; a hub serving thousands of
74
+ * members at once has other limits to meet first (libp2p's connection
75
+ * manager, the hub's own CPU). libp2p's default, 15, is sized for a public
76
+ * relay that anyone can fill, and is the value that broke a real hub.
77
+ *
78
+ * NOT RAISED TO "UNLIMITED", because the gater is the only thing between a
79
+ * large member list and this map; a finite ceiling keeps a runaway (a bug
80
+ * that mints members, say) from growing it without bound.
81
+ */
82
+ export declare const HUB_MAX_RESERVATIONS = 4096;
83
+ export interface HubRelayServiceInit {
84
+ /**
85
+ * The reservation store's size (default `HUB_MAX_RESERVATIONS`). How MANY
86
+ * members can be reached through the hub -- not how much data may cross it,
87
+ * which stays at libp2p's signalling-sized default whatever this is.
88
+ */
89
+ maxReservations?: number;
90
+ }
91
+ /**
92
+ * What the hub relay needs from the node: the relay server's own components,
93
+ * plus the node's event bus, to hear a holder hang up. Both are standard
94
+ * libp2p components; `createLibp2p` hands every service all of them.
95
+ */
96
+ export type HubRelayComponents = CircuitRelayServerComponents & {
97
+ events: TypedEventTarget<Libp2pEvents>;
98
+ };
99
+ /**
100
+ * The circuit-relay server a hub runs for its members.
101
+ *
102
+ * The default DATA limits are stated explicitly rather than left implicit, so
103
+ * that nobody raises them to "make it smoother": they are the thing that keeps
104
+ * a hub a signalling channel instead of a data path. The store's SIZE is a
105
+ * separate setting (`init.maxReservations`) and changes none of that.
106
+ *
107
+ * RELEASES ON DISCONNECT. libp2p keeps a reservation for its full TTL after
108
+ * the holder is gone. This listens for `peer:disconnect` -- which libp2p
109
+ * fires when a peer's LAST connection closes -- and releases the reservation
110
+ * then, rechecking that no connection came back in the meantime.
111
+ */
112
+ export declare function hubRelayService(init?: HubRelayServiceInit): (components: HubRelayComponents) => CircuitRelayService;
113
+ /**
114
+ * Release a peer's reservation on this hub's relay, now. Returns whether there
115
+ * was one. The daemon calls it when it revokes a member; the membership gater
116
+ * already refuses that peer a NEW reservation, and this frees the one it held.
117
+ *
118
+ * THROUGH THE SERVICE'S PUBLIC SURFACE. libp2p offers no "remove reservation"
119
+ * call, but `CircuitRelayService.reservations` is a public map and each entry
120
+ * carries its expiry `signal`. Clearing that signal before removing the entry
121
+ * matters: the store's own expiry listener deletes by PEER, so a timer left
122
+ * running would, two hours later, delete whatever reservation the same peer
123
+ * holds by then.
124
+ *
125
+ * A STRING THAT IS NOT A PEER ID holds no reservation, so it returns false
126
+ * rather than throwing: a revocation must not fail half-way over it.
127
+ */
128
+ export declare function releaseReservation(relay: Pick<CircuitRelayService, "reservations">, peer: PeerId | PeerIdStr): boolean;
129
+ //# sourceMappingURL=hub-relay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hub-relay.d.ts","sourceRoot":"","sources":["../src/hub-relay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,OAAO,EACL,KAAK,4BAA4B,EACjC,KAAK,mBAAmB,EAEzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEjG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAE5D,4EAA4E;AAC5E,MAAM,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC;AAEtD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,QAAQ,GAAG,eAAe,CAUzE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AAEzC,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,4BAA4B,GAAG;IAC9D,MAAM,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAC;CACxC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,IAAI,GAAE,mBAAwB,GAC7B,CAAC,UAAU,EAAE,kBAAkB,KAAK,mBAAmB,CAgBzD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,IAAI,CAAC,mBAAmB,EAAE,cAAc,CAAC,EAChD,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAQT"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The hub as a relay for its own members — signalling only, members only.
3
+ *
4
+ * WHY A HUB RELAYS AT ALL. Members reach each other through their hub rather
5
+ * than through the public relay: the hub is the one peer every member is
6
+ * already connected to and heartbeating against, so it can carry the few
7
+ * kilobytes of WebRTC offer/answer between two members, after which they talk
8
+ * directly. The public relay is then needed only to reach the hub itself.
9
+ *
10
+ * MEMBERS ONLY. `membershipGater` refuses a reservation from a non-member and
11
+ * refuses to relay unless BOTH ends are members. A non-member can still
12
+ * *connect* to a hub — it must, to redeem an invitation — it simply cannot use
13
+ * the hub to reach anyone else. The public relay, by contrast, connects anyone
14
+ * to any reserved peer whose id they know.
15
+ *
16
+ * SIGNALLING ONLY, ENFORCED RATHER THAN HOPED FOR. `hubRelayService` keeps
17
+ * libp2p's default per-circuit limits (128 KiB, 2 min), sized for an SDP
18
+ * exchange. A circuit carrying limits is a LIMITED connection, and libp2p
19
+ * refuses to open an application protocol on one — so application data cannot
20
+ * cross a hub by accident. When two members cannot connect directly, WebRTC
21
+ * falls back to TURN, never to the hub.
22
+ *
23
+ * Opening a protocol with `runOnLimitedConnection` would bypass that, and the
24
+ * prototype measured the result: a 1 MiB transfer silently cut at 112 KiB.
25
+ * **Nothing may set it for application traffic.**
26
+ *
27
+ * HOW MANY, AND FOR HOW LONG -- a different question from how much. The data
28
+ * limits above bound what one CIRCUIT may carry. The reservation store bounds
29
+ * how many members may be REACHABLE through the hub at once, and libp2p's
30
+ * default for it (15, each held for two hours, kept after its holder hangs
31
+ * up) is sized for a public relay, not for a hub. A hub on that default
32
+ * filled after fifteen distinct peers in two hours -- test runs, revoked
33
+ * members, a member whose page was reopened -- and then refused every member
34
+ * with `RESERVATION_REFUSED`, which a member reports as a failed join. So:
35
+ *
36
+ * - the store is sized for a mesh (`HUB_MAX_RESERVATIONS`), which is safe
37
+ * because only members can take a slot;
38
+ * - a reservation is released when its holder's last connection closes
39
+ * (it is useless from then on: libp2p relays only to a CONNECTED
40
+ * reservation holder, and a member re-reserves when it comes back);
41
+ * - `releaseReservation` lets the hub release a REVOKED member's
42
+ * reservation at once, rather than leave the slot held until expiry.
43
+ */
44
+ import { circuitRelayServer, } from "@libp2p/circuit-relay-v2";
45
+ import { peerIdFromString } from "@libp2p/peer-id";
46
+ /**
47
+ * A gater that admits only members to the relay.
48
+ *
49
+ * TAKES A THUNK, and that is not a style preference — it breaks a genuine
50
+ * ordering cycle. The gater is needed to CREATE the node; the answer to "is
51
+ * this peer a member" comes from the hub's member store; and the hub cannot be
52
+ * built until the node exists. Passing `isMember` directly forces a caller to
53
+ * resolve that cycle themselves, and the prototype's two profiles each did it
54
+ * differently.
55
+ *
56
+ * A thunk defers the lookup to the moment a decision is actually needed, by
57
+ * which time the hub exists. It also means a revocation takes effect on the
58
+ * next relay request rather than at the next restart, because nothing is
59
+ * captured.
60
+ */
61
+ export function membershipGater(isMember) {
62
+ return {
63
+ denyInboundRelayReservation: async (source) => !isMember()(source.toString()),
64
+ denyOutboundRelayedConnection: async (source, destination) => {
65
+ const member = isMember();
66
+ // BOTH ends. A member must not be able to use the hub to reach a
67
+ // stranger any more than a stranger may use it to reach a member.
68
+ return !member(source.toString()) || !member(destination.toString());
69
+ },
70
+ };
71
+ }
72
+ /**
73
+ * How many members may hold a reservation on a hub at once: one per member
74
+ * currently connected to it.
75
+ *
76
+ * WHY THIS NUMBER. It is a ceiling, not an allocation -- an empty slot costs
77
+ * nothing, and a held one is a map entry of a few hundred bytes, so 4096 of
78
+ * them is about a megabyte. Only members can take a slot (`membershipGater`),
79
+ * and a slot is given back when its holder disconnects, so what this really
80
+ * bounds is simultaneously connected members; a hub serving thousands of
81
+ * members at once has other limits to meet first (libp2p's connection
82
+ * manager, the hub's own CPU). libp2p's default, 15, is sized for a public
83
+ * relay that anyone can fill, and is the value that broke a real hub.
84
+ *
85
+ * NOT RAISED TO "UNLIMITED", because the gater is the only thing between a
86
+ * large member list and this map; a finite ceiling keeps a runaway (a bug
87
+ * that mints members, say) from growing it without bound.
88
+ */
89
+ export const HUB_MAX_RESERVATIONS = 4096;
90
+ /**
91
+ * The circuit-relay server a hub runs for its members.
92
+ *
93
+ * The default DATA limits are stated explicitly rather than left implicit, so
94
+ * that nobody raises them to "make it smoother": they are the thing that keeps
95
+ * a hub a signalling channel instead of a data path. The store's SIZE is a
96
+ * separate setting (`init.maxReservations`) and changes none of that.
97
+ *
98
+ * RELEASES ON DISCONNECT. libp2p keeps a reservation for its full TTL after
99
+ * the holder is gone. This listens for `peer:disconnect` -- which libp2p
100
+ * fires when a peer's LAST connection closes -- and releases the reservation
101
+ * then, rechecking that no connection came back in the meantime.
102
+ */
103
+ export function hubRelayService(init = {}) {
104
+ const createServer = circuitRelayServer({
105
+ reservations: {
106
+ applyDefaultLimit: true,
107
+ maxReservations: init.maxReservations ?? HUB_MAX_RESERVATIONS,
108
+ },
109
+ });
110
+ return (components) => {
111
+ const relay = createServer(components);
112
+ components.events.addEventListener("peer:disconnect", (event) => {
113
+ const peer = event.detail;
114
+ if (components.connectionManager.getConnections(peer).length > 0)
115
+ return;
116
+ releaseReservation(relay, peer);
117
+ });
118
+ return relay;
119
+ };
120
+ }
121
+ /**
122
+ * Release a peer's reservation on this hub's relay, now. Returns whether there
123
+ * was one. The daemon calls it when it revokes a member; the membership gater
124
+ * already refuses that peer a NEW reservation, and this frees the one it held.
125
+ *
126
+ * THROUGH THE SERVICE'S PUBLIC SURFACE. libp2p offers no "remove reservation"
127
+ * call, but `CircuitRelayService.reservations` is a public map and each entry
128
+ * carries its expiry `signal`. Clearing that signal before removing the entry
129
+ * matters: the store's own expiry listener deletes by PEER, so a timer left
130
+ * running would, two hours later, delete whatever reservation the same peer
131
+ * holds by then.
132
+ *
133
+ * A STRING THAT IS NOT A PEER ID holds no reservation, so it returns false
134
+ * rather than throwing: a revocation must not fail half-way over it.
135
+ */
136
+ export function releaseReservation(relay, peer) {
137
+ const peerId = typeof peer === "string" ? parsePeerId(peer) : peer;
138
+ if (peerId == null)
139
+ return false;
140
+ const reservation = relay.reservations.get(peerId);
141
+ if (reservation == null)
142
+ return false;
143
+ reservation.signal.clear();
144
+ relay.reservations.delete(peerId);
145
+ return true;
146
+ }
147
+ function parsePeerId(peer) {
148
+ try {
149
+ return peerIdFromString(peer);
150
+ }
151
+ catch {
152
+ return undefined;
153
+ }
154
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Identity, before a node exists.
3
+ *
4
+ * WHY THIS IS ITS OWN MODULE. Every process and every page in the mesh needs a
5
+ * peerId *before* it can start a node: a hub's peerId is the mesh identity that
6
+ * every token's `mesh` claim restates, a relay's is baked into the multiaddr
7
+ * its members dial, and a member's is what the hub records. The prototype's
8
+ * three assemblies each derived one their own way, and revision 1 of the
9
+ * extraction exported none of it.
10
+ *
11
+ * ED25519 ONLY, and never branching on key type. The reason is the one
12
+ * `httpeers-access` is built on: an Ed25519 peerId inlines its own public key,
13
+ * so verification never needs a fetch. A key of any other type would produce a
14
+ * peerId that cannot self-certify, and every token issued under it would be
15
+ * unverifiable — so `decodeKey` refuses one rather than carrying it forward.
16
+ *
17
+ * IDEMPOTENCE IS THE WHOLE POINT of `identityStore`. A store that quietly
18
+ * re-generated a key on a second read would silently re-found the mesh: every
19
+ * token already issued stops verifying, every policy naming the issuer goes
20
+ * stale, and nothing reports an error. There is no path in `loadOrCreate` that
21
+ * writes when a key is already present.
22
+ */
23
+ import type { Ed25519PrivateKey, Ed25519PublicKey } from "@libp2p/interface";
24
+ import type { PeerIdStr } from "@statewalker/httpeers-core";
25
+ export interface GenerateKeyInit {
26
+ /**
27
+ * Derive the key deterministically instead of drawing from the CSPRNG, so a
28
+ * test or a CI run can name a peerId as a constant. Exactly 32 bytes.
29
+ */
30
+ seed?: Uint8Array;
31
+ }
32
+ export declare function generateKey(init?: GenerateKeyInit): Promise<Ed25519PrivateKey>;
33
+ /** The peerId a key identifies as — the mesh id, for a hub's key. */
34
+ export declare function peerIdOf(key: Ed25519PrivateKey | Ed25519PublicKey): PeerIdStr;
35
+ /**
36
+ * The libp2p protobuf encoding — the format both platforms already persist and
37
+ * the relay already reads back. Not a format chosen here; see the prototype's
38
+ * `setup/keys.ts` for the contract it exists to satisfy.
39
+ */
40
+ export declare function encodeKey(key: Ed25519PrivateKey): Uint8Array;
41
+ /** Decode a stored key, refusing anything that is not Ed25519 **and saying what it was**. */
42
+ export declare function decodeKey(bytes: Uint8Array): Ed25519PrivateKey;
43
+ /**
44
+ * The bridge to `@statewalker/httpeers-access`.
45
+ *
46
+ * Declared STRUCTURALLY rather than imported, so this package does not depend
47
+ * on `httpeers-access` — the dependency graph runs core → access and
48
+ * core → libp2p, and the two never point at each other. The shape is
49
+ * `access`'s `Signer`, and a mismatch would be a compile error at whichever
50
+ * assembly wires them together, which is where it belongs.
51
+ */
52
+ export interface SignerLike {
53
+ readonly mesh: string;
54
+ readonly seed: Uint8Array;
55
+ }
56
+ /**
57
+ * A key, as something that can mint.
58
+ *
59
+ * A libp2p Ed25519 private key's `raw` is the 64-byte expanded form — the
60
+ * 32-byte seed followed by the public key — and Biscuit signs from the seed
61
+ * alone.
62
+ */
63
+ export declare function signerOf(key: Ed25519PrivateKey): SignerLike;
64
+ /** Somewhere bytes live. A file on a server, IndexedDB in a page. */
65
+ export interface BytesStore {
66
+ get(key: string): Promise<Uint8Array | undefined>;
67
+ set(key: string, value: Uint8Array): Promise<void>;
68
+ delete(key: string): Promise<void>;
69
+ }
70
+ export interface IdentityStoreInit {
71
+ backend: BytesStore;
72
+ /** Which entry in the backend holds it. Defaults to `"identity"`. */
73
+ key?: string;
74
+ }
75
+ export interface IdentityStore {
76
+ /** The stored key, or `null`. Never generates. */
77
+ read(): Promise<Ed25519PrivateKey | null>;
78
+ /** The stored key, generating and persisting one only if there is none. */
79
+ loadOrCreate(): Promise<Ed25519PrivateKey>;
80
+ /** Forget it. The next `loadOrCreate` founds a NEW identity — see the module comment. */
81
+ clear(): Promise<void>;
82
+ }
83
+ export declare function identityStore(init: IdentityStoreInit): IdentityStore;
84
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAQH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAK5D,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,wBAAsB,WAAW,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CASxF;AAED,qEAAqE;AACrE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,SAAS,CAI7E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,UAAU,CAE5D;AAED,6FAA6F;AAC7F,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,GAAG,iBAAiB,CAS9D;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,iBAAiB,GAAG,UAAU,CAE3D;AAED,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAAC;IAClD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,UAAU,CAAC;IACpB,qEAAqE;IACrE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,IAAI,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC1C,2EAA2E;IAC3E,YAAY,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC3C,yFAAyF;IACzF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAwBpE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Identity, before a node exists.
3
+ *
4
+ * WHY THIS IS ITS OWN MODULE. Every process and every page in the mesh needs a
5
+ * peerId *before* it can start a node: a hub's peerId is the mesh identity that
6
+ * every token's `mesh` claim restates, a relay's is baked into the multiaddr
7
+ * its members dial, and a member's is what the hub records. The prototype's
8
+ * three assemblies each derived one their own way, and revision 1 of the
9
+ * extraction exported none of it.
10
+ *
11
+ * ED25519 ONLY, and never branching on key type. The reason is the one
12
+ * `httpeers-access` is built on: an Ed25519 peerId inlines its own public key,
13
+ * so verification never needs a fetch. A key of any other type would produce a
14
+ * peerId that cannot self-certify, and every token issued under it would be
15
+ * unverifiable — so `decodeKey` refuses one rather than carrying it forward.
16
+ *
17
+ * IDEMPOTENCE IS THE WHOLE POINT of `identityStore`. A store that quietly
18
+ * re-generated a key on a second read would silently re-found the mesh: every
19
+ * token already issued stops verifying, every policy naming the issuer goes
20
+ * stale, and nothing reports an error. There is no path in `loadOrCreate` that
21
+ * writes when a key is already present.
22
+ */
23
+ import { generateKeyPair, generateKeyPairFromSeed, privateKeyFromProtobuf, privateKeyToProtobuf, } from "@libp2p/crypto/keys";
24
+ import { peerIdFromPrivateKey, peerIdFromPublicKey } from "@libp2p/peer-id";
25
+ /** `generateKeyPairFromSeed("Ed25519", seed)` requires exactly this many bytes. */
26
+ const SEED_BYTES = 32;
27
+ export async function generateKey(init = {}) {
28
+ if (init.seed === undefined)
29
+ return generateKeyPair("Ed25519");
30
+ if (init.seed.length !== SEED_BYTES) {
31
+ throw new TypeError(`generateKey: a seed must be exactly ${SEED_BYTES} bytes, got ${init.seed.length}. ` +
32
+ "Hash a passphrase to 32 bytes rather than passing it directly.");
33
+ }
34
+ return generateKeyPairFromSeed("Ed25519", init.seed);
35
+ }
36
+ /** The peerId a key identifies as — the mesh id, for a hub's key. */
37
+ export function peerIdOf(key) {
38
+ return key.type === "Ed25519" && "publicKey" in key
39
+ ? peerIdFromPrivateKey(key).toString()
40
+ : peerIdFromPublicKey(key).toString();
41
+ }
42
+ /**
43
+ * The libp2p protobuf encoding — the format both platforms already persist and
44
+ * the relay already reads back. Not a format chosen here; see the prototype's
45
+ * `setup/keys.ts` for the contract it exists to satisfy.
46
+ */
47
+ export function encodeKey(key) {
48
+ return privateKeyToProtobuf(key);
49
+ }
50
+ /** Decode a stored key, refusing anything that is not Ed25519 **and saying what it was**. */
51
+ export function decodeKey(bytes) {
52
+ const key = privateKeyFromProtobuf(bytes);
53
+ if (key.type !== "Ed25519") {
54
+ throw new TypeError(`decodeKey: expected an Ed25519 key, got ${key.type}. Only Ed25519 peerIds carry their ` +
55
+ "own public key, which is what lets a token be verified without fetching anything.");
56
+ }
57
+ return key;
58
+ }
59
+ /**
60
+ * A key, as something that can mint.
61
+ *
62
+ * A libp2p Ed25519 private key's `raw` is the 64-byte expanded form — the
63
+ * 32-byte seed followed by the public key — and Biscuit signs from the seed
64
+ * alone.
65
+ */
66
+ export function signerOf(key) {
67
+ return { mesh: peerIdOf(key), seed: key.raw.slice(0, SEED_BYTES) };
68
+ }
69
+ export function identityStore(init) {
70
+ const entry = init.key ?? "identity";
71
+ const read = async () => {
72
+ const bytes = await init.backend.get(entry);
73
+ return bytes === undefined ? null : decodeKey(bytes);
74
+ };
75
+ return {
76
+ read,
77
+ async loadOrCreate() {
78
+ const existing = await read();
79
+ // The whole point: a key that exists is READ, never regenerated and
80
+ // never overwritten. There is no write on this branch.
81
+ if (existing !== null)
82
+ return existing;
83
+ const created = await generateKey();
84
+ await init.backend.set(entry, encodeKey(created));
85
+ return created;
86
+ },
87
+ async clear() {
88
+ await init.backend.delete(entry);
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The transport — the one package in the extraction that knows what libp2p is.
3
+ *
4
+ * An explicit list, never `export *`: the barrel this code came from ended
5
+ * with `export * from "./transport-duplex.js"`, which is how a package that
6
+ * documented itself as isomorphic pulled libp2p, TCP, Noise and yamux into
7
+ * every consumer's bundle.
8
+ */
9
+ /**
10
+ * The SECOND ALTITUDE: duplex streams over the mesh.
11
+ *
12
+ * A fetch-only contract cannot express a WebSocket — both sides talking with
13
+ * neither input closed. These run one `Duplex` per libp2p stream, addressed by
14
+ * `(peerId, path)`, with identity captured per stream by closure exactly as
15
+ * the fetch path does it.
16
+ */
17
+ export { createDuplexMounts, DUPLEX_PROTOCOL, type DuplexContext, type DuplexHandler, type DuplexMounts, NO_MOUNT, type OpenDuplexInit, openDuplex, type PeerDuplex, type ServeDuplexInit, serveDuplex, } from "./duplex.js";
18
+ export { decodeHopAnswer, HOP_RESERVE_TIMEOUT_MS, HOP_STATUS_NAMES, HOP_STATUS_OK, type HopAnswer, RelayNotConnectedError, type RelayReservationGrant, RelayReservationRefusedError, requestRelayReservation, } from "./hop-reserve.js";
19
+ export { HubReservationError, type HubReservationRefusal, hubRoute, leaveRelay, reachHub, reachHubRelayed, reserveOnHub, type SuperviseHubReservationInit, superviseHubReservation, } from "./hub-link.js";
20
+ export { HUB_MAX_RESERVATIONS, type HubRelayComponents, type HubRelayServiceInit, hubRelayService, type IsMember, membershipGater, releaseReservation, } from "./hub-relay.js";
21
+ export { type BytesStore, decodeKey, encodeKey, type GenerateKeyInit, generateKey, type IdentityStore, type IdentityStoreInit, identityStore, peerIdOf, type SignerLike, signerOf, } from "./identity.js";
22
+ export type { CallOnLimitedConnection } from "./link.js";
23
+ export { lastPeerIdOf } from "./multiaddr-parts.js";
24
+ export { type CircuitAddrs, circuitAddrs, DEFAULT_RENEWAL_INTERVAL_MS, dialRelay, MAX_RENEWAL_INTERVAL_MS, MIN_RENEWAL_INTERVAL_MS, RESERVATION_LOSS_GRACE_MS, RESERVATION_POLL_ATTEMPTS, RESERVATION_POLL_INTERVAL_MS, type RelayLog, type RelayReservationEvent, type RelayReservationState, type RelayReservationStatus, type RelaySupervisor, renewalDelayMs, renewalIntervalMs, reservationHealthy, retryDelayMs, type SuperviseRelayInit, superviseRelay, type WaitForCircuitReservationInit, waitForCircuitReservation, } from "./reservation.js";
25
+ export { type Peer, type ServePeerInit, servePeer } from "./serve-peer.js";
26
+ export { globalTimers, type TimerHandle, type Timers } from "./timers.js";
27
+ export { type CreateNodeInit, type CreateRemoteInit, createNode, createRemote, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_MAX_STREAMS, PROTOCOL, type ServeTransportInit, serveTransport, type TransportFactory, } from "./transport.js";
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,QAAQ,EACR,KAAK,cAAc,EACnB,UAAU,EACV,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACb,KAAK,SAAS,EACd,sBAAsB,EACtB,KAAK,qBAAqB,EAC1B,4BAA4B,EAC5B,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,KAAK,2BAA2B,EAChC,uBAAuB,GACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,eAAe,EACf,KAAK,QAAQ,EACb,eAAe,EACf,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,UAAU,EACf,SAAS,EACT,SAAS,EACT,KAAK,eAAe,EACpB,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,aAAa,EACb,QAAQ,EACR,KAAK,UAAU,EACf,QAAQ,GACT,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EACL,KAAK,YAAY,EACjB,YAAY,EACZ,2BAA2B,EAC3B,SAAS,EACT,uBAAuB,EACvB,uBAAuB,EACvB,yBAAyB,EACzB,yBAAyB,EACzB,4BAA4B,EAC5B,KAAK,QAAQ,EACb,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACpB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,KAAK,kBAAkB,EACvB,cAAc,EACd,KAAK,6BAA6B,EAClC,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,UAAU,EACV,YAAY,EACZ,wBAAwB,EACxB,mBAAmB,EACnB,QAAQ,EACR,KAAK,kBAAkB,EACvB,cAAc,EACd,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The transport — the one package in the extraction that knows what libp2p is.
3
+ *
4
+ * An explicit list, never `export *`: the barrel this code came from ended
5
+ * with `export * from "./transport-duplex.js"`, which is how a package that
6
+ * documented itself as isomorphic pulled libp2p, TCP, Noise and yamux into
7
+ * every consumer's bundle.
8
+ */
9
+ /**
10
+ * The SECOND ALTITUDE: duplex streams over the mesh.
11
+ *
12
+ * A fetch-only contract cannot express a WebSocket — both sides talking with
13
+ * neither input closed. These run one `Duplex` per libp2p stream, addressed by
14
+ * `(peerId, path)`, with identity captured per stream by closure exactly as
15
+ * the fetch path does it.
16
+ */
17
+ export { createDuplexMounts, DUPLEX_PROTOCOL, NO_MOUNT, openDuplex, serveDuplex, } from "./duplex.js";
18
+ export { decodeHopAnswer, HOP_RESERVE_TIMEOUT_MS, HOP_STATUS_NAMES, HOP_STATUS_OK, RelayNotConnectedError, RelayReservationRefusedError, requestRelayReservation, } from "./hop-reserve.js";
19
+ export { HubReservationError, hubRoute, leaveRelay, reachHub, reachHubRelayed, reserveOnHub, superviseHubReservation, } from "./hub-link.js";
20
+ export { HUB_MAX_RESERVATIONS, hubRelayService, membershipGater, releaseReservation, } from "./hub-relay.js";
21
+ export { decodeKey, encodeKey, generateKey, identityStore, peerIdOf, signerOf, } from "./identity.js";
22
+ export { lastPeerIdOf } from "./multiaddr-parts.js";
23
+ export { circuitAddrs, DEFAULT_RENEWAL_INTERVAL_MS, dialRelay, MAX_RENEWAL_INTERVAL_MS, MIN_RENEWAL_INTERVAL_MS, RESERVATION_LOSS_GRACE_MS, RESERVATION_POLL_ATTEMPTS, RESERVATION_POLL_INTERVAL_MS, renewalDelayMs, renewalIntervalMs, reservationHealthy, retryDelayMs, superviseRelay, waitForCircuitReservation, } from "./reservation.js";
24
+ export { servePeer } from "./serve-peer.js";
25
+ export { globalTimers } from "./timers.js";
26
+ export { createNode, createRemote, DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_MAX_STREAMS, PROTOCOL, serveTransport, } from "./transport.js";
package/dist/link.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * libp2p, as a `PeerLink`.
3
+ *
4
+ * This is the whole of what makes the bridge libp2p-specific: how to open a
5
+ * duplex to a peer, and how to accept one. Everything the bridge does with
6
+ * those duplexes -- binding the proven peer, one stream per call, the permit,
7
+ * the timeout -- lives in `@statewalker/httpeers-bridge` and is shared with
8
+ * every other transport, including the MessagePort one the tests use.
9
+ *
10
+ * `serveConnections` is used rather than `node.handle` by hand because it
11
+ * already threads a `ConnectionContext`, and `context.remotePeer` is the
12
+ * answer the Noise handshake established. That value is what the bridge binds
13
+ * to every inbound request, and it is the reason a peer cannot lie about who
14
+ * it is no matter what headers it sets.
15
+ */
16
+ import type { Libp2p } from "@libp2p/interface";
17
+ import type { PeerLink } from "@statewalker/httpeers-bridge";
18
+ import type { PeerIdStr } from "@statewalker/httpeers-core";
19
+ export interface Libp2pLinkInit {
20
+ node: Libp2p;
21
+ /** Defaults to `PROTOCOL`. Two meshes on one node need two protocols. */
22
+ protocol?: string;
23
+ drainTimeoutMs?: number;
24
+ maxInboundStreams?: number;
25
+ maxOutboundStreams?: number;
26
+ /**
27
+ * ACCEPT streams on LIMITED connections (a relay circuit). Off by default:
28
+ * libp2p refuses, and that refusal is what keeps application traffic off a
29
+ * relay's small budget. A hub reached over the public relay sets it; a member
30
+ * never does, so its hub's relay limits still bound member-to-member traffic.
31
+ */
32
+ serveOnLimitedConnection?: boolean;
33
+ /** OPEN streams on limited connections, to the peers this allows. See `CallOnLimitedConnection`. */
34
+ callOnLimitedConnection?: CallOnLimitedConnection;
35
+ }
36
+ /**
37
+ * Which peers a call may reach over a LIMITED connection: `true` for all of
38
+ * them, or a predicate evaluated per target when the call opens its stream.
39
+ *
40
+ * A PREDICATE, NOT A BOOLEAN, because the one caller that needs it -- a member
41
+ * whose hub is reachable only through the relay -- must say "the hub" and
42
+ * nothing wider: `(peerId) => peerId === hubPeerId`. Separate from
43
+ * `serveOnLimitedConnection` for the same reason: a member calls its hub over
44
+ * the circuit without opening its own serving side to circuits.
45
+ */
46
+ export type CallOnLimitedConnection = boolean | ((peerId: PeerIdStr) => boolean);
47
+ /** Wrap a running libp2p node as the one seam the bridge needs. */
48
+ export declare function libp2pLink(init: Libp2pLinkInit): PeerLink;
49
+ //# sourceMappingURL=link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAGhD,OAAO,KAAK,EAAkB,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,4BAA4B,CAAC;AASxE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,oGAAoG;IACpG,uBAAuB,CAAC,EAAE,uBAAuB,CAAC;CACnD;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC,CAAC;AAEjF,mEAAmE;AACnE,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,GAAG,QAAQ,CA6CzD"}