@calimero-network/mero-js 29.4.0 → 29.6.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 CHANGED
@@ -98,7 +98,8 @@ Pinned measurements go through `policy.verifyQuote` (`createQuoteVerifier`).
98
98
  ## The API
99
99
 
100
100
  - **Credentials**: `signUp` (with an `invite`, on a cluster that takes new accounts by invite; `signUpPolicy` reads which), `signIn`, `refresh`, `signOut`, `signOutEverywhere`, `useSession`, `useApiKey`, `me`, `usage`, `addPasskey`, `labelPasskey`, `removePasskey`, `createApiKey`, `listApiKeys`, `revokeApiKey`. Adding or removing a passkey and creating an API key are approved with a passkey the account already has.
101
- - **Package origins**: `packageOrigins.list`, `packageOrigins.add`, `packageOrigins.remove`. A package's managers register the web origins of a frontend they host themselves (approved with a passkey); `signUp` and `signIn` on such a page send its origin, sign for that origin's relying-party id, and open a session bound to the package (`me()` answers its `app` and `origin`). An unregistered origin is refused with `OriginNotRegistered`.
101
+ - **Account recovery** (core #4844): `createRecoveryCode`, `removeRecoveryCode` (passkey-approved; the code is shown once), then signed out `startAccountRecovery(code, passkey)` and, 72 hours later, `finishAccountRecovery(passkey, accountId?)`; a passkey the account still has cancels in between (`cancelAccountRecovery`; `me()` shows `recoveryCode` and `recoveryPending`).
102
+ - **Package origins**: `packageOrigins.list`, `packageOrigins.add`, `packageOrigins.remove`. A package's managers register the web origins of a frontend they host themselves (approved with a passkey); `signUp` and `signIn` on such a page send its origin, sign for that origin's relying-party id, and open a session bound to the package (`me()` answers its `app` and `origin`). An unregistered origin is refused with `OriginNotRegistered`. `packageOrigins.add(pkg, origin, passkey, { served: true })` registers a custom domain: the cluster also serves the package's frontend there from its signed bundle (`https://host`, no port; the passkey's consent names `served: true`); `list` answers `served` on each origin, and the extra refusals are `NotServing` (the cluster serves no frontends), `ClusterNotUpgraded` (a node is on an older release), `QuotaExceeded` (4 served per package, 64 per cluster) and `OriginTaken` (held with the other `served`).
102
103
  - **Packages**: `installPackage`, `uploadPackage`, `packages`, `package`, `uninstallPackage`; `uploadBundle` (raw WASM, development clusters only).
103
104
  - **Apps**: `createApp`, `listApps`, `getApp`, `deleteApp`, `setAccess`, `setAppMetadata`, `appStorage`, `appAbi`, `upgradeApp`, `transferApp`.
104
105
  - **Calls**: `call`, `callConfirmed`, `view`, `events`. A call always carries an idempotency key, and is sent again with it while the node answers `503`, then to the next node.
@@ -106,7 +107,7 @@ Pinned measurements go through `policy.verifyQuote` (`createQuoteVerifier`).
106
107
  - **Organizations**: `createOrg`, `getOrg`, `deleteOrg`, `setOrgMetadata`, `setMember`, `removeMember`, `setMemberMetadata`, `setCapabilities` (`ORG_CAPABILITIES`), teams, `invite`, `redeemInvite`, recovery (`setRecoveryKey`, `startRecovery`, `cancelRecovery`, `finishRecovery`; `newRecoverySeed`, `recoveryPublicKey`). Setting or removing a member or team member, capabilities and invites are approved with the caller's passkey; redeeming an invite is not.
107
108
  - **Owner decisions** (passkey approval, never from an app's frontend): `upgradeOrg`, `upgradeRun`, `retryUpgradeRun`, `approveRelease`, `approveOutbound`, `bindPublisher`, `upgradePublished`.
108
109
  - **Approvals from an app**: `appsReport`, `releaseRules`, `pendingApprovals`, then `requestConsent` / `openConsentPage` to send the user to the node's consent page, and `consentResult`. Show the answer's `code` to the user before sending them to `url` (`openConsentPage`'s `confirm` hook); they should see the same code on the page (`confirmationCode(requestId)` checks it). See [Ask your users to approve node images and app upgrades](https://calimero-network.github.io/mero-js/guides/user-approvals/).
109
- - **Operator** (ops token): `opsApps`, `opsOrgs`, `setReleaseDeadline`, `liftReleaseDeadline`.
110
+ - **Operator** (ops token): `opsApps`, `opsOrgs`, `opsServedOrigins` (every custom domain the log holds, by package label), `setReleaseDeadline`, `liftReleaseDeadline`.
110
111
  - **Any route**: `get`, `post`, `put`, `delete`; `postConfirmed` and `deleteConfirmed` answer a `412 CONFIRM_REQUIRED` with a passkey.
111
112
 
112
113
  Refusals are typed (`LoginRequired`, `WrongNode`, `NotFound`, `ConfirmRequired`, `ConfirmCancelled`,
@@ -12,7 +12,7 @@
12
12
  import { type AttestationDoc, type CloudPolicy } from './attestation.js';
13
13
  import { type PasskeyProvider } from './passkey.js';
14
14
  import { type AppsReport, type ConsentRequest, type ConsentResult, type ConsentStarted, type OpenConsentOptions, type OpsAppsReport, type OpsOrgsReport, type PageOptions, type PendingApprovals, type PublisherBindingInfo, type PublisherProof, type ReleaseRules } from './approvals.js';
15
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
15
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
16
16
  export type { Committed, SignUpRefusal } from './errors.js';
17
17
  export interface CalimeroSession {
18
18
  accountId: string;
@@ -302,6 +302,25 @@ export interface MeInfo extends Record<string, unknown> {
302
302
  app?: string | null;
303
303
  /** The web origin the session was opened on, when the node recorded one. */
304
304
  origin?: string | null;
305
+ /** Whether the account holds a recovery code ({@link CalimeroClient.createRecoveryCode}); never the code. */
306
+ recoveryCode?: boolean;
307
+ /**
308
+ * A recovery of this account under way: someone holding its recovery code
309
+ * started it with a new passkey. Warn on every device; a passkey the
310
+ * account has cancels it ({@link CalimeroClient.cancelAccountRecovery})
311
+ * until `readyAt`.
312
+ */
313
+ recoveryPending?: AccountRecovery | null;
314
+ }
315
+ /** A recovery of an account under way (core #4844, `docs/specs/account-recovery.md`). */
316
+ export interface AccountRecovery {
317
+ accountId: string;
318
+ /** The new passkey the recovery adds when it finishes. */
319
+ credentialId: string;
320
+ /** When it started (Unix seconds). */
321
+ startedAt: number;
322
+ /** The earliest it may finish (Unix seconds): 72 hours after the start. */
323
+ readyAt: number;
305
324
  }
306
325
  /** One web origin registered for a package (`GET /package-origins/{package}`). */
307
326
  export interface PackageOrigin {
@@ -312,6 +331,12 @@ export interface PackageOrigin {
312
331
  /** The account that registered it. */
313
332
  addedBy: string;
314
333
  addedAt: string | number;
334
+ /**
335
+ * The cluster serves the package's frontend on this origin too (a custom
336
+ * domain, from its signed bundle). Absent (`false`) from a node that
337
+ * predates served origins.
338
+ */
339
+ served?: boolean;
315
340
  }
316
341
  /** What registering an origin answers. */
317
342
  export interface PackageOriginAdded extends PackageOrigin {
@@ -337,9 +362,21 @@ export interface PackageOrigins {
337
362
  * caller as {@link ConfirmRequired}). `rpId` defaults to the node's choice
338
363
  * for the origin (its host). Refusals: {@link BadOrigin} (`BAD_ORIGIN`,
339
364
  * `BAD_RP_ID`), {@link OriginTaken}, {@link QuotaExceeded}.
365
+ *
366
+ * `served: true` makes it a custom domain: the cluster also serves the
367
+ * package's frontend on it (`https://host`, no port; never a name under the
368
+ * apps domain or the cluster's rp-id), and the consent the passkey signs
369
+ * names it (`{ origin, rpId, served: true }`). Further refusals:
370
+ * {@link NotServing} (the cluster serves no frontends),
371
+ * {@link ClusterNotUpgraded} (a node of the cluster is on an older release),
372
+ * {@link QuotaExceeded} for the served limits (4 per package, 64 per
373
+ * cluster), {@link OriginTaken} when the origin is held with the other
374
+ * `served` (remove it first). Serving it to browsers also needs the
375
+ * certificate and route Calimero Cloud sets up for the domain.
340
376
  */
341
377
  add(pkg: string, origin: string, passkey?: PasskeyProvider, options?: {
342
378
  rpId?: string;
379
+ served?: boolean;
343
380
  }): Promise<PackageOriginAdded>;
344
381
  /**
345
382
  * Remove `origin` from `pkg`, approved with `passkey` (`__remove_origin`).
@@ -347,6 +384,22 @@ export interface PackageOrigins {
347
384
  */
348
385
  remove(pkg: string, origin: string, passkey?: PasskeyProvider): Promise<PackageOriginRemoved>;
349
386
  }
387
+ /** One served origin (custom domain) of the cluster, as `GET /ops/served-origins` lists it. */
388
+ export interface ServedOrigin {
389
+ /** `https://host`. */
390
+ origin: string;
391
+ /** The origin's host, the name a certificate and a route are for. */
392
+ host: string;
393
+ /** The package's app-host label (`{label}.{apps domain}`); the listing names no package. */
394
+ label: string;
395
+ addedAt: string;
396
+ }
397
+ /** `GET /ops/served-origins`: every served origin the cluster's log holds. Operator token. */
398
+ export interface ServedOrigins {
399
+ /** This node had caught up with the system leader. */
400
+ fresh: boolean;
401
+ origins: ServedOrigin[];
402
+ }
350
403
  /**
351
404
  * Where a sign-up or sign-in says it runs. By default the page's origin
352
405
  * (`window.location.origin`) when it is not the origin of a node the client
@@ -610,6 +663,41 @@ export declare class CalimeroClient {
610
663
  removePasskey(credentialId: string, passkey: PasskeyProvider): Promise<void>;
611
664
  /** Name one of the account's passkeys (a device label); `''` clears the name. */
612
665
  labelPasskey(credentialId: string, label: string): Promise<void>;
666
+ /**
667
+ * Make the account's recovery code (replacing any it had), approved with
668
+ * `passkey` (`412 CONFIRM_REQUIRED`). Answers the code, once: show it to
669
+ * the person to keep offline; the node keeps only its public key. A passkey
670
+ * session on the cluster's own origin only (never an API key or a
671
+ * package-bound session).
672
+ */
673
+ createRecoveryCode(passkey: PasskeyProvider): Promise<string>;
674
+ /** Remove the account's recovery code, approved with `passkey`. */
675
+ removeRecoveryCode(passkey: PasskeyProvider): Promise<void>;
676
+ /**
677
+ * Signed out: start recovering the account `code` names. `passkey` makes a
678
+ * new passkey for it (the browser's create prompt, user verified); the
679
+ * node spends the code and keeps the passkey as pending. Nothing is added
680
+ * and no session opens: finish with {@link finishAccountRecovery} at
681
+ * `readyAt`. A code the cluster does not hold (mistyped, replaced or
682
+ * spent) is {@link WrongRecoveryCode}; an account with a recovery already
683
+ * under way `409 RECOVERY_PENDING`.
684
+ */
685
+ startAccountRecovery(code: string, passkey: PasskeyProvider): Promise<AccountRecovery>;
686
+ /**
687
+ * Cancel the recovery of the signed-in account under way, approved with
688
+ * `passkey` (one the account already has). The code stays spent: make a
689
+ * new one.
690
+ */
691
+ cancelAccountRecovery(passkey: PasskeyProvider): Promise<void>;
692
+ /**
693
+ * Signed out, once the delay passed: finish the recovery with the passkey
694
+ * it registered (`passkey` signs the node's sign-in challenge). The passkey
695
+ * joins the account, every other session of the account ends, and this
696
+ * client is signed in. `accountId` (from the start) lets the node refuse
697
+ * early and offer only that passkey; before `readyAt` it is
698
+ * {@link RecoveryNotReady}, with no recovery under way {@link NotFound}.
699
+ */
700
+ finishAccountRecovery(passkey: PasskeyProvider, accountId?: string): Promise<CalimeroSession>;
613
701
  /** What this account uses (apps, bundle bytes, API keys, owned orgs), with the cluster's quotas. */
614
702
  usage(): Promise<Record<string, unknown>>;
615
703
  /**
@@ -981,6 +1069,12 @@ export declare class CalimeroClient {
981
1069
  opsApps(page?: PageOptions): Promise<OpsAppsReport>;
982
1070
  /** `GET /ops/orgs`: each organization's approved releases. Operator token. */
983
1071
  opsOrgs(page?: PageOptions): Promise<OpsOrgsReport>;
1072
+ /**
1073
+ * `GET /ops/served-origins`: every served origin (custom domain) the
1074
+ * cluster's log holds, with its package's label and no package name or
1075
+ * account. Operator token.
1076
+ */
1077
+ opsServedOrigins(): Promise<ServedOrigins>;
984
1078
  /**
985
1079
  * Announce when work on images older than `version` ends: from `after` on,
986
1080
  * a scope that has not approved it (or newer) starts no new work. Operator
@@ -12,11 +12,11 @@
12
12
  import { SealedTransportError, createSealedFetch } from '../sealed/sealed.js';
13
13
  import { fetchAttestedKey } from './attestation.js';
14
14
  import { DEFAULT_DIRECTORY, orderNodes, readDirectory } from './directory.js';
15
- import { CalimeroError, ConfirmCancelled, ConfirmRequired, LoginRequired, OriginNotRegistered, UnavailableError, WrongNode, errorFrom, } from './errors.js';
15
+ import { CalimeroError, ConfirmCancelled, ConfirmRequired, LoginRequired, OriginNotRegistered, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
16
16
  import { b64url, fromB64url } from './passkey.js';
17
17
  import { signRecovery } from './recovery.js';
18
18
  import { confirmationCode, pageQuery, pendingApprovalsFrom, } from './approvals.js';
19
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
19
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
20
20
  /** A deployer's key, as `GET /deployers` lists it and `DELETE /deployers/{key}` takes it. */
21
21
  export function deployerKey(deployer) {
22
22
  return 'account' in deployer ? `account:${deployer.account}` : `org:${deployer.org}`;
@@ -151,8 +151,10 @@ export function retryDelayMs(retry, options) {
151
151
  * before it spends the challenge or the refresh token (core #4774).
152
152
  */
153
153
  function ceremony(path) {
154
- return path === 'auth/refresh' || path.startsWith('auth/passkey/');
154
+ return path === 'auth/refresh' || path.startsWith('auth/passkey/') || RECOVERY_CEREMONIES.has(path);
155
155
  }
156
+ /** The signed-out halves of an account recovery: a `401` there is the code's, not a session's. */
157
+ const RECOVERY_CEREMONIES = new Set(['auth/recovery/begin', 'auth/recovery/start', 'auth/recovery/finish']);
156
158
  export class CalimeroClient {
157
159
  /**
158
160
  * A client for the Calimero Cloud cluster named `cluster`: its node list read
@@ -198,7 +200,14 @@ export class CalimeroClient {
198
200
  const { origins } = await this.get(`package-origins/${seg(pkg)}`);
199
201
  return origins ?? [];
200
202
  },
201
- add: (pkg, origin, passkey, options = {}) => this.confirmed('POST', `package-origins/${seg(pkg)}`, options.rpId === undefined ? { origin } : { origin, rpId: options.rpId }, passkey),
203
+ add: (pkg, origin, passkey, options = {}) => this.confirmed('POST', `package-origins/${seg(pkg)}`,
204
+ // Only the fields set: the node refuses unknown ones, and one that
205
+ // predates served origins takes a plain add unchanged.
206
+ {
207
+ origin,
208
+ ...(options.rpId === undefined ? {} : { rpId: options.rpId }),
209
+ ...(options.served ? { served: true } : {}),
210
+ }, passkey),
202
211
  remove: (pkg, origin, passkey) => this.confirmed('DELETE', `package-origins/${seg(pkg)}`, { origin }, passkey),
203
212
  };
204
213
  const trim = (url) => url.replace(/\/+$/, '');
@@ -608,6 +617,69 @@ export class CalimeroClient {
608
617
  async labelPasskey(credentialId, label) {
609
618
  await this.post(`auth/passkeys/${seg(credentialId)}`, { label });
610
619
  }
620
+ // ---------------------------------------------------------------- account recovery
621
+ //
622
+ // core #4844 (`docs/specs/account-recovery.md`): a recovery code, made with
623
+ // a passkey and shown once, lets someone who lost every passkey register a
624
+ // new one; it is added, and the account signed in, 72 hours later unless a
625
+ // passkey the account still has cancels it first.
626
+ /**
627
+ * Make the account's recovery code (replacing any it had), approved with
628
+ * `passkey` (`412 CONFIRM_REQUIRED`). Answers the code, once: show it to
629
+ * the person to keep offline; the node keeps only its public key. A passkey
630
+ * session on the cluster's own origin only (never an API key or a
631
+ * package-bound session).
632
+ */
633
+ async createRecoveryCode(passkey) {
634
+ const { code } = await this.postConfirmed('auth/recovery', {}, passkey);
635
+ return code;
636
+ }
637
+ /** Remove the account's recovery code, approved with `passkey`. */
638
+ async removeRecoveryCode(passkey) {
639
+ await this.deleteConfirmed('auth/recovery', passkey);
640
+ }
641
+ /**
642
+ * Signed out: start recovering the account `code` names. `passkey` makes a
643
+ * new passkey for it (the browser's create prompt, user verified); the
644
+ * node spends the code and keeps the passkey as pending. Nothing is added
645
+ * and no session opens: finish with {@link finishAccountRecovery} at
646
+ * `readyAt`. A code the cluster does not hold (mistyped, replaced or
647
+ * spent) is {@link WrongRecoveryCode}; an account with a recovery already
648
+ * under way `409 RECOVERY_PENDING`.
649
+ */
650
+ async startAccountRecovery(code, passkey) {
651
+ try {
652
+ const begin = await this.post('auth/recovery/begin', { code });
653
+ const registration = await passkey.create(begin.options);
654
+ return await this.post('auth/recovery/start', { challengeId: begin.challengeId, code, registration });
655
+ }
656
+ catch (error) {
657
+ if (error instanceof LoginRequired)
658
+ throw new WrongRecoveryCode(error.message.replace(/^401 UNAUTHENTICATED: /, ''), error.details);
659
+ throw error;
660
+ }
661
+ }
662
+ /**
663
+ * Cancel the recovery of the signed-in account under way, approved with
664
+ * `passkey` (one the account already has). The code stays spent: make a
665
+ * new one.
666
+ */
667
+ async cancelAccountRecovery(passkey) {
668
+ await this.postConfirmed('auth/recovery/cancel', {}, passkey);
669
+ }
670
+ /**
671
+ * Signed out, once the delay passed: finish the recovery with the passkey
672
+ * it registered (`passkey` signs the node's sign-in challenge). The passkey
673
+ * joins the account, every other session of the account ends, and this
674
+ * client is signed in. `accountId` (from the start) lets the node refuse
675
+ * early and offer only that passkey; before `readyAt` it is
676
+ * {@link RecoveryNotReady}, with no recovery under way {@link NotFound}.
677
+ */
678
+ async finishAccountRecovery(passkey, accountId) {
679
+ const session = await this.postConfirmed('auth/recovery/finish', accountId ? { accountId } : {}, passkey);
680
+ this.useSession(session);
681
+ return session;
682
+ }
611
683
  /** What this account uses (apps, bundle bytes, API keys, owned orgs), with the cluster's quotas. */
612
684
  usage() {
613
685
  return this.get('auth/usage');
@@ -1176,6 +1248,14 @@ export class CalimeroClient {
1176
1248
  opsOrgs(page = {}) {
1177
1249
  return this.get(`ops/orgs${pageQuery(page)}`);
1178
1250
  }
1251
+ /**
1252
+ * `GET /ops/served-origins`: every served origin (custom domain) the
1253
+ * cluster's log holds, with its package's label and no package name or
1254
+ * account. Operator token.
1255
+ */
1256
+ opsServedOrigins() {
1257
+ return this.get('ops/served-origins');
1258
+ }
1179
1259
  /**
1180
1260
  * Announce when work on images older than `version` ends: from `after` on,
1181
1261
  * a scope that has not approved it (or newer) starts no new work. Operator
@@ -117,15 +117,58 @@ export declare class OriginNotRegistered extends CalimeroError {
117
117
  * `400 BAD_ORIGIN` or `400 BAD_RP_ID`: the node does not accept the origin a
118
118
  * package's manager tried to register (it must be a bare web origin,
119
119
  * `scheme://host[:port]`), or the relying-party id given for it (which must
120
- * fit the origin's host). `code` says which.
120
+ * fit the origin's host). `code` says which. A served origin (`served: true`)
121
+ * must also be `https://host` with no port, and not a name under the
122
+ * cluster's apps domain or its rp-id.
121
123
  */
122
124
  export declare class BadOrigin extends CalimeroError {
123
125
  constructor(code: 'BAD_ORIGIN' | 'BAD_RP_ID', message: string, details?: Record<string, unknown>);
124
126
  }
125
- /** `409 ORIGIN_TAKEN`: the origin is registered already (for this package or another). */
127
+ /**
128
+ * `409 ORIGIN_TAKEN`: the origin is registered already (for another package,
129
+ * or for this one with the other `served`: remove it first), or it is one of
130
+ * the cluster's own (a node's API host, a cluster origin, a frontend it
131
+ * serves).
132
+ */
126
133
  export declare class OriginTaken extends CalimeroError {
127
134
  constructor(message: string, details?: Record<string, unknown>);
128
135
  }
136
+ /**
137
+ * `400 NOT_SERVING`: a served origin (a custom domain, `served: true`) was
138
+ * asked of a cluster that serves no frontends (merod without
139
+ * `--apps-domain`). Register the origin unserved, or use a cluster that
140
+ * serves them.
141
+ */
142
+ export declare class NotServing extends CalimeroError {
143
+ constructor(message: string, details?: Record<string, unknown>);
144
+ }
145
+ /**
146
+ * `409 CLUSTER_NOT_UPGRADED`: the change needs every node of the cluster
147
+ * (replicas, voters and learners) on the node's release or later, and one
148
+ * is not yet; for instance adding a served origin. Retry once the cluster is
149
+ * upgraded.
150
+ */
151
+ export declare class ClusterNotUpgraded extends CalimeroError {
152
+ constructor(message: string, details?: Record<string, unknown>);
153
+ }
154
+ /**
155
+ * `409 RECOVERY_NOT_READY`: a recovery (an account's or an organization's)
156
+ * may finish only once its 72-hour delay has passed. `readyAt` is when it may
157
+ * (Unix seconds), when the node said.
158
+ */
159
+ export declare class RecoveryNotReady extends CalimeroError {
160
+ readonly readyAt: number | undefined;
161
+ constructor(message: string, details?: Record<string, unknown>);
162
+ }
163
+ /**
164
+ * The recovery code is not one the cluster holds: mistyped, replaced by a
165
+ * newer one, or spent by a recovery that started (whether it finished or was
166
+ * cancelled). The node answers it `401`; a recovery is signed out, so it is
167
+ * this error, not {@link LoginRequired}.
168
+ */
169
+ export declare class WrongRecoveryCode extends CalimeroError {
170
+ constructor(message: string, details?: Record<string, unknown>);
171
+ }
129
172
  /** Why a sign-up was refused ({@link SignUpRefused.reason}), read from what the node said. */
130
173
  export type SignUpRefusal = 'invite-only' | 'closed' | 'unknown-invite' | 'expired-invite' | 'org-gone' | 'other';
131
174
  /**
@@ -146,7 +189,10 @@ export declare class SignUpRefused extends CalimeroError {
146
189
  readonly reason: SignUpRefusal;
147
190
  constructor(message: string, details?: Record<string, unknown>);
148
191
  }
149
- /** `429 QUOTA_EXCEEDED`: the caller holds as many of something (origins, blobs, ...) as it may. */
192
+ /**
193
+ * `429 QUOTA_EXCEEDED`: the caller holds as many of something (origins,
194
+ * served origins per package or per cluster, blobs, ...) as it may.
195
+ */
150
196
  export declare class QuotaExceeded extends CalimeroError {
151
197
  constructor(message: string, details?: Record<string, unknown>);
152
198
  }
@@ -150,7 +150,9 @@ export class OriginNotRegistered extends CalimeroError {
150
150
  * `400 BAD_ORIGIN` or `400 BAD_RP_ID`: the node does not accept the origin a
151
151
  * package's manager tried to register (it must be a bare web origin,
152
152
  * `scheme://host[:port]`), or the relying-party id given for it (which must
153
- * fit the origin's host). `code` says which.
153
+ * fit the origin's host). `code` says which. A served origin (`served: true`)
154
+ * must also be `https://host` with no port, and not a name under the
155
+ * cluster's apps domain or its rp-id.
154
156
  */
155
157
  export class BadOrigin extends CalimeroError {
156
158
  constructor(code, message, details = {}) {
@@ -158,13 +160,66 @@ export class BadOrigin extends CalimeroError {
158
160
  this.name = 'BadOrigin';
159
161
  }
160
162
  }
161
- /** `409 ORIGIN_TAKEN`: the origin is registered already (for this package or another). */
163
+ /**
164
+ * `409 ORIGIN_TAKEN`: the origin is registered already (for another package,
165
+ * or for this one with the other `served`: remove it first), or it is one of
166
+ * the cluster's own (a node's API host, a cluster origin, a frontend it
167
+ * serves).
168
+ */
162
169
  export class OriginTaken extends CalimeroError {
163
170
  constructor(message, details = {}) {
164
171
  super(409, 'ORIGIN_TAKEN', message, details);
165
172
  this.name = 'OriginTaken';
166
173
  }
167
174
  }
175
+ /**
176
+ * `400 NOT_SERVING`: a served origin (a custom domain, `served: true`) was
177
+ * asked of a cluster that serves no frontends (merod without
178
+ * `--apps-domain`). Register the origin unserved, or use a cluster that
179
+ * serves them.
180
+ */
181
+ export class NotServing extends CalimeroError {
182
+ constructor(message, details = {}) {
183
+ super(400, 'NOT_SERVING', message, details);
184
+ this.name = 'NotServing';
185
+ }
186
+ }
187
+ /**
188
+ * `409 CLUSTER_NOT_UPGRADED`: the change needs every node of the cluster
189
+ * (replicas, voters and learners) on the node's release or later, and one
190
+ * is not yet; for instance adding a served origin. Retry once the cluster is
191
+ * upgraded.
192
+ */
193
+ export class ClusterNotUpgraded extends CalimeroError {
194
+ constructor(message, details = {}) {
195
+ super(409, 'CLUSTER_NOT_UPGRADED', message, details);
196
+ this.name = 'ClusterNotUpgraded';
197
+ }
198
+ }
199
+ /**
200
+ * `409 RECOVERY_NOT_READY`: a recovery (an account's or an organization's)
201
+ * may finish only once its 72-hour delay has passed. `readyAt` is when it may
202
+ * (Unix seconds), when the node said.
203
+ */
204
+ export class RecoveryNotReady extends CalimeroError {
205
+ constructor(message, details = {}) {
206
+ super(409, 'RECOVERY_NOT_READY', message, details);
207
+ this.name = 'RecoveryNotReady';
208
+ this.readyAt = typeof details.readyAt === 'number' ? details.readyAt : undefined;
209
+ }
210
+ }
211
+ /**
212
+ * The recovery code is not one the cluster holds: mistyped, replaced by a
213
+ * newer one, or spent by a recovery that started (whether it finished or was
214
+ * cancelled). The node answers it `401`; a recovery is signed out, so it is
215
+ * this error, not {@link LoginRequired}.
216
+ */
217
+ export class WrongRecoveryCode extends CalimeroError {
218
+ constructor(message, details = {}) {
219
+ super(401, 'UNAUTHENTICATED', message || 'unknown or used recovery code', details);
220
+ this.name = 'WrongRecoveryCode';
221
+ }
222
+ }
168
223
  /**
169
224
  * `403 SIGN_UP_REFUSED`: the cluster's sign-up policy does not take this
170
225
  * sign-up (core `SystemState::admits`), answered by `register/begin` before a
@@ -199,7 +254,10 @@ function signUpRefusal(message) {
199
254
  return 'org-gone';
200
255
  return 'other';
201
256
  }
202
- /** `429 QUOTA_EXCEEDED`: the caller holds as many of something (origins, blobs, ...) as it may. */
257
+ /**
258
+ * `429 QUOTA_EXCEEDED`: the caller holds as many of something (origins,
259
+ * served origins per package or per cluster, blobs, ...) as it may.
260
+ */
203
261
  export class QuotaExceeded extends CalimeroError {
204
262
  constructor(message, details = {}) {
205
263
  super(429, 'QUOTA_EXCEEDED', message, details);
@@ -297,8 +355,14 @@ export function errorFrom(status, body, retryAfterHeader) {
297
355
  return new BadOrigin(code, message, details);
298
356
  case '409 ORIGIN_TAKEN':
299
357
  return new OriginTaken(message, details);
358
+ case '400 NOT_SERVING':
359
+ return new NotServing(message, details);
360
+ case '409 CLUSTER_NOT_UPGRADED':
361
+ return new ClusterNotUpgraded(message, details);
300
362
  case '429 QUOTA_EXCEEDED':
301
363
  return new QuotaExceeded(message, details);
364
+ case '409 RECOVERY_NOT_READY':
365
+ return new RecoveryNotReady(message, details);
302
366
  case '403 SIGN_UP_REFUSED':
303
367
  return new SignUpRefused(message, details);
304
368
  case '412 CONFIRM_REQUIRED':
@@ -1,9 +1,9 @@
1
1
  export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
2
2
  export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
3
3
  export type { DirectoryFailure } from './directory.js';
4
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
4
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ClusterNotUpgraded, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, NotServing, OriginNotRegistered, OriginTaken, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
5
5
  export type { Committed, SignUpRefusal } from './errors.js';
6
- export type { AppInfo, AppXcalls, BlobContent, BlobInfo, DeadLetter, Deployer, DeployerGrant, DeployersInfo, DeploymentInfo, DeploymentOffer, DeploymentOrgs, FrontendManifest, MeInfo, PackageOrigin, PackageOriginAdded, PackageOriginRemoved, PackageOrigins, SignInOptions, SignUpOptions, SignUpPolicy, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgRecovery, RecoveryTimes, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
6
+ export type { AppInfo, AppXcalls, BlobContent, BlobInfo, DeadLetter, Deployer, DeployerGrant, DeployersInfo, DeploymentInfo, DeploymentOffer, DeploymentOrgs, FrontendManifest, MeInfo, PackageOrigin, PackageOriginAdded, PackageOriginRemoved, PackageOrigins, ServedOrigin, ServedOrigins, SignInOptions, AccountRecovery, SignUpOptions, SignUpPolicy, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgRecovery, RecoveryTimes, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
7
7
  export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';
8
8
  export type { AttestationDoc, CloudPolicy, CloudReleasePolicy } from './attestation.js';
9
9
  export { browserPasskey, b64url, fromB64url } from './passkey.js';
@@ -1,6 +1,6 @@
1
1
  export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
2
2
  export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
3
- export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
3
+ export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ClusterNotUpgraded, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, NotServing, OriginNotRegistered, OriginTaken, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
4
4
  export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';
5
5
  export { browserPasskey, b64url, fromB64url } from './passkey.js';
6
6
  export { MAX_RECOVERY_NONCE_BYTES, RECOVERY_DOMAIN, newRecoverySeed, parseRecoverySeed, recoveryMessage, recoveryPublicKey, recoverySeedText, signRecovery, } from './recovery.js';