@calimero-network/mero-js 26.10.0 → 27.0.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 (220) hide show
  1. package/README.md +73 -288
  2. package/dist/cloud-v2/attestation.d.ts +91 -0
  3. package/dist/cloud-v2/attestation.js +135 -0
  4. package/dist/cloud-v2/capabilities.d.ts +30 -0
  5. package/dist/cloud-v2/capabilities.js +31 -0
  6. package/dist/cloud-v2/client.d.ts +426 -0
  7. package/dist/cloud-v2/client.js +646 -0
  8. package/dist/cloud-v2/errors.d.ts +98 -0
  9. package/dist/cloud-v2/errors.js +165 -0
  10. package/dist/cloud-v2/index.d.ts +10 -0
  11. package/dist/cloud-v2/index.js +5 -0
  12. package/dist/cloud-v2/passkey.d.ts +27 -0
  13. package/dist/cloud-v2/passkey.js +73 -0
  14. package/dist/crypto/internal.d.ts +3 -37
  15. package/dist/crypto/internal.js +3 -72
  16. package/dist/index.browser.mjs +4 -2
  17. package/dist/index.browser.mjs.map +4 -4
  18. package/dist/index.cjs +1750 -12767
  19. package/dist/index.cjs.map +4 -4
  20. package/dist/index.d.ts +3 -56
  21. package/dist/index.js +14 -75
  22. package/dist/index.mjs +1750 -12767
  23. package/dist/index.mjs.map +4 -4
  24. package/dist/sealed/index.d.ts +4 -4
  25. package/dist/sealed/index.js +2 -2
  26. package/dist/sealed/release.d.ts +21 -76
  27. package/dist/sealed/release.js +24 -108
  28. package/dist/sealed/sealed.d.ts +8 -56
  29. package/dist/sealed/sealed.js +11 -89
  30. package/dist/sealed/verify.d.ts +7 -13
  31. package/dist/sealed/verify.js +3 -7
  32. package/package.json +8 -13
  33. package/dist/account/account-admin.d.ts +0 -80
  34. package/dist/account/account-admin.js +0 -346
  35. package/dist/account/account.d.ts +0 -196
  36. package/dist/account/account.js +0 -269
  37. package/dist/account/application-id.d.ts +0 -50
  38. package/dist/account/application-id.js +0 -83
  39. package/dist/account/bootstrap-from-invitation.d.ts +0 -110
  40. package/dist/account/bootstrap-from-invitation.js +0 -80
  41. package/dist/account/create-context.d.ts +0 -103
  42. package/dist/account/create-context.js +0 -267
  43. package/dist/account/govern.d.ts +0 -24
  44. package/dist/account/govern.js +0 -50
  45. package/dist/account/index.d.ts +0 -26
  46. package/dist/account/index.js +0 -15
  47. package/dist/account/join-as-account.d.ts +0 -23
  48. package/dist/account/join-as-account.js +0 -34
  49. package/dist/account/join-with-node.d.ts +0 -115
  50. package/dist/account/join-with-node.js +0 -161
  51. package/dist/account/node-admin.d.ts +0 -20
  52. package/dist/account/node-admin.js +0 -33
  53. package/dist/account/relay-from-invitation.d.ts +0 -137
  54. package/dist/account/relay-from-invitation.js +0 -180
  55. package/dist/account/relay-login-error.d.ts +0 -32
  56. package/dist/account/relay-login-error.js +0 -34
  57. package/dist/account/session.d.ts +0 -266
  58. package/dist/account/session.js +0 -915
  59. package/dist/account/wordlist.d.ts +0 -16
  60. package/dist/account/wordlist.js +0 -273
  61. package/dist/admin-api/admin-client.d.ts +0 -686
  62. package/dist/admin-api/admin-client.js +0 -1225
  63. package/dist/admin-api/admin-factory.d.ts +0 -14
  64. package/dist/admin-api/admin-factory.js +0 -22
  65. package/dist/admin-api/admin-types.d.ts +0 -2041
  66. package/dist/admin-api/admin-types.js +0 -12
  67. package/dist/admin-api/index.d.ts +0 -4
  68. package/dist/admin-api/index.js +0 -5
  69. package/dist/admin-api/redeem-invitation.d.ts +0 -101
  70. package/dist/admin-api/redeem-invitation.js +0 -200
  71. package/dist/auth/index.d.ts +0 -34
  72. package/dist/auth/index.js +0 -67
  73. package/dist/auth-api/auth-client.d.ts +0 -31
  74. package/dist/auth-api/auth-client.js +0 -117
  75. package/dist/auth-api/auth-factory.d.ts +0 -7
  76. package/dist/auth-api/auth-factory.js +0 -41
  77. package/dist/auth-api/auth-types.d.ts +0 -130
  78. package/dist/auth-api/auth-types.js +0 -2
  79. package/dist/auth-api/index.d.ts +0 -3
  80. package/dist/auth-api/index.js +0 -4
  81. package/dist/capabilities.d.ts +0 -84
  82. package/dist/capabilities.js +0 -117
  83. package/dist/cloud/account-ownership.d.ts +0 -63
  84. package/dist/cloud/account-ownership.js +0 -87
  85. package/dist/cloud/cloud-client.d.ts +0 -949
  86. package/dist/cloud/cloud-client.js +0 -1068
  87. package/dist/cloud/connect.d.ts +0 -213
  88. package/dist/cloud/connect.js +0 -260
  89. package/dist/cloud/enrol-redirect.d.ts +0 -181
  90. package/dist/cloud/enrol-redirect.js +0 -244
  91. package/dist/cloud/index.d.ts +0 -12
  92. package/dist/cloud/index.js +0 -10
  93. package/dist/cloud/link-redirect.d.ts +0 -95
  94. package/dist/cloud/link-redirect.js +0 -121
  95. package/dist/cloud/link-state.d.ts +0 -36
  96. package/dist/cloud/link-state.js +0 -104
  97. package/dist/cloud/routing-proof.d.ts +0 -113
  98. package/dist/cloud/routing-proof.js +0 -96
  99. package/dist/device-cert/device-cert.d.ts +0 -189
  100. package/dist/device-cert/device-cert.js +0 -239
  101. package/dist/device-cert/device-scope.d.ts +0 -58
  102. package/dist/device-cert/device-scope.js +0 -76
  103. package/dist/device-cert/index.d.ts +0 -4
  104. package/dist/device-cert/index.js +0 -3
  105. package/dist/ephemeral/codec.d.ts +0 -4
  106. package/dist/ephemeral/codec.js +0 -12
  107. package/dist/ephemeral/index.d.ts +0 -57
  108. package/dist/ephemeral/index.js +0 -101
  109. package/dist/ephemeral/types.d.ts +0 -27
  110. package/dist/ephemeral/types.js +0 -1
  111. package/dist/errors/classify.d.ts +0 -65
  112. package/dist/errors/classify.js +0 -117
  113. package/dist/errors/index.d.ts +0 -2
  114. package/dist/errors/index.js +0 -1
  115. package/dist/events/group.d.ts +0 -95
  116. package/dist/events/group.js +0 -10
  117. package/dist/events/index.d.ts +0 -5
  118. package/dist/events/index.js +0 -2
  119. package/dist/events/sse.d.ts +0 -127
  120. package/dist/events/sse.js +0 -469
  121. package/dist/events/ws.d.ts +0 -57
  122. package/dist/events/ws.js +0 -212
  123. package/dist/http-client/api-response.d.ts +0 -15
  124. package/dist/http-client/api-response.js +0 -1
  125. package/dist/http-client/http-factory.d.ts +0 -71
  126. package/dist/http-client/http-factory.js +0 -68
  127. package/dist/http-client/http-types.d.ts +0 -87
  128. package/dist/http-client/http-types.js +0 -1
  129. package/dist/http-client/index.d.ts +0 -7
  130. package/dist/http-client/index.js +0 -11
  131. package/dist/http-client/path-segment.d.ts +0 -1
  132. package/dist/http-client/path-segment.js +0 -8
  133. package/dist/http-client/retry.d.ts +0 -5
  134. package/dist/http-client/retry.js +0 -82
  135. package/dist/http-client/signal-utils.d.ts +0 -2
  136. package/dist/http-client/signal-utils.js +0 -41
  137. package/dist/http-client/web-client.d.ts +0 -80
  138. package/dist/http-client/web-client.js +0 -557
  139. package/dist/invitation/index.d.ts +0 -2
  140. package/dist/invitation/index.js +0 -2
  141. package/dist/invitation/invitation.d.ts +0 -106
  142. package/dist/invitation/invitation.js +0 -104
  143. package/dist/login/index.d.ts +0 -4
  144. package/dist/login/index.js +0 -3
  145. package/dist/login/login.d.ts +0 -111
  146. package/dist/login/login.js +0 -110
  147. package/dist/login/session.d.ts +0 -124
  148. package/dist/login/session.js +0 -141
  149. package/dist/mero-js.d.ts +0 -231
  150. package/dist/mero-js.js +0 -469
  151. package/dist/namespace-op/index.d.ts +0 -2
  152. package/dist/namespace-op/index.js +0 -3
  153. package/dist/namespace-op/namespace-op.d.ts +0 -150
  154. package/dist/namespace-op/namespace-op.js +0 -191
  155. package/dist/nodeDiscovery.d.ts +0 -47
  156. package/dist/nodeDiscovery.js +0 -101
  157. package/dist/owner-op/index.d.ts +0 -2
  158. package/dist/owner-op/index.js +0 -3
  159. package/dist/owner-op/owner-op.d.ts +0 -119
  160. package/dist/owner-op/owner-op.js +0 -214
  161. package/dist/presence/relay-presence.d.ts +0 -56
  162. package/dist/presence/relay-presence.js +0 -107
  163. package/dist/presence/statement.d.ts +0 -12
  164. package/dist/presence/statement.js +0 -17
  165. package/dist/relay/index.d.ts +0 -4
  166. package/dist/relay/index.js +0 -2
  167. package/dist/relay/nonce-source.d.ts +0 -72
  168. package/dist/relay/nonce-source.js +0 -111
  169. package/dist/relay/relay-client.d.ts +0 -472
  170. package/dist/relay/relay-client.js +0 -518
  171. package/dist/relay-attestation/attest-node-key.d.ts +0 -66
  172. package/dist/relay-attestation/attest-node-key.js +0 -110
  173. package/dist/relay-attestation/index.d.ts +0 -1
  174. package/dist/relay-attestation/index.js +0 -1
  175. package/dist/request/caller-proof.d.ts +0 -43
  176. package/dist/request/caller-proof.js +0 -36
  177. package/dist/request/index.d.ts +0 -3
  178. package/dist/request/index.js +0 -3
  179. package/dist/request/proof-signer.d.ts +0 -49
  180. package/dist/request/proof-signer.js +0 -59
  181. package/dist/request/request-sig.d.ts +0 -99
  182. package/dist/request/request-sig.js +0 -95
  183. package/dist/request-proof/authorizer.d.ts +0 -41
  184. package/dist/request-proof/authorizer.js +0 -60
  185. package/dist/request-proof/index.d.ts +0 -4
  186. package/dist/request-proof/index.js +0 -2
  187. package/dist/request-proof/request-proof.d.ts +0 -53
  188. package/dist/request-proof/request-proof.js +0 -95
  189. package/dist/rpc/index.d.ts +0 -64
  190. package/dist/rpc/index.js +0 -78
  191. package/dist/signer/index.d.ts +0 -2
  192. package/dist/signer/index.js +0 -3
  193. package/dist/signer/signer.d.ts +0 -92
  194. package/dist/signer/signer.js +0 -147
  195. package/dist/token-store/index.d.ts +0 -27
  196. package/dist/token-store/index.js +0 -62
  197. package/dist/transport/index.d.ts +0 -6
  198. package/dist/transport/index.js +0 -3
  199. package/dist/transport/mero-client.d.ts +0 -294
  200. package/dist/transport/mero-client.js +0 -386
  201. package/dist/transport/relay-observer.d.ts +0 -152
  202. package/dist/transport/relay-observer.js +0 -173
  203. package/dist/transport/relay-transport.d.ts +0 -73
  204. package/dist/transport/relay-transport.js +0 -94
  205. package/dist/transport/types.d.ts +0 -100
  206. package/dist/transport/types.js +0 -32
  207. package/dist/types/api-response.d.ts +0 -15
  208. package/dist/types/api-response.js +0 -1
  209. package/dist/types/index.d.ts +0 -1
  210. package/dist/types/index.js +0 -1
  211. package/dist/warrant/creation-warrant.d.ts +0 -163
  212. package/dist/warrant/creation-warrant.js +0 -194
  213. package/dist/warrant/governance-op.d.ts +0 -202
  214. package/dist/warrant/governance-op.js +0 -355
  215. package/dist/warrant/governance-warrant.d.ts +0 -124
  216. package/dist/warrant/governance-warrant.js +0 -164
  217. package/dist/warrant/index.d.ts +0 -8
  218. package/dist/warrant/index.js +0 -5
  219. package/dist/warrant/warrant.d.ts +0 -188
  220. package/dist/warrant/warrant.js +0 -202
@@ -1,294 +0,0 @@
1
- /**
2
- * One client, two transports — chosen once, at construction.
3
- *
4
- * # The problem this exists for
5
- *
6
- * An app that wants to move from "the user has a node" to "the user has a key
7
- * and a relay" had to rewrite itself, because the two write paths are different
8
- * objects with different method shapes. That makes the transport a property of
9
- * the *application source*, when it is really a property of the connection: the
10
- * same screen, the same hook, the same `execute` call wants to work either way.
11
- *
12
- * `createMeroClient` takes the decision at construction and nowhere else. After
13
- * that, `client.rpc.execute({ contextId, method, argsJson })` is the same call
14
- * against a node and against a relay — same arguments, same return type. The
15
- * node shape is the one both speak, because shipped apps already write it and
16
- * the alternative would be a breaking change wearing an abstraction's clothes.
17
- *
18
- * # Defaulting to the node is a compatibility guarantee
19
- *
20
- * `createMeroClient({ baseUrl })` is `createMeroJs({ baseUrl })` with a facade
21
- * in front. No `transport` field means `'node'`, so nothing an existing app
22
- * does can accidentally select the relay.
23
- *
24
- * # Events work on both transports, given one input
25
- *
26
- * A relay is an ordinary node, so it serves `/sse` and `/ws` to a credential it
27
- * issued, and a keyholder can obtain one by logging in with its account proof.
28
- * `client.events` and `client.ws` are therefore the same call on both
29
- * transports — provided the caller supplies the relay node's device signing
30
- * key, which must be learned out of band and is the one thing that cannot be
31
- * inferred. See `./relay-observer.ts` for why, and mdma #312 for the hosted
32
- * case. Without it, {@link MeroClient.canSubscribe} is `false` and the
33
- * accessors throw naming exactly that.
34
- *
35
- * # What is deliberately NOT unified
36
- *
37
- * Reads and admin. A relay serves exactly two routes to a caller with no
38
- * credential — `GET` and `POST /admin-api/contexts/:id/intents` — and while a
39
- * session obtained by logging in carries `context:query`, `sdk.admin` wants
40
- * `admin`, which the `account_proof` provider does not grant and a keyholder
41
- * should never ask for.
42
- *
43
- * So those accessors throw on a relay client, with a message naming what is
44
- * missing, rather than returning something that quietly connects somewhere
45
- * else. Falling back to "some node" would be the worst of the options: the app
46
- * would observe a node it never chose, under a credential it never granted, and
47
- * find out only when the two disagreed. `canSubscribe` and `transport` are
48
- * there so an app can branch *before* touching any of it.
49
- */
50
- import { MeroJs } from '../mero-js.js';
51
- import type { MeroJsConfig } from '../mero-js.js';
52
- import { RelayClient } from '../relay/relay-client.js';
53
- import type { RelayClientConfig } from '../relay/relay-client.js';
54
- import type { RelayObserveConfig } from './relay-observer.js';
55
- import type { ExecuteTransport, TransportKind } from './types.js';
56
- import type { AdminApiClient } from '../admin-api/index.js';
57
- import type { ProofCredential } from '../request-proof/index.js';
58
- import { SseClient } from '../events/sse.js';
59
- import type { AuthApiClient } from '../auth-api/index.js';
60
- import type { WsClient } from '../events/ws.js';
61
- import type { EphemeralClient } from '../ephemeral/index.js';
62
- import { RelayPresenceClient } from '../presence/relay-presence.js';
63
- import type { CloudClient } from '../cloud/cloud-client.js';
64
- /** The node transport: everything `createMeroJs` takes, plus an explicit opt-in. */
65
- export interface NodeTransportConfig extends MeroJsConfig {
66
- /**
67
- * Optional, and the default. Spelled out so a config object can be built
68
- * generically without the node case looking like an omission.
69
- */
70
- transport?: 'node';
71
- }
72
- /** The relay transport: delegated execution, no node and no node credential. */
73
- export interface RelayTransportConfig {
74
- transport: 'relay';
75
- /**
76
- * The relay to write through — a configured {@link RelayClient}, or the
77
- * config to build one from.
78
- *
79
- * An already-built client is accepted because the useful ones are not built
80
- * by hand: `connectCloud` discovers the relay URL and executor account from a
81
- * cloud sign-in and hands back a ready client. Requiring the config here
82
- * would have that caller take the client apart to put it back together.
83
- */
84
- relay: RelayClient | RelayClientConfig;
85
- /**
86
- * Fetch for everything this client sends the relay: intents (unless `relay`
87
- * names its own), admin reads, the observing login and events. Typically a
88
- * sealed fetch (`createAttestedSealedFetch`), so none of it can be read where
89
- * TLS ends. Defaults to global `fetch`.
90
- */
91
- fetch?: typeof fetch;
92
- /**
93
- * A session on the relay, if this client has one.
94
- *
95
- * The delegated path has **two** credentials, not one. A warrant authorises a
96
- * single intent and is what `execute` sends; a session — minted by
97
- * `account_proof` from the same device key, see `login()` — is what the
98
- * caller-scoped admin READS are authenticated by (`context:list-own`,
99
- * `namespace:list-own`). They are independent: a client with only warrants
100
- * writes but cannot list, which is what `admin` throwing used to describe.
101
- *
102
- * Supply a token, or a function returning the current one so a refreshed
103
- * session is picked up without rebuilding the client.
104
- */
105
- session?: string | (() => Promise<string | undefined>);
106
- /**
107
- * How this client observes the relay node, if it can.
108
- *
109
- * Omit it — or pass it with no `nodeKey` — and the client writes but does not
110
- * observe: `canSubscribe` is `false` and `events`/`ws` throw naming the
111
- * missing key. Supply `{ nodeKey }` and `client.events` is the very same
112
- * `SseClient` a node client hands out.
113
- *
114
- * The shape takes a nullable key on purpose, so the hosted path is one code
115
- * path rather than two: `connectCloud` always passes this object, with
116
- * whatever key the cloud reported, and starts working the day mdma #312
117
- * publishes one. Nothing here ever fills the key in.
118
- */
119
- observe?: RelayObserveConfig;
120
- /**
121
- * The device cert this client authenticates its own requests with.
122
- *
123
- * This is the shortest path to everything but writes, and it needs no session
124
- * and no node key. The device key signs each request — `X-Calimero-Proof` —
125
- * and the node verifies the signature against the certificate the account
126
- * signed, with no store read and no prior relationship. Measured against a
127
- * node built from core `aecba573b`: the caller-scoped reads answer `200`,
128
- * `/sse` answers `200`, and a `/ws` upgrade completes `101`.
129
- *
130
- * Supplying this makes {@link MeroClient.admin} and {@link MeroClient.events}
131
- * available and {@link MeroClient.canSubscribe} true, with no `session` and no
132
- * `observe.nodeKey`. Those two remain for the case they were built for: a
133
- * device key behind a boundary worth crossing once, where a per-session
134
- * signature beats a per-request one.
135
- */
136
- proof?: ProofCredential;
137
- }
138
- export type MeroClientConfig = NodeTransportConfig | RelayTransportConfig;
139
- /**
140
- * A connection whose transport was decided at construction.
141
- *
142
- * Everything reachable on a node client is reachable in exactly the shape
143
- * `MeroJs` exposes it, by delegation rather than re-implementation — this adds
144
- * no behaviour to the node path and cannot drift from it.
145
- */
146
- export declare class MeroClient {
147
- /** Which path writes take. Set once, never changes. */
148
- readonly transport: TransportKind;
149
- private readonly nodeClient;
150
- private readonly rpcTransport;
151
- /** The relay's event session, or `null` on a node client / with no node key. */
152
- private readonly relayObserver;
153
- /** Admin reads against the relay, when a session or a proof was supplied. */
154
- private readonly relayAdmin;
155
- /**
156
- * Events off the relay authenticated by a request proof, built lazily.
157
- *
158
- * Separate from {@link relayObserver} rather than a mode inside it: the
159
- * observer's whole subject is a session addressed to a node key, and a proof
160
- * has neither. Holding the authorizer instead of the client keeps construction
161
- * free of the first signature.
162
- */
163
- private readonly proofAuthorizer;
164
- private readonly relayUrl;
165
- private proofEvents;
166
- /** The relay transport's `fetch`, if one was given. */
167
- private readonly relayFetch;
168
- private relayPresence;
169
- constructor(config: MeroClientConfig);
170
- /**
171
- * The write surface. Identical on both transports — this is the whole point.
172
- *
173
- * On a node this IS the `RpcClient` a `MeroJs` would have handed out, not a
174
- * wrapper around it, so `client.rpc` and `client.node.rpc` are the same
175
- * object.
176
- */
177
- get rpc(): ExecuteTransport;
178
- /**
179
- * Whether `events` / `ws` will work on this client.
180
- *
181
- * Always `true` on the node transport. On the relay transport it says whether
182
- * a node key was supplied — the relay can be observed, but only by a caller
183
- * who can name the key the login statement is signed against.
184
- */
185
- get canSubscribe(): boolean;
186
- /** The underlying `MeroJs`. Node transport only. */
187
- get node(): MeroJs;
188
- /** The underlying `RelayClient`, for `describe()`. Relay transport only. */
189
- get relay(): RelayClient;
190
- /**
191
- * Admin API. Node transport only.
192
- *
193
- * Two different things live behind this accessor today, and only one of them
194
- * is genuinely unavailable through a relay.
195
- *
196
- * The **mutations** — `createNamespace`, `setMemberCapabilities`,
197
- * `upgradeGroup` and the rest — are operator actions on somebody else's
198
- * machine. A keyholder does not get those, and should not; admin is for
199
- * admins.
200
- *
201
- * The **reads** are a different matter. `getContext`, `getContexts`,
202
- * `getApplication`, `listNamespaces`, `getBlob` are an app asking what it is
203
- * looking at, not an operator administering anything. They sit under
204
- * `/admin-api/` for historical reasons, and a delegated session is refused
205
- * them (measured: `403`, while the same session reads context state through
206
- * `POST /contexts/{ctx}/query` and writes through `/intents` quite happily).
207
- *
208
- * So this is the one place where "the same code runs on both transports"
209
- * does not hold yet, and it is a missing surface rather than a missing
210
- * permission. A transport-independent read API is designed but not built; the
211
- * throw below is deliberate until it exists, because the alternative — quietly
212
- * reaching for a node the caller did not choose — is worse than a clear stop.
213
- */
214
- /**
215
- * The admin surface.
216
- *
217
- * On a node client this is the node's own. On a relay client it exists only
218
- * when a `session` was supplied, and it is **the relay's admin API
219
- * authenticated as the account** — so the caller-scoped reads answer:
220
- * `getContexts` and `listNamespaces` return this account's own, because
221
- * `account_proof` grants `context:list-own` and `namespace:list-own`.
222
- *
223
- * Mutations are not filtered out here, deliberately. A delegated session does
224
- * not carry the grants they require, so the node refuses them with a `403`
225
- * that names the missing permission — which is a better answer than a
226
- * client-side guess, and keeps the policy in the one place that owns it.
227
- */
228
- get admin(): AdminApiClient;
229
- /** Auth API. Node transport only — a relay issues no node credential. */
230
- get auth(): AuthApiClient;
231
- /**
232
- * SSE events — on **both** transports.
233
- *
234
- * On a relay client this is an `SseClient` against the relay node, fed by a
235
- * session minted with the author's account proof. The session is minted
236
- * lazily, on the first token the stream asks for, so reading this getter
237
- * performs no network call.
238
- *
239
- * Throws only when the relay client was given no node key: the one input
240
- * that cannot be inferred. See {@link missingNodeKey}.
241
- */
242
- get events(): SseClient;
243
- /** WebSocket events, on both transports. @experimental — prefer `events`. */
244
- get ws(): WsClient;
245
- /**
246
- * Ephemeral presence. Node transport only.
247
- *
248
- * On a relay client this is an account's presence client
249
- * ({@link RelayPresenceClient}): `set` signs each update with the device key
250
- * and posts it to the relay's `presence-intents`, resending every 2.5 s, and
251
- * `subscribe` reads the relay's event stream. The relay checks and forwards
252
- * the update; other members verify the device's signature and certificate
253
- * themselves, so it can drop or delay an account's presence but not invent or
254
- * alter it. Only `subscribe` needs the relay's node key (for the event
255
- * stream), so publishing works on any relay client.
256
- */
257
- get ephemeral(): EphemeralClient | RelayPresenceClient;
258
- /**
259
- * Calimero Cloud.
260
- *
261
- * Available on both transports, because it is neither: the cloud session is
262
- * independent of the node credential and of the relay's signing key, and a
263
- * relay-transport app still needs it to list namespaces and manage billing.
264
- */
265
- get cloud(): CloudClient;
266
- /**
267
- * Release whatever the transport is holding.
268
- *
269
- * Safe on a relay client, where there is nothing open to close — a caller
270
- * that tears down on unmount should not have to know which transport it got.
271
- */
272
- close(): void;
273
- }
274
- /**
275
- * Build a client for the chosen transport. Node unless told otherwise.
276
- *
277
- * ```ts
278
- * // unchanged, and still the default
279
- * const client = createMeroClient({ baseUrl: 'http://localhost:2428' });
280
- *
281
- * // the same call sites, through a relay
282
- * const client = createMeroClient({
283
- * transport: 'relay',
284
- * relay: connection.relay,
285
- * // the relay node's signing key, learned out of band — without it the
286
- * // client writes but does not observe
287
- * observe: { nodeKey },
288
- * });
289
- *
290
- * await client.rpc.execute({ contextId, method: 'set', argsJson: { key, value } });
291
- * if (client.canSubscribe) client.events.on('event', onEvent);
292
- * ```
293
- */
294
- export declare function createMeroClient(config: MeroClientConfig): MeroClient;
@@ -1,386 +0,0 @@
1
- /**
2
- * One client, two transports — chosen once, at construction.
3
- *
4
- * # The problem this exists for
5
- *
6
- * An app that wants to move from "the user has a node" to "the user has a key
7
- * and a relay" had to rewrite itself, because the two write paths are different
8
- * objects with different method shapes. That makes the transport a property of
9
- * the *application source*, when it is really a property of the connection: the
10
- * same screen, the same hook, the same `execute` call wants to work either way.
11
- *
12
- * `createMeroClient` takes the decision at construction and nowhere else. After
13
- * that, `client.rpc.execute({ contextId, method, argsJson })` is the same call
14
- * against a node and against a relay — same arguments, same return type. The
15
- * node shape is the one both speak, because shipped apps already write it and
16
- * the alternative would be a breaking change wearing an abstraction's clothes.
17
- *
18
- * # Defaulting to the node is a compatibility guarantee
19
- *
20
- * `createMeroClient({ baseUrl })` is `createMeroJs({ baseUrl })` with a facade
21
- * in front. No `transport` field means `'node'`, so nothing an existing app
22
- * does can accidentally select the relay.
23
- *
24
- * # Events work on both transports, given one input
25
- *
26
- * A relay is an ordinary node, so it serves `/sse` and `/ws` to a credential it
27
- * issued, and a keyholder can obtain one by logging in with its account proof.
28
- * `client.events` and `client.ws` are therefore the same call on both
29
- * transports — provided the caller supplies the relay node's device signing
30
- * key, which must be learned out of band and is the one thing that cannot be
31
- * inferred. See `./relay-observer.ts` for why, and mdma #312 for the hosted
32
- * case. Without it, {@link MeroClient.canSubscribe} is `false` and the
33
- * accessors throw naming exactly that.
34
- *
35
- * # What is deliberately NOT unified
36
- *
37
- * Reads and admin. A relay serves exactly two routes to a caller with no
38
- * credential — `GET` and `POST /admin-api/contexts/:id/intents` — and while a
39
- * session obtained by logging in carries `context:query`, `sdk.admin` wants
40
- * `admin`, which the `account_proof` provider does not grant and a keyholder
41
- * should never ask for.
42
- *
43
- * So those accessors throw on a relay client, with a message naming what is
44
- * missing, rather than returning something that quietly connects somewhere
45
- * else. Falling back to "some node" would be the worst of the options: the app
46
- * would observe a node it never chose, under a credential it never granted, and
47
- * find out only when the two disagreed. `canSubscribe` and `transport` are
48
- * there so an app can branch *before* touching any of it.
49
- */
50
- import { MeroJs } from '../mero-js.js';
51
- import { RelayClient } from '../relay/relay-client.js';
52
- import { RelayTransport } from './relay-transport.js';
53
- import { RelayObserver, defaultAudience } from './relay-observer.js';
54
- import { createAdminApiClient, createAdminApiClientFromHttpClient } from '../admin-api/index.js';
55
- import { createHttpClient } from '../http-client/index.js';
56
- import { createProofAuthorizer } from '../request-proof/index.js';
57
- import { SseClient } from '../events/sse.js';
58
- import { RelayPresenceClient } from '../presence/relay-presence.js';
59
- /** Message shared by every node-only accessor, so the reason is stated once. */
60
- function relayHasNo(surface, why) {
61
- return new Error(`this client was constructed with the relay transport, which has no ${surface}: ${why}. ` +
62
- 'Construct a node client (createMeroClient({ baseUrl })) for that surface — ' +
63
- 'nothing here will silently connect to a node you did not choose.');
64
- }
65
- /**
66
- * The one thing a relay client can be missing, named precisely.
67
- *
68
- * Deliberately not the same message as {@link relayHasNo}: this is not "the
69
- * relay transport has no such surface". The relay has the surface — it serves
70
- * `/sse` and `/ws`, and the `account_proof` provider grants
71
- * `context:subscribe`. What is missing is a single 32-byte value, and a caller
72
- * who can obtain it needs to be told that, not told to go and build a node
73
- * client.
74
- */
75
- function missingNodeKey(surface) {
76
- return new Error(`this relay-transport client cannot open ${surface}: it was given no relay node key. ` +
77
- 'A relay is an ordinary node and does serve /sse and /ws — but the login statement must be ' +
78
- "signed against the node's device signing key, learned out of band (never read from the node, " +
79
- 'and never derived from its peerId). Pass it as `observe: { nodeKey }`. ' +
80
- 'For a cloud-hosted relay the cloud does not publish it yet — see mdma #312.');
81
- }
82
- /**
83
- * Resolve an observe config against the relay client that will do the writing.
84
- *
85
- * Returns `null` for "no key, so no observation" — the single place that
86
- * decision is made, so `canSubscribe`, `events` and `ws` cannot disagree about
87
- * it. The signer and proof default to the relay's own: the same device that
88
- * authors is the device that observes, and taking a second copy from the caller
89
- * is how the two come to name different keys.
90
- */
91
- function buildObserver(relay, observe, fetchImpl) {
92
- const nodeKey = observe?.nodeKey;
93
- if (!nodeKey)
94
- return null;
95
- return new RelayObserver({
96
- nodeUrl: observe?.nodeUrl ?? relay.relayUrl,
97
- nodeKey,
98
- accountProof: observe?.accountProof ?? relay.authorProof,
99
- audience: observe?.audience ?? defaultAudience(),
100
- signer: observe?.signer ? async () => observe.signer : () => relay.authorSigner(),
101
- clientName: observe?.clientName,
102
- ttlSeconds: observe?.ttlSeconds,
103
- fetch: observe?.fetch ?? fetchImpl,
104
- timeoutMs: observe?.timeoutMs,
105
- });
106
- }
107
- /**
108
- * A connection whose transport was decided at construction.
109
- *
110
- * Everything reachable on a node client is reachable in exactly the shape
111
- * `MeroJs` exposes it, by delegation rather than re-implementation — this adds
112
- * no behaviour to the node path and cannot drift from it.
113
- */
114
- export class MeroClient {
115
- constructor(config) {
116
- /** The relay's event session, or `null` on a node client / with no node key. */
117
- this.relayObserver = null;
118
- /** Admin reads against the relay, when a session or a proof was supplied. */
119
- this.relayAdmin = null;
120
- /**
121
- * Events off the relay authenticated by a request proof, built lazily.
122
- *
123
- * Separate from {@link relayObserver} rather than a mode inside it: the
124
- * observer's whole subject is a session addressed to a node key, and a proof
125
- * has neither. Holding the authorizer instead of the client keeps construction
126
- * free of the first signature.
127
- */
128
- this.proofAuthorizer = null;
129
- this.relayUrl = null;
130
- this.proofEvents = null;
131
- /** The relay transport's `fetch`, if one was given. */
132
- this.relayFetch = undefined;
133
- this.relayPresence = null;
134
- if (config.transport === 'relay') {
135
- this.transport = 'relay';
136
- this.nodeClient = null;
137
- const fetchImpl = config.fetch;
138
- this.relayFetch = fetchImpl;
139
- const relay = config.relay instanceof RelayClient
140
- ? config.relay
141
- : new RelayClient({ ...config.relay, fetch: config.relay.fetch ?? fetchImpl });
142
- this.relayObserver = buildObserver(relay, config.observe, fetchImpl);
143
- this.rpcTransport = new RelayTransport(relay, this.relayObserver);
144
- this.relayUrl = relay.relayUrl;
145
- if (config.proof) {
146
- this.proofAuthorizer = createProofAuthorizer(config.proof);
147
- }
148
- // A session wins where both are present. Not a preference between
149
- // credentials but the node's own precedence: it answers a request carrying
150
- // a token by that token and consults a proof only when there is none, so
151
- // sending both would authenticate by the session anyway — and a client
152
- // whose reads and whose events disagreed about which identity they ran as
153
- // is the one outcome worth ruling out here.
154
- if (config.session) {
155
- const token = config.session;
156
- this.relayAdmin = createAdminApiClient({
157
- baseUrl: relay.relayUrl,
158
- getAuthToken: typeof token === 'string' ? async () => token : token,
159
- fetch: fetchImpl,
160
- });
161
- }
162
- else if (this.proofAuthorizer) {
163
- const authorizeRequest = this.proofAuthorizer;
164
- this.relayAdmin = createAdminApiClientFromHttpClient(createHttpClient({
165
- fetch: (url, init) => fetchImpl ? fetchImpl(url, init) : globalThis.fetch(url, init),
166
- baseUrl: relay.relayUrl,
167
- authorizeRequest,
168
- }), { baseUrl: relay.relayUrl });
169
- }
170
- return;
171
- }
172
- this.transport = 'node';
173
- // `transport` is a marker this class reads and `MeroJs` has never seen.
174
- // Stripped rather than passed through, so nothing downstream has to know
175
- // that the field exists.
176
- const { transport: _ignored, ...nodeConfig } = config;
177
- this.nodeClient = new MeroJs(nodeConfig);
178
- this.rpcTransport = this.nodeClient.rpc;
179
- }
180
- /**
181
- * The write surface. Identical on both transports — this is the whole point.
182
- *
183
- * On a node this IS the `RpcClient` a `MeroJs` would have handed out, not a
184
- * wrapper around it, so `client.rpc` and `client.node.rpc` are the same
185
- * object.
186
- */
187
- get rpc() {
188
- return this.rpcTransport;
189
- }
190
- /**
191
- * Whether `events` / `ws` will work on this client.
192
- *
193
- * Always `true` on the node transport. On the relay transport it says whether
194
- * a node key was supplied — the relay can be observed, but only by a caller
195
- * who can name the key the login statement is signed against.
196
- */
197
- get canSubscribe() {
198
- return this.rpcTransport.canSubscribe || this.proofAuthorizer !== null;
199
- }
200
- /** The underlying `MeroJs`. Node transport only. */
201
- get node() {
202
- if (!this.nodeClient) {
203
- throw relayHasNo('node connection', 'it holds a signing key and a relay URL, not a node');
204
- }
205
- return this.nodeClient;
206
- }
207
- /** The underlying `RelayClient`, for `describe()`. Relay transport only. */
208
- get relay() {
209
- if (!(this.rpcTransport instanceof RelayTransport)) {
210
- throw new Error('this client was constructed with the node transport and has no relay');
211
- }
212
- return this.rpcTransport.client;
213
- }
214
- /**
215
- * Admin API. Node transport only.
216
- *
217
- * Two different things live behind this accessor today, and only one of them
218
- * is genuinely unavailable through a relay.
219
- *
220
- * The **mutations** — `createNamespace`, `setMemberCapabilities`,
221
- * `upgradeGroup` and the rest — are operator actions on somebody else's
222
- * machine. A keyholder does not get those, and should not; admin is for
223
- * admins.
224
- *
225
- * The **reads** are a different matter. `getContext`, `getContexts`,
226
- * `getApplication`, `listNamespaces`, `getBlob` are an app asking what it is
227
- * looking at, not an operator administering anything. They sit under
228
- * `/admin-api/` for historical reasons, and a delegated session is refused
229
- * them (measured: `403`, while the same session reads context state through
230
- * `POST /contexts/{ctx}/query` and writes through `/intents` quite happily).
231
- *
232
- * So this is the one place where "the same code runs on both transports"
233
- * does not hold yet, and it is a missing surface rather than a missing
234
- * permission. A transport-independent read API is designed but not built; the
235
- * throw below is deliberate until it exists, because the alternative — quietly
236
- * reaching for a node the caller did not choose — is worse than a clear stop.
237
- */
238
- /**
239
- * The admin surface.
240
- *
241
- * On a node client this is the node's own. On a relay client it exists only
242
- * when a `session` was supplied, and it is **the relay's admin API
243
- * authenticated as the account** — so the caller-scoped reads answer:
244
- * `getContexts` and `listNamespaces` return this account's own, because
245
- * `account_proof` grants `context:list-own` and `namespace:list-own`.
246
- *
247
- * Mutations are not filtered out here, deliberately. A delegated session does
248
- * not carry the grants they require, so the node refuses them with a `403`
249
- * that names the missing permission — which is a better answer than a
250
- * client-side guess, and keeps the policy in the one place that owns it.
251
- */
252
- get admin() {
253
- if (this.nodeClient)
254
- return this.nodeClient.admin;
255
- if (this.relayAdmin)
256
- return this.relayAdmin;
257
- throw new Error('this relay-transport client has no admin surface because it was given neither a `proof` ' +
258
- 'nor a `session`. A warrant authorises one intent and is not a credential for reading. ' +
259
- 'The caller-scoped reads (getContexts, listNamespaces) want one of the other two: pass ' +
260
- '`proof: { credential, deviceSecret }` — the device cert you already hold, which needs ' +
261
- 'no login and no node key — or a `session` minted with `login()`. Context STATE reads ' +
262
- 'work through execute/query either way.');
263
- }
264
- /** Auth API. Node transport only — a relay issues no node credential. */
265
- get auth() {
266
- return this.node.auth;
267
- }
268
- /**
269
- * SSE events — on **both** transports.
270
- *
271
- * On a relay client this is an `SseClient` against the relay node, fed by a
272
- * session minted with the author's account proof. The session is minted
273
- * lazily, on the first token the stream asks for, so reading this getter
274
- * performs no network call.
275
- *
276
- * Throws only when the relay client was given no node key: the one input
277
- * that cannot be inferred. See {@link missingNodeKey}.
278
- */
279
- get events() {
280
- if (this.nodeClient)
281
- return this.nodeClient.events;
282
- if (this.relayObserver)
283
- return this.relayObserver.events;
284
- if (this.proofAuthorizer && this.relayUrl) {
285
- // Built on first use and then kept: a second `SseClient` would open a
286
- // second stream and split the subscriptions across two sessions, so the
287
- // caller would see some of its events on an object it no longer holds.
288
- this.proofEvents ?? (this.proofEvents = new SseClient({
289
- baseUrl: this.relayUrl,
290
- authorize: this.proofAuthorizer,
291
- fetch: this.relayFetch,
292
- }));
293
- return this.proofEvents;
294
- }
295
- throw missingNodeKey('SSE events');
296
- }
297
- /** WebSocket events, on both transports. @experimental — prefer `events`. */
298
- get ws() {
299
- if (this.nodeClient)
300
- return this.nodeClient.ws;
301
- if (this.relayObserver)
302
- return this.relayObserver.ws;
303
- if (this.proofAuthorizer) {
304
- // Not a missing credential: a proof DOES open `/ws` — measured, `101`
305
- // against a node built from core `aecba573b`. What is missing is a way to
306
- // carry it, and that is the browser's. `WebSocket` cannot set a request
307
- // header, and the guard reads the proof from a header only; the `?token=`
308
- // query parameter this client uses for a session exists for exactly that
309
- // reason and has no proof equivalent yet.
310
- throw new Error('this client authenticates with a request proof, which cannot open a WebSocket from a ' +
311
- "browser: `WebSocket` cannot set the X-Calimero-Proof header, and core's guard reads " +
312
- 'the proof from a header only (the ?token= query parameter is the session equivalent ' +
313
- 'and has no proof counterpart). Use `client.events` — SSE is fetch-based, carries the ' +
314
- 'header, and is the recommended transport anyway.');
315
- }
316
- throw missingNodeKey('WebSocket events');
317
- }
318
- /**
319
- * Ephemeral presence. Node transport only.
320
- *
321
- * On a relay client this is an account's presence client
322
- * ({@link RelayPresenceClient}): `set` signs each update with the device key
323
- * and posts it to the relay's `presence-intents`, resending every 2.5 s, and
324
- * `subscribe` reads the relay's event stream. The relay checks and forwards
325
- * the update; other members verify the device's signature and certificate
326
- * themselves, so it can drop or delay an account's presence but not invent or
327
- * alter it. Only `subscribe` needs the relay's node key (for the event
328
- * stream), so publishing works on any relay client.
329
- */
330
- get ephemeral() {
331
- if (this.nodeClient)
332
- return this.nodeClient.ephemeral;
333
- this.relayPresence ?? (this.relayPresence = new RelayPresenceClient({ relay: this.relay, events: () => this.events }));
334
- return this.relayPresence;
335
- }
336
- /**
337
- * Calimero Cloud.
338
- *
339
- * Available on both transports, because it is neither: the cloud session is
340
- * independent of the node credential and of the relay's signing key, and a
341
- * relay-transport app still needs it to list namespaces and manage billing.
342
- */
343
- get cloud() {
344
- if (!this.nodeClient) {
345
- throw relayHasNo('bundled cloud client', 'a relay client is built from a cloud connection rather than owning one — reach it through the `cloud` field that `connectCloud` returns, or construct a `CloudClient` directly');
346
- }
347
- return this.nodeClient.cloud;
348
- }
349
- /**
350
- * Release whatever the transport is holding.
351
- *
352
- * Safe on a relay client, where there is nothing open to close — a caller
353
- * that tears down on unmount should not have to know which transport it got.
354
- */
355
- close() {
356
- this.nodeClient?.close();
357
- // The observer and the proof stream, not the relay: a relay client holds
358
- // nothing open. Tearing down on unmount must not have to know which
359
- // transport — or which credential — it got.
360
- this.relayObserver?.close();
361
- this.proofEvents?.close();
362
- }
363
- }
364
- /**
365
- * Build a client for the chosen transport. Node unless told otherwise.
366
- *
367
- * ```ts
368
- * // unchanged, and still the default
369
- * const client = createMeroClient({ baseUrl: 'http://localhost:2428' });
370
- *
371
- * // the same call sites, through a relay
372
- * const client = createMeroClient({
373
- * transport: 'relay',
374
- * relay: connection.relay,
375
- * // the relay node's signing key, learned out of band — without it the
376
- * // client writes but does not observe
377
- * observe: { nodeKey },
378
- * });
379
- *
380
- * await client.rpc.execute({ contextId, method: 'set', argsJson: { key, value } });
381
- * if (client.canSubscribe) client.events.on('event', onEvent);
382
- * ```
383
- */
384
- export function createMeroClient(config) {
385
- return new MeroClient(config);
386
- }