wire-mesh-core 3.0.0 → 3.1.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.
- package/dist/adapters/memory-storage.d.cts +1 -1
- package/dist/adapters/memory-storage.d.mts +1 -1
- package/dist/adapters/node-fs-storage.d.cts +1 -1
- package/dist/adapters/node-fs-storage.d.mts +1 -1
- package/dist/domain/bulk.d.cts +1 -1
- package/dist/domain/bulk.d.mts +1 -1
- package/dist/domain/capability-grant.d.cts +1 -1
- package/dist/domain/capability-grant.d.mts +1 -1
- package/dist/domain/capability-request.d.cts +1 -1
- package/dist/domain/capability-request.d.mts +1 -1
- package/dist/domain/data-sync.d.cts +1 -1
- package/dist/domain/data-sync.d.mts +1 -1
- package/dist/domain/hub-mailbox.cjs +81 -0
- package/dist/domain/hub-mailbox.d.cts +29 -0
- package/dist/domain/hub-mailbox.d.mts +29 -0
- package/dist/domain/hub-mailbox.mjs +79 -0
- package/dist/domain/notice-board.d.cts +1 -1
- package/dist/domain/notice-board.d.mts +1 -1
- package/dist/domain/relay-hub.cjs +14 -0
- package/dist/domain/relay-hub.d.cts +10 -0
- package/dist/domain/relay-hub.d.mts +10 -0
- package/dist/domain/relay-hub.mjs +14 -0
- package/dist/ports/storage.d.cts +2 -9
- package/dist/ports/storage.d.mts +2 -9
- package/dist/storage-B403CRyu.d.cts +10 -0
- package/dist/storage-B403CRyu.d.mts +10 -0
- package/package.json +5 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeyValueStorage } from "../
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
|
|
2
2
|
//#region src/adapters/memory-storage.d.ts
|
|
3
3
|
/** An in-process KeyValueStorage backed by a Map -- for tests and single-process nodes; a real deployment substitutes a persistent adapter behind the same contract without touching anything that depends on the port. Not declared `async`: every operation is genuinely synchronous under the hood, so the contract's Promise return is satisfied directly via Promise.resolve() rather than an async function with no await in its body. */
|
|
4
4
|
export declare function createMemoryStorage(): KeyValueStorage;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeyValueStorage } from "../
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
|
|
2
2
|
//#region src/adapters/memory-storage.d.ts
|
|
3
3
|
/** An in-process KeyValueStorage backed by a Map -- for tests and single-process nodes; a real deployment substitutes a persistent adapter behind the same contract without touching anything that depends on the port. Not declared `async`: every operation is genuinely synchronous under the hood, so the contract's Promise return is satisfied directly via Promise.resolve() rather than an async function with no await in its body. */
|
|
4
4
|
export declare function createMemoryStorage(): KeyValueStorage;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeyValueStorage } from "../
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
|
|
2
2
|
//#region src/adapters/node-fs-storage.d.ts
|
|
3
3
|
export interface NodeFsStorageOptions {
|
|
4
4
|
/** Directory to store entries under. Created on first write if it doesn't already exist. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KeyValueStorage } from "../
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
|
|
2
2
|
//#region src/adapters/node-fs-storage.d.ts
|
|
3
3
|
export interface NodeFsStorageOptions {
|
|
4
4
|
/** Directory to store entries under. Created on first write if it doesn't already exist. */
|
package/dist/domain/bulk.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, N as Frame, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DCDae3z4.cjs";
|
|
2
|
-
import { KeyValueStorage } from "../
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
|
|
3
3
|
import { t as Connection } from "../transport-CfW7fsGK.cjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.cjs";
|
|
5
5
|
//#region src/domain/bulk.d.ts
|
package/dist/domain/bulk.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, N as Frame, V as ManageCommand, f as CapabilityScope, p as CapabilityToken } from "../protocol-DCDae3z4.mjs";
|
|
2
|
-
import { KeyValueStorage } from "../
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
|
|
3
3
|
import { t as Connection } from "../transport-iLBxIkRS.mjs";
|
|
4
4
|
import { IncomingManageRequest, MeshSession } from "./mesh-session.mjs";
|
|
5
5
|
//#region src/domain/bulk.d.ts
|
|
@@ -13,7 +13,7 @@ export declare function buildCapabilityGrantCommand(capability: string, grantedT
|
|
|
13
13
|
*
|
|
14
14
|
* scope and targetDevice forward directly to MeshSession.sendManageRequest's own identically-named parameters (the request's own top-level scope obligation 4 checks the token against, and relay routing respectively).
|
|
15
15
|
*/
|
|
16
|
-
export declare function sendCapabilityGrant(session: Readonly<MeshSession
|
|
16
|
+
export declare function sendCapabilityGrant(session: Readonly<Pick<MeshSession, "sendManageRequest">>, capability: string, grantedToken: CapabilityToken, scope: Readonly<CapabilityScope>, targetDevice?: DeviceId): Promise<ManageOutcome>;
|
|
17
17
|
/** One incoming, fully-verified capability-grant, surfaced for the domain to react to (store the token, notify a human, etc.) -- there is no decide() here because management.cddl specifies no protocol-level approval round trip for this primitive; by the time onGrant fires, the wire response has already been sent. */
|
|
18
18
|
export interface CapabilityGrantEvent {
|
|
19
19
|
/** The capability granted -- equals both the outer manage-command.verb and the verified token's own capability claim (obligation 2 already enforced this before onGrant fires). */
|
|
@@ -13,7 +13,7 @@ export declare function buildCapabilityGrantCommand(capability: string, grantedT
|
|
|
13
13
|
*
|
|
14
14
|
* scope and targetDevice forward directly to MeshSession.sendManageRequest's own identically-named parameters (the request's own top-level scope obligation 4 checks the token against, and relay routing respectively).
|
|
15
15
|
*/
|
|
16
|
-
export declare function sendCapabilityGrant(session: Readonly<MeshSession
|
|
16
|
+
export declare function sendCapabilityGrant(session: Readonly<Pick<MeshSession, "sendManageRequest">>, capability: string, grantedToken: CapabilityToken, scope: Readonly<CapabilityScope>, targetDevice?: DeviceId): Promise<ManageOutcome>;
|
|
17
17
|
/** One incoming, fully-verified capability-grant, surfaced for the domain to react to (store the token, notify a human, etc.) -- there is no decide() here because management.cddl specifies no protocol-level approval round trip for this primitive; by the time onGrant fires, the wire response has already been sent. */
|
|
18
18
|
export interface CapabilityGrantEvent {
|
|
19
19
|
/** The capability granted -- equals both the outer manage-command.verb and the verified token's own capability claim (obligation 2 already enforced this before onGrant fires). */
|
|
@@ -12,7 +12,7 @@ export declare function buildCapabilityRequestCommand(capability: string, validU
|
|
|
12
12
|
*
|
|
13
13
|
* targetDevice and timeoutMs forward directly to MeshSession.sendManageRequest's own identically-named parameters (relay routing and a requester-side give-up bound respectively -- wire-mesh#80 is already implemented there, not duplicated here). validUntil, when given, is attached to the request itself (wire-mesh#82) so a slow-to-answer receiver -- or a relay/facilitator forwarding this request on the caller's behalf -- can refuse or drop a now-stale ask outright rather than holding or forwarding it.
|
|
14
14
|
*/
|
|
15
|
-
export declare function requestCapability(session: Readonly<MeshSession
|
|
15
|
+
export declare function requestCapability(session: Readonly<Pick<MeshSession, "sendManageRequest">>, capability: string, scope: Readonly<CapabilityScope>, targetDevice?: DeviceId, timeoutMs?: number, validUntil?: number): Promise<CapabilityGrantOk>;
|
|
16
16
|
/**
|
|
17
17
|
* A receiver's decision on one incoming capability-request, mirroring room-client.ts's own (now capability-specific) RoomJoinDecision, generalized to any capability: "accept" mints and returns a fresh token; "reject" sends an ordinary manage-error carrying an optional human-readable reason.
|
|
18
18
|
*/
|
|
@@ -12,7 +12,7 @@ export declare function buildCapabilityRequestCommand(capability: string, validU
|
|
|
12
12
|
*
|
|
13
13
|
* targetDevice and timeoutMs forward directly to MeshSession.sendManageRequest's own identically-named parameters (relay routing and a requester-side give-up bound respectively -- wire-mesh#80 is already implemented there, not duplicated here). validUntil, when given, is attached to the request itself (wire-mesh#82) so a slow-to-answer receiver -- or a relay/facilitator forwarding this request on the caller's behalf -- can refuse or drop a now-stale ask outright rather than holding or forwarding it.
|
|
14
14
|
*/
|
|
15
|
-
export declare function requestCapability(session: Readonly<MeshSession
|
|
15
|
+
export declare function requestCapability(session: Readonly<Pick<MeshSession, "sendManageRequest">>, capability: string, scope: Readonly<CapabilityScope>, targetDevice?: DeviceId, timeoutMs?: number, validUntil?: number): Promise<CapabilityGrantOk>;
|
|
16
16
|
/**
|
|
17
17
|
* A receiver's decision on one incoming capability-request, mirroring room-client.ts's own (now capability-specific) RoomJoinDecision, generalized to any capability: "accept" mints and returns a fresh token; "reject" sends an ordinary manage-error carrying an optional human-readable reason.
|
|
18
18
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, E as DataRequestFrame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DCDae3z4.cjs";
|
|
2
|
-
import { KeyValueStorage } from "../
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
|
|
3
3
|
import { t as IdentityPort } from "../identity-DVCNuFLG.cjs";
|
|
4
4
|
//#region src/domain/data-sync.d.ts
|
|
5
5
|
export interface DataSyncOptions {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, E as DataRequestFrame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DCDae3z4.mjs";
|
|
2
|
-
import { KeyValueStorage } from "../
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
|
|
3
3
|
import { t as IdentityPort } from "../identity-DcaYgjXT.mjs";
|
|
4
4
|
//#region src/domain/data-sync.d.ts
|
|
5
5
|
export interface DataSyncOptions {
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_domain_device_id = require("./device-id.cjs");
|
|
3
|
+
const require_domain_data_sync = require("./data-sync.cjs");
|
|
4
|
+
//#region src/domain/hub-mailbox.ts
|
|
5
|
+
/** The data frames a mailbox answers. */
|
|
6
|
+
function isDataFrame(frame) {
|
|
7
|
+
return frame.type === "data-have" || frame.type === "data-request" || frame.type === "data-entries";
|
|
8
|
+
}
|
|
9
|
+
const LOG_COUNT_KEY = "mailbox/log-count";
|
|
10
|
+
function logBytesKey(peer) {
|
|
11
|
+
return `mailbox/${require_domain_device_id.deviceIdToHex(peer)}/bytes`;
|
|
12
|
+
}
|
|
13
|
+
function encodeCount(value) {
|
|
14
|
+
return new TextEncoder().encode(String(value));
|
|
15
|
+
}
|
|
16
|
+
async function readCount(storage, key) {
|
|
17
|
+
const raw = await storage.get(key);
|
|
18
|
+
return raw === void 0 ? 0 : Number(new TextDecoder().decode(raw));
|
|
19
|
+
}
|
|
20
|
+
function sameDevice(a, b) {
|
|
21
|
+
return require_domain_device_id.deviceIdToHex(a) === require_domain_device_id.deviceIdToHex(b);
|
|
22
|
+
}
|
|
23
|
+
function createMailbox(options) {
|
|
24
|
+
const { storage, limits } = options;
|
|
25
|
+
async function handleHave(sender, frame, refuse) {
|
|
26
|
+
if (!sameDevice(sender, frame.peer)) {
|
|
27
|
+
refuse("not-the-owner");
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (await require_domain_data_sync.headSeqFor(storage, frame.peer) === 0) {
|
|
31
|
+
if (await readCount(storage, LOG_COUNT_KEY) >= limits.maxLogs) {
|
|
32
|
+
refuse("log-limit");
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return require_domain_data_sync.handleDataHave(storage, frame);
|
|
37
|
+
}
|
|
38
|
+
async function handleEntries(sender, frame, refuse) {
|
|
39
|
+
if (!sameDevice(sender, frame.peer)) {
|
|
40
|
+
refuse("not-the-owner");
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (frame.entries.some((entry) => entry.length > limits.maxEntryBytes)) {
|
|
44
|
+
refuse("size-limit");
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const held = await readCount(storage, logBytesKey(frame.peer));
|
|
48
|
+
const added = frame.entries.reduce((sum, entry) => sum + entry.length, 0);
|
|
49
|
+
if (held + added > limits.maxLogBytes) {
|
|
50
|
+
refuse("entry-limit");
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const isNewLog = await require_domain_data_sync.headSeqFor(storage, frame.peer) === 0;
|
|
54
|
+
if (isNewLog && await readCount(storage, LOG_COUNT_KEY) >= limits.maxLogs) {
|
|
55
|
+
refuse("log-limit");
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
if (!(await require_domain_data_sync.handleDataEntries(storage, frame)).ok) {
|
|
59
|
+
refuse("gap");
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
await storage.set(logBytesKey(frame.peer), encodeCount(held + added));
|
|
63
|
+
if (isNewLog && frame.entries.length > 0) await storage.set(LOG_COUNT_KEY, encodeCount(await readCount(storage, LOG_COUNT_KEY) + 1));
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return { async handle(sender, frame, onRefused) {
|
|
67
|
+
const refuse = (reason) => {
|
|
68
|
+
onRefused?.(reason);
|
|
69
|
+
};
|
|
70
|
+
if (sender === void 0) {
|
|
71
|
+
refuse("unregistered-sender");
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (frame.type === "data-have") return handleHave(sender, frame, refuse);
|
|
75
|
+
if (frame.type === "data-entries") return handleEntries(sender, frame, refuse);
|
|
76
|
+
return require_domain_data_sync.handleDataRequest(storage, frame, limits.readBatch);
|
|
77
|
+
} };
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
exports.createMailbox = createMailbox;
|
|
81
|
+
exports.isDataFrame = isDataFrame;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DCDae3z4.cjs";
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
|
|
3
|
+
//#region src/domain/hub-mailbox.d.ts
|
|
4
|
+
export interface MailboxLimits {
|
|
5
|
+
/** How many devices' logs the mailbox will hold at once. */
|
|
6
|
+
readonly maxLogs: number;
|
|
7
|
+
/** How many bytes of entries one log may hold in total. */
|
|
8
|
+
readonly maxLogBytes: number;
|
|
9
|
+
/** How large one entry may be. */
|
|
10
|
+
readonly maxEntryBytes: number;
|
|
11
|
+
/** How many entries one reply carries at most; a longer log takes more rounds. */
|
|
12
|
+
readonly readBatch: number;
|
|
13
|
+
}
|
|
14
|
+
export interface MailboxOptions {
|
|
15
|
+
readonly storage: KeyValueStorage;
|
|
16
|
+
readonly limits: Readonly<MailboxLimits>;
|
|
17
|
+
}
|
|
18
|
+
/** Called for a data frame the mailbox refuses, so the host can report why; the sender is given no reply, since data-domain.cddl defines no error frame. */
|
|
19
|
+
export type MailboxRefusalReason = "not-the-owner" | "unregistered-sender" | "log-limit" | "size-limit" | "entry-limit" | "gap";
|
|
20
|
+
export interface Mailbox {
|
|
21
|
+
/**
|
|
22
|
+
* Handles one data frame from a connection. `sender` is the device the hub has verified that connection to be, or undefined when it has not gossiped one. Returns the frame to send back, or null for none. `onRefused` is told when the frame was refused and why.
|
|
23
|
+
*/
|
|
24
|
+
handle: (sender: DeviceId | undefined, frame: Readonly<DataHaveFrame | DataRequestFrame | DataEntriesFrame>, onRefused?: (reason: MailboxRefusalReason) => void) => Promise<Frame | null>;
|
|
25
|
+
}
|
|
26
|
+
/** The data frames a mailbox answers. */
|
|
27
|
+
export declare function isDataFrame(frame: Readonly<Frame>): frame is DataHaveFrame | DataRequestFrame | DataEntriesFrame;
|
|
28
|
+
export declare function createMailbox(options: Readonly<MailboxOptions>): Mailbox;
|
|
29
|
+
//#endregion
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { D as DeviceId, E as DataRequestFrame, N as Frame, T as DataHaveFrame, w as DataEntriesFrame } from "../protocol-DCDae3z4.mjs";
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
|
|
3
|
+
//#region src/domain/hub-mailbox.d.ts
|
|
4
|
+
export interface MailboxLimits {
|
|
5
|
+
/** How many devices' logs the mailbox will hold at once. */
|
|
6
|
+
readonly maxLogs: number;
|
|
7
|
+
/** How many bytes of entries one log may hold in total. */
|
|
8
|
+
readonly maxLogBytes: number;
|
|
9
|
+
/** How large one entry may be. */
|
|
10
|
+
readonly maxEntryBytes: number;
|
|
11
|
+
/** How many entries one reply carries at most; a longer log takes more rounds. */
|
|
12
|
+
readonly readBatch: number;
|
|
13
|
+
}
|
|
14
|
+
export interface MailboxOptions {
|
|
15
|
+
readonly storage: KeyValueStorage;
|
|
16
|
+
readonly limits: Readonly<MailboxLimits>;
|
|
17
|
+
}
|
|
18
|
+
/** Called for a data frame the mailbox refuses, so the host can report why; the sender is given no reply, since data-domain.cddl defines no error frame. */
|
|
19
|
+
export type MailboxRefusalReason = "not-the-owner" | "unregistered-sender" | "log-limit" | "size-limit" | "entry-limit" | "gap";
|
|
20
|
+
export interface Mailbox {
|
|
21
|
+
/**
|
|
22
|
+
* Handles one data frame from a connection. `sender` is the device the hub has verified that connection to be, or undefined when it has not gossiped one. Returns the frame to send back, or null for none. `onRefused` is told when the frame was refused and why.
|
|
23
|
+
*/
|
|
24
|
+
handle: (sender: DeviceId | undefined, frame: Readonly<DataHaveFrame | DataRequestFrame | DataEntriesFrame>, onRefused?: (reason: MailboxRefusalReason) => void) => Promise<Frame | null>;
|
|
25
|
+
}
|
|
26
|
+
/** The data frames a mailbox answers. */
|
|
27
|
+
export declare function isDataFrame(frame: Readonly<Frame>): frame is DataHaveFrame | DataRequestFrame | DataEntriesFrame;
|
|
28
|
+
export declare function createMailbox(options: Readonly<MailboxOptions>): Mailbox;
|
|
29
|
+
//#endregion
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { deviceIdToHex } from "./device-id.mjs";
|
|
2
|
+
import { handleDataEntries, handleDataHave, handleDataRequest, headSeqFor } from "./data-sync.mjs";
|
|
3
|
+
//#region src/domain/hub-mailbox.ts
|
|
4
|
+
/** The data frames a mailbox answers. */
|
|
5
|
+
function isDataFrame(frame) {
|
|
6
|
+
return frame.type === "data-have" || frame.type === "data-request" || frame.type === "data-entries";
|
|
7
|
+
}
|
|
8
|
+
const LOG_COUNT_KEY = "mailbox/log-count";
|
|
9
|
+
function logBytesKey(peer) {
|
|
10
|
+
return `mailbox/${deviceIdToHex(peer)}/bytes`;
|
|
11
|
+
}
|
|
12
|
+
function encodeCount(value) {
|
|
13
|
+
return new TextEncoder().encode(String(value));
|
|
14
|
+
}
|
|
15
|
+
async function readCount(storage, key) {
|
|
16
|
+
const raw = await storage.get(key);
|
|
17
|
+
return raw === void 0 ? 0 : Number(new TextDecoder().decode(raw));
|
|
18
|
+
}
|
|
19
|
+
function sameDevice(a, b) {
|
|
20
|
+
return deviceIdToHex(a) === deviceIdToHex(b);
|
|
21
|
+
}
|
|
22
|
+
function createMailbox(options) {
|
|
23
|
+
const { storage, limits } = options;
|
|
24
|
+
async function handleHave(sender, frame, refuse) {
|
|
25
|
+
if (!sameDevice(sender, frame.peer)) {
|
|
26
|
+
refuse("not-the-owner");
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (await headSeqFor(storage, frame.peer) === 0) {
|
|
30
|
+
if (await readCount(storage, LOG_COUNT_KEY) >= limits.maxLogs) {
|
|
31
|
+
refuse("log-limit");
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return handleDataHave(storage, frame);
|
|
36
|
+
}
|
|
37
|
+
async function handleEntries(sender, frame, refuse) {
|
|
38
|
+
if (!sameDevice(sender, frame.peer)) {
|
|
39
|
+
refuse("not-the-owner");
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (frame.entries.some((entry) => entry.length > limits.maxEntryBytes)) {
|
|
43
|
+
refuse("size-limit");
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const held = await readCount(storage, logBytesKey(frame.peer));
|
|
47
|
+
const added = frame.entries.reduce((sum, entry) => sum + entry.length, 0);
|
|
48
|
+
if (held + added > limits.maxLogBytes) {
|
|
49
|
+
refuse("entry-limit");
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const isNewLog = await headSeqFor(storage, frame.peer) === 0;
|
|
53
|
+
if (isNewLog && await readCount(storage, LOG_COUNT_KEY) >= limits.maxLogs) {
|
|
54
|
+
refuse("log-limit");
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
if (!(await handleDataEntries(storage, frame)).ok) {
|
|
58
|
+
refuse("gap");
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
await storage.set(logBytesKey(frame.peer), encodeCount(held + added));
|
|
62
|
+
if (isNewLog && frame.entries.length > 0) await storage.set(LOG_COUNT_KEY, encodeCount(await readCount(storage, LOG_COUNT_KEY) + 1));
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return { async handle(sender, frame, onRefused) {
|
|
66
|
+
const refuse = (reason) => {
|
|
67
|
+
onRefused?.(reason);
|
|
68
|
+
};
|
|
69
|
+
if (sender === void 0) {
|
|
70
|
+
refuse("unregistered-sender");
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (frame.type === "data-have") return handleHave(sender, frame, refuse);
|
|
74
|
+
if (frame.type === "data-entries") return handleEntries(sender, frame, refuse);
|
|
75
|
+
return handleDataRequest(storage, frame, limits.readBatch);
|
|
76
|
+
} };
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { createMailbox, isDataFrame };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, Ot as RoomNotice, T as DataHaveFrame, kt as RoomNoticeClaims, p as CapabilityToken } from "../protocol-DCDae3z4.cjs";
|
|
2
|
-
import { KeyValueStorage } from "../
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
|
|
3
3
|
import { t as IdentityPort } from "../identity-DVCNuFLG.cjs";
|
|
4
4
|
import { t as Clock } from "../clock-DiSx-WKM.cjs";
|
|
5
5
|
import { a as RevocationCheck } from "../tokens-DqFu7iMX.cjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { D as DeviceId, Ot as RoomNotice, T as DataHaveFrame, kt as RoomNoticeClaims, p as CapabilityToken } from "../protocol-DCDae3z4.mjs";
|
|
2
|
-
import { KeyValueStorage } from "../
|
|
2
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
|
|
3
3
|
import { t as IdentityPort } from "../identity-DcaYgjXT.mjs";
|
|
4
4
|
import { t as Clock } from "../clock-DiSx-WKM.mjs";
|
|
5
5
|
import { a as RevocationCheck } from "../tokens-CBiSYuzp.mjs";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_domain_peer_advert = require("./peer-advert.cjs");
|
|
3
3
|
const require_domain_advert_extension_policy = require("./advert-extension-policy.cjs");
|
|
4
|
+
const require_domain_hub_mailbox = require("./hub-mailbox.cjs");
|
|
4
5
|
//#region src/domain/relay-hub.ts
|
|
5
6
|
const HEX_RADIX = 16;
|
|
6
7
|
const HEX_DIGITS_PER_BYTE = 2;
|
|
@@ -192,6 +193,19 @@ function createRelayHub(options) {
|
|
|
192
193
|
});
|
|
193
194
|
return;
|
|
194
195
|
}
|
|
196
|
+
if (options.mailbox !== void 0 && require_domain_hub_mailbox.isDataFrame(frame)) {
|
|
197
|
+
const sender = connectionDevice.get(connection);
|
|
198
|
+
const reply = await options.mailbox.handle(sender, frame, (reason) => {
|
|
199
|
+
options.onMailboxRefused?.({
|
|
200
|
+
device: sender,
|
|
201
|
+
reason
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
if (reply !== null) try {
|
|
205
|
+
await connection.send(reply);
|
|
206
|
+
} catch {}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
195
209
|
if (frame.type === "ping") {
|
|
196
210
|
try {
|
|
197
211
|
await connection.send({ type: "pong" });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { D as DeviceId, N as Frame, et as PeerAdvert } from "../protocol-DCDae3z4.cjs";
|
|
2
2
|
import { t as Connection } from "../transport-CfW7fsGK.cjs";
|
|
3
3
|
import { AdvertExtensionPolicy } from "./advert-extension-policy.cjs";
|
|
4
|
+
import { Mailbox, MailboxRefusalReason } from "./hub-mailbox.cjs";
|
|
4
5
|
import { PeerAdvertVerifier } from "./peer-advert.cjs";
|
|
5
6
|
//#region src/domain/relay-hub.d.ts
|
|
6
7
|
/**
|
|
@@ -36,6 +37,15 @@ export interface RelayHubOptions {
|
|
|
36
37
|
readonly extensionPolicy?: AdvertExtensionPolicy;
|
|
37
38
|
/** Called with each advert refused by extensionPolicy, so the host can report it; the advert's sender is given no reply, since the transport spec defines none. */
|
|
38
39
|
readonly onAdvertRefused?: (refusal: Readonly<AdvertRefusal>) => void;
|
|
40
|
+
/**
|
|
41
|
+
* The announcer role: a mailbox that holds other devices' `core/data` logs while they are offline (hub-mailbox.ts). Absent, data frames sent to this hub are ignored, as they always were.
|
|
42
|
+
*/
|
|
43
|
+
readonly mailbox?: Mailbox;
|
|
44
|
+
/** Called with each data frame the mailbox refused and why, so the host can report it; the sender is given no reply. */
|
|
45
|
+
readonly onMailboxRefused?: (refusal: Readonly<{
|
|
46
|
+
device: DeviceId | undefined;
|
|
47
|
+
reason: MailboxRefusalReason;
|
|
48
|
+
}>) => void;
|
|
39
49
|
/** Called whenever the value exportConnection returns for a connection changes: it gossiped an advert, gained or lost a pairing, or had one of its adverts taken over by another connection. A host whose own instance can be torn down while its connections stay open (the Cloudflare hub's hibernating Durable Object) reads the new value back with exportConnection, persists it against the connection, and hands it to a later instance through restoreConnections. */
|
|
40
50
|
readonly onConnectionStateChanged?: (connection: Readonly<Connection>) => void;
|
|
41
51
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { D as DeviceId, N as Frame, et as PeerAdvert } from "../protocol-DCDae3z4.mjs";
|
|
2
2
|
import { t as Connection } from "../transport-iLBxIkRS.mjs";
|
|
3
3
|
import { AdvertExtensionPolicy } from "./advert-extension-policy.mjs";
|
|
4
|
+
import { Mailbox, MailboxRefusalReason } from "./hub-mailbox.mjs";
|
|
4
5
|
import { PeerAdvertVerifier } from "./peer-advert.mjs";
|
|
5
6
|
//#region src/domain/relay-hub.d.ts
|
|
6
7
|
/**
|
|
@@ -36,6 +37,15 @@ export interface RelayHubOptions {
|
|
|
36
37
|
readonly extensionPolicy?: AdvertExtensionPolicy;
|
|
37
38
|
/** Called with each advert refused by extensionPolicy, so the host can report it; the advert's sender is given no reply, since the transport spec defines none. */
|
|
38
39
|
readonly onAdvertRefused?: (refusal: Readonly<AdvertRefusal>) => void;
|
|
40
|
+
/**
|
|
41
|
+
* The announcer role: a mailbox that holds other devices' `core/data` logs while they are offline (hub-mailbox.ts). Absent, data frames sent to this hub are ignored, as they always were.
|
|
42
|
+
*/
|
|
43
|
+
readonly mailbox?: Mailbox;
|
|
44
|
+
/** Called with each data frame the mailbox refused and why, so the host can report it; the sender is given no reply. */
|
|
45
|
+
readonly onMailboxRefused?: (refusal: Readonly<{
|
|
46
|
+
device: DeviceId | undefined;
|
|
47
|
+
reason: MailboxRefusalReason;
|
|
48
|
+
}>) => void;
|
|
39
49
|
/** Called whenever the value exportConnection returns for a connection changes: it gossiped an advert, gained or lost a pairing, or had one of its adverts taken over by another connection. A host whose own instance can be torn down while its connections stay open (the Cloudflare hub's hibernating Durable Object) reads the new value back with exportConnection, persists it against the connection, and hands it to a later instance through restoreConnections. */
|
|
40
50
|
readonly onConnectionStateChanged?: (connection: Readonly<Connection>) => void;
|
|
41
51
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { verifyPeerAdvert } from "./peer-advert.mjs";
|
|
2
2
|
import { checkAdvertExtensions } from "./advert-extension-policy.mjs";
|
|
3
|
+
import { isDataFrame } from "./hub-mailbox.mjs";
|
|
3
4
|
//#region src/domain/relay-hub.ts
|
|
4
5
|
const HEX_RADIX = 16;
|
|
5
6
|
const HEX_DIGITS_PER_BYTE = 2;
|
|
@@ -191,6 +192,19 @@ function createRelayHub(options) {
|
|
|
191
192
|
});
|
|
192
193
|
return;
|
|
193
194
|
}
|
|
195
|
+
if (options.mailbox !== void 0 && isDataFrame(frame)) {
|
|
196
|
+
const sender = connectionDevice.get(connection);
|
|
197
|
+
const reply = await options.mailbox.handle(sender, frame, (reason) => {
|
|
198
|
+
options.onMailboxRefused?.({
|
|
199
|
+
device: sender,
|
|
200
|
+
reason
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
if (reply !== null) try {
|
|
204
|
+
await connection.send(reply);
|
|
205
|
+
} catch {}
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
194
208
|
if (frame.type === "ping") {
|
|
195
209
|
try {
|
|
196
210
|
await connection.send({ type: "pong" });
|
package/dist/ports/storage.d.cts
CHANGED
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
get: (key: string) => Promise<Uint8Array<ArrayBuffer> | undefined>;
|
|
4
|
-
set: (key: string, value: Uint8Array<ArrayBuffer>) => Promise<void>;
|
|
5
|
-
delete: (key: string) => Promise<void>;
|
|
6
|
-
/** Keys with the given prefix, in no particular order. */
|
|
7
|
-
keys: (prefix: string) => Promise<string[]>;
|
|
8
|
-
}
|
|
9
|
-
//#endregion
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.cjs";
|
|
2
|
+
export { KeyValueStorage };
|
package/dist/ports/storage.d.mts
CHANGED
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
get: (key: string) => Promise<Uint8Array<ArrayBuffer> | undefined>;
|
|
4
|
-
set: (key: string, value: Uint8Array<ArrayBuffer>) => Promise<void>;
|
|
5
|
-
delete: (key: string) => Promise<void>;
|
|
6
|
-
/** Keys with the given prefix, in no particular order. */
|
|
7
|
-
keys: (prefix: string) => Promise<string[]>;
|
|
8
|
-
}
|
|
9
|
-
//#endregion
|
|
1
|
+
import { t as KeyValueStorage } from "../storage-B403CRyu.mjs";
|
|
2
|
+
export { KeyValueStorage };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/ports/storage.d.ts
|
|
2
|
+
interface KeyValueStorage {
|
|
3
|
+
get: (key: string) => Promise<Uint8Array<ArrayBuffer> | undefined>;
|
|
4
|
+
set: (key: string, value: Uint8Array<ArrayBuffer>) => Promise<void>;
|
|
5
|
+
delete: (key: string) => Promise<void>;
|
|
6
|
+
/** Keys with the given prefix, in no particular order. */
|
|
7
|
+
keys: (prefix: string) => Promise<string[]>;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { KeyValueStorage as t };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/ports/storage.d.ts
|
|
2
|
+
interface KeyValueStorage {
|
|
3
|
+
get: (key: string) => Promise<Uint8Array<ArrayBuffer> | undefined>;
|
|
4
|
+
set: (key: string, value: Uint8Array<ArrayBuffer>) => Promise<void>;
|
|
5
|
+
delete: (key: string) => Promise<void>;
|
|
6
|
+
/** Keys with the given prefix, in no particular order. */
|
|
7
|
+
keys: (prefix: string) => Promise<string[]>;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { KeyValueStorage as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wire-mesh-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"cbor2": "2.3.0",
|
|
6
6
|
"cddl.js": "1.0.1",
|
|
@@ -118,6 +118,10 @@
|
|
|
118
118
|
"import": "./dist/domain/handshake.mjs",
|
|
119
119
|
"require": "./dist/domain/handshake.cjs"
|
|
120
120
|
},
|
|
121
|
+
"./domain/hub-mailbox": {
|
|
122
|
+
"import": "./dist/domain/hub-mailbox.mjs",
|
|
123
|
+
"require": "./dist/domain/hub-mailbox.cjs"
|
|
124
|
+
},
|
|
121
125
|
"./domain/mesh-session": {
|
|
122
126
|
"import": "./dist/domain/mesh-session.mjs",
|
|
123
127
|
"require": "./dist/domain/mesh-session.cjs"
|