@calimero-network/mero-js 27.0.2 → 27.2.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.
@@ -11,7 +11,8 @@
11
11
  */
12
12
  import { type AttestationDoc, type CloudPolicy } from './attestation.js';
13
13
  import { type PasskeyProvider } from './passkey.js';
14
- export { AppError, ArgsTooLarge, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, NotFound, TrapError, UnavailableError, errorFrom, } from './errors.js';
14
+ import { type AppsReport, type ConsentRequest, type ConsentResult, type ConsentStarted, type OpsAppsReport, type OpsOrgsReport, type PageOptions, type PendingApprovals, type PublisherBindingInfo, type PublisherProof, type ReleaseRules } from './approvals.js';
15
+ export { AppError, ArgsTooLarge, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
15
16
  export type { Committed } from './errors.js';
16
17
  export interface CalimeroSession {
17
18
  accountId: string;
@@ -112,6 +113,23 @@ export interface OrgUpgradeTarget {
112
113
  cascade?: boolean;
113
114
  migrate?: string;
114
115
  }
116
+ /**
117
+ * `GET /orgs/{org}`: the organization as the caller sees it. Besides its
118
+ * members, teams and metadata, who may approve its releases, the package
119
+ * signing keys it publishes, and whether a passed release deadline has
120
+ * stopped new work on its own set.
121
+ */
122
+ export interface OrgInfo extends Record<string, unknown> {
123
+ /** Accounts holding `APPROVE_RELEASES` there (owner and admins included). */
124
+ releaseApprovers: string[];
125
+ /** The signer ids (`did:key`) bound to it as publisher. */
126
+ publishes: string[];
127
+ /** `null` unless a deadline stopped new work: approve `release` (or newer) to lift it. */
128
+ newWorkStopped: {
129
+ release: string;
130
+ after: string;
131
+ } | null;
132
+ }
115
133
  /**
116
134
  * How a call or a view is sent again while the node is unavailable (D70): with
117
135
  * the same key, after a backoff that starts at `baseDelayMs` and doubles up to
@@ -138,8 +156,15 @@ export declare function retryDelayMs(retry: number, options: {
138
156
  maxDelayMs: number;
139
157
  }): number;
140
158
  export interface CalimeroClientOptions {
141
- /** One node of the cluster. */
159
+ /** One node of the cluster: the one the client talks to first. */
142
160
  baseUrl: string;
161
+ /**
162
+ * Other nodes of the same cluster, in the order to move to them when a node
163
+ * answers `421 WRONG_NODE` (its release may not serve this account). Each is
164
+ * attested under the same `policy` before anything is sent to it. Without
165
+ * them a `421` reaches the caller as {@link WrongNode}.
166
+ */
167
+ nodes?: string[];
143
168
  policy: CloudPolicy;
144
169
  /** Fetch that carries attestation and envelopes. Defaults to global `fetch`. */
145
170
  fetch?: typeof fetch;
@@ -147,21 +172,70 @@ export interface CalimeroClientOptions {
147
172
  retry?: CloudRetryOptions;
148
173
  }
149
174
  export declare class CalimeroClient {
150
- private readonly base;
151
- private readonly sealed;
175
+ /** Every node this client may use, `baseUrl` first. */
176
+ private readonly nodeUrls;
177
+ /** The one in use: an index into {@link nodeUrls}. */
178
+ private at;
179
+ /** A sealed fetch per node, each attesting its own node on first use. */
180
+ private readonly channels;
181
+ private readonly policy;
152
182
  /** The unsealed fetch, for the one route served in the clear (`/attestation`). */
153
183
  private readonly raw;
154
184
  private auth;
155
185
  private current;
186
+ /** The refresh under way, which every request that needs one shares. */
187
+ private refreshing;
156
188
  private readonly retry;
157
189
  /** Rejections of the underlying `fetch` itself: the node could not be reached or dropped the connection. */
158
190
  private readonly networkFailures;
159
191
  constructor(options: CalimeroClientOptions);
192
+ /** The node requests go to now: `baseUrl`, until a `421 WRONG_NODE` moved the client on. */
193
+ get nodeUrl(): string;
194
+ /** The sealed channel to `url`, attested under the client's policy before its first request. */
195
+ private channel;
196
+ /**
197
+ * Move on from node `from` (the one that answered `421 WRONG_NODE`) to the
198
+ * next one. False when there is no other. A request that saw the same node
199
+ * refuse after another request moved on already does not move again.
200
+ */
201
+ private moveOn;
160
202
  /** Authenticate every request with an API key. */
161
203
  useApiKey(key: string): void;
162
204
  useSession(session: CalimeroSession): void;
163
205
  get session(): CalimeroSession | undefined;
206
+ /**
207
+ * Send one request to the node in use, with the credential held now, and
208
+ * answer the response, whatever its status. {@link send} judges it.
209
+ */
164
210
  private request;
211
+ /**
212
+ * {@link request}, answering what a lost issuing node or a node on the
213
+ * wrong release does to a session: a successful response, or the refusal as a
214
+ * typed error ({@link errorFrom}).
215
+ *
216
+ * - `401 UNAUTHENTICATED` to a request that carried a session: the node that
217
+ * signed the session may be gone, replaced, or out of reach of the node
218
+ * asked, which any node of the cluster answers with a refresh. The client
219
+ * refreshes once, shared by every request that needs it at the same time
220
+ * ({@link refresh}), and repeats the request once. A refresh that is
221
+ * refused surfaces as {@link LoginRequired}.
222
+ * - `421 WRONG_NODE`: this node's release may not serve the account. The
223
+ * client moves to the next node it knows and repeats the request once; with
224
+ * none, the {@link WrongNode} reaches the caller.
225
+ *
226
+ * Repeating is safe even for a request that is not idempotent: the node
227
+ * answers `401` and `421` when it judges the credential, before it reads what
228
+ * the request asks, so the first attempt ran nothing. The exceptions are the
229
+ * ceremonies that mint a session ({@link ceremony}), never repeated here.
230
+ */
231
+ private send;
232
+ /** Whether a `401` to a request sent with `token` is one a refresh may answer. */
233
+ private mayRefresh;
234
+ /**
235
+ * A fresh session in place of the one `token` belonged to: by the refresh
236
+ * another request already made, or by one this request starts.
237
+ */
238
+ private renew;
165
239
  private json;
166
240
  /**
167
241
  * {@link json} for a call or a view (D70): while the node answers 503 or
@@ -181,8 +255,14 @@ export declare class CalimeroClient {
181
255
  signUp(passkey: PasskeyProvider, name: string): Promise<CalimeroSession>;
182
256
  /** Sign in with an existing passkey. */
183
257
  signIn(passkey: PasskeyProvider): Promise<CalimeroSession>;
184
- /** Swap the refresh token for a new session. */
258
+ /**
259
+ * Swap the refresh token for a new session, on whichever node the client
260
+ * uses: any node of the cluster takes it. A refresh token is used once, so
261
+ * calls made while one refresh is under way share it rather than sending the
262
+ * same token again (which would revoke the session).
263
+ */
185
264
  refresh(): Promise<CalimeroSession>;
265
+ private exchangeRefresh;
186
266
  signOut(): Promise<void>;
187
267
  /** End every session of the signed-in account, on every device, this one too. */
188
268
  signOutEverywhere(): Promise<void>;
@@ -266,8 +346,11 @@ export declare class CalimeroClient {
266
346
  * Move an app to another release of its package (`{ version }`), another
267
347
  * package, or a bundle hash, running its migration. Answers the upgrade's
268
348
  * entry; a failed migration rejects with code `UPGRADE_FAILED` (422).
269
- * Where the owner holds app upgrades, the node asks for the owner's approval
270
- * (`412 CONFIRM_REQUIRED`), which `passkey` gives.
349
+ * Where owners hold app upgrades, the node asks for the approval of
350
+ * whoever the package's manifest names (`412 CONFIRM_REQUIRED`), which
351
+ * `passkey` gives. Refused (`403`) to a session opened on an app's
352
+ * frontend: an app asks its user with {@link requestConsent} instead. Past
353
+ * a release deadline its approver has not met: {@link ReleaseDeadlineError}.
271
354
  */
272
355
  upgradeApp(app: string, target: {
273
356
  version?: string;
@@ -346,7 +429,7 @@ export declare class CalimeroClient {
346
429
  deleteAlias(alias: string): Promise<void>;
347
430
  createOrg(name: string): Promise<string>;
348
431
  /** The organization, with the teams this caller reaches or manages. */
349
- getOrg(org: string): Promise<Record<string, unknown>>;
432
+ getOrg(org: string): Promise<OrgInfo>;
350
433
  /**
351
434
  * Delete an organization with its teams, invites and apps (its owner only).
352
435
  * The node asks for the owner's passkey (`412 CONFIRM_REQUIRED`), which `passkey` gives.
@@ -370,8 +453,11 @@ export declare class CalimeroClient {
370
453
  /**
371
454
  * Approve a mero-tee release to hold the apps of `org`, or of this account
372
455
  * outside any organization (`org` `null`); `approved: false` withdraws it.
373
- * Until then, nodes on that release get none of those apps' data. The owner
374
- * approves with `passkey`.
456
+ * Until then, nodes on that release get none of those apps' data. For an
457
+ * organization, a member holding `APPROVE_RELEASES` approves with `passkey`;
458
+ * the node binds the release's measurements itself. Refused (`403`) to a
459
+ * session opened on an app's frontend: an app asks its user with
460
+ * {@link requestConsent} instead.
375
461
  */
376
462
  approveRelease(org: string | null, version: string, passkey: PasskeyProvider, approved?: boolean): Promise<Record<string, unknown>>;
377
463
  /**
@@ -379,12 +465,95 @@ export declare class CalimeroClient {
379
465
  * `org`'s apps or this account's own (`org` `null`). `prefixes` `'manifest'`
380
466
  * (the default) approves every prefix the package's latest release declares;
381
467
  * `replace` sets exactly `prefixes`, which is how one is revoked. Approved
382
- * with `passkey`.
468
+ * with `passkey`. Refused (`403`) to a session opened on an app's frontend:
469
+ * an app asks with {@link requestConsent} (`kind: 'outbound'`) instead.
383
470
  */
384
471
  approveOutbound(org: string | null, pkg: string, passkey: PasskeyProvider, options?: {
385
472
  prefixes?: string[] | 'manifest';
386
473
  replace?: boolean;
387
474
  }): Promise<Record<string, unknown>>;
475
+ /**
476
+ * One page of the report of a scope's apps: an organization's (any member;
477
+ * `404` to anyone else) or, with `org` `null`, the account's own outside any
478
+ * organization (a session bound to an app's frontend: that package's apps).
479
+ * The governance, the scope's approved releases, the releases the cluster's
480
+ * nodes run and whether each is approved, and per app its release, newer
481
+ * releases it may move to, last upgrade and replicas.
482
+ */
483
+ appsReport(org: string | null, page?: PageOptions): Promise<AppsReport>;
484
+ /** {@link appsReport}, every page, with the apps of all of them in `apps`. */
485
+ appsReportAll(org: string | null): Promise<AppsReport>;
486
+ /** `GET /releases`: the cluster's governance and every release deadline. Public. */
487
+ releaseRules(): Promise<ReleaseRules>;
488
+ /**
489
+ * What waits for a decision in a scope (`org`, or the account's own apps
490
+ * with `null`): mero-tee releases the cluster's nodes run that the scope has
491
+ * not approved, apps with newer releases of their package, and whether a
492
+ * passed deadline stops new work. Read over the sealed channel; act on an
493
+ * item by sending the user to the consent page ({@link requestConsent},
494
+ * `releaseConsent`, `upgradeConsent`).
495
+ */
496
+ pendingApprovals(org?: string | null): Promise<PendingApprovals>;
497
+ /**
498
+ * Ask the node to ask the user: a release, upgrade or outbound approval
499
+ * that only the account holder may make, decided on the node's own consent
500
+ * page, never in the app. Needs a passkey session (a session bound to the
501
+ * app's frontend is fine). Send the person to the answer's `url`; the page
502
+ * sends them back to `returnTo?request_id=…&result=approved|declined`
503
+ * (`consentReturn`), and {@link consentResult} confirms the decision.
504
+ */
505
+ requestConsent(request: ConsentRequest): Promise<ConsentStarted>;
506
+ /**
507
+ * {@link requestConsent}, then send the browser to the consent page
508
+ * (`navigate`, by default `window.location.assign`). Answers the request
509
+ * once `navigate` returns: with a popup (`url => window.open(url)`), poll it
510
+ * with {@link waitForConsent}.
511
+ */
512
+ openConsentPage(request: ConsentRequest, navigate?: (url: string) => void): Promise<ConsentStarted>;
513
+ /** The decision on a consent request (any node answers, for the account that asked). */
514
+ consentResult(requestId: string): Promise<ConsentResult>;
515
+ /**
516
+ * Poll {@link consentResult} until the request is decided, every
517
+ * `intervalMs` (2 s), until `signal` aborts or `expiresAt` (Unix seconds)
518
+ * passes, when it answers the last `pending` result.
519
+ */
520
+ waitForConsent(requestId: string, options?: {
521
+ intervalMs?: number;
522
+ signal?: AbortSignal;
523
+ expiresAt?: number;
524
+ }): Promise<ConsentResult>;
525
+ /** `GET /publishers/{signerId}`: the cluster name and binding generation a key signs to bind it next. */
526
+ publisherBinding(signerId: string): Promise<PublisherBindingInfo>;
527
+ /**
528
+ * Bind a package signing key to `org`, which then approves the node images
529
+ * every instance of the key's packages runs on, and upgrades those whose
530
+ * package declares `upgrades: publisher`. `proof` is the key's signature
531
+ * over `publisherBindingMessage(cluster, org, signerId, generation)`; a
532
+ * member holding `APPROVE_RELEASES` sends it with `passkey`.
533
+ */
534
+ bindPublisher(org: string, proof: PublisherProof, passkey: PasskeyProvider): Promise<Record<string, unknown>>;
535
+ /**
536
+ * Upgrade every instance of `pkg` whose release declares `upgrades:
537
+ * publisher`, whoever owns it, as its publisher `org` (a
538
+ * `MANAGE_APPLICATION` holder there), with one passkey approval. Answers
539
+ * the run, kept on the publisher.
540
+ */
541
+ upgradePublished(org: string, pkg: string, target: {
542
+ version: string;
543
+ migrate?: string;
544
+ }, passkey: PasskeyProvider): Promise<UpgradeRun>;
545
+ /** `GET /ops/apps`: what every app runs, without names or accounts. Operator token (`useApiKey`). */
546
+ opsApps(page?: PageOptions): Promise<OpsAppsReport>;
547
+ /** `GET /ops/orgs`: each organization's approved releases. Operator token. */
548
+ opsOrgs(page?: PageOptions): Promise<OpsOrgsReport>;
549
+ /**
550
+ * Announce when work on images older than `version` ends: from `after` on,
551
+ * a scope that has not approved it (or newer) starts no new work. Operator
552
+ * token.
553
+ */
554
+ setReleaseDeadline(version: string, after: Date | string): Promise<void>;
555
+ /** Lift a release's deadline. Operator token. */
556
+ liftReleaseDeadline(version: string): Promise<void>;
388
557
  /** Add a member, or change their role. */
389
558
  setMember(org: string, account: string, role: 'guest' | 'member' | 'admin' | 'owner'): Promise<void>;
390
559
  removeMember(org: string, account: string): Promise<Record<string, unknown>>;