wire-mesh-core 3.1.0 → 3.2.0

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.
@@ -0,0 +1,79 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_generated_protocol = require("../generated/protocol.cjs");
3
+ let cbor2 = require("cbor2");
4
+ //#region src/adapters/byte-stream-connection.ts
5
+ const LENGTH_PREFIX_BYTES = 4;
6
+ const BYTES_PER_KIBIBYTE = 1024;
7
+ /** The largest frame body a peer may announce in a length prefix, matching the 100 MiB the `ws` package applies to one message on the same hub, so a WebSocket peer and a byte-stream peer are bounded alike. Without a bound a peer-controlled 4 GiB prefix makes the reader buffer until memory runs out. */
8
+ const MAX_FRAME_BYTES = 100 * (BYTES_PER_KIBIBYTE * BYTES_PER_KIBIBYTE);
9
+ /** A frame with an empty body, which carries nothing and is skipped by the reader. A QUIC stream is invisible to the peer until a byte crosses it, so the side that opens a stream writes one of these to make it visible before it has a real frame to send. */
10
+ const STREAM_OPEN_MARKER = new Uint8Array(LENGTH_PREFIX_BYTES);
11
+ /** The bytes that carry one frame on a byte stream: a 4-byte big-endian length, then the frame's CBOR body, the same framing the TCP adapter uses. */
12
+ function encodeLengthPrefixedFrame(frame) {
13
+ const body = (0, cbor2.encode)(frame, cbor2.cdeEncodeOptions);
14
+ const message = new Uint8Array(LENGTH_PREFIX_BYTES + body.length);
15
+ new DataView(message.buffer).setUint32(0, body.length);
16
+ message.set(body, LENGTH_PREFIX_BYTES);
17
+ return message;
18
+ }
19
+ function concat(first, second) {
20
+ const joined = new Uint8Array(first.length + second.length);
21
+ joined.set(first);
22
+ joined.set(second, first.length);
23
+ return joined;
24
+ }
25
+ /**
26
+ * Frames arriving on a byte stream, in order, until it ends. An empty body (STREAM_OPEN_MARKER) is skipped. A body that does not decode as CBOR, or a length prefix above MAX_FRAME_BYTES, rejects the iteration (a connection-level failure); a body that decodes but fails frame validation is dropped and the stream continues, since an unrecognised frame from a newer peer is what version negotiation exists to tolerate.
27
+ */
28
+ async function* readFrames(reader) {
29
+ let buffered = /* @__PURE__ */ new Uint8Array(0);
30
+ try {
31
+ for (;;) {
32
+ const chunk = await reader.read();
33
+ if (chunk.done) return;
34
+ buffered = concat(buffered, chunk.value);
35
+ while (buffered.length >= LENGTH_PREFIX_BYTES) {
36
+ const bodyLength = new DataView(buffered.buffer, buffered.byteOffset).getUint32(0);
37
+ if (bodyLength > 104857600) throw new Error(`frame of ${String(bodyLength)} bytes exceeds the ${String(MAX_FRAME_BYTES)}-byte limit`);
38
+ if (buffered.length < LENGTH_PREFIX_BYTES + bodyLength) break;
39
+ if (bodyLength === 0) {
40
+ buffered = buffered.slice(LENGTH_PREFIX_BYTES);
41
+ continue;
42
+ }
43
+ const body = buffered.slice(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + bodyLength);
44
+ buffered = buffered.slice(LENGTH_PREFIX_BYTES + bodyLength);
45
+ const result = require_generated_protocol.frameSchema.safeParse((0, cbor2.decode)(body, cbor2.cdeDecodeOptions));
46
+ if (result.success) yield result.data;
47
+ }
48
+ }
49
+ } catch (error) {
50
+ await reader.cancel(error);
51
+ throw error;
52
+ }
53
+ }
54
+ /**
55
+ * A Connection over an ordered, reliable byte stream, one frame per length-prefixed CBOR body. Pass `opened: true` on the side that opened the stream: it writes a STREAM_OPEN_MARKER first, so the peer sees the stream before the first real frame. Pass `opened: true` on the side that opened the stream, which writes a STREAM_OPEN_MARKER first so the peer sees the stream before the first real frame. `close` ends the writable side and cancels the readable side, so the peer sees the stream finish and this side's `receive()` ends.
56
+ */
57
+ function connectionFromByteStream(stream, options = { opened: false }) {
58
+ const writer = stream.writable.getWriter();
59
+ const reader = stream.readable.getReader();
60
+ const frames = readFrames(reader);
61
+ const marked = options.opened ? writer.write(STREAM_OPEN_MARKER) : Promise.resolve();
62
+ marked.catch(() => void 0);
63
+ return {
64
+ send: async (frame) => {
65
+ await marked;
66
+ await writer.write(encodeLengthPrefixedFrame(frame));
67
+ },
68
+ receive: () => frames,
69
+ close: async () => {
70
+ await writer.close();
71
+ await reader.cancel();
72
+ }
73
+ };
74
+ }
75
+ //#endregion
76
+ exports.MAX_FRAME_BYTES = MAX_FRAME_BYTES;
77
+ exports.STREAM_OPEN_MARKER = STREAM_OPEN_MARKER;
78
+ exports.connectionFromByteStream = connectionFromByteStream;
79
+ exports.encodeLengthPrefixedFrame = encodeLengthPrefixedFrame;
@@ -0,0 +1,21 @@
1
+ import { N as Frame } from "../protocol-DCDae3z4.cjs";
2
+ import { t as Connection } from "../transport-CfW7fsGK.cjs";
3
+ //#region src/adapters/byte-stream-connection.d.ts
4
+ /** The largest frame body a peer may announce in a length prefix, matching the 100 MiB the `ws` package applies to one message on the same hub, so a WebSocket peer and a byte-stream peer are bounded alike. Without a bound a peer-controlled 4 GiB prefix makes the reader buffer until memory runs out. */
5
+ export declare const MAX_FRAME_BYTES: number;
6
+ /** A frame with an empty body, which carries nothing and is skipped by the reader. A QUIC stream is invisible to the peer until a byte crosses it, so the side that opens a stream writes one of these to make it visible before it has a real frame to send. */
7
+ export declare const STREAM_OPEN_MARKER: Uint8Array;
8
+ /** The two halves of an ordered, reliable byte stream, as the Web Streams API models them: a WebTransport bidirectional stream is one, and so is any other stream a runtime exposes in that shape. */
9
+ export interface ByteStream {
10
+ readonly readable: ReadableStream<Uint8Array>;
11
+ readonly writable: WritableStream<Uint8Array>;
12
+ }
13
+ /** The bytes that carry one frame on a byte stream: a 4-byte big-endian length, then the frame's CBOR body, the same framing the TCP adapter uses. */
14
+ export declare function encodeLengthPrefixedFrame(frame: Frame): Uint8Array;
15
+ /**
16
+ * A Connection over an ordered, reliable byte stream, one frame per length-prefixed CBOR body. Pass `opened: true` on the side that opened the stream: it writes a STREAM_OPEN_MARKER first, so the peer sees the stream before the first real frame. Pass `opened: true` on the side that opened the stream, which writes a STREAM_OPEN_MARKER first so the peer sees the stream before the first real frame. `close` ends the writable side and cancels the readable side, so the peer sees the stream finish and this side's `receive()` ends.
17
+ */
18
+ export declare function connectionFromByteStream(stream: Readonly<ByteStream>, options?: Readonly<{
19
+ opened: boolean;
20
+ }>): Connection;
21
+ //#endregion
@@ -0,0 +1,21 @@
1
+ import { N as Frame } from "../protocol-DCDae3z4.mjs";
2
+ import { t as Connection } from "../transport-iLBxIkRS.mjs";
3
+ //#region src/adapters/byte-stream-connection.d.ts
4
+ /** The largest frame body a peer may announce in a length prefix, matching the 100 MiB the `ws` package applies to one message on the same hub, so a WebSocket peer and a byte-stream peer are bounded alike. Without a bound a peer-controlled 4 GiB prefix makes the reader buffer until memory runs out. */
5
+ export declare const MAX_FRAME_BYTES: number;
6
+ /** A frame with an empty body, which carries nothing and is skipped by the reader. A QUIC stream is invisible to the peer until a byte crosses it, so the side that opens a stream writes one of these to make it visible before it has a real frame to send. */
7
+ export declare const STREAM_OPEN_MARKER: Uint8Array;
8
+ /** The two halves of an ordered, reliable byte stream, as the Web Streams API models them: a WebTransport bidirectional stream is one, and so is any other stream a runtime exposes in that shape. */
9
+ export interface ByteStream {
10
+ readonly readable: ReadableStream<Uint8Array>;
11
+ readonly writable: WritableStream<Uint8Array>;
12
+ }
13
+ /** The bytes that carry one frame on a byte stream: a 4-byte big-endian length, then the frame's CBOR body, the same framing the TCP adapter uses. */
14
+ export declare function encodeLengthPrefixedFrame(frame: Frame): Uint8Array;
15
+ /**
16
+ * A Connection over an ordered, reliable byte stream, one frame per length-prefixed CBOR body. Pass `opened: true` on the side that opened the stream: it writes a STREAM_OPEN_MARKER first, so the peer sees the stream before the first real frame. Pass `opened: true` on the side that opened the stream, which writes a STREAM_OPEN_MARKER first so the peer sees the stream before the first real frame. `close` ends the writable side and cancels the readable side, so the peer sees the stream finish and this side's `receive()` ends.
17
+ */
18
+ export declare function connectionFromByteStream(stream: Readonly<ByteStream>, options?: Readonly<{
19
+ opened: boolean;
20
+ }>): Connection;
21
+ //#endregion
@@ -0,0 +1,75 @@
1
+ import { frameSchema } from "../generated/protocol.mjs";
2
+ import { cdeDecodeOptions, cdeEncodeOptions, decode, encode } from "cbor2";
3
+ //#region src/adapters/byte-stream-connection.ts
4
+ const LENGTH_PREFIX_BYTES = 4;
5
+ const BYTES_PER_KIBIBYTE = 1024;
6
+ /** The largest frame body a peer may announce in a length prefix, matching the 100 MiB the `ws` package applies to one message on the same hub, so a WebSocket peer and a byte-stream peer are bounded alike. Without a bound a peer-controlled 4 GiB prefix makes the reader buffer until memory runs out. */
7
+ const MAX_FRAME_BYTES = 100 * (BYTES_PER_KIBIBYTE * BYTES_PER_KIBIBYTE);
8
+ /** A frame with an empty body, which carries nothing and is skipped by the reader. A QUIC stream is invisible to the peer until a byte crosses it, so the side that opens a stream writes one of these to make it visible before it has a real frame to send. */
9
+ const STREAM_OPEN_MARKER = new Uint8Array(LENGTH_PREFIX_BYTES);
10
+ /** The bytes that carry one frame on a byte stream: a 4-byte big-endian length, then the frame's CBOR body, the same framing the TCP adapter uses. */
11
+ function encodeLengthPrefixedFrame(frame) {
12
+ const body = encode(frame, cdeEncodeOptions);
13
+ const message = new Uint8Array(LENGTH_PREFIX_BYTES + body.length);
14
+ new DataView(message.buffer).setUint32(0, body.length);
15
+ message.set(body, LENGTH_PREFIX_BYTES);
16
+ return message;
17
+ }
18
+ function concat(first, second) {
19
+ const joined = new Uint8Array(first.length + second.length);
20
+ joined.set(first);
21
+ joined.set(second, first.length);
22
+ return joined;
23
+ }
24
+ /**
25
+ * Frames arriving on a byte stream, in order, until it ends. An empty body (STREAM_OPEN_MARKER) is skipped. A body that does not decode as CBOR, or a length prefix above MAX_FRAME_BYTES, rejects the iteration (a connection-level failure); a body that decodes but fails frame validation is dropped and the stream continues, since an unrecognised frame from a newer peer is what version negotiation exists to tolerate.
26
+ */
27
+ async function* readFrames(reader) {
28
+ let buffered = /* @__PURE__ */ new Uint8Array(0);
29
+ try {
30
+ for (;;) {
31
+ const chunk = await reader.read();
32
+ if (chunk.done) return;
33
+ buffered = concat(buffered, chunk.value);
34
+ while (buffered.length >= LENGTH_PREFIX_BYTES) {
35
+ const bodyLength = new DataView(buffered.buffer, buffered.byteOffset).getUint32(0);
36
+ if (bodyLength > 104857600) throw new Error(`frame of ${String(bodyLength)} bytes exceeds the ${String(MAX_FRAME_BYTES)}-byte limit`);
37
+ if (buffered.length < LENGTH_PREFIX_BYTES + bodyLength) break;
38
+ if (bodyLength === 0) {
39
+ buffered = buffered.slice(LENGTH_PREFIX_BYTES);
40
+ continue;
41
+ }
42
+ const body = buffered.slice(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + bodyLength);
43
+ buffered = buffered.slice(LENGTH_PREFIX_BYTES + bodyLength);
44
+ const result = frameSchema.safeParse(decode(body, cdeDecodeOptions));
45
+ if (result.success) yield result.data;
46
+ }
47
+ }
48
+ } catch (error) {
49
+ await reader.cancel(error);
50
+ throw error;
51
+ }
52
+ }
53
+ /**
54
+ * A Connection over an ordered, reliable byte stream, one frame per length-prefixed CBOR body. Pass `opened: true` on the side that opened the stream: it writes a STREAM_OPEN_MARKER first, so the peer sees the stream before the first real frame. Pass `opened: true` on the side that opened the stream, which writes a STREAM_OPEN_MARKER first so the peer sees the stream before the first real frame. `close` ends the writable side and cancels the readable side, so the peer sees the stream finish and this side's `receive()` ends.
55
+ */
56
+ function connectionFromByteStream(stream, options = { opened: false }) {
57
+ const writer = stream.writable.getWriter();
58
+ const reader = stream.readable.getReader();
59
+ const frames = readFrames(reader);
60
+ const marked = options.opened ? writer.write(STREAM_OPEN_MARKER) : Promise.resolve();
61
+ marked.catch(() => void 0);
62
+ return {
63
+ send: async (frame) => {
64
+ await marked;
65
+ await writer.write(encodeLengthPrefixedFrame(frame));
66
+ },
67
+ receive: () => frames,
68
+ close: async () => {
69
+ await writer.close();
70
+ await reader.cancel();
71
+ }
72
+ };
73
+ }
74
+ //#endregion
75
+ export { MAX_FRAME_BYTES, STREAM_OPEN_MARKER, connectionFromByteStream, encodeLengthPrefixedFrame };
@@ -1,6 +1,6 @@
1
1
  import { D as DeviceId, N as Frame, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DCDae3z4.cjs";
2
- import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
3
2
  import { t as Connection } from "../transport-CfW7fsGK.cjs";
3
+ import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
4
4
  import { IncomingManageRequest, MeshSession } from "./mesh-session.cjs";
5
5
  //#region src/domain/bulk.d.ts
6
6
  /** The two capabilities matching the direction each manage-command actually flows (spec/bulk.cddl): the sender proves write authority to open a transfer, the receiver proves read authority to ask for one to continue. */
@@ -1,6 +1,6 @@
1
1
  import { D as DeviceId, N as Frame, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DCDae3z4.mjs";
2
- import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
3
2
  import { t as Connection } from "../transport-iLBxIkRS.mjs";
3
+ import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
4
4
  import { IncomingManageRequest, MeshSession } from "./mesh-session.mjs";
5
5
  //#region src/domain/bulk.d.ts
6
6
  /** The two capabilities matching the direction each manage-command actually flows (spec/bulk.cddl): the sender proves write authority to open a transfer, the receiver proves read authority to ask for one to continue. */
@@ -1,7 +1,7 @@
1
1
  import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, nn as TopologyPeers, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-DCDae3z4.cjs";
2
+ import { r as Transport, t as Connection } from "../transport-CfW7fsGK.cjs";
2
3
  import { t as IdentityPort } from "../identity-DVCNuFLG.cjs";
3
4
  import { t as Clock } from "../clock-DiSx-WKM.cjs";
4
- import { r as Transport, t as Connection } from "../transport-CfW7fsGK.cjs";
5
5
  //#region src/domain/mesh-session.d.ts
6
6
  /** How long to wait for the node's handshake before calling it unanswered. A relay-only node never sends one; that is a state to display, not an error. */
7
7
  export declare const HANDSHAKE_TIMEOUT_MS = 3000;
@@ -1,7 +1,7 @@
1
1
  import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, U as ManageError, V as ManageCommand, W as ManageOk, bt as RevocationEntry, et as PeerAdvert, f as CapabilityScope, lt as ProtocolVersion, nn as TopologyPeers, p as CapabilityToken, w as DataEntriesFrame } from "../protocol-DCDae3z4.mjs";
2
+ import { r as Transport, t as Connection } from "../transport-iLBxIkRS.mjs";
2
3
  import { t as IdentityPort } from "../identity-DcaYgjXT.mjs";
3
4
  import { t as Clock } from "../clock-DiSx-WKM.mjs";
4
- import { r as Transport, t as Connection } from "../transport-iLBxIkRS.mjs";
5
5
  //#region src/domain/mesh-session.d.ts
6
6
  /** How long to wait for the node's handshake before calling it unanswered. A relay-only node never sends one; that is a state to display, not an error. */
7
7
  export declare const HANDSHAKE_TIMEOUT_MS = 3000;
@@ -0,0 +1,39 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/domain/pinned-address.ts
3
+ const PIN_FRAGMENT = "#sha256=";
4
+ const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
5
+ function hexToBytes(hex) {
6
+ const bytes = new Uint8Array(hex.length / 2);
7
+ for (let index = 0; index < bytes.length; index++) bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
8
+ return bytes;
9
+ }
10
+ /**
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
+ */
13
+ function formatPinnedAddress(hostPort, sha256Hex) {
14
+ return `https://${hostPort}${PIN_FRAGMENT}${sha256Hex}`;
15
+ }
16
+ /**
17
+ * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
18
+ */
19
+ function isPinnedAddress(address) {
20
+ return address.startsWith("https://") && address.includes(PIN_FRAGMENT);
21
+ }
22
+ /**
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.
25
+ */
26
+ function parsePinnedAddress(address) {
27
+ 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}"`);
30
+ url.hash = "";
31
+ return {
32
+ url: url.toString(),
33
+ sha256: hexToBytes(hex)
34
+ };
35
+ }
36
+ //#endregion
37
+ exports.formatPinnedAddress = formatPinnedAddress;
38
+ exports.isPinnedAddress = isPinnedAddress;
39
+ exports.parsePinnedAddress = parsePinnedAddress;
@@ -0,0 +1,22 @@
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. */
3
+ export interface PinnedAddress {
4
+ /** `https://host:port/`, the URL to open. */
5
+ readonly url: string;
6
+ /** The certificate's SHA-256 hash, as bytes. */
7
+ readonly sha256: Uint8Array<ArrayBuffer>;
8
+ }
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.
11
+ */
12
+ export declare function formatPinnedAddress(hostPort: string, sha256Hex: string): string;
13
+ /**
14
+ * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
15
+ */
16
+ export declare function isPinnedAddress(address: string): boolean;
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.
20
+ */
21
+ export declare function parsePinnedAddress(address: string): PinnedAddress;
22
+ //#endregion
@@ -0,0 +1,22 @@
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. */
3
+ export interface PinnedAddress {
4
+ /** `https://host:port/`, the URL to open. */
5
+ readonly url: string;
6
+ /** The certificate's SHA-256 hash, as bytes. */
7
+ readonly sha256: Uint8Array<ArrayBuffer>;
8
+ }
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.
11
+ */
12
+ export declare function formatPinnedAddress(hostPort: string, sha256Hex: string): string;
13
+ /**
14
+ * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
15
+ */
16
+ export declare function isPinnedAddress(address: string): boolean;
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.
20
+ */
21
+ export declare function parsePinnedAddress(address: string): PinnedAddress;
22
+ //#endregion
@@ -0,0 +1,36 @@
1
+ //#region src/domain/pinned-address.ts
2
+ const PIN_FRAGMENT = "#sha256=";
3
+ const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
4
+ function hexToBytes(hex) {
5
+ const bytes = new Uint8Array(hex.length / 2);
6
+ for (let index = 0; index < bytes.length; index++) bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16);
7
+ return bytes;
8
+ }
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.
11
+ */
12
+ function formatPinnedAddress(hostPort, sha256Hex) {
13
+ return `https://${hostPort}${PIN_FRAGMENT}${sha256Hex}`;
14
+ }
15
+ /**
16
+ * Whether an address carries a certificate pin, and so is dialled over WebTransport rather than WebSocket.
17
+ */
18
+ function isPinnedAddress(address) {
19
+ return address.startsWith("https://") && address.includes(PIN_FRAGMENT);
20
+ }
21
+ /**
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.
24
+ */
25
+ function parsePinnedAddress(address) {
26
+ 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}"`);
29
+ url.hash = "";
30
+ return {
31
+ url: url.toString(),
32
+ sha256: hexToBytes(hex)
33
+ };
34
+ }
35
+ //#endregion
36
+ export { formatPinnedAddress, isPinnedAddress, parsePinnedAddress };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wire-mesh-core",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "dependencies": {
5
5
  "cbor2": "2.3.0",
6
6
  "cddl.js": "1.0.1",
@@ -26,6 +26,10 @@
26
26
  "vitest": "5.0.0"
27
27
  },
28
28
  "exports": {
29
+ "./adapters/byte-stream-connection": {
30
+ "import": "./dist/adapters/byte-stream-connection.mjs",
31
+ "require": "./dist/adapters/byte-stream-connection.cjs"
32
+ },
29
33
  "./adapters/frame-codec": {
30
34
  "import": "./dist/adapters/frame-codec.mjs",
31
35
  "require": "./dist/adapters/frame-codec.cjs"
@@ -142,6 +146,10 @@
142
146
  "import": "./dist/domain/ping-round-trips.mjs",
143
147
  "require": "./dist/domain/ping-round-trips.cjs"
144
148
  },
149
+ "./domain/pinned-address": {
150
+ "import": "./dist/domain/pinned-address.mjs",
151
+ "require": "./dist/domain/pinned-address.cjs"
152
+ },
145
153
  "./domain/relay-hub": {
146
154
  "import": "./dist/domain/relay-hub.mjs",
147
155
  "require": "./dist/domain/relay-hub.cjs"