@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.
- 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,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 {};
|