@calimero-network/mero-js 25.3.2 → 26.0.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
@@ -1219,50 +1219,6 @@ async function redeemInvitation(parsed, redeemer) {
1219
1219
  }
1220
1220
 
1221
1221
  // src/admin-api/admin-client.ts
1222
- var WarrantNonceRouteUnavailableError = class extends Error {
1223
- constructor(path, cause) {
1224
- super(
1225
- `this node does not serve warrant-nonce discovery (404 on ${path}); it predates the route, so a client must fall back to its own nonce counter`
1226
- );
1227
- this.path = path;
1228
- this.cause = cause;
1229
- this.name = "WarrantNonceRouteUnavailableError";
1230
- }
1231
- };
1232
- function u64FromBody(body, parsed, field) {
1233
- const value = parsed[field];
1234
- if (value === void 0 || value === null) return void 0;
1235
- const digits = new RegExp(`"${field}"\\s*:\\s*(\\d+)`).exec(body);
1236
- if (digits) return BigInt(digits[1]);
1237
- if (typeof value === "string" && /^\d+$/.test(value)) return BigInt(value);
1238
- throw new Error(`warrant-nonce response field \`${field}\` is not a u64: ${JSON.stringify(value)}`);
1239
- }
1240
- function parseWarrantNonce(body) {
1241
- const parsed = JSON.parse(body);
1242
- const data = parsed?.data;
1243
- if (!data || typeof data !== "object") {
1244
- throw new Error(`warrant-nonce response had no \`data\`: ${body.slice(0, 200)}`);
1245
- }
1246
- const common = {
1247
- contextId: String(data.contextId ?? ""),
1248
- authorDeviceKey: String(data.authorDeviceKey ?? ""),
1249
- seen: data.seen === true,
1250
- highWaterNonce: u64FromBody(body, data, "highWaterNonce"),
1251
- // Not defaulted: a node that stopped sending it is a node whose window this
1252
- // client would be guessing at, and the window is not a nonce input anyway.
1253
- windowWidth: u64FromBody(body, data, "windowWidth") ?? 0n
1254
- };
1255
- const nextNonce = u64FromBody(body, data, "nextNonce");
1256
- if (nextNonce === void 0) {
1257
- return {
1258
- ...common,
1259
- kind: "exhausted",
1260
- seen: true,
1261
- highWaterNonce: common.highWaterNonce ?? 0xffffffffffffffffn
1262
- };
1263
- }
1264
- return { ...common, kind: "open", nextNonce };
1265
- }
1266
1222
  function unwrap(response) {
1267
1223
  return response?.data;
1268
1224
  }
@@ -1476,14 +1432,6 @@ var AdminApiClient = class {
1476
1432
  );
1477
1433
  return r ?? { contextId, resyncStarted: true };
1478
1434
  }
1479
- async inviteSpecializedNode(request) {
1480
- return unwrap(
1481
- await this.httpClient.post(
1482
- "/admin-api/contexts/invite-specialized-node",
1483
- request
1484
- )
1485
- );
1486
- }
1487
1435
  async updateContextApplication(contextId, request) {
1488
1436
  await this.httpClient.post(`/admin-api/contexts/${pathSegment(contextId)}/application`, request);
1489
1437
  }
@@ -1675,29 +1623,6 @@ var AdminApiClient = class {
1675
1623
  async getNodeIdentity() {
1676
1624
  return unwrap(await this.httpClient.get("/admin-api/identity"));
1677
1625
  }
1678
- /**
1679
- * @deprecated Use {@link getNodeIdentity} instead. Every field this returns is
1680
- * node-level, so the namespace was only ever decoration — it now delegates to
1681
- * the node-level route and reshapes the answer.
1682
- *
1683
- * Three consequences of that delegation, all visible to a caller who relied on
1684
- * the old route's behaviour:
1685
- *
1686
- * - **No participation gate.** The namespace route answered 404 when this node
1687
- * had never joined the namespace; this answers whatever the node's identity
1688
- * is, regardless. If you were using it to ask "am I in this namespace?", ask
1689
- * {@link listNamespaces} instead — that is the question you were really
1690
- * asking.
1691
- * - **`namespaceId` is echoed verbatim.** The old route resolved a subgroup id
1692
- * to its root namespace and returned that; passing a subgroup id here gives
1693
- * the subgroup id back.
1694
- * - **404 means something else.** Previously "not my namespace", now "this node
1695
- * holds no account root yet".
1696
- */
1697
- async getNamespaceIdentity(namespaceId) {
1698
- const node = await this.getNodeIdentity();
1699
- return { namespaceId, publicKey: node.publicKey, account: node.accountId };
1700
- }
1701
1626
  /**
1702
1627
  * Ask this node to run one method on a member's behalf, under a warrant that
1703
1628
  * member signed.
@@ -2171,104 +2096,6 @@ var AdminApiClient = class {
2171
2096
  );
2172
2097
  return response.members;
2173
2098
  }
2174
- /**
2175
- * Where `authorDeviceKey` stands in its warrant-nonce sequence in `contextId`.
2176
- *
2177
- * # What this is for
2178
- *
2179
- * The nonce is the one input to a warrant that is stateful on the *client*.
2180
- * Everything else an author needs it either holds (its keys, the method, the
2181
- * args) or can read from the relay ({@link RelayClient.describe} gives the
2182
- * executor account and the grant). So an author that has lost its counter —
2183
- * a browser keyholder in partitioned or periodically cleared storage, which
2184
- * is its normal lifecycle rather than a fault — otherwise has exactly one
2185
- * strategy: guess upward and burn a refused round trip per wrong guess, with
2186
- * each refusal indistinguishable at the API from a genuinely spent nonce.
2187
- *
2188
- * Read {@link WarrantNonceOpen.nextNonce} and mint there. If you still hold
2189
- * your own counter, mint at `max(ownNext, nextNonce)`: nonce state folds per
2190
- * peer, so a node can only be BEHIND a client whose warrants it has not
2191
- * applied — never ahead of the truth. An author spread across several relays
2192
- * asks each and takes the highest answer. {@link createRecoveringNonceSource}
2193
- * does all of that.
2194
- *
2195
- * Purely a read. It spends nothing and advances nothing, so it may be called
2196
- * as often as you like.
2197
- *
2198
- * # Forward compatibility — this route is NEW
2199
- *
2200
- * It is not on core `master` yet, so a current node answers **404**, which
2201
- * this method surfaces as {@link WarrantNonceRouteUnavailableError} rather
2202
- * than as an opaque {@link HTTPError}. That distinction matters because the
2203
- * handler itself never 404s: a device with no row is a `200` with
2204
- * `seen: false`, and a malformed id is a `400`. So a 404 here means the node
2205
- * does not serve the route at all — an older build, or a deployment that has
2206
- * not re-mounted it — and the answer is to fall back to a local counter, not
2207
- * to retry.
2208
- *
2209
- * # Authentication
2210
- *
2211
- * On the PROTECTED admin router, unlike the two delegated-execution routes it
2212
- * sits beside: it takes another principal's device key as input and reports
2213
- * that principal's activity. A `--public-intents` deployment does not serve
2214
- * this anonymously, so an admin credential is required.
2215
- *
2216
- * @param authorDeviceKey the device's signing key, base58 (43-44 chars) or
2217
- * hex (exactly 64). The node accepts either; the two never collide.
2218
- */
2219
- async getWarrantNonce(contextId, authorDeviceKey) {
2220
- const path = `/admin-api/contexts/${pathSegment(contextId)}/warrant-nonce/${pathSegment(authorDeviceKey)}`;
2221
- let raw;
2222
- try {
2223
- raw = await this.httpClient.get(path, { parse: "text" });
2224
- } catch (error) {
2225
- if (error instanceof HTTPError && error.status === 404) {
2226
- throw new WarrantNonceRouteUnavailableError(path, error);
2227
- }
2228
- throw error;
2229
- }
2230
- return parseWarrantNonce(raw);
2231
- }
2232
- /**
2233
- * The same answer, asked for the way a delegated client has to ask.
2234
- *
2235
- * {@link getWarrantNonce} is on the protected admin router, so a keyholder
2236
- * writing through a relay — the client this whole facility exists for — is
2237
- * refused it: measured `403` for a session carrying exactly
2238
- * `context:intent`, `context:query`, `context:subscribe`. That client has no
2239
- * admin credential and cannot be given one without defeating the point of the
2240
- * delegated surface.
2241
- *
2242
- * What it does have is the credential proving the device is its own, which it
2243
- * already sends on every write. Presenting it here makes the request
2244
- * self-scoped: you learn a device's position only by proving, at call time,
2245
- * that the device is yours. So there is no oracle to protect against, and no
2246
- * reason for an admin gate.
2247
- *
2248
- * The device key is NOT a parameter. It is read from the verified
2249
- * certificate server-side, which is the only reading that cannot disagree
2250
- * with the signature — a key supplied alongside would be a second spelling of
2251
- * the same fact, and two spellings can differ.
2252
- *
2253
- * Returns the identical shape {@link getWarrantNonce} returns, so a caller can
2254
- * swap one for the other without touching what it does with the answer.
2255
- *
2256
- * @param authorProof hex-encoded borsh `AccountProof<DeviceCert>` — the same
2257
- * value passed as `authorProof` to the relay client.
2258
- */
2259
- async getWarrantNonceAsAuthor(contextId, authorProof) {
2260
- const path = `/admin-api/contexts/${pathSegment(contextId)}/warrant-nonce`;
2261
- let raw;
2262
- try {
2263
- raw = await this.httpClient.post(path, { authorProof }, { parse: "text" });
2264
- } catch (error) {
2265
- if (error instanceof HTTPError && error.status === 404) {
2266
- throw new WarrantNonceRouteUnavailableError(path, error);
2267
- }
2268
- throw error;
2269
- }
2270
- return parseWarrantNonce(raw);
2271
- }
2272
2099
  /**
2273
2100
  * Member accounts in the group and the devices bound to each. An admin node
2274
2101
  * sees every account; a plain member only its own. `403` if this node is not a
@@ -2591,15 +2418,6 @@ var AdminApiClient = class {
2591
2418
  async teeAttest(request) {
2592
2419
  return unwrap(await this.httpClient.post("/admin-api/tee/attest", request));
2593
2420
  }
2594
- /**
2595
- * @deprecated Nodes no longer serve `/admin-api/tee/verify-quote` (removed in
2596
- * core#3262), so this fails. Verify quotes with `createQuoteVerifier`.
2597
- */
2598
- async teeVerifyQuote(request) {
2599
- return unwrap(
2600
- await this.httpClient.post("/admin-api/tee/verify-quote", request)
2601
- );
2602
- }
2603
2421
  // ---- Network ----
2604
2422
  async getPeersCount() {
2605
2423
  return this.httpClient.get("/admin-api/peers");
@@ -3028,7 +2846,8 @@ var SseClient = class {
3028
2846
  return;
3029
2847
  }
3030
2848
  this.closed = false;
3031
- this.abortController = new AbortController();
2849
+ const controller = new AbortController();
2850
+ this.abortController = controller;
3032
2851
  try {
3033
2852
  const url2 = `${this.baseUrl}/sse`;
3034
2853
  const headers = {
@@ -3038,7 +2857,7 @@ var SseClient = class {
3038
2857
  const proofAttached = carriesProof(headers);
3039
2858
  const response = await this.fetchImpl(url2, {
3040
2859
  headers,
3041
- signal: this.abortController.signal,
2860
+ signal: controller.signal,
3042
2861
  ...proofAttached && { redirect: "manual" }
3043
2862
  });
3044
2863
  if (proofAttached) refuseProofRedirect(response, url2);
@@ -3066,21 +2885,31 @@ var SseClient = class {
3066
2885
  if (!response.body) {
3067
2886
  throw new Error("SSE response has no body");
3068
2887
  }
3069
- this.readStream(response.body).catch((err) => {
3070
- if (this.closed) return;
2888
+ this.readStream(response.body, controller).catch((err) => {
2889
+ if (this.superseded(controller)) return;
3071
2890
  const error = err instanceof Error ? err : new Error(String(err));
3072
2891
  this.emit("error", error);
3073
2892
  this.scheduleReconnect();
3074
2893
  });
3075
2894
  } catch (err) {
3076
- if (this.closed) return;
2895
+ if (this.superseded(controller)) return;
3077
2896
  const error = err instanceof Error ? err : new Error(String(err));
3078
2897
  this.emit("error", error);
3079
2898
  if (error instanceof AuthRevokedError) return;
3080
2899
  this.scheduleReconnect();
3081
2900
  }
3082
2901
  }
3083
- async readStream(body) {
2902
+ /**
2903
+ * Whether `controller`'s connection is no longer the one this client holds:
2904
+ * closed, or replaced by a later connect. Its abort lands asynchronously,
2905
+ * after a `close()` + `connect()` has already cleared `closed`, so `closed`
2906
+ * alone cannot tell; reporting it, and reconnecting for it, would abort the
2907
+ * live connection, whose own abort would do the same, without end.
2908
+ */
2909
+ superseded(controller) {
2910
+ return this.closed || controller.signal.aborted || this.abortController !== controller;
2911
+ }
2912
+ async readStream(body, controller) {
3084
2913
  const reader = body.getReader();
3085
2914
  const decoder = new TextDecoder();
3086
2915
  let buffer = "";
@@ -3108,11 +2937,11 @@ var SseClient = class {
3108
2937
  }
3109
2938
  }
3110
2939
  } catch (err) {
3111
- if (this.closed) return;
2940
+ if (this.superseded(controller)) return;
3112
2941
  const error = err instanceof Error ? err : new Error(String(err));
3113
2942
  this.emit("error", error);
3114
2943
  }
3115
- if (!this.closed) {
2944
+ if (!this.superseded(controller)) {
3116
2945
  this.scheduleReconnect();
3117
2946
  }
3118
2947
  }
@@ -9772,81 +9601,6 @@ function createLocalStorageNonceSource(key, storage) {
9772
9601
  }
9773
9602
  };
9774
9603
  }
9775
- function authorNonceLookup(options) {
9776
- return {
9777
- getWarrantNonce: (contextId, _authorDeviceKey) => options.client.getWarrantNonceAsAuthor(contextId, options.authorProof)
9778
- };
9779
- }
9780
- var WarrantNonceExhaustedError = class extends Error {
9781
- constructor(contextId, authorDeviceKey) {
9782
- super(
9783
- `author device ${authorDeviceKey} has spent every nonce in context ${contextId}; it must re-key to write again`
9784
- );
9785
- this.contextId = contextId;
9786
- this.authorDeviceKey = authorDeviceKey;
9787
- this.name = "WarrantNonceExhaustedError";
9788
- }
9789
- };
9790
- function createRecoveringNonceSource(options) {
9791
- const lookups = Array.isArray(options.lookups) ? options.lookups : [options.lookups];
9792
- if (lookups.length === 0) {
9793
- throw new Error("a recovering nonce source needs at least one node to ask");
9794
- }
9795
- const local = options.local ?? createMemoryNonceSource(1);
9796
- const onLookupFailure = options.onLookupFailure ?? "throw";
9797
- let floor = null;
9798
- let pending = null;
9799
- const resolveFloor = async () => {
9800
- const answers = await Promise.allSettled(
9801
- lookups.map(
9802
- (lookup) => lookup.getWarrantNonce(options.contextId, options.authorDeviceKey)
9803
- )
9804
- );
9805
- let highest = null;
9806
- let exhausted = false;
9807
- for (const answer of answers) {
9808
- if (answer.status !== "fulfilled") continue;
9809
- const state = answer.value;
9810
- if (state.kind === "exhausted") {
9811
- exhausted = true;
9812
- continue;
9813
- }
9814
- if (highest === null || state.nextNonce > highest) highest = state.nextNonce;
9815
- }
9816
- if (highest === null && exhausted) {
9817
- throw new WarrantNonceExhaustedError(options.contextId, options.authorDeviceKey);
9818
- }
9819
- if (highest === null) {
9820
- const first = answers.find((a) => a.status === "rejected");
9821
- const reason = first && first.status === "rejected" ? first.reason : void 0;
9822
- if (onLookupFailure === "local") {
9823
- floor = 0n;
9824
- return;
9825
- }
9826
- throw reason instanceof Error ? reason : new Error(`no node could answer where the warrant-nonce sequence stands: ${String(reason)}`);
9827
- }
9828
- floor = highest;
9829
- await local.advanceTo?.(highest);
9830
- };
9831
- return {
9832
- async next() {
9833
- if (floor === null) {
9834
- pending ?? (pending = resolveFloor().finally(() => {
9835
- pending = null;
9836
- }));
9837
- await pending;
9838
- }
9839
- const mine = await local.next();
9840
- const value = floor !== null && floor > mine ? floor : mine;
9841
- floor = value + 1n;
9842
- return value;
9843
- },
9844
- async advanceTo(wanted) {
9845
- if (floor === null || wanted > floor) floor = wanted;
9846
- await local.advanceTo?.(wanted);
9847
- }
9848
- };
9849
- }
9850
9604
 
9851
9605
  // src/cloud/connect.ts
9852
9606
  async function connectCloud(options) {
@@ -13296,8 +13050,6 @@ export {
13296
13050
  SseClient,
13297
13051
  StaleTransportKeyError,
13298
13052
  UnknownRelayError,
13299
- WarrantNonceExhaustedError,
13300
- WarrantNonceRouteUnavailableError,
13301
13053
  WebHttpClient,
13302
13054
  WsClient,
13303
13055
  accountForRoot,
@@ -13314,7 +13066,6 @@ export {
13314
13066
  attestAgent,
13315
13067
  attestKeyBinding,
13316
13068
  attestRelayNodeKey,
13317
- authorNonceLookup,
13318
13069
  beginCloudLink,
13319
13070
  beginDeviceEnrolment,
13320
13071
  bootstrapFromInvitation,
@@ -13365,7 +13116,6 @@ export {
13365
13116
  createProofAuthorizer,
13366
13117
  createProofSigner,
13367
13118
  createQuoteVerifier,
13368
- createRecoveringNonceSource,
13369
13119
  createRetryableMethod,
13370
13120
  createSealedFetch,
13371
13121
  createSignedReleaseSealedFetch,
@@ -13439,7 +13189,6 @@ export {
13439
13189
  parseCreationWarrant,
13440
13190
  parseDeviceCredential,
13441
13191
  parseGovernanceWarrant,
13442
- parseWarrantNonce,
13443
13192
  persistedNonces,
13444
13193
  pinRelayNodeKey,
13445
13194
  presenceStatementBytes,