@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/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{};
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translates a thrown value into a `HandlerError` when it carries a registered
|
|
3
|
+
* `KubunErrorCode`, so `HandlerError.from` in `@enkaku/server` passes it through
|
|
4
|
+
* instead of flattening it to `EK01` / "Handler execution failed".
|
|
5
|
+
*
|
|
6
|
+
* Anything else is returned unchanged, and that default is the security
|
|
7
|
+
* property: an error with no registered code is internal, and its message may
|
|
8
|
+
* carry DIDs, paths, or SQL. Forwarding a refusal is safe only because the
|
|
9
|
+
* throw site attached a code the registry allows to leave the device.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toHandlerError(cause: unknown): unknown;
|
|
12
|
+
/**
|
|
13
|
+
* Applies {@link toHandlerError} to every handler in a procedure record, so the
|
|
14
|
+
* translation lives in one place rather than in each handler.
|
|
15
|
+
*/
|
|
16
|
+
export declare function wrapHandlers(handlers: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { HandlerError } from '@enkaku/server';
|
|
2
|
+
import { isKubunErrorCode } from '@kubun/protocol';
|
|
3
|
+
import { GraphQLError } from 'graphql';
|
|
4
|
+
function isObject(value) {
|
|
5
|
+
return typeof value === 'object' && value != null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Translates a thrown value into a `HandlerError` when it carries a registered
|
|
9
|
+
* `KubunErrorCode`, so `HandlerError.from` in `@enkaku/server` passes it through
|
|
10
|
+
* instead of flattening it to `EK01` / "Handler execution failed".
|
|
11
|
+
*
|
|
12
|
+
* Anything else is returned unchanged, and that default is the security
|
|
13
|
+
* property: an error with no registered code is internal, and its message may
|
|
14
|
+
* carry DIDs, paths, or SQL. Forwarding a refusal is safe only because the
|
|
15
|
+
* throw site attached a code the registry allows to leave the device.
|
|
16
|
+
*/ export function toHandlerError(cause) {
|
|
17
|
+
if (cause instanceof HandlerError) {
|
|
18
|
+
return cause;
|
|
19
|
+
}
|
|
20
|
+
if (cause instanceof GraphQLError) {
|
|
21
|
+
const { code, ...data } = cause.extensions;
|
|
22
|
+
return isKubunErrorCode(code) ? new HandlerError({
|
|
23
|
+
code,
|
|
24
|
+
message: cause.message,
|
|
25
|
+
data,
|
|
26
|
+
cause
|
|
27
|
+
}) : cause;
|
|
28
|
+
}
|
|
29
|
+
if (isObject(cause) && isKubunErrorCode(cause.code)) {
|
|
30
|
+
const message = typeof cause.message === 'string' ? cause.message : undefined;
|
|
31
|
+
const data = isObject(cause.data) ? cause.data : {};
|
|
32
|
+
return new HandlerError({
|
|
33
|
+
code: cause.code,
|
|
34
|
+
message,
|
|
35
|
+
data,
|
|
36
|
+
cause
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return cause;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Applies {@link toHandlerError} to every handler in a procedure record, so the
|
|
43
|
+
* translation lives in one place rather than in each handler.
|
|
44
|
+
*/ export function wrapHandlers(handlers) {
|
|
45
|
+
return Object.fromEntries(Object.entries(handlers).map(([name, handler])=>{
|
|
46
|
+
if (typeof handler !== 'function') {
|
|
47
|
+
return [
|
|
48
|
+
name,
|
|
49
|
+
handler
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
const wrapped = async (...args)=>{
|
|
53
|
+
try {
|
|
54
|
+
return await handler(...args);
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
throw toHandlerError(cause);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return [
|
|
60
|
+
name,
|
|
61
|
+
wrapped
|
|
62
|
+
];
|
|
63
|
+
}));
|
|
64
|
+
}
|
package/lib/util/mutex.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
export type MutexRunOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Give up WAITING for the lock after this long. Bounds the wait only — the
|
|
4
|
+
* deadline is cleared when the callback is invoked, so a slow holder is never
|
|
5
|
+
* rejected out from under work that then completes.
|
|
6
|
+
*
|
|
7
|
+
* A backstop, not a policy. A waiter that gives up does NOT run its callback
|
|
8
|
+
* when its turn comes; it yields the slot so everything behind it proceeds.
|
|
9
|
+
*/
|
|
10
|
+
acquireTimeoutMs?: number;
|
|
11
|
+
/** Builds the rejection, so the caller can name what it was waiting for. */
|
|
12
|
+
acquireTimeoutError?: () => Error;
|
|
13
|
+
};
|
|
1
14
|
export type Mutex = {
|
|
2
|
-
run<T>(fn: () => Promise<T
|
|
15
|
+
run<T>(fn: () => Promise<T>, options?: MutexRunOptions): Promise<T>;
|
|
3
16
|
};
|
|
4
17
|
export declare function createMutex(): Mutex;
|
package/lib/util/mutex.js
CHANGED
|
@@ -1 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
/** A waiter that gave up before its turn. Read by nothing: its `run` already rejected. */ const EXPIRED = Symbol('mutex.expired');
|
|
2
|
+
export function createMutex() {
|
|
3
|
+
let tail = Promise.resolve();
|
|
4
|
+
return {
|
|
5
|
+
run (fn, options) {
|
|
6
|
+
const timeoutMs = options?.acquireTimeoutMs;
|
|
7
|
+
let expired = false;
|
|
8
|
+
let timer;
|
|
9
|
+
// The race below is on the RESULT, never on the acquisition: racing the
|
|
10
|
+
// acquisition inserts microtask hops between a handle operation and
|
|
11
|
+
// everything ordered against it. `fn` stays one microtask off the
|
|
12
|
+
// previous holder.
|
|
13
|
+
const next = tail.then(async ()=>{
|
|
14
|
+
if (expired) {
|
|
15
|
+
return EXPIRED;
|
|
16
|
+
}
|
|
17
|
+
// Safe to read a timer the executor below assigns: `new Promise` runs
|
|
18
|
+
// its executor synchronously, this callback is a microtask away.
|
|
19
|
+
if (timer != null) {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
timer = undefined;
|
|
22
|
+
}
|
|
23
|
+
return await fn();
|
|
24
|
+
});
|
|
25
|
+
tail = next.catch(()=>{});
|
|
26
|
+
if (timeoutMs == null) {
|
|
27
|
+
return next;
|
|
28
|
+
}
|
|
29
|
+
return new Promise((resolve, reject)=>{
|
|
30
|
+
timer = setTimeout(()=>{
|
|
31
|
+
expired = true;
|
|
32
|
+
reject(options?.acquireTimeoutError?.() ?? new Error(`lock not acquired in ${timeoutMs}ms`));
|
|
33
|
+
}, timeoutMs);
|
|
34
|
+
next.then((value)=>{
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
resolve(value);
|
|
37
|
+
}, (error)=>{
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
reject(error);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -1 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
/** Node returns a Timeout object with `unref()`; browsers return a number. */ export function unref(timer) {
|
|
2
|
+
;
|
|
3
|
+
timer.unref?.();
|
|
4
|
+
}
|
|
5
|
+
/** Resolve after `ms`, without keeping the process alive. */ export function sleep(ms) {
|
|
6
|
+
return new Promise((resolve)=>{
|
|
7
|
+
const timer = setTimeout(resolve, ms);
|
|
8
|
+
unref(timer);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function bytesEqual(a, b) {
|
|
12
|
+
if (a.length !== b.length) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
for(let i = 0; i < a.length; i++){
|
|
16
|
+
if (a[i] !== b[i]) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
package/package.json
CHANGED
|
@@ -1,71 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubun/plugin-p2p",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "see LICENSE.md",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/index.d.ts",
|
|
8
7
|
"exports": {
|
|
9
8
|
".": "./lib/index.js"
|
|
10
9
|
},
|
|
10
|
+
"main": "lib/index.js",
|
|
11
|
+
"types": "lib/index.d.ts",
|
|
11
12
|
"files": [
|
|
12
13
|
"lib/*",
|
|
13
14
|
"LICENSE.md"
|
|
14
15
|
],
|
|
15
|
-
"sideEffects": false,
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@enkaku/
|
|
18
|
-
"@enkaku/
|
|
19
|
-
"@enkaku/
|
|
20
|
-
"@enkaku/
|
|
21
|
-
"@enkaku/
|
|
22
|
-
"@enkaku/
|
|
23
|
-
"@
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
27
|
-
"@
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
30
|
-
"@
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
33
|
-
"@enkaku/token": "^0.16.0",
|
|
34
|
-
"@enkaku/transport": "^0.16.0",
|
|
17
|
+
"@enkaku/client": "^0.20.0",
|
|
18
|
+
"@enkaku/http-fetch": "^0.20.0",
|
|
19
|
+
"@enkaku/http-serve": "^0.20.0",
|
|
20
|
+
"@enkaku/protocol": "^0.20.0",
|
|
21
|
+
"@enkaku/server": "^0.20.0",
|
|
22
|
+
"@enkaku/transport": "^0.20.0",
|
|
23
|
+
"@kokuin/capability": "^0.2.1",
|
|
24
|
+
"@kokuin/token": "^0.3.0",
|
|
25
|
+
"@kumiai/broadcast": "^0.5.0",
|
|
26
|
+
"@kumiai/hub-protocol": "^0.5.0",
|
|
27
|
+
"@kumiai/hub-server": "^0.5.0",
|
|
28
|
+
"@kumiai/hub-tunnel": "^0.5.0",
|
|
29
|
+
"@kumiai/mls": "^0.5.0",
|
|
30
|
+
"@kumiai/mls-rpc": "^0.5.0",
|
|
31
|
+
"@kumiai/rpc": "^0.5.0",
|
|
32
|
+
"@noble/ciphers": "^2.2.0",
|
|
35
33
|
"@noble/hashes": "^2.2.0",
|
|
34
|
+
"@sozai/async": "^0.2.1",
|
|
35
|
+
"@sozai/codec": "^0.4.0",
|
|
36
|
+
"@sozai/event": "^0.1.3",
|
|
37
|
+
"@sozai/generator": "^0.2.0",
|
|
38
|
+
"@sozai/runtime": "^0.1.0",
|
|
39
|
+
"@sozai/schema": "^0.1.1",
|
|
40
|
+
"@sozai/stream": "^0.2.0",
|
|
36
41
|
"graphql": "^16.14.2",
|
|
37
|
-
"kysely": "^0.29.
|
|
38
|
-
"
|
|
39
|
-
"@kubun/db
|
|
40
|
-
"@kubun/db": "^0.
|
|
41
|
-
"@kubun/engine": "^0.
|
|
42
|
-
"@kubun/
|
|
43
|
-
"@kubun/
|
|
44
|
-
"@kubun/
|
|
45
|
-
"@kubun/
|
|
46
|
-
"@kubun/
|
|
47
|
-
"@kubun/
|
|
48
|
-
"@kubun/
|
|
49
|
-
"@kubun/protocol": "^0.
|
|
42
|
+
"kysely": "^0.29.4",
|
|
43
|
+
"ts-mls": "2.0.0-rc.13",
|
|
44
|
+
"@kubun/db": "^0.12.0",
|
|
45
|
+
"@kubun/db-adapter": "^0.12.0",
|
|
46
|
+
"@kubun/engine": "^0.12.0",
|
|
47
|
+
"@kubun/graphql": "^0.12.0",
|
|
48
|
+
"@kubun/hlc": "^0.12.0",
|
|
49
|
+
"@kubun/http-util": "^0.12.0",
|
|
50
|
+
"@kubun/logger": "^0.12.0",
|
|
51
|
+
"@kubun/mutation": "^0.12.0",
|
|
52
|
+
"@kubun/plugin-http": "^0.12.0",
|
|
53
|
+
"@kubun/plugin-rpc": "^0.12.0",
|
|
54
|
+
"@kubun/protocol": "^0.12.0",
|
|
55
|
+
"@kubun/store-delegation": "^0.12.0",
|
|
56
|
+
"@kubun/id": "^0.12.0",
|
|
57
|
+
"@kubun/store-graph": "^0.12.0",
|
|
58
|
+
"@kubun/store-p2p": "^0.12.0"
|
|
50
59
|
},
|
|
51
60
|
"devDependencies": {
|
|
52
|
-
"@
|
|
61
|
+
"@kumiai/hub-conformance": "^0.5.0",
|
|
62
|
+
"@kumiai/rpc-conformance": "^0.5.0",
|
|
63
|
+
"@testcontainers/postgresql": "^12.1.0",
|
|
53
64
|
"get-port": "^7.2.0",
|
|
54
|
-
"@kubun/db-
|
|
55
|
-
"@kubun/
|
|
56
|
-
"@kubun/plugin-connector": "^0.
|
|
57
|
-
"@kubun/
|
|
58
|
-
"@kubun/
|
|
59
|
-
"@kubun/
|
|
60
|
-
"@kubun/
|
|
65
|
+
"@kubun/db-node-sqlite": "^0.12.0",
|
|
66
|
+
"@kubun/client": "^0.12.0",
|
|
67
|
+
"@kubun/plugin-connector": "^0.12.0",
|
|
68
|
+
"@kubun/test-utils": "^0.12.0",
|
|
69
|
+
"@kubun/db-postgres": "^0.12.0",
|
|
70
|
+
"@kubun/hub": "^0.12.0",
|
|
71
|
+
"@kubun/db-better-sqlite": "^0.12.0"
|
|
61
72
|
},
|
|
62
73
|
"scripts": {
|
|
74
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
63
75
|
"build:clean": "del lib",
|
|
64
|
-
"build:js": "swc src -d ./lib --config-file ../../swc.json --strip-leading-paths",
|
|
76
|
+
"build:js": "swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
65
77
|
"build:types": "tsc --emitDeclarationOnly --skipLibCheck",
|
|
66
|
-
"
|
|
78
|
+
"test": "pnpm run test:types && pnpm run test:unit",
|
|
67
79
|
"test:types": "tsc --noEmit -p tsconfig.test.json",
|
|
68
|
-
"test:unit": "vitest run"
|
|
69
|
-
"test": "pnpm run test:types && pnpm run test:unit"
|
|
80
|
+
"test:unit": "vitest run"
|
|
70
81
|
}
|
|
71
82
|
}
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from '@enkaku/event';
|
|
2
|
-
import { type GroupHandle, type MemberCredential } from '@enkaku/group';
|
|
3
|
-
import type { DefaultAccessLevel, GraphInternals } from '@kubun/engine';
|
|
4
|
-
import { HLC } from '@kubun/hlc';
|
|
5
|
-
import type { Logger } from '@kubun/logger';
|
|
6
|
-
import type { GraphStoreAPI } from '@kubun/store-graph';
|
|
7
|
-
import type { P2PStoreAPI } from '@kubun/store-p2p';
|
|
8
|
-
import type { ForwardingConfig } from '../sync/forwarder.js';
|
|
9
|
-
import type { StoreUnreadableMode } from '../types.js';
|
|
10
|
-
import { type AffectedRow, type ApplyCatchupReplyResult, type GroupBroadcastMessage } from './broadcast.js';
|
|
11
|
-
import type { P2PEventEmitter } from './events.js';
|
|
12
|
-
import type { GroupHandleRegistry } from './group-handle-registry.js';
|
|
13
|
-
/**
|
|
14
|
-
* Thrown when a received broadcast fails to decrypt (MLS restore, decrypt,
|
|
15
|
-
* or deserialize). Callers should typically ack-and-skip these, since the
|
|
16
|
-
* message is unrecoverable and redelivery will not help.
|
|
17
|
-
*/
|
|
18
|
-
export declare class DecryptError extends Error {
|
|
19
|
-
#private;
|
|
20
|
-
constructor(message: string, cause: unknown);
|
|
21
|
-
get cause(): unknown;
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Decode failure on a rejoin control frame. Distinct from {@link DecryptError}
|
|
25
|
-
* so the receive path classifies it `control`, not `decrypt-failed` — a corrupt
|
|
26
|
-
* frame is not evidence of epoch-staleness and must not drive the health
|
|
27
|
-
* detector. Ack-and-skip: redelivery can't fix a malformed frame.
|
|
28
|
-
*/
|
|
29
|
-
export declare class MalformedControlError extends Error {
|
|
30
|
-
#private;
|
|
31
|
-
constructor(message: string, cause: unknown);
|
|
32
|
-
get cause(): unknown;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Thrown when a successfully decrypted broadcast fails to apply to the local
|
|
36
|
-
* DB. Callers should typically NOT ack these, so the message can be
|
|
37
|
-
* redelivered after the transient failure clears.
|
|
38
|
-
*/
|
|
39
|
-
export declare class ApplyError extends Error {
|
|
40
|
-
#private;
|
|
41
|
-
constructor(message: string, cause: unknown);
|
|
42
|
-
get cause(): unknown;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Thrown when a received MLS Commit targets a future epoch the local handle
|
|
46
|
-
* has not reached yet (a gap in the in-order Commit stream). Callers should
|
|
47
|
-
* NOT ack — the hub redelivers in `sequence_id` order, so the gap-filling
|
|
48
|
-
* Commit arrives first and this one applies on a later delivery.
|
|
49
|
-
*/
|
|
50
|
-
export declare class CommitOutOfOrderError extends Error {
|
|
51
|
-
#private;
|
|
52
|
-
constructor(messageEpoch: bigint, handleEpoch: bigint);
|
|
53
|
-
get messageEpoch(): bigint;
|
|
54
|
-
get handleEpoch(): bigint;
|
|
55
|
-
}
|
|
56
|
-
export type BroadcastEvent = {
|
|
57
|
-
groupID: string;
|
|
58
|
-
message: GroupBroadcastMessage;
|
|
59
|
-
applied: boolean;
|
|
60
|
-
/**
|
|
61
|
-
* Row affected by the broadcast. After-state for create / update,
|
|
62
|
-
* before-state for delete / remove. Undefined when `applied === false`.
|
|
63
|
-
*/
|
|
64
|
-
affected?: AffectedRow;
|
|
65
|
-
/**
|
|
66
|
-
* Count of `mutation:apply` entries the local broadcast log already had.
|
|
67
|
-
* Only set on the `mutation:apply` branch; absent on control-plane
|
|
68
|
-
* variants.
|
|
69
|
-
*/
|
|
70
|
-
duplicates?: number;
|
|
71
|
-
};
|
|
72
|
-
export type BroadcastServiceEvents = {
|
|
73
|
-
broadcast: BroadcastEvent;
|
|
74
|
-
};
|
|
75
|
-
export type SendBroadcastParams = {
|
|
76
|
-
groupID: string;
|
|
77
|
-
message: GroupBroadcastMessage;
|
|
78
|
-
/**
|
|
79
|
-
* Wire-send hook invoked with the MLS-encrypted payload INSIDE the
|
|
80
|
-
* registry's per-group lock. A throw here propagates out of `prepareSend`
|
|
81
|
-
* and the registry skips persist — so a failed send leaves the device's
|
|
82
|
-
* MLS state at its pre-encrypt epoch, preventing the wedge where local
|
|
83
|
-
* state outpaces recipients.
|
|
84
|
-
*/
|
|
85
|
-
send: (encrypted: Uint8Array) => Promise<void>;
|
|
86
|
-
};
|
|
87
|
-
export type SendCommitParams = {
|
|
88
|
-
groupID: string;
|
|
89
|
-
/**
|
|
90
|
-
* Canonical MLS-encoded handshake Commit bytes (an encrypted
|
|
91
|
-
* `PrivateMessage`) produced by the committer's `GroupManager` call.
|
|
92
|
-
*/
|
|
93
|
-
commitBytes: Uint8Array;
|
|
94
|
-
/** Wire-send hook; receives the `mls`-framed bytes. */
|
|
95
|
-
send: (framed: Uint8Array) => Promise<void>;
|
|
96
|
-
};
|
|
97
|
-
export type ReceiveBroadcastParams = {
|
|
98
|
-
groupID: string;
|
|
99
|
-
encrypted: Uint8Array;
|
|
100
|
-
};
|
|
101
|
-
export type ReceiveAppResult = {
|
|
102
|
-
kind: 'app';
|
|
103
|
-
message: GroupBroadcastMessage;
|
|
104
|
-
applied: boolean;
|
|
105
|
-
affected?: AffectedRow;
|
|
106
|
-
duplicates?: number;
|
|
107
|
-
};
|
|
108
|
-
/**
|
|
109
|
-
* Result of processing a received MLS handshake Commit (`mls` wire frame).
|
|
110
|
-
* `removed`/`added` are the membership deltas derived by diffing the group
|
|
111
|
-
* roster before and after applying the Commit. `removed` DIDs are tombstoned
|
|
112
|
-
* via `markMemberRemoved` so the apply-time membership check can reject their
|
|
113
|
-
* post-removal writes.
|
|
114
|
-
*/
|
|
115
|
-
export type ReceiveCommitResult = {
|
|
116
|
-
kind: 'mls';
|
|
117
|
-
epoch: bigint;
|
|
118
|
-
removed: Array<string>;
|
|
119
|
-
added: Array<string>;
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* A received `rejoin-request`: a stale device asking current members for fresh
|
|
123
|
-
* GroupInfo. Cleartext; does not touch the MLS handle.
|
|
124
|
-
*/
|
|
125
|
-
export type ReceiveRejoinRequestResult = {
|
|
126
|
-
kind: 'rejoin-request';
|
|
127
|
-
requestID: string;
|
|
128
|
-
};
|
|
129
|
-
/**
|
|
130
|
-
* A current member's `rejoin-groupinfo` reply carrying fresh GroupInfo. Consumed
|
|
131
|
-
* by the requester's rejoin flow; also drives responder suppression on peers.
|
|
132
|
-
*/
|
|
133
|
-
export type ReceiveRejoinGroupInfoResult = {
|
|
134
|
-
kind: 'rejoin-groupinfo';
|
|
135
|
-
requestID: string;
|
|
136
|
-
groupInfo: Uint8Array;
|
|
137
|
-
};
|
|
138
|
-
export type ReceiveBroadcastResult = ReceiveAppResult | ReceiveCommitResult | ReceiveRejoinRequestResult | ReceiveRejoinGroupInfoResult;
|
|
139
|
-
export type SendRejoinRequestParams = {
|
|
140
|
-
requestID: string;
|
|
141
|
-
/** Wire-send hook; receives the `rejoinRequest`-framed cleartext bytes. */
|
|
142
|
-
send: (framed: Uint8Array) => Promise<void>;
|
|
143
|
-
};
|
|
144
|
-
export type SendRejoinGroupInfoParams = {
|
|
145
|
-
requestID: string;
|
|
146
|
-
groupInfo: Uint8Array;
|
|
147
|
-
/** Wire-send hook; receives the `rejoinGroupInfo`-framed cleartext bytes. */
|
|
148
|
-
send: (framed: Uint8Array) => Promise<void>;
|
|
149
|
-
};
|
|
150
|
-
/**
|
|
151
|
-
* BroadcastService handles MLS encrypt/decrypt for group broadcasts
|
|
152
|
-
* and applies received messages to the local DB.
|
|
153
|
-
*
|
|
154
|
-
* Encrypt and decrypt go through `GroupHandleRegistry.withHandle` so MLS
|
|
155
|
-
* state mutations serialize through the per-group mutex with write-through
|
|
156
|
-
* persist. The DB-apply step (`processBroadcast`) for receives runs OUTSIDE
|
|
157
|
-
* the lock so a slow apply does not block subsequent encrypt/decrypt on the
|
|
158
|
-
* same group.
|
|
159
|
-
*/
|
|
160
|
-
export type BroadcastServiceParams = {
|
|
161
|
-
p2pStore: P2PStoreAPI;
|
|
162
|
-
graphStore: GraphStoreAPI;
|
|
163
|
-
/**
|
|
164
|
-
* Engine graph internals — required so `mutation:apply` broadcasts route
|
|
165
|
-
* through {@link GraphInternals.applyVerifiedMutation} with `origin: 'peer'`.
|
|
166
|
-
*/
|
|
167
|
-
graph: GraphInternals;
|
|
168
|
-
/** Self DID for `mutation:apply` loopback detection. */
|
|
169
|
-
selfDID: string;
|
|
170
|
-
/** Single canonical access point for MLS `GroupHandle` instances. */
|
|
171
|
-
registry: GroupHandleRegistry;
|
|
172
|
-
/**
|
|
173
|
-
* Device-wide monotonic clock for stamping the `removed_at_hlc` of members
|
|
174
|
-
* dropped by a received MLS Commit (the receive side has no HLC otherwise).
|
|
175
|
-
* Required: production passes the engine's single instance so every write
|
|
176
|
-
* from this device advances one shared clock, preventing two same-millisecond
|
|
177
|
-
* writes from minting identical timestamps (the second would silently lose
|
|
178
|
-
* under last-writer-wins).
|
|
179
|
-
*/
|
|
180
|
-
hlc: HLC;
|
|
181
|
-
/**
|
|
182
|
-
* Receiver storage mode for incoming `mutation:apply` entries. Defaults
|
|
183
|
-
* to `'persist'`. When `'drop'`, `defaultAccessLevel` must be supplied
|
|
184
|
-
* so the gate factory can resolve server-side default access rules.
|
|
185
|
-
*/
|
|
186
|
-
storeUnreadable?: StoreUnreadableMode;
|
|
187
|
-
/** Server default access level — required when `storeUnreadable === 'drop'`. */
|
|
188
|
-
defaultAccessLevel?: DefaultAccessLevel;
|
|
189
|
-
/**
|
|
190
|
-
* Forwarding config. Threaded into `processBroadcast` per call.
|
|
191
|
-
* `false` / undefined → forwarding disabled (default).
|
|
192
|
-
*/
|
|
193
|
-
forwarding?: ForwardingConfig;
|
|
194
|
-
/**
|
|
195
|
-
* Hub forwarding hook — required when `forwarding` is set. Called once per
|
|
196
|
-
* surviving candidate group on the receive path.
|
|
197
|
-
*/
|
|
198
|
-
scheduleBroadcast?: (groupID: string, message: GroupBroadcastMessage) => void | Promise<void>;
|
|
199
|
-
/**
|
|
200
|
-
* Emitter forwarded to `processBroadcast` so `delegation:share` receives
|
|
201
|
-
* surface a `delegationTokenAdded` event to local subscribers.
|
|
202
|
-
*/
|
|
203
|
-
emitter?: P2PEventEmitter;
|
|
204
|
-
logger?: Logger;
|
|
205
|
-
};
|
|
206
|
-
export declare class BroadcastService extends EventEmitter<BroadcastServiceEvents> {
|
|
207
|
-
#private;
|
|
208
|
-
constructor(params: BroadcastServiceParams);
|
|
209
|
-
/**
|
|
210
|
-
* Encrypt + send a broadcast for a group, then persist the advanced MLS state.
|
|
211
|
-
*
|
|
212
|
-
* Failure semantics: send happens inside the registry's per-group lock. A
|
|
213
|
-
* throw from `send` propagates out and the registry skips persist — so the
|
|
214
|
-
* device's MLS state stays at its pre-encrypt epoch and the next attempt
|
|
215
|
-
* re-derives a fresh handle from the prior generation. Wedge protection
|
|
216
|
-
* intact.
|
|
217
|
-
*/
|
|
218
|
-
prepareSend(params: SendBroadcastParams): Promise<void>;
|
|
219
|
-
/**
|
|
220
|
-
* Send a pre-produced MLS handshake Commit to the group, tagged `mls` on the
|
|
221
|
-
* wire. The Commit is already a self-protected (encrypted `PrivateMessage`)
|
|
222
|
-
* framed message produced by the committer's `GroupManager` call — it is NOT
|
|
223
|
-
* re-encrypted through the application path, so this does not touch the
|
|
224
|
-
* handle. The committer's MLS state has already advanced + persisted; the
|
|
225
|
-
* hub's durable + ordered mailbox carries the Commit to existing members and
|
|
226
|
-
* replays it on reconnect.
|
|
227
|
-
*/
|
|
228
|
-
prepareSendCommit(params: SendCommitParams): Promise<void>;
|
|
229
|
-
/**
|
|
230
|
-
* Export a fresh `MLSMessage(GroupInfo)` from the current handle. Read-only:
|
|
231
|
-
* takes the registry lock but does not advance or persist state.
|
|
232
|
-
*/
|
|
233
|
-
exportGroupInfo(groupID: string): Promise<Uint8Array>;
|
|
234
|
-
readCredential(groupID: string): Promise<MemberCredential>;
|
|
235
|
-
/**
|
|
236
|
-
* Swap+persist the cached handle (under the per-group mutex), then reconcile
|
|
237
|
-
* the store roster. The swap is security-critical; a reconcile failure is
|
|
238
|
-
* logged and swallowed so it can't reject the rejoin after the handle is
|
|
239
|
-
* installed (which would skip `markRecovered` and wedge the monitor) — the
|
|
240
|
-
* next rejoin re-runs the idempotent reconcile.
|
|
241
|
-
*/
|
|
242
|
-
replaceHandle(groupID: string, handle: GroupHandle): Promise<void>;
|
|
243
|
-
/** Send a `rejoin-request` (cleartext) to the group channel. */
|
|
244
|
-
prepareSendRejoinRequest(params: SendRejoinRequestParams): Promise<void>;
|
|
245
|
-
/** Send a `rejoin-groupinfo` reply (cleartext) to the group channel. */
|
|
246
|
-
prepareSendRejoinGroupInfo(params: SendRejoinGroupInfoParams): Promise<void>;
|
|
247
|
-
/**
|
|
248
|
-
* Process a received broadcast: MLS decrypt INSIDE the registry lock,
|
|
249
|
-
* then apply to the DB OUTSIDE the lock so a slow apply doesn't block
|
|
250
|
-
* subsequent encrypt/decrypt on the same group.
|
|
251
|
-
*/
|
|
252
|
-
processReceived(params: ReceiveBroadcastParams): Promise<ReceiveBroadcastResult>;
|
|
253
|
-
/**
|
|
254
|
-
* Apply a `circle-catchup:reply` batch (circles + circle members, including
|
|
255
|
-
* tombstoned) row-by-row through the existing handlers. Lock-free: like
|
|
256
|
-
* `processReceived`'s post-decrypt apply, this runs OUTSIDE the registry's
|
|
257
|
-
* per-group lock — it touches no MLS handle, and each row apply is idempotent
|
|
258
|
-
* and order-independent, so a late or duplicate reply converges harmlessly.
|
|
259
|
-
*/
|
|
260
|
-
applyCatchupReply(groupID: string, reply: Extract<GroupBroadcastMessage, {
|
|
261
|
-
type: 'circle-catchup:reply';
|
|
262
|
-
}>): Promise<ApplyCatchupReplyResult>;
|
|
263
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{EventEmitter as r}from"@enkaku/event";import{exportGroupInfo as e,readMessageEpoch as t}from"@enkaku/group";import{HLC as a}from"@kubun/hlc";import{applyCatchupReply as o,processBroadcast as s}from"./broadcast.js";import{deserializeBroadcast as i,serializeBroadcast as c}from"./broadcast-codec.js";import{mlsDecryptFramed as n,mlsEncryptFramed as l}from"./mls-codec.js";import{decodeRejoinGroupInfo as d,decodeRejoinRequest as m,encodeRejoinGroupInfo as p,encodeRejoinRequest as h}from"./rejoin-codec.js";import{frameWire as u,unframeWire as w,WireKind as f}from"./wire-frame.js";export class DecryptError extends Error{#r;constructor(r,e){super(r),this.name="DecryptError",this.#r=e}get cause(){return this.#r}}export class MalformedControlError extends Error{#r;constructor(r,e){super(r),this.name="MalformedControlError",this.#r=e}get cause(){return this.#r}}export class ApplyError extends Error{#r;constructor(r,e){super(r),this.name="ApplyError",this.#r=e}get cause(){return this.#r}}export class CommitOutOfOrderError extends Error{#e;#t;constructor(r,e){super(`MLS commit epoch ${r} ahead of handle epoch ${e}`),this.name="CommitOutOfOrderError",this.#e=r,this.#t=e}get messageEpoch(){return this.#e}get handleEpoch(){return this.#t}}export class BroadcastService extends r{#a;#o;#s;constructor(r){super(),this.#s=r.hlc,this.#a={p2pStore:r.p2pStore,graphStore:r.graphStore,graph:r.graph,selfDID:r.selfDID,storeUnreadable:r.storeUnreadable,defaultAccessLevel:r.defaultAccessLevel,forwarding:r.forwarding,scheduleBroadcast:r.scheduleBroadcast,emitter:r.emitter,logger:r.logger},this.#o=r.registry}async prepareSend(r){let{groupID:e,message:t,send:a}=r,o=c(t);await this.#o.withHandle(e,async r=>{let{framed:e}=await l(r,o);await a(u(f.app,e))})}async prepareSendCommit(r){let{commitBytes:e,send:t}=r;await t(u(f.mls,e))}async exportGroupInfo(r){return await this.#o.withHandle(r,async r=>{let{groupInfo:t}=await e({group:r});return t})}async readCredential(r){return await this.#o.withHandle(r,async r=>r.credential)}async replaceHandle(r,e){await this.#o.replaceHandle(r,e);try{await this.#i(r,e)}catch(e){this.#a.logger?.warn("rejoin: store roster reconcile failed",{groupID:r,error:e})}}async #i(r,e){let{p2pStore:t}=this.#a,o=e.listMembers().map(r=>r.id),s=new Set(o),i=await t.listGroupMembers(r),c=new Set(i.map(r=>r.member_did)),n=a.serialize(this.#s.now());for(let e of i)s.has(e.member_did)||await t.markMemberRemoved(r,e.member_did,n);for(let e of o)c.has(e)||await t.addGroupMember({group_id:r,member_did:e,role:"member",hlc:n})}async prepareSendRejoinRequest(r){let{requestID:e,send:t}=r;await t(u(f.rejoinRequest,h({requestID:e})))}async prepareSendRejoinGroupInfo(r){let{requestID:e,groupInfo:t,send:a}=r;await a(u(f.rejoinGroupInfo,p({requestID:e,groupInfo:t})))}async processReceived(r){let e,t,a,o,{groupID:c,encrypted:l}=r;try{({kind:e,payload:t}=w(l))}catch(r){throw new DecryptError("Malformed group wire frame",r)}if(e===f.mls)return await this.#c(c,t);if(e===f.rejoinRequest)try{let{requestID:r}=m(t);return{kind:"rejoin-request",requestID:r}}catch(r){throw new MalformedControlError("Malformed rejoin-request frame",r)}if(e===f.rejoinGroupInfo)try{let{requestID:r,groupInfo:e}=d(t);return{kind:"rejoin-groupinfo",requestID:r,groupInfo:e}}catch(r){throw new MalformedControlError("Malformed rejoin-groupinfo frame",r)}try{a=await this.#o.withHandle(c,async r=>{let e=await n(r,t);return i(e)})}catch(r){throw new DecryptError("Failed to decrypt broadcast",r)}try{o=await s(this.#a,a,c)}catch(r){throw new ApplyError("Failed to apply broadcast",r)}let{applied:p,affected:h,duplicates:u}=o;return await this.emit("broadcast",{groupID:c,message:a,applied:p,affected:h,duplicates:u}),{kind:"app",message:a,applied:p,affected:h,duplicates:u}}async applyCatchupReply(r,e){return await o(this.#a,e,r)}async #c(r,e){return await this.#o.withHandle(r,async o=>{let s=t(e),i=o.epoch;if(null!=s&&s<i)return{kind:"mls",epoch:i,removed:[],added:[]};if(null!=s&&s>i)throw new CommitOutOfOrderError(s,i);let c=o.listMembers();try{await o.processMessage(e)}catch(r){throw new DecryptError("Failed to process MLS commit",r)}let n=o.listMembers(),l=new Set(c.map(r=>r.id)),d=new Set(n.map(r=>r.id)),m=c.filter(r=>!d.has(r.id)).map(r=>r.id),p=n.filter(r=>!l.has(r.id)).map(r=>r.id),h=a.serialize(this.#s.now()),{p2pStore:u}=this.#a;for(let e of m)await u.markMemberRemoved(r,e,h);for(let e of p)await u.addGroupMember({group_id:r,member_did:e,role:"member",hlc:h});return{kind:"mls",epoch:o.epoch,removed:m,added:p}})}}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/** A stale device's request for a fresh GroupInfo. Cleartext on the wire. */
|
|
2
|
-
export type RejoinRequest = {
|
|
3
|
-
requestID: string;
|
|
4
|
-
};
|
|
5
|
-
/** A current member's reply carrying a framed `MLSMessage(GroupInfo)`. */
|
|
6
|
-
export type RejoinGroupInfo = {
|
|
7
|
-
requestID: string;
|
|
8
|
-
/** Framed `MLSMessage(GroupInfo)` bytes. */
|
|
9
|
-
groupInfo: Uint8Array;
|
|
10
|
-
};
|
|
11
|
-
export declare function encodeRejoinRequest(request: RejoinRequest): Uint8Array;
|
|
12
|
-
export declare function decodeRejoinRequest(bytes: Uint8Array): RejoinRequest;
|
|
13
|
-
export declare function encodeRejoinGroupInfo(message: RejoinGroupInfo): Uint8Array;
|
|
14
|
-
export declare function decodeRejoinGroupInfo(bytes: Uint8Array): RejoinGroupInfo;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{replacer as e,reviver as r}from"./mls-json.js";let o=new TextEncoder,n=new TextDecoder;export function encodeRejoinRequest(r){return o.encode(JSON.stringify(r,e))}export function decodeRejoinRequest(e){let o=JSON.parse(n.decode(e),r);if("string"!=typeof o?.requestID)throw Error("invalid rejoin-request: missing requestID");return{requestID:o.requestID}}export function encodeRejoinGroupInfo(r){return o.encode(JSON.stringify(r,e))}export function decodeRejoinGroupInfo(e){let o=JSON.parse(n.decode(e),r);if("string"!=typeof o?.requestID)throw Error("invalid rejoin-groupinfo: missing requestID");if(!(o.groupInfo instanceof Uint8Array))throw Error("invalid rejoin-groupinfo: missing groupInfo bytes");return{requestID:o.requestID,groupInfo:o.groupInfo}}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Outer wire-frame discriminator for group hub payloads.
|
|
3
|
-
*
|
|
4
|
-
* The hub relay treats a group send payload as opaque bytes. Kubun prepends a
|
|
5
|
-
* single `kind` byte so the receiver can route a payload to the right decoder
|
|
6
|
-
* without trial-decryption:
|
|
7
|
-
*
|
|
8
|
-
* - `app` — an MLS-encrypted application broadcast (`GroupBroadcastMessage`),
|
|
9
|
-
* decoded via `handle.decrypt` + `deserializeBroadcast`.
|
|
10
|
-
* - `mls` — a framed MLS handshake message (a Commit), fed straight to
|
|
11
|
-
* `handle.processMessage`. The handshake is self-protected (encrypted
|
|
12
|
-
* `PrivateMessage`), so it is NOT wrapped in the application-encryption path.
|
|
13
|
-
*
|
|
14
|
-
* The discriminator lives entirely in kubun's payload bytes; the hub protocol
|
|
15
|
-
* is unchanged.
|
|
16
|
-
*/
|
|
17
|
-
export declare const WireKind: {
|
|
18
|
-
readonly app: 0;
|
|
19
|
-
readonly mls: 1;
|
|
20
|
-
readonly rejoinRequest: 2;
|
|
21
|
-
readonly rejoinGroupInfo: 3;
|
|
22
|
-
};
|
|
23
|
-
export type WireKind = (typeof WireKind)[keyof typeof WireKind];
|
|
24
|
-
/** Prepend the `kind` byte to a payload. */
|
|
25
|
-
export declare function frameWire(kind: WireKind, payload: Uint8Array): Uint8Array;
|
|
26
|
-
/**
|
|
27
|
-
* Split a framed payload into its `kind` and the remaining payload bytes.
|
|
28
|
-
* Throws on an empty frame or an unrecognized `kind` byte. The returned
|
|
29
|
-
* payload is a view (`subarray`) over the input, not a copy.
|
|
30
|
-
*/
|
|
31
|
-
export declare function unframeWire(framed: Uint8Array): {
|
|
32
|
-
kind: WireKind;
|
|
33
|
-
payload: Uint8Array;
|
|
34
|
-
};
|
package/lib/groups/wire-frame.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const WireKind={app:0,mls:1,rejoinRequest:2,rejoinGroupInfo:3};let r=new Set(Object.values(WireKind));export function frameWire(r,e){let n=new Uint8Array(e.length+1);return n[0]=r,n.set(e,1),n}export function unframeWire(e){if(0===e.length)throw Error("empty wire frame");let n=e[0];if(!r.has(n))throw Error(`unknown wire frame kind: ${n}`);return{kind:n,payload:e.subarray(1)}}
|