@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.cjs CHANGED
@@ -76,10 +76,12 @@ __export(index_exports, {
76
76
  nodeEndpoint: () => nodeEndpoint,
77
77
  parseAuthCallback: () => parseAuthCallback,
78
78
  probeNodeHealth: () => probeNodeHealth,
79
+ routingProofHeaders: () => routingProofHeaders,
79
80
  signAccountLink: () => signAccountLink,
80
81
  signDeviceCert: () => signDeviceCert,
81
82
  signLoginStatement: () => signLoginStatement,
82
83
  signMemberJoinOp: () => signMemberJoinOp,
84
+ signRoutingChallenge: () => signRoutingChallenge,
83
85
  signWarrant: () => signWarrant,
84
86
  withCap: () => withCap,
85
87
  withRetry: () => withRetry,
@@ -4838,6 +4840,38 @@ function base64(bytes) {
4838
4840
  return btoa(binary);
4839
4841
  }
4840
4842
 
4843
+ // src/cloud/routing-proof.ts
4844
+ var ROUTING_PROOF_DOMAIN = new TextEncoder().encode(
4845
+ "calimero.mdma.routing-read.v1\0"
4846
+ );
4847
+ async function signRoutingChallenge(nonce, deviceSecret) {
4848
+ fromHex(deviceSecret, "deviceSecret", 32);
4849
+ const key = await importSigningKey(deviceSecret);
4850
+ const message = concat(
4851
+ ROUTING_PROOF_DOMAIN,
4852
+ new TextEncoder().encode(nonce)
4853
+ );
4854
+ const signature = new Uint8Array(
4855
+ await crypto.subtle.sign({ name: "Ed25519" }, key, message)
4856
+ );
4857
+ return base642(signature);
4858
+ }
4859
+ async function routingProofHeaders(challenge, credential) {
4860
+ return {
4861
+ "X-Calimero-Credential": credential.credential,
4862
+ "X-Calimero-Nonce": challenge.nonce,
4863
+ "X-Calimero-Signature": await signRoutingChallenge(
4864
+ challenge.nonce,
4865
+ credential.deviceSecret
4866
+ )
4867
+ };
4868
+ }
4869
+ function base642(bytes) {
4870
+ let binary = "";
4871
+ for (const byte of bytes) binary += String.fromCharCode(byte);
4872
+ return btoa(binary);
4873
+ }
4874
+
4841
4875
  // src/cloud/cloud-client.ts
4842
4876
  var DEFAULT_CLOUD_BASE_URL = "https://cloud.calimero.network";
4843
4877
  var REFRESH_HEADER = "x-mdma-session-refresh";
@@ -4850,6 +4884,7 @@ var CloudClient = class {
4850
4884
  const injected = config.fetch;
4851
4885
  this.fetchImpl = injected ? (input, init) => injected(input, init) : (input, init) => globalThis.fetch(input, init);
4852
4886
  this.timeoutMs = config.timeoutMs ?? 1e4;
4887
+ this.routingCredential = config.routingCredential;
4853
4888
  if (config.sessionToken) {
4854
4889
  this.session = {
4855
4890
  sessionToken: config.sessionToken,
@@ -4976,7 +5011,8 @@ var CloudClient = class {
4976
5011
  */
4977
5012
  async getNamespaceRouting(namespaceId) {
4978
5013
  const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/admitters`, {
4979
- anonymous: true
5014
+ anonymous: true,
5015
+ headers: await this.routingProof(namespaceId)
4980
5016
  });
4981
5017
  return {
4982
5018
  namespaceId: String(body.namespace_id ?? namespaceId),
@@ -4995,6 +5031,41 @@ var CloudClient = class {
4995
5031
  writable: body.writable === true
4996
5032
  };
4997
5033
  }
5034
+ /**
5035
+ * Ask the cloud for a challenge to prove an account against.
5036
+ *
5037
+ * Public and unauthenticated on purpose, and safe to be: the nonce is sealed
5038
+ * and bound to this one namespace, so it is not a capability — it is the
5039
+ * thing a capability gets demonstrated against. A caller with no credential
5040
+ * gains nothing by holding one.
5041
+ *
5042
+ * {@link getNamespaceRouting} calls this for you when the client was
5043
+ * constructed with a `routingCredential`. Call it directly only to sign a
5044
+ * challenge somewhere this client cannot reach the key.
5045
+ */
5046
+ async getRoutingChallenge(namespaceId) {
5047
+ const body = await this.request("GET", `/api/cloud/namespaces/${encodeURIComponent(namespaceId)}/challenge`, {
5048
+ anonymous: true
5049
+ });
5050
+ return {
5051
+ namespaceId: String(body.namespace_id ?? namespaceId),
5052
+ nonce: String(body.nonce ?? ""),
5053
+ expiresAtMs: Number(body.expires_at_ms ?? 0)
5054
+ };
5055
+ }
5056
+ /**
5057
+ * The proof headers for one routing read, or nothing when no credential was
5058
+ * configured.
5059
+ *
5060
+ * A challenge per read rather than a cached one: they expire in about two
5061
+ * minutes and are single-namespace, so caching would trade one cheap request
5062
+ * for a class of expiry bug that only shows up on a slow client.
5063
+ */
5064
+ async routingProof(namespaceId) {
5065
+ if (!this.routingCredential) return void 0;
5066
+ const challenge = await this.getRoutingChallenge(namespaceId);
5067
+ return routingProofHeaders(challenge, this.routingCredential);
5068
+ }
4998
5069
  /**
4999
5070
  * The node to send a signed join to, chosen from those an invitation names.
5000
5071
  *
@@ -5248,7 +5319,10 @@ var CloudClient = class {
5248
5319
  "Not signed in to Calimero Cloud: call signInWithGoogle() or construct the client with a sessionToken"
5249
5320
  );
5250
5321
  }
5251
- const headers = { Accept: "application/json" };
5322
+ const headers = {
5323
+ ...options.headers,
5324
+ Accept: "application/json"
5325
+ };
5252
5326
  if (options.body !== void 0) headers["Content-Type"] = "application/json";
5253
5327
  if (!options.anonymous && this.session) {
5254
5328
  headers.Authorization = `Bearer ${this.session.sessionToken}`;