@calimero-network/mero-js 22.1.0 → 22.3.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/README.md +7 -5
- package/dist/cloud/cloud-client.d.ts +44 -14
- package/dist/cloud/cloud-client.js +82 -37
- package/dist/cloud/connect.d.ts +2 -1
- package/dist/cloud/connect.js +23 -12
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +3 -3
- package/dist/index.cjs +113 -45
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +113 -45
- package/dist/index.mjs.map +2 -2
- package/dist/sealed/release.d.ts +16 -4
- package/dist/sealed/release.js +35 -5
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5379,6 +5379,15 @@ function base642(bytes) {
|
|
|
5379
5379
|
|
|
5380
5380
|
// src/cloud/cloud-client.ts
|
|
5381
5381
|
var DEFAULT_CLOUD_BASE_URL = "https://cloud.calimero.network";
|
|
5382
|
+
function canExecuteOf(row, legacy) {
|
|
5383
|
+
return typeof row.can_execute === "boolean" ? row.can_execute : legacy;
|
|
5384
|
+
}
|
|
5385
|
+
function teeRoleOf(row) {
|
|
5386
|
+
return typeof row.tee_role === "string" && row.tee_role !== "" ? row.tee_role : null;
|
|
5387
|
+
}
|
|
5388
|
+
function pickExecutingRelay(relays) {
|
|
5389
|
+
return relays.find((r) => r.canExecute && r.relayUrl !== null && r.executorAccount !== null) ?? null;
|
|
5390
|
+
}
|
|
5382
5391
|
var REFRESH_HEADER = "x-mdma-session-refresh";
|
|
5383
5392
|
var REFRESH_EXPIRES_HEADER = "x-mdma-session-expires";
|
|
5384
5393
|
var CloudClient = class {
|
|
@@ -5527,37 +5536,48 @@ var CloudClient = class {
|
|
|
5527
5536
|
* The relays serving one namespace: where to present an intent, and as whom.
|
|
5528
5537
|
*
|
|
5529
5538
|
* Everything a client needs in order to mint a warrant it can actually
|
|
5530
|
-
* spend. Filter on `
|
|
5531
|
-
*
|
|
5532
|
-
*
|
|
5539
|
+
* spend. Filter on `canExecute` before signing — a warrant presented to a
|
|
5540
|
+
* relay that does not relay (a `ReadOnlyTee` replica, or a node with no role
|
|
5541
|
+
* and no grant) is refused *after* the author has burned a nonce from its
|
|
5542
|
+
* monotonic sequence on it.
|
|
5533
5543
|
*/
|
|
5534
5544
|
async getNamespaceRelays(namespaceId) {
|
|
5535
5545
|
const body = await this.request(
|
|
5536
5546
|
"GET",
|
|
5537
5547
|
`/api/cloud/me/namespaces/${encodeURIComponent(namespaceId)}/relays`
|
|
5538
5548
|
);
|
|
5539
|
-
return (body.relays ?? []).map((row) =>
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5549
|
+
return (body.relays ?? []).map((row) => {
|
|
5550
|
+
const relayUrl = row.relay_url ?? null;
|
|
5551
|
+
const executorAccount = row.executor_account ?? null;
|
|
5552
|
+
const authorshipReady = row.authorship_ready === true;
|
|
5553
|
+
return {
|
|
5554
|
+
peerId: String(row.peer_id ?? ""),
|
|
5555
|
+
relayUrl,
|
|
5556
|
+
executorAccount,
|
|
5557
|
+
status: String(row.status ?? ""),
|
|
5558
|
+
authorshipReady,
|
|
5559
|
+
teeRole: teeRoleOf(row),
|
|
5560
|
+
canExecute: canExecuteOf(
|
|
5561
|
+
row,
|
|
5562
|
+
authorshipReady && relayUrl !== null && executorAccount !== null
|
|
5563
|
+
),
|
|
5564
|
+
lastSeenAt: row.last_seen_at ?? null,
|
|
5565
|
+
confirmedAt: row.confirmed_at ?? null
|
|
5566
|
+
};
|
|
5567
|
+
});
|
|
5548
5568
|
}
|
|
5549
5569
|
/**
|
|
5550
5570
|
* The first relay for `namespaceId` that can actually run an intent, or
|
|
5551
5571
|
* `null`.
|
|
5552
5572
|
*
|
|
5553
|
-
* "Can actually run one" is
|
|
5554
|
-
* gets a refusal it cannot explain: the cloud must know a URL, the
|
|
5555
|
-
* have reported the executor account a warrant has to name, and
|
|
5556
|
-
* must
|
|
5573
|
+
* "Can actually run one" is several conditions, and a client that checks
|
|
5574
|
+
* fewer gets a refusal it cannot explain: the cloud must know a URL, the
|
|
5575
|
+
* relay must have reported the executor account a warrant has to name, and
|
|
5576
|
+
* the node must relay — which the cloud folds into `canExecute`.
|
|
5557
5577
|
*/
|
|
5558
5578
|
async findExecutingRelay(namespaceId) {
|
|
5559
5579
|
const relays = await this.getNamespaceRelays(namespaceId);
|
|
5560
|
-
return relays
|
|
5580
|
+
return pickExecutingRelay(relays);
|
|
5561
5581
|
}
|
|
5562
5582
|
/**
|
|
5563
5583
|
* Where to reach a namespace without owning it: one read, both verbs.
|
|
@@ -5585,17 +5605,27 @@ var CloudClient = class {
|
|
|
5585
5605
|
});
|
|
5586
5606
|
return {
|
|
5587
5607
|
namespaceId: String(body.namespace_id ?? namespaceId),
|
|
5588
|
-
nodes: (body.admitters ?? []).map((row) =>
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5608
|
+
nodes: (body.admitters ?? []).map((row) => {
|
|
5609
|
+
const account = row.account ?? null;
|
|
5610
|
+
const relayUrl = row.relay_url ?? null;
|
|
5611
|
+
const fresh = row.fresh === true;
|
|
5612
|
+
const authorshipReady = row.authorship_ready === true;
|
|
5613
|
+
return {
|
|
5614
|
+
peerId: String(row.peer_id ?? ""),
|
|
5615
|
+
account,
|
|
5616
|
+
relayUrl,
|
|
5617
|
+
admitUrl: row.admit_url ?? null,
|
|
5618
|
+
status: String(row.status ?? ""),
|
|
5619
|
+
fresh,
|
|
5620
|
+
canAdmit: row.can_admit === true,
|
|
5621
|
+
authorshipReady,
|
|
5622
|
+
teeRole: teeRoleOf(row),
|
|
5623
|
+
canExecute: canExecuteOf(
|
|
5624
|
+
row,
|
|
5625
|
+
authorshipReady && relayUrl !== null && account !== null && fresh
|
|
5626
|
+
)
|
|
5627
|
+
};
|
|
5628
|
+
}),
|
|
5599
5629
|
servable: body.servable === true,
|
|
5600
5630
|
writable: body.writable === true
|
|
5601
5631
|
};
|
|
@@ -5726,23 +5756,32 @@ var CloudClient = class {
|
|
|
5726
5756
|
"GET",
|
|
5727
5757
|
"/api/cloud/me/machines"
|
|
5728
5758
|
);
|
|
5729
|
-
return (body.machines ?? []).map((row) =>
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
canExecute: row.can_execute === true,
|
|
5734
|
-
namespaces: (Array.isArray(row.namespaces) ? row.namespaces : []).map((n) => {
|
|
5759
|
+
return (body.machines ?? []).map((row) => {
|
|
5760
|
+
const relayUrl = row.relay_url ?? null;
|
|
5761
|
+
const executorAccount = row.executor_account ?? null;
|
|
5762
|
+
const namespaces = (Array.isArray(row.namespaces) ? row.namespaces : []).map((n) => {
|
|
5735
5763
|
const ns = n;
|
|
5736
5764
|
return {
|
|
5737
5765
|
namespaceId: String(ns.namespace_id ?? ""),
|
|
5738
5766
|
status: String(ns.status ?? ""),
|
|
5739
5767
|
authorshipReady: ns.authorship_ready === true,
|
|
5768
|
+
teeRole: teeRoleOf(ns),
|
|
5740
5769
|
fresh: ns.fresh === true,
|
|
5741
5770
|
confirmedAt: ns.confirmed_at ?? null,
|
|
5742
5771
|
lastSeenAt: ns.last_seen_at ?? null
|
|
5743
5772
|
};
|
|
5744
|
-
})
|
|
5745
|
-
|
|
5773
|
+
});
|
|
5774
|
+
return {
|
|
5775
|
+
peerId: String(row.peer_id ?? ""),
|
|
5776
|
+
relayUrl,
|
|
5777
|
+
executorAccount,
|
|
5778
|
+
canExecute: canExecuteOf(
|
|
5779
|
+
row,
|
|
5780
|
+
relayUrl !== null && executorAccount !== null && namespaces.some((n) => n.authorshipReady)
|
|
5781
|
+
),
|
|
5782
|
+
namespaces
|
|
5783
|
+
};
|
|
5784
|
+
});
|
|
5746
5785
|
}
|
|
5747
5786
|
/**
|
|
5748
5787
|
* Claim a namespace under this account, proving ownership.
|
|
@@ -7282,12 +7321,12 @@ async function connectCloud(options) {
|
|
|
7282
7321
|
const namespaces = await cloud.getMyNamespaces();
|
|
7283
7322
|
const namespace = pickNamespace(namespaces, options.namespaceId);
|
|
7284
7323
|
const relays = await cloud.getNamespaceRelays(namespace.namespaceId);
|
|
7285
|
-
const relayInfo = relays
|
|
7324
|
+
const relayInfo = pickExecutingRelay(relays);
|
|
7286
7325
|
if (!relayInfo) {
|
|
7287
7326
|
throw new Error(explainNoRelay(namespace, relays));
|
|
7288
7327
|
}
|
|
7289
7328
|
const relay = new RelayClient({
|
|
7290
|
-
// Checked by `
|
|
7329
|
+
// Checked by `pickExecutingRelay`, which is the only thing that returns a
|
|
7291
7330
|
// relay — narrowed here rather than re-validated, so the invariant lives in
|
|
7292
7331
|
// one place.
|
|
7293
7332
|
relayUrl: relayInfo.relayUrl,
|
|
@@ -7341,11 +7380,15 @@ function explainNoRelay(namespace, relays) {
|
|
|
7341
7380
|
if (relays.length === 0) {
|
|
7342
7381
|
return namespace.haStatus === "enabled" ? `namespace ${namespace.namespaceId} has HA enabled but no relay has been assigned to it yet \u2014 retry shortly` : `namespace ${namespace.namespaceId} has no relays: enable HA on it in the cloud to have an attested node assigned`;
|
|
7343
7382
|
}
|
|
7344
|
-
const
|
|
7345
|
-
if (
|
|
7383
|
+
const replicas = relays.filter((r) => r.teeRole !== null && r.teeRole !== "RelayTee");
|
|
7384
|
+
if (replicas.length === relays.length) {
|
|
7385
|
+
return `every node on namespace ${namespace.namespaceId} is a read-only TEE replica (ReadOnlyTee), which never relays delegated writes: an admin of the namespace must set its TEE admission policy with mode: 'relay' \u2014 AdminClient.setTeeAdmissionPolicy(namespaceId, { ..., mode: 'relay' }) \u2014 so fleet nodes are admitted as RelayTee`;
|
|
7386
|
+
}
|
|
7387
|
+
const waitingOnGrant = relays.filter((r) => r.teeRole === null && !r.authorshipReady);
|
|
7388
|
+
if (waitingOnGrant.length > 0 && waitingOnGrant.length + replicas.length === relays.length) {
|
|
7346
7389
|
return `every relay on namespace ${namespace.namespaceId} is waiting for the authorship grant: an admin of the namespace must grant CAN_AUTHOR_ON_BEHALF to ${waitingOnGrant.map((r) => r.executorAccount ?? r.peerId).join(", ") || "the relay account"} \u2014 with AdminClient.grantAuthorship(namespaceId, account) for these relays, and AdminClient.openToDelegatedExecution(namespaceId) so relays assigned later need no further op`;
|
|
7347
7390
|
}
|
|
7348
|
-
return `namespace ${namespace.namespaceId} has ${relays.length} relay(s), but none
|
|
7391
|
+
return `namespace ${namespace.namespaceId} has ${relays.length} relay(s), but none is ready to relay yet (a URL, an executor account and a recent heartbeat) \u2014 retry shortly`;
|
|
7349
7392
|
}
|
|
7350
7393
|
async function defaultNonceSource(deviceSecret) {
|
|
7351
7394
|
const key = `mero-js:warrant-nonce:${hex(await derivePublicKey(deviceSecret))}`;
|
|
@@ -7922,8 +7965,24 @@ function nodeReleaseVersionFromImage(osImage) {
|
|
|
7922
7965
|
return `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`;
|
|
7923
7966
|
}
|
|
7924
7967
|
async function fetchNodeReleaseVersion(baseUrl, options = {}) {
|
|
7925
|
-
const
|
|
7926
|
-
|
|
7968
|
+
const version = await readNodeReleaseVersion(baseUrl, options.fetch ?? fetch);
|
|
7969
|
+
if (version === void 0) {
|
|
7970
|
+
throw new Error(
|
|
7971
|
+
`${infoUrl(baseUrl)} was refused unsealed: the node requires sealing and is too old to name its release before a session exists. Name the release it runs yourself (createSignedReleaseSealedFetch's \`releaseVersion\`), or upgrade the node`
|
|
7972
|
+
);
|
|
7973
|
+
}
|
|
7974
|
+
return version;
|
|
7975
|
+
}
|
|
7976
|
+
function infoUrl(baseUrl) {
|
|
7977
|
+
return `${baseUrl.replace(/\/+$/, "")}/admin-api/tee/info`;
|
|
7978
|
+
}
|
|
7979
|
+
async function readNodeReleaseVersion(baseUrl, givenFetch) {
|
|
7980
|
+
const url = infoUrl(baseUrl);
|
|
7981
|
+
const response = await givenFetch(url, { headers: { accept: "application/json" } });
|
|
7982
|
+
if (response.status === 403) {
|
|
7983
|
+
const code = await response.json().then((body) => body?.error?.code).catch(() => void 0);
|
|
7984
|
+
if (code === "sealed_required") return void 0;
|
|
7985
|
+
}
|
|
7927
7986
|
if (!response.ok) throw new Error(`Fetching ${url} failed: HTTP ${response.status}`);
|
|
7928
7987
|
let osImage;
|
|
7929
7988
|
try {
|
|
@@ -7970,10 +8029,19 @@ function createSignedReleaseVerifier(options) {
|
|
|
7970
8029
|
return Object.assign(verifier, { includeCollateral: true });
|
|
7971
8030
|
}
|
|
7972
8031
|
function createSignedReleaseSealedFetch(options) {
|
|
7973
|
-
const {
|
|
8032
|
+
const {
|
|
8033
|
+
baseUrl,
|
|
8034
|
+
releaseSource,
|
|
8035
|
+
releaseVersion: fallbackVersion,
|
|
8036
|
+
applicationId,
|
|
8037
|
+
applicationHash,
|
|
8038
|
+
fetch: givenFetch,
|
|
8039
|
+
...verifierOptions
|
|
8040
|
+
} = options;
|
|
8041
|
+
if (fallbackVersion !== void 0) releaseVersion(fallbackVersion, "releaseVersion");
|
|
7974
8042
|
const baseFetch = givenFetch ?? ((input, init) => fetch(input, init));
|
|
7975
8043
|
const release = async () => {
|
|
7976
|
-
const version = await fetchNodeReleaseVersion(baseUrl, { fetch: baseFetch });
|
|
8044
|
+
const version = fallbackVersion === void 0 ? await fetchNodeReleaseVersion(baseUrl, { fetch: baseFetch }) : await readNodeReleaseVersion(baseUrl, baseFetch) ?? fallbackVersion;
|
|
7977
8045
|
return typeof releaseSource === "function" ? releaseSource({ baseUrl, version, fetch: baseFetch }) : fetchNodeRelease(cloudNodeReleaseUrl(releaseSource ?? DEFAULT_RELEASE_MIRROR, version), { fetch: baseFetch });
|
|
7978
8046
|
};
|
|
7979
8047
|
return createAttestedSealedFetch({
|