@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,141 @@
1
+ /**
2
+ * A mount table, served to the mesh.
3
+ *
4
+ * THE SEAM THREE ASSEMBLIES COLLAPSE INTO. The prototype builds a peer three
5
+ * times — the Node hub, the browser hub and the test hub — each wiring a node,
6
+ * a reservation, identity registration, a router and mounts in its own order,
7
+ * and the coverage audit found that the one symbol every assembly turns on was
8
+ * exported nowhere. This is it.
9
+ *
10
+ * WHAT IT DOES NOT DO, and the omission is the design. It does not verify a
11
+ * token, evaluate a policy, or know what a hub is. `access` is a middleware
12
+ * the caller supplies — `withAccess` from `@statewalker/httpeers-access`, or
13
+ * nothing at all for a peer that trusts its transport — because this package
14
+ * must not learn about Biscuit and `httpeers-access` must not learn about
15
+ * libp2p. They meet at a `FetchHandler`, which is the only contract either
16
+ * needs.
17
+ *
18
+ * IDENTITY IS REGISTERED BEFORE ANYTHING DOWNSTREAM RUNS. `serveTransport`
19
+ * gives every inbound stream its own `Duplex` closing over that stream's
20
+ * Noise-proven `remotePeer`, and calls `registerPeer` on the request before
21
+ * the handler sees it. That ordering is the whole basis of the binding check:
22
+ * a token's subject is compared against what the TRANSPORT proved, never
23
+ * against anything the caller said.
24
+ */
25
+
26
+ import type { Libp2p } from "@libp2p/interface";
27
+ import type { FetchHandler, Mounts, PeerIdStr } from "@statewalker/httpeers-core";
28
+ import { createPeerRouter } from "@statewalker/httpeers-core";
29
+ import type { CallOnLimitedConnection } from "./link.js";
30
+ import { createRemote, PROTOCOL, serveTransport } from "./transport.js";
31
+
32
+ export interface ServePeerInit {
33
+ node: Libp2p;
34
+ /**
35
+ * What this peer serves. A value, or a factory taking the peer's own id —
36
+ * a hub's endpoints need to know which mesh they are, and the alternative
37
+ * is for this package to learn what a hub is.
38
+ */
39
+ mounts: Mounts | ((self: PeerIdStr) => Mounts);
40
+ /**
41
+ * Everything between the transport and the mounts: identity binding and
42
+ * policy. Omitted, requests reach the mounts with only the transport's
43
+ * proof — which is a legitimate shape for a peer whose mounts are public,
44
+ * and a serious mistake for one whose are not.
45
+ *
46
+ * Passed through to the router, which applies it to LOCAL handling only.
47
+ * That distinction is load-bearing and easy to lose: wrapping the whole
48
+ * router instead would run policy over FORWARDED requests too, and an
49
+ * outbound call is not this peer's business to authorize.
50
+ */
51
+ access?: (next: FetchHandler) => FetchHandler;
52
+ /**
53
+ * May this request be forwarded to `target`? **Deny by default**, and the
54
+ * default is inherited rather than restated here.
55
+ *
56
+ * Relaying is a distinct capability, not a side effect of knowing how to
57
+ * route: without it any peer can make this one dial a third party and pump
58
+ * a stream on its behalf. The hole is invisible for a while because it
59
+ * fails CLOSED at the far end — the third party rejects the tokenless
60
+ * request, so every observable outcome looks right. The damage is the work
61
+ * done, not the answer given.
62
+ */
63
+ allowForward?: (request: Request, target: PeerIdStr) => Promise<boolean>;
64
+ protocol?: string;
65
+ /** Forwarded to the transport; see `transport.ts` for what each bounds. */
66
+ maxInboundStreams?: number;
67
+ maxOutboundStreams?: number;
68
+ drainTimeoutMs?: number;
69
+ /**
70
+ * Accept streams on limited connections (relay circuits) -- SERVING ONLY. A
71
+ * HUB sets it, so members that can reach it only through the public relay
72
+ * are served; a member never does, so the hub's relay limits still bound
73
+ * member-to-member traffic.
74
+ */
75
+ serveOnLimitedConnection?: boolean;
76
+ /**
77
+ * Open streams on limited connections to the peers this allows -- CALLING
78
+ * ONLY, and it covers every outbound path of this peer (`call`, and the
79
+ * router's forwarding behind `dispatch`). A member in relay mode passes
80
+ * `(peerId) => peerId === hubPeerId`. See `CallOnLimitedConnection`.
81
+ */
82
+ callOnLimitedConnection?: CallOnLimitedConnection;
83
+ }
84
+
85
+ export interface Peer {
86
+ peerId: PeerIdStr;
87
+ /** Every multiaddr this node is reachable on, right now. */
88
+ addrs(): string[];
89
+ /** Inbound, after identity binding and policy. Exposed so an edge can reuse it locally. */
90
+ dispatch: FetchHandler;
91
+ /** Call another peer. The request's URL path is what the far side routes on. */
92
+ call(peerId: PeerIdStr, request: Request): Promise<Response>;
93
+ stop(): Promise<void>;
94
+ }
95
+
96
+ export async function servePeer(init: ServePeerInit): Promise<Peer> {
97
+ const peerId = init.node.peerId.toString();
98
+ const protocol = init.protocol ?? PROTOCOL;
99
+
100
+ const mounts = typeof init.mounts === "function" ? init.mounts(peerId) : init.mounts;
101
+
102
+ const remote = createRemote({
103
+ node: init.node,
104
+ protocol,
105
+ maxOutboundStreams: init.maxOutboundStreams,
106
+ callOnLimitedConnection: init.callOnLimitedConnection,
107
+ });
108
+
109
+ const dispatch = createPeerRouter({
110
+ selfPeerId: peerId,
111
+ mounts,
112
+ remote,
113
+ access: init.access,
114
+ allowForward: init.allowForward,
115
+ });
116
+
117
+ const stopServing = await serveTransport({
118
+ node: init.node,
119
+ dispatch,
120
+ protocol,
121
+ maxInboundStreams: init.maxInboundStreams,
122
+ maxOutboundStreams: init.maxOutboundStreams,
123
+ drainTimeoutMs: init.drainTimeoutMs,
124
+ runOnLimitedConnection: init.serveOnLimitedConnection,
125
+ });
126
+
127
+ let stopped = false;
128
+ return {
129
+ peerId,
130
+ addrs: () => init.node.getMultiaddrs().map((a) => a.toString()),
131
+ dispatch,
132
+ call: async (target, request) => remote(target, request),
133
+ async stop() {
134
+ // Idempotent: an assembly that tears down twice — a page unloading while
135
+ // a test also cleans up — must not see the second call throw.
136
+ if (stopped) return;
137
+ stopped = true;
138
+ await stopServing();
139
+ },
140
+ };
141
+ }
package/src/timers.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The timer seam: the four calls anything long-lived in this package schedules
3
+ * with, behind an interface instead of the global functions.
4
+ *
5
+ * WHY, AND IT IS NOT TESTABILITY. A browser throttles `setTimeout` and
6
+ * `setInterval` in a background tab -- to once a minute, and on some engines
7
+ * far less -- and a hub running in a tab is a hub whose relay reservation is
8
+ * renewed on the browser's schedule rather than on the relay's. The fix is
9
+ * `worker-timers`, which schedules from a Web Worker and is not throttled; the
10
+ * fix is NOT to shorten the interval, which changes nothing about what the
11
+ * browser does to it. That wiring is deliberately not here yet (the Node hub is
12
+ * what runs today); this seam is what makes it a one-line change later:
13
+ *
14
+ * import { clearInterval, clearTimeout, setInterval, setTimeout } from "worker-timers";
15
+ * const timers: Timers = { setTimeout, clearTimeout, setInterval, clearInterval };
16
+ *
17
+ * Tests get the seam for free, and that is the cheaper of the two reasons.
18
+ *
19
+ * `TimerHandle` IS `unknown` ON PURPOSE. Node's timers return an object,
20
+ * browsers return a number, `worker-timers` returns a number of its own that
21
+ * only its own `clearTimeout` understands. An implementation must therefore
22
+ * only ever hand its OWN handles back to its OWN clear functions -- which is
23
+ * exactly what an opaque type states and a `number` alias would quietly
24
+ * permit callers to violate.
25
+ */
26
+
27
+ /** A handle from one `Timers` implementation, meaningful only to that same one. */
28
+ export type TimerHandle = unknown;
29
+
30
+ export interface Timers {
31
+ setTimeout(handler: () => void, delayMs: number): TimerHandle;
32
+ clearTimeout(handle: TimerHandle): void;
33
+ setInterval(handler: () => void, delayMs: number): TimerHandle;
34
+ clearInterval(handle: TimerHandle): void;
35
+ }
36
+
37
+ /** The global timers, which is what everything uses unless it is told otherwise. */
38
+ export const globalTimers: Timers = {
39
+ setTimeout: (handler, delayMs) => setTimeout(handler, delayMs),
40
+ clearTimeout: (handle) => {
41
+ if (handle !== undefined) clearTimeout(handle as ReturnType<typeof setTimeout>);
42
+ },
43
+ setInterval: (handler, delayMs) => setInterval(handler, delayMs),
44
+ clearInterval: (handle) => {
45
+ if (handle !== undefined) clearInterval(handle as ReturnType<typeof setInterval>);
46
+ },
47
+ };