@calimero-network/mero-js 19.7.2 → 19.8.1

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
@@ -4749,6 +4749,38 @@ function base64(bytes) {
4749
4749
  return btoa(binary);
4750
4750
  }
4751
4751
 
4752
+ // src/cloud/routing-proof.ts
4753
+ var ROUTING_PROOF_DOMAIN = new TextEncoder().encode(
4754
+ "calimero.mdma.routing-read.v1\0"
4755
+ );
4756
+ async function signRoutingChallenge(nonce, deviceSecret) {
4757
+ fromHex(deviceSecret, "deviceSecret", 32);
4758
+ const key = await importSigningKey(deviceSecret);
4759
+ const message = concat(
4760
+ ROUTING_PROOF_DOMAIN,
4761
+ new TextEncoder().encode(nonce)
4762
+ );
4763
+ const signature = new Uint8Array(
4764
+ await crypto.subtle.sign({ name: "Ed25519" }, key, message)
4765
+ );
4766
+ return base642(signature);
4767
+ }
4768
+ async function routingProofHeaders(challenge, credential) {
4769
+ return {
4770
+ "X-Calimero-Credential": credential.credential,
4771
+ "X-Calimero-Nonce": challenge.nonce,
4772
+ "X-Calimero-Signature": await signRoutingChallenge(
4773
+ challenge.nonce,
4774
+ credential.deviceSecret
4775
+ )
4776
+ };
4777
+ }
4778
+ function base642(bytes) {
4779
+ let binary = "";
4780
+ for (const byte of bytes) binary += String.fromCharCode(byte);
4781
+ return btoa(binary);
4782
+ }
4783
+
4752
4784
  // src/cloud/cloud-client.ts
4753
4785
  var DEFAULT_CLOUD_BASE_URL = "https://cloud.calimero.network";
4754
4786
  var REFRESH_HEADER = "x-mdma-session-refresh";
@@ -4761,6 +4793,7 @@ var CloudClient = class {
4761
4793
  const injected = config.fetch;
4762
4794
  this.fetchImpl = injected ? (input, init) => injected(input, init) : (input, init) => globalThis.fetch(input, init);
4763
4795
  this.timeoutMs = config.timeoutMs ?? 1e4;
4796
+ this.routingCredential = config.routingCredential;
4764
4797
  if (config.sessionToken) {
4765
4798
  this.session = {
4766
4799
  sessionToken: config.sessionToken,
@@ -4887,7 +4920,8 @@ var CloudClient = class {
4887
4920
  */
4888
4921
  async getNamespaceRouting(namespaceId) {
4889
4922
  const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/admitters`, {
4890
- anonymous: true
4923
+ anonymous: true,
4924
+ headers: await this.routingProof(namespaceId)
4891
4925
  });
4892
4926
  return {
4893
4927
  namespaceId: String(body.namespace_id ?? namespaceId),
@@ -4906,6 +4940,41 @@ var CloudClient = class {
4906
4940
  writable: body.writable === true
4907
4941
  };
4908
4942
  }
4943
+ /**
4944
+ * Ask the cloud for a challenge to prove an account against.
4945
+ *
4946
+ * Public and unauthenticated on purpose, and safe to be: the nonce is sealed
4947
+ * and bound to this one namespace, so it is not a capability — it is the
4948
+ * thing a capability gets demonstrated against. A caller with no credential
4949
+ * gains nothing by holding one.
4950
+ *
4951
+ * {@link getNamespaceRouting} calls this for you when the client was
4952
+ * constructed with a `routingCredential`. Call it directly only to sign a
4953
+ * challenge somewhere this client cannot reach the key.
4954
+ */
4955
+ async getRoutingChallenge(namespaceId) {
4956
+ const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/challenge`, {
4957
+ anonymous: true
4958
+ });
4959
+ return {
4960
+ namespaceId: String(body.namespace_id ?? namespaceId),
4961
+ nonce: String(body.nonce ?? ""),
4962
+ expiresAtMs: Number(body.expires_at_ms ?? 0)
4963
+ };
4964
+ }
4965
+ /**
4966
+ * The proof headers for one routing read, or nothing when no credential was
4967
+ * configured.
4968
+ *
4969
+ * A challenge per read rather than a cached one: they expire in about two
4970
+ * minutes and are single-namespace, so caching would trade one cheap request
4971
+ * for a class of expiry bug that only shows up on a slow client.
4972
+ */
4973
+ async routingProof(namespaceId) {
4974
+ if (!this.routingCredential) return void 0;
4975
+ const challenge = await this.getRoutingChallenge(namespaceId);
4976
+ return routingProofHeaders(challenge, this.routingCredential);
4977
+ }
4909
4978
  /**
4910
4979
  * The node to send a signed join to, chosen from those an invitation names.
4911
4980
  *
@@ -5159,7 +5228,10 @@ var CloudClient = class {
5159
5228
  "Not signed in to Calimero Cloud: call signInWithGoogle() or construct the client with a sessionToken"
5160
5229
  );
5161
5230
  }
5162
- const headers = { Accept: "application/json" };
5231
+ const headers = {
5232
+ ...options.headers,
5233
+ Accept: "application/json"
5234
+ };
5163
5235
  if (options.body !== void 0) headers["Content-Type"] = "application/json";
5164
5236
  if (!options.anonymous && this.session) {
5165
5237
  headers.Authorization = `Bearer ${this.session.sessionToken}`;
@@ -6314,10 +6386,12 @@ export {
6314
6386
  nodeEndpoint,
6315
6387
  parseAuthCallback,
6316
6388
  probeNodeHealth,
6389
+ routingProofHeaders,
6317
6390
  signAccountLink,
6318
6391
  signDeviceCert,
6319
6392
  signLoginStatement,
6320
6393
  signMemberJoinOp,
6394
+ signRoutingChallenge,
6321
6395
  signWarrant,
6322
6396
  withCap,
6323
6397
  withRetry,