@kubun/plugin-p2p 0.10.0 → 0.11.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/lib/context/delegation.js +130 -1
- package/lib/context/group.js +1319 -1
- package/lib/context/hub.js +150 -1
- package/lib/context/join.js +80 -1
- package/lib/context/sync.js +19 -1
- package/lib/context/types.d.ts +12 -3
- package/lib/context/types.js +28 -1
- package/lib/groups/admin-roster.d.ts +130 -0
- package/lib/groups/admin-roster.js +184 -0
- package/lib/groups/broadcast-codec.js +6 -1
- package/lib/groups/broadcast.d.ts +122 -38
- package/lib/groups/broadcast.js +627 -1
- package/lib/groups/circle-projection.d.ts +122 -0
- package/lib/groups/circle-projection.js +288 -0
- package/lib/groups/circle-reducers.d.ts +178 -0
- package/lib/groups/circle-reducers.js +198 -0
- package/lib/groups/events.d.ts +7 -2
- package/lib/groups/events.js +10 -1
- package/lib/groups/group-anchor.d.ts +63 -0
- package/lib/groups/group-anchor.js +107 -0
- package/lib/groups/group-crypto.d.ts +19 -0
- package/lib/groups/group-crypto.js +85 -0
- package/lib/groups/group-handle-registry.d.ts +69 -2
- package/lib/groups/group-handle-registry.js +243 -1
- package/lib/groups/group-handlers.d.ts +29 -0
- package/lib/groups/group-handlers.js +92 -0
- package/lib/groups/group-health-monitor.d.ts +56 -0
- package/lib/groups/group-health-monitor.js +139 -0
- package/lib/groups/group-mls.d.ts +35 -0
- package/lib/groups/group-mls.js +164 -0
- package/lib/groups/group-peer-manager.d.ts +108 -0
- package/lib/groups/group-peer-manager.js +344 -0
- package/lib/groups/group-protocols.d.ts +297 -0
- package/lib/groups/group-protocols.js +174 -0
- package/lib/groups/invite-payload.d.ts +1 -1
- package/lib/groups/invite-payload.js +26 -1
- package/lib/groups/join-utils.d.ts +1 -0
- package/lib/groups/join-utils.js +104 -1
- package/lib/groups/ledger-affected-events.d.ts +15 -0
- package/lib/groups/ledger-affected-events.js +96 -0
- package/lib/groups/ledger-entry.d.ts +37 -0
- package/lib/groups/ledger-entry.js +56 -0
- package/lib/groups/ledger-fold.d.ts +56 -0
- package/lib/groups/ledger-fold.js +53 -0
- package/lib/groups/manager.d.ts +29 -71
- package/lib/groups/manager.js +281 -1
- package/lib/groups/mls-codec.d.ts +1 -1
- package/lib/groups/mls-codec.js +18 -1
- package/lib/groups/mls-encryptor.d.ts +2 -2
- package/lib/groups/mls-encryptor.js +30 -1
- package/lib/groups/mls-group-handle.d.ts +1 -1
- package/lib/groups/mls-group-handle.js +10 -1
- package/lib/groups/mls-json.js +17 -1
- package/lib/groups/mls-receive-errors.d.ts +29 -0
- package/lib/groups/mls-receive-errors.js +42 -0
- package/lib/groups/mls-state.d.ts +1 -1
- package/lib/groups/mls-state.js +56 -1
- package/lib/groups/store-received-grant.d.ts +4 -1
- package/lib/groups/store-received-grant.js +116 -1
- package/lib/groups/store-received-revocation.d.ts +3 -3
- package/lib/groups/store-received-revocation.js +82 -1
- package/lib/hub/ack-backoff.d.ts +22 -0
- package/lib/hub/ack-backoff.js +88 -0
- package/lib/hub/did-observing-transport.d.ts +13 -2
- package/lib/hub/did-observing-transport.js +126 -1
- package/lib/hub/errors.d.ts +4 -8
- package/lib/hub/errors.js +33 -1
- package/lib/hub/http-client.d.ts +28 -3
- package/lib/hub/http-client.js +32 -1
- package/lib/hub/hub-like.d.ts +24 -0
- package/lib/hub/hub-like.js +535 -0
- package/lib/hub/manager.d.ts +5 -4
- package/lib/hub/manager.js +198 -1
- package/lib/hub/wiring.d.ts +28 -41
- package/lib/hub/wiring.js +132 -1
- package/lib/index.d.ts +29 -13
- package/lib/index.js +392 -1
- package/lib/protocol.d.ts +8 -1
- package/lib/protocol.js +251 -1
- package/lib/schema.d.ts +2 -1
- package/lib/schema.js +471 -2
- package/lib/sync/authorize.d.ts +25 -0
- package/lib/sync/authorize.js +73 -0
- package/lib/sync/broadcast-queue.js +183 -1
- package/lib/sync/broadcast-sender.js +179 -1
- package/lib/sync/catalog-scope.js +68 -1
- package/lib/sync/errors.d.ts +12 -0
- package/lib/sync/errors.js +19 -0
- package/lib/sync/forwarder.js +277 -1
- package/lib/sync/handlers.d.ts +3 -22
- package/lib/sync/handlers.js +160 -1
- package/lib/sync/hub-tunnel-sync-listener.d.ts +9 -3
- package/lib/sync/hub-tunnel-sync-listener.js +172 -1
- package/lib/sync/hub-tunnel-sync-provider.d.ts +14 -14
- package/lib/sync/hub-tunnel-sync-provider.js +64 -1
- package/lib/sync/merkle-apply.d.ts +5 -0
- package/lib/sync/merkle-apply.js +194 -1
- package/lib/sync/merkle-channel.d.ts +19 -0
- package/lib/sync/merkle-channel.js +182 -1
- package/lib/sync/merkle-tree.js +120 -1
- package/lib/sync/peer-registry.d.ts +8 -1
- package/lib/sync/peer-registry.js +107 -1
- package/lib/sync/receive-access-gate.js +24 -1
- package/lib/sync/scope-resolver.js +41 -1
- package/lib/sync/sync-client.d.ts +5 -3
- package/lib/sync/sync-client.js +192 -1
- package/lib/sync/sync-manager.d.ts +2 -2
- package/lib/sync/sync-manager.js +272 -1
- package/lib/types.d.ts +41 -0
- package/lib/types.js +2 -1
- package/lib/util/mutex.js +10 -1
- package/lib/utils.d.ts +5 -0
- package/lib/utils.js +21 -0
- package/package.json +48 -41
- package/lib/groups/broadcast-service.d.ts +0 -189
- package/lib/groups/broadcast-service.js +0 -1
- package/lib/groups/wire-frame.d.ts +0 -32
- package/lib/groups/wire-frame.js +0 -1
- package/lib/hub/connection-pool.d.ts +0 -43
- package/lib/hub/connection-pool.js +0 -1
- package/lib/hub/forward-remote-broadcast.d.ts +0 -15
- package/lib/hub/forward-remote-broadcast.js +0 -1
- package/lib/hub/group-channel.d.ts +0 -67
- package/lib/hub/group-channel.js +0 -1
- package/lib/hub/hub-connection.d.ts +0 -96
- package/lib/hub/hub-connection.js +0 -1
- package/lib/hub/receive-handler.d.ts +0 -33
- package/lib/hub/receive-handler.js +0 -1
- package/lib/hub/relay-manager.d.ts +0 -127
- package/lib/hub/relay-manager.js +0 -1
- package/lib/hub/send-handler.d.ts +0 -40
- package/lib/hub/send-handler.js +0 -1
- package/lib/hub/tunnel-inbox.d.ts +0 -20
- package/lib/hub/tunnel-inbox.js +0 -1
- package/lib/hub/wait-for-gate.d.ts +0 -14
- package/lib/hub/wait-for-gate.js +0 -1
package/lib/hub/manager.js
CHANGED
|
@@ -1 +1,198 @@
|
|
|
1
|
-
import{getP2PStore
|
|
1
|
+
import { getP2PStore } from '@kubun/store-p2p';
|
|
2
|
+
export class HubServerDIDConflictError extends Error {
|
|
3
|
+
#hubID;
|
|
4
|
+
#url;
|
|
5
|
+
#existingServerDID;
|
|
6
|
+
#providedServerDID;
|
|
7
|
+
constructor(params){
|
|
8
|
+
super(`hub at ${params.url} is pinned to ${params.existingServerDID}; refusing to overwrite with ${params.providedServerDID}`);
|
|
9
|
+
this.name = 'HubServerDIDConflictError';
|
|
10
|
+
this.#hubID = params.hubID;
|
|
11
|
+
this.#url = params.url;
|
|
12
|
+
this.#existingServerDID = params.existingServerDID;
|
|
13
|
+
this.#providedServerDID = params.providedServerDID;
|
|
14
|
+
}
|
|
15
|
+
get hubID() {
|
|
16
|
+
return this.#hubID;
|
|
17
|
+
}
|
|
18
|
+
get url() {
|
|
19
|
+
return this.#url;
|
|
20
|
+
}
|
|
21
|
+
get existingServerDID() {
|
|
22
|
+
return this.#existingServerDID;
|
|
23
|
+
}
|
|
24
|
+
get providedServerDID() {
|
|
25
|
+
return this.#providedServerDID;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function rowToHub(row) {
|
|
29
|
+
return {
|
|
30
|
+
id: row.id,
|
|
31
|
+
label: row.label,
|
|
32
|
+
url: row.url,
|
|
33
|
+
serverDID: row.server_did,
|
|
34
|
+
createdAt: new Date(row.created_at),
|
|
35
|
+
updatedAt: row.updated_at == null ? null : new Date(row.updated_at)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export async function createHub(params) {
|
|
39
|
+
const store = await getP2PStore(params.stores);
|
|
40
|
+
const providedDID = params.serverDID ?? null;
|
|
41
|
+
const existing = await store.getHubByURL(params.url);
|
|
42
|
+
if (existing != null) {
|
|
43
|
+
if (existing.server_did != null && providedDID != null && existing.server_did !== providedDID) {
|
|
44
|
+
throw new HubServerDIDConflictError({
|
|
45
|
+
hubID: existing.id,
|
|
46
|
+
url: params.url,
|
|
47
|
+
existingServerDID: existing.server_did,
|
|
48
|
+
providedServerDID: providedDID
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (existing.server_did == null && providedDID != null) {
|
|
52
|
+
await store.updateHub(existing.id, {
|
|
53
|
+
server_did: providedDID
|
|
54
|
+
});
|
|
55
|
+
const updated = await store.getHub(existing.id);
|
|
56
|
+
if (updated == null) throw new Error('Failed to read hub after upgrade');
|
|
57
|
+
return rowToHub(updated);
|
|
58
|
+
}
|
|
59
|
+
return rowToHub(existing);
|
|
60
|
+
}
|
|
61
|
+
const id = crypto.randomUUID();
|
|
62
|
+
await store.insertHub({
|
|
63
|
+
id,
|
|
64
|
+
label: params.label ?? null,
|
|
65
|
+
url: params.url,
|
|
66
|
+
server_did: providedDID
|
|
67
|
+
});
|
|
68
|
+
const created = await store.getHub(id);
|
|
69
|
+
if (created == null) throw new Error('Failed to read hub after insert');
|
|
70
|
+
return rowToHub(created);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Idempotent register-or-pin entry-point. Applies the conflict matrix:
|
|
74
|
+
*
|
|
75
|
+
* - URL not registered → insert a fresh row with `serverDID` (or null) and
|
|
76
|
+
* return it.
|
|
77
|
+
* - existing row, `server_did === null`, provided null → no-op.
|
|
78
|
+
* - existing row, `server_did === null`, provided DID-A → upgrade pin to
|
|
79
|
+
* DID-A.
|
|
80
|
+
* - existing row, `server_did === DID-A`, provided null or matching DID-A →
|
|
81
|
+
* no-op (no silent downgrade).
|
|
82
|
+
* - existing row, `server_did === DID-A`, provided DID-B → throws
|
|
83
|
+
* `HubServerDIDConflictError`. Caller must explicit `updateHub` to change
|
|
84
|
+
* a pinned DID.
|
|
85
|
+
*
|
|
86
|
+
* Unlike `createHub`, `upsertHub` takes no `label` — it is for atomic
|
|
87
|
+
* register+bind paths where the inviter only ships connectivity + pin.
|
|
88
|
+
*/ export async function upsertHub(params) {
|
|
89
|
+
return await createHub({
|
|
90
|
+
stores: params.stores,
|
|
91
|
+
url: params.url,
|
|
92
|
+
serverDID: params.serverDID
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export async function updateHub(params) {
|
|
96
|
+
const store = await getP2PStore(params.stores);
|
|
97
|
+
const existing = await store.getHub(params.id);
|
|
98
|
+
if (existing == null) {
|
|
99
|
+
throw new Error(`Hub ${params.id} not found`);
|
|
100
|
+
}
|
|
101
|
+
if (params.url != null && params.url !== existing.url) {
|
|
102
|
+
const collision = await store.getHubByURL(params.url);
|
|
103
|
+
if (collision != null && collision.id !== params.id) {
|
|
104
|
+
throw new Error(`URL ${params.url} already in use by hub ${collision.id}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const update = {};
|
|
108
|
+
if (params.label !== undefined) update.label = params.label;
|
|
109
|
+
if (params.url !== undefined) update.url = params.url;
|
|
110
|
+
if (params.serverDID !== undefined) update.server_did = params.serverDID;
|
|
111
|
+
await store.updateHub(params.id, update);
|
|
112
|
+
const updated = await store.getHub(params.id);
|
|
113
|
+
if (updated == null) throw new Error('Failed to read hub after update');
|
|
114
|
+
return rowToHub(updated);
|
|
115
|
+
}
|
|
116
|
+
export async function deleteHub(params) {
|
|
117
|
+
const store = await getP2PStore(params.stores);
|
|
118
|
+
const boundGroupIDs = await store.listGroupIDsByHubID(params.id);
|
|
119
|
+
if (boundGroupIDs.length > 0 && !params.force) {
|
|
120
|
+
return {
|
|
121
|
+
success: false,
|
|
122
|
+
boundGroupIDs
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (boundGroupIDs.length > 0) {
|
|
126
|
+
await store.deleteGroupHubsByHubID(params.id);
|
|
127
|
+
}
|
|
128
|
+
await store.deleteHub(params.id);
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
boundGroupIDs
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
export async function getHub(params) {
|
|
135
|
+
const store = await getP2PStore(params.stores);
|
|
136
|
+
const row = await store.getHub(params.id);
|
|
137
|
+
return row == null ? null : rowToHub(row);
|
|
138
|
+
}
|
|
139
|
+
export async function listHubs(params) {
|
|
140
|
+
const store = await getP2PStore(params.stores);
|
|
141
|
+
const rows = await store.listHubs();
|
|
142
|
+
return rows.map(rowToHub);
|
|
143
|
+
}
|
|
144
|
+
export async function bindHubToGroup(params) {
|
|
145
|
+
const store = await getP2PStore(params.stores);
|
|
146
|
+
return await store.insertGroupHub({
|
|
147
|
+
group_id: params.groupID,
|
|
148
|
+
hub_id: params.hubID
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export async function unbindHubFromGroup(params) {
|
|
152
|
+
const store = await getP2PStore(params.stores);
|
|
153
|
+
return await store.deleteGroupHub({
|
|
154
|
+
groupID: params.groupID,
|
|
155
|
+
hubID: params.hubID
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
export async function listHubsByGroup(params) {
|
|
159
|
+
const store = await getP2PStore(params.stores);
|
|
160
|
+
const rows = await store.listHubsByGroupID(params.groupID);
|
|
161
|
+
return rows.map(rowToHub);
|
|
162
|
+
}
|
|
163
|
+
export async function listGroupsByHub(params) {
|
|
164
|
+
const store = await getP2PStore(params.stores);
|
|
165
|
+
return await store.listGroupIDsByHubID(params.hubID);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* TOFU capture path. Looks up the local hub row by URL and applies the
|
|
169
|
+
* conflict matrix:
|
|
170
|
+
* - URL not registered → no-op, returns `false`.
|
|
171
|
+
* - existing `server_did` is null → upgrade to `serverDID`, returns `true`.
|
|
172
|
+
* - existing `server_did` matches `serverDID` → no-op, returns `false`.
|
|
173
|
+
* - existing `server_did` differs from `serverDID` → throws
|
|
174
|
+
* `HubServerDIDConflictError`. Pinned-mismatch enforcement (hard-fail
|
|
175
|
+
* before any message is delivered) lives in the transport wrapper and
|
|
176
|
+
* surfaces `HubServerDIDMismatchError` to callers.
|
|
177
|
+
*/ export async function captureServerDID(params) {
|
|
178
|
+
const store = await getP2PStore(params.stores);
|
|
179
|
+
const existing = await store.getHubByURL(params.hubURL);
|
|
180
|
+
if (existing == null) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (existing.server_did == null) {
|
|
184
|
+
await store.updateHub(existing.id, {
|
|
185
|
+
server_did: params.serverDID
|
|
186
|
+
});
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
if (existing.server_did === params.serverDID) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
throw new HubServerDIDConflictError({
|
|
193
|
+
hubID: existing.id,
|
|
194
|
+
url: params.hubURL,
|
|
195
|
+
existingServerDID: existing.server_did,
|
|
196
|
+
providedServerDID: params.serverDID
|
|
197
|
+
});
|
|
198
|
+
}
|
package/lib/hub/wiring.d.ts
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import type { OwnIdentity } from '@
|
|
1
|
+
import type { OwnIdentity } from '@kokuin/token';
|
|
2
2
|
import type { KubunDB } from '@kubun/db';
|
|
3
3
|
import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
|
|
4
|
+
import type { HLC } from '@kubun/hlc';
|
|
4
5
|
import type { Logger } from '@kubun/logger';
|
|
5
6
|
import type { GroupBroadcastMessage } from '../groups/broadcast.js';
|
|
6
|
-
import { type BroadcastEvent } from '../groups/broadcast-service.js';
|
|
7
7
|
import type { P2PEventEmitter } from '../groups/events.js';
|
|
8
8
|
import type { GroupHandleRegistry } from '../groups/group-handle-registry.js';
|
|
9
|
-
import type {
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
12
|
-
import type { GroupChannel, GroupChannelParams } from './group-channel.js';
|
|
13
|
-
import type { HubConnection, HubConnectionParams } from './hub-connection.js';
|
|
14
|
-
import { type CreateHubClient } from './relay-manager.js';
|
|
9
|
+
import type { RejoinResult, StoreUnreadableMode } from '../types.js';
|
|
10
|
+
import type { OnServerDIDObserved } from './did-observing-transport.js';
|
|
11
|
+
import type { CreateHubClient } from './http-client.js';
|
|
15
12
|
export type HubWiring = {
|
|
16
13
|
ready: Promise<void>;
|
|
17
14
|
scheduleBroadcast: (groupID: string, message: GroupBroadcastMessage) => void;
|
|
@@ -24,62 +21,52 @@ export type HubWiring = {
|
|
|
24
21
|
broadcastNow: (groupID: string, message: GroupBroadcastMessage) => Promise<void>;
|
|
25
22
|
/**
|
|
26
23
|
* Fire-and-forget fan-out of a pre-produced MLS handshake Commit to a
|
|
27
|
-
* group's existing members. Wired to `
|
|
24
|
+
* group's existing members. Wired to `GroupPeerManager.sendCommit`.
|
|
28
25
|
*/
|
|
29
26
|
scheduleSendCommit: (groupID: string, commitBytes: Uint8Array) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Run stale-device MLS epoch recovery for an active group. Wired to
|
|
29
|
+
* `GroupPeerManager.rejoinGroup`; deep recovery is deferred so this currently
|
|
30
|
+
* reports `recovery-failed`.
|
|
31
|
+
*/
|
|
32
|
+
rejoinGroup: (groupID: string) => Promise<RejoinResult>;
|
|
30
33
|
dispose: () => Promise<void>;
|
|
31
34
|
};
|
|
32
35
|
export type SetupHubRelayParams = {
|
|
33
36
|
identity: OwnIdentity;
|
|
37
|
+
/** Fresh correlation ids for group-rpc. */
|
|
38
|
+
getRandomID: () => string;
|
|
34
39
|
db: KubunDB;
|
|
35
40
|
/**
|
|
36
|
-
* Engine graph internals — threaded through to
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
41
|
+
* Engine graph internals — threaded through to the per-group peer apply path
|
|
42
|
+
* so a `sync/mutationApply` frame routes peer-authored mutations through the
|
|
43
|
+
* engine with `origin: 'peer'`.
|
|
39
44
|
*/
|
|
40
45
|
graph: GraphInternals;
|
|
41
46
|
emitter: P2PEventEmitter;
|
|
42
47
|
createHubClient: CreateHubClient;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Single canonical access point for MLS `GroupHandle` instances. Threaded
|
|
46
|
-
* into `BroadcastService` so encrypt/decrypt go through the per-group
|
|
47
|
-
* mutex once Phase 3 lands. Currently stored but unused.
|
|
48
|
-
*/
|
|
48
|
+
/** Single canonical access point for MLS `GroupHandle` instances. */
|
|
49
49
|
registry: GroupHandleRegistry;
|
|
50
50
|
logger: Logger;
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* constructor.
|
|
55
|
-
*/
|
|
56
|
-
createGroupChannel?: (params: GroupChannelParams) => GroupChannel;
|
|
57
|
-
/**
|
|
58
|
-
* Optional testing seam forwarded to `HubRelayManager`. Production code
|
|
59
|
-
* leaves this unset so the manager uses its default `HubConnection`
|
|
60
|
-
* constructor.
|
|
61
|
-
*/
|
|
62
|
-
createHubConnection?: (params: HubConnectionParams) => HubConnection;
|
|
63
|
-
/**
|
|
64
|
-
* Receive-side storage mode. Forwarded to `BroadcastService` →
|
|
65
|
-
* `processBroadcast`. Defaults to `'persist'` when omitted.
|
|
52
|
+
* Receive-side storage mode. Forwarded to the apply path. Defaults to
|
|
53
|
+
* `'persist'` when omitted.
|
|
66
54
|
*/
|
|
67
55
|
storeUnreadable?: StoreUnreadableMode;
|
|
68
56
|
/** Server default access level — required when `storeUnreadable === 'drop'`. */
|
|
69
57
|
defaultAccessLevel?: DefaultAccessLevel;
|
|
70
58
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
59
|
+
* Fired with the observed `serverDID` after the first signed response from a
|
|
60
|
+
* freshly-spawned client, hub URL bound. Lets callers route capture into
|
|
61
|
+
* `captureServerDID` for TOFU pinning; omitting it skips capture.
|
|
74
62
|
*/
|
|
75
|
-
|
|
63
|
+
onServerDIDObserved?: OnServerDIDObserved;
|
|
76
64
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* pinning; omitting it skips capture entirely.
|
|
65
|
+
* Device-wide monotonic clock forwarded to the apply path for stamping
|
|
66
|
+
* `removed_at_hlc` on roster reconciliation. Required: production passes the
|
|
67
|
+
* engine's single instance so roster writes share the device's one clock.
|
|
81
68
|
*/
|
|
82
|
-
|
|
69
|
+
hlc: HLC;
|
|
83
70
|
};
|
|
84
71
|
export declare function setupHubRelay(params: SetupHubRelayParams): HubWiring;
|
|
85
72
|
export declare const DISABLED_HUB: HubWiring;
|
package/lib/hub/wiring.js
CHANGED
|
@@ -1 +1,132 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import { createGroupPeerManager } from '../groups/group-peer-manager.js';
|
|
2
|
+
export function setupHubRelay(params) {
|
|
3
|
+
const { identity, getRandomID, db, graph, emitter, createHubClient, registry, logger, storeUnreadable, defaultAccessLevel, onServerDIDObserved, hlc } = params;
|
|
4
|
+
const unsubscribes = [];
|
|
5
|
+
// Late-bound `ready` resolving to the live manager. The fire-and-forget
|
|
6
|
+
// schedulers chain off it so a caller can enqueue work before boot completes;
|
|
7
|
+
// the awaitable variants simply await it.
|
|
8
|
+
let ready;
|
|
9
|
+
const scheduleBroadcast = (groupID, message)=>{
|
|
10
|
+
void ready.then((manager)=>manager.broadcast(groupID, message).catch((error)=>{
|
|
11
|
+
logger.error('broadcast failed', {
|
|
12
|
+
groupID,
|
|
13
|
+
error
|
|
14
|
+
});
|
|
15
|
+
}), ()=>{
|
|
16
|
+
// ready rejected; setup failure already logged below
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const broadcastNow = async (groupID, message)=>{
|
|
20
|
+
const manager = await ready;
|
|
21
|
+
await manager.broadcast(groupID, message);
|
|
22
|
+
};
|
|
23
|
+
const scheduleSendCommit = (groupID, commitBytes)=>{
|
|
24
|
+
void ready.then((manager)=>manager.sendCommit(groupID, commitBytes).catch((error)=>{
|
|
25
|
+
logger.error('sendCommit failed', {
|
|
26
|
+
groupID,
|
|
27
|
+
error
|
|
28
|
+
});
|
|
29
|
+
}), ()=>{
|
|
30
|
+
// ready rejected; setup failure already logged below
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const rejoinGroup = async (groupID)=>{
|
|
34
|
+
const manager = await ready;
|
|
35
|
+
return await manager.rejoinGroup(groupID);
|
|
36
|
+
};
|
|
37
|
+
ready = (async ()=>{
|
|
38
|
+
const [p2pStore, graphStore] = await Promise.all([
|
|
39
|
+
db.getStore('p2p'),
|
|
40
|
+
db.getStore('graph')
|
|
41
|
+
]);
|
|
42
|
+
const manager = createGroupPeerManager({
|
|
43
|
+
createHubClient,
|
|
44
|
+
registry,
|
|
45
|
+
stores: db,
|
|
46
|
+
graphStore,
|
|
47
|
+
graph,
|
|
48
|
+
hlc,
|
|
49
|
+
localDID: identity.id,
|
|
50
|
+
emitter,
|
|
51
|
+
getRandomID,
|
|
52
|
+
logger,
|
|
53
|
+
storeUnreadable,
|
|
54
|
+
defaultAccessLevel,
|
|
55
|
+
onServerDIDObserved
|
|
56
|
+
});
|
|
57
|
+
unsubscribes.push(emitter.on('groupJoined', (group)=>manager.addGroup(group.id).catch((error)=>{
|
|
58
|
+
logger.error('addGroup failed', {
|
|
59
|
+
groupID: group.id,
|
|
60
|
+
error
|
|
61
|
+
});
|
|
62
|
+
})));
|
|
63
|
+
unsubscribes.push(emitter.on('groupLeft', (group)=>manager.removeGroup(group.groupID).catch((error)=>{
|
|
64
|
+
logger.error('removeGroup failed', {
|
|
65
|
+
groupID: group.groupID,
|
|
66
|
+
error
|
|
67
|
+
});
|
|
68
|
+
})));
|
|
69
|
+
unsubscribes.push(emitter.on('hubBound', ({ groupID, hubURL })=>manager.addBinding(groupID, hubURL).catch((error)=>{
|
|
70
|
+
logger.error('addBinding failed', {
|
|
71
|
+
groupID,
|
|
72
|
+
hubURL,
|
|
73
|
+
error
|
|
74
|
+
});
|
|
75
|
+
})));
|
|
76
|
+
unsubscribes.push(emitter.on('hubUnbound', ({ groupID, hubURL })=>manager.removeBinding(groupID, hubURL).catch((error)=>{
|
|
77
|
+
logger.error('removeBinding failed', {
|
|
78
|
+
groupID,
|
|
79
|
+
hubURL,
|
|
80
|
+
error
|
|
81
|
+
});
|
|
82
|
+
})));
|
|
83
|
+
const groups = await p2pStore.listGroups();
|
|
84
|
+
await manager.start(groups.map((g)=>g.id));
|
|
85
|
+
return manager;
|
|
86
|
+
})();
|
|
87
|
+
// Prevent an unhandled rejection if nobody awaits `ready` directly.
|
|
88
|
+
ready.catch((error)=>{
|
|
89
|
+
logger.error('hub relay setup failed', {
|
|
90
|
+
error
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
// Handle rejection here too: the base `.catch` covers the root `ready`, not
|
|
95
|
+
// this derived chain, and callers of `.ready` don't always await it.
|
|
96
|
+
ready: ready.then(()=>undefined, (error)=>{
|
|
97
|
+
logger.error('hub relay setup failed', {
|
|
98
|
+
error
|
|
99
|
+
});
|
|
100
|
+
}),
|
|
101
|
+
scheduleBroadcast,
|
|
102
|
+
broadcastNow,
|
|
103
|
+
scheduleSendCommit,
|
|
104
|
+
rejoinGroup,
|
|
105
|
+
dispose: async ()=>{
|
|
106
|
+
for (const off of unsubscribes){
|
|
107
|
+
try {
|
|
108
|
+
off();
|
|
109
|
+
} catch {
|
|
110
|
+
// ignore
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
unsubscribes.length = 0;
|
|
114
|
+
try {
|
|
115
|
+
const manager = await ready;
|
|
116
|
+
await manager.stop();
|
|
117
|
+
} catch {
|
|
118
|
+
// setup failed; nothing to stop
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export const DISABLED_HUB = {
|
|
124
|
+
ready: Promise.resolve(),
|
|
125
|
+
scheduleBroadcast: ()=>{},
|
|
126
|
+
broadcastNow: async ()=>{},
|
|
127
|
+
scheduleSendCommit: ()=>{},
|
|
128
|
+
rejoinGroup: async ()=>({
|
|
129
|
+
status: 'recovery-failed'
|
|
130
|
+
}),
|
|
131
|
+
dispose: async ()=>{}
|
|
132
|
+
};
|
package/lib/index.d.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import type { DefaultAccessLevel, KubunPlugin, PluginFactoryParams } from '@kubun/engine';
|
|
2
|
-
import type
|
|
2
|
+
import { type CreateHubClient } from './hub/http-client.js';
|
|
3
3
|
import { type BroadcastBatchConfig } from './sync/broadcast-queue.js';
|
|
4
4
|
import { type PushSyncConfig } from './sync/broadcast-sender.js';
|
|
5
5
|
import type { ForwardingConfig } from './sync/forwarder.js';
|
|
6
6
|
import type { StoreUnreadableMode } from './types.js';
|
|
7
7
|
export type { ContextDeps, PendingJoinRequest } from './context/types.js';
|
|
8
|
+
export { ADMIN_ROLE_ENTRY_TYPE, ADMIN_ROSTER_PROJECTION, type AdminRoleValue, type AdminRosterState, adminRosterReducer, applyAdminEntryIncremental, foldAdminRoster, type ProjectAdminRosterParams, projectAdminRoster, projectAdminRosterFromStore, reprojectAdminRoster, } from './groups/admin-roster.js';
|
|
8
9
|
export { type AffectedRow, type GroupBroadcastMessage, type MutationApplyEntry, type ProcessBroadcastParams, type ProcessBroadcastResult, processBroadcast, } from './groups/broadcast.js';
|
|
9
10
|
export { deserializeBroadcast, serializeBroadcast } from './groups/broadcast-codec.js';
|
|
10
|
-
export { ApplyError, type BroadcastEvent, BroadcastService, type BroadcastServiceEvents, type BroadcastServiceParams, DecryptError, type ReceiveBroadcastParams, type ReceiveBroadcastResult, type SendBroadcastParams, } from './groups/broadcast-service.js';
|
|
11
11
|
export { createFilteredGenerator, createP2PEventEmitter, type P2PEventEmitter, type P2PEventMap, } from './groups/events.js';
|
|
12
|
+
export { type GroupAnchor, groupAnchorCapabilities, readGroupAnchor, } from './groups/group-anchor.js';
|
|
12
13
|
export { GroupHandleRegistry, type GroupHandleRegistryParams, type SeedParams, } from './groups/group-handle-registry.js';
|
|
13
14
|
export { decodeFullJoinRequest, decodeInvitePayload, decodeJoinRequest, encodeFullJoinRequest, encodeInvitePayload, encodeJoinRequest, type FullJoinRequestPayload, type InvitePayload, type JoinRequestPayload, type SuggestedHub, } from './groups/invite-payload.js';
|
|
14
|
-
export { type
|
|
15
|
+
export { type LedgerEntry, ledgerEntryDigest, signLedgerEntry, type VerifiedLedgerEntry, verifyLedgerEntry, } from './groups/ledger-entry.js';
|
|
16
|
+
export { type FoldInput, foldLedger, type LedgerReducer, } from './groups/ledger-fold.js';
|
|
17
|
+
export { type CreateGroupParams, type CreateGroupResult, GroupManager, type GroupManagerParams, type InviteToGroupParams, type InviteToGroupResult, type JoinGroupParams, type JoinGroupResult, type LeaveGroupParams, type LeaveGroupResult, type RemoveGroupMemberParams, type RemoveGroupMemberResult, type RemoveMemberParams, type RemoveMemberResult, } from './groups/manager.js';
|
|
15
18
|
export { MLSEncryptor, type MLSEncryptorParams } from './groups/mls-encryptor.js';
|
|
16
19
|
export { type MLSGroupHandle, restoreMLSGroupHandle } from './groups/mls-group-handle.js';
|
|
17
20
|
export { replacer as mlsJSONReplacer, reviver as mlsJSONReviver } from './groups/mls-json.js';
|
|
@@ -21,21 +24,17 @@ export { createP2PSchemaExtension } from './schema.js';
|
|
|
21
24
|
export { type BroadcastBatchConfig, type BroadcastQueue, type BroadcastQueueParams, createBroadcastQueue, DEFAULT_BROADCAST_BATCH_CONFIG, } from './sync/broadcast-queue.js';
|
|
22
25
|
export { type BroadcastSenderParams, DEFAULT_PUSH_SYNC_CONFIG, type PushSyncConfig, wireBroadcastSender, } from './sync/broadcast-sender.js';
|
|
23
26
|
export { type CatalogSyncScope, resolveCatalogSyncScopes } from './sync/catalog-scope.js';
|
|
27
|
+
export { SyncAccessDeniedError } from './sync/errors.js';
|
|
24
28
|
export { type EvaluateAndForwardParams, type EvaluateAndForwardResult, evaluateAndForward, type ForwardContext, type ForwardFilter, type ForwardingConfig, } from './sync/forwarder.js';
|
|
25
|
-
export { checkSyncDelegation
|
|
29
|
+
export { checkSyncDelegation } from './sync/handlers.js';
|
|
26
30
|
export { HubTunnelSyncListener, type HubTunnelSyncListenerParams, } from './sync/hub-tunnel-sync-listener.js';
|
|
27
31
|
export { HubTunnelSyncProvider, type HubTunnelSyncProviderParams, } from './sync/hub-tunnel-sync-provider.js';
|
|
28
|
-
export { type PeerConfig, type PeerConfigWithID, PeerRegistry } from './sync/peer-registry.js';
|
|
32
|
+
export { negotiateDirection, type PeerConfig, type PeerConfigWithID, PeerRegistry, } from './sync/peer-registry.js';
|
|
29
33
|
export { SyncClient, type SyncClientParams, type SyncScope, type SyncTransportProvider, } from './sync/sync-client.js';
|
|
30
34
|
export { type SyncEvent, SyncManager, type SyncManagerParams, type SyncSessionInfo, type SyncStatus, } from './sync/sync-manager.js';
|
|
31
|
-
export type
|
|
32
|
-
export {
|
|
33
|
-
export { ReconnectingError } from './hub/errors.js';
|
|
34
|
-
export type { GroupChannel, GroupChannelParams } from './hub/group-channel.js';
|
|
35
|
-
export { type CreateHTTPHubClientParams, createHTTPHubClient } from './hub/http-client.js';
|
|
36
|
-
export type { ConnectionLifecycleEvent } from './hub/hub-connection.js';
|
|
35
|
+
export { DIDObservingTransport, type DIDObservingTransportParams, type OnServerDIDObserved, type ServerDIDObserver, } from './hub/did-observing-transport.js';
|
|
36
|
+
export { type CreateHTTPHubClientParams, type CreateHubClient, type CreateHubClientOptions, createHTTPHubClient, } from './hub/http-client.js';
|
|
37
37
|
export { type BindHubToGroupParams, bindHubToGroup, type CaptureServerDIDParams, type CreateHubParams, captureServerDID, createHub, type DeleteHubParams, type DeleteHubResult, deleteHub, type GetHubParams, getHub, type Hub, HubServerDIDConflictError, type ListGroupsByHubParams, type ListHubsByGroupParams, type ListHubsParams, listGroupsByHub, listHubs, listHubsByGroup, type UnbindHubFromGroupParams, type UpdateHubParams, type UpsertHubParams, unbindHubFromGroup, updateHub, upsertHub, } from './hub/manager.js';
|
|
38
|
-
export { type CreateGroupChannel, type CreateHubClient, type CreateHubClientOptions, type CreateHubConnection, HubRelayManager, type HubRelayManagerParams, type RelayEvent, } from './hub/relay-manager.js';
|
|
39
38
|
export { DISABLED_HUB, type HubWiring, type SetupHubRelayParams, setupHubRelay, } from './hub/wiring.js';
|
|
40
39
|
export type { CatalogData, CircleData, CircleMemberData, DeleteCatalogData, DeleteCircleData, GroupData, GroupMemberData, GroupRequestContext, InviteToGroupData, JoinGroupData, LeaveGroupData, P2PContext, P2PJoinRequestContext, RemoveCircleMemberData, RemoveGroupMemberData, StoreUnreadableMode, SyncPluginAPI, SyncRequestContext, SyncSessionResult, UpdateCircleInput, UpdateGroupInput, } from './types.js';
|
|
41
40
|
export type ReceiveConfig = {
|
|
@@ -57,7 +56,24 @@ export type ReceiveConfig = {
|
|
|
57
56
|
export declare const DEFAULT_RECEIVE_CONFIG: ReceiveConfig;
|
|
58
57
|
export type P2PPluginOptions = {
|
|
59
58
|
autoAcceptPeers?: Array<string>;
|
|
60
|
-
|
|
59
|
+
/**
|
|
60
|
+
* Expose the sync protocol over the HTTP plugin transport.
|
|
61
|
+
* - `true` / `{}` — enable at the default protocol path `'sync'`. CORS
|
|
62
|
+
* defaults to deny cross-origin browser requests.
|
|
63
|
+
* - `'name'` / `'/name'` — enable at the given protocol path.
|
|
64
|
+
* - `{ path?, allowedOrigin?, bodyLimit? }` — `path` is the protocol path
|
|
65
|
+
* (default `'sync'`); `allowedOrigin` opts into cross-origin (default deny).
|
|
66
|
+
* The sync server authenticates every request, so CORS is defense-in-depth
|
|
67
|
+
* rather than the security boundary; non-browser clients are unaffected.
|
|
68
|
+
* `bodyLimit` is the maximum request body size in bytes (default 1 MiB),
|
|
69
|
+
* enforced by the transport.
|
|
70
|
+
* - absent / `false` — HTTP sync transport disabled.
|
|
71
|
+
*/
|
|
72
|
+
http?: boolean | string | {
|
|
73
|
+
path?: string;
|
|
74
|
+
allowedOrigin?: string | Array<string>;
|
|
75
|
+
bodyLimit?: number;
|
|
76
|
+
};
|
|
61
77
|
/**
|
|
62
78
|
* Enable hub-mediated group broadcasts.
|
|
63
79
|
* - `true` / `{}` — build a default HTTP hub client factory from
|