@calimero-network/mero-js 26.11.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 -12772
  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 -12772
  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 -688
  62. package/dist/admin-api/admin-client.js +0 -1230
  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 -2046
  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,180 +0,0 @@
1
- /**
2
- * Resolving a relay from an INVITATION rather than from an account.
3
- *
4
- * # The gap this closes
5
- *
6
- * `ConnectButtonAccount` finds a relay by asking the cloud what serves the
7
- * *account* (`CloudClient.getAccountRelays`). A freshly enrolled account is a
8
- * member of nothing, so that read correctly answers `[]` and the button
9
- * correctly refuses to connect: there is nowhere to write, and a typed guess
10
- * would be a node that refuses every warrant.
11
- *
12
- * But a newcomer is not stuck — they are holding an invitation, and an
13
- * invitation names a namespace. `CloudClient.getNamespaceRouting` answers which
14
- * nodes serve *that namespace*, and it is deliberately anonymous: a joiner has
15
- * no cloud session, and needing one is exactly what this path exists to avoid.
16
- * So the bootstrap order is namespace first, account second — admission is what
17
- * makes `getAccountRelays` answer at all, not the other way round.
18
- *
19
- * # Two sources, one intersection, and why it must not be skipped
20
- *
21
- * The invitation's `admitters` list sits INSIDE the body the group admin
22
- * signed, so it is authorization: merod answers `403` to an `/admit` from a node
23
- * that list does not name, whatever else is true of it. The cloud's list is
24
- * every node currently assigned to the namespace — it cannot know which
25
- * invitation the caller holds, so it cannot filter by the signed list, and a
26
- * node assigned *after* the invitation was minted looks perfect in every field
27
- * the cloud reports and still refuses the claim.
28
- *
29
- * Intersecting here is therefore not belt-and-braces: it is the check that
30
- * turns an unexplainable `403` into "your invitation names none of these nodes,
31
- * ask for a fresh one". It is never relaxed to make a flow work.
32
- *
33
- * # `servable` and `canAdmit` are advisory; `writable` is information
34
- *
35
- * `servable` and `canAdmit` are derived from assignment heartbeats, and a lapsed
36
- * heartbeat is not the same fact as a node that will refuse. Measured against
37
- * prod: the cloud reported `servable: false, canAdmit: false, fresh: false` for a
38
- * node whose `/admin-api/health` answered `{"status":"alive"}` and which had just
39
- * minted the invitation being claimed. Gating on them meant refusing to knock on
40
- * an open door, so a node with an address is tried and the NODE decides —
41
- * `admit_join` re-verifies the invitation, the signature and the admitters list,
42
- * so asking loosens nothing and a refusal names its own cause. `stale` on the
43
- * result says the attempt rests on advisory data.
44
- *
45
- * `writable` — and its per-node form, `canExecute` — is about `CAN_AUTHOR_ON_BEHALF`,
46
- * which admission does **not** require: relaying a join the joiner signed is not
47
- * authoring on anyone's behalf. So a non-writable namespace is not a refusal
48
- * here; it is reported on the result, because it is precisely why the writes
49
- * would fail *after* connecting, and a caller that cannot tell the two apart
50
- * sends the user to the wrong place.
51
- */
52
- import { CloudClient, } from '../cloud/index.js';
53
- import { pathSegment } from '../http-client/path-segment.js';
54
- /**
55
- * Normalise a hex account for comparison: lower-cased, `0x` stripped.
56
- *
57
- * Both sides are hex for the same 32 bytes, and a spelling mismatch would
58
- * present as "your invitation does not name this node" — a refusal that looks
59
- * like policy and is really formatting.
60
- */
61
- export function normaliseAccount(account) {
62
- if (!account)
63
- return null;
64
- const trimmed = account.trim().toLowerCase();
65
- const bare = trimmed.startsWith('0x') ? trimmed.slice(2) : trimmed;
66
- return bare.length > 0 ? bare : null;
67
- }
68
- /** Whether the invitation's signed list names this node. */
69
- function isInvited(node, invited) {
70
- if (invited.size === 0)
71
- return true;
72
- const account = normaliseAccount(node.account);
73
- return account !== null && invited.has(account);
74
- }
75
- /**
76
- * Resolve a relay to bootstrap through, from an invitation and nothing else.
77
- *
78
- * Reads the cloud's routing for the namespace, intersects it with the
79
- * invitation's signed `admitters`, and returns the node to use — or which step
80
- * stopped it and what that means. It performs no admission and mints no keys:
81
- * the credential passed in is the one the wallet already certified.
82
- */
83
- export async function resolveRelayFromInvitation(input) {
84
- const cloud = input.cloud ??
85
- new CloudClient({
86
- cloudBaseUrl: input.cloudBaseUrl,
87
- routingCredential: { credential: input.credential, deviceSecret: input.deviceSecret },
88
- });
89
- let nodes;
90
- let servable;
91
- try {
92
- const routing = await cloud.getNamespaceRouting(input.namespaceId);
93
- nodes = routing.nodes;
94
- servable = routing.servable;
95
- }
96
- catch (e) {
97
- // Named as its own step: nothing has been learned about the namespace, so
98
- // reporting this as "no nodes serve it" would be a claim the read never
99
- // made — and would send the user to ask for a new invitation over what is
100
- // usually a refused proof or a network blip.
101
- return {
102
- ok: false,
103
- step: 'admitters-lookup',
104
- reason: `The cloud could not be asked which nodes serve namespace ${input.namespaceId}: ` +
105
- `${e instanceof Error ? e.message : String(e)}. Nothing is known about the ` +
106
- 'invitation yet — this is the lookup failing, not the invitation.',
107
- };
108
- }
109
- if (nodes.length === 0) {
110
- return {
111
- ok: false,
112
- step: 'no-nodes',
113
- reason: 'The cloud lists no nodes serving this namespace, so there is no hosted node to be ' +
114
- 'admitted through. The namespace has no fleet assignment — whoever invited you is ' +
115
- 'running their own node, and admission has to go through a node the cloud routes to.',
116
- };
117
- }
118
- const invited = new Set((input.admitters ?? [])
119
- .map(normaliseAccount)
120
- .filter((a) => a !== null));
121
- const named = nodes.filter((n) => isInvited(n, invited));
122
- if (named.length === 0) {
123
- return {
124
- ok: false,
125
- step: 'not-invited',
126
- reason: `${nodes.length} node${nodes.length === 1 ? '' : 's'} serve this namespace, but your ` +
127
- 'invitation names none of them. The signed admitter list is a snapshot from when the ' +
128
- 'invitation was minted, so a node assigned since is healthy and will still refuse the ' +
129
- 'claim. Ask for a fresh invitation.',
130
- };
131
- }
132
- // `canAdmit` and `servable` are ADVISORY here, not gates.
133
- //
134
- // They are derived from assignment heartbeats, and a lapsed heartbeat is not
135
- // the same fact as a node that will refuse: measured against prod, the cloud
136
- // reported `servable: false, canAdmit: false, fresh: false` for a node whose
137
- // `/admin-api/health` answered `{"status":"alive"}` and which had just minted
138
- // the invitation being claimed. Refusing on those flags meant refusing to
139
- // knock on a door that was open.
140
- //
141
- // So a node with an address is tried, and the node itself decides. Its answer
142
- // is the authoritative one: `admit_join` re-verifies the invitation, the
143
- // signature and the admitters list, so nothing is loosened by asking — the
144
- // worst case is a 403 that names its own cause, which is strictly better than
145
- // a client-side guess that names a heartbeat.
146
- const usable = named.filter((n) => n.canAdmit);
147
- const candidates = usable.length > 0 ? usable : named;
148
- const advisory = usable.length === 0 || !servable;
149
- const addressable = candidates.filter((n) => typeof n.relayUrl === 'string' && n.relayUrl.length > 0);
150
- const [firstAddressable] = addressable;
151
- if (firstAddressable === undefined) {
152
- return {
153
- ok: false,
154
- step: 'no-relay-url',
155
- reason: 'A node your invitation names can admit you, but the cloud knows no address for it ' +
156
- 'yet, so there would be nothing to connect through afterwards. Try again shortly.',
157
- };
158
- }
159
- // Prefer a node that can do both legs — admit now and author later — so the
160
- // session lands on one node where possible. Preference only: admission is the
161
- // step that cannot proceed without a node, and authorship is not on the
162
- // invitation's list at all, so insisting would refuse a perfectly good
163
- // admitter for a permission admission never needed.
164
- const chosen = addressable.find((n) => n.canExecute) ?? addressable.find((n) => n.fresh) ?? firstAddressable;
165
- return {
166
- ok: true,
167
- relayUrl: chosen.relayUrl,
168
- // The cloud's ready-made URL when it gave one, so the path is never rebuilt
169
- // from a guess; the fallback is the documented route on the same origin.
170
- admitUrl: chosen.admitUrl ??
171
- `${chosen.relayUrl.replace(/\/+$/, '')}/admin-api/namespaces/${pathSegment(input.namespaceId)}/admit`,
172
- admitterAccount: chosen.account,
173
- peerId: chosen.peerId,
174
- writable: chosen.canExecute,
175
- // Stale when the node's own heartbeat lapsed OR when the cloud's summary
176
- // disagrees with trying it at all. Both mean the same thing to a caller:
177
- // this was attempted on advisory data, so if the admit fails that is why.
178
- stale: !chosen.fresh || advisory,
179
- };
180
- }
@@ -1,32 +0,0 @@
1
- import { RequestAuthError } from '../http-client/web-client.js';
2
- /**
3
- * Why a relay login failed, as far as it decides what the app does next.
4
- *
5
- * - `signed-out` — the relay answered and refused the credential (401 or 403):
6
- * it expired or was revoked, and signing in again is the way forward.
7
- * - `unavailable` — the login never got an answer about the credential: the
8
- * relay could not be reached (DNS, TLS, offline, a timeout), or it failed
9
- * with a 5xx, or answered something else. Trying again later may work.
10
- */
11
- export type RelayLoginFailureKind = 'signed-out' | 'unavailable';
12
- /**
13
- * The relay session a request needed could not be minted.
14
- *
15
- * Thrown by the admin requests of a delegated client (`client.admin.*`) in
16
- * place of sending them without a token, so the caller sees the login's own
17
- * failure rather than the node's 401 for the unauthenticated request — or, for
18
- * a relay that is down, the same transport error a second time. Nothing is
19
- * remembered: the next request logs in again.
20
- */
21
- export declare class RelayLoginError extends RequestAuthError {
22
- name: string;
23
- readonly relayUrl: string;
24
- /** The login's own failure: an `HTTPError` (status 0 for no response) or what it threw. */
25
- readonly cause: unknown;
26
- readonly kind: RelayLoginFailureKind;
27
- /** The HTTP status the login answered, 0 when no response arrived; undefined when not an HTTP failure. */
28
- readonly loginStatus: number | undefined;
29
- constructor(relayUrl: string, cause: unknown);
30
- }
31
- /** `signed-out` for a login answered 401/403, `unavailable` for anything else. */
32
- export declare function relayLoginFailureKind(cause: unknown): RelayLoginFailureKind;
@@ -1,34 +0,0 @@
1
- import { RequestAuthError } from '../http-client/web-client.js';
2
- /**
3
- * The relay session a request needed could not be minted.
4
- *
5
- * Thrown by the admin requests of a delegated client (`client.admin.*`) in
6
- * place of sending them without a token, so the caller sees the login's own
7
- * failure rather than the node's 401 for the unauthenticated request — or, for
8
- * a relay that is down, the same transport error a second time. Nothing is
9
- * remembered: the next request logs in again.
10
- */
11
- export class RelayLoginError extends RequestAuthError {
12
- constructor(relayUrl, cause) {
13
- const kind = relayLoginFailureKind(cause);
14
- const detail = cause instanceof Error ? cause.message : String(cause);
15
- super(kind === 'signed-out'
16
- ? `the relay ${relayUrl} refused this device's credential (${detail}); sign in again`
17
- : `could not log in to the relay ${relayUrl}: ${detail}`);
18
- this.name = 'RelayLoginError';
19
- this.cause = cause;
20
- this.relayUrl = relayUrl;
21
- this.kind = kind;
22
- this.loginStatus = loginStatusOf(cause);
23
- }
24
- }
25
- /** `signed-out` for a login answered 401/403, `unavailable` for anything else. */
26
- export function relayLoginFailureKind(cause) {
27
- const status = loginStatusOf(cause);
28
- return status === 401 || status === 403 ? 'signed-out' : 'unavailable';
29
- }
30
- /** The status a login failure carries (`HTTPError.status`), if any. */
31
- function loginStatusOf(cause) {
32
- const status = cause?.status;
33
- return typeof status === 'number' && Number.isFinite(status) ? status : undefined;
34
- }
@@ -1,266 +0,0 @@
1
- import { type MeroClient } from '../transport/index.js';
2
- /**
3
- * What a delegated connection needs to be rebuilt after a reload.
4
- *
5
- * `deviceSecret` is here because the device key is what signs warrants, and a
6
- * reload that could not sign would be a dead session. It is a per-tab secret for
7
- * a certificate the account can revoke — not the account root, which never
8
- * leaves the wallet's origin.
9
- */
10
- /**
11
- * What the wallet certified: an account, a device it trusts, and the key that
12
- * device signs with. Everything except somewhere to send the result.
13
- *
14
- * Split out of {@link DelegatedSession} because it is reachable one step
15
- * earlier. An account that has just enrolled holds exactly this and no relay —
16
- * it is a member of nothing, so the cloud has no relay to name — and this is the
17
- * input that resolves one from an invitation instead.
18
- */
19
- export interface DelegatedCredential {
20
- /** The author's account id, hex. */
21
- account: string;
22
- /** The author's `AccountProof<DeviceCert>`, hex borsh. */
23
- credential: string;
24
- /** The certified device's ed25519 signing secret, hex. */
25
- deviceSecret: string;
26
- }
27
- export interface DelegatedSession extends DelegatedCredential {
28
- /**
29
- * The relay to write through — a node origin, or `null` for none yet.
30
- *
31
- * **`null` is a logged-in state, not a broken one.** A brand-new account is a
32
- * member of nothing, so the cloud names no relay for it — and that is the
33
- * normal condition of an account that has just been created, because being
34
- * signed in is how you come to be invited in the first place. Refusing to
35
- * connect without a relay locked a new account out of the only path that
36
- * would earn it one.
37
- *
38
- * What it does NOT mean is that writes quietly do nothing. There is no
39
- * client at all without a URL ({@link buildDelegatedClient} returns `null`),
40
- * so `mero` stays `null`, the session is authenticated, and anything reaching
41
- * for `rpc.execute` is told the relay is missing. A placeholder URL would be
42
- * the one unacceptable answer: it would look connected and refuse every
43
- * warrant.
44
- */
45
- relayUrl: string | null;
46
- /**
47
- * The relay's executor account, hex, when the app knows it — the cloud's
48
- * machine page names it beside the relay's address.
49
- *
50
- * Founding a namespace through a relay names the relay's account (and its
51
- * node key) in the warrant, and a namespace that does not exist yet cannot be
52
- * asked for them.
53
- * Without this the account learns it from a namespace it is already in, so a
54
- * brand-new account would have to join one first; with it, the account founds
55
- * on `relayUrl` directly.
56
- */
57
- executorAccount?: string | null;
58
- }
59
- export declare function readDelegatedSession(): DelegatedSession | null;
60
- export declare function saveDelegatedSession(s: DelegatedSession): void;
61
- export declare function clearDelegatedSession(): void;
62
- /**
63
- * The enrolled credential, whether or not a relay was ever found for it.
64
- *
65
- * A connected session is authoritative: it carries the same three fields and a
66
- * relay, so it is read first and the standalone record is the fallback. Without
67
- * that order a stale record from an earlier enrolment could out-rank the account
68
- * actually connected.
69
- */
70
- export declare function readDelegatedCredential(): DelegatedCredential | null;
71
- export declare function saveDelegatedCredential(c: DelegatedCredential): void;
72
- export declare function clearDelegatedCredential(): void;
73
- /**
74
- * A warrant nonce is spent on the node, so the counter must outlive the page.
75
- *
76
- * In `localStorage`, not session: a reload that restarted from zero would
77
- * replay a spent nonce and be refused. Keyed by relay and context because the
78
- * ledger the node keeps is per `(context, author device)`.
79
- *
80
- * No node serves a "where does my sequence stand" lookup (core #4018), so
81
- * holding the counter locally is the only option, and losing storage means
82
- * burning refusals until the counter catches up.
83
- */
84
- export declare function persistedNonces(relayUrl: string): {
85
- next: () => Promise<bigint>;
86
- };
87
- /**
88
- * Record that `nonce` is spent for this device, so later intents start above
89
- * it. A creation warrant's nonce is spent in the NEW context's own window; one
90
- * sequence per relay covers that context like any other.
91
- */
92
- export declare function markContextNonceSpent(relayUrl: string, _contextId: string, nonce: bigint): void;
93
- /**
94
- * Pin a relay's device signing key, hex (32 bytes), learned out of band.
95
- *
96
- * `login()` binds the session to this key, and it must never be read from the
97
- * relay being logged in to — the answering party would choose what the device
98
- * signs about. Until the cloud publishes it (mdma#312), it comes from the
99
- * relay's operator.
100
- */
101
- export declare function pinRelayNodeKey(relayUrl: string, nodeKey: string): void;
102
- /** The pinned key for a relay, or `null` if none was pinned. */
103
- export declare function readPinnedRelayNodeKey(relayUrl: string): string | null;
104
- /**
105
- * One attempt at a relay's node key, and what became of it:
106
- *
107
- * - `learned`: verified (or already pinned), and pinned now;
108
- * - `unavailable`: something on the way did not answer — the relay, the release
109
- * mirror, the quote library's download. Nothing was decided; try again.
110
- * - `refused`: the relay answered and its proof did not hold — a quote that is
111
- * not of a trusted image, not bound to this request, a mock from a hosted
112
- * relay. Trying again cannot change that answer into a yes, and must not.
113
- */
114
- export type RelayNodeKeyAttempt = {
115
- kind: 'learned';
116
- nodeKey: string;
117
- } | {
118
- kind: 'unavailable';
119
- error: unknown;
120
- } | {
121
- kind: 'refused';
122
- error: unknown;
123
- };
124
- /**
125
- * The relay's node key: the pinned one, or else learned from the relay's TEE
126
- * attestation (see mero-js `attestRelayNodeKey`) and pinned for next time.
127
- *
128
- * A mock quote proves nothing about hardware, so one is accepted only from a
129
- * loopback relay — a local rig. A real one must be a signed release of the
130
- * trusted profile ({@link relayQuoteVerifier}).
131
- *
132
- * `fresh` skips the pin and attests now, for a step that hands the relay
133
- * something it keeps — founding a namespace through it. A pinned key was
134
- * checked once, on some earlier day, against whatever answered then.
135
- */
136
- export declare function attemptRelayNodeKey(relayUrl: string, options?: {
137
- fresh?: boolean;
138
- }): Promise<RelayNodeKeyAttempt>;
139
- /**
140
- * The relay's node key: the pinned one, or else learned from the relay's TEE
141
- * attestation in one attempt; `null` when that attempt did not learn it, for
142
- * whatever reason. Callers that must end up with the key use
143
- * {@link learnRelayNodeKey}.
144
- */
145
- export declare function resolveRelayNodeKey(relayUrl: string): Promise<string | null>;
146
- /**
147
- * How long to wait before each retry of an unavailable relay key: quickly at
148
- * first, for a blip or a cold cache, then every 30 s for as long as it takes.
149
- */
150
- export declare const RELAY_NODE_KEY_RETRY_MS: readonly number[];
151
- /**
152
- * The relay's node key, however long the way to it is down.
153
- *
154
- * One failed fetch of the release used to cost the whole session: no key, so no
155
- * relay session, so every admin read and every event stream unauthenticated
156
- * until a reload. A transport failure here is now waited out with backoff
157
- * ({@link RELAY_NODE_KEY_RETRY_MS}) until the key is learned or `signal` aborts.
158
- *
159
- * A refusal ends it at once with `null`. A quote that did not verify is an
160
- * answer, and asking again is not how it becomes a different one.
161
- */
162
- export declare function learnRelayNodeKey(relayUrl: string, options?: {
163
- signal?: AbortSignal;
164
- }): Promise<string | null>;
165
- /** Forget every relay's sealed transport. For tests. */
166
- export declare function forgetRelaySealing(): void;
167
- /**
168
- * The `fetch` a delegated session uses for `relayUrl`: sealed to its attested
169
- * TD, one per relay so its session is shared. `undefined` for a loopback relay,
170
- * a dev rig answering mock quotes, which keeps the global `fetch`.
171
- */
172
- export declare function relayTransportFetch(relayUrl: string): typeof fetch | undefined;
173
- /** Forget what was learned about which methods read. For tests. */
174
- export declare function forgetMethodKinds(): void;
175
- /**
176
- * Build the client a delegated session talks through.
177
- *
178
- * The result is a relay-transport `MeroClient`. Its `rpc.execute` takes the same
179
- * named arguments a node client's does, which is what lets a generated ABI
180
- * client — and every hook that only calls `execute` — work unchanged. What it
181
- * also has is `admin` and `events`, pointed at the relay and authenticated as
182
- * the account by the device certificate, so the caller-scoped reads answer this
183
- * account's own contexts and namespaces rather than the node's.
184
- */
185
- export declare function buildDelegatedClient(s: DelegatedSession, _contextId: string | null): MeroClient | null;
186
- export interface RelayMap {
187
- namespaces: Record<string, string>;
188
- contexts: Record<string, string>;
189
- }
190
- export declare function readRelayMap(account: string): RelayMap;
191
- /** Record the relay that admitted this account to a namespace (and, when known, the context it came for). */
192
- export declare function rememberRelay(account: string, relayUrl: string, at: {
193
- namespaceId?: string;
194
- contextId?: string;
195
- }): void;
196
- /** The relay that serves a context, if this account has learned it. */
197
- export declare function relayForContext(account: string, contextId: string): string | null;
198
- /**
199
- * A session moving to `next`, keeping the relay's executor account the previous
200
- * one knew when both are the same relay.
201
- *
202
- * A join hands back a session built from the admitting node alone, which has no
203
- * executor on it. When that node is the relay the account was already on — the
204
- * one the cloud assigned it, say — dropping the executor would cost nothing
205
- * today (the joined namespace can answer for it) but leaves the session knowing
206
- * less than it did. On a different relay the old executor is the wrong account,
207
- * so it is left behind.
208
- */
209
- export declare function carryExecutorAccount(prev: DelegatedSession | null, next: DelegatedSession): DelegatedSession;
210
- /** Every relay this account is known to use: the session's, and each in the map. */
211
- export declare function knownRelays(s: DelegatedSession): string[];
212
- /**
213
- * Thrown by {@link listDelegatedContexts} and {@link listDelegatedNamespaces}
214
- * when the account knows relays and **none** of them answered.
215
- *
216
- * One relay down is skipped, as before: the others still answer for what they
217
- * serve. Every relay down is different. Returning `[]` then reads as "a member
218
- * of nothing", and an app renders an empty account, with no error, for what is
219
- * really "could not ask". A relay whose hostname no longer resolves (a node
220
- * rebuilt or deleted in the cloud) did exactly that.
221
- */
222
- /**
223
- * Why one relay did not answer a listing.
224
- *
225
- * `refused`: its attestation did not verify (an untrusted image, a release
226
- * below the floor). Asking again cannot change that, and it may be worth a
227
- * closer look. `unavailable`: something on the way did not answer, or the
228
- * relay refused the request itself; trying again later may.
229
- */
230
- export interface RelayListingFailure {
231
- relayUrl: string;
232
- kind: 'refused' | 'unavailable';
233
- error: unknown;
234
- }
235
- export declare class RelaysUnavailableError extends Error {
236
- readonly failures: readonly RelayListingFailure[];
237
- readonly relays: readonly string[];
238
- constructor(failures: readonly RelayListingFailure[]);
239
- /** Every relay refused its attestation: retrying will not help. */
240
- get refused(): boolean;
241
- }
242
- type ListedContext = Awaited<ReturnType<MeroClient['admin']['getContexts']>>['contexts'][number];
243
- /**
244
- * This account's contexts on every relay it knows, each tagged with the relay
245
- * that listed it — and that relay recorded as the one serving it.
246
- *
247
- * Each relay answers only for itself and only for this caller, so the union is
248
- * exactly the account's contexts across relays. A relay that cannot be reached
249
- * or logged in to is skipped (its contexts simply do not appear), not fatal,
250
- * unless it is every relay: then {@link RelaysUnavailableError}.
251
- */
252
- export declare function listDelegatedContexts(s: DelegatedSession): Promise<Array<ListedContext & {
253
- relayUrl: string;
254
- }>>;
255
- type ListedNamespace = Awaited<ReturnType<MeroClient['admin']['listNamespaces']>>[number];
256
- /**
257
- * This account's namespaces on every relay it knows, each tagged with the relay
258
- * that listed it and recorded as that namespace's relay. Same rules as
259
- * {@link listDelegatedContexts}: each relay answers for itself and this caller
260
- * only (`namespace:list-own`); an unreachable relay is skipped, and every
261
- * relay unreachable is {@link RelaysUnavailableError}.
262
- */
263
- export declare function listDelegatedNamespaces(s: DelegatedSession): Promise<Array<ListedNamespace & {
264
- relayUrl: string;
265
- }>>;
266
- export {};