@did-btcr2/method 0.37.0 → 0.39.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.
Files changed (245) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/browser.js +47868 -34771
  3. package/dist/browser.mjs +47867 -34770
  4. package/dist/cjs/index.js +462 -6060
  5. package/dist/esm/core/beacon/beacon.js +101 -10
  6. package/dist/esm/core/beacon/beacon.js.map +1 -1
  7. package/dist/esm/core/beacon/fee-estimator.js +12 -27
  8. package/dist/esm/core/beacon/fee-estimator.js.map +1 -1
  9. package/dist/esm/core/did-sender-resolver.js +26 -0
  10. package/dist/esm/core/did-sender-resolver.js.map +1 -0
  11. package/dist/esm/index.js +3 -17
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/types/core/beacon/beacon.d.ts +59 -3
  14. package/dist/types/core/beacon/beacon.d.ts.map +1 -1
  15. package/dist/types/core/beacon/fee-estimator.d.ts +10 -37
  16. package/dist/types/core/beacon/fee-estimator.d.ts.map +1 -1
  17. package/dist/types/core/did-sender-resolver.d.ts +14 -0
  18. package/dist/types/core/did-sender-resolver.d.ts.map +1 -0
  19. package/dist/types/index.d.ts +2 -16
  20. package/dist/types/index.d.ts.map +1 -1
  21. package/package.json +6 -5
  22. package/src/core/beacon/beacon.ts +131 -12
  23. package/src/core/beacon/fee-estimator.ts +15 -49
  24. package/src/core/did-sender-resolver.ts +25 -0
  25. package/src/index.ts +3 -17
  26. package/dist/esm/core/aggregation/beacon-strategy.js +0 -76
  27. package/dist/esm/core/aggregation/beacon-strategy.js.map +0 -1
  28. package/dist/esm/core/aggregation/cohort.js +0 -316
  29. package/dist/esm/core/aggregation/cohort.js.map +0 -1
  30. package/dist/esm/core/aggregation/conditions.js +0 -102
  31. package/dist/esm/core/aggregation/conditions.js.map +0 -1
  32. package/dist/esm/core/aggregation/errors.js +0 -22
  33. package/dist/esm/core/aggregation/errors.js.map +0 -1
  34. package/dist/esm/core/aggregation/fallback-spend.js +0 -132
  35. package/dist/esm/core/aggregation/fallback-spend.js.map +0 -1
  36. package/dist/esm/core/aggregation/logger.js +0 -15
  37. package/dist/esm/core/aggregation/logger.js.map +0 -1
  38. package/dist/esm/core/aggregation/messages/base.js +0 -36
  39. package/dist/esm/core/aggregation/messages/base.js.map +0 -1
  40. package/dist/esm/core/aggregation/messages/bodies.js +0 -149
  41. package/dist/esm/core/aggregation/messages/bodies.js.map +0 -1
  42. package/dist/esm/core/aggregation/messages/constants.js +0 -37
  43. package/dist/esm/core/aggregation/messages/constants.js.map +0 -1
  44. package/dist/esm/core/aggregation/messages/factories.js +0 -139
  45. package/dist/esm/core/aggregation/messages/factories.js.map +0 -1
  46. package/dist/esm/core/aggregation/messages/guards.js +0 -40
  47. package/dist/esm/core/aggregation/messages/guards.js.map +0 -1
  48. package/dist/esm/core/aggregation/messages/index.js +0 -6
  49. package/dist/esm/core/aggregation/messages/index.js.map +0 -1
  50. package/dist/esm/core/aggregation/participant.js +0 -588
  51. package/dist/esm/core/aggregation/participant.js.map +0 -1
  52. package/dist/esm/core/aggregation/phases.js +0 -45
  53. package/dist/esm/core/aggregation/phases.js.map +0 -1
  54. package/dist/esm/core/aggregation/recovery-policy.js +0 -156
  55. package/dist/esm/core/aggregation/recovery-policy.js.map +0 -1
  56. package/dist/esm/core/aggregation/recovery-spend.js +0 -106
  57. package/dist/esm/core/aggregation/recovery-spend.js.map +0 -1
  58. package/dist/esm/core/aggregation/runner/aggregation-runner.js +0 -74
  59. package/dist/esm/core/aggregation/runner/aggregation-runner.js.map +0 -1
  60. package/dist/esm/core/aggregation/runner/events.js +0 -2
  61. package/dist/esm/core/aggregation/runner/events.js.map +0 -1
  62. package/dist/esm/core/aggregation/runner/index.js +0 -6
  63. package/dist/esm/core/aggregation/runner/index.js.map +0 -1
  64. package/dist/esm/core/aggregation/runner/participant-runner.js +0 -407
  65. package/dist/esm/core/aggregation/runner/participant-runner.js.map +0 -1
  66. package/dist/esm/core/aggregation/runner/service-runner.js +0 -694
  67. package/dist/esm/core/aggregation/runner/service-runner.js.map +0 -1
  68. package/dist/esm/core/aggregation/runner/typed-emitter.js +0 -80
  69. package/dist/esm/core/aggregation/runner/typed-emitter.js.map +0 -1
  70. package/dist/esm/core/aggregation/service.js +0 -774
  71. package/dist/esm/core/aggregation/service.js.map +0 -1
  72. package/dist/esm/core/aggregation/signer.js +0 -33
  73. package/dist/esm/core/aggregation/signer.js.map +0 -1
  74. package/dist/esm/core/aggregation/signing-session.js +0 -195
  75. package/dist/esm/core/aggregation/signing-session.js.map +0 -1
  76. package/dist/esm/core/aggregation/transport/didcomm.js +0 -41
  77. package/dist/esm/core/aggregation/transport/didcomm.js.map +0 -1
  78. package/dist/esm/core/aggregation/transport/error.js +0 -12
  79. package/dist/esm/core/aggregation/transport/error.js.map +0 -1
  80. package/dist/esm/core/aggregation/transport/factory.js +0 -29
  81. package/dist/esm/core/aggregation/transport/factory.js.map +0 -1
  82. package/dist/esm/core/aggregation/transport/http/client.js +0 -350
  83. package/dist/esm/core/aggregation/transport/http/client.js.map +0 -1
  84. package/dist/esm/core/aggregation/transport/http/envelope.js +0 -126
  85. package/dist/esm/core/aggregation/transport/http/envelope.js.map +0 -1
  86. package/dist/esm/core/aggregation/transport/http/errors.js +0 -11
  87. package/dist/esm/core/aggregation/transport/http/errors.js.map +0 -1
  88. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js +0 -45
  89. package/dist/esm/core/aggregation/transport/http/inbox-buffer.js.map +0 -1
  90. package/dist/esm/core/aggregation/transport/http/index.js +0 -12
  91. package/dist/esm/core/aggregation/transport/http/index.js.map +0 -1
  92. package/dist/esm/core/aggregation/transport/http/nonce-cache.js +0 -38
  93. package/dist/esm/core/aggregation/transport/http/nonce-cache.js.map +0 -1
  94. package/dist/esm/core/aggregation/transport/http/protocol.js +0 -28
  95. package/dist/esm/core/aggregation/transport/http/protocol.js.map +0 -1
  96. package/dist/esm/core/aggregation/transport/http/rate-limiter.js +0 -45
  97. package/dist/esm/core/aggregation/transport/http/rate-limiter.js.map +0 -1
  98. package/dist/esm/core/aggregation/transport/http/request-auth.js +0 -100
  99. package/dist/esm/core/aggregation/transport/http/request-auth.js.map +0 -1
  100. package/dist/esm/core/aggregation/transport/http/server.js +0 -481
  101. package/dist/esm/core/aggregation/transport/http/server.js.map +0 -1
  102. package/dist/esm/core/aggregation/transport/http/sse-stream.js +0 -110
  103. package/dist/esm/core/aggregation/transport/http/sse-stream.js.map +0 -1
  104. package/dist/esm/core/aggregation/transport/http/sse-writer.js +0 -25
  105. package/dist/esm/core/aggregation/transport/http/sse-writer.js.map +0 -1
  106. package/dist/esm/core/aggregation/transport/in-memory.js +0 -149
  107. package/dist/esm/core/aggregation/transport/in-memory.js.map +0 -1
  108. package/dist/esm/core/aggregation/transport/index.js +0 -8
  109. package/dist/esm/core/aggregation/transport/index.js.map +0 -1
  110. package/dist/esm/core/aggregation/transport/nostr.js +0 -491
  111. package/dist/esm/core/aggregation/transport/nostr.js.map +0 -1
  112. package/dist/esm/core/aggregation/transport/transport.js +0 -2
  113. package/dist/esm/core/aggregation/transport/transport.js.map +0 -1
  114. package/dist/types/core/aggregation/beacon-strategy.d.ts +0 -56
  115. package/dist/types/core/aggregation/beacon-strategy.d.ts.map +0 -1
  116. package/dist/types/core/aggregation/cohort.d.ts +0 -182
  117. package/dist/types/core/aggregation/cohort.d.ts.map +0 -1
  118. package/dist/types/core/aggregation/conditions.d.ts +0 -86
  119. package/dist/types/core/aggregation/conditions.d.ts.map +0 -1
  120. package/dist/types/core/aggregation/errors.d.ts +0 -14
  121. package/dist/types/core/aggregation/errors.d.ts.map +0 -1
  122. package/dist/types/core/aggregation/fallback-spend.d.ts +0 -94
  123. package/dist/types/core/aggregation/fallback-spend.d.ts.map +0 -1
  124. package/dist/types/core/aggregation/logger.d.ts +0 -22
  125. package/dist/types/core/aggregation/logger.d.ts.map +0 -1
  126. package/dist/types/core/aggregation/messages/base.d.ts +0 -58
  127. package/dist/types/core/aggregation/messages/base.d.ts.map +0 -1
  128. package/dist/types/core/aggregation/messages/bodies.d.ts +0 -177
  129. package/dist/types/core/aggregation/messages/bodies.d.ts.map +0 -1
  130. package/dist/types/core/aggregation/messages/constants.d.ts +0 -31
  131. package/dist/types/core/aggregation/messages/constants.d.ts.map +0 -1
  132. package/dist/types/core/aggregation/messages/factories.d.ts +0 -217
  133. package/dist/types/core/aggregation/messages/factories.d.ts.map +0 -1
  134. package/dist/types/core/aggregation/messages/guards.d.ts +0 -11
  135. package/dist/types/core/aggregation/messages/guards.d.ts.map +0 -1
  136. package/dist/types/core/aggregation/messages/index.d.ts +0 -6
  137. package/dist/types/core/aggregation/messages/index.d.ts.map +0 -1
  138. package/dist/types/core/aggregation/participant.d.ts +0 -162
  139. package/dist/types/core/aggregation/participant.d.ts.map +0 -1
  140. package/dist/types/core/aggregation/phases.d.ts +0 -55
  141. package/dist/types/core/aggregation/phases.d.ts.map +0 -1
  142. package/dist/types/core/aggregation/recovery-policy.d.ts +0 -132
  143. package/dist/types/core/aggregation/recovery-policy.d.ts.map +0 -1
  144. package/dist/types/core/aggregation/recovery-spend.d.ts +0 -94
  145. package/dist/types/core/aggregation/recovery-spend.d.ts.map +0 -1
  146. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts +0 -66
  147. package/dist/types/core/aggregation/runner/aggregation-runner.d.ts.map +0 -1
  148. package/dist/types/core/aggregation/runner/events.d.ts +0 -147
  149. package/dist/types/core/aggregation/runner/events.d.ts.map +0 -1
  150. package/dist/types/core/aggregation/runner/index.d.ts +0 -6
  151. package/dist/types/core/aggregation/runner/index.d.ts.map +0 -1
  152. package/dist/types/core/aggregation/runner/participant-runner.d.ts +0 -122
  153. package/dist/types/core/aggregation/runner/participant-runner.d.ts.map +0 -1
  154. package/dist/types/core/aggregation/runner/service-runner.d.ts +0 -211
  155. package/dist/types/core/aggregation/runner/service-runner.d.ts.map +0 -1
  156. package/dist/types/core/aggregation/runner/typed-emitter.d.ts +0 -41
  157. package/dist/types/core/aggregation/runner/typed-emitter.d.ts.map +0 -1
  158. package/dist/types/core/aggregation/service.d.ts +0 -177
  159. package/dist/types/core/aggregation/service.d.ts.map +0 -1
  160. package/dist/types/core/aggregation/signer.d.ts +0 -50
  161. package/dist/types/core/aggregation/signer.d.ts.map +0 -1
  162. package/dist/types/core/aggregation/signing-session.d.ts +0 -81
  163. package/dist/types/core/aggregation/signing-session.d.ts.map +0 -1
  164. package/dist/types/core/aggregation/transport/didcomm.d.ts +0 -23
  165. package/dist/types/core/aggregation/transport/didcomm.d.ts.map +0 -1
  166. package/dist/types/core/aggregation/transport/error.d.ts +0 -8
  167. package/dist/types/core/aggregation/transport/error.d.ts.map +0 -1
  168. package/dist/types/core/aggregation/transport/factory.d.ts +0 -28
  169. package/dist/types/core/aggregation/transport/factory.d.ts.map +0 -1
  170. package/dist/types/core/aggregation/transport/http/client.d.ts +0 -48
  171. package/dist/types/core/aggregation/transport/http/client.d.ts.map +0 -1
  172. package/dist/types/core/aggregation/transport/http/envelope.d.ts +0 -64
  173. package/dist/types/core/aggregation/transport/http/envelope.d.ts.map +0 -1
  174. package/dist/types/core/aggregation/transport/http/errors.d.ts +0 -9
  175. package/dist/types/core/aggregation/transport/http/errors.d.ts.map +0 -1
  176. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts +0 -32
  177. package/dist/types/core/aggregation/transport/http/inbox-buffer.d.ts.map +0 -1
  178. package/dist/types/core/aggregation/transport/http/index.d.ts +0 -12
  179. package/dist/types/core/aggregation/transport/http/index.d.ts.map +0 -1
  180. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts +0 -26
  181. package/dist/types/core/aggregation/transport/http/nonce-cache.d.ts.map +0 -1
  182. package/dist/types/core/aggregation/transport/http/protocol.d.ts +0 -53
  183. package/dist/types/core/aggregation/transport/http/protocol.d.ts.map +0 -1
  184. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts +0 -41
  185. package/dist/types/core/aggregation/transport/http/rate-limiter.d.ts.map +0 -1
  186. package/dist/types/core/aggregation/transport/http/request-auth.d.ts +0 -50
  187. package/dist/types/core/aggregation/transport/http/request-auth.d.ts.map +0 -1
  188. package/dist/types/core/aggregation/transport/http/server.d.ts +0 -110
  189. package/dist/types/core/aggregation/transport/http/server.d.ts.map +0 -1
  190. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts +0 -34
  191. package/dist/types/core/aggregation/transport/http/sse-stream.d.ts.map +0 -1
  192. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts +0 -12
  193. package/dist/types/core/aggregation/transport/http/sse-writer.d.ts.map +0 -1
  194. package/dist/types/core/aggregation/transport/in-memory.d.ts +0 -64
  195. package/dist/types/core/aggregation/transport/in-memory.d.ts.map +0 -1
  196. package/dist/types/core/aggregation/transport/index.d.ts +0 -8
  197. package/dist/types/core/aggregation/transport/index.d.ts.map +0 -1
  198. package/dist/types/core/aggregation/transport/nostr.d.ts +0 -153
  199. package/dist/types/core/aggregation/transport/nostr.d.ts.map +0 -1
  200. package/dist/types/core/aggregation/transport/transport.d.ts +0 -62
  201. package/dist/types/core/aggregation/transport/transport.d.ts.map +0 -1
  202. package/src/core/aggregation/beacon-strategy.ts +0 -142
  203. package/src/core/aggregation/cohort.ts +0 -415
  204. package/src/core/aggregation/conditions.ts +0 -172
  205. package/src/core/aggregation/errors.ts +0 -25
  206. package/src/core/aggregation/fallback-spend.ts +0 -223
  207. package/src/core/aggregation/logger.ts +0 -33
  208. package/src/core/aggregation/messages/base.ts +0 -80
  209. package/src/core/aggregation/messages/bodies.ts +0 -328
  210. package/src/core/aggregation/messages/constants.ts +0 -40
  211. package/src/core/aggregation/messages/factories.ts +0 -297
  212. package/src/core/aggregation/messages/guards.ts +0 -61
  213. package/src/core/aggregation/messages/index.ts +0 -5
  214. package/src/core/aggregation/participant.ts +0 -779
  215. package/src/core/aggregation/phases.ts +0 -91
  216. package/src/core/aggregation/recovery-policy.ts +0 -237
  217. package/src/core/aggregation/recovery-spend.ts +0 -192
  218. package/src/core/aggregation/runner/aggregation-runner.ts +0 -118
  219. package/src/core/aggregation/runner/events.ts +0 -129
  220. package/src/core/aggregation/runner/index.ts +0 -5
  221. package/src/core/aggregation/runner/participant-runner.ts +0 -488
  222. package/src/core/aggregation/runner/service-runner.ts +0 -866
  223. package/src/core/aggregation/runner/typed-emitter.ts +0 -87
  224. package/src/core/aggregation/service.ts +0 -1000
  225. package/src/core/aggregation/signer.ts +0 -67
  226. package/src/core/aggregation/signing-session.ts +0 -290
  227. package/src/core/aggregation/transport/didcomm.ts +0 -59
  228. package/src/core/aggregation/transport/error.ts +0 -13
  229. package/src/core/aggregation/transport/factory.ts +0 -65
  230. package/src/core/aggregation/transport/http/client.ts +0 -409
  231. package/src/core/aggregation/transport/http/envelope.ts +0 -204
  232. package/src/core/aggregation/transport/http/errors.ts +0 -11
  233. package/src/core/aggregation/transport/http/inbox-buffer.ts +0 -53
  234. package/src/core/aggregation/transport/http/index.ts +0 -11
  235. package/src/core/aggregation/transport/http/nonce-cache.ts +0 -43
  236. package/src/core/aggregation/transport/http/protocol.ts +0 -57
  237. package/src/core/aggregation/transport/http/rate-limiter.ts +0 -75
  238. package/src/core/aggregation/transport/http/request-auth.ts +0 -164
  239. package/src/core/aggregation/transport/http/server.ts +0 -615
  240. package/src/core/aggregation/transport/http/sse-stream.ts +0 -121
  241. package/src/core/aggregation/transport/http/sse-writer.ts +0 -23
  242. package/src/core/aggregation/transport/in-memory.ts +0 -178
  243. package/src/core/aggregation/transport/index.ts +0 -7
  244. package/src/core/aggregation/transport/nostr.ts +0 -576
  245. package/src/core/aggregation/transport/transport.ts +0 -79
@@ -1,615 +0,0 @@
1
- import type { SchnorrKeyPair } from '@did-btcr2/keypair';
2
- import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
3
-
4
- import { Identifier } from '../../../identifier.js';
5
- import type { Logger } from '../../logger.js';
6
- import { CONSOLE_LOGGER } from '../../logger.js';
7
- import type { BaseMessage } from '../../messages/base.js';
8
- import type { MessageHandler, Transport } from '../transport.js';
9
- import { reviveFromWire, signEnvelope, verifyEnvelope } from './envelope.js';
10
- import { HttpTransportError } from './errors.js';
11
- import { InboxBuffer } from './inbox-buffer.js';
12
- import { NonceCache } from './nonce-cache.js';
13
- import {
14
- DEFAULT_CLOCK_SKEW_SEC,
15
- HTTP_ENVELOPE_VERSION,
16
- HTTP_ROUTE,
17
- SSE_EVENT,
18
- type SignedEnvelope,
19
- } from './protocol.js';
20
- import { RateLimiter } from './rate-limiter.js';
21
- import { verifyRequestAuth } from './request-auth.js';
22
-
23
- /** Framework-agnostic incoming-request shape. */
24
- export interface HttpRequestLike {
25
- method: string;
26
- /** Either a full URL or path+query; path extraction works with both. */
27
- url: string;
28
- /** Header names MUST be lowercased. */
29
- headers: Record<string, string>;
30
- /** Request body (already read). Undefined for GETs. */
31
- body?: string;
32
- /** Optional remote-address hint for per-IP policies (advert rate limits etc). */
33
- remoteAddr?: string;
34
- }
35
-
36
- /** Framework-agnostic outgoing-response shape. */
37
- export interface HttpResponseLike {
38
- status: number;
39
- headers: Record<string, string>;
40
- body: string;
41
- }
42
-
43
- /**
44
- * Framework-agnostic SSE stream handle. Callers (Express/Hono/Fastify/Workers)
45
- * adapt this to their response-writing primitives. All methods are synchronous
46
- * from the transport's perspective; the adapter is free to buffer/batch.
47
- */
48
- export interface SseStream {
49
- /** Write a named event with data and optional id. */
50
- writeEvent(event: string, data: string, id?: string): void;
51
- /** Write a comment frame (keepalive; ignored by parsers). */
52
- writeComment(comment: string): void;
53
- /** Close the stream from the server side. */
54
- close(): void;
55
- /** Register a callback fired when the client disconnects (or we close). */
56
- onClose(cb: () => void): void;
57
- }
58
-
59
- export type CorsPolicy =
60
- | { mode: 'permissive' }
61
- | { mode: 'allowlist'; origins: string[] }
62
- | { mode: 'same-origin' };
63
-
64
- export interface HttpServerTransportConfig {
65
- logger?: Logger;
66
- /** CORS policy. Defaults to `{ mode: 'permissive' }`. */
67
- cors?: CorsPolicy;
68
- /** Envelope / request-auth clock-skew tolerance, seconds. */
69
- clockSkewSec?: number;
70
- /** Per-recipient inbox buffer size. Default 100. */
71
- inboxBufferSize?: number;
72
- /** Advert cache TTL, milliseconds. Default 5 minutes. */
73
- advertTtlMs?: number;
74
- /** Custom rate limiter (pre-configured). If absent, uses defaults. */
75
- rateLimiter?: RateLimiter;
76
- /** Custom nonce cache (pre-configured). If absent, uses defaults. */
77
- nonceCache?: NonceCache;
78
- /** SSE heartbeat interval, milliseconds. Default 20000. Set 0 to disable. */
79
- heartbeatIntervalMs?: number;
80
- /** Clock injection point for tests. Returns unix milliseconds. */
81
- now?: () => number;
82
- }
83
-
84
- interface ActorEntry {
85
- keys: SchnorrKeyPair;
86
- handlers: Map<string, MessageHandler>;
87
- }
88
-
89
- interface InboxSubscriber {
90
- stream: SseStream;
91
- heartbeatTimer?: ReturnType<typeof setInterval>;
92
- }
93
-
94
- interface BroadcastSubscriber {
95
- stream: SseStream;
96
- heartbeatTimer?: ReturnType<typeof setInterval>;
97
- }
98
-
99
- interface InboxState {
100
- buffer: InboxBuffer;
101
- subscribers: Set<InboxSubscriber>;
102
- }
103
-
104
- interface CurrentAdvert {
105
- dataJson: string;
106
- id: string;
107
- expiresAtMs: number;
108
- }
109
-
110
- const INBOX_PATH_PREFIX = '/v1/actors/';
111
- const INBOX_PATH_SUFFIX = '/inbox';
112
-
113
- const DEFAULT_ADVERT_TTL_MS = 5 * 60 * 1000;
114
- const DEFAULT_HEARTBEAT_MS = 20_000;
115
-
116
- /**
117
- * Server-side HTTP transport. Sans-I/O - the caller mounts
118
- * {@link handleRequest} and {@link handleSse} under their HTTP framework of
119
- * choice; the transport owns only in-memory state (actors, inboxes, advert
120
- * cache, replay / rate-limit policies).
121
- *
122
- * Implements the generic {@link Transport} interface so the aggregation
123
- * runners can drive it exactly the same way they drive {@link NostrTransport}
124
- * or {@link HttpClientTransport}.
125
- */
126
- export class HttpServerTransport implements Transport {
127
- readonly name = 'http';
128
-
129
- readonly #logger: Logger;
130
- readonly #cors: CorsPolicy;
131
- readonly #clockSkewSec: number;
132
- readonly #inboxBufferSize: number;
133
- readonly #advertTtlMs: number;
134
- readonly #heartbeatMs: number;
135
- readonly #rateLimiter: RateLimiter;
136
- readonly #nonceCache: NonceCache;
137
- readonly #now: () => number;
138
-
139
- readonly #actors: Map<string, ActorEntry> = new Map();
140
- readonly #peers: Map<string, Uint8Array> = new Map();
141
- readonly #inboxes: Map<string, InboxState> = new Map();
142
-
143
- readonly #broadcastSubscribers: Set<BroadcastSubscriber> = new Set();
144
-
145
- #currentAdvert?: CurrentAdvert;
146
- #advertSeq = 0;
147
-
148
- constructor(config: HttpServerTransportConfig = {}) {
149
- this.#logger = config.logger ?? CONSOLE_LOGGER;
150
- this.#cors = config.cors ?? { mode: 'permissive' };
151
- this.#clockSkewSec = config.clockSkewSec ?? DEFAULT_CLOCK_SKEW_SEC;
152
- this.#inboxBufferSize = config.inboxBufferSize ?? 100;
153
- this.#advertTtlMs = config.advertTtlMs ?? DEFAULT_ADVERT_TTL_MS;
154
- this.#heartbeatMs = config.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_MS;
155
- this.#rateLimiter = config.rateLimiter ?? new RateLimiter();
156
- this.#nonceCache = config.nonceCache ?? new NonceCache();
157
- this.#now = config.now ?? (() => Date.now());
158
- }
159
-
160
- // ----------------------------------------------------------------
161
- // Transport interface
162
- // ----------------------------------------------------------------
163
-
164
- start(): void {
165
- // No-op: server-side transport has no persistent outbound connections.
166
- // SSE subscribers connect on demand via handleSse().
167
- }
168
-
169
- /**
170
- * Detach the transport: close every open SSE subscription, clear the advert
171
- * cache, and drop all actor / peer / inbox state. Intended for shutdown and
172
- * for test teardown.
173
- */
174
- stop(): void {
175
- for(const sub of this.#broadcastSubscribers) this.#closeBroadcastSubscriber(sub);
176
- this.#broadcastSubscribers.clear();
177
- for(const inbox of this.#inboxes.values()) {
178
- for(const sub of inbox.subscribers) this.#closeInboxSubscriber(sub);
179
- inbox.subscribers.clear();
180
- }
181
- this.#inboxes.clear();
182
- this.#currentAdvert = undefined;
183
- }
184
-
185
- registerActor(did: string, keys: SchnorrKeyPair): void {
186
- this.#actors.set(did, { keys, handlers: new Map() });
187
- }
188
-
189
- unregisterActor(did: string): void {
190
- this.#actors.delete(did);
191
- this.#peers.delete(did);
192
- }
193
-
194
- getActorPk(did: string): Uint8Array | undefined {
195
- return this.#actors.get(did)?.keys.publicKey.compressed;
196
- }
197
-
198
- registerPeer(did: string, communicationPk: Uint8Array): void {
199
- try {
200
- new CompressedSecp256k1PublicKey(communicationPk);
201
- } catch {
202
- throw new HttpTransportError(
203
- `Invalid peer public key for ${did}`,
204
- 'INVALID_PEER_KEY',
205
- { did, keyLength: communicationPk.length },
206
- );
207
- }
208
- this.#peers.set(did, communicationPk);
209
- }
210
-
211
- getPeerPk(did: string): Uint8Array | undefined {
212
- return this.#peers.get(did);
213
- }
214
-
215
- registerMessageHandler(actorDid: string, messageType: string, handler: MessageHandler): void {
216
- const actor = this.#actors.get(actorDid);
217
- if(!actor) {
218
- throw new HttpTransportError(
219
- `Cannot register handler: actor ${actorDid} not registered`,
220
- 'UNKNOWN_ACTOR',
221
- { did: actorDid },
222
- );
223
- }
224
- actor.handlers.set(messageType, handler);
225
- }
226
-
227
- unregisterMessageHandler(actorDid: string, messageType: string): void {
228
- this.#actors.get(actorDid)?.handlers.delete(messageType);
229
- }
230
-
231
- async sendMessage(message: BaseMessage, sender: string, recipient?: string): Promise<void> {
232
- if(!recipient) {
233
- throw new HttpTransportError(
234
- 'HttpServerTransport.sendMessage requires a recipient. Use publishRepeating for broadcasts.',
235
- 'MISSING_RECIPIENT',
236
- { messageType: message.type },
237
- );
238
- }
239
- const actor = this.#actors.get(sender);
240
- if(!actor) {
241
- throw new HttpTransportError(
242
- `Unknown sender: ${sender}`,
243
- 'UNKNOWN_SENDER',
244
- { did: sender },
245
- );
246
- }
247
- const envelope = signEnvelope(message, { did: sender, keys: actor.keys }, { to: recipient });
248
- const dataJson = JSON.stringify(envelope);
249
-
250
- const inbox = this.#getOrCreateInbox(recipient);
251
- const stored = inbox.buffer.append(SSE_EVENT.MESSAGE, dataJson);
252
- for(const sub of inbox.subscribers) {
253
- this.#safeWrite(sub.stream, stored.event, stored.data, stored.id);
254
- }
255
- }
256
-
257
- publishRepeating(
258
- message: BaseMessage,
259
- sender: string,
260
- _intervalMs: number,
261
- _recipient?: string,
262
- ): () => void {
263
- const actor = this.#actors.get(sender);
264
- if(!actor) {
265
- throw new HttpTransportError(`Unknown sender: ${sender}`, 'UNKNOWN_SENDER', { did: sender });
266
- }
267
- const envelope = signEnvelope(message, { did: sender, keys: actor.keys });
268
- const dataJson = JSON.stringify(envelope);
269
- const id = String(++this.#advertSeq);
270
- const expiresAtMs = this.#now() + this.#advertTtlMs;
271
-
272
- this.#currentAdvert = { dataJson, id, expiresAtMs };
273
- for(const sub of this.#broadcastSubscribers) {
274
- this.#safeWrite(sub.stream, SSE_EVENT.ADVERT, dataJson, id);
275
- }
276
-
277
- return (): void => {
278
- if(this.#currentAdvert?.id === id) this.#currentAdvert = undefined;
279
- };
280
- }
281
-
282
- // ----------------------------------------------------------------
283
- // Sans-I/O HTTP surface
284
- // ----------------------------------------------------------------
285
-
286
- /**
287
- * Handle a POST / GET request (non-SSE). The caller dispatches SSE paths to
288
- * {@link handleSse} instead. Returns a fully formed response; the caller's
289
- * adapter turns it into an HTTP write.
290
- */
291
- async handleRequest(req: HttpRequestLike): Promise<HttpResponseLike> {
292
- const method = req.method.toUpperCase();
293
- if(method === 'OPTIONS') return this.#respond(204, '', req);
294
-
295
- const path = extractPath(req.url);
296
-
297
- if(method === 'GET' && path === HTTP_ROUTE.WELL_KNOWN) {
298
- return this.#respondJson(200, this.#wellKnownMetadata(), req);
299
- }
300
- if(method === 'POST' && path === HTTP_ROUTE.MESSAGES) {
301
- return await this.#handleMessagesPost(req);
302
- }
303
- if(method === 'POST' && path === HTTP_ROUTE.ADVERTS) {
304
- return await this.#handleAdvertsPost(req);
305
- }
306
- return this.#respondJson(404, { error: 'not_found' }, req);
307
- }
308
-
309
- /**
310
- * Open an SSE stream for a GET request. The caller is responsible for
311
- * flushing writes and propagating the `onClose` callback when the HTTP
312
- * connection ends.
313
- */
314
- handleSse(req: HttpRequestLike, stream: SseStream): void {
315
- if(req.method.toUpperCase() !== 'GET') {
316
- stream.close();
317
- return;
318
- }
319
- const path = extractPath(req.url);
320
-
321
- if(path === HTTP_ROUTE.ADVERTS) {
322
- this.#openBroadcastSubscription(stream);
323
- return;
324
- }
325
- const inboxMatch = matchInboxPath(path);
326
- if(inboxMatch) {
327
- this.#openInboxSubscription(req, stream, inboxMatch.did, path);
328
- return;
329
- }
330
- stream.close();
331
- }
332
-
333
- // ----------------------------------------------------------------
334
- // Request handlers
335
- // ----------------------------------------------------------------
336
-
337
- async #handleMessagesPost(req: HttpRequestLike): Promise<HttpResponseLike> {
338
- const envelope = parseJsonBody<SignedEnvelope>(req.body);
339
- if(!envelope) return this.#respondJson(400, { error: 'invalid_json' }, req);
340
-
341
- const senderPk = this.#resolveSenderPk(envelope.from);
342
- if(!senderPk) {
343
- return this.#respondJson(401, { error: 'unknown_sender' }, req);
344
- }
345
-
346
- try {
347
- verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
348
- } catch(err) {
349
- this.#logger.debug('POST /v1/messages: envelope verification failed:', err);
350
- return this.#respondJson(401, { error: 'invalid_envelope' }, req);
351
- }
352
-
353
- if(!this.#nonceCache.store(envelope.from, envelope.nonce, envelope.timestamp)) {
354
- return this.#respondJson(409, { error: 'replay' }, req);
355
- }
356
-
357
- if(!this.#rateLimiter.consume(envelope.from, this.#now())) {
358
- return this.#respondJson(429, { error: 'rate_limited' }, req);
359
- }
360
-
361
- if(!envelope.to) {
362
- return this.#respondJson(400, { error: 'missing_recipient' }, req);
363
- }
364
- const actor = this.#actors.get(envelope.to);
365
- if(!actor) {
366
- return this.#respondJson(404, { error: 'unknown_recipient' }, req);
367
- }
368
-
369
- const revived = reviveFromWire(envelope.message) as Record<string, unknown>;
370
- const flat = flattenMessage(revived);
371
- const messageType = typeof flat.type === 'string' ? flat.type : undefined;
372
- if(!messageType) return this.#respondJson(400, { error: 'missing_message_type' }, req);
373
-
374
- const handler = actor.handlers.get(messageType);
375
- if(handler) {
376
- try { await handler(flat); }
377
- catch(err) {
378
- this.#logger.debug(`Handler threw for ${messageType}:`, err);
379
- }
380
- }
381
- return this.#respondJson(202, { ok: true }, req);
382
- }
383
-
384
- async #handleAdvertsPost(req: HttpRequestLike): Promise<HttpResponseLike> {
385
- const envelope = parseJsonBody<SignedEnvelope>(req.body);
386
- if(!envelope) return this.#respondJson(400, { error: 'invalid_json' }, req);
387
-
388
- const senderPk = this.#resolveSenderPk(envelope.from);
389
- if(!senderPk) return this.#respondJson(401, { error: 'unknown_sender' }, req);
390
-
391
- try {
392
- verifyEnvelope(envelope, senderPk, { clockSkewSec: this.#clockSkewSec });
393
- } catch {
394
- return this.#respondJson(401, { error: 'invalid_envelope' }, req);
395
- }
396
-
397
- if(!this.#nonceCache.store(envelope.from, envelope.nonce, envelope.timestamp)) {
398
- return this.#respondJson(409, { error: 'replay' }, req);
399
- }
400
- if(!this.#rateLimiter.consume(envelope.from, this.#now())) {
401
- return this.#respondJson(429, { error: 'rate_limited' }, req);
402
- }
403
-
404
- // Only registered actors can publish adverts on this server.
405
- if(!this.#actors.has(envelope.from)) {
406
- return this.#respondJson(403, { error: 'not_an_actor' }, req);
407
- }
408
-
409
- const id = String(++this.#advertSeq);
410
- this.#currentAdvert = {
411
- dataJson : JSON.stringify(envelope),
412
- id,
413
- expiresAtMs : this.#now() + this.#advertTtlMs,
414
- };
415
- for(const sub of this.#broadcastSubscribers) {
416
- this.#safeWrite(sub.stream, SSE_EVENT.ADVERT, this.#currentAdvert.dataJson, id);
417
- }
418
- return this.#respondJson(202, { ok: true }, req);
419
- }
420
-
421
- #openBroadcastSubscription(stream: SseStream): void {
422
- const sub: BroadcastSubscriber = { stream };
423
- this.#broadcastSubscribers.add(sub);
424
- stream.onClose(() => {
425
- this.#closeBroadcastSubscriber(sub);
426
- this.#broadcastSubscribers.delete(sub);
427
- });
428
-
429
- // Replay current advert if still within TTL.
430
- if(this.#currentAdvert && this.#currentAdvert.expiresAtMs > this.#now()) {
431
- this.#safeWrite(stream, SSE_EVENT.ADVERT, this.#currentAdvert.dataJson, this.#currentAdvert.id);
432
- }
433
-
434
- if(this.#heartbeatMs > 0) {
435
- sub.heartbeatTimer = setInterval(() => {
436
- try { stream.writeComment('hb'); } catch { /* caller-owned failure */ }
437
- }, this.#heartbeatMs);
438
- }
439
- }
440
-
441
- #openInboxSubscription(req: HttpRequestLike, stream: SseStream, did: string, path: string): void {
442
- const auth = req.headers.authorization;
443
- if(!auth) {
444
- this.#logger.debug(`Inbox subscribe: missing authorization header for ${did}`);
445
- stream.close();
446
- return;
447
- }
448
- const senderPk = this.#resolveSenderPk(did);
449
- if(!senderPk) {
450
- stream.close();
451
- return;
452
- }
453
- let parsedTs = 0;
454
- let parsedNonce = '';
455
- try {
456
- const parsed = verifyRequestAuth(auth, path, senderPk, {
457
- clockSkewSec : this.#clockSkewSec,
458
- now : () => this.#now(),
459
- });
460
- if(parsed.did !== did) { stream.close(); return; }
461
- parsedTs = parsed.ts;
462
- parsedNonce = parsed.nonce;
463
- } catch(err) {
464
- this.#logger.debug(`Inbox subscribe: auth verification failed for ${did}:`, err);
465
- stream.close();
466
- return;
467
- }
468
- if(!this.#nonceCache.store(did, parsedNonce, parsedTs)) {
469
- stream.close();
470
- return;
471
- }
472
- if(!this.#rateLimiter.consume(did, this.#now())) {
473
- stream.close();
474
- return;
475
- }
476
-
477
- const inbox = this.#getOrCreateInbox(did);
478
- const sub: InboxSubscriber = { stream };
479
- inbox.subscribers.add(sub);
480
- stream.onClose(() => {
481
- this.#closeInboxSubscriber(sub);
482
- inbox.subscribers.delete(sub);
483
- });
484
-
485
- // Replay buffered events since the client's Last-Event-ID, if any.
486
- const lastEventId = req.headers['last-event-id'];
487
- for(const stored of inbox.buffer.since(lastEventId)) {
488
- this.#safeWrite(stream, stored.event, stored.data, stored.id);
489
- }
490
-
491
- if(this.#heartbeatMs > 0) {
492
- sub.heartbeatTimer = setInterval(() => {
493
- try { stream.writeComment('hb'); } catch { /* caller-owned failure */ }
494
- }, this.#heartbeatMs);
495
- }
496
- }
497
-
498
- // ----------------------------------------------------------------
499
- // Internal helpers
500
- // ----------------------------------------------------------------
501
-
502
- #getOrCreateInbox(did: string): InboxState {
503
- let inbox = this.#inboxes.get(did);
504
- if(!inbox) {
505
- inbox = { buffer: new InboxBuffer(this.#inboxBufferSize), subscribers: new Set() };
506
- this.#inboxes.set(did, inbox);
507
- }
508
- return inbox;
509
- }
510
-
511
- #resolveSenderPk(did: string): CompressedSecp256k1PublicKey | undefined {
512
- const peerBytes = this.#peers.get(did);
513
- if(peerBytes) {
514
- try { return new CompressedSecp256k1PublicKey(peerBytes); }
515
- catch { /* fall through */ }
516
- }
517
- try {
518
- const components = Identifier.decode(did);
519
- if(components.idType === 'KEY') {
520
- return new CompressedSecp256k1PublicKey(components.genesisBytes);
521
- }
522
- } catch { /* not decodable */ }
523
- return undefined;
524
- }
525
-
526
- #safeWrite(stream: SseStream, event: string, data: string, id?: string): void {
527
- try { stream.writeEvent(event, data, id); }
528
- catch(err) { this.#logger.debug('SSE writeEvent failed:', err); }
529
- }
530
-
531
- #closeBroadcastSubscriber(sub: BroadcastSubscriber): void {
532
- if(sub.heartbeatTimer) clearInterval(sub.heartbeatTimer);
533
- try { sub.stream.close(); } catch { /* already closed */ }
534
- }
535
-
536
- #closeInboxSubscriber(sub: InboxSubscriber): void {
537
- if(sub.heartbeatTimer) clearInterval(sub.heartbeatTimer);
538
- try { sub.stream.close(); } catch { /* already closed */ }
539
- }
540
-
541
- #respondJson(status: number, body: unknown, req: HttpRequestLike): HttpResponseLike {
542
- return {
543
- status,
544
- headers : { 'content-type': 'application/json', ...this.#corsHeaders(req) },
545
- body : JSON.stringify(body),
546
- };
547
- }
548
-
549
- #respond(status: number, body: string, req: HttpRequestLike): HttpResponseLike {
550
- return { status, headers: this.#corsHeaders(req), body };
551
- }
552
-
553
- #corsHeaders(req: HttpRequestLike): Record<string, string> {
554
- const origin = req.headers.origin;
555
- if(!origin) return {};
556
- const common: Record<string, string> = {
557
- 'access-control-allow-methods' : 'GET, POST, OPTIONS',
558
- 'access-control-allow-headers' : 'authorization, content-type, last-event-id',
559
- 'access-control-max-age' : '86400',
560
- };
561
- switch(this.#cors.mode) {
562
- case 'permissive':
563
- return { 'access-control-allow-origin': '*', ...common };
564
- case 'allowlist':
565
- if(this.#cors.origins.includes(origin)) {
566
- return { 'access-control-allow-origin': origin, vary: 'origin', ...common };
567
- }
568
- return {};
569
- case 'same-origin':
570
- return {};
571
- }
572
- }
573
-
574
- #wellKnownMetadata(): Record<string, unknown> {
575
- return {
576
- envelopeVersion : HTTP_ENVELOPE_VERSION,
577
- heartbeatIntervalMs : this.#heartbeatMs,
578
- inboxBufferSize : this.#inboxBufferSize,
579
- advertTtlMs : this.#advertTtlMs,
580
- };
581
- }
582
- }
583
-
584
- // ----------------------------------------------------------------
585
- // Module-local pure helpers
586
- // ----------------------------------------------------------------
587
-
588
- function extractPath(reqUrl: string): string {
589
- if(reqUrl.startsWith('http://') || reqUrl.startsWith('https://')) {
590
- return new URL(reqUrl).pathname;
591
- }
592
- const q = reqUrl.indexOf('?');
593
- return q === -1 ? reqUrl : reqUrl.slice(0, q);
594
- }
595
-
596
- function matchInboxPath(path: string): { did: string } | undefined {
597
- if(!path.startsWith(INBOX_PATH_PREFIX) || !path.endsWith(INBOX_PATH_SUFFIX)) return undefined;
598
- const encodedDid = path.slice(INBOX_PATH_PREFIX.length, path.length - INBOX_PATH_SUFFIX.length);
599
- if(!encodedDid) return undefined;
600
- try { return { did: decodeURIComponent(encodedDid) }; }
601
- catch { return undefined; }
602
- }
603
-
604
- function parseJsonBody<T>(body: string | undefined): T | undefined {
605
- if(body === undefined || body === '') return undefined;
606
- try { return JSON.parse(body) as T; }
607
- catch { return undefined; }
608
- }
609
-
610
- function flattenMessage(msg: Record<string, unknown>): Record<string, unknown> {
611
- if(msg.body && typeof msg.body === 'object') {
612
- return { ...msg, ...(msg.body as Record<string, unknown>) };
613
- }
614
- return msg;
615
- }