@calimero-network/mero-js 27.5.0 → 28.1.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/dist/index.mjs CHANGED
@@ -1648,6 +1648,26 @@ function upgradeConsent(app, toVersion, returnTo) {
1648
1648
  }
1649
1649
 
1650
1650
  // src/cloud-v2/client.ts
1651
+ function deployerKey(deployer) {
1652
+ return "account" in deployer ? `account:${deployer.account}` : `org:${deployer.org}`;
1653
+ }
1654
+ async function frontendManifest(origin, fetcher = fetch) {
1655
+ const base = origin ?? globalThis.location?.origin;
1656
+ if (!base) throw new Error("no page origin to read the frontend manifest from; pass `origin`");
1657
+ const response = await fetcher(new URL("/.well-known/calimero.json", base).toString());
1658
+ if (!response.ok) throw await refusal(response);
1659
+ const manifest = await response.json();
1660
+ return { ...manifest, deployments: manifest.deployments ?? [] };
1661
+ }
1662
+ function pageDeployment(href) {
1663
+ const at = href ?? globalThis.location?.href;
1664
+ if (!at) return null;
1665
+ try {
1666
+ return new URL(at).searchParams.get("deployment") || null;
1667
+ } catch {
1668
+ return null;
1669
+ }
1670
+ }
1651
1671
  function seg(value) {
1652
1672
  return encodeURIComponent(value);
1653
1673
  }
@@ -2046,19 +2066,25 @@ var CalimeroClient = class _CalimeroClient {
2046
2066
  me() {
2047
2067
  return this.get("auth/me");
2048
2068
  }
2049
- /** Add another passkey (another device) to the signed-in account; answers its credential id. */
2050
- async addPasskey(passkey) {
2069
+ /**
2070
+ * Add another passkey (another device, `passkey`) to the signed-in account;
2071
+ * answers its credential id. The node asks for the approval of a passkey
2072
+ * the account already has (`412 CONFIRM_REQUIRED`, never the new one),
2073
+ * which `approver` gives.
2074
+ */
2075
+ async addPasskey(passkey, approver) {
2051
2076
  const begin = await this.post("auth/passkeys/add/begin");
2052
2077
  const registration = await passkey.create(begin.options);
2053
- const { credentialId } = await this.post("auth/passkeys/add/finish", {
2054
- challengeId: begin.challengeId,
2055
- registration
2056
- });
2078
+ const { credentialId } = await this.postConfirmed(
2079
+ "auth/passkeys/add/finish",
2080
+ { challengeId: begin.challengeId, registration },
2081
+ approver
2082
+ );
2057
2083
  return credentialId;
2058
2084
  }
2059
- /** Remove one of the account's passkeys (not the last). */
2060
- async removePasskey(credentialId) {
2061
- await this.delete(`auth/passkeys/${seg(credentialId)}`);
2085
+ /** Remove one of the account's passkeys (not the last), approved with `passkey` (one it has). */
2086
+ async removePasskey(credentialId, passkey) {
2087
+ await this.deleteConfirmed(`auth/passkeys/${seg(credentialId)}`, passkey);
2062
2088
  }
2063
2089
  /** Name one of the account's passkeys (a device label); `''` clears the name. */
2064
2090
  async labelPasskey(credentialId, label) {
@@ -2086,9 +2112,13 @@ var CalimeroClient = class _CalimeroClient {
2086
2112
  async revokeApiKey(keyId) {
2087
2113
  await this.delete(`auth/apikeys/${seg(keyId)}`);
2088
2114
  }
2089
- /** Create an API key for an agent or server. The key is returned once. */
2090
- createApiKey(options) {
2091
- return this.post("auth/apikeys", options);
2115
+ /**
2116
+ * Create an API key for an agent or server, approved with the account's
2117
+ * `passkey` (`412 CONFIRM_REQUIRED`); needs a passkey session (an API key
2118
+ * is refused `403`). The key is returned once.
2119
+ */
2120
+ createApiKey(options, passkey) {
2121
+ return this.postConfirmed("auth/apikeys", { ...options }, passkey);
2092
2122
  }
2093
2123
  // ---------------------------------------------------------------- apps
2094
2124
  /** Upload a raw WASM bundle (development clusters only); answers its hash. */
@@ -2096,14 +2126,41 @@ var CalimeroClient = class _CalimeroClient {
2096
2126
  const { hash } = await this.json("POST", "bundles", void 0, wasm);
2097
2127
  return hash;
2098
2128
  }
2099
- /** Upload a blob for `app`: `blobId` is base58 (what apps print), `id` hex. */
2100
- uploadBlob(app, data) {
2101
- return this.json("POST", `apps/${seg(app)}/blobs`, void 0, data);
2129
+ /**
2130
+ * Upload a blob for `app`: `blobId` is base58 (what apps print), `id` hex.
2131
+ * `contentType` (default `application/octet-stream`; printable ASCII, the
2132
+ * node refuses another with `400`) is sent as the upload's `Content-Type`:
2133
+ * a component app records it and serves downloads with it. A component
2134
+ * app holding as many blobs as it may answers `429 QUOTA_EXCEEDED`.
2135
+ */
2136
+ uploadBlob(app, data, contentType = {}) {
2137
+ const mime = (typeof contentType === "string" ? contentType : contentType.contentType) ?? "application/octet-stream";
2138
+ return this.json("POST", `apps/${seg(app)}/blobs`, void 0, data, mime);
2102
2139
  }
2103
2140
  /** One of `app`'s blobs, by id (base58 or hex). */
2104
2141
  async downloadBlob(app, id) {
2105
- const response = await this.send("GET", `apps/${seg(app)}/blobs/${seg(id)}`);
2106
- return new Uint8Array(await response.arrayBuffer());
2142
+ return (await this.blobContent(app, id)).data;
2143
+ }
2144
+ /**
2145
+ * Download a component app's blob with a grant one of its runs handed out
2146
+ * (`BlobRef::grant`, up to 24 h). No session needed: the grant is the
2147
+ * permission. A bad or expired grant answers `NotFound`, as a missing blob.
2148
+ */
2149
+ async downloadBlobGranted(app, id, grant) {
2150
+ return (await this.blobContent(app, id, { grant })).data;
2151
+ }
2152
+ /**
2153
+ * {@link downloadBlob} (or, with `grant`, {@link downloadBlobGranted}) with
2154
+ * the content type the node serves it as: the uploader's, for a component
2155
+ * app; `application/octet-stream` otherwise.
2156
+ */
2157
+ async blobContent(app, id, options = {}) {
2158
+ const query = options.grant === void 0 ? "" : `?grant=${encodeURIComponent(options.grant)}`;
2159
+ const response = await this.send("GET", `apps/${seg(app)}/blobs/${seg(id)}${query}`);
2160
+ return {
2161
+ data: new Uint8Array(await response.arrayBuffer()),
2162
+ contentType: response.headers.get("content-type") ?? "application/octet-stream"
2163
+ };
2107
2164
  }
2108
2165
  /** `app`'s blobs. */
2109
2166
  async listBlobs(app) {
@@ -2165,6 +2222,15 @@ var CalimeroClient = class _CalimeroClient {
2165
2222
  async setAppMetadata(app, metadata) {
2166
2223
  await this.post(`apps/${seg(app)}/metadata`, { metadata });
2167
2224
  }
2225
+ /**
2226
+ * A component app's xcalls: how many messages wait in its outbox, and the
2227
+ * dead letters (never delivered), newest last. Its owner's or an org
2228
+ * admin's (`403` to anyone else); `409 NOT_SUPPORTED` for an app on the
2229
+ * wasmer runtime, whose xcalls are follow-ups.
2230
+ */
2231
+ appXcalls(app) {
2232
+ return this.get(`apps/${seg(app)}/xcalls`);
2233
+ }
2168
2234
  /** What the app's storage holds: `{ keys, bytes, blobs, blobBytes }`. */
2169
2235
  appStorage(app) {
2170
2236
  return this.get(`apps/${seg(app)}/storage`);
@@ -2196,12 +2262,19 @@ var CalimeroClient = class _CalimeroClient {
2196
2262
  }
2197
2263
  /**
2198
2264
  * POST `body` to `path`, answering a `412 CONFIRM_REQUIRED` with `passkey`'s
2199
- * assertion over the node's challenge (deleting an org, approving a release, ...).
2265
+ * assertion over the node's challenge (deleting an org, approving a release,
2266
+ * adding a member, ...): the same body again, with `confirm`. Without a
2267
+ * passkey (a caller in plain JavaScript passing none) the `412` reaches the
2268
+ * caller as {@link ConfirmRequired}, carrying the challenge.
2200
2269
  */
2201
2270
  postConfirmed(path, body, passkey) {
2202
2271
  return this.confirmed("POST", path, body, passkey);
2203
2272
  }
2204
- /** {@link postConfirmed} for a DELETE (deleting an organization). */
2273
+ /**
2274
+ * {@link postConfirmed} for a DELETE (deleting an organization, removing a
2275
+ * member or a passkey): `body` (`{}` by default) goes as JSON, and the
2276
+ * confirmed retry carries `confirm` beside it.
2277
+ */
2205
2278
  deleteConfirmed(path, passkey, body = {}) {
2206
2279
  return this.confirmed("DELETE", path, body, passkey);
2207
2280
  }
@@ -2209,7 +2282,7 @@ var CalimeroClient = class _CalimeroClient {
2209
2282
  try {
2210
2283
  return await send(body);
2211
2284
  } catch (error) {
2212
- if (!(error instanceof ConfirmRequired)) throw error;
2285
+ if (!(error instanceof ConfirmRequired) || !passkey) throw error;
2213
2286
  const d = error.details;
2214
2287
  let assertion;
2215
2288
  try {
@@ -2225,7 +2298,13 @@ var CalimeroClient = class _CalimeroClient {
2225
2298
  /**
2226
2299
  * Create an app instance from a bundle hash, or from a package release
2227
2300
  * (`{ package, version?, service? }`, installed from the registry first if
2228
- * the cluster lacks it).
2301
+ * the cluster lacks it). With `deployment`, the instance runs the release
2302
+ * that deployment offers for `package` (`version`, if given, must be it)
2303
+ * and records the deployment; no install, no deployer grant. A package or
2304
+ * version it does not offer answers `400 NOT_OFFERED`, several offered
2305
+ * releases with no `version` `400 VERSION_REQUIRED`, and an unknown
2306
+ * `service` `400 NO_SUCH_SERVICE`. A page opened through `/d/{deployment}/`
2307
+ * finds its deployment with {@link pageDeployment}.
2229
2308
  */
2230
2309
  async createApp(name, bundle, options = {}) {
2231
2310
  const target = typeof bundle === "string" ? { bundle } : bundle;
@@ -2533,20 +2612,24 @@ var CalimeroClient = class _CalimeroClient {
2533
2612
  async liftReleaseDeadline(version) {
2534
2613
  await this.delete(`ops/releases/${seg(version)}/deadline`);
2535
2614
  }
2536
- /** Add a member, or change their role. */
2537
- async setMember(org, account, role) {
2538
- await this.post(`orgs/${seg(org)}/members`, { account, role });
2615
+ /** Add a member, or change their role, approved with the caller's `passkey`. */
2616
+ async setMember(org, account, role, passkey) {
2617
+ await this.postConfirmed(`orgs/${seg(org)}/members`, { account, role }, passkey);
2539
2618
  }
2540
- removeMember(org, account) {
2541
- return this.delete(`orgs/${seg(org)}/members/${seg(account)}`);
2619
+ /**
2620
+ * Remove a member, or leave (`account` the caller's own), approved with the
2621
+ * caller's `passkey`.
2622
+ */
2623
+ removeMember(org, account, passkey) {
2624
+ return this.deleteConfirmed(`orgs/${seg(org)}/members/${seg(account)}`, passkey);
2542
2625
  }
2543
2626
  /**
2544
2627
  * Set capability bits ({@link ORG_CAPABILITIES}): for a member (`account`),
2545
2628
  * for a member of one team (`account` and `team`), or the organization's
2546
- * default (neither).
2629
+ * default (neither). Approved with the caller's `passkey`.
2547
2630
  */
2548
- async setCapabilities(org, capabilities, at = {}) {
2549
- await this.post(`orgs/${seg(org)}/capabilities`, { capabilities, ...at });
2631
+ async setCapabilities(org, capabilities, passkey, at = {}) {
2632
+ await this.postConfirmed(`orgs/${seg(org)}/capabilities`, { capabilities, ...at }, passkey);
2550
2633
  }
2551
2634
  async createTeam(org, name, join, parent) {
2552
2635
  const { teamId } = await this.post(`orgs/${seg(org)}/teams`, { name, join, parent });
@@ -2566,20 +2649,110 @@ var CalimeroClient = class _CalimeroClient {
2566
2649
  async deleteTeam(org, team) {
2567
2650
  await this.delete(`orgs/${seg(org)}/teams/${seg(team)}`);
2568
2651
  }
2569
- /** Add a member to a team, or change their row: role, lead, capability bits. */
2570
- async setTeamMember(org, team, account, row = {}) {
2571
- await this.post(`orgs/${seg(org)}/teams/${seg(team)}/members`, { account, ...row });
2652
+ /**
2653
+ * Add a member to a team, or change their row: role, lead, capability bits.
2654
+ * Approved with the caller's `passkey`.
2655
+ */
2656
+ async setTeamMember(org, team, account, passkey, row = {}) {
2657
+ await this.postConfirmed(`orgs/${seg(org)}/teams/${seg(team)}/members`, { account, ...row }, passkey);
2572
2658
  }
2573
- async removeTeamMember(org, team, account) {
2574
- await this.delete(`orgs/${seg(org)}/teams/${seg(team)}/members/${seg(account)}`);
2659
+ /** Remove a member from a team, approved with the caller's `passkey`. */
2660
+ async removeTeamMember(org, team, account, passkey) {
2661
+ await this.deleteConfirmed(`orgs/${seg(org)}/teams/${seg(team)}/members/${seg(account)}`, passkey);
2575
2662
  }
2576
- async invite(org, role, teams = [], options = {}) {
2577
- const { token } = await this.post(`orgs/${seg(org)}/invites`, { role, teams, ...options });
2663
+ /**
2664
+ * Make an invite into `org` (and `teams`), approved with the caller's
2665
+ * `passkey`; answers its token, which {@link redeemInvite} takes (no
2666
+ * approval there).
2667
+ */
2668
+ async invite(org, role, passkey, teams = [], options = {}) {
2669
+ const { token } = await this.postConfirmed(
2670
+ `orgs/${seg(org)}/invites`,
2671
+ { role, teams, ...options },
2672
+ passkey
2673
+ );
2578
2674
  return token;
2579
2675
  }
2580
2676
  redeemInvite(token) {
2581
2677
  return this.post(`invites/${seg(token)}/redeem`);
2582
2678
  }
2679
+ // ---------------------------------------------------------------- deployers
2680
+ /**
2681
+ * Let an account or organization install apps where the cluster's
2682
+ * deployments are `granted`, until `until` (RFC 3339 or a `Date`; absent,
2683
+ * the grant does not end). The cluster's owner grants it, approved with
2684
+ * `passkey`, where governance says `deployers: owner`; elsewhere the
2685
+ * operator grants with its key, and this is refused `403`.
2686
+ */
2687
+ grantDeployer(deployer, passkey, options = {}) {
2688
+ const until = options.until instanceof Date ? options.until.toISOString() : options.until;
2689
+ return this.postConfirmed("deployers", { deployer, ...until === void 0 ? {} : { until } }, passkey);
2690
+ }
2691
+ /**
2692
+ * Who may install apps on the cluster: its deployments rule, who grants
2693
+ * deployers, its owner and every grant. For the cluster's owner, or the
2694
+ * operator (its token, `useApiKey`).
2695
+ */
2696
+ deployers() {
2697
+ return this.get("deployers");
2698
+ }
2699
+ /**
2700
+ * End a deployer's grant (`deployer`, or its key `account:{id}` /
2701
+ * `org:{id}`), as the cluster's owner, approved with `passkey`. Apps it
2702
+ * installed, and the apps running them, stay.
2703
+ */
2704
+ async revokeDeployer(deployer, passkey) {
2705
+ const key = typeof deployer === "string" ? deployer : deployerKey(deployer);
2706
+ await this.deleteConfirmed(`deployers/${seg(key)}`, passkey);
2707
+ }
2708
+ // ---------------------------------------------------------------- deployments
2709
+ /**
2710
+ * Create a deployment: `owner`'s (the caller's own account, or an
2711
+ * organization where it holds `MANAGE_APPLICATION` org-wide) list of the
2712
+ * apps it offers, named `name` (1 to 64 characters, one line) for
2713
+ * customers. Approved with `passkey`; where deployments are `granted` the
2714
+ * caller needs a deployer grant. Answers the deployment's id.
2715
+ */
2716
+ async createDeployment(name, owner, passkey) {
2717
+ const { id } = await this.postConfirmed("deployments", { name, owner }, passkey);
2718
+ return id;
2719
+ }
2720
+ /** The deployments the caller manages: its account's, and its organizations' where it holds `MANAGE_APPLICATION`. */
2721
+ async deployments() {
2722
+ const { deployments } = await this.get("deployments");
2723
+ return deployments;
2724
+ }
2725
+ /** A deployment as anyone may read it: its name, its owner's kind and name, and the apps it offers. Public. */
2726
+ deployment(id) {
2727
+ return this.get(`deployments/${seg(id)}`);
2728
+ }
2729
+ /** Delete a deployment that offers nothing and has no instances, approved with `passkey`. */
2730
+ async deleteDeployment(id, passkey) {
2731
+ await this.deleteConfirmed(`deployments/${seg(id)}`, passkey);
2732
+ }
2733
+ /**
2734
+ * Offer `package@version` (installed, and seen by the caller) as `app`
2735
+ * (1 to 64 letters, digits, `.`, `_`, `-`) in deployment `id`, or move an
2736
+ * offered app to another release of its package. Approved with `passkey`.
2737
+ */
2738
+ offerApp(id, offer, passkey) {
2739
+ return this.postConfirmed(`deployments/${seg(id)}/apps`, { ...offer }, passkey);
2740
+ }
2741
+ /**
2742
+ * Withdraw `app` from deployment `id`, approved with `passkey`; refused
2743
+ * while an instance created from the deployment still runs it.
2744
+ */
2745
+ async withdrawApp(id, app, passkey) {
2746
+ await this.deleteConfirmed(`deployments/${seg(id)}/apps/${seg(app)}`, passkey);
2747
+ }
2748
+ /**
2749
+ * The organizations with instances created from deployment `id`, for its
2750
+ * managers: each one's id, name, instance count, apps and releases, and
2751
+ * first instance. Never members, teams or app data.
2752
+ */
2753
+ deploymentOrgs(id) {
2754
+ return this.get(`deployments/${seg(id)}/orgs`);
2755
+ }
2583
2756
  };
2584
2757
 
2585
2758
  // src/cloud-v2/capabilities.ts
@@ -3099,15 +3272,18 @@ export {
3099
3272
  createQuoteVerifier,
3100
3273
  createSealedFetch,
3101
3274
  createSignedReleaseVerifier,
3275
+ deployerKey,
3102
3276
  errorFrom,
3103
3277
  fetchAttestedKey,
3104
3278
  fetchGithubNodeRelease,
3105
3279
  fetchNodeRelease,
3106
3280
  freshCollateral,
3107
3281
  fromB64url,
3282
+ frontendManifest,
3108
3283
  generateOwnerKey,
3109
3284
  orgCapabilities,
3110
3285
  ownerPublicKey,
3286
+ pageDeployment,
3111
3287
  pageQuery,
3112
3288
  pendingApprovalsFrom,
3113
3289
  provisionAgent,