@le-space/ui 0.1.55 → 0.1.57
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/package.json +4 -4
- package/react/index.d.ts +2 -2
- package/react/index.js +141 -25
- package/shared/index.d.ts +2 -2
- package/shared/index.js +141 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.57",
|
|
4
4
|
"description": "Floating Aleph sponsor relay UI components for Svelte and React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@
|
|
40
|
-
"@le-space/
|
|
41
|
-
"
|
|
39
|
+
"@le-space/browser": "0.1.57",
|
|
40
|
+
"@le-space/core": "0.1.57",
|
|
41
|
+
"viem": "^2.38.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": ">=18",
|
package/react/index.d.ts
CHANGED
|
@@ -174,12 +174,12 @@ interface RootfsResolution {
|
|
|
174
174
|
gatewayError?: string | null;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
type DeploymentProgressStage =
|
|
177
|
+
type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
|
|
178
178
|
interface DeploymentProgressEvent {
|
|
179
179
|
stage: DeploymentProgressStage;
|
|
180
180
|
label: string;
|
|
181
181
|
progress: number;
|
|
182
|
-
status:
|
|
182
|
+
status: "info" | "success" | "warning" | "error";
|
|
183
183
|
itemHash?: string | null;
|
|
184
184
|
detail?: string | null;
|
|
185
185
|
error?: string | null;
|
package/react/index.js
CHANGED
|
@@ -48,7 +48,8 @@ function formatNumber(value, digits = 0) {
|
|
|
48
48
|
}
|
|
49
49
|
function formatDateTime(value) {
|
|
50
50
|
if (!value) return "-";
|
|
51
|
-
const
|
|
51
|
+
const normalizedValue = typeof value === "number" && value > 0 && value < 1e10 ? value * 1e3 : value;
|
|
52
|
+
const date = typeof normalizedValue === "number" ? new Date(normalizedValue) : new Date(normalizedValue);
|
|
52
53
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
53
54
|
return date.toLocaleString();
|
|
54
55
|
}
|
|
@@ -183,7 +184,7 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
|
183
184
|
const payload = await response.json();
|
|
184
185
|
return (payload.messages ?? []).map((message) => ({
|
|
185
186
|
...message,
|
|
186
|
-
status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" :
|
|
187
|
+
status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : "pending"
|
|
187
188
|
}));
|
|
188
189
|
}
|
|
189
190
|
async function fetch2n6WebAccessUrl(itemHash, twoN6ApiHost = DEFAULT_2N6_API_HOST) {
|
|
@@ -709,21 +710,16 @@ function resolveManifestSource(args) {
|
|
|
709
710
|
}
|
|
710
711
|
|
|
711
712
|
// src/shared/wallet-controller.ts
|
|
712
|
-
import {
|
|
713
|
+
import { getAddress } from "viem";
|
|
713
714
|
function getEthereumProvider() {
|
|
714
715
|
return globalThis.window?.ethereum ?? null;
|
|
715
716
|
}
|
|
716
717
|
function toChecksumAddress(address) {
|
|
717
|
-
|
|
718
|
+
try {
|
|
719
|
+
return getAddress(address);
|
|
720
|
+
} catch {
|
|
718
721
|
throw new Error("Invalid EVM address.");
|
|
719
722
|
}
|
|
720
|
-
const normalized = address.slice(2).toLowerCase();
|
|
721
|
-
const hash = Array.from(keccak_256(new TextEncoder().encode(normalized))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
722
|
-
let result = "0x";
|
|
723
|
-
for (let index = 0; index < normalized.length; index += 1) {
|
|
724
|
-
result += Number.parseInt(hash[index], 16) >= 8 ? normalized[index].toUpperCase() : normalized[index];
|
|
725
|
-
}
|
|
726
|
-
return result;
|
|
727
723
|
}
|
|
728
724
|
async function connectWallet(provider = getEthereumProvider()) {
|
|
729
725
|
if (!provider) {
|
|
@@ -1228,6 +1224,88 @@ async function deployInstance(args) {
|
|
|
1228
1224
|
};
|
|
1229
1225
|
}
|
|
1230
1226
|
|
|
1227
|
+
// ../core/src/guest.ts
|
|
1228
|
+
async function notifyCrnAllocation2(args) {
|
|
1229
|
+
const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
|
|
1230
|
+
if (!normalizedCrnUrl) {
|
|
1231
|
+
return {
|
|
1232
|
+
status: "skipped",
|
|
1233
|
+
reason: "No CRN URL available for allocation notification."
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
try {
|
|
1237
|
+
const response = await args.fetch(
|
|
1238
|
+
`${normalizedCrnUrl}/control/allocation/notify`,
|
|
1239
|
+
{
|
|
1240
|
+
method: "POST",
|
|
1241
|
+
headers: {
|
|
1242
|
+
"content-type": "application/json"
|
|
1243
|
+
},
|
|
1244
|
+
body: JSON.stringify({ instance: args.itemHash })
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
const payload = await response.json().catch(() => null);
|
|
1248
|
+
const responseText = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1249
|
+
if (!response.ok) {
|
|
1250
|
+
return {
|
|
1251
|
+
status: "unconfirmed",
|
|
1252
|
+
reason: `CRN allocation notify returned ${response.status}${responseText ? ` ${responseText}` : ""}.`,
|
|
1253
|
+
payload
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
return {
|
|
1257
|
+
status: "confirmed",
|
|
1258
|
+
payload
|
|
1259
|
+
};
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
return {
|
|
1262
|
+
status: "unconfirmed",
|
|
1263
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
function isRetryableAllocationNotifyReason(reason) {
|
|
1268
|
+
const normalized = String(reason ?? "").toLowerCase();
|
|
1269
|
+
return normalized.includes("503") && (normalized.includes("node hash not yet discovered") || normalized.includes("cannot accept targeted allocations"));
|
|
1270
|
+
}
|
|
1271
|
+
async function notifyCrnAllocationWithRetry(args) {
|
|
1272
|
+
const attempts = Math.max(1, Number(args.attempts ?? 6));
|
|
1273
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 2e3));
|
|
1274
|
+
const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1275
|
+
let lastResult = null;
|
|
1276
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1277
|
+
args.onProgress?.({
|
|
1278
|
+
stage: "notifying-crn",
|
|
1279
|
+
label: "Notifying selected CRN",
|
|
1280
|
+
progress: 84,
|
|
1281
|
+
status: "info",
|
|
1282
|
+
itemHash: args.itemHash,
|
|
1283
|
+
detail: `${attempt + 1}/${attempts} ${String(args.crnUrl ?? "").trim() || "selected CRN"}`,
|
|
1284
|
+
error: null,
|
|
1285
|
+
timestamp: Date.now()
|
|
1286
|
+
});
|
|
1287
|
+
const result = await notifyCrnAllocation2({
|
|
1288
|
+
crnUrl: args.crnUrl,
|
|
1289
|
+
itemHash: args.itemHash,
|
|
1290
|
+
fetch: args.fetch
|
|
1291
|
+
});
|
|
1292
|
+
if (result.status === "confirmed" || result.status === "skipped") {
|
|
1293
|
+
return result;
|
|
1294
|
+
}
|
|
1295
|
+
lastResult = result;
|
|
1296
|
+
if (!isRetryableAllocationNotifyReason(result.reason)) {
|
|
1297
|
+
return result;
|
|
1298
|
+
}
|
|
1299
|
+
if (attempt < attempts - 1) {
|
|
1300
|
+
await sleep(delayMs);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return lastResult ?? {
|
|
1304
|
+
status: "unconfirmed",
|
|
1305
|
+
reason: "CRN allocation notify was not confirmed."
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1231
1309
|
// src/shared/events.ts
|
|
1232
1310
|
function createDeploymentProgressEmitter() {
|
|
1233
1311
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1301,14 +1379,19 @@ function defaultState(props = {}) {
|
|
|
1301
1379
|
};
|
|
1302
1380
|
}
|
|
1303
1381
|
async function sha256Hex(payload) {
|
|
1304
|
-
const digest = await globalThis.crypto.subtle.digest(
|
|
1382
|
+
const digest = await globalThis.crypto.subtle.digest(
|
|
1383
|
+
"SHA-256",
|
|
1384
|
+
new TextEncoder().encode(payload)
|
|
1385
|
+
);
|
|
1305
1386
|
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1306
1387
|
}
|
|
1307
1388
|
function mappedPorts(execution) {
|
|
1308
|
-
return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1389
|
+
return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
|
|
1390
|
+
([port, mapping]) => ({
|
|
1391
|
+
label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
|
|
1392
|
+
hostPort: mapping.host ?? null
|
|
1393
|
+
})
|
|
1394
|
+
);
|
|
1312
1395
|
}
|
|
1313
1396
|
function rootfsHealth(args) {
|
|
1314
1397
|
if (!args.manifestState.valid || !args.manifestState.manifest) {
|
|
@@ -1366,7 +1449,9 @@ async function resolveManifest(args) {
|
|
|
1366
1449
|
}
|
|
1367
1450
|
async function inspectInstanceRuntime(args) {
|
|
1368
1451
|
const details = {
|
|
1369
|
-
messageStatus: String(
|
|
1452
|
+
messageStatus: String(
|
|
1453
|
+
args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")
|
|
1454
|
+
).toLowerCase(),
|
|
1370
1455
|
allocationSource: null,
|
|
1371
1456
|
crnUrl: null,
|
|
1372
1457
|
hostIpv4: null,
|
|
@@ -1397,11 +1482,15 @@ async function inspectInstanceRuntime(args) {
|
|
|
1397
1482
|
details.allocationSource = allocation?.source ?? null;
|
|
1398
1483
|
details.crnUrl = allocation?.crnUrl ?? null;
|
|
1399
1484
|
details.ipv6 = allocation?.vmIpv6 ?? null;
|
|
1400
|
-
details.webUrl = await args.client.fetch2n6WebAccessUrl(
|
|
1485
|
+
details.webUrl = await args.client.fetch2n6WebAccessUrl(
|
|
1486
|
+
args.instance.item_hash
|
|
1487
|
+
);
|
|
1401
1488
|
if (!allocation?.crnUrl) {
|
|
1402
1489
|
return details;
|
|
1403
1490
|
}
|
|
1404
|
-
const executionLookup = await args.client.fetchCrnExecutionMap(
|
|
1491
|
+
const executionLookup = await args.client.fetchCrnExecutionMap(
|
|
1492
|
+
allocation.crnUrl
|
|
1493
|
+
);
|
|
1405
1494
|
const executionPayload = executionLookup.payload?.[args.instance.item_hash];
|
|
1406
1495
|
if (!executionPayload) {
|
|
1407
1496
|
return details;
|
|
@@ -1429,7 +1518,10 @@ function compatibleCrnsForTier(crns, state) {
|
|
|
1429
1518
|
if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
|
|
1430
1519
|
return [];
|
|
1431
1520
|
}
|
|
1432
|
-
const spec = tierSpec(
|
|
1521
|
+
const spec = tierSpec(
|
|
1522
|
+
state.pricingSummary.pricing,
|
|
1523
|
+
state.pricingSummary.tier
|
|
1524
|
+
);
|
|
1433
1525
|
return filterDeployableCrns(crns, { spec });
|
|
1434
1526
|
}
|
|
1435
1527
|
async function pingPeer(libp2p) {
|
|
@@ -1719,8 +1811,14 @@ var SponsorRelayController = class {
|
|
|
1719
1811
|
status: "info",
|
|
1720
1812
|
detail: manifest.rootfsItemHash
|
|
1721
1813
|
});
|
|
1722
|
-
rootfsVerified = await verifyRootfsExists(
|
|
1723
|
-
|
|
1814
|
+
rootfsVerified = await verifyRootfsExists(
|
|
1815
|
+
manifest.rootfsItemHash,
|
|
1816
|
+
this.client.apiHost
|
|
1817
|
+
);
|
|
1818
|
+
rootfsResolution = await resolveRootfsReference(
|
|
1819
|
+
manifest.rootfsItemHash,
|
|
1820
|
+
this.client.apiHost
|
|
1821
|
+
);
|
|
1724
1822
|
}
|
|
1725
1823
|
this.patch({
|
|
1726
1824
|
manifestState,
|
|
@@ -1775,7 +1873,9 @@ var SponsorRelayController = class {
|
|
|
1775
1873
|
return;
|
|
1776
1874
|
}
|
|
1777
1875
|
if (!this.state.manifest || !this.state.rootfsVerified || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
|
|
1778
|
-
this.patch({
|
|
1876
|
+
this.patch({
|
|
1877
|
+
errorText: "Manifest, rootfs, and pricing must be ready before deploying."
|
|
1878
|
+
});
|
|
1779
1879
|
return;
|
|
1780
1880
|
}
|
|
1781
1881
|
this.patch({
|
|
@@ -1791,7 +1891,10 @@ var SponsorRelayController = class {
|
|
|
1791
1891
|
status: "info",
|
|
1792
1892
|
detail: "Checking wallet, manifest, rootfs, pricing, and SSH key."
|
|
1793
1893
|
});
|
|
1794
|
-
const spec = tierSpec(
|
|
1894
|
+
const spec = tierSpec(
|
|
1895
|
+
this.state.pricingSummary.pricing,
|
|
1896
|
+
this.state.pricingSummary.tier
|
|
1897
|
+
);
|
|
1795
1898
|
this.emitProgress({
|
|
1796
1899
|
stage: "selecting-crn",
|
|
1797
1900
|
label: "Selecting CRN",
|
|
@@ -1804,7 +1907,10 @@ var SponsorRelayController = class {
|
|
|
1804
1907
|
name: this.state.instanceName.trim(),
|
|
1805
1908
|
sshPublicKey: this.state.sshPublicKey.trim(),
|
|
1806
1909
|
rootfsItemHash: this.state.manifest.rootfsItemHash,
|
|
1807
|
-
rootfsSizeMiB: Math.max(
|
|
1910
|
+
rootfsSizeMiB: Math.max(
|
|
1911
|
+
this.state.manifest.rootfsSizeMiB,
|
|
1912
|
+
spec.diskMiB
|
|
1913
|
+
),
|
|
1808
1914
|
vcpus: spec.vcpus,
|
|
1809
1915
|
memoryMiB: spec.memoryMiB,
|
|
1810
1916
|
rootfsVersion: this.state.manifest.version,
|
|
@@ -1827,6 +1933,16 @@ var SponsorRelayController = class {
|
|
|
1827
1933
|
statusText: `Deployment submitted: ${result.itemHash}`,
|
|
1828
1934
|
lastDeploymentHash: result.itemHash
|
|
1829
1935
|
});
|
|
1936
|
+
if (result.status === "processed" && this.state.selectedCrn?.address) {
|
|
1937
|
+
await notifyCrnAllocationWithRetry({
|
|
1938
|
+
crnUrl: this.state.selectedCrn.address,
|
|
1939
|
+
itemHash: result.itemHash,
|
|
1940
|
+
fetch: (url, init) => fetch(url, init),
|
|
1941
|
+
onProgress: (event) => {
|
|
1942
|
+
this.emitProgress(event);
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1830
1946
|
this.emitProgress({
|
|
1831
1947
|
stage: "refreshing-instances",
|
|
1832
1948
|
label: "Refreshing instances",
|
package/shared/index.d.ts
CHANGED
|
@@ -172,12 +172,12 @@ interface RootfsResolution {
|
|
|
172
172
|
gatewayError?: string | null;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
type DeploymentProgressStage =
|
|
175
|
+
type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
|
|
176
176
|
interface DeploymentProgressEvent {
|
|
177
177
|
stage: DeploymentProgressStage;
|
|
178
178
|
label: string;
|
|
179
179
|
progress: number;
|
|
180
|
-
status:
|
|
180
|
+
status: "info" | "success" | "warning" | "error";
|
|
181
181
|
itemHash?: string | null;
|
|
182
182
|
detail?: string | null;
|
|
183
183
|
error?: string | null;
|
package/shared/index.js
CHANGED
|
@@ -45,7 +45,8 @@ function formatNumber(value, digits = 0) {
|
|
|
45
45
|
}
|
|
46
46
|
function formatDateTime(value) {
|
|
47
47
|
if (!value) return "-";
|
|
48
|
-
const
|
|
48
|
+
const normalizedValue = typeof value === "number" && value > 0 && value < 1e10 ? value * 1e3 : value;
|
|
49
|
+
const date = typeof normalizedValue === "number" ? new Date(normalizedValue) : new Date(normalizedValue);
|
|
49
50
|
if (Number.isNaN(date.getTime())) return String(value);
|
|
50
51
|
return date.toLocaleString();
|
|
51
52
|
}
|
|
@@ -180,7 +181,7 @@ async function fetchInstances(address, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
|
180
181
|
const payload = await response.json();
|
|
181
182
|
return (payload.messages ?? []).map((message) => ({
|
|
182
183
|
...message,
|
|
183
|
-
status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" :
|
|
184
|
+
status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : "pending"
|
|
184
185
|
}));
|
|
185
186
|
}
|
|
186
187
|
async function fetch2n6WebAccessUrl(itemHash, twoN6ApiHost = DEFAULT_2N6_API_HOST) {
|
|
@@ -706,21 +707,16 @@ function resolveManifestSource(args) {
|
|
|
706
707
|
}
|
|
707
708
|
|
|
708
709
|
// src/shared/wallet-controller.ts
|
|
709
|
-
import {
|
|
710
|
+
import { getAddress } from "viem";
|
|
710
711
|
function getEthereumProvider() {
|
|
711
712
|
return globalThis.window?.ethereum ?? null;
|
|
712
713
|
}
|
|
713
714
|
function toChecksumAddress(address) {
|
|
714
|
-
|
|
715
|
+
try {
|
|
716
|
+
return getAddress(address);
|
|
717
|
+
} catch {
|
|
715
718
|
throw new Error("Invalid EVM address.");
|
|
716
719
|
}
|
|
717
|
-
const normalized = address.slice(2).toLowerCase();
|
|
718
|
-
const hash = Array.from(keccak_256(new TextEncoder().encode(normalized))).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
719
|
-
let result = "0x";
|
|
720
|
-
for (let index = 0; index < normalized.length; index += 1) {
|
|
721
|
-
result += Number.parseInt(hash[index], 16) >= 8 ? normalized[index].toUpperCase() : normalized[index];
|
|
722
|
-
}
|
|
723
|
-
return result;
|
|
724
720
|
}
|
|
725
721
|
async function connectWallet(provider = getEthereumProvider()) {
|
|
726
722
|
if (!provider) {
|
|
@@ -1225,6 +1221,88 @@ async function deployInstance(args) {
|
|
|
1225
1221
|
};
|
|
1226
1222
|
}
|
|
1227
1223
|
|
|
1224
|
+
// ../core/src/guest.ts
|
|
1225
|
+
async function notifyCrnAllocation2(args) {
|
|
1226
|
+
const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
|
|
1227
|
+
if (!normalizedCrnUrl) {
|
|
1228
|
+
return {
|
|
1229
|
+
status: "skipped",
|
|
1230
|
+
reason: "No CRN URL available for allocation notification."
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
try {
|
|
1234
|
+
const response = await args.fetch(
|
|
1235
|
+
`${normalizedCrnUrl}/control/allocation/notify`,
|
|
1236
|
+
{
|
|
1237
|
+
method: "POST",
|
|
1238
|
+
headers: {
|
|
1239
|
+
"content-type": "application/json"
|
|
1240
|
+
},
|
|
1241
|
+
body: JSON.stringify({ instance: args.itemHash })
|
|
1242
|
+
}
|
|
1243
|
+
);
|
|
1244
|
+
const payload = await response.json().catch(() => null);
|
|
1245
|
+
const responseText = payload == null ? "" : typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1246
|
+
if (!response.ok) {
|
|
1247
|
+
return {
|
|
1248
|
+
status: "unconfirmed",
|
|
1249
|
+
reason: `CRN allocation notify returned ${response.status}${responseText ? ` ${responseText}` : ""}.`,
|
|
1250
|
+
payload
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
return {
|
|
1254
|
+
status: "confirmed",
|
|
1255
|
+
payload
|
|
1256
|
+
};
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
return {
|
|
1259
|
+
status: "unconfirmed",
|
|
1260
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1261
|
+
};
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function isRetryableAllocationNotifyReason(reason) {
|
|
1265
|
+
const normalized = String(reason ?? "").toLowerCase();
|
|
1266
|
+
return normalized.includes("503") && (normalized.includes("node hash not yet discovered") || normalized.includes("cannot accept targeted allocations"));
|
|
1267
|
+
}
|
|
1268
|
+
async function notifyCrnAllocationWithRetry(args) {
|
|
1269
|
+
const attempts = Math.max(1, Number(args.attempts ?? 6));
|
|
1270
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 2e3));
|
|
1271
|
+
const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
1272
|
+
let lastResult = null;
|
|
1273
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1274
|
+
args.onProgress?.({
|
|
1275
|
+
stage: "notifying-crn",
|
|
1276
|
+
label: "Notifying selected CRN",
|
|
1277
|
+
progress: 84,
|
|
1278
|
+
status: "info",
|
|
1279
|
+
itemHash: args.itemHash,
|
|
1280
|
+
detail: `${attempt + 1}/${attempts} ${String(args.crnUrl ?? "").trim() || "selected CRN"}`,
|
|
1281
|
+
error: null,
|
|
1282
|
+
timestamp: Date.now()
|
|
1283
|
+
});
|
|
1284
|
+
const result = await notifyCrnAllocation2({
|
|
1285
|
+
crnUrl: args.crnUrl,
|
|
1286
|
+
itemHash: args.itemHash,
|
|
1287
|
+
fetch: args.fetch
|
|
1288
|
+
});
|
|
1289
|
+
if (result.status === "confirmed" || result.status === "skipped") {
|
|
1290
|
+
return result;
|
|
1291
|
+
}
|
|
1292
|
+
lastResult = result;
|
|
1293
|
+
if (!isRetryableAllocationNotifyReason(result.reason)) {
|
|
1294
|
+
return result;
|
|
1295
|
+
}
|
|
1296
|
+
if (attempt < attempts - 1) {
|
|
1297
|
+
await sleep(delayMs);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return lastResult ?? {
|
|
1301
|
+
status: "unconfirmed",
|
|
1302
|
+
reason: "CRN allocation notify was not confirmed."
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1228
1306
|
// src/shared/events.ts
|
|
1229
1307
|
function createDeploymentProgressEmitter() {
|
|
1230
1308
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1298,14 +1376,19 @@ function defaultState(props = {}) {
|
|
|
1298
1376
|
};
|
|
1299
1377
|
}
|
|
1300
1378
|
async function sha256Hex(payload) {
|
|
1301
|
-
const digest = await globalThis.crypto.subtle.digest(
|
|
1379
|
+
const digest = await globalThis.crypto.subtle.digest(
|
|
1380
|
+
"SHA-256",
|
|
1381
|
+
new TextEncoder().encode(payload)
|
|
1382
|
+
);
|
|
1302
1383
|
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1303
1384
|
}
|
|
1304
1385
|
function mappedPorts(execution) {
|
|
1305
|
-
return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1386
|
+
return Object.entries(execution?.networking?.mapped_ports ?? {}).map(
|
|
1387
|
+
([port, mapping]) => ({
|
|
1388
|
+
label: `${port}/${mapping.udp ? "udp" : "tcp"}`,
|
|
1389
|
+
hostPort: mapping.host ?? null
|
|
1390
|
+
})
|
|
1391
|
+
);
|
|
1309
1392
|
}
|
|
1310
1393
|
function rootfsHealth(args) {
|
|
1311
1394
|
if (!args.manifestState.valid || !args.manifestState.manifest) {
|
|
@@ -1363,7 +1446,9 @@ async function resolveManifest(args) {
|
|
|
1363
1446
|
}
|
|
1364
1447
|
async function inspectInstanceRuntime(args) {
|
|
1365
1448
|
const details = {
|
|
1366
|
-
messageStatus: String(
|
|
1449
|
+
messageStatus: String(
|
|
1450
|
+
args.instance.status ?? (args.instance.confirmed ? "processed" : "unknown")
|
|
1451
|
+
).toLowerCase(),
|
|
1367
1452
|
allocationSource: null,
|
|
1368
1453
|
crnUrl: null,
|
|
1369
1454
|
hostIpv4: null,
|
|
@@ -1394,11 +1479,15 @@ async function inspectInstanceRuntime(args) {
|
|
|
1394
1479
|
details.allocationSource = allocation?.source ?? null;
|
|
1395
1480
|
details.crnUrl = allocation?.crnUrl ?? null;
|
|
1396
1481
|
details.ipv6 = allocation?.vmIpv6 ?? null;
|
|
1397
|
-
details.webUrl = await args.client.fetch2n6WebAccessUrl(
|
|
1482
|
+
details.webUrl = await args.client.fetch2n6WebAccessUrl(
|
|
1483
|
+
args.instance.item_hash
|
|
1484
|
+
);
|
|
1398
1485
|
if (!allocation?.crnUrl) {
|
|
1399
1486
|
return details;
|
|
1400
1487
|
}
|
|
1401
|
-
const executionLookup = await args.client.fetchCrnExecutionMap(
|
|
1488
|
+
const executionLookup = await args.client.fetchCrnExecutionMap(
|
|
1489
|
+
allocation.crnUrl
|
|
1490
|
+
);
|
|
1402
1491
|
const executionPayload = executionLookup.payload?.[args.instance.item_hash];
|
|
1403
1492
|
if (!executionPayload) {
|
|
1404
1493
|
return details;
|
|
@@ -1426,7 +1515,10 @@ function compatibleCrnsForTier(crns, state) {
|
|
|
1426
1515
|
if (!state.pricingSummary.pricing || !state.pricingSummary.tier) {
|
|
1427
1516
|
return [];
|
|
1428
1517
|
}
|
|
1429
|
-
const spec = tierSpec(
|
|
1518
|
+
const spec = tierSpec(
|
|
1519
|
+
state.pricingSummary.pricing,
|
|
1520
|
+
state.pricingSummary.tier
|
|
1521
|
+
);
|
|
1430
1522
|
return filterDeployableCrns(crns, { spec });
|
|
1431
1523
|
}
|
|
1432
1524
|
async function pingPeer(libp2p) {
|
|
@@ -1716,8 +1808,14 @@ var SponsorRelayController = class {
|
|
|
1716
1808
|
status: "info",
|
|
1717
1809
|
detail: manifest.rootfsItemHash
|
|
1718
1810
|
});
|
|
1719
|
-
rootfsVerified = await verifyRootfsExists(
|
|
1720
|
-
|
|
1811
|
+
rootfsVerified = await verifyRootfsExists(
|
|
1812
|
+
manifest.rootfsItemHash,
|
|
1813
|
+
this.client.apiHost
|
|
1814
|
+
);
|
|
1815
|
+
rootfsResolution = await resolveRootfsReference(
|
|
1816
|
+
manifest.rootfsItemHash,
|
|
1817
|
+
this.client.apiHost
|
|
1818
|
+
);
|
|
1721
1819
|
}
|
|
1722
1820
|
this.patch({
|
|
1723
1821
|
manifestState,
|
|
@@ -1772,7 +1870,9 @@ var SponsorRelayController = class {
|
|
|
1772
1870
|
return;
|
|
1773
1871
|
}
|
|
1774
1872
|
if (!this.state.manifest || !this.state.rootfsVerified || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
|
|
1775
|
-
this.patch({
|
|
1873
|
+
this.patch({
|
|
1874
|
+
errorText: "Manifest, rootfs, and pricing must be ready before deploying."
|
|
1875
|
+
});
|
|
1776
1876
|
return;
|
|
1777
1877
|
}
|
|
1778
1878
|
this.patch({
|
|
@@ -1788,7 +1888,10 @@ var SponsorRelayController = class {
|
|
|
1788
1888
|
status: "info",
|
|
1789
1889
|
detail: "Checking wallet, manifest, rootfs, pricing, and SSH key."
|
|
1790
1890
|
});
|
|
1791
|
-
const spec = tierSpec(
|
|
1891
|
+
const spec = tierSpec(
|
|
1892
|
+
this.state.pricingSummary.pricing,
|
|
1893
|
+
this.state.pricingSummary.tier
|
|
1894
|
+
);
|
|
1792
1895
|
this.emitProgress({
|
|
1793
1896
|
stage: "selecting-crn",
|
|
1794
1897
|
label: "Selecting CRN",
|
|
@@ -1801,7 +1904,10 @@ var SponsorRelayController = class {
|
|
|
1801
1904
|
name: this.state.instanceName.trim(),
|
|
1802
1905
|
sshPublicKey: this.state.sshPublicKey.trim(),
|
|
1803
1906
|
rootfsItemHash: this.state.manifest.rootfsItemHash,
|
|
1804
|
-
rootfsSizeMiB: Math.max(
|
|
1907
|
+
rootfsSizeMiB: Math.max(
|
|
1908
|
+
this.state.manifest.rootfsSizeMiB,
|
|
1909
|
+
spec.diskMiB
|
|
1910
|
+
),
|
|
1805
1911
|
vcpus: spec.vcpus,
|
|
1806
1912
|
memoryMiB: spec.memoryMiB,
|
|
1807
1913
|
rootfsVersion: this.state.manifest.version,
|
|
@@ -1824,6 +1930,16 @@ var SponsorRelayController = class {
|
|
|
1824
1930
|
statusText: `Deployment submitted: ${result.itemHash}`,
|
|
1825
1931
|
lastDeploymentHash: result.itemHash
|
|
1826
1932
|
});
|
|
1933
|
+
if (result.status === "processed" && this.state.selectedCrn?.address) {
|
|
1934
|
+
await notifyCrnAllocationWithRetry({
|
|
1935
|
+
crnUrl: this.state.selectedCrn.address,
|
|
1936
|
+
itemHash: result.itemHash,
|
|
1937
|
+
fetch: (url, init) => fetch(url, init),
|
|
1938
|
+
onProgress: (event) => {
|
|
1939
|
+
this.emitProgress(event);
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1827
1943
|
this.emitProgress({
|
|
1828
1944
|
stage: "refreshing-instances",
|
|
1829
1945
|
label: "Refreshing instances",
|