@kubun/plugin-p2p 0.10.1 → 0.12.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 +1646 -1
- package/lib/context/hub.js +151 -1
- package/lib/context/join.js +115 -1
- package/lib/context/peer.d.ts +92 -0
- package/lib/context/peer.js +1216 -0
- package/lib/context/require-admin.d.ts +57 -0
- package/lib/context/require-admin.js +57 -0
- package/lib/context/sync.d.ts +12 -0
- package/lib/context/sync.js +252 -1
- package/lib/context/types.d.ts +58 -22
- package/lib/context/types.js +48 -1
- package/lib/groups/access-default-token.d.ts +44 -0
- package/lib/groups/access-default-token.js +103 -0
- package/lib/groups/admin-roster.d.ts +55 -0
- package/lib/groups/admin-roster.js +69 -0
- package/lib/groups/anchor-store.d.ts +15 -0
- package/lib/groups/anchor-store.js +37 -0
- package/lib/groups/app-cursor-store.d.ts +17 -0
- package/lib/groups/app-cursor-store.js +34 -0
- package/lib/groups/broadcast-codec.js +6 -1
- package/lib/groups/broadcast.d.ts +238 -106
- package/lib/groups/broadcast.js +702 -1
- package/lib/groups/catalog-token.d.ts +50 -0
- package/lib/groups/catalog-token.js +96 -0
- package/lib/groups/circle-projection.d.ts +90 -0
- package/lib/groups/circle-projection.js +202 -0
- package/lib/groups/circle-reducers.d.ts +139 -0
- package/lib/groups/circle-reducers.js +128 -0
- package/lib/groups/commit-adoption.d.ts +77 -0
- package/lib/groups/commit-adoption.js +108 -0
- package/lib/groups/commit-journal.d.ts +14 -0
- package/lib/groups/commit-journal.js +65 -0
- package/lib/groups/control-request.d.ts +66 -0
- package/lib/groups/control-request.js +101 -0
- package/lib/groups/events.d.ts +19 -2
- package/lib/groups/events.js +10 -1
- package/lib/groups/group-anchor.d.ts +24 -0
- package/lib/groups/group-anchor.js +22 -0
- package/lib/groups/group-crypto.d.ts +42 -0
- package/lib/groups/group-crypto.js +159 -0
- package/lib/groups/group-handle-registry.d.ts +138 -9
- package/lib/groups/group-handle-registry.js +533 -1
- package/lib/groups/group-handlers.d.ts +30 -0
- package/lib/groups/group-handlers.js +218 -0
- package/lib/groups/group-health-monitor.d.ts +11 -0
- package/lib/groups/group-health-monitor.js +139 -1
- package/lib/groups/group-mls.d.ts +66 -0
- package/lib/groups/group-mls.js +696 -0
- package/lib/groups/group-peer-manager.d.ts +301 -0
- package/lib/groups/group-peer-manager.js +1336 -0
- package/lib/groups/group-protocols.d.ts +754 -0
- package/lib/groups/group-protocols.js +448 -0
- package/lib/groups/invite-payload.d.ts +49 -1
- package/lib/groups/invite-payload.js +26 -1
- package/lib/groups/join-request-identity.d.ts +21 -0
- package/lib/groups/join-request-identity.js +43 -0
- package/lib/groups/join-utils.d.ts +61 -0
- package/lib/groups/join-utils.js +366 -1
- package/lib/groups/ledger-affected-events.d.ts +20 -0
- package/lib/groups/ledger-affected-events.js +85 -0
- package/lib/groups/ledger-commit-fold.d.ts +107 -0
- package/lib/groups/ledger-commit-fold.js +167 -0
- package/lib/groups/ledger-ingest.d.ts +124 -0
- package/lib/groups/ledger-ingest.js +212 -0
- package/lib/groups/ledger.d.ts +44 -0
- package/lib/groups/ledger.js +30 -0
- package/lib/groups/manager.d.ts +217 -85
- package/lib/groups/manager.js +798 -1
- package/lib/groups/mls-codec.d.ts +28 -7
- package/lib/groups/mls-codec.js +33 -1
- package/lib/groups/mls-encryptor.d.ts +2 -2
- package/lib/groups/mls-encryptor.js +33 -1
- package/lib/groups/mls-group-handle.d.ts +37 -2
- package/lib/groups/mls-group-handle.js +18 -1
- package/lib/groups/mls-json.js +17 -1
- package/lib/groups/mls-receive-errors.d.ts +27 -0
- package/lib/groups/mls-receive-errors.js +39 -0
- package/lib/groups/mls-state.d.ts +6 -4
- package/lib/groups/mls-state.js +79 -1
- package/lib/groups/peer-presence.d.ts +90 -0
- package/lib/groups/peer-presence.js +295 -0
- package/lib/groups/peer-selection.d.ts +37 -0
- package/lib/groups/peer-selection.js +72 -0
- package/lib/groups/roster-projection.d.ts +53 -0
- package/lib/groups/roster-projection.js +65 -0
- package/lib/groups/store-received-grant.d.ts +7 -3
- package/lib/groups/store-received-grant.js +161 -1
- package/lib/groups/store-received-revocation.d.ts +19 -16
- package/lib/groups/store-received-revocation.js +83 -1
- package/lib/hub/ack-backoff.d.ts +22 -0
- package/lib/hub/ack-backoff.js +88 -0
- package/lib/hub/http-client.d.ts +18 -10
- package/lib/hub/http-client.js +15 -1
- package/lib/hub/hub-like.d.ts +75 -0
- package/lib/hub/hub-like.js +1022 -0
- package/lib/hub/loopback-log-hub.d.ts +20 -0
- package/lib/hub/loopback-log-hub.js +178 -0
- package/lib/hub/manager.d.ts +25 -40
- package/lib/hub/manager.js +155 -1
- package/lib/hub/peer-scoped-hub-view.d.ts +35 -0
- package/lib/hub/peer-scoped-hub-view.js +92 -0
- package/lib/hub/sender-scoped-hub-view.d.ts +41 -0
- package/lib/hub/sender-scoped-hub-view.js +59 -0
- package/lib/hub/server-did.d.ts +67 -0
- package/lib/hub/server-did.js +131 -0
- package/lib/hub/wiring.d.ts +77 -51
- package/lib/hub/wiring.js +189 -1
- package/lib/index.d.ts +58 -22
- package/lib/index.js +563 -1
- package/lib/peer/connection-registry.d.ts +45 -0
- package/lib/peer/connection-registry.js +52 -0
- package/lib/peer/handlers.d.ts +69 -0
- package/lib/peer/handlers.js +110 -0
- package/lib/protocol.d.ts +214 -1
- package/lib/protocol.js +519 -1
- package/lib/schema.d.ts +3 -1
- package/lib/schema.js +1150 -39
- package/lib/sync/access-default-sender.d.ts +23 -0
- package/lib/sync/access-default-sender.js +80 -0
- package/lib/sync/authorize.d.ts +58 -4
- package/lib/sync/authorize.js +114 -1
- package/lib/sync/broadcast-queue.d.ts +3 -4
- package/lib/sync/broadcast-queue.js +182 -1
- package/lib/sync/broadcast-sender.js +179 -1
- package/lib/sync/catalog-scope.d.ts +8 -7
- package/lib/sync/catalog-scope.js +84 -1
- package/lib/sync/errors.d.ts +12 -2
- package/lib/sync/errors.js +29 -1
- package/lib/sync/forwarder.d.ts +13 -19
- package/lib/sync/forwarder.js +271 -1
- package/lib/sync/handlers.js +200 -1
- package/lib/sync/hub-tunnel-sync-listener.d.ts +19 -5
- package/lib/sync/hub-tunnel-sync-listener.js +261 -1
- package/lib/sync/hub-tunnel-sync-provider.d.ts +34 -14
- package/lib/sync/hub-tunnel-sync-provider.js +113 -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 +11 -3
- package/lib/sync/merkle-channel.js +0 -0
- package/lib/sync/merkle-tree.js +120 -1
- package/lib/sync/peer-registry.d.ts +15 -11
- package/lib/sync/peer-registry.js +82 -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 +14 -4
- package/lib/sync/sync-client.js +254 -1
- package/lib/sync/sync-manager.d.ts +24 -6
- package/lib/sync/sync-manager.js +301 -1
- package/lib/sync/tunnel-listeners.d.ts +52 -0
- package/lib/sync/tunnel-listeners.js +175 -0
- package/lib/sync/tunnel-observability.d.ts +25 -0
- package/lib/sync/tunnel-observability.js +63 -0
- package/lib/sync/tunnel-topics.d.ts +20 -0
- package/lib/sync/tunnel-topics.js +28 -0
- package/lib/types.d.ts +704 -22
- package/lib/types.js +1 -1
- package/lib/util/handler-error.d.ts +16 -0
- package/lib/util/handler-error.js +64 -0
- package/lib/util/mutex.d.ts +14 -1
- package/lib/util/mutex.js +44 -1
- package/lib/utils.js +21 -1
- package/package.json +58 -47
- package/lib/groups/broadcast-service.d.ts +0 -263
- package/lib/groups/broadcast-service.js +0 -1
- package/lib/groups/rejoin-codec.d.ts +0 -14
- package/lib/groups/rejoin-codec.js +0 -1
- package/lib/groups/wire-frame.d.ts +0 -34
- package/lib/groups/wire-frame.js +0 -1
- package/lib/hub/circle-catchup-requester.d.ts +0 -58
- package/lib/hub/circle-catchup-requester.js +0 -1
- package/lib/hub/circle-catchup-responder.d.ts +0 -50
- package/lib/hub/circle-catchup-responder.js +0 -1
- package/lib/hub/connection-pool.d.ts +0 -43
- package/lib/hub/connection-pool.js +0 -1
- package/lib/hub/did-observing-transport.d.ts +0 -64
- package/lib/hub/did-observing-transport.js +0 -1
- package/lib/hub/epoch-stale-detector.d.ts +0 -18
- package/lib/hub/epoch-stale-detector.js +0 -1
- package/lib/hub/errors.d.ts +0 -30
- package/lib/hub/errors.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 -89
- 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 -51
- package/lib/hub/receive-handler.js +0 -1
- package/lib/hub/rejoin-manager.d.ts +0 -78
- package/lib/hub/rejoin-manager.js +0 -1
- package/lib/hub/rejoin-responder.d.ts +0 -32
- package/lib/hub/rejoin-responder.js +0 -1
- package/lib/hub/relay-manager.d.ts +0 -142
- 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,563 @@
|
|
|
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 { getGraphStore } from '@kubun/store-graph';
|
|
7
|
+
import { p2pStoreDefinition } from '@kubun/store-p2p';
|
|
8
|
+
import { createDelegationContext } from './context/delegation.js';
|
|
9
|
+
import { createGroupContext } from './context/group.js';
|
|
10
|
+
import { createHubContext } from './context/hub.js';
|
|
11
|
+
import { createJoinContext } from './context/join.js';
|
|
12
|
+
import { createPeerContext, serveGroupInvite, servePushControl } from './context/peer.js';
|
|
13
|
+
import { createSyncContext } from './context/sync.js';
|
|
14
|
+
import { createP2PEventEmitter } from './groups/events.js';
|
|
15
|
+
import { GroupHandleRegistry } from './groups/group-handle-registry.js';
|
|
16
|
+
import { GroupHealthMonitor } from './groups/group-health-monitor.js';
|
|
17
|
+
import { GroupManager } from './groups/manager.js';
|
|
18
|
+
import { createHTTPHubClient } from './hub/http-client.js';
|
|
19
|
+
import { setupHubRelay } from './hub/wiring.js';
|
|
20
|
+
import { PeerConnectionRegistry } from './peer/connection-registry.js';
|
|
21
|
+
import { createPeerHandlers } from './peer/handlers.js';
|
|
22
|
+
import { createP2PSchemaExtension } from './schema.js';
|
|
23
|
+
import { wireAccessDefaultSender } from './sync/access-default-sender.js';
|
|
24
|
+
import { createBroadcastQueue, DEFAULT_BROADCAST_BATCH_CONFIG } from './sync/broadcast-queue.js';
|
|
25
|
+
import { DEFAULT_PUSH_SYNC_CONFIG, wireBroadcastSender } from './sync/broadcast-sender.js';
|
|
26
|
+
import { createSyncHandlers } from './sync/handlers.js';
|
|
27
|
+
import { SyncManager } from './sync/sync-manager.js';
|
|
28
|
+
export { GROUP_CONTROL_DENIED, LAST_GROUP_ADMIN, NOT_GROUP_ADMIN, requireGroupAdmin } from './context/require-admin.js';
|
|
29
|
+
export { ADMIN_ROLE_ENTRY_TYPE, foldAdminRoster } from './groups/admin-roster.js';
|
|
30
|
+
export { processBroadcast } from './groups/broadcast.js';
|
|
31
|
+
export { deserializeBroadcast, serializeBroadcast } from './groups/broadcast-codec.js';
|
|
32
|
+
export { createFilteredGenerator, createP2PEventEmitter } from './groups/events.js';
|
|
33
|
+
export { buildKubunGroupAnchorExtension, readRecoverySecret } from './groups/group-anchor.js';
|
|
34
|
+
export { GroupHandleRegistry } from './groups/group-handle-registry.js';
|
|
35
|
+
export { decodeFullJoinRequest, decodeInvitePayload, decodeJoinRequest, encodeFullJoinRequest, encodeInvitePayload, encodeJoinRequest } from './groups/invite-payload.js';
|
|
36
|
+
export { resolveJoinRequestDID } from './groups/join-request-identity.js';
|
|
37
|
+
export { entryOrd, foldLedgerEntries, ledgerEntryDigest, signLedgerEntry, verifyLedgerEntry } from './groups/ledger.js';
|
|
38
|
+
export { MLSEncryptor } from './groups/mls-encryptor.js';
|
|
39
|
+
export { restoreMLSGroupHandle } from './groups/mls-group-handle.js';
|
|
40
|
+
export { replacer as mlsJSONReplacer, reviver as mlsJSONReviver } from './groups/mls-json.js';
|
|
41
|
+
export { deserializeMLSGroupState, serializeMLSGroupState } from './groups/mls-state.js';
|
|
42
|
+
export { MERKLE_SYNC_PROTOCOL } from './groups/peer-selection.js';
|
|
43
|
+
export { createHTTPHubClient } from './hub/http-client.js';
|
|
44
|
+
export { bindHubToGroup, createHub, deleteHub, getHub, listGroupsByHub, listHubs, listHubsByGroup, unbindHubFromGroup, updateHub, upsertHub } from './hub/manager.js';
|
|
45
|
+
export { createHubServerDIDResolver, fetchHubInfo } from './hub/server-did.js';
|
|
46
|
+
export { setupHubRelay } from './hub/wiring.js';
|
|
47
|
+
export { PeerConnectionRegistry } from './peer/connection-registry.js';
|
|
48
|
+
export { createPeerHandlers } from './peer/handlers.js';
|
|
49
|
+
export { peerProtocol } from './protocol.js';
|
|
50
|
+
export { createP2PSchemaExtension } from './schema.js';
|
|
51
|
+
export { createBroadcastQueue, DEFAULT_BROADCAST_BATCH_CONFIG } from './sync/broadcast-queue.js';
|
|
52
|
+
export { DEFAULT_PUSH_SYNC_CONFIG, wireBroadcastSender } from './sync/broadcast-sender.js';
|
|
53
|
+
export { resolveCatalogSyncScopes } from './sync/catalog-scope.js';
|
|
54
|
+
export { SyncAccessDeniedError } from './sync/errors.js';
|
|
55
|
+
export { evaluateAndForward } from './sync/forwarder.js';
|
|
56
|
+
export { checkSyncDelegation } from './sync/handlers.js';
|
|
57
|
+
export { HubTunnelSyncListener } from './sync/hub-tunnel-sync-listener.js';
|
|
58
|
+
export { HubTunnelSyncProvider } from './sync/hub-tunnel-sync-provider.js';
|
|
59
|
+
export { negotiateDirection, PeerRegistry } from './sync/peer-registry.js';
|
|
60
|
+
export { SyncClient } from './sync/sync-client.js';
|
|
61
|
+
export { SyncManager } from './sync/sync-manager.js';
|
|
62
|
+
/**
|
|
63
|
+
* Default receive config. Under `'persist'` no gate is built and no extra
|
|
64
|
+
* DB lookups occur during receive.
|
|
65
|
+
*/ export const DEFAULT_RECEIVE_CONFIG = {
|
|
66
|
+
storeUnreadable: 'persist'
|
|
67
|
+
};
|
|
68
|
+
function isOwnIdentity(identity) {
|
|
69
|
+
return isFullIdentity(identity) && 'privateKey' in identity;
|
|
70
|
+
}
|
|
71
|
+
export function createP2PPlugin(options) {
|
|
72
|
+
return (params)=>{
|
|
73
|
+
params.db.register(p2pStoreDefinition);
|
|
74
|
+
const adapter = params.db.adapter;
|
|
75
|
+
if (!isOwnIdentity(params.identity)) {
|
|
76
|
+
throw new Error('createP2PPlugin requires an OwnIdentity (full identity with privateKey) — received a narrower Identity');
|
|
77
|
+
}
|
|
78
|
+
const identity = params.identity;
|
|
79
|
+
// The engine's single device-wide clock. Sharing it across the p2p group/
|
|
80
|
+
// circle/member metadata paths (context, GroupManager, BroadcastService)
|
|
81
|
+
// keeps every write from this device on one monotonic counter, so two
|
|
82
|
+
// writes in the same millisecond mint strictly-increasing timestamps rather
|
|
83
|
+
// than identical ones (the second of which would silently lose under
|
|
84
|
+
// last-writer-wins).
|
|
85
|
+
const hlc = params.hlc;
|
|
86
|
+
// Computed up front so SyncManager + sync handlers + the hub-relay
|
|
87
|
+
// BroadcastService all see the same receive policy. Hot-path invariant:
|
|
88
|
+
// when `storeUnreadable === 'persist'` (default), no gate is built and
|
|
89
|
+
// no extra DB lookups occur.
|
|
90
|
+
const receiveConfig = {
|
|
91
|
+
...DEFAULT_RECEIVE_CONFIG,
|
|
92
|
+
...options?.receive
|
|
93
|
+
};
|
|
94
|
+
const defaultAccessLevel = options?.defaultAccessLevel ?? {
|
|
95
|
+
read: 'only_owner',
|
|
96
|
+
write: 'only_owner'
|
|
97
|
+
};
|
|
98
|
+
// Single canonical access point for the device's MLS GroupHandle instances.
|
|
99
|
+
// Threaded through GroupManager + BroadcastService + the tunnel
|
|
100
|
+
// listener/provider so all five mutation paths serialize through one
|
|
101
|
+
// per-group mutex with write-through persist.
|
|
102
|
+
const registry = new GroupHandleRegistry({
|
|
103
|
+
stores: params.db,
|
|
104
|
+
deviceID: identity.id,
|
|
105
|
+
logger: params.getLogger('group-handle-registry')
|
|
106
|
+
});
|
|
107
|
+
const emitter = createP2PEventEmitter();
|
|
108
|
+
// `stores` + `emitter` are the lane-side wiring: a commit's `onAccepted`
|
|
109
|
+
// fires decoupled from the resolver that started it, so it folds this
|
|
110
|
+
// device's own committed entries on the device-wide connection and emits the
|
|
111
|
+
// resulting domain events itself.
|
|
112
|
+
const groupManager = new GroupManager({
|
|
113
|
+
identity,
|
|
114
|
+
runtime: params.runtime,
|
|
115
|
+
registry,
|
|
116
|
+
hlc,
|
|
117
|
+
stores: params.db,
|
|
118
|
+
emitter,
|
|
119
|
+
logger: params.getLogger('group-manager')
|
|
120
|
+
});
|
|
121
|
+
const groupHealthMonitor = new GroupHealthMonitor({
|
|
122
|
+
emitter,
|
|
123
|
+
logger: params.getLogger('group-health-monitor')
|
|
124
|
+
});
|
|
125
|
+
const syncManager = new SyncManager({
|
|
126
|
+
deployClusters: async (clusters)=>{
|
|
127
|
+
// Values are ClusterModel objects serialized through the protocol —
|
|
128
|
+
// cast through unknown since SyncManager doesn't depend on protocol types.
|
|
129
|
+
const clusterModels = Object.values(clusters);
|
|
130
|
+
// Into the graphs this device already syncs on, never a fresh one. A
|
|
131
|
+
// received model deployed under a new id gets its tables and nothing
|
|
132
|
+
// else: the document lands and the device's own graph — the one its app
|
|
133
|
+
// queries — has no schema for it, so the model reads as absent while
|
|
134
|
+
// being present. Deployed WITHOUT `plugins`, which leaves each graph's
|
|
135
|
+
// existing extension SDL and plugin config untouched.
|
|
136
|
+
const graphStore = await getGraphStore(params.db);
|
|
137
|
+
const targets = (await graphStore.listGraphs()).filter((graph)=>graph.plugin_config != null && 'p2p' in graph.plugin_config);
|
|
138
|
+
if (targets.length === 0) {
|
|
139
|
+
params.getLogger('sync').warn('Received clusters but no p2p-enabled graph to deploy them into');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const graph of targets){
|
|
143
|
+
await params.engine.deployGraph({
|
|
144
|
+
id: graph.id,
|
|
145
|
+
clusters: clusterModels
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
graph: params.graph,
|
|
150
|
+
runtime: params.runtime,
|
|
151
|
+
identity: params.identity,
|
|
152
|
+
logger: params.getLogger('sync'),
|
|
153
|
+
storeUnreadable: receiveConfig.storeUnreadable,
|
|
154
|
+
defaultAccessLevel
|
|
155
|
+
});
|
|
156
|
+
const syncHandlers = createSyncHandlers({
|
|
157
|
+
db: params.db,
|
|
158
|
+
graph: params.graph,
|
|
159
|
+
logger: params.getLogger('sync-handlers'),
|
|
160
|
+
peerRegistry: syncManager.peerRegistry,
|
|
161
|
+
selfDID: identity.id,
|
|
162
|
+
storeUnreadable: receiveConfig.storeUnreadable,
|
|
163
|
+
defaultAccessLevel
|
|
164
|
+
});
|
|
165
|
+
// In-memory registry of live peer connections, keyed by remote DID. Written
|
|
166
|
+
// by `connectPeer`; nothing persisted. Plugin-scoped so a connection
|
|
167
|
+
// outlives a single GraphQL request.
|
|
168
|
+
const peerConnections = new PeerConnectionRegistry({
|
|
169
|
+
logger: params.getLogger('peer-connections')
|
|
170
|
+
});
|
|
171
|
+
// The p2p plugin manages its own Enkaku server for sync handlers,
|
|
172
|
+
// separate from plugin-rpc's graph server.
|
|
173
|
+
const syncServers = [];
|
|
174
|
+
function createSyncTransport(signal) {
|
|
175
|
+
const directTransports = new DirectTransports({
|
|
176
|
+
signal
|
|
177
|
+
});
|
|
178
|
+
const server = new Server({
|
|
179
|
+
runtime: params.runtime,
|
|
180
|
+
handlers: syncHandlers,
|
|
181
|
+
identity,
|
|
182
|
+
// Open at the procedure layer by design: the sync protocol's
|
|
183
|
+
// authorization is enforced downstream, per document (read-ACL and
|
|
184
|
+
// write-delegation tokens), not by a coarse caller allow-list here.
|
|
185
|
+
accessRules: {
|
|
186
|
+
'sync/*': {
|
|
187
|
+
allow: true
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
logger: params.getLogger('sync-server'),
|
|
191
|
+
transports: [
|
|
192
|
+
directTransports.server
|
|
193
|
+
],
|
|
194
|
+
signal
|
|
195
|
+
});
|
|
196
|
+
syncServers.push(server);
|
|
197
|
+
return directTransports.client;
|
|
198
|
+
}
|
|
199
|
+
const hubRelayLogger = params.getLogger('hub-relay');
|
|
200
|
+
let unwireBroadcastSender;
|
|
201
|
+
let unwireAccessDefaultSender;
|
|
202
|
+
let broadcastQueue;
|
|
203
|
+
const pushSyncConfig = {
|
|
204
|
+
...DEFAULT_PUSH_SYNC_CONFIG,
|
|
205
|
+
...options?.pushSync
|
|
206
|
+
};
|
|
207
|
+
// The relay always runs: it is what gives every joined group a commit lane,
|
|
208
|
+
// including a group with no hub bound (which commits against an in-process
|
|
209
|
+
// loopback log). Hub clients are only ever spawned for a group that has a
|
|
210
|
+
// hub row, so a device configured without hubs opens no connection.
|
|
211
|
+
const createHubClient = typeof options?.hub === 'object' && options.hub.createHubClient != null ? options.hub.createHubClient : createHTTPHubClient({
|
|
212
|
+
identity,
|
|
213
|
+
fetch: params.runtime.fetch
|
|
214
|
+
});
|
|
215
|
+
const hubReconnectBackoff = typeof options?.hub === 'object' ? options.hub.reconnectBackoff : undefined;
|
|
216
|
+
const hub = setupHubRelay({
|
|
217
|
+
identity,
|
|
218
|
+
runtime: params.runtime,
|
|
219
|
+
db: params.db,
|
|
220
|
+
hlc,
|
|
221
|
+
graph: params.graph,
|
|
222
|
+
emitter,
|
|
223
|
+
createHubClient,
|
|
224
|
+
registry,
|
|
225
|
+
// Re-drive a lost `ledger` commit's surviving tokens through the group
|
|
226
|
+
// manager's own ledger write path, so the seam stays a consumer of it.
|
|
227
|
+
buildLedgerRedrive: (groupID, tokens, requestID)=>groupManager.buildEnactLedgerCommit({
|
|
228
|
+
groupID,
|
|
229
|
+
tokens,
|
|
230
|
+
requestID
|
|
231
|
+
}),
|
|
232
|
+
logger: hubRelayLogger,
|
|
233
|
+
storeUnreadable: receiveConfig.storeUnreadable,
|
|
234
|
+
defaultAccessLevel,
|
|
235
|
+
forwarding: options?.forwarding,
|
|
236
|
+
...hubReconnectBackoff != null ? {
|
|
237
|
+
hubReconnectBackoff
|
|
238
|
+
} : {},
|
|
239
|
+
...options?.tunnelIdleTimeoutMs != null ? {
|
|
240
|
+
tunnelIdleTimeoutMs: options.tunnelIdleTimeoutMs
|
|
241
|
+
} : {},
|
|
242
|
+
// The same handlers the direct and HTTP transports serve — a tunnel is a
|
|
243
|
+
// route, so it must not reach a different sync implementation.
|
|
244
|
+
syncHandlers: syncHandlers
|
|
245
|
+
});
|
|
246
|
+
// Turn a `tunnel://<groupID>/<peerDID>` route into a live session over the
|
|
247
|
+
// group's hub. Until this existed the scheme parsed and then failed at
|
|
248
|
+
// "Server resolver not configured", so every device could only be reached by
|
|
249
|
+
// URL — the topology this design retires.
|
|
250
|
+
//
|
|
251
|
+
// Route resolution only. A resolver hands back a transport; what the far end
|
|
252
|
+
// serves over it is decided per document there, so nothing here can widen
|
|
253
|
+
// what a peer may read.
|
|
254
|
+
syncManager.setServerResolver((serverID, route)=>{
|
|
255
|
+
if (route == null) {
|
|
256
|
+
// `direct://` — in-process peers, which only a test wires by hand.
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
// Built lazily: the hub relay binds asynchronously, and a resolver that
|
|
260
|
+
// waited for it here would have to be async, which this seam is not.
|
|
261
|
+
return {
|
|
262
|
+
createSyncTransport: async (signal)=>{
|
|
263
|
+
const provider = await hub.syncTransportTo(route.groupID, serverID);
|
|
264
|
+
if (provider == null) {
|
|
265
|
+
throw new Error(`No hub bound for group ${route.groupID}: cannot open a tunnel`);
|
|
266
|
+
}
|
|
267
|
+
return await provider.createSyncTransport(signal);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
if (options?.hub) {
|
|
272
|
+
// Replicate this peer's own model access-defaults to co-members.
|
|
273
|
+
// Control-plane, not document push-sync: always wired when a hub exists so
|
|
274
|
+
// an owner's sharing policy reaches members that must evaluate its docs.
|
|
275
|
+
unwireAccessDefaultSender = wireAccessDefaultSender({
|
|
276
|
+
db: params.db,
|
|
277
|
+
eventBus: params.eventBus,
|
|
278
|
+
identity,
|
|
279
|
+
scheduleBroadcast: hub.scheduleBroadcast,
|
|
280
|
+
logger: params.getLogger('access-default-sender')
|
|
281
|
+
});
|
|
282
|
+
// Subscribe to locally-authored mutations and fan out per-scope
|
|
283
|
+
// `mutation:apply` broadcasts through the hub. Gated independently from
|
|
284
|
+
// the hub itself: the hub is needed for receive + pull regardless, but
|
|
285
|
+
// push-sync is opt-in via `pushSync.enabled`. Without the gate an
|
|
286
|
+
// unconfigured device would still accrue broadcast_log rows for sends that
|
|
287
|
+
// reach no hub-backed peer.
|
|
288
|
+
if (pushSyncConfig.enabled) {
|
|
289
|
+
const batchConfig = {
|
|
290
|
+
...DEFAULT_BROADCAST_BATCH_CONFIG,
|
|
291
|
+
...options.batch
|
|
292
|
+
};
|
|
293
|
+
broadcastQueue = createBroadcastQueue({
|
|
294
|
+
config: batchConfig,
|
|
295
|
+
scheduleBroadcast: hub.scheduleBroadcast,
|
|
296
|
+
logger: params.getLogger('broadcast-queue')
|
|
297
|
+
});
|
|
298
|
+
unwireBroadcastSender = wireBroadcastSender({
|
|
299
|
+
db: params.db,
|
|
300
|
+
eventBus: params.eventBus,
|
|
301
|
+
selfDID: identity.id,
|
|
302
|
+
queue: broadcastQueue,
|
|
303
|
+
logger: params.getLogger('broadcast-sender')
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Single builder for the per-request ContextDeps, shared by the GraphQL
|
|
308
|
+
// context factory and the peer-procedure runners. Reads `hub` at call time
|
|
309
|
+
// so the hub wiring assigned above is always current.
|
|
310
|
+
function buildContextDeps(stores) {
|
|
311
|
+
return {
|
|
312
|
+
identity,
|
|
313
|
+
groupManager,
|
|
314
|
+
registry,
|
|
315
|
+
syncManager,
|
|
316
|
+
peerConnections,
|
|
317
|
+
stores,
|
|
318
|
+
deviceStores: params.db,
|
|
319
|
+
adapter,
|
|
320
|
+
hlc,
|
|
321
|
+
emitter,
|
|
322
|
+
runtime: params.runtime,
|
|
323
|
+
autoAcceptPeers: options?.autoAcceptPeers,
|
|
324
|
+
...options?.inviteCommitTimeoutMs != null ? {
|
|
325
|
+
inviteCommitTimeoutMs: options.inviteCommitTimeoutMs
|
|
326
|
+
} : {},
|
|
327
|
+
scheduleBroadcast: hub.scheduleBroadcast,
|
|
328
|
+
broadcastNow: hub.broadcastNow,
|
|
329
|
+
commitToGroup: hub.commitToGroup,
|
|
330
|
+
monitor: groupHealthMonitor,
|
|
331
|
+
rejoinGroup: hub.rejoinGroup,
|
|
332
|
+
requestLedgerCatchup: hub.requestLedgerCatchup,
|
|
333
|
+
logger: params.getLogger('context')
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// The dance runners reuse the SAME join context ops the GraphQL mutations
|
|
337
|
+
// use, executed on behalf of the verified caller against the plugin's own
|
|
338
|
+
// DB (peer procedures run outside an engine transaction; `joinGroup` manages
|
|
339
|
+
// its own inner transaction). No MLS logic is reimplemented here.
|
|
340
|
+
const peerHandlers = createPeerHandlers({
|
|
341
|
+
db: params.db,
|
|
342
|
+
identity,
|
|
343
|
+
logger: params.getLogger('peer-handlers'),
|
|
344
|
+
autoAcceptPeers: options?.autoAcceptPeers,
|
|
345
|
+
runPrepareJoin: (callerDID)=>createJoinContext({
|
|
346
|
+
viewerDID: callerDID
|
|
347
|
+
}, buildContextDeps(params.db)).prepareRequest(),
|
|
348
|
+
runCompleteJoin: (callerDID, invitePayload)=>createJoinContext({
|
|
349
|
+
viewerDID: callerDID
|
|
350
|
+
}, buildContextDeps(params.db)).complete(invitePayload),
|
|
351
|
+
runInvite: (callerDID, groupID, joinRequest)=>serveGroupInvite(buildContextDeps(params.db), {
|
|
352
|
+
callerDID,
|
|
353
|
+
groupID,
|
|
354
|
+
joinRequest
|
|
355
|
+
}),
|
|
356
|
+
runPushControl: (callerDID, param)=>servePushControl(buildContextDeps(params.db), {
|
|
357
|
+
callerDID,
|
|
358
|
+
...param
|
|
359
|
+
})
|
|
360
|
+
});
|
|
361
|
+
const pluginAPI = {
|
|
362
|
+
hubReady: hub.ready,
|
|
363
|
+
addPeer: (config)=>syncManager.addPeer({
|
|
364
|
+
config,
|
|
365
|
+
stores: params.db
|
|
366
|
+
}),
|
|
367
|
+
removePeer: (peerDID)=>syncManager.removePeer({
|
|
368
|
+
peerDID,
|
|
369
|
+
stores: params.db
|
|
370
|
+
}),
|
|
371
|
+
listPeers: ()=>syncManager.listPeers({
|
|
372
|
+
stores: params.db
|
|
373
|
+
}),
|
|
374
|
+
syncWithPeer: (syncParams)=>syncManager.merkleSyncWithPeer({
|
|
375
|
+
...syncParams,
|
|
376
|
+
stores: params.db
|
|
377
|
+
}),
|
|
378
|
+
getStatus: (peerDID)=>syncManager.getStatus({
|
|
379
|
+
peerDID,
|
|
380
|
+
stores: params.db
|
|
381
|
+
}),
|
|
382
|
+
// Through the same context the GraphQL surface uses, as the peer-procedure
|
|
383
|
+
// runners do: the scopes an automatic catch-up asks for cannot drift from
|
|
384
|
+
// the ones an explicit one asks for if there is only one implementation.
|
|
385
|
+
catchUpWithBestPeer: (groupID)=>createSyncContext(null, buildContextDeps(params.db)).catchUpWithBestPeer(groupID),
|
|
386
|
+
listPeerDevices: (groupID)=>createSyncContext(null, buildContextDeps(params.db)).listPeerDevices(groupID),
|
|
387
|
+
onSyncEvent: (callback)=>syncManager.onSyncEvent(callback),
|
|
388
|
+
onHubServerDIDChanged: (listener)=>emitter.on('hubServerDIDChanged', listener),
|
|
389
|
+
createSyncTransport,
|
|
390
|
+
requestLedgerCatchup: (groupID, options)=>hub.requestLedgerCatchup(groupID, options),
|
|
391
|
+
setLocalPeerProfile: async (profile)=>{
|
|
392
|
+
await (await hub.presence()).setProfile(profile);
|
|
393
|
+
},
|
|
394
|
+
getLocalPeerProfile: async ()=>await (await hub.presence()).getProfile(),
|
|
395
|
+
gatherPeers: async (groupID, options)=>await (await hub.presence()).gather(groupID, options),
|
|
396
|
+
refreshPeerPresence: async (groupID, options)=>await (await hub.presence()).refresh(groupID, options),
|
|
397
|
+
retryHubConnection: async ()=>await hub.retryHubs(),
|
|
398
|
+
getPeerConnection: (peerDID)=>peerConnections.get(peerDID)
|
|
399
|
+
};
|
|
400
|
+
let httpSyncTransport;
|
|
401
|
+
let httpSyncServer;
|
|
402
|
+
let httpPeerTransport;
|
|
403
|
+
let httpPeerServer;
|
|
404
|
+
if (options?.http) {
|
|
405
|
+
// Capture in the outer scope so the union is narrowed before the async
|
|
406
|
+
// closure below (re-reading options.http inside the IIFE would widen it).
|
|
407
|
+
const httpOption = options.http;
|
|
408
|
+
const rawPath = typeof httpOption === 'string' ? httpOption : typeof httpOption === 'object' ? httpOption.path ?? 'sync' : 'sync';
|
|
409
|
+
const protocolName = rawPath.replace(/^\//, '');
|
|
410
|
+
// Default-deny cross-origin browser requests, consistent with the other
|
|
411
|
+
// HTTP surfaces. The sync server authenticates every request (signed
|
|
412
|
+
// Enkaku procedures), so CORS is defense-in-depth here, not the security
|
|
413
|
+
// boundary; non-browser sync clients send no Origin and are unaffected.
|
|
414
|
+
// Operators opt into cross-origin via the object form's `allowedOrigin`.
|
|
415
|
+
const allowedOrigin = resolveAllowedOrigin(typeof httpOption === 'object' ? httpOption.allowedOrigin : undefined);
|
|
416
|
+
// Maximum request body size, enforced by the transport (default 1 MiB,
|
|
417
|
+
// matching the Enkaku ServerTransport default).
|
|
418
|
+
const maxRequestBodySize = (typeof httpOption === 'object' ? httpOption.bodyLimit : undefined) ?? 1024 * 1024;
|
|
419
|
+
pluginAPI.syncReady = (async ()=>{
|
|
420
|
+
const httpAPI = await params.engine.getAPI('http');
|
|
421
|
+
await httpAPI.listening;
|
|
422
|
+
httpSyncTransport = new ServerTransport({
|
|
423
|
+
allowedOrigin,
|
|
424
|
+
maxRequestBodySize
|
|
425
|
+
});
|
|
426
|
+
httpSyncServer = new Server({
|
|
427
|
+
runtime: params.runtime,
|
|
428
|
+
handlers: syncHandlers,
|
|
429
|
+
identity,
|
|
430
|
+
// Open at the procedure layer by design: the sync protocol's
|
|
431
|
+
// authorization is enforced downstream, per document (read-ACL and
|
|
432
|
+
// write-delegation tokens), not by a coarse caller allow-list here.
|
|
433
|
+
accessRules: {
|
|
434
|
+
'sync/*': {
|
|
435
|
+
allow: true
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
logger: params.getLogger('sync-http-server'),
|
|
439
|
+
transports: [
|
|
440
|
+
httpSyncTransport
|
|
441
|
+
]
|
|
442
|
+
});
|
|
443
|
+
syncServers.push(httpSyncServer);
|
|
444
|
+
httpAPI.registerProtocol(protocolName, httpSyncTransport.fetch.bind(httpSyncTransport));
|
|
445
|
+
// Mount the peer protocol alongside sync, advertised under its own
|
|
446
|
+
// well-known name so a connecting device discovers it in `/info`. Same
|
|
447
|
+
// CORS + body-limit policy as the sync surface. Its procedure layer is
|
|
448
|
+
// open (`allow: true`); `peer/groups` gates the verified caller in the
|
|
449
|
+
// handler via `checkPeerAccess`, mirroring the dance mutations.
|
|
450
|
+
httpPeerTransport = new ServerTransport({
|
|
451
|
+
allowedOrigin,
|
|
452
|
+
maxRequestBodySize
|
|
453
|
+
});
|
|
454
|
+
httpPeerServer = new Server({
|
|
455
|
+
runtime: params.runtime,
|
|
456
|
+
handlers: peerHandlers,
|
|
457
|
+
identity,
|
|
458
|
+
// Open at the procedure layer, like the sync server: each peer handler
|
|
459
|
+
// gates the verified caller itself (forged-`sub` rejection +
|
|
460
|
+
// `checkPeerAccess`). Covers `peer/*` and the dance `group/*`.
|
|
461
|
+
accessRules: {
|
|
462
|
+
'peer/*': {
|
|
463
|
+
allow: true
|
|
464
|
+
},
|
|
465
|
+
'group/*': {
|
|
466
|
+
allow: true
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
logger: params.getLogger('peer-http-server'),
|
|
470
|
+
transports: [
|
|
471
|
+
httpPeerTransport
|
|
472
|
+
]
|
|
473
|
+
});
|
|
474
|
+
httpAPI.registerProtocol('peer', httpPeerTransport.fetch.bind(httpPeerTransport));
|
|
475
|
+
})();
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
name: 'p2p',
|
|
479
|
+
schemaExtension: (_config)=>createP2PSchemaExtension(emitter, syncManager, params.getLogger('p2p-schema')),
|
|
480
|
+
api: pluginAPI,
|
|
481
|
+
createContextFactory: ()=>{
|
|
482
|
+
return (ctx, stores)=>{
|
|
483
|
+
const deps = buildContextDeps(stores);
|
|
484
|
+
return {
|
|
485
|
+
group: createGroupContext(ctx, deps),
|
|
486
|
+
sync: createSyncContext(ctx, deps),
|
|
487
|
+
join: createJoinContext(ctx, deps),
|
|
488
|
+
hub: createHubContext(ctx, deps),
|
|
489
|
+
delegation: createDelegationContext(ctx, deps),
|
|
490
|
+
peer: createPeerContext(ctx, deps)
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
},
|
|
494
|
+
dispose: async ()=>{
|
|
495
|
+
// Unwire the broadcast-sender subscriber first so no new broadcasts
|
|
496
|
+
// are enqueued while teardown is in flight.
|
|
497
|
+
if (unwireBroadcastSender != null) {
|
|
498
|
+
try {
|
|
499
|
+
unwireBroadcastSender();
|
|
500
|
+
} catch {
|
|
501
|
+
// ignore
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (unwireAccessDefaultSender != null) {
|
|
505
|
+
try {
|
|
506
|
+
unwireAccessDefaultSender();
|
|
507
|
+
} catch {
|
|
508
|
+
// ignore
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// Synchronously drain the batch queue BEFORE other teardown so any
|
|
512
|
+
// pending broadcasts ship through the still-live hub transport.
|
|
513
|
+
if (broadcastQueue != null) {
|
|
514
|
+
try {
|
|
515
|
+
broadcastQueue.dispose();
|
|
516
|
+
} catch {
|
|
517
|
+
// ignore
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Tear down every live outbound peer connection (client + transport).
|
|
521
|
+
const peerConnectionsPath = peerConnections.disposeAll();
|
|
522
|
+
// Sync + peer HTTP servers depend on their transports — dispose servers
|
|
523
|
+
// first, then the transports. Runs in parallel with syncManager and
|
|
524
|
+
// hub teardown, which have no such dependency.
|
|
525
|
+
const httpPath = (async ()=>{
|
|
526
|
+
for (const server of syncServers){
|
|
527
|
+
await server.dispose();
|
|
528
|
+
}
|
|
529
|
+
if (httpPeerServer != null) {
|
|
530
|
+
await httpPeerServer.dispose();
|
|
531
|
+
}
|
|
532
|
+
if (httpSyncTransport != null) {
|
|
533
|
+
await httpSyncTransport.dispose();
|
|
534
|
+
}
|
|
535
|
+
if (httpPeerTransport != null) {
|
|
536
|
+
await httpPeerTransport.dispose();
|
|
537
|
+
}
|
|
538
|
+
})();
|
|
539
|
+
const disposeLogger = params.getLogger('dispose');
|
|
540
|
+
const results = await Promise.allSettled([
|
|
541
|
+
httpPath,
|
|
542
|
+
peerConnectionsPath,
|
|
543
|
+
syncManager.dispose(),
|
|
544
|
+
hub.dispose()
|
|
545
|
+
]);
|
|
546
|
+
const labels = [
|
|
547
|
+
'sync-http',
|
|
548
|
+
'peer-connections',
|
|
549
|
+
'sync-manager',
|
|
550
|
+
'hub'
|
|
551
|
+
];
|
|
552
|
+
results.forEach((result, i)=>{
|
|
553
|
+
if (result.status === 'rejected') {
|
|
554
|
+
disposeLogger.error('dispose branch failed', {
|
|
555
|
+
branch: labels[i],
|
|
556
|
+
error: result.reason
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { Client } from '@enkaku/client';
|
|
2
|
+
import type { ClientTransport } from '@enkaku/http-fetch';
|
|
3
|
+
import type { Logger } from '@kubun/logger';
|
|
4
|
+
import type { PeerProtocol } from '../protocol.js';
|
|
5
|
+
/**
|
|
6
|
+
* A live, in-memory connection to a remote peer. Holds the Enkaku client used
|
|
7
|
+
* to dispatch peer procedures plus the transport it rides, so the connection
|
|
8
|
+
* can be torn down cleanly on replacement or plugin dispose. Nothing here is
|
|
9
|
+
* persisted — a connection lives only for the process, keyed by `peerDID`.
|
|
10
|
+
*/
|
|
11
|
+
export type PeerConnection = {
|
|
12
|
+
peerDID: string;
|
|
13
|
+
/** Base URL the peer was reached at (the `connectPeer` argument). */
|
|
14
|
+
endpoint: string;
|
|
15
|
+
/**
|
|
16
|
+
* Fully-resolved sync-protocol endpoint discovered from the peer's `/info`.
|
|
17
|
+
* Recorded at connect so `sharePeerGroup` can register the sync peer without
|
|
18
|
+
* re-fetching `/info` or assuming the default `/sync` path.
|
|
19
|
+
*/
|
|
20
|
+
syncEndpoint: string;
|
|
21
|
+
client: Client<PeerProtocol>;
|
|
22
|
+
transport: ClientTransport<PeerProtocol>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* In-memory registry of live peer connections, keyed by `peerDID`. A second
|
|
26
|
+
* `connectPeer` to the same DID replaces the prior entry and disposes the old
|
|
27
|
+
* client + transport, so a stale connection is never leaked. Purely ephemeral:
|
|
28
|
+
* durable peer state (endpoint, DID) lives in the peer registry, written by the
|
|
29
|
+
* later dance ops — not here.
|
|
30
|
+
*/
|
|
31
|
+
export declare class PeerConnectionRegistry {
|
|
32
|
+
#private;
|
|
33
|
+
constructor(params: {
|
|
34
|
+
logger: Logger;
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Register a connection, replacing (and disposing) any existing connection
|
|
38
|
+
* for the same `peerDID`.
|
|
39
|
+
*/
|
|
40
|
+
set(connection: PeerConnection): Promise<void>;
|
|
41
|
+
get(peerDID: string): PeerConnection | undefined;
|
|
42
|
+
list(): Array<PeerConnection>;
|
|
43
|
+
/** Dispose every held connection and clear the registry. */
|
|
44
|
+
disposeAll(): Promise<void>;
|
|
45
|
+
}
|