@calimero-network/mero-js 19.11.0 → 19.13.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.
@@ -1,5 +1,5 @@
1
1
  import { HttpClient } from '../http-client/index.js';
2
- import type { HealthStatus, AdminAuthStatus, InstallApplicationRequest, InstallApplicationResponseData, InstallDevApplicationRequest, UninstallApplicationResponseData, ListApplicationsResponseData, GetApplicationResponseData, ApplicationVersionEntry, GetApplicationAbiResponseData, GetLatestVersionResponseData, ListPackagesResponseData, ListVersionsResponseData, CreateContextRequest, CreateContextResponseData, DeleteContextResponseData, GetContextsResponseData, Context, GenerateContextIdentityResponseData, GetContextIdentitiesResponseData, JoinContextResponseData, JoinSubgroupInheritanceResponseData, ContextGroupResponseData, ContextStorageResponseData, InviteSpecializedNodeRequest, InviteSpecializedNodeResponseData, UpdateContextApplicationRequest, ResyncContextRequest, ResyncContextResponseData, ContextsWithExecutorsResponseData, UploadBlobRequest, UploadBlobResponseData, DeleteBlobResponseData, ListBlobsResponseData, GetBlobInfoResponseData, BlobReadOptions, CreateContextAliasRequest, CreateApplicationAliasRequest, CreateDeviceAliasRequest, CreateAliasResponseData, LookupAliasResponseData, DeleteAliasResponseData, ListAliasesResponseData, ListNamespacesResponseData, CreateNamespaceRequest, CreateNamespaceResponseData, DeleteNamespaceResponseData, CreateNamespaceInvitationRequest, CreateNamespaceInvitationResponseData, CreateRecursiveInvitationResponseData, JoinNamespaceRequest, JoinNamespaceResponseData, CreateGroupInNamespaceRequest, CreateGroupInNamespaceResponseData, SubgroupEntry, Namespace, NamespaceIdentity, NodeIdentity, AccountPairInitRequest, AccountPairInitResponseData, AccountPairCompleteRequest, AccountPairCompleteResponseData, RelinkDeviceRequest, RelinkDeviceResponseData, RescopeDeviceRequest, RescopeDeviceResponseData, AccountDeviceEntry, AccountApplicationEntry, RevokeAccountDeviceRequest, RevokeAccountDeviceResponseData, GroupInfoResponseData, CreateGroupRequest, CreateGroupResponseData, DeleteGroupResponseData, ListGroupMembersResponseData, ListGroupContextsResponseData, AddGroupMembersRequest, RemoveGroupMembersRequest, UpdateMemberRoleRequest, MemberCapabilities, SetMemberCapabilitiesRequest, SetDefaultCapabilitiesRequest, SetMemberAutoFollowRequest, SetSubgroupVisibilityRequest, SetTeeAdmissionPolicyRequest, GetTeeAdmissionPolicyResponseData, SetGroupMetadataRequest, SetMemberMetadataRequest, SetContextMetadataRequest, MetadataRecord, SyncGroupResponseData, UpgradeGroupRequest, UpgradeGroupResponseData, GroupUpgradeStatusResponseData, MigrationStatus, CascadeStatusEntry, RetryGroupUpgradeResponseData, ReparentGroupRequest, ReparentGroupResponseData, IssueOwnershipProofRequest, IssueNamespaceOwnershipProofRequest, IssueOwnershipProofResponseData, CreateGroupInvitationRequest, CreateGroupInvitationResponseData, CreateRecursiveGroupInvitationResponseData, JoinGroupRequest, JoinGroupResponseData, TeeInfoResponseData, TeeAttestRequest, TeeAttestResponseData, TeeVerifyQuoteRequest, TeeVerifyQuoteResponseData, PerformIntentRequest, PerformIntentResponseData, IntentRelayInfo, AdmitJoinRequest, AdmitJoinResponseData } from './admin-types.js';
2
+ import type { HealthStatus, AdminAuthStatus, InstallApplicationRequest, InstallApplicationResponseData, InstallDevApplicationRequest, UninstallApplicationResponseData, ListApplicationsResponseData, GetApplicationResponseData, ApplicationVersionEntry, GetApplicationAbiResponseData, GetLatestVersionResponseData, ListPackagesResponseData, ListVersionsResponseData, CreateContextRequest, CreateContextResponseData, DeleteContextResponseData, GetContextsResponseData, Context, GenerateContextIdentityResponseData, GetContextIdentitiesResponseData, JoinContextResponseData, JoinSubgroupInheritanceResponseData, ContextGroupResponseData, ContextStorageResponseData, InviteSpecializedNodeRequest, InviteSpecializedNodeResponseData, UpdateContextApplicationRequest, ResyncContextRequest, ResyncContextResponseData, ContextsWithExecutorsResponseData, UploadBlobRequest, UploadBlobResponseData, DeleteBlobResponseData, ListBlobsResponseData, GetBlobInfoResponseData, BlobReadOptions, CreateContextAliasRequest, CreateApplicationAliasRequest, CreateDeviceAliasRequest, CreateAliasResponseData, LookupAliasResponseData, DeleteAliasResponseData, ListAliasesResponseData, ListNamespacesResponseData, CreateNamespaceRequest, CreateNamespaceResponseData, DeleteNamespaceResponseData, CreateNamespaceInvitationRequest, CreateNamespaceInvitationResponseData, CreateRecursiveInvitationResponseData, JoinNamespaceRequest, JoinNamespaceResponseData, CreateGroupInNamespaceRequest, CreateGroupInNamespaceResponseData, SubgroupEntry, Namespace, NamespaceIdentity, NodeIdentity, AccountPairInitRequest, AccountPairInitResponseData, AccountPairCompleteRequest, AccountPairCompleteResponseData, RelinkDeviceRequest, RelinkDeviceResponseData, RescopeDeviceRequest, RescopeDeviceResponseData, LabelDeviceRequest, LabelDeviceResponseData, AccountDeviceEntry, AccountApplicationEntry, RevokeAccountDeviceRequest, RevokeAccountDeviceResponseData, GroupInfoResponseData, CreateGroupRequest, CreateGroupResponseData, DeleteGroupResponseData, ListGroupMembersResponseData, ListGroupContextsResponseData, AddGroupMembersRequest, RemoveGroupMembersRequest, UpdateMemberRoleRequest, MemberCapabilities, SetMemberCapabilitiesRequest, SetDefaultCapabilitiesRequest, SetMemberAutoFollowRequest, SetSubgroupVisibilityRequest, SetTeeAdmissionPolicyRequest, GetTeeAdmissionPolicyResponseData, SetGroupMetadataRequest, SetMemberMetadataRequest, SetContextMetadataRequest, MetadataRecord, SyncGroupResponseData, UpgradeGroupRequest, UpgradeGroupResponseData, GroupUpgradeStatusResponseData, MigrationStatus, CascadeStatusEntry, RetryGroupUpgradeResponseData, ReparentGroupRequest, ReparentGroupResponseData, IssueOwnershipProofRequest, IssueNamespaceOwnershipProofRequest, IssueOwnershipProofResponseData, CreateGroupInvitationRequest, CreateGroupInvitationResponseData, CreateRecursiveGroupInvitationResponseData, JoinGroupRequest, JoinGroupResponseData, TeeInfoResponseData, TeeAttestRequest, TeeAttestResponseData, TeeVerifyQuoteRequest, TeeVerifyQuoteResponseData, PerformIntentRequest, PerformIntentResponseData, IntentRelayInfo, AdmitJoinRequest, AdmitJoinResponseData } from './admin-types.js';
3
3
  /**
4
4
  * Compare two version strings by SemVer 2.0 precedence, ascending: negative if
5
5
  * `a < b`, positive if `a > b`, `0` if equal. Build metadata (`+...`) is stripped
@@ -280,6 +280,13 @@ export declare class AdminApiClient {
280
280
  * device need not be online.
281
281
  */
282
282
  rescopeAccountDevice(deviceId: string, request: RescopeDeviceRequest): Promise<RescopeDeviceResponseData>;
283
+ /**
284
+ * Name a device of this account, for a listing to render.
285
+ *
286
+ * Run on the node holding the account root to name any device; a paired
287
+ * node is accepted only for that device's own id.
288
+ */
289
+ labelAccountDevice(deviceId: string, request: LabelDeviceRequest): Promise<LabelDeviceResponseData>;
283
290
  /**
284
291
  * Every device of this account, as this node sees them - its own included.
285
292
  *
@@ -597,6 +597,15 @@ export class AdminApiClient {
597
597
  async rescopeAccountDevice(deviceId, request) {
598
598
  return unwrap(await this.httpClient.put(`/admin-api/account/devices/${encodeURIComponent(deviceId)}/scope`, request));
599
599
  }
600
+ /**
601
+ * Name a device of this account, for a listing to render.
602
+ *
603
+ * Run on the node holding the account root to name any device; a paired
604
+ * node is accepted only for that device's own id.
605
+ */
606
+ async labelAccountDevice(deviceId, request) {
607
+ return unwrap(await this.httpClient.put(`/admin-api/account/devices/${encodeURIComponent(deviceId)}/label`, request));
608
+ }
600
609
  /**
601
610
  * Every device of this account, as this node sees them - its own included.
602
611
  *
@@ -530,6 +530,18 @@ export interface NodeIdentity {
530
530
  * a root nor has paired with the id; absent on an old node.
531
531
  */
532
532
  accountNamespaceId?: string | null;
533
+ /**
534
+ * The account that withdrew this node's device, absent on a node no
535
+ * revocation has reached.
536
+ */
537
+ revokedFrom?: RevokedFromEntry;
538
+ }
539
+ /** Which account withdrew this node's device, and which device it was. */
540
+ export interface RevokedFromEntry {
541
+ /** The account the device spoke for, 64 hex characters. */
542
+ accountId: string;
543
+ /** The device that was withdrawn, 64 hex characters. */
544
+ deviceId: string;
533
545
  }
534
546
  /**
535
547
  * @deprecated Use {@link AdminApiClient.getNodeIdentity} instead. Nothing here
@@ -923,6 +935,33 @@ export interface AccountDeviceEntry {
923
935
  * hex characters each. Empty for a certified device not yet bound anywhere.
924
936
  */
925
937
  namespaces: string[];
938
+ /**
939
+ * The replicated name the account gave this device, absent while it has
940
+ * none. Every device of the account reads the same one.
941
+ */
942
+ label?: string;
943
+ }
944
+ /**
945
+ * Name a device of this account, for a listing to render.
946
+ *
947
+ * Run on the node holding the account root to name any device; a paired node
948
+ * is accepted only for that device's own id.
949
+ */
950
+ export interface LabelDeviceRequest {
951
+ /** Trimmed, non-empty, bounded and free of control characters. */
952
+ label: string;
953
+ }
954
+ /**
955
+ * The name that was published, and the epoch that orders it against a rename
956
+ * another device of the account made at the same time.
957
+ */
958
+ export interface LabelDeviceResponseData {
959
+ /** The account the device speaks for, 64 hex characters. */
960
+ accountId: string;
961
+ /** The device that was named, 64 hex characters. */
962
+ deviceId: string;
963
+ label: string;
964
+ labelEpoch: number;
926
965
  }
927
966
  /**
928
967
  * One application this account speaks in, derived from the namespaces this node
@@ -24,7 +24,7 @@
24
24
  * ever sent, and would be a throughput bottleneck on a path that has no reason
25
25
  * to involve it.
26
26
  */
27
- import { type RoutingChallenge, type RoutingCredential } from './routing-proof.js';
27
+ import { type DiscoveryChallenge, type RoutingChallenge, type RoutingCredential } from './routing-proof.js';
28
28
  export interface CloudClientConfig {
29
29
  cloudBaseUrl?: string;
30
30
  /**
@@ -116,6 +116,20 @@ export interface CloudRelay {
116
116
  * namespace id anyone holding an invitation already has, and answering both
117
117
  * things a keyholder needs from a node rather than one of them.
118
118
  */
119
+ /**
120
+ * One relay that serves an account, as the bootstrap read reports it.
121
+ *
122
+ * `fresh` and a null `relayUrl` are reported rather than filtered, because
123
+ * "your relay is down", "we have no address for it" and "you have no relay"
124
+ * need different actions from a caller and an empty list erases the
125
+ * distinction. A caller looking for somewhere to talk to wants
126
+ * `fresh && relayUrl`; one diagnosing wants the rest.
127
+ */
128
+ export interface CloudAccountRelay {
129
+ peerId: string;
130
+ relayUrl: string | null;
131
+ fresh: boolean;
132
+ }
119
133
  export interface CloudNamespaceNode {
120
134
  peerId: string;
121
135
  /**
@@ -438,6 +452,36 @@ export declare class CloudClient {
438
452
  * for a class of expiry bug that only shows up on a slow client.
439
453
  */
440
454
  private routingProof;
455
+ /**
456
+ * Ask the cloud for a challenge bound to one ACCOUNT.
457
+ *
458
+ * Public and unauthenticated like {@link getRoutingChallenge}: an account id
459
+ * is `H(genesis(root_pk))` and travels in the clear in every device-link op,
460
+ * so naming one here reveals nothing and authorises nothing. MDMA re-derives
461
+ * the account from the certificate and refuses a mismatch.
462
+ */
463
+ getAccountRelaysChallenge(accountId: string): Promise<DiscoveryChallenge>;
464
+ /**
465
+ * Which relays serve this account — the bootstrap, and nothing more.
466
+ *
467
+ * A device holding a certificate can resolve a namespace it can *name*
468
+ * ({@link getNamespaceRouting}) and can ask a node it can *reach* what its
469
+ * account belongs to. It starts with neither. This closes that one gap.
470
+ *
471
+ * **It does not return namespaces, deliberately.** The node answers that from
472
+ * the governance DAG, scoped to the calling account: log in at one of these
473
+ * relays with an account proof and read its namespace listing. A cloud-side
474
+ * copy would be a second source of truth built from rows that linger for an
475
+ * account which left a namespace.
476
+ *
477
+ * So the sequence is: this → log in at a relay → ask it what you are in.
478
+ *
479
+ * Proven with the **device certificate**, not a cloud session, so it needs a
480
+ * `routingCredential` and throws without one rather than returning an empty
481
+ * list — "you configured no credential" and "you have no relays" are
482
+ * different answers and must not look alike.
483
+ */
484
+ getAccountRelays(accountId: string): Promise<CloudAccountRelay[]>;
441
485
  /**
442
486
  * The node to send a signed join to, chosen from those an invitation names.
443
487
  *
@@ -288,6 +288,60 @@ export class CloudClient {
288
288
  const challenge = await this.getRoutingChallenge(namespaceId);
289
289
  return routingProofHeaders(challenge, this.routingCredential);
290
290
  }
291
+ /**
292
+ * Ask the cloud for a challenge bound to one ACCOUNT.
293
+ *
294
+ * Public and unauthenticated like {@link getRoutingChallenge}: an account id
295
+ * is `H(genesis(root_pk))` and travels in the clear in every device-link op,
296
+ * so naming one here reveals nothing and authorises nothing. MDMA re-derives
297
+ * the account from the certificate and refuses a mismatch.
298
+ */
299
+ async getAccountRelaysChallenge(accountId) {
300
+ const body = await this.request('GET', `/api/cloud/accounts/${encodeURIComponent(accountId)}/challenge`, {
301
+ anonymous: true,
302
+ });
303
+ return {
304
+ accountId: String(body.account_id ?? accountId),
305
+ nonce: String(body.nonce ?? ''),
306
+ expiresAtMs: Number(body.expires_at_ms ?? 0),
307
+ };
308
+ }
309
+ /**
310
+ * Which relays serve this account — the bootstrap, and nothing more.
311
+ *
312
+ * A device holding a certificate can resolve a namespace it can *name*
313
+ * ({@link getNamespaceRouting}) and can ask a node it can *reach* what its
314
+ * account belongs to. It starts with neither. This closes that one gap.
315
+ *
316
+ * **It does not return namespaces, deliberately.** The node answers that from
317
+ * the governance DAG, scoped to the calling account: log in at one of these
318
+ * relays with an account proof and read its namespace listing. A cloud-side
319
+ * copy would be a second source of truth built from rows that linger for an
320
+ * account which left a namespace.
321
+ *
322
+ * So the sequence is: this → log in at a relay → ask it what you are in.
323
+ *
324
+ * Proven with the **device certificate**, not a cloud session, so it needs a
325
+ * `routingCredential` and throws without one rather than returning an empty
326
+ * list — "you configured no credential" and "you have no relays" are
327
+ * different answers and must not look alike.
328
+ */
329
+ async getAccountRelays(accountId) {
330
+ if (!this.routingCredential) {
331
+ throw new Error('getAccountRelays needs a routingCredential: construct the client with the device ' +
332
+ 'certificate and its signing secret, since this read is proven by the device, not by a session.');
333
+ }
334
+ const challenge = await this.getAccountRelaysChallenge(accountId);
335
+ const body = await this.request('GET', `/api/cloud/accounts/${encodeURIComponent(accountId)}/relays`, {
336
+ anonymous: true,
337
+ headers: await routingProofHeaders(challenge, this.routingCredential),
338
+ });
339
+ return (body.relays ?? []).map((row) => ({
340
+ peerId: String(row.peer_id ?? ''),
341
+ relayUrl: row.relay_url ?? null,
342
+ fresh: row.fresh === true,
343
+ }));
344
+ }
291
345
  /**
292
346
  * The node to send a signed join to, chosen from those an invitation names.
293
347
  *
@@ -1,6 +1,6 @@
1
1
  export { CloudClient } from './cloud-client.js';
2
- export type { CloudClientConfig, CloudSession, CloudNamespace, CloudRelay, CloudNamespaceNode, CloudNamespaceRouting, CloudMachine, CloudMachineNamespace, CloudLinkedAccount, CloudLinkedAccounts, CloudAccountLink, AccountLinkChallenge, AccountLinkProof, AccountLinkHandoff, AccountLinkCallback, AccountLoginChallenge, AccountLoginProof, EnableHAOptions, DisableHAOptions, } from './cloud-client.js';
2
+ export type { CloudClientConfig, CloudSession, CloudNamespace, CloudRelay, CloudAccountRelay, CloudNamespaceNode, CloudNamespaceRouting, CloudMachine, CloudMachineNamespace, CloudLinkedAccount, CloudLinkedAccounts, CloudAccountLink, AccountLinkChallenge, AccountLinkProof, AccountLinkHandoff, AccountLinkCallback, AccountLoginChallenge, AccountLoginProof, EnableHAOptions, DisableHAOptions, } from './cloud-client.js';
3
3
  export { connectCloud } from './connect.js';
4
4
  export type { ConnectCloudOptions, CloudConnection } from './connect.js';
5
5
  export { signRoutingChallenge, routingProofHeaders } from './routing-proof.js';
6
- export type { RoutingCredential, RoutingChallenge, RoutingProofHeaders, } from './routing-proof.js';
6
+ export type { RoutingCredential, DiscoveryChallenge, RoutingChallenge, RoutingProofHeaders, } from './routing-proof.js';
@@ -61,6 +61,23 @@ export interface RoutingChallenge {
61
61
  /** Epoch **milliseconds**, as MDMA reports it. Nonces live about two minutes. */
62
62
  expiresAtMs: number;
63
63
  }
64
+ /**
65
+ * A challenge bound to an ACCOUNT rather than a namespace, for asking which
66
+ * relays serve that account.
67
+ *
68
+ * Separate from {@link RoutingChallenge} because the binding is the security
69
+ * property, not a field name: MDMA seals the account into this one and the
70
+ * namespace into that one, and refuses each at the other's route. Keeping the
71
+ * TypeScript types distinct is what stops a caller passing the wrong one and
72
+ * learning about it as a 403.
73
+ */
74
+ export interface DiscoveryChallenge {
75
+ accountId: string;
76
+ /** Opaque and sealed — the client neither parses nor constructs this. */
77
+ nonce: string;
78
+ /** Epoch **milliseconds**, as MDMA reports it. Nonces live about two minutes. */
79
+ expiresAtMs: number;
80
+ }
64
81
  /**
65
82
  * The three header values a proven routing read carries.
66
83
  *
@@ -88,5 +105,9 @@ export declare function signRoutingChallenge(nonce: string, deviceSecret: string
88
105
  * Separated from the request so a caller that fetches its own challenge — a
89
106
  * test, or a client batching several reads against one nonce — can use the same
90
107
  * encoding rather than a second spelling of it.
108
+ *
109
+ * Takes either challenge kind: only the nonce is signed and both are signed
110
+ * over the same domain, so one encoder serves both. What separates them is what
111
+ * MDMA sealed inside, which is checked server-side.
91
112
  */
92
- export declare function routingProofHeaders(challenge: RoutingChallenge, credential: RoutingCredential): Promise<RoutingProofHeaders>;
113
+ export declare function routingProofHeaders(challenge: RoutingChallenge | DiscoveryChallenge, credential: RoutingCredential): Promise<RoutingProofHeaders>;
@@ -69,6 +69,10 @@ export async function signRoutingChallenge(nonce, deviceSecret) {
69
69
  * Separated from the request so a caller that fetches its own challenge — a
70
70
  * test, or a client batching several reads against one nonce — can use the same
71
71
  * encoding rather than a second spelling of it.
72
+ *
73
+ * Takes either challenge kind: only the nonce is signed and both are signed
74
+ * over the same domain, so one encoder serves both. What separates them is what
75
+ * MDMA sealed inside, which is checked server-side.
72
76
  */
73
77
  export async function routingProofHeaders(challenge, credential) {
74
78
  return {