@statewalker/httpeers-libp2p 0.1.3 → 0.1.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.
@@ -6,6 +6,7 @@
6
6
  * so the same code runs in a page and under the Node tests.
7
7
  */
8
8
  import type { Connection, Libp2p } from "@libp2p/interface";
9
+ import { type Multiaddr } from "@multiformats/multiaddr";
9
10
  import { type RelaySupervisor } from "./reservation.js";
10
11
  /**
11
12
  * The address a member dials another member at: through their hub, with a
@@ -20,6 +21,28 @@ import { type RelaySupervisor } from "./reservation.js";
20
21
  * connection it already holds to the hub.
21
22
  */
22
23
  export declare function hubRoute(hubPeerId: string, peerId: string): string;
24
+ /** How many times `dialWebRTC` tries a WebRTC upgrade before giving up. */
25
+ export declare const WEBRTC_UPGRADE_ATTEMPTS = 3;
26
+ /**
27
+ * Dial a `/webrtc` address, trying the upgrade again when it fails.
28
+ *
29
+ * WHY. Under Node, WebRTC is libdatachannel (`node-datachannel`), and
30
+ * libdatachannel <= 0.24.5 can lose a race on the OFFERER: setting the remote
31
+ * answer hands it to the ICE agent before the answer's fingerprint is
32
+ * committed, ICE can complete at once (the answerer has been sending checks),
33
+ * and the DTLS handshake that follows finds no remote fingerprint to verify
34
+ * against -- `DTLS alert: unknown CA`, the peer connection fails, and
35
+ * libp2p's dial times out. It needs the threads to interleave badly, which a
36
+ * busy CPU makes common: the conformance suite lost it in most runs under
37
+ * load. Each attempt is an independent race, so one retry nearly always
38
+ * succeeds. Fixed upstream by libdatachannel 0235225 ("start DTLS after
39
+ * remote description"); drop the retry once node-datachannel ships it.
40
+ * Browsers implement WebRTC themselves and are not affected.
41
+ *
42
+ * A WebRTC path that cannot come up fails every attempt, and the caller's
43
+ * fallback (the relay circuit) runs as before.
44
+ */
45
+ export declare function dialWebRTC(node: Libp2p, target: Multiaddr | Multiaddr[], attempts?: number): Promise<Connection>;
23
46
  /**
24
47
  * Reach the hub over WebRTC through the public relay, and keep ONLY the
25
48
  * WebRTC connection.
@@ -1 +1 @@
1
- {"version":3,"file":"hub-link.d.ts","sourceRoot":"","sources":["../src/hub-link.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAI5D,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,kBAAkB,CAAC;AAExE;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAElE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhG;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAErB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,qBAAqB,GAC7B,YAAY,GACZ,gBAAgB,GAChB,cAAc,GACd,UAAU,GACV,WAAW,GACX,OAAO,CAAC;AAEZ,uHAAuH;AACvH,qBAAa,mBAAoB,SAAQ,KAAK;IAE1C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,oHAAoH;IACpH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAC9B,QAAQ,CAAC,OAAO,EAAE,qBAAqB;IAJzC,YACW,SAAS,EAAE,MAAM;IAC1B,oHAAoH;IAC3G,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,qBAAqB,EACvC,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,EAI5B;CACF;AA2ED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAenF;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG/E;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,2BAA2B,GAAG,eAAe,CAY1F"}
1
+ {"version":3,"file":"hub-link.d.ts","sourceRoot":"","sources":["../src/hub-link.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,yBAAyB,CAAC;AAEpE,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,kBAAkB,CAAC;AAExE;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,EAC/B,QAAQ,GAAE,MAAgC,GACzC,OAAO,CAAC,UAAU,CAAC,CAUrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhG;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAErB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,qBAAqB,GAC7B,YAAY,GACZ,gBAAgB,GAChB,cAAc,GACd,UAAU,GACV,WAAW,GACX,OAAO,CAAC;AAEZ,uHAAuH;AACvH,qBAAa,mBAAoB,SAAQ,KAAK;IAE1C,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,oHAAoH;IACpH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAC9B,QAAQ,CAAC,OAAO,EAAE,qBAAqB;IAJzC,YACW,SAAS,EAAE,MAAM;IAC1B,oHAAoH;IAC3G,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,qBAAqB,EACvC,OAAO,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,EAI5B;CACF;AA2ED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAenF;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG/E;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,2BAA2B,GAAG,eAAe,CAY1F"}
package/dist/hub-link.js CHANGED
@@ -24,6 +24,39 @@ import { superviseRelay } from "./reservation.js";
24
24
  export function hubRoute(hubPeerId, peerId) {
25
25
  return `/p2p/${hubPeerId}/p2p-circuit/webrtc/p2p/${peerId}`;
26
26
  }
27
+ /** How many times `dialWebRTC` tries a WebRTC upgrade before giving up. */
28
+ export const WEBRTC_UPGRADE_ATTEMPTS = 3;
29
+ /**
30
+ * Dial a `/webrtc` address, trying the upgrade again when it fails.
31
+ *
32
+ * WHY. Under Node, WebRTC is libdatachannel (`node-datachannel`), and
33
+ * libdatachannel <= 0.24.5 can lose a race on the OFFERER: setting the remote
34
+ * answer hands it to the ICE agent before the answer's fingerprint is
35
+ * committed, ICE can complete at once (the answerer has been sending checks),
36
+ * and the DTLS handshake that follows finds no remote fingerprint to verify
37
+ * against -- `DTLS alert: unknown CA`, the peer connection fails, and
38
+ * libp2p's dial times out. It needs the threads to interleave badly, which a
39
+ * busy CPU makes common: the conformance suite lost it in most runs under
40
+ * load. Each attempt is an independent race, so one retry nearly always
41
+ * succeeds. Fixed upstream by libdatachannel 0235225 ("start DTLS after
42
+ * remote description"); drop the retry once node-datachannel ships it.
43
+ * Browsers implement WebRTC themselves and are not affected.
44
+ *
45
+ * A WebRTC path that cannot come up fails every attempt, and the caller's
46
+ * fallback (the relay circuit) runs as before.
47
+ */
48
+ export async function dialWebRTC(node, target, attempts = WEBRTC_UPGRADE_ATTEMPTS) {
49
+ let lastError;
50
+ for (let attempt = 1; attempt <= attempts; attempt++) {
51
+ try {
52
+ return await node.dial(target);
53
+ }
54
+ catch (err) {
55
+ lastError = err;
56
+ }
57
+ }
58
+ throw lastError;
59
+ }
27
60
  /**
28
61
  * Reach the hub over WebRTC through the public relay, and keep ONLY the
29
62
  * WebRTC connection.
@@ -37,7 +70,7 @@ export function hubRoute(hubPeerId, peerId) {
37
70
  * prototype; the "connects two members directly" test fails without it.
38
71
  */
39
72
  export async function reachHub(node, relayAddr, hubPeerId) {
40
- await node.dial(multiaddr(`${relayAddr}/p2p-circuit/webrtc/p2p/${hubPeerId}`));
73
+ await dialWebRTC(node, multiaddr(`${relayAddr}/p2p-circuit/webrtc/p2p/${hubPeerId}`));
41
74
  const limited = node.getConnections(peerIdFromString(hubPeerId)).filter((c) => c.limits != null);
42
75
  await Promise.all(limited.map((c) => c.close()));
43
76
  }
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  */
17
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
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";
19
+ export { dialWebRTC, HubReservationError, type HubReservationRefusal, hubRoute, leaveRelay, reachHub, reachHubRelayed, reserveOnHub, type SuperviseHubReservationInit, superviseHubReservation, WEBRTC_UPGRADE_ATTEMPTS, } from "./hub-link.js";
20
20
  export { HUB_MAX_RESERVATIONS, type HubRelayComponents, type HubRelayServiceInit, hubRelayService, type IsMember, membershipGater, releaseReservation, } from "./hub-relay.js";
21
21
  export { type BytesStore, decodeKey, encodeKey, type GenerateKeyInit, generateKey, type IdentityStore, type IdentityStoreInit, identityStore, peerIdOf, type SignerLike, signerOf, } from "./identity.js";
22
22
  export type { CallOnLimitedConnection } from "./link.js";
@@ -1 +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"}
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,UAAU,EACV,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,KAAK,2BAA2B,EAChC,uBAAuB,EACvB,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 CHANGED
@@ -16,7 +16,7 @@
16
16
  */
17
17
  export { createDuplexMounts, DUPLEX_PROTOCOL, NO_MOUNT, openDuplex, serveDuplex, } from "./duplex.js";
18
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";
19
+ export { dialWebRTC, HubReservationError, hubRoute, leaveRelay, reachHub, reachHubRelayed, reserveOnHub, superviseHubReservation, WEBRTC_UPGRADE_ATTEMPTS, } from "./hub-link.js";
20
20
  export { HUB_MAX_RESERVATIONS, hubRelayService, membershipGater, releaseReservation, } from "./hub-relay.js";
21
21
  export { decodeKey, encodeKey, generateKey, identityStore, peerIdOf, signerOf, } from "./identity.js";
22
22
  export { lastPeerIdOf } from "./multiaddr-parts.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/httpeers-libp2p",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "The transport: nodes, identity, and servePeer — the one place in the extraction that knows what libp2p is.",
6
6
  "license": "MIT",
@@ -48,7 +48,7 @@
48
48
  "@multiformats/multiaddr": "^13.0.3",
49
49
  "@statewalker/webrun-http-streams": "^0.2.2",
50
50
  "@statewalker/webrun-streams": "^0.2.0",
51
- "@statewalker/webrun-streams-libp2p": "^0.1.2",
51
+ "@statewalker/webrun-streams-libp2p": "^0.1.6",
52
52
  "libp2p": "^3.3.11",
53
53
  "@statewalker/httpeers-bridge": "^0.1.3",
54
54
  "@statewalker/httpeers-core": "^0.1.2"
package/src/hub-link.ts CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  import type { Connection, Libp2p } from "@libp2p/interface";
10
10
  import { peerIdFromString } from "@libp2p/peer-id";
11
- import { multiaddr } from "@multiformats/multiaddr";
11
+ import { type Multiaddr, multiaddr } from "@multiformats/multiaddr";
12
12
  import { lastPeerIdOf } from "./multiaddr-parts.js";
13
13
  import { type RelaySupervisor, superviseRelay } from "./reservation.js";
14
14
 
@@ -28,6 +28,44 @@ export function hubRoute(hubPeerId: string, peerId: string): string {
28
28
  return `/p2p/${hubPeerId}/p2p-circuit/webrtc/p2p/${peerId}`;
29
29
  }
30
30
 
31
+ /** How many times `dialWebRTC` tries a WebRTC upgrade before giving up. */
32
+ export const WEBRTC_UPGRADE_ATTEMPTS = 3;
33
+
34
+ /**
35
+ * Dial a `/webrtc` address, trying the upgrade again when it fails.
36
+ *
37
+ * WHY. Under Node, WebRTC is libdatachannel (`node-datachannel`), and
38
+ * libdatachannel <= 0.24.5 can lose a race on the OFFERER: setting the remote
39
+ * answer hands it to the ICE agent before the answer's fingerprint is
40
+ * committed, ICE can complete at once (the answerer has been sending checks),
41
+ * and the DTLS handshake that follows finds no remote fingerprint to verify
42
+ * against -- `DTLS alert: unknown CA`, the peer connection fails, and
43
+ * libp2p's dial times out. It needs the threads to interleave badly, which a
44
+ * busy CPU makes common: the conformance suite lost it in most runs under
45
+ * load. Each attempt is an independent race, so one retry nearly always
46
+ * succeeds. Fixed upstream by libdatachannel 0235225 ("start DTLS after
47
+ * remote description"); drop the retry once node-datachannel ships it.
48
+ * Browsers implement WebRTC themselves and are not affected.
49
+ *
50
+ * A WebRTC path that cannot come up fails every attempt, and the caller's
51
+ * fallback (the relay circuit) runs as before.
52
+ */
53
+ export async function dialWebRTC(
54
+ node: Libp2p,
55
+ target: Multiaddr | Multiaddr[],
56
+ attempts: number = WEBRTC_UPGRADE_ATTEMPTS,
57
+ ): Promise<Connection> {
58
+ let lastError: unknown;
59
+ for (let attempt = 1; attempt <= attempts; attempt++) {
60
+ try {
61
+ return await node.dial(target);
62
+ } catch (err) {
63
+ lastError = err;
64
+ }
65
+ }
66
+ throw lastError;
67
+ }
68
+
31
69
  /**
32
70
  * Reach the hub over WebRTC through the public relay, and keep ONLY the
33
71
  * WebRTC connection.
@@ -41,7 +79,7 @@ export function hubRoute(hubPeerId: string, peerId: string): string {
41
79
  * prototype; the "connects two members directly" test fails without it.
42
80
  */
43
81
  export async function reachHub(node: Libp2p, relayAddr: string, hubPeerId: string): Promise<void> {
44
- await node.dial(multiaddr(`${relayAddr}/p2p-circuit/webrtc/p2p/${hubPeerId}`));
82
+ await dialWebRTC(node, multiaddr(`${relayAddr}/p2p-circuit/webrtc/p2p/${hubPeerId}`));
45
83
  const limited = node.getConnections(peerIdFromString(hubPeerId)).filter((c) => c.limits != null);
46
84
  await Promise.all(limited.map((c) => c.close()));
47
85
  }
package/src/index.ts CHANGED
@@ -40,6 +40,7 @@ export {
40
40
  requestRelayReservation,
41
41
  } from "./hop-reserve.js";
42
42
  export {
43
+ dialWebRTC,
43
44
  HubReservationError,
44
45
  type HubReservationRefusal,
45
46
  hubRoute,
@@ -49,6 +50,7 @@ export {
49
50
  reserveOnHub,
50
51
  type SuperviseHubReservationInit,
51
52
  superviseHubReservation,
53
+ WEBRTC_UPGRADE_ATTEMPTS,
52
54
  } from "./hub-link.js";
53
55
  export {
54
56
  HUB_MAX_RESERVATIONS,