@calimero-network/mero-js 26.11.0 → 27.0.1
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/README.md +73 -288
- package/dist/cloud-v2/attestation.d.ts +91 -0
- package/dist/cloud-v2/attestation.js +135 -0
- package/dist/cloud-v2/capabilities.d.ts +30 -0
- package/dist/cloud-v2/capabilities.js +31 -0
- package/dist/cloud-v2/client.d.ts +426 -0
- package/dist/cloud-v2/client.js +646 -0
- package/dist/cloud-v2/errors.d.ts +98 -0
- package/dist/cloud-v2/errors.js +165 -0
- package/dist/cloud-v2/index.d.ts +10 -0
- package/dist/cloud-v2/index.js +5 -0
- package/dist/cloud-v2/passkey.d.ts +27 -0
- package/dist/cloud-v2/passkey.js +73 -0
- package/dist/crypto/internal.d.ts +3 -37
- package/dist/crypto/internal.js +3 -72
- package/dist/index.browser.mjs +4 -2
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +1750 -12772
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -56
- package/dist/index.js +14 -75
- package/dist/index.mjs +1750 -12772
- package/dist/index.mjs.map +4 -4
- package/dist/sealed/index.d.ts +4 -4
- package/dist/sealed/index.js +2 -2
- package/dist/sealed/release.d.ts +21 -76
- package/dist/sealed/release.js +24 -108
- package/dist/sealed/sealed.d.ts +8 -56
- package/dist/sealed/sealed.js +11 -89
- package/dist/sealed/verify.d.ts +7 -13
- package/dist/sealed/verify.js +3 -7
- package/package.json +8 -13
- package/dist/account/account-admin.d.ts +0 -80
- package/dist/account/account-admin.js +0 -346
- package/dist/account/account.d.ts +0 -196
- package/dist/account/account.js +0 -269
- package/dist/account/application-id.d.ts +0 -50
- package/dist/account/application-id.js +0 -83
- package/dist/account/bootstrap-from-invitation.d.ts +0 -110
- package/dist/account/bootstrap-from-invitation.js +0 -80
- package/dist/account/create-context.d.ts +0 -103
- package/dist/account/create-context.js +0 -267
- package/dist/account/govern.d.ts +0 -24
- package/dist/account/govern.js +0 -50
- package/dist/account/index.d.ts +0 -26
- package/dist/account/index.js +0 -15
- package/dist/account/join-as-account.d.ts +0 -23
- package/dist/account/join-as-account.js +0 -34
- package/dist/account/join-with-node.d.ts +0 -115
- package/dist/account/join-with-node.js +0 -161
- package/dist/account/node-admin.d.ts +0 -20
- package/dist/account/node-admin.js +0 -33
- package/dist/account/relay-from-invitation.d.ts +0 -137
- package/dist/account/relay-from-invitation.js +0 -180
- package/dist/account/relay-login-error.d.ts +0 -32
- package/dist/account/relay-login-error.js +0 -34
- package/dist/account/session.d.ts +0 -266
- package/dist/account/session.js +0 -915
- package/dist/account/wordlist.d.ts +0 -16
- package/dist/account/wordlist.js +0 -273
- package/dist/admin-api/admin-client.d.ts +0 -688
- package/dist/admin-api/admin-client.js +0 -1230
- package/dist/admin-api/admin-factory.d.ts +0 -14
- package/dist/admin-api/admin-factory.js +0 -22
- package/dist/admin-api/admin-types.d.ts +0 -2046
- package/dist/admin-api/admin-types.js +0 -12
- package/dist/admin-api/index.d.ts +0 -4
- package/dist/admin-api/index.js +0 -5
- package/dist/admin-api/redeem-invitation.d.ts +0 -101
- package/dist/admin-api/redeem-invitation.js +0 -200
- package/dist/auth/index.d.ts +0 -34
- package/dist/auth/index.js +0 -67
- package/dist/auth-api/auth-client.d.ts +0 -31
- package/dist/auth-api/auth-client.js +0 -117
- package/dist/auth-api/auth-factory.d.ts +0 -7
- package/dist/auth-api/auth-factory.js +0 -41
- package/dist/auth-api/auth-types.d.ts +0 -130
- package/dist/auth-api/auth-types.js +0 -2
- package/dist/auth-api/index.d.ts +0 -3
- package/dist/auth-api/index.js +0 -4
- package/dist/capabilities.d.ts +0 -84
- package/dist/capabilities.js +0 -117
- package/dist/cloud/account-ownership.d.ts +0 -63
- package/dist/cloud/account-ownership.js +0 -87
- package/dist/cloud/cloud-client.d.ts +0 -949
- package/dist/cloud/cloud-client.js +0 -1068
- package/dist/cloud/connect.d.ts +0 -213
- package/dist/cloud/connect.js +0 -260
- package/dist/cloud/enrol-redirect.d.ts +0 -181
- package/dist/cloud/enrol-redirect.js +0 -244
- package/dist/cloud/index.d.ts +0 -12
- package/dist/cloud/index.js +0 -10
- package/dist/cloud/link-redirect.d.ts +0 -95
- package/dist/cloud/link-redirect.js +0 -121
- package/dist/cloud/link-state.d.ts +0 -36
- package/dist/cloud/link-state.js +0 -104
- package/dist/cloud/routing-proof.d.ts +0 -113
- package/dist/cloud/routing-proof.js +0 -96
- package/dist/device-cert/device-cert.d.ts +0 -189
- package/dist/device-cert/device-cert.js +0 -239
- package/dist/device-cert/device-scope.d.ts +0 -58
- package/dist/device-cert/device-scope.js +0 -76
- package/dist/device-cert/index.d.ts +0 -4
- package/dist/device-cert/index.js +0 -3
- package/dist/ephemeral/codec.d.ts +0 -4
- package/dist/ephemeral/codec.js +0 -12
- package/dist/ephemeral/index.d.ts +0 -57
- package/dist/ephemeral/index.js +0 -101
- package/dist/ephemeral/types.d.ts +0 -27
- package/dist/ephemeral/types.js +0 -1
- package/dist/errors/classify.d.ts +0 -65
- package/dist/errors/classify.js +0 -117
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.js +0 -1
- package/dist/events/group.d.ts +0 -95
- package/dist/events/group.js +0 -10
- package/dist/events/index.d.ts +0 -5
- package/dist/events/index.js +0 -2
- package/dist/events/sse.d.ts +0 -127
- package/dist/events/sse.js +0 -469
- package/dist/events/ws.d.ts +0 -57
- package/dist/events/ws.js +0 -212
- package/dist/http-client/api-response.d.ts +0 -15
- package/dist/http-client/api-response.js +0 -1
- package/dist/http-client/http-factory.d.ts +0 -71
- package/dist/http-client/http-factory.js +0 -68
- package/dist/http-client/http-types.d.ts +0 -87
- package/dist/http-client/http-types.js +0 -1
- package/dist/http-client/index.d.ts +0 -7
- package/dist/http-client/index.js +0 -11
- package/dist/http-client/path-segment.d.ts +0 -1
- package/dist/http-client/path-segment.js +0 -8
- package/dist/http-client/retry.d.ts +0 -5
- package/dist/http-client/retry.js +0 -82
- package/dist/http-client/signal-utils.d.ts +0 -2
- package/dist/http-client/signal-utils.js +0 -41
- package/dist/http-client/web-client.d.ts +0 -80
- package/dist/http-client/web-client.js +0 -557
- package/dist/invitation/index.d.ts +0 -2
- package/dist/invitation/index.js +0 -2
- package/dist/invitation/invitation.d.ts +0 -106
- package/dist/invitation/invitation.js +0 -104
- package/dist/login/index.d.ts +0 -4
- package/dist/login/index.js +0 -3
- package/dist/login/login.d.ts +0 -111
- package/dist/login/login.js +0 -110
- package/dist/login/session.d.ts +0 -124
- package/dist/login/session.js +0 -141
- package/dist/mero-js.d.ts +0 -231
- package/dist/mero-js.js +0 -469
- package/dist/namespace-op/index.d.ts +0 -2
- package/dist/namespace-op/index.js +0 -3
- package/dist/namespace-op/namespace-op.d.ts +0 -150
- package/dist/namespace-op/namespace-op.js +0 -191
- package/dist/nodeDiscovery.d.ts +0 -47
- package/dist/nodeDiscovery.js +0 -101
- package/dist/owner-op/index.d.ts +0 -2
- package/dist/owner-op/index.js +0 -3
- package/dist/owner-op/owner-op.d.ts +0 -119
- package/dist/owner-op/owner-op.js +0 -214
- package/dist/presence/relay-presence.d.ts +0 -56
- package/dist/presence/relay-presence.js +0 -107
- package/dist/presence/statement.d.ts +0 -12
- package/dist/presence/statement.js +0 -17
- package/dist/relay/index.d.ts +0 -4
- package/dist/relay/index.js +0 -2
- package/dist/relay/nonce-source.d.ts +0 -72
- package/dist/relay/nonce-source.js +0 -111
- package/dist/relay/relay-client.d.ts +0 -472
- package/dist/relay/relay-client.js +0 -518
- package/dist/relay-attestation/attest-node-key.d.ts +0 -66
- package/dist/relay-attestation/attest-node-key.js +0 -110
- package/dist/relay-attestation/index.d.ts +0 -1
- package/dist/relay-attestation/index.js +0 -1
- package/dist/request/caller-proof.d.ts +0 -43
- package/dist/request/caller-proof.js +0 -36
- package/dist/request/index.d.ts +0 -3
- package/dist/request/index.js +0 -3
- package/dist/request/proof-signer.d.ts +0 -49
- package/dist/request/proof-signer.js +0 -59
- package/dist/request/request-sig.d.ts +0 -99
- package/dist/request/request-sig.js +0 -95
- package/dist/request-proof/authorizer.d.ts +0 -41
- package/dist/request-proof/authorizer.js +0 -60
- package/dist/request-proof/index.d.ts +0 -4
- package/dist/request-proof/index.js +0 -2
- package/dist/request-proof/request-proof.d.ts +0 -53
- package/dist/request-proof/request-proof.js +0 -95
- package/dist/rpc/index.d.ts +0 -64
- package/dist/rpc/index.js +0 -78
- package/dist/signer/index.d.ts +0 -2
- package/dist/signer/index.js +0 -3
- package/dist/signer/signer.d.ts +0 -92
- package/dist/signer/signer.js +0 -147
- package/dist/token-store/index.d.ts +0 -27
- package/dist/token-store/index.js +0 -62
- package/dist/transport/index.d.ts +0 -6
- package/dist/transport/index.js +0 -3
- package/dist/transport/mero-client.d.ts +0 -294
- package/dist/transport/mero-client.js +0 -386
- package/dist/transport/relay-observer.d.ts +0 -152
- package/dist/transport/relay-observer.js +0 -173
- package/dist/transport/relay-transport.d.ts +0 -73
- package/dist/transport/relay-transport.js +0 -94
- package/dist/transport/types.d.ts +0 -100
- package/dist/transport/types.js +0 -32
- package/dist/types/api-response.d.ts +0 -15
- package/dist/types/api-response.js +0 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/warrant/creation-warrant.d.ts +0 -163
- package/dist/warrant/creation-warrant.js +0 -194
- package/dist/warrant/governance-op.d.ts +0 -202
- package/dist/warrant/governance-op.js +0 -355
- package/dist/warrant/governance-warrant.d.ts +0 -124
- package/dist/warrant/governance-warrant.js +0 -164
- package/dist/warrant/index.d.ts +0 -8
- package/dist/warrant/index.js +0 -5
- package/dist/warrant/warrant.d.ts +0 -188
- 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
|
-
}
|