@did-btcr2/aggregation 0.1.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/LICENSE +373 -0
- package/README.md +19 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/browser.js +24017 -0
- package/dist/browser.mjs +24011 -0
- package/dist/cjs/index.js +5690 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/beacon-strategy.js +76 -0
- package/dist/esm/beacon-strategy.js.map +1 -0
- package/dist/esm/cohort.js +316 -0
- package/dist/esm/cohort.js.map +1 -0
- package/dist/esm/conditions.js +102 -0
- package/dist/esm/conditions.js.map +1 -0
- package/dist/esm/errors.js +22 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/fallback-spend.js +132 -0
- package/dist/esm/fallback-spend.js.map +1 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logger.js +15 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/messages/base.js +36 -0
- package/dist/esm/messages/base.js.map +1 -0
- package/dist/esm/messages/bodies.js +149 -0
- package/dist/esm/messages/bodies.js.map +1 -0
- package/dist/esm/messages/constants.js +37 -0
- package/dist/esm/messages/constants.js.map +1 -0
- package/dist/esm/messages/factories.js +139 -0
- package/dist/esm/messages/factories.js.map +1 -0
- package/dist/esm/messages/guards.js +40 -0
- package/dist/esm/messages/guards.js.map +1 -0
- package/dist/esm/messages/index.js +6 -0
- package/dist/esm/messages/index.js.map +1 -0
- package/dist/esm/participant.js +588 -0
- package/dist/esm/participant.js.map +1 -0
- package/dist/esm/phases.js +45 -0
- package/dist/esm/phases.js.map +1 -0
- package/dist/esm/recovery-policy.js +156 -0
- package/dist/esm/recovery-policy.js.map +1 -0
- package/dist/esm/recovery-spend.js +106 -0
- package/dist/esm/recovery-spend.js.map +1 -0
- package/dist/esm/runner/aggregation-runner.js +75 -0
- package/dist/esm/runner/aggregation-runner.js.map +1 -0
- package/dist/esm/runner/events.js +2 -0
- package/dist/esm/runner/events.js.map +1 -0
- package/dist/esm/runner/index.js +6 -0
- package/dist/esm/runner/index.js.map +1 -0
- package/dist/esm/runner/participant-runner.js +407 -0
- package/dist/esm/runner/participant-runner.js.map +1 -0
- package/dist/esm/runner/service-runner.js +701 -0
- package/dist/esm/runner/service-runner.js.map +1 -0
- package/dist/esm/runner/typed-emitter.js +80 -0
- package/dist/esm/runner/typed-emitter.js.map +1 -0
- package/dist/esm/service.js +774 -0
- package/dist/esm/service.js.map +1 -0
- package/dist/esm/signer.js +33 -0
- package/dist/esm/signer.js.map +1 -0
- package/dist/esm/signing-session.js +195 -0
- package/dist/esm/signing-session.js.map +1 -0
- package/dist/esm/transport/didcomm.js +41 -0
- package/dist/esm/transport/didcomm.js.map +1 -0
- package/dist/esm/transport/error.js +12 -0
- package/dist/esm/transport/error.js.map +1 -0
- package/dist/esm/transport/factory.js +29 -0
- package/dist/esm/transport/factory.js.map +1 -0
- package/dist/esm/transport/http/client.js +342 -0
- package/dist/esm/transport/http/client.js.map +1 -0
- package/dist/esm/transport/http/envelope.js +126 -0
- package/dist/esm/transport/http/envelope.js.map +1 -0
- package/dist/esm/transport/http/errors.js +11 -0
- package/dist/esm/transport/http/errors.js.map +1 -0
- package/dist/esm/transport/http/inbox-buffer.js +45 -0
- package/dist/esm/transport/http/inbox-buffer.js.map +1 -0
- package/dist/esm/transport/http/index.js +12 -0
- package/dist/esm/transport/http/index.js.map +1 -0
- package/dist/esm/transport/http/nonce-cache.js +38 -0
- package/dist/esm/transport/http/nonce-cache.js.map +1 -0
- package/dist/esm/transport/http/protocol.js +28 -0
- package/dist/esm/transport/http/protocol.js.map +1 -0
- package/dist/esm/transport/http/rate-limiter.js +45 -0
- package/dist/esm/transport/http/rate-limiter.js.map +1 -0
- package/dist/esm/transport/http/request-auth.js +100 -0
- package/dist/esm/transport/http/request-auth.js.map +1 -0
- package/dist/esm/transport/http/server.js +475 -0
- package/dist/esm/transport/http/server.js.map +1 -0
- package/dist/esm/transport/http/sse-stream.js +110 -0
- package/dist/esm/transport/http/sse-stream.js.map +1 -0
- package/dist/esm/transport/http/sse-writer.js +25 -0
- package/dist/esm/transport/http/sse-writer.js.map +1 -0
- package/dist/esm/transport/in-memory.js +149 -0
- package/dist/esm/transport/in-memory.js.map +1 -0
- package/dist/esm/transport/index.js +8 -0
- package/dist/esm/transport/index.js.map +1 -0
- package/dist/esm/transport/nostr.js +491 -0
- package/dist/esm/transport/nostr.js.map +1 -0
- package/dist/esm/transport/transport.js +2 -0
- package/dist/esm/transport/transport.js.map +1 -0
- package/dist/types/beacon-strategy.d.ts +56 -0
- package/dist/types/beacon-strategy.d.ts.map +1 -0
- package/dist/types/cohort.d.ts +188 -0
- package/dist/types/cohort.d.ts.map +1 -0
- package/dist/types/conditions.d.ts +86 -0
- package/dist/types/conditions.d.ts.map +1 -0
- package/dist/types/errors.d.ts +14 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/fallback-spend.d.ts +94 -0
- package/dist/types/fallback-spend.d.ts.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logger.d.ts +22 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/messages/base.d.ts +58 -0
- package/dist/types/messages/base.d.ts.map +1 -0
- package/dist/types/messages/bodies.d.ts +177 -0
- package/dist/types/messages/bodies.d.ts.map +1 -0
- package/dist/types/messages/constants.d.ts +31 -0
- package/dist/types/messages/constants.d.ts.map +1 -0
- package/dist/types/messages/factories.d.ts +217 -0
- package/dist/types/messages/factories.d.ts.map +1 -0
- package/dist/types/messages/guards.d.ts +11 -0
- package/dist/types/messages/guards.d.ts.map +1 -0
- package/dist/types/messages/index.d.ts +6 -0
- package/dist/types/messages/index.d.ts.map +1 -0
- package/dist/types/participant.d.ts +162 -0
- package/dist/types/participant.d.ts.map +1 -0
- package/dist/types/phases.d.ts +55 -0
- package/dist/types/phases.d.ts.map +1 -0
- package/dist/types/recovery-policy.d.ts +132 -0
- package/dist/types/recovery-policy.d.ts.map +1 -0
- package/dist/types/recovery-spend.d.ts +94 -0
- package/dist/types/recovery-spend.d.ts.map +1 -0
- package/dist/types/runner/aggregation-runner.d.ts +72 -0
- package/dist/types/runner/aggregation-runner.d.ts.map +1 -0
- package/dist/types/runner/events.d.ts +147 -0
- package/dist/types/runner/events.d.ts.map +1 -0
- package/dist/types/runner/index.d.ts +6 -0
- package/dist/types/runner/index.d.ts.map +1 -0
- package/dist/types/runner/participant-runner.d.ts +122 -0
- package/dist/types/runner/participant-runner.d.ts.map +1 -0
- package/dist/types/runner/service-runner.d.ts +228 -0
- package/dist/types/runner/service-runner.d.ts.map +1 -0
- package/dist/types/runner/typed-emitter.d.ts +41 -0
- package/dist/types/runner/typed-emitter.d.ts.map +1 -0
- package/dist/types/service.d.ts +177 -0
- package/dist/types/service.d.ts.map +1 -0
- package/dist/types/signer.d.ts +50 -0
- package/dist/types/signer.d.ts.map +1 -0
- package/dist/types/signing-session.d.ts +81 -0
- package/dist/types/signing-session.d.ts.map +1 -0
- package/dist/types/transport/didcomm.d.ts +23 -0
- package/dist/types/transport/didcomm.d.ts.map +1 -0
- package/dist/types/transport/error.d.ts +8 -0
- package/dist/types/transport/error.d.ts.map +1 -0
- package/dist/types/transport/factory.d.ts +28 -0
- package/dist/types/transport/factory.d.ts.map +1 -0
- package/dist/types/transport/http/client.d.ts +56 -0
- package/dist/types/transport/http/client.d.ts.map +1 -0
- package/dist/types/transport/http/envelope.d.ts +64 -0
- package/dist/types/transport/http/envelope.d.ts.map +1 -0
- package/dist/types/transport/http/errors.d.ts +9 -0
- package/dist/types/transport/http/errors.d.ts.map +1 -0
- package/dist/types/transport/http/inbox-buffer.d.ts +32 -0
- package/dist/types/transport/http/inbox-buffer.d.ts.map +1 -0
- package/dist/types/transport/http/index.d.ts +12 -0
- package/dist/types/transport/http/index.d.ts.map +1 -0
- package/dist/types/transport/http/nonce-cache.d.ts +26 -0
- package/dist/types/transport/http/nonce-cache.d.ts.map +1 -0
- package/dist/types/transport/http/protocol.d.ts +53 -0
- package/dist/types/transport/http/protocol.d.ts.map +1 -0
- package/dist/types/transport/http/rate-limiter.d.ts +41 -0
- package/dist/types/transport/http/rate-limiter.d.ts.map +1 -0
- package/dist/types/transport/http/request-auth.d.ts +50 -0
- package/dist/types/transport/http/request-auth.d.ts.map +1 -0
- package/dist/types/transport/http/server.d.ts +118 -0
- package/dist/types/transport/http/server.d.ts.map +1 -0
- package/dist/types/transport/http/sse-stream.d.ts +34 -0
- package/dist/types/transport/http/sse-stream.d.ts.map +1 -0
- package/dist/types/transport/http/sse-writer.d.ts +12 -0
- package/dist/types/transport/http/sse-writer.d.ts.map +1 -0
- package/dist/types/transport/in-memory.d.ts +64 -0
- package/dist/types/transport/in-memory.d.ts.map +1 -0
- package/dist/types/transport/index.d.ts +8 -0
- package/dist/types/transport/index.d.ts.map +1 -0
- package/dist/types/transport/nostr.d.ts +153 -0
- package/dist/types/transport/nostr.d.ts.map +1 -0
- package/dist/types/transport/transport.d.ts +62 -0
- package/dist/types/transport/transport.d.ts.map +1 -0
- package/package.json +128 -0
- package/src/beacon-strategy.ts +142 -0
- package/src/cohort.ts +422 -0
- package/src/conditions.ts +172 -0
- package/src/errors.ts +25 -0
- package/src/fallback-spend.ts +223 -0
- package/src/index.ts +23 -0
- package/src/logger.ts +33 -0
- package/src/messages/base.ts +80 -0
- package/src/messages/bodies.ts +328 -0
- package/src/messages/constants.ts +40 -0
- package/src/messages/factories.ts +297 -0
- package/src/messages/guards.ts +61 -0
- package/src/messages/index.ts +5 -0
- package/src/participant.ts +779 -0
- package/src/phases.ts +91 -0
- package/src/recovery-policy.ts +237 -0
- package/src/recovery-spend.ts +192 -0
- package/src/runner/aggregation-runner.ts +125 -0
- package/src/runner/events.ts +129 -0
- package/src/runner/index.ts +5 -0
- package/src/runner/participant-runner.ts +488 -0
- package/src/runner/service-runner.ts +891 -0
- package/src/runner/typed-emitter.ts +87 -0
- package/src/service.ts +1000 -0
- package/src/signer.ts +67 -0
- package/src/signing-session.ts +290 -0
- package/src/transport/didcomm.ts +59 -0
- package/src/transport/error.ts +13 -0
- package/src/transport/factory.ts +65 -0
- package/src/transport/http/client.ts +409 -0
- package/src/transport/http/envelope.ts +204 -0
- package/src/transport/http/errors.ts +11 -0
- package/src/transport/http/inbox-buffer.ts +53 -0
- package/src/transport/http/index.ts +11 -0
- package/src/transport/http/nonce-cache.ts +43 -0
- package/src/transport/http/protocol.ts +57 -0
- package/src/transport/http/rate-limiter.ts +75 -0
- package/src/transport/http/request-auth.ts +164 -0
- package/src/transport/http/server.ts +617 -0
- package/src/transport/http/sse-stream.ts +121 -0
- package/src/transport/http/sse-writer.ts +23 -0
- package/src/transport/in-memory.ts +178 -0
- package/src/transport/index.ts +7 -0
- package/src/transport/nostr.ts +576 -0
- package/src/transport/transport.ts +79 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed Server-Sent Events record.
|
|
3
|
+
*
|
|
4
|
+
* Events without a `data` field are never yielded (per the SSE spec - only a
|
|
5
|
+
* blank line that follows at least one `data:` line dispatches an event).
|
|
6
|
+
*/
|
|
7
|
+
export interface SseEvent {
|
|
8
|
+
/** Optional event name (from `event:` field). Defaults to "message" if omitted. */
|
|
9
|
+
event?: string;
|
|
10
|
+
/** Accumulated data payload (multiple `data:` lines joined with `\n`). */
|
|
11
|
+
data: string;
|
|
12
|
+
/** Last-Event-ID value for reconnect resumption. */
|
|
13
|
+
id?: string;
|
|
14
|
+
/** Retry delay hint in milliseconds. */
|
|
15
|
+
retry?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse an SSE stream into an async iterable of {@link SseEvent} records.
|
|
20
|
+
*
|
|
21
|
+
* The parser follows the HTML Living Standard ({@link https://html.spec.whatwg.org/multipage/server-sent-events.html})
|
|
22
|
+
* closely enough for our needs: LF and CRLF line terminators, multi-line
|
|
23
|
+
* `data` fields, `event` / `id` / `retry` fields, and `:`-prefixed comments.
|
|
24
|
+
* CR-only line terminators are not supported (every mainstream SSE
|
|
25
|
+
* implementation emits LF or CRLF).
|
|
26
|
+
*
|
|
27
|
+
* Pure, runtime-agnostic - works anywhere `ReadableStream<Uint8Array>` and
|
|
28
|
+
* `TextDecoder` exist (browsers and Node 22+).
|
|
29
|
+
*
|
|
30
|
+
* The caller owns stream lifecycle: cancellation should be effected via an
|
|
31
|
+
* `AbortController` on the producing `fetch`, which propagates as a read
|
|
32
|
+
* error and cleanly unwinds this generator's `finally`.
|
|
33
|
+
*/
|
|
34
|
+
export async function* parseSseStream(
|
|
35
|
+
readable: ReadableStream<Uint8Array>,
|
|
36
|
+
): AsyncGenerator<SseEvent, void, void> {
|
|
37
|
+
const decoder = new TextDecoder('utf-8');
|
|
38
|
+
const reader = readable.getReader();
|
|
39
|
+
|
|
40
|
+
let buffer = '';
|
|
41
|
+
let pending: { event?: string; data?: string; id?: string; retry?: number } = {};
|
|
42
|
+
|
|
43
|
+
const dispatchPending = (): SseEvent | null => {
|
|
44
|
+
if(pending.data === undefined) {
|
|
45
|
+
pending = {};
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const ev: SseEvent = { data: pending.data };
|
|
49
|
+
if(pending.event !== undefined) ev.event = pending.event;
|
|
50
|
+
if(pending.id !== undefined) ev.id = pending.id;
|
|
51
|
+
if(pending.retry !== undefined) ev.retry = pending.retry;
|
|
52
|
+
pending = {};
|
|
53
|
+
return ev;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const processLine = (line: string): void => {
|
|
57
|
+
if(line.startsWith(':')) return; // comment
|
|
58
|
+
|
|
59
|
+
const colon = line.indexOf(':');
|
|
60
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
61
|
+
let value = colon === -1 ? '' : line.slice(colon + 1);
|
|
62
|
+
if(value.startsWith(' ')) value = value.slice(1);
|
|
63
|
+
|
|
64
|
+
switch(field) {
|
|
65
|
+
case 'data':
|
|
66
|
+
pending.data = pending.data === undefined ? value : `${pending.data}\n${value}`;
|
|
67
|
+
break;
|
|
68
|
+
case 'event':
|
|
69
|
+
pending.event = value;
|
|
70
|
+
break;
|
|
71
|
+
case 'id':
|
|
72
|
+
// Per spec: ignore ids containing NUL.
|
|
73
|
+
if(!value.includes('\0')) pending.id = value;
|
|
74
|
+
break;
|
|
75
|
+
case 'retry': {
|
|
76
|
+
const n = Number(value);
|
|
77
|
+
if(Number.isInteger(n) && n >= 0) pending.retry = n;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
// Other fields (including unknown names) are ignored per the spec.
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
for(;;) {
|
|
86
|
+
const { value, done } = await reader.read();
|
|
87
|
+
if(done) {
|
|
88
|
+
// Flush any bytes the decoder is still holding.
|
|
89
|
+
buffer += decoder.decode();
|
|
90
|
+
if(buffer.length > 0) {
|
|
91
|
+
const line = buffer.endsWith('\r') ? buffer.slice(0, -1) : buffer;
|
|
92
|
+
if(line.length > 0) processLine(line);
|
|
93
|
+
buffer = '';
|
|
94
|
+
}
|
|
95
|
+
const tail = dispatchPending();
|
|
96
|
+
if(tail) yield tail;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
buffer += decoder.decode(value, { stream: true });
|
|
101
|
+
|
|
102
|
+
// Drain as many complete lines as are available.
|
|
103
|
+
let lineEnd = buffer.indexOf('\n');
|
|
104
|
+
while(lineEnd !== -1) {
|
|
105
|
+
let line = buffer.slice(0, lineEnd);
|
|
106
|
+
if(line.endsWith('\r')) line = line.slice(0, -1);
|
|
107
|
+
buffer = buffer.slice(lineEnd + 1);
|
|
108
|
+
|
|
109
|
+
if(line.length === 0) {
|
|
110
|
+
const ev = dispatchPending();
|
|
111
|
+
if(ev) yield ev;
|
|
112
|
+
} else {
|
|
113
|
+
processLine(line);
|
|
114
|
+
}
|
|
115
|
+
lineEnd = buffer.indexOf('\n');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
try { reader.releaseLock(); } catch { /* already released */ }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format an SSE event frame. Pairs with {@link parseSseStream}.
|
|
3
|
+
*
|
|
4
|
+
* Multi-line `data` is split across multiple `data:` lines per the SSE spec -
|
|
5
|
+
* each embedded `\n` becomes its own line, and the parser rejoins them.
|
|
6
|
+
*
|
|
7
|
+
* The returned string includes a trailing blank line (the dispatch marker).
|
|
8
|
+
*/
|
|
9
|
+
export function formatSseEvent(event: string, data: string, id?: string): string {
|
|
10
|
+
const lines: string[] = [];
|
|
11
|
+
if(id !== undefined) lines.push(`id: ${id}`);
|
|
12
|
+
lines.push(`event: ${event}`);
|
|
13
|
+
for(const part of data.split('\n')) lines.push(`data: ${part}`);
|
|
14
|
+
lines.push('');
|
|
15
|
+
lines.push('');
|
|
16
|
+
return lines.join('\n');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** SSE comment frame (server keepalive). Lines starting with `:` are ignored by compliant parsers. */
|
|
20
|
+
export function formatSseComment(comment: string): string {
|
|
21
|
+
const safe = comment.replace(/\n/g, ' ');
|
|
22
|
+
return `: ${safe}\n\n`;
|
|
23
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
2
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
3
|
+
import type { BaseMessage } from '../messages/base.js';
|
|
4
|
+
import type { MessageHandler, Transport } from './transport.js';
|
|
5
|
+
|
|
6
|
+
/** Internal registration for a single actor sharing an {@link InMemoryTransport}. */
|
|
7
|
+
interface ActorEntry {
|
|
8
|
+
/** Compressed communication public key. The in-process bus does no encryption, so no secret is retained. */
|
|
9
|
+
publicKey: Uint8Array;
|
|
10
|
+
handlers: Map<string, MessageHandler>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* In-process message bus connecting one or more {@link InMemoryTransport}
|
|
15
|
+
* instances. Routes broadcasts to every registered actor and directed messages
|
|
16
|
+
* to the actor that owns the recipient DID, with no relay, server, or network.
|
|
17
|
+
*
|
|
18
|
+
* Each delivery does a JSON round-trip (Uint8Array preserved as `__bytes` hex)
|
|
19
|
+
* so handlers receive an isolated, serialization-faithful copy, exactly as a
|
|
20
|
+
* real transport would. The message `body` is merged to the top level to match
|
|
21
|
+
* the shape the {@link NostrTransport} dispatch produces.
|
|
22
|
+
*
|
|
23
|
+
* @class InMemoryBus
|
|
24
|
+
*/
|
|
25
|
+
export class InMemoryBus {
|
|
26
|
+
#transports: Set<InMemoryTransport> = new Set();
|
|
27
|
+
|
|
28
|
+
/** Attach a transport to this bus. Called by the transport's constructor. */
|
|
29
|
+
register(transport: InMemoryTransport): void {
|
|
30
|
+
this.#transports.add(transport);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Detach a transport from this bus. */
|
|
34
|
+
unregister(transport: InMemoryTransport): void {
|
|
35
|
+
this.#transports.delete(transport);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Deliver a message. With no `recipient` the message is broadcast to every
|
|
40
|
+
* actor on the bus; otherwise it is routed to the single transport that owns
|
|
41
|
+
* the recipient DID.
|
|
42
|
+
*/
|
|
43
|
+
async deliver(message: BaseMessage, _sender: string, recipient?: string): Promise<void> {
|
|
44
|
+
const type = (message as { type?: string }).type;
|
|
45
|
+
if(!type) return;
|
|
46
|
+
|
|
47
|
+
// JSON round-trip to mimic transport serialization, preserving Uint8Array.
|
|
48
|
+
const replacer = (_k: string, v: unknown): unknown => v instanceof Uint8Array ? { __bytes: bytesToHex(v) } : v;
|
|
49
|
+
const reviver = (_k: string, v: unknown): unknown =>
|
|
50
|
+
v && typeof v === 'object' && '__bytes' in (v as Record<string, unknown>)
|
|
51
|
+
? hexToBytes((v as { __bytes: string }).__bytes)
|
|
52
|
+
: v;
|
|
53
|
+
const raw = JSON.parse(JSON.stringify(message, replacer), reviver) as Record<string, unknown>;
|
|
54
|
+
const serialized = { ...raw, ...((raw.body as Record<string, unknown> | undefined) ?? {}) };
|
|
55
|
+
|
|
56
|
+
if(!recipient) {
|
|
57
|
+
for(const t of this.#transports) {
|
|
58
|
+
await t.dispatchBroadcast(type, serialized);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
for(const t of this.#transports) {
|
|
63
|
+
if(t.hasActor(recipient)) {
|
|
64
|
+
await t.dispatchDirected(recipient, type, serialized);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* In-process {@link Transport} that routes aggregation messages through an
|
|
73
|
+
* {@link InMemoryBus} instead of a relay or HTTP server. Supports multiple
|
|
74
|
+
* actors per instance, so a single transport can host both a service and its
|
|
75
|
+
* participants (e.g. a cohort-of-one via {@link AggregationRunner.solo}).
|
|
76
|
+
*
|
|
77
|
+
* Encryption is a no-op (in-process, same trust domain); `registerPeer` /
|
|
78
|
+
* `getPeerPk` keep a registry so the contract matches the wire transports.
|
|
79
|
+
*
|
|
80
|
+
* @class InMemoryTransport
|
|
81
|
+
* @implements {Transport}
|
|
82
|
+
*/
|
|
83
|
+
export class InMemoryTransport implements Transport {
|
|
84
|
+
name: string = 'in-memory';
|
|
85
|
+
readonly bus: InMemoryBus;
|
|
86
|
+
|
|
87
|
+
#actors: Map<string, ActorEntry> = new Map();
|
|
88
|
+
#peers: Map<string, Uint8Array> = new Map();
|
|
89
|
+
|
|
90
|
+
/** @param bus Shared bus. Pass the same bus to connect multiple transports. */
|
|
91
|
+
constructor(bus: InMemoryBus = new InMemoryBus()) {
|
|
92
|
+
this.bus = bus;
|
|
93
|
+
this.bus.register(this);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
start(): void {
|
|
97
|
+
// No-op: there is no underlying connection to open.
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
registerActor(did: string, keys: SchnorrKeyPair): void {
|
|
101
|
+
// In-process delivery does no signing or encryption (same trust domain), so
|
|
102
|
+
// retain only the public key - the secret-bearing keypair is never stored in
|
|
103
|
+
// the transport registry (see ADR 038).
|
|
104
|
+
this.#actors.set(did, { publicKey: keys.publicKey.compressed, handlers: new Map() });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getActorPk(did: string): Uint8Array | undefined {
|
|
108
|
+
return this.#actors.get(did)?.publicKey;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** True if `did` is registered on this transport. Used by the bus for routing. */
|
|
112
|
+
hasActor(did: string): boolean {
|
|
113
|
+
return this.#actors.has(did);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
registerPeer(did: string, communicationPk: Uint8Array): void {
|
|
117
|
+
this.#peers.set(did, communicationPk);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getPeerPk(did: string): Uint8Array | undefined {
|
|
121
|
+
return this.#peers.get(did);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
registerMessageHandler(actorDid: string, messageType: string, handler: MessageHandler): void {
|
|
125
|
+
const actor = this.#actors.get(actorDid);
|
|
126
|
+
if(actor) actor.handlers.set(messageType, handler);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
unregisterMessageHandler(actorDid: string, messageType: string): void {
|
|
130
|
+
const actor = this.#actors.get(actorDid);
|
|
131
|
+
if(actor) actor.handlers.delete(messageType);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
unregisterActor(did: string): void {
|
|
135
|
+
const actor = this.#actors.get(did);
|
|
136
|
+
if(!actor) return;
|
|
137
|
+
actor.handlers.clear();
|
|
138
|
+
this.#actors.delete(did);
|
|
139
|
+
this.#peers.delete(did);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async sendMessage(message: BaseMessage, sender: string, recipient?: string): Promise<void> {
|
|
143
|
+
await this.bus.deliver(message, sender, recipient);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
publishRepeating(
|
|
147
|
+
message: BaseMessage,
|
|
148
|
+
sender: string,
|
|
149
|
+
intervalMs: number,
|
|
150
|
+
recipient?: string,
|
|
151
|
+
): () => void {
|
|
152
|
+
let stopped = false;
|
|
153
|
+
void this.sendMessage(message, sender, recipient).catch(() => { /* in-process: no relay to reject */ });
|
|
154
|
+
const timer = setInterval(() => {
|
|
155
|
+
if(stopped) return;
|
|
156
|
+
void this.sendMessage(message, sender, recipient).catch(() => { /* ignore */ });
|
|
157
|
+
}, intervalMs);
|
|
158
|
+
return () => {
|
|
159
|
+
if(stopped) return;
|
|
160
|
+
stopped = true;
|
|
161
|
+
clearInterval(timer);
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Deliver a broadcast message to every actor on this transport that handles `type`. */
|
|
166
|
+
async dispatchBroadcast(type: string, message: unknown): Promise<void> {
|
|
167
|
+
for(const actor of this.#actors.values()) {
|
|
168
|
+
const handler = actor.handlers.get(type);
|
|
169
|
+
if(handler) await handler(message);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Deliver a directed message to the recipient actor's handler for `type`. */
|
|
174
|
+
async dispatchDirected(recipientDid: string, type: string, message: unknown): Promise<void> {
|
|
175
|
+
const handler = this.#actors.get(recipientDid)?.handlers.get(type);
|
|
176
|
+
if(handler) await handler(message);
|
|
177
|
+
}
|
|
178
|
+
}
|