@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.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  AgentGateError: () => AgentGateError,
35
35
  AppError: () => AppError,
36
36
  ArgsTooLarge: () => ArgsTooLarge,
37
+ BadOrigin: () => BadOrigin,
37
38
  CalimeroClient: () => CalimeroClient,
38
39
  CalimeroError: () => CalimeroError,
39
40
  ConfirmCancelled: () => ConfirmCancelled,
@@ -54,7 +55,10 @@ __export(index_exports, {
54
55
  NODE_RELEASE_SIGNER: () => NODE_RELEASE_SIGNER,
55
56
  NotFound: () => NotFound,
56
57
  ORG_CAPABILITIES: () => ORG_CAPABILITIES,
58
+ OriginNotRegistered: () => OriginNotRegistered,
59
+ OriginTaken: () => OriginTaken,
57
60
  PRODUCTION_PROFILE: () => PRODUCTION_PROFILE,
61
+ QuotaExceeded: () => QuotaExceeded,
58
62
  RELEASE_VERIFIER: () => RELEASE_VERIFIER,
59
63
  ReleaseDeadlineError: () => ReleaseDeadlineError,
60
64
  SEALED_CONTENT_TYPE: () => SEALED_CONTENT_TYPE,
@@ -1508,6 +1512,36 @@ var ConsentStale = class extends CalimeroError {
1508
1512
  this.name = "ConsentStale";
1509
1513
  }
1510
1514
  };
1515
+ var OriginNotRegistered = class extends CalimeroError {
1516
+ constructor(message, details = {}) {
1517
+ const origin = typeof details.origin === "string" ? details.origin : "";
1518
+ const pkg = typeof details.package === "string" ? details.package : "";
1519
+ const site = origin ? `this site (${origin})` : "this site";
1520
+ super(403, "ORIGIN_NOT_REGISTERED", `${site} is not registered for ${pkg || "any package"}${message ? ` (${message})` : ""}`, details);
1521
+ this.name = "OriginNotRegistered";
1522
+ this.origin = origin;
1523
+ this.package = pkg;
1524
+ this.nodeMessage = message;
1525
+ }
1526
+ };
1527
+ var BadOrigin = class extends CalimeroError {
1528
+ constructor(code, message, details = {}) {
1529
+ super(400, code, message, details);
1530
+ this.name = "BadOrigin";
1531
+ }
1532
+ };
1533
+ var OriginTaken = class extends CalimeroError {
1534
+ constructor(message, details = {}) {
1535
+ super(409, "ORIGIN_TAKEN", message, details);
1536
+ this.name = "OriginTaken";
1537
+ }
1538
+ };
1539
+ var QuotaExceeded = class extends CalimeroError {
1540
+ constructor(message, details = {}) {
1541
+ super(429, "QUOTA_EXCEEDED", message, details);
1542
+ this.name = "QuotaExceeded";
1543
+ }
1544
+ };
1511
1545
  var AppError = class extends CalimeroError {
1512
1546
  constructor(message, details = {}) {
1513
1547
  super(422, "APP_ERROR", message, details);
@@ -1567,6 +1601,15 @@ function errorFrom(status, body, retryAfterHeader) {
1567
1601
  return new ReleaseDeadlineError(message, details);
1568
1602
  case "409 CONSENT_STALE":
1569
1603
  return new ConsentStale(message, details);
1604
+ case "403 ORIGIN_NOT_REGISTERED":
1605
+ return new OriginNotRegistered(message, details);
1606
+ case "400 BAD_ORIGIN":
1607
+ case "400 BAD_RP_ID":
1608
+ return new BadOrigin(code, message, details);
1609
+ case "409 ORIGIN_TAKEN":
1610
+ return new OriginTaken(message, details);
1611
+ case "429 QUOTA_EXCEEDED":
1612
+ return new QuotaExceeded(message, details);
1570
1613
  case "412 CONFIRM_REQUIRED":
1571
1614
  return new ConfirmRequired(message, details);
1572
1615
  case "413 ARGS_TOO_LARGE":
@@ -1855,6 +1898,25 @@ var CalimeroClient = class _CalimeroClient {
1855
1898
  this.channels = /* @__PURE__ */ new Map();
1856
1899
  /** Rejections of the underlying `fetch` itself: the node could not be reached or dropped the connection. */
1857
1900
  this.networkFailures = /* @__PURE__ */ new WeakSet();
1901
+ // ---------------------------------------------------------------- package origins
1902
+ /**
1903
+ * The web origins `{package}`'s managers registered so a frontend they host
1904
+ * themselves can sign users in with passkeys ({@link PackageOrigins}).
1905
+ * Adding and removing one is approved with a passkey.
1906
+ */
1907
+ this.packageOrigins = {
1908
+ list: async (pkg) => {
1909
+ const { origins } = await this.get(`package-origins/${seg(pkg)}`);
1910
+ return origins ?? [];
1911
+ },
1912
+ add: (pkg, origin, passkey, options = {}) => this.confirmed(
1913
+ "POST",
1914
+ `package-origins/${seg(pkg)}`,
1915
+ options.rpId === void 0 ? { origin } : { origin, rpId: options.rpId },
1916
+ passkey
1917
+ ),
1918
+ remove: (pkg, origin, passkey) => this.confirmed("DELETE", `package-origins/${seg(pkg)}`, { origin }, passkey)
1919
+ };
1858
1920
  const trim = (url2) => url2.replace(/\/+$/, "");
1859
1921
  this.nodeUrls = [...new Set([options.baseUrl, ...options.nodes ?? []].map(trim))];
1860
1922
  this.policy = options.policy;
@@ -2108,26 +2170,43 @@ var CalimeroClient = class _CalimeroClient {
2108
2170
  return this.json("DELETE", path, body);
2109
2171
  }
2110
2172
  // ---------------------------------------------------------------- identity
2111
- /** Create an account with a new passkey, and sign in. */
2112
- async signUp(passkey, name) {
2113
- const begin = await this.post(
2173
+ /**
2174
+ * Create an account with a new passkey, and sign in. The node then asks the
2175
+ * new passkey to sign the account it creates (`412 CONFIRM_REQUIRED`), so a
2176
+ * sign-up prompts twice: create, then sign. On a page whose origin is not
2177
+ * the cluster's ({@link SignInOptions}), the node makes the passkey for that
2178
+ * origin's relying party (the confirmation's `rpId` is that origin's too)
2179
+ * and binds the session to the package the origin is registered for; an
2180
+ * unregistered origin is refused with {@link OriginNotRegistered}.
2181
+ */
2182
+ async signUp(passkey, name, options = {}) {
2183
+ const origin = this.pageOrigin(options);
2184
+ const begin = await this.begin(
2114
2185
  "auth/passkey/register/begin",
2115
- { name }
2186
+ origin === void 0 ? { name } : { name, origin },
2187
+ origin
2116
2188
  );
2117
2189
  const registration = await passkey.create(begin.options);
2118
- const session = await this.post("auth/passkey/register/finish", {
2119
- challengeId: begin.challengeId,
2120
- registration
2121
- });
2190
+ const session = await this.postConfirmed(
2191
+ "auth/passkey/register/finish",
2192
+ { challengeId: begin.challengeId, registration },
2193
+ passkey
2194
+ );
2122
2195
  this.useSession(session);
2123
2196
  return session;
2124
2197
  }
2125
- /** Sign in with an existing passkey. */
2126
- async signIn(passkey) {
2127
- const begin = await this.post(
2128
- "auth/passkey/login/begin"
2129
- );
2130
- const assertion = await passkey.get(fromB64url(begin.options.challenge), begin.options.rpId);
2198
+ /**
2199
+ * Sign in with an existing passkey. On a page whose origin is not the
2200
+ * cluster's ({@link SignInOptions}), the assertion is made for that
2201
+ * origin's relying-party id, as the node answers it, and the session is
2202
+ * bound to the origin's package; an unregistered origin is refused with
2203
+ * {@link OriginNotRegistered}.
2204
+ */
2205
+ async signIn(passkey, options = {}) {
2206
+ const origin = this.pageOrigin(options);
2207
+ const begin = await this.begin("auth/passkey/login/begin", origin === void 0 ? void 0 : { origin }, origin);
2208
+ const rpId = begin.options.rpId ?? begin.options.rp?.id ?? "";
2209
+ const assertion = await passkey.get(fromB64url(begin.options.challenge), rpId);
2131
2210
  const session = await this.post("auth/passkey/login/finish", {
2132
2211
  challengeId: begin.challengeId,
2133
2212
  assertion
@@ -2135,6 +2214,36 @@ var CalimeroClient = class _CalimeroClient {
2135
2214
  this.useSession(session);
2136
2215
  return session;
2137
2216
  }
2217
+ /**
2218
+ * The origin a sign-up or sign-in sends: the one given, or the page's own
2219
+ * when it is not a node's origin; `undefined` for none.
2220
+ */
2221
+ pageOrigin(options) {
2222
+ if (options.origin !== void 0) return options.origin ?? void 0;
2223
+ const page = globalThis.location?.origin;
2224
+ if (!page || page === "null") return void 0;
2225
+ const nodes = new Set(
2226
+ this.nodeUrls.map((url2) => {
2227
+ try {
2228
+ return new URL(url2).origin;
2229
+ } catch {
2230
+ return url2;
2231
+ }
2232
+ })
2233
+ );
2234
+ return nodes.has(page) ? void 0 : page;
2235
+ }
2236
+ /** POST a ceremony's `begin`, naming the page's `origin` in an {@link OriginNotRegistered} the node leaves it out of. */
2237
+ async begin(path, body, origin) {
2238
+ try {
2239
+ return await this.post(path, body);
2240
+ } catch (error) {
2241
+ if (error instanceof OriginNotRegistered && !error.origin && origin !== void 0) {
2242
+ throw new OriginNotRegistered(error.nodeMessage, { ...error.details, origin });
2243
+ }
2244
+ throw error;
2245
+ }
2246
+ }
2138
2247
  /**
2139
2248
  * Swap the refresh token for a new session, on whichever node the client
2140
2249
  * uses: any node of the cluster takes it. A refresh token is used once, so
@@ -2177,6 +2286,7 @@ var CalimeroClient = class _CalimeroClient {
2177
2286
  this.auth = void 0;
2178
2287
  this.current = void 0;
2179
2288
  }
2289
+ /** Who the credential is: the account, how it signed in, and the package (`app`) and `origin` a session is bound to. */
2180
2290
  me() {
2181
2291
  return this.get("auth/me");
2182
2292
  }
@@ -2223,8 +2333,9 @@ var CalimeroClient = class _CalimeroClient {
2223
2333
  const { keys } = await this.get("auth/apikeys");
2224
2334
  return keys;
2225
2335
  }
2226
- async revokeApiKey(keyId) {
2227
- await this.delete(`auth/apikeys/${seg(keyId)}`);
2336
+ /** Revoke one of the account's API keys, approved with its `passkey` (`412 CONFIRM_REQUIRED`). */
2337
+ async revokeApiKey(keyId, passkey) {
2338
+ await this.deleteConfirmed(`auth/apikeys/${seg(keyId)}`, passkey);
2228
2339
  }
2229
2340
  /**
2230
2341
  * Create an API key for an agent or server, approved with the account's