wire-mesh-core 3.2.0 → 3.3.1

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.
@@ -67,8 +67,7 @@ function connectionFromByteStream(stream, options = { opened: false }) {
67
67
  },
68
68
  receive: () => frames,
69
69
  close: async () => {
70
- await writer.close();
71
- await reader.cancel();
70
+ await Promise.allSettled([writer.close(), reader.cancel()]);
72
71
  }
73
72
  };
74
73
  }
@@ -66,8 +66,7 @@ function connectionFromByteStream(stream, options = { opened: false }) {
66
66
  },
67
67
  receive: () => frames,
68
68
  close: async () => {
69
- await writer.close();
70
- await reader.cancel();
69
+ await Promise.allSettled([writer.close(), reader.cancel()]);
71
70
  }
72
71
  };
73
72
  }
@@ -19,6 +19,9 @@ function createRelayPairings() {
19
19
  remove: (device) => {
20
20
  established.delete(require_domain_device_id.deviceIdToHex(device));
21
21
  },
22
+ clear: () => {
23
+ established.clear();
24
+ },
22
25
  list: () => [...established.values()]
23
26
  };
24
27
  }
@@ -560,6 +563,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
560
563
  async function wireUpConnection(link, address, localDomains) {
561
564
  connection = link;
562
565
  relayChannels.reset("the connection changed before the channel was ready");
566
+ relayPairings.clear();
563
567
  localHandshakeSent = localHandshake(localDomains);
564
568
  handshake = { status: "pending" };
565
569
  state = {
@@ -18,6 +18,9 @@ function createRelayPairings() {
18
18
  remove: (device) => {
19
19
  established.delete(deviceIdToHex(device));
20
20
  },
21
+ clear: () => {
22
+ established.clear();
23
+ },
21
24
  list: () => [...established.values()]
22
25
  };
23
26
  }
@@ -559,6 +562,7 @@ function createSessionCore(identity, clock, reconnect, dial, onPeerAdvert, addre
559
562
  async function wireUpConnection(link, address, localDomains) {
560
563
  connection = link;
561
564
  relayChannels.reset("the connection changed before the channel was ready");
565
+ relayPairings.clear();
562
566
  localHandshakeSent = localHandshake(localDomains);
563
567
  handshake = { status: "pending" };
564
568
  state = {
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  //#region src/domain/pinned-address.ts
3
3
  const PIN_FRAGMENT = "#sha256=";
4
+ const HASH_SEPARATOR = ",";
4
5
  const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
5
6
  function hexToBytes(hex) {
6
7
  const bytes = new Uint8Array(hex.length / 2);
@@ -8,29 +9,29 @@ function hexToBytes(hex) {
8
9
  return bytes;
9
10
  }
10
11
  /**
11
- * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>`, the address and the hash of the certificate it serves. A client that pins the hash needs nothing else from the network to trust the node, so the pair travels together in one string, which `wire-candidate.address` already permits.
12
+ * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>[,<hex>...]`, the address and the hashes of the certificates it serves now and will serve next. A client that pins the hashes needs nothing else from the network to trust the node, so they travel with the address in one string, which `wire-candidate.address` already permits.
12
13
  */
13
14
  function formatPinnedAddress(hostPort, sha256Hex) {
14
- return `https://${hostPort}${PIN_FRAGMENT}${sha256Hex}`;
15
+ return `https://${hostPort}${PIN_FRAGMENT}${sha256Hex.join(HASH_SEPARATOR)}`;
15
16
  }
16
17
  /**
17
- * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
18
+ * Whether an address carries certificate pins, and so is dialled over WebTransport rather than WebSocket.
18
19
  */
19
20
  function isPinnedAddress(address) {
20
21
  return address.startsWith("https://") && address.includes(PIN_FRAGMENT);
21
22
  }
22
23
  /**
23
- * Splits an address of the form `https://host:port#sha256=<hex>`.
24
- * @throws Error when the address is not `https://` or carries no valid 64-digit lowercase hex hash.
24
+ * Splits an address of the form `https://host:port#sha256=<hex>[,<hex>...]`.
25
+ * @throws Error when the address is not `https://` or does not carry one or more 64-digit lowercase hex hashes.
25
26
  */
26
27
  function parsePinnedAddress(address) {
27
28
  const url = new URL(address);
28
- const hex = url.hash.startsWith(PIN_FRAGMENT) ? url.hash.slice(8) : "";
29
- if (url.protocol !== "https:" || !SHA256_HEX_PATTERN.test(hex)) throw new Error(`expected "https://host:port${PIN_FRAGMENT}<64 hex digits>", got "${address}"`);
29
+ const hashes = url.hash.startsWith(PIN_FRAGMENT) ? url.hash.slice(8).split(HASH_SEPARATOR) : [];
30
+ if (url.protocol !== "https:" || hashes.length === 0 || !hashes.every((hash) => SHA256_HEX_PATTERN.test(hash))) throw new Error(`expected "https://host:port${PIN_FRAGMENT}<64 hex digits>[${HASH_SEPARATOR}<64 hex digits>...]", got "${address}"`);
30
31
  url.hash = "";
31
32
  return {
32
33
  url: url.toString(),
33
- sha256: hexToBytes(hex)
34
+ sha256: hashes.map(hexToBytes)
34
35
  };
35
36
  }
36
37
  //#endregion
@@ -1,22 +1,22 @@
1
1
  //#region src/domain/pinned-address.d.ts
2
- /** A WebTransport address and the SHA-256 hash of the certificate it is expected to serve. */
2
+ /** A WebTransport address and the SHA-256 hashes of the certificates it may serve. */
3
3
  export interface PinnedAddress {
4
4
  /** `https://host:port/`, the URL to open. */
5
5
  readonly url: string;
6
- /** The certificate's SHA-256 hash, as bytes. */
7
- readonly sha256: Uint8Array<ArrayBuffer>;
6
+ /** Each acceptable certificate's SHA-256 hash, as bytes. A browser accepts the server's certificate when it matches any of them, so a node can list the certificates it will serve next and an address stays usable across its renewals. */
7
+ readonly sha256: readonly Uint8Array<ArrayBuffer>[];
8
8
  }
9
9
  /**
10
- * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>`, the address and the hash of the certificate it serves. A client that pins the hash needs nothing else from the network to trust the node, so the pair travels together in one string, which `wire-candidate.address` already permits.
10
+ * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>[,<hex>...]`, the address and the hashes of the certificates it serves now and will serve next. A client that pins the hashes needs nothing else from the network to trust the node, so they travel with the address in one string, which `wire-candidate.address` already permits.
11
11
  */
12
- export declare function formatPinnedAddress(hostPort: string, sha256Hex: string): string;
12
+ export declare function formatPinnedAddress(hostPort: string, sha256Hex: readonly string[]): string;
13
13
  /**
14
- * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
14
+ * Whether an address carries certificate pins, and so is dialled over WebTransport rather than WebSocket.
15
15
  */
16
16
  export declare function isPinnedAddress(address: string): boolean;
17
17
  /**
18
- * Splits an address of the form `https://host:port#sha256=<hex>`.
19
- * @throws Error when the address is not `https://` or carries no valid 64-digit lowercase hex hash.
18
+ * Splits an address of the form `https://host:port#sha256=<hex>[,<hex>...]`.
19
+ * @throws Error when the address is not `https://` or does not carry one or more 64-digit lowercase hex hashes.
20
20
  */
21
21
  export declare function parsePinnedAddress(address: string): PinnedAddress;
22
22
  //#endregion
@@ -1,22 +1,22 @@
1
1
  //#region src/domain/pinned-address.d.ts
2
- /** A WebTransport address and the SHA-256 hash of the certificate it is expected to serve. */
2
+ /** A WebTransport address and the SHA-256 hashes of the certificates it may serve. */
3
3
  export interface PinnedAddress {
4
4
  /** `https://host:port/`, the URL to open. */
5
5
  readonly url: string;
6
- /** The certificate's SHA-256 hash, as bytes. */
7
- readonly sha256: Uint8Array<ArrayBuffer>;
6
+ /** Each acceptable certificate's SHA-256 hash, as bytes. A browser accepts the server's certificate when it matches any of them, so a node can list the certificates it will serve next and an address stays usable across its renewals. */
7
+ readonly sha256: readonly Uint8Array<ArrayBuffer>[];
8
8
  }
9
9
  /**
10
- * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>`, the address and the hash of the certificate it serves. A client that pins the hash needs nothing else from the network to trust the node, so the pair travels together in one string, which `wire-candidate.address` already permits.
10
+ * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>[,<hex>...]`, the address and the hashes of the certificates it serves now and will serve next. A client that pins the hashes needs nothing else from the network to trust the node, so they travel with the address in one string, which `wire-candidate.address` already permits.
11
11
  */
12
- export declare function formatPinnedAddress(hostPort: string, sha256Hex: string): string;
12
+ export declare function formatPinnedAddress(hostPort: string, sha256Hex: readonly string[]): string;
13
13
  /**
14
- * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
14
+ * Whether an address carries certificate pins, and so is dialled over WebTransport rather than WebSocket.
15
15
  */
16
16
  export declare function isPinnedAddress(address: string): boolean;
17
17
  /**
18
- * Splits an address of the form `https://host:port#sha256=<hex>`.
19
- * @throws Error when the address is not `https://` or carries no valid 64-digit lowercase hex hash.
18
+ * Splits an address of the form `https://host:port#sha256=<hex>[,<hex>...]`.
19
+ * @throws Error when the address is not `https://` or does not carry one or more 64-digit lowercase hex hashes.
20
20
  */
21
21
  export declare function parsePinnedAddress(address: string): PinnedAddress;
22
22
  //#endregion
@@ -1,5 +1,6 @@
1
1
  //#region src/domain/pinned-address.ts
2
2
  const PIN_FRAGMENT = "#sha256=";
3
+ const HASH_SEPARATOR = ",";
3
4
  const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
4
5
  function hexToBytes(hex) {
5
6
  const bytes = new Uint8Array(hex.length / 2);
@@ -7,29 +8,29 @@ function hexToBytes(hex) {
7
8
  return bytes;
8
9
  }
9
10
  /**
10
- * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>`, the address and the hash of the certificate it serves. A client that pins the hash needs nothing else from the network to trust the node, so the pair travels together in one string, which `wire-candidate.address` already permits.
11
+ * The address a node advertises for WebTransport: `https://host:port#sha256=<hex>[,<hex>...]`, the address and the hashes of the certificates it serves now and will serve next. A client that pins the hashes needs nothing else from the network to trust the node, so they travel with the address in one string, which `wire-candidate.address` already permits.
11
12
  */
12
13
  function formatPinnedAddress(hostPort, sha256Hex) {
13
- return `https://${hostPort}${PIN_FRAGMENT}${sha256Hex}`;
14
+ return `https://${hostPort}${PIN_FRAGMENT}${sha256Hex.join(HASH_SEPARATOR)}`;
14
15
  }
15
16
  /**
16
- * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
17
+ * Whether an address carries certificate pins, and so is dialled over WebTransport rather than WebSocket.
17
18
  */
18
19
  function isPinnedAddress(address) {
19
20
  return address.startsWith("https://") && address.includes(PIN_FRAGMENT);
20
21
  }
21
22
  /**
22
- * Splits an address of the form `https://host:port#sha256=<hex>`.
23
- * @throws Error when the address is not `https://` or carries no valid 64-digit lowercase hex hash.
23
+ * Splits an address of the form `https://host:port#sha256=<hex>[,<hex>...]`.
24
+ * @throws Error when the address is not `https://` or does not carry one or more 64-digit lowercase hex hashes.
24
25
  */
25
26
  function parsePinnedAddress(address) {
26
27
  const url = new URL(address);
27
- const hex = url.hash.startsWith(PIN_FRAGMENT) ? url.hash.slice(8) : "";
28
- if (url.protocol !== "https:" || !SHA256_HEX_PATTERN.test(hex)) throw new Error(`expected "https://host:port${PIN_FRAGMENT}<64 hex digits>", got "${address}"`);
28
+ const hashes = url.hash.startsWith(PIN_FRAGMENT) ? url.hash.slice(8).split(HASH_SEPARATOR) : [];
29
+ if (url.protocol !== "https:" || hashes.length === 0 || !hashes.every((hash) => SHA256_HEX_PATTERN.test(hash))) throw new Error(`expected "https://host:port${PIN_FRAGMENT}<64 hex digits>[${HASH_SEPARATOR}<64 hex digits>...]", got "${address}"`);
29
30
  url.hash = "";
30
31
  return {
31
32
  url: url.toString(),
32
- sha256: hexToBytes(hex)
33
+ sha256: hashes.map(hexToBytes)
33
34
  };
34
35
  }
35
36
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wire-mesh-core",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "dependencies": {
5
5
  "cbor2": "2.3.0",
6
6
  "cddl.js": "1.0.1",