@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/index.js
CHANGED
|
@@ -1 +1,392 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ServerTransport } from '@enkaku/http-serve';
|
|
2
|
+
import { Server } from '@enkaku/server';
|
|
3
|
+
import { DirectTransports } from '@enkaku/transport';
|
|
4
|
+
import { isFullIdentity } from '@kokuin/token';
|
|
5
|
+
import { resolveAllowedOrigin } from '@kubun/http-util';
|
|
6
|
+
import { p2pStoreDefinition } from '@kubun/store-p2p';
|
|
7
|
+
import { createDelegationContext } from './context/delegation.js';
|
|
8
|
+
import { createGroupContext } from './context/group.js';
|
|
9
|
+
import { createHubContext } from './context/hub.js';
|
|
10
|
+
import { createJoinContext } from './context/join.js';
|
|
11
|
+
import { createSyncContext } from './context/sync.js';
|
|
12
|
+
import { createP2PEventEmitter } from './groups/events.js';
|
|
13
|
+
import { GroupHandleRegistry } from './groups/group-handle-registry.js';
|
|
14
|
+
import { GroupHealthMonitor } from './groups/group-health-monitor.js';
|
|
15
|
+
import { GroupManager } from './groups/manager.js';
|
|
16
|
+
import { createHTTPHubClient } from './hub/http-client.js';
|
|
17
|
+
import { captureServerDID } from './hub/manager.js';
|
|
18
|
+
import { DISABLED_HUB, setupHubRelay } from './hub/wiring.js';
|
|
19
|
+
import { createP2PSchemaExtension } from './schema.js';
|
|
20
|
+
import { createBroadcastQueue, DEFAULT_BROADCAST_BATCH_CONFIG } from './sync/broadcast-queue.js';
|
|
21
|
+
import { DEFAULT_PUSH_SYNC_CONFIG, wireBroadcastSender } from './sync/broadcast-sender.js';
|
|
22
|
+
import { createSyncHandlers } from './sync/handlers.js';
|
|
23
|
+
import { SyncManager } from './sync/sync-manager.js';
|
|
24
|
+
// ---- Re-exports: groups ----
|
|
25
|
+
export { ADMIN_ROLE_ENTRY_TYPE, ADMIN_ROSTER_PROJECTION, adminRosterReducer, applyAdminEntryIncremental, foldAdminRoster, projectAdminRoster, projectAdminRosterFromStore, reprojectAdminRoster } from './groups/admin-roster.js';
|
|
26
|
+
export { processBroadcast } from './groups/broadcast.js';
|
|
27
|
+
export { deserializeBroadcast, serializeBroadcast } from './groups/broadcast-codec.js';
|
|
28
|
+
export { createFilteredGenerator, createP2PEventEmitter } from './groups/events.js';
|
|
29
|
+
export { groupAnchorCapabilities, readGroupAnchor } from './groups/group-anchor.js';
|
|
30
|
+
export { GroupHandleRegistry } from './groups/group-handle-registry.js';
|
|
31
|
+
export { decodeFullJoinRequest, decodeInvitePayload, decodeJoinRequest, encodeFullJoinRequest, encodeInvitePayload, encodeJoinRequest } from './groups/invite-payload.js';
|
|
32
|
+
export { ledgerEntryDigest, signLedgerEntry, verifyLedgerEntry } from './groups/ledger-entry.js';
|
|
33
|
+
export { foldLedger } from './groups/ledger-fold.js';
|
|
34
|
+
export { GroupManager } from './groups/manager.js';
|
|
35
|
+
export { MLSEncryptor } from './groups/mls-encryptor.js';
|
|
36
|
+
export { restoreMLSGroupHandle } from './groups/mls-group-handle.js';
|
|
37
|
+
export { replacer as mlsJSONReplacer, reviver as mlsJSONReviver } from './groups/mls-json.js';
|
|
38
|
+
export { deserializeMLSGroupState, serializeMLSGroupState } from './groups/mls-state.js';
|
|
39
|
+
export { createP2PSchemaExtension } from './schema.js';
|
|
40
|
+
// ---- Re-exports: sync ----
|
|
41
|
+
export { createBroadcastQueue, DEFAULT_BROADCAST_BATCH_CONFIG } from './sync/broadcast-queue.js';
|
|
42
|
+
export { DEFAULT_PUSH_SYNC_CONFIG, wireBroadcastSender } from './sync/broadcast-sender.js';
|
|
43
|
+
export { resolveCatalogSyncScopes } from './sync/catalog-scope.js';
|
|
44
|
+
export { SyncAccessDeniedError } from './sync/errors.js';
|
|
45
|
+
export { evaluateAndForward } from './sync/forwarder.js';
|
|
46
|
+
export { checkSyncDelegation } from './sync/handlers.js';
|
|
47
|
+
export { HubTunnelSyncListener } from './sync/hub-tunnel-sync-listener.js';
|
|
48
|
+
export { HubTunnelSyncProvider } from './sync/hub-tunnel-sync-provider.js';
|
|
49
|
+
export { negotiateDirection, PeerRegistry } from './sync/peer-registry.js';
|
|
50
|
+
export { SyncClient } from './sync/sync-client.js';
|
|
51
|
+
export { SyncManager } from './sync/sync-manager.js';
|
|
52
|
+
// ---- Re-exports: hub ----
|
|
53
|
+
export { DIDObservingTransport } from './hub/did-observing-transport.js';
|
|
54
|
+
export { createHTTPHubClient } from './hub/http-client.js';
|
|
55
|
+
export { bindHubToGroup, captureServerDID, createHub, deleteHub, getHub, HubServerDIDConflictError, listGroupsByHub, listHubs, listHubsByGroup, unbindHubFromGroup, updateHub, upsertHub } from './hub/manager.js';
|
|
56
|
+
export { DISABLED_HUB, setupHubRelay } from './hub/wiring.js';
|
|
57
|
+
/**
|
|
58
|
+
* Default receive config. Under `'persist'` no gate is built and no extra
|
|
59
|
+
* DB lookups occur during receive.
|
|
60
|
+
*/ export const DEFAULT_RECEIVE_CONFIG = {
|
|
61
|
+
storeUnreadable: 'persist'
|
|
62
|
+
};
|
|
63
|
+
// ---- Plugin factory ----
|
|
64
|
+
function isOwnIdentity(identity) {
|
|
65
|
+
return isFullIdentity(identity) && 'privateKey' in identity;
|
|
66
|
+
}
|
|
67
|
+
export function createP2PPlugin(options) {
|
|
68
|
+
return (params)=>{
|
|
69
|
+
params.db.register(p2pStoreDefinition);
|
|
70
|
+
const adapter = params.db.adapter;
|
|
71
|
+
if (!isOwnIdentity(params.identity)) {
|
|
72
|
+
throw new Error('createP2PPlugin requires an OwnIdentity (full identity with privateKey) — received a narrower Identity');
|
|
73
|
+
}
|
|
74
|
+
const identity = params.identity;
|
|
75
|
+
// The engine's single device-wide clock. Sharing it across the p2p group/
|
|
76
|
+
// circle/member metadata paths (context, GroupManager, BroadcastService)
|
|
77
|
+
// keeps every write from this device on one monotonic counter, so two
|
|
78
|
+
// writes in the same millisecond mint strictly-increasing timestamps rather
|
|
79
|
+
// than identical ones (the second of which would silently lose under
|
|
80
|
+
// last-writer-wins).
|
|
81
|
+
const hlc = params.hlc;
|
|
82
|
+
// ---- Receive config ----
|
|
83
|
+
// Computed up front so SyncManager + sync handlers + the hub-relay
|
|
84
|
+
// BroadcastService all see the same receive policy. Hot-path invariant:
|
|
85
|
+
// when `storeUnreadable === 'persist'` (default), no gate is built and
|
|
86
|
+
// no extra DB lookups occur.
|
|
87
|
+
const receiveConfig = {
|
|
88
|
+
...DEFAULT_RECEIVE_CONFIG,
|
|
89
|
+
...options?.receive
|
|
90
|
+
};
|
|
91
|
+
const defaultAccessLevel = options?.defaultAccessLevel ?? {
|
|
92
|
+
read: 'only_owner',
|
|
93
|
+
write: 'only_owner'
|
|
94
|
+
};
|
|
95
|
+
// ---- Group handle registry ----
|
|
96
|
+
// Single canonical access point for the device's MLS GroupHandle instances.
|
|
97
|
+
// Threaded through GroupManager + BroadcastService + (Phase 4) tunnel
|
|
98
|
+
// listener/provider so all five mutation paths serialize through one
|
|
99
|
+
// per-group mutex with write-through persist.
|
|
100
|
+
const registry = new GroupHandleRegistry({
|
|
101
|
+
stores: params.db,
|
|
102
|
+
deviceID: identity.id,
|
|
103
|
+
logger: params.getLogger('group-handle-registry')
|
|
104
|
+
});
|
|
105
|
+
// ---- Group manager ----
|
|
106
|
+
const groupManager = new GroupManager({
|
|
107
|
+
identity,
|
|
108
|
+
getRandomID: params.runtime.getRandomID,
|
|
109
|
+
registry,
|
|
110
|
+
hlc
|
|
111
|
+
});
|
|
112
|
+
const emitter = createP2PEventEmitter();
|
|
113
|
+
const groupHealthMonitor = new GroupHealthMonitor({
|
|
114
|
+
emitter,
|
|
115
|
+
logger: params.getLogger('group-health-monitor')
|
|
116
|
+
});
|
|
117
|
+
// ---- Sync manager ----
|
|
118
|
+
const syncManager = new SyncManager({
|
|
119
|
+
deployClusters: async (clusters)=>{
|
|
120
|
+
// Values are ClusterModel objects serialized through the protocol —
|
|
121
|
+
// cast through unknown since SyncManager doesn't depend on protocol types.
|
|
122
|
+
const clusterModels = Object.values(clusters);
|
|
123
|
+
await params.engine.deployGraph({
|
|
124
|
+
clusters: clusterModels
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
graph: params.graph,
|
|
128
|
+
runtime: params.runtime,
|
|
129
|
+
identity: params.identity,
|
|
130
|
+
logger: params.getLogger('sync'),
|
|
131
|
+
storeUnreadable: receiveConfig.storeUnreadable,
|
|
132
|
+
defaultAccessLevel
|
|
133
|
+
});
|
|
134
|
+
const syncHandlers = createSyncHandlers({
|
|
135
|
+
db: params.db,
|
|
136
|
+
graph: params.graph,
|
|
137
|
+
logger: params.getLogger('sync-handlers'),
|
|
138
|
+
peerRegistry: syncManager.peerRegistry,
|
|
139
|
+
selfDID: identity.id,
|
|
140
|
+
storeUnreadable: receiveConfig.storeUnreadable,
|
|
141
|
+
defaultAccessLevel
|
|
142
|
+
});
|
|
143
|
+
// ---- Pending join requests (for CLI peering flow) ----
|
|
144
|
+
const pendingJoinRequests = new Map();
|
|
145
|
+
// ---- Sync server ----
|
|
146
|
+
// The p2p plugin manages its own Enkaku server for sync handlers,
|
|
147
|
+
// separate from plugin-rpc's graph server.
|
|
148
|
+
const syncServers = [];
|
|
149
|
+
function createSyncTransport(signal) {
|
|
150
|
+
const directTransports = new DirectTransports({
|
|
151
|
+
signal
|
|
152
|
+
});
|
|
153
|
+
const server = new Server({
|
|
154
|
+
getRandomID: params.runtime.getRandomID,
|
|
155
|
+
handlers: syncHandlers,
|
|
156
|
+
identity,
|
|
157
|
+
// Open at the procedure layer by design: the sync protocol's
|
|
158
|
+
// authorization is enforced downstream, per document (read-ACL and
|
|
159
|
+
// write-delegation tokens), not by a coarse caller allow-list here.
|
|
160
|
+
accessRules: {
|
|
161
|
+
'sync/*': {
|
|
162
|
+
allow: true
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
logger: params.getLogger('sync-server'),
|
|
166
|
+
transports: [
|
|
167
|
+
directTransports.server
|
|
168
|
+
],
|
|
169
|
+
signal
|
|
170
|
+
});
|
|
171
|
+
syncServers.push(server);
|
|
172
|
+
return directTransports.client;
|
|
173
|
+
}
|
|
174
|
+
// ---- Hub relay ----
|
|
175
|
+
const hubRelayLogger = params.getLogger('hub-relay');
|
|
176
|
+
let hub = DISABLED_HUB;
|
|
177
|
+
let unwireBroadcastSender;
|
|
178
|
+
let broadcastQueue;
|
|
179
|
+
const pushSyncConfig = {
|
|
180
|
+
...DEFAULT_PUSH_SYNC_CONFIG,
|
|
181
|
+
...options?.pushSync
|
|
182
|
+
};
|
|
183
|
+
if (options?.hub) {
|
|
184
|
+
const createHubClient = typeof options.hub === 'object' && options.hub.createHubClient != null ? options.hub.createHubClient : createHTTPHubClient({
|
|
185
|
+
identity,
|
|
186
|
+
fetch: params.runtime.fetch
|
|
187
|
+
});
|
|
188
|
+
hub = setupHubRelay({
|
|
189
|
+
identity,
|
|
190
|
+
getRandomID: params.runtime.getRandomID,
|
|
191
|
+
db: params.db,
|
|
192
|
+
hlc,
|
|
193
|
+
graph: params.graph,
|
|
194
|
+
emitter,
|
|
195
|
+
createHubClient,
|
|
196
|
+
registry,
|
|
197
|
+
logger: hubRelayLogger,
|
|
198
|
+
storeUnreadable: receiveConfig.storeUnreadable,
|
|
199
|
+
defaultAccessLevel,
|
|
200
|
+
// TOFU capture. The pool fires this once per fresh client when the
|
|
201
|
+
// first signed response arrives. Manager-level conflicts are caught
|
|
202
|
+
// and logged inside the pool; the pinned-DID mismatch path surfaces
|
|
203
|
+
// a typed error to callers via the transport wrapper.
|
|
204
|
+
onServerDIDObserved: ({ hubURL, serverDID })=>captureServerDID({
|
|
205
|
+
stores: params.db,
|
|
206
|
+
hubURL,
|
|
207
|
+
serverDID
|
|
208
|
+
}).then(()=>undefined)
|
|
209
|
+
});
|
|
210
|
+
// Subscribe to locally-authored mutations and fan out per-scope
|
|
211
|
+
// `mutation:apply` broadcasts through the hub. Gated independently from
|
|
212
|
+
// the hub itself: the hub is needed for receive + pull regardless, but
|
|
213
|
+
// push-sync is opt-in via `pushSync.enabled`. With `DISABLED_HUB` we'd
|
|
214
|
+
// also skip wiring (scheduleBroadcast no-ops + spurious broadcast_log
|
|
215
|
+
// rows), but the explicit gate here makes intent observable.
|
|
216
|
+
if (pushSyncConfig.enabled) {
|
|
217
|
+
const batchConfig = {
|
|
218
|
+
...DEFAULT_BROADCAST_BATCH_CONFIG,
|
|
219
|
+
...options.batch
|
|
220
|
+
};
|
|
221
|
+
broadcastQueue = createBroadcastQueue({
|
|
222
|
+
config: batchConfig,
|
|
223
|
+
scheduleBroadcast: hub.scheduleBroadcast,
|
|
224
|
+
logger: params.getLogger('broadcast-queue')
|
|
225
|
+
});
|
|
226
|
+
unwireBroadcastSender = wireBroadcastSender({
|
|
227
|
+
db: params.db,
|
|
228
|
+
eventBus: params.eventBus,
|
|
229
|
+
selfDID: identity.id,
|
|
230
|
+
queue: broadcastQueue,
|
|
231
|
+
logger: params.getLogger('broadcast-sender')
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// ---- Plugin API ----
|
|
236
|
+
const pluginAPI = {
|
|
237
|
+
hubReady: hub.ready,
|
|
238
|
+
addPeer: (config)=>syncManager.addPeer({
|
|
239
|
+
config,
|
|
240
|
+
stores: params.db
|
|
241
|
+
}),
|
|
242
|
+
removePeer: (peerDID)=>syncManager.removePeer({
|
|
243
|
+
peerDID,
|
|
244
|
+
stores: params.db
|
|
245
|
+
}),
|
|
246
|
+
listPeers: ()=>syncManager.listPeers({
|
|
247
|
+
stores: params.db
|
|
248
|
+
}),
|
|
249
|
+
syncWithPeer: (syncParams)=>syncManager.merkleSyncWithPeer({
|
|
250
|
+
...syncParams,
|
|
251
|
+
stores: params.db
|
|
252
|
+
}),
|
|
253
|
+
getStatus: (peerDID)=>syncManager.getStatus({
|
|
254
|
+
peerDID,
|
|
255
|
+
stores: params.db
|
|
256
|
+
}),
|
|
257
|
+
onSyncEvent: (callback)=>syncManager.onSyncEvent(callback),
|
|
258
|
+
createSyncTransport
|
|
259
|
+
};
|
|
260
|
+
// ---- HTTP sync transport (optional) ----
|
|
261
|
+
let httpSyncTransport;
|
|
262
|
+
let httpSyncServer;
|
|
263
|
+
if (options?.http) {
|
|
264
|
+
// Capture in the outer scope so the union is narrowed before the async
|
|
265
|
+
// closure below (re-reading options.http inside the IIFE would widen it).
|
|
266
|
+
const httpOption = options.http;
|
|
267
|
+
const rawPath = typeof httpOption === 'string' ? httpOption : typeof httpOption === 'object' ? httpOption.path ?? 'sync' : 'sync';
|
|
268
|
+
const protocolName = rawPath.replace(/^\//, '');
|
|
269
|
+
// Default-deny cross-origin browser requests, consistent with the other
|
|
270
|
+
// HTTP surfaces. The sync server authenticates every request (signed
|
|
271
|
+
// Enkaku procedures), so CORS is defense-in-depth here, not the security
|
|
272
|
+
// boundary; non-browser sync clients send no Origin and are unaffected.
|
|
273
|
+
// Operators opt into cross-origin via the object form's `allowedOrigin`.
|
|
274
|
+
const allowedOrigin = resolveAllowedOrigin(typeof httpOption === 'object' ? httpOption.allowedOrigin : undefined);
|
|
275
|
+
// Maximum request body size, enforced by the transport (default 1 MiB,
|
|
276
|
+
// matching the Enkaku ServerTransport default).
|
|
277
|
+
const maxRequestBodySize = (typeof httpOption === 'object' ? httpOption.bodyLimit : undefined) ?? 1024 * 1024;
|
|
278
|
+
pluginAPI.syncReady = (async ()=>{
|
|
279
|
+
const httpAPI = await params.engine.getAPI('http');
|
|
280
|
+
await httpAPI.listening;
|
|
281
|
+
httpSyncTransport = new ServerTransport({
|
|
282
|
+
allowedOrigin,
|
|
283
|
+
maxRequestBodySize
|
|
284
|
+
});
|
|
285
|
+
httpSyncServer = new Server({
|
|
286
|
+
getRandomID: params.runtime.getRandomID,
|
|
287
|
+
handlers: syncHandlers,
|
|
288
|
+
identity,
|
|
289
|
+
// Open at the procedure layer by design: the sync protocol's
|
|
290
|
+
// authorization is enforced downstream, per document (read-ACL and
|
|
291
|
+
// write-delegation tokens), not by a coarse caller allow-list here.
|
|
292
|
+
accessRules: {
|
|
293
|
+
'sync/*': {
|
|
294
|
+
allow: true
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
logger: params.getLogger('sync-http-server'),
|
|
298
|
+
transports: [
|
|
299
|
+
httpSyncTransport
|
|
300
|
+
]
|
|
301
|
+
});
|
|
302
|
+
syncServers.push(httpSyncServer);
|
|
303
|
+
httpAPI.registerProtocol(protocolName, httpSyncTransport.fetch.bind(httpSyncTransport));
|
|
304
|
+
})();
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
name: 'p2p',
|
|
308
|
+
schemaExtension: (_config)=>createP2PSchemaExtension(emitter, params.getLogger('p2p-schema')),
|
|
309
|
+
api: pluginAPI,
|
|
310
|
+
createContextFactory: ()=>{
|
|
311
|
+
return (ctx, stores)=>{
|
|
312
|
+
const deps = {
|
|
313
|
+
identity,
|
|
314
|
+
groupManager,
|
|
315
|
+
registry,
|
|
316
|
+
syncManager,
|
|
317
|
+
stores,
|
|
318
|
+
adapter,
|
|
319
|
+
hlc,
|
|
320
|
+
emitter,
|
|
321
|
+
pendingJoinRequests,
|
|
322
|
+
runtime: params.runtime,
|
|
323
|
+
autoAcceptPeers: options?.autoAcceptPeers,
|
|
324
|
+
scheduleBroadcast: hub.scheduleBroadcast,
|
|
325
|
+
broadcastNow: hub.broadcastNow,
|
|
326
|
+
scheduleSendCommit: hub.scheduleSendCommit,
|
|
327
|
+
monitor: groupHealthMonitor,
|
|
328
|
+
rejoinGroup: hub.rejoinGroup,
|
|
329
|
+
logger: params.getLogger('context')
|
|
330
|
+
};
|
|
331
|
+
return {
|
|
332
|
+
group: createGroupContext(ctx, deps),
|
|
333
|
+
sync: createSyncContext(ctx, deps),
|
|
334
|
+
join: createJoinContext(ctx, deps),
|
|
335
|
+
hub: createHubContext(ctx, deps),
|
|
336
|
+
delegation: createDelegationContext(ctx, deps)
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
},
|
|
340
|
+
dispose: async ()=>{
|
|
341
|
+
// Unwire the broadcast-sender subscriber first so no new broadcasts
|
|
342
|
+
// are enqueued while teardown is in flight.
|
|
343
|
+
if (unwireBroadcastSender != null) {
|
|
344
|
+
try {
|
|
345
|
+
unwireBroadcastSender();
|
|
346
|
+
} catch {
|
|
347
|
+
// ignore
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Synchronously drain the batch queue BEFORE other teardown so any
|
|
351
|
+
// pending broadcasts ship through the still-live hub transport.
|
|
352
|
+
if (broadcastQueue != null) {
|
|
353
|
+
try {
|
|
354
|
+
broadcastQueue.dispose();
|
|
355
|
+
} catch {
|
|
356
|
+
// ignore
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Sync HTTP server depends on httpSyncTransport — dispose servers
|
|
360
|
+
// first, then the transport. Runs in parallel with syncManager and
|
|
361
|
+
// hub teardown, which have no such dependency.
|
|
362
|
+
const httpPath = (async ()=>{
|
|
363
|
+
for (const server of syncServers){
|
|
364
|
+
await server.dispose();
|
|
365
|
+
}
|
|
366
|
+
if (httpSyncTransport != null) {
|
|
367
|
+
await httpSyncTransport.dispose();
|
|
368
|
+
}
|
|
369
|
+
})();
|
|
370
|
+
const disposeLogger = params.getLogger('dispose');
|
|
371
|
+
const results = await Promise.allSettled([
|
|
372
|
+
httpPath,
|
|
373
|
+
syncManager.dispose(),
|
|
374
|
+
hub.dispose()
|
|
375
|
+
]);
|
|
376
|
+
const labels = [
|
|
377
|
+
'sync-http',
|
|
378
|
+
'sync-manager',
|
|
379
|
+
'hub'
|
|
380
|
+
];
|
|
381
|
+
results.forEach((result, i)=>{
|
|
382
|
+
if (result.status === 'rejected') {
|
|
383
|
+
disposeLogger.error('dispose branch failed', {
|
|
384
|
+
branch: labels[i],
|
|
385
|
+
error: result.reason
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
};
|
|
392
|
+
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyClientMessageOf, AnyServerMessageOf } from '@enkaku/protocol';
|
|
2
|
-
import type { FromSchema } from '@
|
|
2
|
+
import type { FromSchema } from '@sozai/schema';
|
|
3
3
|
export declare const syncProtocol: {
|
|
4
4
|
readonly 'sync/negotiate': {
|
|
5
5
|
readonly type: "request";
|
|
@@ -129,6 +129,13 @@ export declare const syncProtocol: {
|
|
|
129
129
|
readonly type: "string";
|
|
130
130
|
readonly enum: readonly ["push", "pull", "both"];
|
|
131
131
|
};
|
|
132
|
+
readonly delegationTokens: {
|
|
133
|
+
readonly type: "array";
|
|
134
|
+
readonly items: {
|
|
135
|
+
readonly type: "string";
|
|
136
|
+
};
|
|
137
|
+
readonly description: "Delegation JWTs authorizing the caller for non-owned scopes";
|
|
138
|
+
};
|
|
132
139
|
};
|
|
133
140
|
readonly required: readonly ["scopes", "excludedDocumentIDs", "tree", "direction"];
|
|
134
141
|
readonly additionalProperties: false;
|