@calimero-network/mero-js 28.1.1 → 29.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
@@ -1394,6 +1394,36 @@ var ConsentStale = class extends CalimeroError {
1394
1394
  this.name = "ConsentStale";
1395
1395
  }
1396
1396
  };
1397
+ var OriginNotRegistered = class extends CalimeroError {
1398
+ constructor(message, details = {}) {
1399
+ const origin = typeof details.origin === "string" ? details.origin : "";
1400
+ const pkg = typeof details.package === "string" ? details.package : "";
1401
+ const site = origin ? `this site (${origin})` : "this site";
1402
+ super(403, "ORIGIN_NOT_REGISTERED", `${site} is not registered for ${pkg || "any package"}${message ? ` (${message})` : ""}`, details);
1403
+ this.name = "OriginNotRegistered";
1404
+ this.origin = origin;
1405
+ this.package = pkg;
1406
+ this.nodeMessage = message;
1407
+ }
1408
+ };
1409
+ var BadOrigin = class extends CalimeroError {
1410
+ constructor(code, message, details = {}) {
1411
+ super(400, code, message, details);
1412
+ this.name = "BadOrigin";
1413
+ }
1414
+ };
1415
+ var OriginTaken = class extends CalimeroError {
1416
+ constructor(message, details = {}) {
1417
+ super(409, "ORIGIN_TAKEN", message, details);
1418
+ this.name = "OriginTaken";
1419
+ }
1420
+ };
1421
+ var QuotaExceeded = class extends CalimeroError {
1422
+ constructor(message, details = {}) {
1423
+ super(429, "QUOTA_EXCEEDED", message, details);
1424
+ this.name = "QuotaExceeded";
1425
+ }
1426
+ };
1397
1427
  var AppError = class extends CalimeroError {
1398
1428
  constructor(message, details = {}) {
1399
1429
  super(422, "APP_ERROR", message, details);
@@ -1453,6 +1483,15 @@ function errorFrom(status, body, retryAfterHeader) {
1453
1483
  return new ReleaseDeadlineError(message, details);
1454
1484
  case "409 CONSENT_STALE":
1455
1485
  return new ConsentStale(message, details);
1486
+ case "403 ORIGIN_NOT_REGISTERED":
1487
+ return new OriginNotRegistered(message, details);
1488
+ case "400 BAD_ORIGIN":
1489
+ case "400 BAD_RP_ID":
1490
+ return new BadOrigin(code, message, details);
1491
+ case "409 ORIGIN_TAKEN":
1492
+ return new OriginTaken(message, details);
1493
+ case "429 QUOTA_EXCEEDED":
1494
+ return new QuotaExceeded(message, details);
1456
1495
  case "412 CONFIRM_REQUIRED":
1457
1496
  return new ConfirmRequired(message, details);
1458
1497
  case "413 ARGS_TOO_LARGE":
@@ -1741,6 +1780,25 @@ var CalimeroClient = class _CalimeroClient {
1741
1780
  this.channels = /* @__PURE__ */ new Map();
1742
1781
  /** Rejections of the underlying `fetch` itself: the node could not be reached or dropped the connection. */
1743
1782
  this.networkFailures = /* @__PURE__ */ new WeakSet();
1783
+ // ---------------------------------------------------------------- package origins
1784
+ /**
1785
+ * The web origins `{package}`'s managers registered so a frontend they host
1786
+ * themselves can sign users in with passkeys ({@link PackageOrigins}).
1787
+ * Adding and removing one is approved with a passkey.
1788
+ */
1789
+ this.packageOrigins = {
1790
+ list: async (pkg) => {
1791
+ const { origins } = await this.get(`package-origins/${seg(pkg)}`);
1792
+ return origins ?? [];
1793
+ },
1794
+ add: (pkg, origin, passkey, options = {}) => this.confirmed(
1795
+ "POST",
1796
+ `package-origins/${seg(pkg)}`,
1797
+ options.rpId === void 0 ? { origin } : { origin, rpId: options.rpId },
1798
+ passkey
1799
+ ),
1800
+ remove: (pkg, origin, passkey) => this.confirmed("DELETE", `package-origins/${seg(pkg)}`, { origin }, passkey)
1801
+ };
1744
1802
  const trim = (url2) => url2.replace(/\/+$/, "");
1745
1803
  this.nodeUrls = [...new Set([options.baseUrl, ...options.nodes ?? []].map(trim))];
1746
1804
  this.policy = options.policy;
@@ -1994,26 +2052,43 @@ var CalimeroClient = class _CalimeroClient {
1994
2052
  return this.json("DELETE", path, body);
1995
2053
  }
1996
2054
  // ---------------------------------------------------------------- identity
1997
- /** Create an account with a new passkey, and sign in. */
1998
- async signUp(passkey, name) {
1999
- const begin = await this.post(
2055
+ /**
2056
+ * Create an account with a new passkey, and sign in. The node then asks the
2057
+ * new passkey to sign the account it creates (`412 CONFIRM_REQUIRED`), so a
2058
+ * sign-up prompts twice: create, then sign. On a page whose origin is not
2059
+ * the cluster's ({@link SignInOptions}), the node makes the passkey for that
2060
+ * origin's relying party (the confirmation's `rpId` is that origin's too)
2061
+ * and binds the session to the package the origin is registered for; an
2062
+ * unregistered origin is refused with {@link OriginNotRegistered}.
2063
+ */
2064
+ async signUp(passkey, name, options = {}) {
2065
+ const origin = this.pageOrigin(options);
2066
+ const begin = await this.begin(
2000
2067
  "auth/passkey/register/begin",
2001
- { name }
2068
+ origin === void 0 ? { name } : { name, origin },
2069
+ origin
2002
2070
  );
2003
2071
  const registration = await passkey.create(begin.options);
2004
- const session = await this.post("auth/passkey/register/finish", {
2005
- challengeId: begin.challengeId,
2006
- registration
2007
- });
2072
+ const session = await this.postConfirmed(
2073
+ "auth/passkey/register/finish",
2074
+ { challengeId: begin.challengeId, registration },
2075
+ passkey
2076
+ );
2008
2077
  this.useSession(session);
2009
2078
  return session;
2010
2079
  }
2011
- /** Sign in with an existing passkey. */
2012
- async signIn(passkey) {
2013
- const begin = await this.post(
2014
- "auth/passkey/login/begin"
2015
- );
2016
- const assertion = await passkey.get(fromB64url(begin.options.challenge), begin.options.rpId);
2080
+ /**
2081
+ * Sign in with an existing passkey. On a page whose origin is not the
2082
+ * cluster's ({@link SignInOptions}), the assertion is made for that
2083
+ * origin's relying-party id, as the node answers it, and the session is
2084
+ * bound to the origin's package; an unregistered origin is refused with
2085
+ * {@link OriginNotRegistered}.
2086
+ */
2087
+ async signIn(passkey, options = {}) {
2088
+ const origin = this.pageOrigin(options);
2089
+ const begin = await this.begin("auth/passkey/login/begin", origin === void 0 ? void 0 : { origin }, origin);
2090
+ const rpId = begin.options.rpId ?? begin.options.rp?.id ?? "";
2091
+ const assertion = await passkey.get(fromB64url(begin.options.challenge), rpId);
2017
2092
  const session = await this.post("auth/passkey/login/finish", {
2018
2093
  challengeId: begin.challengeId,
2019
2094
  assertion
@@ -2021,6 +2096,36 @@ var CalimeroClient = class _CalimeroClient {
2021
2096
  this.useSession(session);
2022
2097
  return session;
2023
2098
  }
2099
+ /**
2100
+ * The origin a sign-up or sign-in sends: the one given, or the page's own
2101
+ * when it is not a node's origin; `undefined` for none.
2102
+ */
2103
+ pageOrigin(options) {
2104
+ if (options.origin !== void 0) return options.origin ?? void 0;
2105
+ const page = globalThis.location?.origin;
2106
+ if (!page || page === "null") return void 0;
2107
+ const nodes = new Set(
2108
+ this.nodeUrls.map((url2) => {
2109
+ try {
2110
+ return new URL(url2).origin;
2111
+ } catch {
2112
+ return url2;
2113
+ }
2114
+ })
2115
+ );
2116
+ return nodes.has(page) ? void 0 : page;
2117
+ }
2118
+ /** POST a ceremony's `begin`, naming the page's `origin` in an {@link OriginNotRegistered} the node leaves it out of. */
2119
+ async begin(path, body, origin) {
2120
+ try {
2121
+ return await this.post(path, body);
2122
+ } catch (error) {
2123
+ if (error instanceof OriginNotRegistered && !error.origin && origin !== void 0) {
2124
+ throw new OriginNotRegistered(error.nodeMessage, { ...error.details, origin });
2125
+ }
2126
+ throw error;
2127
+ }
2128
+ }
2024
2129
  /**
2025
2130
  * Swap the refresh token for a new session, on whichever node the client
2026
2131
  * uses: any node of the cluster takes it. A refresh token is used once, so
@@ -2063,6 +2168,7 @@ var CalimeroClient = class _CalimeroClient {
2063
2168
  this.auth = void 0;
2064
2169
  this.current = void 0;
2065
2170
  }
2171
+ /** Who the credential is: the account, how it signed in, and the package (`app`) and `origin` a session is bound to. */
2066
2172
  me() {
2067
2173
  return this.get("auth/me");
2068
2174
  }
@@ -2109,8 +2215,9 @@ var CalimeroClient = class _CalimeroClient {
2109
2215
  const { keys } = await this.get("auth/apikeys");
2110
2216
  return keys;
2111
2217
  }
2112
- async revokeApiKey(keyId) {
2113
- await this.delete(`auth/apikeys/${seg(keyId)}`);
2218
+ /** Revoke one of the account's API keys, approved with its `passkey` (`412 CONFIRM_REQUIRED`). */
2219
+ async revokeApiKey(keyId, passkey) {
2220
+ await this.deleteConfirmed(`auth/apikeys/${seg(keyId)}`, passkey);
2114
2221
  }
2115
2222
  /**
2116
2223
  * Create an API key for an agent or server, approved with the account's
@@ -3228,6 +3335,7 @@ export {
3228
3335
  AgentGateError,
3229
3336
  AppError,
3230
3337
  ArgsTooLarge,
3338
+ BadOrigin,
3231
3339
  CalimeroClient,
3232
3340
  CalimeroError,
3233
3341
  ConfirmCancelled,
@@ -3248,7 +3356,10 @@ export {
3248
3356
  NODE_RELEASE_SIGNER,
3249
3357
  NotFound,
3250
3358
  ORG_CAPABILITIES,
3359
+ OriginNotRegistered,
3360
+ OriginTaken,
3251
3361
  PRODUCTION_PROFILE,
3362
+ QuotaExceeded,
3252
3363
  RELEASE_VERIFIER,
3253
3364
  ReleaseDeadlineError,
3254
3365
  SEALED_CONTENT_TYPE,