@calimero-network/mero-js 24.6.0 → 24.7.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
@@ -166,6 +166,7 @@ __export(index_exports, {
166
166
  fetchNodeReleaseVersion: () => fetchNodeReleaseVersion,
167
167
  fetchRegistryBundles: () => fetchRegistryBundles,
168
168
  forgetMethodKinds: () => forgetMethodKinds,
169
+ forgetRelaySealing: () => forgetRelaySealing,
169
170
  foundDelegatedNamespace: () => foundDelegatedNamespace,
170
171
  foundedNamespaceId: () => foundedNamespaceId,
171
172
  generateAccountRoot: () => generateAccountRoot,
@@ -226,6 +227,7 @@ __export(index_exports, {
226
227
  readRelayMap: () => readRelayMap,
227
228
  redeemInvitation: () => redeemInvitation,
228
229
  relayForContext: () => relayForContext,
230
+ relayTransportFetch: () => relayTransportFetch,
229
231
  rememberGroupNamespace: () => rememberGroupNamespace,
230
232
  rememberRelay: () => rememberRelay,
231
233
  reportDataOf: () => reportDataOf,
@@ -9118,7 +9120,7 @@ function missingNodeKey(surface) {
9118
9120
  `this relay-transport client cannot open ${surface}: it was given no relay node key. A relay is an ordinary node and does serve /sse and /ws \u2014 but the login statement must be signed against the node's device signing key, learned out of band (never read from the node, and never derived from its peerId). Pass it as \`observe: { nodeKey }\`. For a cloud-hosted relay the cloud does not publish it yet \u2014 see mdma #312.`
9119
9121
  );
9120
9122
  }
9121
- function buildObserver(relay, observe) {
9123
+ function buildObserver(relay, observe, fetchImpl) {
9122
9124
  const nodeKey = observe?.nodeKey;
9123
9125
  if (!nodeKey) return null;
9124
9126
  return new RelayObserver({
@@ -9129,7 +9131,7 @@ function buildObserver(relay, observe) {
9129
9131
  signer: observe?.signer ? async () => observe.signer : () => relay.authorSigner(),
9130
9132
  clientName: observe?.clientName,
9131
9133
  ttlSeconds: observe?.ttlSeconds,
9132
- fetch: observe?.fetch,
9134
+ fetch: observe?.fetch ?? fetchImpl,
9133
9135
  timeoutMs: observe?.timeoutMs
9134
9136
  });
9135
9137
  }
@@ -9150,12 +9152,16 @@ var MeroClient = class {
9150
9152
  this.proofAuthorizer = null;
9151
9153
  this.relayUrl = null;
9152
9154
  this.proofEvents = null;
9155
+ /** The relay transport's `fetch`, if one was given. */
9156
+ this.relayFetch = void 0;
9153
9157
  this.relayPresence = null;
9154
9158
  if (config.transport === "relay") {
9155
9159
  this.transport = "relay";
9156
9160
  this.nodeClient = null;
9157
- const relay = config.relay instanceof RelayClient ? config.relay : new RelayClient(config.relay);
9158
- this.relayObserver = buildObserver(relay, config.observe);
9161
+ const fetchImpl = config.fetch;
9162
+ this.relayFetch = fetchImpl;
9163
+ const relay = config.relay instanceof RelayClient ? config.relay : new RelayClient({ ...config.relay, fetch: config.relay.fetch ?? fetchImpl });
9164
+ this.relayObserver = buildObserver(relay, config.observe, fetchImpl);
9159
9165
  this.rpcTransport = new RelayTransport(relay, this.relayObserver);
9160
9166
  this.relayUrl = relay.relayUrl;
9161
9167
  if (config.proof) {
@@ -9165,13 +9171,14 @@ var MeroClient = class {
9165
9171
  const token = config.session;
9166
9172
  this.relayAdmin = createAdminApiClient({
9167
9173
  baseUrl: relay.relayUrl,
9168
- getAuthToken: typeof token === "string" ? async () => token : token
9174
+ getAuthToken: typeof token === "string" ? async () => token : token,
9175
+ fetch: fetchImpl
9169
9176
  });
9170
9177
  } else if (this.proofAuthorizer) {
9171
9178
  const authorizeRequest = this.proofAuthorizer;
9172
9179
  this.relayAdmin = createAdminApiClientFromHttpClient(
9173
9180
  createHttpClient({
9174
- fetch: (url, init) => globalThis.fetch(url, init),
9181
+ fetch: (url, init) => fetchImpl ? fetchImpl(url, init) : globalThis.fetch(url, init),
9175
9182
  baseUrl: relay.relayUrl,
9176
9183
  authorizeRequest
9177
9184
  }),
@@ -9285,7 +9292,8 @@ var MeroClient = class {
9285
9292
  if (this.proofAuthorizer && this.relayUrl) {
9286
9293
  this.proofEvents ?? (this.proofEvents = new SseClient({
9287
9294
  baseUrl: this.relayUrl,
9288
- authorize: this.proofAuthorizer
9295
+ authorize: this.proofAuthorizer,
9296
+ fetch: this.relayFetch
9289
9297
  }));
9290
9298
  return this.proofEvents;
9291
9299
  }
@@ -9535,8 +9543,8 @@ function relayQuoteVerifier(relayUrl, fetchRelay) {
9535
9543
  };
9536
9544
  return Object.assign(verify, { includeCollateral: true });
9537
9545
  }
9538
- async function attemptRelayNodeKey(relayUrl) {
9539
- const pinned = readPinnedRelayNodeKey(relayUrl);
9546
+ async function attemptRelayNodeKey(relayUrl, options = {}) {
9547
+ const pinned = options.fresh ? null : readPinnedRelayNodeKey(relayUrl);
9540
9548
  if (pinned) return { kind: "learned", nodeKey: pinned };
9541
9549
  const marked = { failure: null };
9542
9550
  const fetchRelay = relayFetch(marked);
@@ -9600,6 +9608,54 @@ function jwtExpiryMs2(token) {
9600
9608
  return null;
9601
9609
  }
9602
9610
  }
9611
+ var sealedFetches = /* @__PURE__ */ new Map();
9612
+ function forgetRelaySealing() {
9613
+ sealedFetches.clear();
9614
+ }
9615
+ async function isUnguardedRefusal(response) {
9616
+ if (response.status !== 403) return false;
9617
+ try {
9618
+ const body = await response.clone().json();
9619
+ return body.error?.code === "sealed_route_unguarded";
9620
+ } catch {
9621
+ return false;
9622
+ }
9623
+ }
9624
+ function needsSession(request) {
9625
+ return request.headers.has("authorization") || new URL(request.url).pathname.startsWith("/auth/");
9626
+ }
9627
+ function sealedRelayFetch(relayUrl) {
9628
+ const plain = (input, init) => globalThis.fetch(input, init);
9629
+ const sealed = createAttestedSealedFetch({
9630
+ baseUrl: relayUrl,
9631
+ verify: relayQuoteVerifier(relayUrl, plain),
9632
+ fetch: plain
9633
+ });
9634
+ let unguarded = false;
9635
+ return async (input, init) => {
9636
+ const request = new Request(input, init);
9637
+ if (unguarded && needsSession(request)) return plain(request);
9638
+ const response = await sealed(request.clone());
9639
+ if (!await isUnguardedRefusal(response)) return response;
9640
+ if (!unguarded) {
9641
+ unguarded = true;
9642
+ console.warn(
9643
+ `[mero-js] ${relayOrigin(relayUrl)} cannot guard logged-in calls sealed (no forward_auth); they go over its TLS until it does`
9644
+ );
9645
+ }
9646
+ return plain(request);
9647
+ };
9648
+ }
9649
+ function relayTransportFetch(relayUrl) {
9650
+ if (isLoopback(relayUrl)) return void 0;
9651
+ const origin2 = relayOrigin(relayUrl);
9652
+ let found = sealedFetches.get(origin2);
9653
+ if (!found) {
9654
+ found = sealedRelayFetch(relayUrl);
9655
+ sealedFetches.set(origin2, found);
9656
+ }
9657
+ return found;
9658
+ }
9603
9659
  function relaySession(s, nodeKey) {
9604
9660
  let token = null;
9605
9661
  let inflight = null;
@@ -9609,6 +9665,8 @@ function relaySession(s, nodeKey) {
9609
9665
  if (!inflight) {
9610
9666
  inflight = login({
9611
9667
  nodeUrl: s.relayUrl,
9668
+ // Sealed: the token is the session, and the TLS ends outside the TD.
9669
+ fetch: relayTransportFetch(s.relayUrl),
9612
9670
  node: nodeKey,
9613
9671
  deviceSecret: s.deviceSecret,
9614
9672
  accountProof: s.credential,
@@ -9704,6 +9762,9 @@ function buildDelegatedClient(s, _contextId) {
9704
9762
  const nodeKey = readPinnedRelayNodeKey(s.relayUrl);
9705
9763
  const client2 = createMeroClient({
9706
9764
  transport: "relay",
9765
+ // Every request sealed to the relay's attested TD: intents, admin reads,
9766
+ // queries, the login and events (see `relayTransportFetch`).
9767
+ fetch: relayTransportFetch(s.relayUrl),
9707
9768
  relay: {
9708
9769
  relayUrl: s.relayUrl,
9709
9770
  authorAccount: s.account,
@@ -10793,7 +10854,7 @@ function client(s, relayUrl, nonces, fetch2) {
10793
10854
  authorProof: s.credential,
10794
10855
  deviceSecret: s.deviceSecret,
10795
10856
  nonces,
10796
- fetch: fetch2
10857
+ fetch: fetch2 ?? relayTransportFetch(relayUrl)
10797
10858
  });
10798
10859
  }
10799
10860
  async function createDelegatedContext(s, req, deps = {}) {
@@ -10880,10 +10941,10 @@ async function foundingExecutor(s, relay, fetch2) {
10880
10941
  return { executorAccount: described.executorAccount, executorKey };
10881
10942
  }
10882
10943
  async function attestedNodeKey(relay) {
10883
- const key = await attemptRelayNodeKey(relay);
10944
+ const key = await attemptRelayNodeKey(relay, { fresh: true });
10884
10945
  if (key.kind === "learned") return key.nodeKey;
10885
10946
  throw new Error(
10886
- key.kind === "unavailable" ? "the relay's signing key could not be learned: its attestation did not answer, try again" : "the relay's attestation was refused, so founding through it is not possible (only a key from its operator, pinned with pinRelayNodeKey, can stand in)"
10947
+ key.kind === "unavailable" ? "the relay's signing key could not be learned: its attestation did not answer, try again" : "the relay's attestation was refused, so founding through it is not possible: it is not a signed locked-read-only release"
10887
10948
  );
10888
10949
  }
10889
10950
  var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so invitees can find this namespace";