@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory registry of live peer connections, keyed by `peerDID`. A second
|
|
3
|
+
* `connectPeer` to the same DID replaces the prior entry and disposes the old
|
|
4
|
+
* client + transport, so a stale connection is never leaked. Purely ephemeral:
|
|
5
|
+
* durable peer state (endpoint, DID) lives in the peer registry, written by the
|
|
6
|
+
* later dance ops — not here.
|
|
7
|
+
*/ export class PeerConnectionRegistry {
|
|
8
|
+
#connections = new Map();
|
|
9
|
+
#logger;
|
|
10
|
+
constructor(params){
|
|
11
|
+
this.#logger = params.logger;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Register a connection, replacing (and disposing) any existing connection
|
|
15
|
+
* for the same `peerDID`.
|
|
16
|
+
*/ async set(connection) {
|
|
17
|
+
const existing = this.#connections.get(connection.peerDID);
|
|
18
|
+
if (existing != null) {
|
|
19
|
+
await this.#dispose(existing);
|
|
20
|
+
}
|
|
21
|
+
this.#connections.set(connection.peerDID, connection);
|
|
22
|
+
}
|
|
23
|
+
get(peerDID) {
|
|
24
|
+
return this.#connections.get(peerDID);
|
|
25
|
+
}
|
|
26
|
+
list() {
|
|
27
|
+
return Array.from(this.#connections.values());
|
|
28
|
+
}
|
|
29
|
+
/** Dispose every held connection and clear the registry. */ async disposeAll() {
|
|
30
|
+
const all = Array.from(this.#connections.values());
|
|
31
|
+
this.#connections.clear();
|
|
32
|
+
await Promise.all(all.map((connection)=>this.#dispose(connection)));
|
|
33
|
+
}
|
|
34
|
+
async #dispose(connection) {
|
|
35
|
+
try {
|
|
36
|
+
await connection.client.dispose();
|
|
37
|
+
} catch (error) {
|
|
38
|
+
this.#logger.debug('peer client dispose failed', {
|
|
39
|
+
peerDID: connection.peerDID,
|
|
40
|
+
error
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await connection.transport.dispose();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
this.#logger.debug('peer transport dispose failed', {
|
|
47
|
+
peerDID: connection.peerDID,
|
|
48
|
+
error
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { OwnIdentity } from '@kokuin/token';
|
|
2
|
+
import type { KubunDB } from '@kubun/db';
|
|
3
|
+
import type { Logger } from '@kubun/logger';
|
|
4
|
+
import type { InviteAccessDefault } from '../groups/invite-payload.js';
|
|
5
|
+
export type CreatePeerHandlersParams = {
|
|
6
|
+
db: KubunDB;
|
|
7
|
+
identity: OwnIdentity;
|
|
8
|
+
logger: Logger;
|
|
9
|
+
/**
|
|
10
|
+
* DIDs permitted to reach the peer procedures. Mirrors the gating the join
|
|
11
|
+
* context applies (`prepareJoinRequest`, `completeJoin`): when set,
|
|
12
|
+
* only these DIDs may list the device's groups; when omitted, only the
|
|
13
|
+
* device's own identity (self / same-DID other device) may.
|
|
14
|
+
*/
|
|
15
|
+
autoAcceptPeers?: Array<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Run the SAME context op as the `prepareJoinRequest` GraphQL mutation, on
|
|
18
|
+
* behalf of the verified caller. Wired by the plugin factory to
|
|
19
|
+
* `createJoinContext(...).prepareRequest`; the handler does not reimplement
|
|
20
|
+
* MLS logic.
|
|
21
|
+
*/
|
|
22
|
+
runPrepareJoin: (callerDID: string) => Promise<{
|
|
23
|
+
joinRequest: string;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Run the SAME context op as the `completeJoin` GraphQL mutation, on behalf of
|
|
27
|
+
* the verified caller. Wired to `createJoinContext(...).complete`.
|
|
28
|
+
*/
|
|
29
|
+
runCompleteJoin: (callerDID: string, invitePayload: string) => Promise<{
|
|
30
|
+
group: {
|
|
31
|
+
id: string;
|
|
32
|
+
} | null;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* Run the inviter leg of the inbound dance on behalf of the verified caller:
|
|
36
|
+
* add the caller to the group's share circles and run the invite as this
|
|
37
|
+
* device's own identity. Wired by the plugin factory to `serveGroupInvite`;
|
|
38
|
+
* the handler does not reimplement MLS or circle logic.
|
|
39
|
+
*/
|
|
40
|
+
runInvite: (callerDID: string, groupID: string, joinRequest: string) => Promise<{
|
|
41
|
+
invitePayload: string;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Apply the access-default rows a verified caller pushed for itself. Wired by
|
|
45
|
+
* the plugin factory to `servePushControl`; the handler does not reimplement
|
|
46
|
+
* the untrusted-source apply — access-default rows are sender-bound to the
|
|
47
|
+
* caller.
|
|
48
|
+
*/
|
|
49
|
+
runPushControl: (callerDID: string, param: {
|
|
50
|
+
groupID: string;
|
|
51
|
+
accessDefaults: Array<InviteAccessDefault>;
|
|
52
|
+
}) => Promise<{
|
|
53
|
+
accessDefaultsApplied: number;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Create the peer-protocol handlers served by plugin-p2p's own Enkaku server,
|
|
58
|
+
* a sibling to the sync handlers. `peer/groups` returns the serving device's
|
|
59
|
+
* DID and its groups so a connecting device can build its `connectPeer`
|
|
60
|
+
* payload.
|
|
61
|
+
*
|
|
62
|
+
* Unlike `sync/*` — whose procedure layer is intentionally open because
|
|
63
|
+
* authorization is enforced downstream per document — `peer/groups` has no
|
|
64
|
+
* downstream per-document gate: it lists group metadata directly. So the caller
|
|
65
|
+
* is authorized here, using the SAME gate the low-level dance mutations use
|
|
66
|
+
* (`checkPeerAccess`): the peer procedures wrap those same ops, and the group
|
|
67
|
+
* list is only served to DIDs already trusted to run the dance.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createPeerHandlers(params: CreatePeerHandlersParams): Record<string, unknown>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getP2PStore } from '@kubun/store-p2p';
|
|
2
|
+
import { checkPeerAccess } from '../context/types.js';
|
|
3
|
+
import { SyncAccessDeniedError } from '../sync/errors.js';
|
|
4
|
+
import { wrapHandlers } from '../util/handler-error.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create the peer-protocol handlers served by plugin-p2p's own Enkaku server,
|
|
7
|
+
* a sibling to the sync handlers. `peer/groups` returns the serving device's
|
|
8
|
+
* DID and its groups so a connecting device can build its `connectPeer`
|
|
9
|
+
* payload.
|
|
10
|
+
*
|
|
11
|
+
* Unlike `sync/*` — whose procedure layer is intentionally open because
|
|
12
|
+
* authorization is enforced downstream per document — `peer/groups` has no
|
|
13
|
+
* downstream per-document gate: it lists group metadata directly. So the caller
|
|
14
|
+
* is authorized here, using the SAME gate the low-level dance mutations use
|
|
15
|
+
* (`checkPeerAccess`): the peer procedures wrap those same ops, and the group
|
|
16
|
+
* list is only served to DIDs already trusted to run the dance.
|
|
17
|
+
*/ export function createPeerHandlers(params) {
|
|
18
|
+
const { db, identity, logger, autoAcceptPeers, runPrepareJoin, runCompleteJoin, runInvite, runPushControl } = params;
|
|
19
|
+
// Derive the cryptographically-verified caller DID from the signed payload and
|
|
20
|
+
// reject a forged subject. Shared by every peer procedure — the same
|
|
21
|
+
// forged-`sub` guard `sync/negotiate` applies before any authorization.
|
|
22
|
+
function verifiedCaller(payload) {
|
|
23
|
+
const signed = payload;
|
|
24
|
+
if (signed.sub != null && signed.sub !== signed.iss) {
|
|
25
|
+
throw new SyncAccessDeniedError('forged-subject', 'Peer access denied: signed payload subject does not match issuer');
|
|
26
|
+
}
|
|
27
|
+
return signed.iss;
|
|
28
|
+
}
|
|
29
|
+
// Wrapped so a refusal carrying a registered KubunErrorCode reaches the
|
|
30
|
+
// calling device with its code and reason instead of enkaku's generic EK01.
|
|
31
|
+
return wrapHandlers({
|
|
32
|
+
'peer/groups': async (ctx)=>{
|
|
33
|
+
const callerDID = verifiedCaller(ctx.message.payload);
|
|
34
|
+
// Same gate as the dance mutations: only a trusted caller (in
|
|
35
|
+
// `autoAcceptPeers`, or the device's own DID when unset) may list groups.
|
|
36
|
+
checkPeerAccess(callerDID, identity.id, autoAcceptPeers);
|
|
37
|
+
const store = await getP2PStore(db);
|
|
38
|
+
const rows = await store.listGroups();
|
|
39
|
+
logger.debug('peer/groups served', {
|
|
40
|
+
callerDID,
|
|
41
|
+
groupCount: rows.length
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
did: identity.id,
|
|
45
|
+
groups: rows.map((row)=>({
|
|
46
|
+
id: row.id,
|
|
47
|
+
name: row.name
|
|
48
|
+
}))
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
// The joining device mints its KeyPackage bundle. `runPrepareJoin` runs the
|
|
52
|
+
// same context op as `prepareJoinRequest`, which itself re-checks
|
|
53
|
+
// `checkPeerAccess` for the verified caller and stashes the pending bundle
|
|
54
|
+
// keyed by that caller DID.
|
|
55
|
+
'group/prepare-join': async (ctx)=>{
|
|
56
|
+
const callerDID = verifiedCaller(ctx.message.payload);
|
|
57
|
+
const result = await runPrepareJoin(callerDID);
|
|
58
|
+
logger.debug('group/prepare-join served', {
|
|
59
|
+
callerDID
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
joinRequest: result.joinRequest
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
// The joining device processes the Welcome the caller returns. `runCompleteJoin`
|
|
66
|
+
// runs the same context op as `completeJoin` (`joinGroup` + invite seeding),
|
|
67
|
+
// re-checking `checkPeerAccess` and consuming the pending bundle.
|
|
68
|
+
'group/complete-join': async (ctx)=>{
|
|
69
|
+
const callerDID = verifiedCaller(ctx.message.payload);
|
|
70
|
+
const result = await runCompleteJoin(callerDID, ctx.param.invitePayload);
|
|
71
|
+
logger.debug('group/complete-join served', {
|
|
72
|
+
callerDID,
|
|
73
|
+
groupID: result.group?.id ?? null
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
groupID: result.group?.id ?? null
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
// The inviter leg of the inbound dance. Same gate as `peer/groups`: only a
|
|
80
|
+
// trusted caller may pull an invite. `runInvite` adds the caller to the
|
|
81
|
+
// group's share circles and runs the invite as this device's own identity,
|
|
82
|
+
// returning the Welcome + seeds for the caller to complete the join.
|
|
83
|
+
'group/invite': async (ctx)=>{
|
|
84
|
+
const callerDID = verifiedCaller(ctx.message.payload);
|
|
85
|
+
checkPeerAccess(callerDID, identity.id, autoAcceptPeers);
|
|
86
|
+
const result = await runInvite(callerDID, ctx.param.groupID, ctx.param.joinRequest);
|
|
87
|
+
logger.debug('group/invite served', {
|
|
88
|
+
callerDID,
|
|
89
|
+
groupID: ctx.param.groupID
|
|
90
|
+
});
|
|
91
|
+
return {
|
|
92
|
+
invitePayload: result.invitePayload
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
// A verified caller applies access-default rows it minted for itself. Same
|
|
96
|
+
// gate as `group/invite`. `runPushControl` applies those rows sender-bound to
|
|
97
|
+
// `callerDID`, so a trusted peer cannot inject another owner's sharing policy.
|
|
98
|
+
'group/push-control': async (ctx)=>{
|
|
99
|
+
const callerDID = verifiedCaller(ctx.message.payload);
|
|
100
|
+
checkPeerAccess(callerDID, identity.id, autoAcceptPeers);
|
|
101
|
+
const result = await runPushControl(callerDID, ctx.param);
|
|
102
|
+
logger.debug('group/push-control served', {
|
|
103
|
+
callerDID,
|
|
104
|
+
groupID: ctx.param.groupID,
|
|
105
|
+
accessDefaultsApplied: result.accessDefaultsApplied
|
|
106
|
+
});
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
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";
|
|
@@ -205,6 +205,217 @@ export declare const syncProtocol: {
|
|
|
205
205
|
};
|
|
206
206
|
};
|
|
207
207
|
export type SyncProtocol = typeof syncProtocol;
|
|
208
|
+
/**
|
|
209
|
+
* Peer protocol — the wire leg of the high-level peer ops. Served as a sibling
|
|
210
|
+
* to {@link syncProtocol} and advertised in `/info` under its own name so a
|
|
211
|
+
* connecting device can discover it independently of the data-sync protocol.
|
|
212
|
+
*
|
|
213
|
+
* The GraphQL mutations remain the app-facing surface; these procedures are the
|
|
214
|
+
* device-to-device leg the mutations dispatch to. `peer/groups` returns the
|
|
215
|
+
* serving device's DID and its groups for the `connectPeer` payload. The MLS
|
|
216
|
+
* dance procedures (`group/prepare-join`, `group/invite`, `group/complete-join`)
|
|
217
|
+
* extend this same protocol in later work.
|
|
218
|
+
*/
|
|
219
|
+
export declare const peerProtocol: {
|
|
220
|
+
readonly 'peer/groups': {
|
|
221
|
+
readonly type: "request";
|
|
222
|
+
readonly param: {
|
|
223
|
+
readonly type: "object";
|
|
224
|
+
readonly properties: {};
|
|
225
|
+
readonly additionalProperties: false;
|
|
226
|
+
};
|
|
227
|
+
readonly result: {
|
|
228
|
+
readonly type: "object";
|
|
229
|
+
readonly properties: {
|
|
230
|
+
readonly did: {
|
|
231
|
+
readonly type: "string";
|
|
232
|
+
readonly description: "DID of the serving device";
|
|
233
|
+
};
|
|
234
|
+
readonly groups: {
|
|
235
|
+
readonly type: "array";
|
|
236
|
+
readonly items: {
|
|
237
|
+
readonly type: "object";
|
|
238
|
+
readonly properties: {
|
|
239
|
+
readonly id: {
|
|
240
|
+
readonly type: "string";
|
|
241
|
+
};
|
|
242
|
+
readonly name: {
|
|
243
|
+
readonly type: "string";
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
readonly required: readonly ["id", "name"];
|
|
247
|
+
readonly additionalProperties: false;
|
|
248
|
+
};
|
|
249
|
+
readonly description: "The serving device's groups (id + name)";
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
readonly required: readonly ["did", "groups"];
|
|
253
|
+
readonly additionalProperties: false;
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
readonly 'group/prepare-join': {
|
|
257
|
+
readonly type: "request";
|
|
258
|
+
readonly param: {
|
|
259
|
+
readonly type: "object";
|
|
260
|
+
readonly properties: {};
|
|
261
|
+
readonly additionalProperties: false;
|
|
262
|
+
};
|
|
263
|
+
readonly result: {
|
|
264
|
+
readonly type: "object";
|
|
265
|
+
readonly properties: {
|
|
266
|
+
readonly joinRequest: {
|
|
267
|
+
readonly type: "string";
|
|
268
|
+
readonly description: "The serving device's encoded join request (its KeyPackage)";
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
readonly required: readonly ["joinRequest"];
|
|
272
|
+
readonly additionalProperties: false;
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
readonly 'group/complete-join': {
|
|
276
|
+
readonly type: "request";
|
|
277
|
+
readonly param: {
|
|
278
|
+
readonly type: "object";
|
|
279
|
+
readonly properties: {
|
|
280
|
+
readonly invitePayload: {
|
|
281
|
+
readonly type: "string";
|
|
282
|
+
readonly description: "The invite payload (Welcome + seeds) minted by the caller";
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
readonly required: readonly ["invitePayload"];
|
|
286
|
+
readonly additionalProperties: false;
|
|
287
|
+
};
|
|
288
|
+
readonly result: {
|
|
289
|
+
readonly type: "object";
|
|
290
|
+
readonly properties: {
|
|
291
|
+
readonly groupID: {
|
|
292
|
+
readonly type: readonly ["string", "null"];
|
|
293
|
+
readonly description: "The joined group ID, or null when the group row was not found";
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
readonly required: readonly ["groupID"];
|
|
297
|
+
readonly additionalProperties: false;
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
readonly 'group/invite': {
|
|
301
|
+
readonly type: "request";
|
|
302
|
+
readonly param: {
|
|
303
|
+
readonly type: "object";
|
|
304
|
+
readonly properties: {
|
|
305
|
+
readonly groupID: {
|
|
306
|
+
readonly type: "string";
|
|
307
|
+
readonly description: "The group the caller wants to join";
|
|
308
|
+
};
|
|
309
|
+
readonly joinRequest: {
|
|
310
|
+
readonly type: "string";
|
|
311
|
+
readonly description: "The caller's encoded join request (its KeyPackage)";
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
readonly required: readonly ["groupID", "joinRequest"];
|
|
315
|
+
readonly additionalProperties: false;
|
|
316
|
+
};
|
|
317
|
+
readonly result: {
|
|
318
|
+
readonly type: "object";
|
|
319
|
+
readonly properties: {
|
|
320
|
+
readonly invitePayload: {
|
|
321
|
+
readonly type: "string";
|
|
322
|
+
readonly description: "The invite payload (Welcome + seeds) for the caller to complete the join";
|
|
323
|
+
};
|
|
324
|
+
};
|
|
325
|
+
readonly required: readonly ["invitePayload"];
|
|
326
|
+
readonly additionalProperties: false;
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
readonly 'group/push-control': {
|
|
330
|
+
readonly type: "request";
|
|
331
|
+
readonly param: {
|
|
332
|
+
readonly type: "object";
|
|
333
|
+
readonly properties: {
|
|
334
|
+
readonly groupID: {
|
|
335
|
+
readonly type: "string";
|
|
336
|
+
readonly description: "The group whose control state is being pushed";
|
|
337
|
+
};
|
|
338
|
+
readonly accessDefaults: {
|
|
339
|
+
readonly type: "array";
|
|
340
|
+
readonly items: {
|
|
341
|
+
readonly type: "object";
|
|
342
|
+
readonly properties: {
|
|
343
|
+
readonly ownerDID: {
|
|
344
|
+
readonly type: "string";
|
|
345
|
+
};
|
|
346
|
+
readonly modelID: {
|
|
347
|
+
readonly type: "string";
|
|
348
|
+
};
|
|
349
|
+
readonly permissionType: {
|
|
350
|
+
readonly type: "string";
|
|
351
|
+
readonly enum: readonly ["read", "write"];
|
|
352
|
+
};
|
|
353
|
+
readonly rule: {
|
|
354
|
+
readonly type: "object";
|
|
355
|
+
readonly properties: {
|
|
356
|
+
readonly level: {
|
|
357
|
+
readonly type: "string";
|
|
358
|
+
};
|
|
359
|
+
readonly allowedDIDs: {
|
|
360
|
+
readonly type: readonly ["array", "null"];
|
|
361
|
+
readonly items: {
|
|
362
|
+
readonly type: "string";
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
readonly allowedCircles: {
|
|
366
|
+
readonly type: readonly ["array", "null"];
|
|
367
|
+
readonly items: {
|
|
368
|
+
readonly type: "string";
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
readonly allowedGroups: {
|
|
372
|
+
readonly type: readonly ["array", "null"];
|
|
373
|
+
readonly items: {
|
|
374
|
+
readonly type: "string";
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
readonly required: readonly ["level", "allowedDIDs", "allowedCircles", "allowedGroups"];
|
|
379
|
+
readonly additionalProperties: false;
|
|
380
|
+
};
|
|
381
|
+
readonly hlc: {
|
|
382
|
+
readonly type: "string";
|
|
383
|
+
};
|
|
384
|
+
readonly auth: {
|
|
385
|
+
readonly type: "string";
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
readonly required: readonly ["ownerDID", "modelID", "permissionType", "rule", "hlc", "auth"];
|
|
389
|
+
readonly additionalProperties: false;
|
|
390
|
+
};
|
|
391
|
+
readonly description: "The caller's own signed access-default rows (sender-bound on apply)";
|
|
392
|
+
};
|
|
393
|
+
};
|
|
394
|
+
readonly required: readonly ["groupID", "accessDefaults"];
|
|
395
|
+
readonly additionalProperties: false;
|
|
396
|
+
};
|
|
397
|
+
readonly result: {
|
|
398
|
+
readonly type: "object";
|
|
399
|
+
readonly properties: {
|
|
400
|
+
readonly accessDefaultsApplied: {
|
|
401
|
+
readonly type: "number";
|
|
402
|
+
readonly description: "Count of pushed access-default rows applied (sender-bound, LWW)";
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
readonly required: readonly ["accessDefaultsApplied"];
|
|
406
|
+
readonly additionalProperties: false;
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
export type PeerProtocol = typeof peerProtocol;
|
|
411
|
+
export type PeerGroupsResult = FromSchema<PeerProtocol['peer/groups']['result']>;
|
|
412
|
+
export type PrepareJoinResult = FromSchema<PeerProtocol['group/prepare-join']['result']>;
|
|
413
|
+
export type CompleteJoinParam = FromSchema<PeerProtocol['group/complete-join']['param']>;
|
|
414
|
+
export type CompleteJoinResult = FromSchema<PeerProtocol['group/complete-join']['result']>;
|
|
415
|
+
export type GroupInviteParam = FromSchema<PeerProtocol['group/invite']['param']>;
|
|
416
|
+
export type GroupInviteResult = FromSchema<PeerProtocol['group/invite']['result']>;
|
|
417
|
+
export type PushControlParam = FromSchema<PeerProtocol['group/push-control']['param']>;
|
|
418
|
+
export type PushControlResult = FromSchema<PeerProtocol['group/push-control']['result']>;
|
|
208
419
|
export type SyncNegotiateParams = FromSchema<SyncProtocol['sync/negotiate']['param']>;
|
|
209
420
|
export type SyncNegotiateResult = FromSchema<SyncProtocol['sync/negotiate']['result']>;
|
|
210
421
|
export type SyncMerkleSyncParams = FromSchema<SyncProtocol['sync/merkle-sync']['param']>;
|
|
@@ -214,3 +425,5 @@ export type SyncMerkleSyncResult = FromSchema<SyncProtocol['sync/merkle-sync']['
|
|
|
214
425
|
export type SyncDirection = 'push' | 'pull' | 'both';
|
|
215
426
|
export type SyncClientMessage = AnyClientMessageOf<SyncProtocol>;
|
|
216
427
|
export type SyncServerMessage = AnyServerMessageOf<SyncProtocol>;
|
|
428
|
+
export type PeerClientMessage = AnyClientMessageOf<PeerProtocol>;
|
|
429
|
+
export type PeerServerMessage = AnyServerMessageOf<PeerProtocol>;
|