@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
package/dist/link.js ADDED
@@ -0,0 +1,88 @@
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 { peerIdFromString } from "@libp2p/peer-id";
17
+ import { multiaddr } from "@multiformats/multiaddr";
18
+ import { connect, serveConnections, } from "@statewalker/webrun-streams-libp2p";
19
+ import { DEFAULT_DRAIN_TIMEOUT_MS, DEFAULT_MAX_STREAMS, PROTOCOL } from "./transport.js";
20
+ /** Wrap a running libp2p node as the one seam the bridge needs. */
21
+ export function libp2pLink(init) {
22
+ const { node, protocol = PROTOCOL, drainTimeoutMs = DEFAULT_DRAIN_TIMEOUT_MS, maxInboundStreams = DEFAULT_MAX_STREAMS, maxOutboundStreams = DEFAULT_MAX_STREAMS, serveOnLimitedConnection, callOnLimitedConnection = false, } = init;
23
+ return {
24
+ async open(peerId) {
25
+ // A BARE `/p2p/<id>` dial, deliberately: a peer dialled once by full
26
+ // multiaddr is dialable again by id alone, and composing an address here
27
+ // would duplicate the routing decisions `reservation.ts` already made.
28
+ const limited = typeof callOnLimitedConnection === "function"
29
+ ? callOnLimitedConnection(peerId)
30
+ : callOnLimitedConnection;
31
+ const conn = await connect({
32
+ node: limited ? keptCircuitFirst(node, peerId) : node,
33
+ peer: multiaddr(`/p2p/${peerId}`),
34
+ protocol,
35
+ drainTimeoutMs,
36
+ maxOutboundStreams,
37
+ ...(limited ? { runOnLimitedConnection: true } : {}),
38
+ });
39
+ return { call: conn.call, close: async () => await conn.close() };
40
+ },
41
+ async serve(handlerFor) {
42
+ return serveConnections({
43
+ node,
44
+ protocol,
45
+ drainTimeoutMs,
46
+ maxInboundStreams,
47
+ maxOutboundStreams,
48
+ runOnLimitedConnection: serveOnLimitedConnection,
49
+ }, (context) => handlerFor(context.remotePeer.toString()));
50
+ },
51
+ };
52
+ }
53
+ /**
54
+ * `node`, except that a stream to `peerId` opens on the LIMITED connection
55
+ * already held to it when that is the only kind there is. Used only for a
56
+ * target `callOnLimitedConnection` allows.
57
+ *
58
+ * WHY THE FLAG ALONE IS NOT ENOUGH. libp2p 3.3.8 does not reuse a limited
59
+ * connection for a dial: `findExistingConnection`
60
+ * (`connection-manager/utils.js`) returns only connections with
61
+ * `limits == null`, so `dialProtocol` to a peer reachable only by a kept relay
62
+ * circuit dials AGAIN, from the peer store -- a second circuit per call, through
63
+ * whichever of the relay's addresses identify taught it. The
64
+ * relay-fallback conformance test measured exactly that (two new connections
65
+ * for two requests) before this existed. The kept connection is the one the
66
+ * caller chose; the stream goes there.
67
+ *
68
+ * An UNLIMITED connection still wins when there is one (a WebRTC upgrade that
69
+ * did come up): that case is left to libp2p's own dial, which reuses it.
70
+ *
71
+ * A NARROW STAND-IN, not a wrapper: `connect` (`webrun-streams-libp2p`) reads
72
+ * nothing from the node but `dialProtocol`, and giving that package a way to
73
+ * take a `Connection` would be a change in another repository for one caller.
74
+ */
75
+ function keptCircuitFirst(node, peerId) {
76
+ const dialer = {
77
+ dialProtocol: async (peer, protocols, options) => {
78
+ const open = node.getConnections(peerIdFromString(peerId)).filter((c) => c.status === "open");
79
+ const kept = open.some((c) => c.limits == null)
80
+ ? undefined
81
+ : open.find((c) => c.limits != null);
82
+ return kept != null
83
+ ? kept.newStream(protocols, options)
84
+ : node.dialProtocol(peer, protocols, options);
85
+ },
86
+ };
87
+ return dialer;
88
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Reading a peer id out of a multiaddr, without `Array.prototype.findLast`.
3
+ *
4
+ * The prototype used `findLast`, which needs `lib: ES2023`. Raising this
5
+ * package's lib would raise the RUNTIME floor for every consumer — the emitted
6
+ * code calls the method regardless of what the types allow — for one line of
7
+ * convenience. A reverse loop costs nothing and keeps the floor at ES2022,
8
+ * matching every other package here.
9
+ *
10
+ * To reverse: set `"lib": ["ES2023"]` in tsconfig.json and inline
11
+ * `.getComponents().findLast((c) => c.name === "p2p")?.value` at the two call
12
+ * sites (`reservation.ts`, `hub-link.ts`).
13
+ *
14
+ * LAST, not first, and that is the whole point: a circuit address is
15
+ * `/…/p2p/<relay>/p2p-circuit/p2p/<target>`, so the FIRST `p2p` component is
16
+ * the relay and the last is whoever the address finally names. Taking the
17
+ * first would hang up on the relay when asked to hang up on a peer.
18
+ */
19
+ import type { Multiaddr } from "@multiformats/multiaddr";
20
+ export declare function lastPeerIdOf(addr: Multiaddr): string | undefined;
21
+ //# sourceMappingURL=multiaddr-parts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiaddr-parts.d.ts","sourceRoot":"","sources":["../src/multiaddr-parts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAOhE"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Reading a peer id out of a multiaddr, without `Array.prototype.findLast`.
3
+ *
4
+ * The prototype used `findLast`, which needs `lib: ES2023`. Raising this
5
+ * package's lib would raise the RUNTIME floor for every consumer — the emitted
6
+ * code calls the method regardless of what the types allow — for one line of
7
+ * convenience. A reverse loop costs nothing and keeps the floor at ES2022,
8
+ * matching every other package here.
9
+ *
10
+ * To reverse: set `"lib": ["ES2023"]` in tsconfig.json and inline
11
+ * `.getComponents().findLast((c) => c.name === "p2p")?.value` at the two call
12
+ * sites (`reservation.ts`, `hub-link.ts`).
13
+ *
14
+ * LAST, not first, and that is the whole point: a circuit address is
15
+ * `/…/p2p/<relay>/p2p-circuit/p2p/<target>`, so the FIRST `p2p` component is
16
+ * the relay and the last is whoever the address finally names. Taking the
17
+ * first would hang up on the relay when asked to hang up on a peer.
18
+ */
19
+ export function lastPeerIdOf(addr) {
20
+ const components = addr.getComponents();
21
+ for (let i = components.length - 1; i >= 0; i--) {
22
+ const component = components[i];
23
+ if (component?.name === "p2p")
24
+ return component.value;
25
+ }
26
+ return undefined;
27
+ }
package/dist/node.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The Node profile. Everything here is why the root export is not this.
3
+ *
4
+ * `tcp()` cannot run in a browser, and the prototype hard-coded it inside its
5
+ * node factory — so importing the transport module at all dragged a Node-only
6
+ * transport into a browser bundle, in a package whose whole claim is that one
7
+ * implementation runs on both. Separating the profiles is the fix, and the
8
+ * root's boundary test asserts `@libp2p/tcp` never appears there.
9
+ */
10
+ import type { BytesStore } from "./identity.js";
11
+ import type { TransportFactory } from "./transport.js";
12
+ /**
13
+ * Transports for a server: TCP, and nothing a page could not also do.
14
+ *
15
+ * Listed as a function rather than a constant so a caller gets fresh factory
16
+ * instances — libp2p's are not reusable across nodes.
17
+ */
18
+ export declare function nodeTransports(): TransportFactory[];
19
+ /**
20
+ * A `BytesStore` on the filesystem — where a server keeps its identity.
21
+ *
22
+ * WRITE-THEN-MOVE, not write-in-place. A key file truncated by a crash
23
+ * mid-write is not a corrupt file you notice, it is a mesh that silently
24
+ * re-founds itself on the next start: `identityStore.loadOrCreate` sees
25
+ * unreadable bytes, generates a new key, and every token the old identity
26
+ * issued stops verifying. A rename is atomic on POSIX, so the file is either
27
+ * the old key or the new one and never half of either.
28
+ *
29
+ * The prototype's snapshot store wrote in place; extraction prototype 04
30
+ * measured the consequence and this is the shape that survived it.
31
+ */
32
+ export declare function fileBytesStore(dir: string): BytesStore;
33
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../src/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,gBAAgB,EAAE,CAEnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAuBtD"}
package/dist/node.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The Node profile. Everything here is why the root export is not this.
3
+ *
4
+ * `tcp()` cannot run in a browser, and the prototype hard-coded it inside its
5
+ * node factory — so importing the transport module at all dragged a Node-only
6
+ * transport into a browser bundle, in a package whose whole claim is that one
7
+ * implementation runs on both. Separating the profiles is the fix, and the
8
+ * root's boundary test asserts `@libp2p/tcp` never appears there.
9
+ */
10
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
11
+ import { join } from "node:path";
12
+ import { tcp } from "@libp2p/tcp";
13
+ /**
14
+ * Transports for a server: TCP, and nothing a page could not also do.
15
+ *
16
+ * Listed as a function rather than a constant so a caller gets fresh factory
17
+ * instances — libp2p's are not reusable across nodes.
18
+ */
19
+ export function nodeTransports() {
20
+ return [tcp()];
21
+ }
22
+ /**
23
+ * A `BytesStore` on the filesystem — where a server keeps its identity.
24
+ *
25
+ * WRITE-THEN-MOVE, not write-in-place. A key file truncated by a crash
26
+ * mid-write is not a corrupt file you notice, it is a mesh that silently
27
+ * re-founds itself on the next start: `identityStore.loadOrCreate` sees
28
+ * unreadable bytes, generates a new key, and every token the old identity
29
+ * issued stops verifying. A rename is atomic on POSIX, so the file is either
30
+ * the old key or the new one and never half of either.
31
+ *
32
+ * The prototype's snapshot store wrote in place; extraction prototype 04
33
+ * measured the consequence and this is the shape that survived it.
34
+ */
35
+ export function fileBytesStore(dir) {
36
+ return {
37
+ async get(key) {
38
+ try {
39
+ return new Uint8Array(await readFile(join(dir, key)));
40
+ }
41
+ catch (error) {
42
+ if (error.code === "ENOENT")
43
+ return undefined;
44
+ throw error;
45
+ }
46
+ },
47
+ async set(key, value) {
48
+ await mkdir(dir, { recursive: true });
49
+ const target = join(dir, key);
50
+ // Same directory, so the rename cannot cross a filesystem boundary —
51
+ // which is the one case where it would stop being atomic.
52
+ const staging = `${target}.${process.pid}.${Date.now()}.tmp`;
53
+ await writeFile(staging, value, { mode: 0o600 });
54
+ await rename(staging, target);
55
+ },
56
+ async delete(key) {
57
+ await rm(join(dir, key), { force: true });
58
+ },
59
+ };
60
+ }
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Dialing the relay, and waiting for the reservation it grants afterwards.
3
+ *
4
+ * TRANSPORT-NEUTRAL ON PURPOSE, AND THAT IS WHY IT LIVES HERE. Both halves
5
+ * of this module touch nothing but `Libp2p.dial` and `Libp2p.getMultiaddrs`,
6
+ * so they are identical for a browser node (`browser/node-profile.ts`), a
7
+ * Node hub (`hub/node-profile.ts`) and a test peer (`tests/e2e/harness.ts`).
8
+ * They used to live in `browser/node-profile.ts`, whose top-level imports
9
+ * include `@libp2p/webrtc` -- which is why `tests/e2e/harness.ts` grew a
10
+ * second, hand-copied poll loop rather than import them (Task 14 reported
11
+ * that duplication as a finding). `@libp2p/webrtc` now loads under Node
12
+ * (its `node-datachannel` binary is installed, Task 20 Step 1), so the
13
+ * import would work today; the copy is still gone because ONE poll loop
14
+ * with one timing contract is the point, not because importing was
15
+ * impossible.
16
+ *
17
+ * READINESS IS POLLED, NOT AWAITED (design note 17 §4). `node.dial(relay)`
18
+ * resolving means the connection to the relay opened -- it says nothing
19
+ * about whether the relay has finished granting a reservation. That
20
+ * reservation lands asynchronously afterwards, so `waitForCircuitReservation`
21
+ * polls `getMultiaddrs()` until a `p2p-circuit` address actually appears, on
22
+ * the same schedule (250 ms x 40 attempts = 10 s ceiling) the validated
23
+ * relay test used (`prototypes/16-.../src/relay.test.ts`). Treating `dial`'s
24
+ * resolution as "ready" is exactly the race that note documents this project
25
+ * already got bitten by once.
26
+ */
27
+ import type { Libp2p } from "@libp2p/interface";
28
+ import { type RelayReservationGrant } from "./hop-reserve.js";
29
+ import { type Timers } from "./timers.js";
30
+ /** Dial the relay named by `relayAddr` (`httpeers.json`'s `relayAddrs[0]`). Resolving means the link is up -- NOT that a circuit reservation exists yet; see `waitForCircuitReservation`. */
31
+ export declare function dialRelay(node: Libp2p, relayAddr: string): Promise<void>;
32
+ /** How often `waitForCircuitReservation` re-checks `getMultiaddrs()`. */
33
+ export declare const RESERVATION_POLL_INTERVAL_MS = 250;
34
+ /** How many times it checks before giving up -- 40 x 250 ms = 10 s, the ceiling the validated relay test (`relay.test.ts`) used. */
35
+ export declare const RESERVATION_POLL_ATTEMPTS = 40;
36
+ export interface WaitForCircuitReservationInit {
37
+ intervalMs?: number;
38
+ attempts?: number;
39
+ }
40
+ /**
41
+ * Poll `node.getMultiaddrs()` until a `p2p-circuit` address appears,
42
+ * returning it as a string. See this module's own "READINESS IS POLLED,
43
+ * NOT AWAITED" note for why this exists instead of trusting `dialRelay`'s
44
+ * resolution: the reservation is granted by the relay asynchronously,
45
+ * after the dial itself has already resolved.
46
+ */
47
+ export declare function waitForCircuitReservation(node: Libp2p, init?: WaitForCircuitReservationInit): Promise<string>;
48
+ /**
49
+ * WHAT `superviseRelay` NOW DOES, AND WHY IT IS NOT WHAT IT USED TO DO.
50
+ *
51
+ * The supervisor below used to answer "am I reserved?" from
52
+ * `node.getMultiaddrs()` -- the node's OWN belief, held by libp2p's transport
53
+ * reservation store. On 2026-09-19 that belief was wrong for hours: the relay
54
+ * held no reservation for the hub, the websocket to it was still ESTABLISHED,
55
+ * every member got `NO_RESERVATION`, and the supervisor saw a perfectly good
56
+ * `/p2p-circuit` address and did nothing. Only a container restart fixed it,
57
+ * and the root cause on the relay's side was never found -- which is the
58
+ * argument for repairing the CLASS of fault rather than that one cause.
59
+ *
60
+ * So the relay decides now. On a schedule well inside the reservation's TTL
61
+ * the supervisor asks the relay for a reservation over the connection it
62
+ * already holds (`./hop-reserve.ts`, which explains the wire-level choice).
63
+ * That single request is both the question and the repair: a relay's
64
+ * reservation store is keyed by peer, so the answer is OK whether it renewed
65
+ * an entry or created a missing one. The incident therefore heals on the next
66
+ * tick without anything having to notice it first -- and if the relay instead
67
+ * REFUSES, or cannot be asked at all, that is a loss, with the backoff and
68
+ * `restore` path below.
69
+ *
70
+ * THE LOCAL ADDRESS LIST STILL EARNS ITS KEEP, in one direction only: an
71
+ * address that has GONE is proof of a loss (libp2p removes it when the relay
72
+ * connection closes), and reacting to it turns a dropped link into a restore
73
+ * in milliseconds rather than at the next renewal. An address that is PRESENT
74
+ * proves nothing and can no longer put the supervisor back into `reserved` --
75
+ * `tests/reservation-supervisor.test.ts` pins exactly that asymmetry.
76
+ *
77
+ * NOTHING IS SWALLOWED. Every transition logs one line naming the relay and
78
+ * the reason. The old implementation had a bare `catch {}` around the whole
79
+ * restore, which is why the incident left no trace in the hub's own output.
80
+ */
81
+ /** Where a supervised reservation stands. `stopped` is terminal. */
82
+ export type RelayReservationStatus = "reserved" | "lost" | "stopped";
83
+ /** Every transition the supervisor logs. There is no "gave up": it retries forever, with a capped backoff, and the health signal is what escalates. */
84
+ export type RelayReservationEvent = "reserved" | "renewed" | "renewal-failed" | "lost" | "restoring" | "restored" | "restore-failed" | "stopped";
85
+ /** A supervisor's current view, as `GET /hub/api/relay` reports it. */
86
+ export interface RelayReservationState {
87
+ status: RelayReservationStatus;
88
+ relayAddr: string;
89
+ /** The relay's peer id, or `null` when `relayAddr` names none. */
90
+ relayPeerId: string | null;
91
+ /** When the RELAY last confirmed the reservation (ms since the epoch); `null` until it first has. */
92
+ verifiedAt: number | null;
93
+ /** When the relay says the reservation expires (ms since the epoch); `null` when it did not say. */
94
+ expiresAt: number | null;
95
+ /** When the loss was first noticed (ms since the epoch); `null` while reserved. */
96
+ lostSince: number | null;
97
+ /** Renewals or restores that have failed in a row. Zero the moment the relay says OK. */
98
+ consecutiveFailures: number;
99
+ /** Relay-confirmed renewals since this supervisor started. */
100
+ renewals: number;
101
+ /** Recoveries completed since this supervisor started. */
102
+ restores: number;
103
+ /** The most recent failure, verbatim; kept after recovery so an operator can see what happened. */
104
+ lastError: string | null;
105
+ }
106
+ /** One line, plus the structured form of the same thing. */
107
+ export type RelayLog = (message: string, detail: {
108
+ event: RelayReservationEvent;
109
+ state: RelayReservationState;
110
+ }) => void;
111
+ export interface SuperviseRelayInit {
112
+ node: Libp2p;
113
+ /**
114
+ * The same string `dialRelay` was given. Its last `/p2p/<id>` names the
115
+ * relay whose reservation counts; with a custom `restore`, `/p2p/<id>`
116
+ * alone is enough. Without any `/p2p/<id>` there is nobody to ask, and
117
+ * proactive renewal is off.
118
+ */
119
+ relayAddr: string;
120
+ /**
121
+ * How to get the reservation back. Defaults to dialling `relayAddr` and
122
+ * waiting for libp2p to reserve on it, which is right for a relay libp2p
123
+ * DISCOVERED (a `/p2p-circuit` listen address). A CONFIGURED relay -- a
124
+ * member's reservation on its hub -- is never re-reserved by libp2p, so its
125
+ * restore has to ask again itself; see `./hub-link.ts`.
126
+ */
127
+ restore?: () => Promise<void>;
128
+ /** The first retry after a failed attempt. */
129
+ minRetryDelayMs?: number;
130
+ /** The ceiling the backoff grows to. */
131
+ maxRetryDelayMs?: number;
132
+ /**
133
+ * Ask the relay itself. Defaults to a circuit-relay v2 HOP `RESERVE` over
134
+ * the connection this node already holds (`requestRelayReservation`).
135
+ * `false` turns proactive renewal off entirely, which leaves only the
136
+ * address-disappeared detector -- i.e. the behaviour that missed the
137
+ * incident. It exists for tests, not for production.
138
+ */
139
+ verify?: ((options: {
140
+ signal?: AbortSignal;
141
+ }) => Promise<RelayReservationGrant>) | false;
142
+ /**
143
+ * How long between renewals. Defaults to a quarter of the TTL the relay
144
+ * itself reported in its last grant -- see `renewalIntervalMs`.
145
+ */
146
+ renewalIntervalMs?: number;
147
+ /** The backstop check for a loss no event reported. */
148
+ checkIntervalMs?: number;
149
+ /** Defaults to the global timers; a browser tab should pass `worker-timers` (see `./timers.ts`). */
150
+ timers?: Timers;
151
+ /** Defaults to one `console.log` per transition. A silent supervisor is what hid the incident. */
152
+ log?: RelayLog;
153
+ now?: () => number;
154
+ random?: () => number;
155
+ }
156
+ export interface RelaySupervisor {
157
+ /** Re-check now, and if the reservation is gone try at once rather than wait out the backoff. */
158
+ poke(): void;
159
+ /** What the relay last said, and when. Safe to call at any time; a copy, not the live object. */
160
+ state(): RelayReservationState;
161
+ stop(): void;
162
+ }
163
+ /**
164
+ * The floor and ceiling on the renewal cadence, and the cadence used when the
165
+ * relay has not told us a TTL yet.
166
+ *
167
+ * A QUARTER OF THE TTL is the rule (`renewalIntervalMs`). Against the relay
168
+ * this project runs -- `apps/relay/src/limits.ts`, TTL two hours, restated
169
+ * there precisely so a library change is visible -- that is thirty minutes,
170
+ * and it means three consecutive renewals can fail before the reservation
171
+ * would lapse on its own. A half would leave room for one, which is no margin
172
+ * at all; an eighth would double the traffic for a margin nobody needs.
173
+ *
174
+ * The CEILING is the same thirty minutes, so a relay that one day advertises a
175
+ * longer TTL does not stretch the cadence with it: the incident was not a
176
+ * reservation that expired, it was one that vanished, and how fast that is
177
+ * repaired is bounded by this number, not by the TTL. The FLOOR keeps a relay
178
+ * with an implausibly short TTL from being asked every few seconds.
179
+ */
180
+ export declare const MIN_RENEWAL_INTERVAL_MS = 60000;
181
+ export declare const MAX_RENEWAL_INTERVAL_MS: number;
182
+ export declare const DEFAULT_RENEWAL_INTERVAL_MS: number;
183
+ /** The renewal cadence for a reservation the relay says lasts `ttlMs`. See the constants above. */
184
+ export declare function renewalIntervalMs(ttlMs: number | null | undefined): number;
185
+ /**
186
+ * The actual wait before the next renewal: `intervalMs` jittered DOWN into its
187
+ * upper quarter, never up.
188
+ *
189
+ * Down, because the interval is already the deadline -- stretching it past a
190
+ * quarter of the TTL would spend the margin the quarter exists to buy. The
191
+ * spread is what keeps a fleet of hubs that started together, or came back
192
+ * together after a relay restart, from asking the relay in the same instant
193
+ * for ever after.
194
+ */
195
+ export declare function renewalDelayMs(intervalMs: number, random?: () => number): number;
196
+ /**
197
+ * How long a lost reservation may go unrepaired before the hub should call
198
+ * itself unhealthy.
199
+ *
200
+ * TWO MINUTES. A restore normally completes in seconds: the loss is noticed
201
+ * either at once (the address disappears) or at the next renewal, the restore
202
+ * dials and waits at most five seconds for the reservation, and the retry
203
+ * backoff caps at thirty. Two minutes is therefore at least four attempts
204
+ * including capped retries -- long enough that a relay restart or a dropped
205
+ * link never shows up as an unhealthy appliance, short enough to be a small
206
+ * fraction of the two-hour TTL, so a hub that truly cannot get back is
207
+ * reported long before members would notice on their own.
208
+ */
209
+ export declare const RESERVATION_LOSS_GRACE_MS = 120000;
210
+ /** Is this state healthy -- reserved, or lost for less than `graceMs`? See `RESERVATION_LOSS_GRACE_MS`. */
211
+ export declare function reservationHealthy(state: Pick<RelayReservationState, "status" | "lostSince">, graceMs?: number, now?: number): boolean;
212
+ /**
213
+ * Keep this node's reservation on the relay at `relayAddr`: renew it against
214
+ * the relay on a schedule, and re-dial whenever it is lost. Call once the
215
+ * first reservation has landed.
216
+ */
217
+ export declare function superviseRelay(init: SuperviseRelayInit): RelaySupervisor;
218
+ /**
219
+ * The wait before retry number `failures` (0-based): doubling from
220
+ * `minDelayMs`, capped at `maxDelayMs`, then jittered into its upper half
221
+ * so a relay restart does not bring every peer back in the same instant.
222
+ */
223
+ export declare function retryDelayMs(failures: number, minDelayMs: number, maxDelayMs: number, random?: () => number): number;
224
+ /** The circuit addresses a node currently holds, split by kind. */
225
+ export interface CircuitAddrs {
226
+ /** `/…/p2p-circuit/p2p/<self>` — reachable through the relay, no WebRTC. */
227
+ bare?: string;
228
+ /** `/…/p2p-circuit/webrtc/p2p/<self>` — the relay carries only the handshake. */
229
+ webrtc?: string;
230
+ }
231
+ /**
232
+ * Which of a node's addresses a peer should actually be told.
233
+ *
234
+ * After reserving, a node carries several multiaddrs at once and they are NOT
235
+ * interchangeable: its own transport address is not reachable through a relay
236
+ * at all, the bare circuit relays every byte, and the `/webrtc` circuit uses
237
+ * the relay for the handshake only and then talks directly. Handing out the
238
+ * wrong one is a connection that fails for a reason nobody can read from the
239
+ * error.
240
+ *
241
+ * Each assembly in the prototype picked one inline, and differently — the kind
242
+ * of triplication the extraction exists to end.
243
+ */
244
+ export declare function circuitAddrs(node: Libp2p): CircuitAddrs;
245
+ //# sourceMappingURL=reservation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reservation.d.ts","sourceRoot":"","sources":["../src/reservation.ts"],"names":[],"mappings":"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"}