@le-space/ui 0.4.1 → 0.4.2
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 +3 -3
- package/react/index.js +370 -78
- package/shared/index.d.ts +1 -1
- package/shared/index.js +370 -78
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Floating Aleph sponsor relay UI components for Svelte and React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@le-space/browser": "0.4.
|
|
40
|
-
"@le-space/core": "0.4.
|
|
39
|
+
"@le-space/browser": "0.4.2",
|
|
40
|
+
"@le-space/core": "0.4.2",
|
|
41
41
|
"viem": "^2.38.5"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
package/react/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
|
28
28
|
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
29
29
|
|
|
30
30
|
// src/shared/package-version.ts
|
|
31
|
-
var UI_PACKAGE_VERSION = "0.4.
|
|
31
|
+
var UI_PACKAGE_VERSION = "0.4.2";
|
|
32
32
|
|
|
33
33
|
// src/shared/format.ts
|
|
34
34
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -1237,16 +1237,6 @@ function normalizeBroadcastStatus(httpStatus, responseStatus) {
|
|
|
1237
1237
|
if (httpStatus === 202) return "pending";
|
|
1238
1238
|
return normalizeMessageStatus2(responseStatus);
|
|
1239
1239
|
}
|
|
1240
|
-
function isInvalidMessageFormatResponse2(response, payload) {
|
|
1241
|
-
if (response.status !== 422) return false;
|
|
1242
|
-
const details = payload?.details;
|
|
1243
|
-
if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
|
|
1244
|
-
if (details && typeof details === "object") {
|
|
1245
|
-
const detailMessage = details.message;
|
|
1246
|
-
if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
|
|
1247
|
-
}
|
|
1248
|
-
return false;
|
|
1249
|
-
}
|
|
1250
1240
|
function isRetryableBroadcastFailure(response, payload) {
|
|
1251
1241
|
if (response.status >= 500) return true;
|
|
1252
1242
|
const publicationStatus = payload?.publication_status?.status;
|
|
@@ -1268,25 +1258,22 @@ async function postBroadcastPayload2(body, options) {
|
|
|
1268
1258
|
};
|
|
1269
1259
|
}
|
|
1270
1260
|
async function broadcastAlephMessage2(message, options) {
|
|
1271
|
-
const
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
];
|
|
1276
|
-
for (let index = 0; index < attempts.length; index += 1) {
|
|
1277
|
-
const result = await postBroadcastPayload2(attempts[index], {
|
|
1261
|
+
const requestBody = { sync: options.sync ?? false, message };
|
|
1262
|
+
const maxAttempts = 3;
|
|
1263
|
+
for (let index = 0; index < maxAttempts; index += 1) {
|
|
1264
|
+
const result = await postBroadcastPayload2(requestBody, {
|
|
1278
1265
|
apiHost: options.apiHost,
|
|
1279
1266
|
fetch: options.fetch
|
|
1280
1267
|
});
|
|
1281
1268
|
if (result.httpStatus === 202 || normalizeBroadcastStatus(result.httpStatus, result.response?.message_status) !== "unknown" || result.httpStatus >= 200 && result.httpStatus < 300) {
|
|
1282
1269
|
return result;
|
|
1283
1270
|
}
|
|
1284
|
-
const canRetry = index <
|
|
1271
|
+
const canRetry = index < maxAttempts - 1 && isRetryableBroadcastFailure({ status: result.httpStatus }, result.response);
|
|
1285
1272
|
if (!canRetry) {
|
|
1286
1273
|
throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response ?? {})}`);
|
|
1287
1274
|
}
|
|
1288
1275
|
}
|
|
1289
|
-
throw new Error("Broadcast failed:
|
|
1276
|
+
throw new Error("Broadcast failed: retry budget exhausted");
|
|
1290
1277
|
}
|
|
1291
1278
|
|
|
1292
1279
|
// ../core/src/forget.ts
|
|
@@ -1653,6 +1640,26 @@ function asString3(value) {
|
|
|
1653
1640
|
function asNumber3(value) {
|
|
1654
1641
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1655
1642
|
}
|
|
1643
|
+
function normalizeIpv6(value) {
|
|
1644
|
+
if (typeof value !== "string") return null;
|
|
1645
|
+
const normalized = value.trim().toLowerCase();
|
|
1646
|
+
if (!normalized) return null;
|
|
1647
|
+
const zoneIndex = normalized.indexOf("%");
|
|
1648
|
+
return zoneIndex >= 0 ? normalized.slice(0, zoneIndex) : normalized;
|
|
1649
|
+
}
|
|
1650
|
+
function isLikelyGlobalIpv6(value) {
|
|
1651
|
+
const normalized = normalizeIpv6(value);
|
|
1652
|
+
if (!normalized || normalized === "::" || normalized === "::1") return false;
|
|
1653
|
+
const firstHextetText = normalized.split(":", 1)[0];
|
|
1654
|
+
if (!firstHextetText) return false;
|
|
1655
|
+
const firstHextet = Number.parseInt(firstHextetText, 16);
|
|
1656
|
+
if (!Number.isFinite(firstHextet)) return false;
|
|
1657
|
+
if ((firstHextet & 65024) === 64512) return false;
|
|
1658
|
+
if ((firstHextet & 65472) === 65152) return false;
|
|
1659
|
+
if ((firstHextet & 65280) === 65280) return false;
|
|
1660
|
+
if ((firstHextet & 65472) === 65216) return false;
|
|
1661
|
+
return true;
|
|
1662
|
+
}
|
|
1656
1663
|
function findCrnByHash(crns, crnHash) {
|
|
1657
1664
|
return crns.find((crn) => crn.hash === crnHash) ?? null;
|
|
1658
1665
|
}
|
|
@@ -1733,10 +1740,22 @@ function describeRuntimeAvailability(runtime) {
|
|
|
1733
1740
|
const execution = runtime.execution ?? null;
|
|
1734
1741
|
const mappedPorts2 = runtime.mappedPorts ?? {};
|
|
1735
1742
|
const hostIpv4 = runtime.hostIpv4 ?? null;
|
|
1743
|
+
const ipv6 = runtime.ipv6 ?? null;
|
|
1736
1744
|
const proxyUrl = runtime.proxyUrl ?? null;
|
|
1737
1745
|
const schedulerSource = runtime.allocation?.source ?? null;
|
|
1738
1746
|
const webAccessActive = runtime.webAccess?.active ?? null;
|
|
1739
1747
|
const mappedPortCount = Object.keys(mappedPorts2).length;
|
|
1748
|
+
if (hostIpv4 && mappedPortCount > 0 && runtime.requirePublicGuestIpv6ForProxy === true && proxyUrl && !isLikelyGlobalIpv6(ipv6)) {
|
|
1749
|
+
return {
|
|
1750
|
+
state: "execution-invalid-public-ipv6",
|
|
1751
|
+
reason: `The CRN exposed runtime networking, but the guest IPv6${ipv6 ? ` (${ipv6})` : ""} is not globally routable. Proxy-backed HTTPS activation cannot succeed until the CRN reports a public guest IPv6.`,
|
|
1752
|
+
schedulerSource,
|
|
1753
|
+
executionSeen: Boolean(execution),
|
|
1754
|
+
webAccessActive,
|
|
1755
|
+
mappedPortCount,
|
|
1756
|
+
proxyUrl
|
|
1757
|
+
};
|
|
1758
|
+
}
|
|
1740
1759
|
if (hostIpv4 && mappedPortCount > 0) {
|
|
1741
1760
|
return {
|
|
1742
1761
|
state: "ready",
|
|
@@ -1853,7 +1872,8 @@ async function fetchVmRuntime(args) {
|
|
|
1853
1872
|
hostIpv4,
|
|
1854
1873
|
ipv6,
|
|
1855
1874
|
proxyUrl,
|
|
1856
|
-
mappedPorts: mappedPorts2
|
|
1875
|
+
mappedPorts: mappedPorts2,
|
|
1876
|
+
requirePublicGuestIpv6ForProxy: args.requirePublicGuestIpv6ForProxy
|
|
1857
1877
|
});
|
|
1858
1878
|
return {
|
|
1859
1879
|
allocation,
|
|
@@ -1877,7 +1897,7 @@ async function waitForVmRuntime(args) {
|
|
|
1877
1897
|
let lastRuntime = await fetchVmRuntime(args);
|
|
1878
1898
|
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1879
1899
|
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1880
|
-
if (lastRuntime.
|
|
1900
|
+
if (lastRuntime.diagnostics?.state === "ready" || lastRuntime.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
1881
1901
|
return lastRuntime;
|
|
1882
1902
|
}
|
|
1883
1903
|
await sleep2(delayMs);
|
|
@@ -2676,6 +2696,16 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
2676
2696
|
|
|
2677
2697
|
// ../core/src/bootstrap-config.ts
|
|
2678
2698
|
var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
|
|
2699
|
+
var VM_BOOTSTRAP_CONFIG_SIGNAL_REF = "vm-bootstrap-config";
|
|
2700
|
+
var VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE = "vm-bootstrap-config-status";
|
|
2701
|
+
function parseJsonObject(value) {
|
|
2702
|
+
try {
|
|
2703
|
+
const parsed = JSON.parse(value);
|
|
2704
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2705
|
+
} catch {
|
|
2706
|
+
return null;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2679
2709
|
async function fetchVmBootstrapConfigAggregate(address, options) {
|
|
2680
2710
|
const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2681
2711
|
requestUrl.searchParams.set("keys", VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY);
|
|
@@ -2795,6 +2825,89 @@ async function deleteVmBootstrapConfig(args) {
|
|
|
2795
2825
|
aggregate: content
|
|
2796
2826
|
};
|
|
2797
2827
|
}
|
|
2828
|
+
function normalizeVmBootstrapConfigSignalRecord(value) {
|
|
2829
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2830
|
+
const candidate = value;
|
|
2831
|
+
const deploymentToken = typeof candidate.deploymentToken === "string" ? candidate.deploymentToken.trim() : "";
|
|
2832
|
+
const status = typeof candidate.status === "string" ? candidate.status.trim() : "";
|
|
2833
|
+
const profile = typeof candidate.profile === "string" ? candidate.profile.trim() : "";
|
|
2834
|
+
const ownerAddress = typeof candidate.ownerAddress === "string" ? candidate.ownerAddress.trim() : "";
|
|
2835
|
+
const instanceItemHash = typeof candidate.instanceItemHash === "string" ? candidate.instanceItemHash.trim() : "";
|
|
2836
|
+
const updatedAt = typeof candidate.updatedAt === "string" ? candidate.updatedAt.trim() : "";
|
|
2837
|
+
if (!deploymentToken || !status || !profile || !ownerAddress || !instanceItemHash || !updatedAt) {
|
|
2838
|
+
return null;
|
|
2839
|
+
}
|
|
2840
|
+
return {
|
|
2841
|
+
deploymentToken,
|
|
2842
|
+
status,
|
|
2843
|
+
profile,
|
|
2844
|
+
ownerAddress,
|
|
2845
|
+
instanceItemHash,
|
|
2846
|
+
updatedAt,
|
|
2847
|
+
publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
|
|
2848
|
+
authorization: candidate.authorization,
|
|
2849
|
+
peerId: typeof candidate.peerId === "string" && candidate.peerId.trim() ? candidate.peerId.trim() : null,
|
|
2850
|
+
probeMultiaddrs: Array.isArray(candidate.probeMultiaddrs) ? candidate.probeMultiaddrs.filter(
|
|
2851
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2852
|
+
) : [],
|
|
2853
|
+
browserBootstrapMultiaddrs: Array.isArray(candidate.browserBootstrapMultiaddrs) ? candidate.browserBootstrapMultiaddrs.filter(
|
|
2854
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2855
|
+
) : []
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
async function fetchVmBootstrapConfigSignals(args) {
|
|
2859
|
+
const requestUrl = new URL("/api/v0/posts.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2860
|
+
requestUrl.searchParams.set("refs", args.ref ?? VM_BOOTSTRAP_CONFIG_SIGNAL_REF);
|
|
2861
|
+
requestUrl.searchParams.set("types", args.postType ?? VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE);
|
|
2862
|
+
requestUrl.searchParams.set("pagination", String(Math.max(1, Number(args.pagination ?? 100))));
|
|
2863
|
+
requestUrl.searchParams.set("page", String(Math.max(1, Number(args.page ?? 1))));
|
|
2864
|
+
requestUrl.searchParams.set("sortOrder", "-1");
|
|
2865
|
+
const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
|
|
2866
|
+
if (!response.ok) {
|
|
2867
|
+
throw new Error(`VM bootstrap config signal request failed: ${response.status}`);
|
|
2868
|
+
}
|
|
2869
|
+
const payload = await response.json();
|
|
2870
|
+
const expectedDeploymentToken = args.deploymentToken?.trim() ?? "";
|
|
2871
|
+
const expectedOwnerAddress = args.ownerAddress?.trim().toLowerCase() ?? "";
|
|
2872
|
+
const expectedInstanceItemHash = args.instanceItemHash?.trim() ?? "";
|
|
2873
|
+
return (payload.posts ?? []).map((entry) => {
|
|
2874
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
2875
|
+
const parsed = "content" in entry && entry.content && typeof entry.content === "object" && !Array.isArray(entry.content) ? entry.content : "item_content" in entry && typeof entry.item_content === "string" ? parseJsonObject(entry.item_content) : null;
|
|
2876
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2877
|
+
const content = "deploymentToken" in parsed ? parsed : "content" in parsed && parsed.content && typeof parsed.content === "object" && !Array.isArray(parsed.content) ? parsed.content : null;
|
|
2878
|
+
if (!content || typeof content !== "object" || Array.isArray(content)) return null;
|
|
2879
|
+
return normalizeVmBootstrapConfigSignalRecord(content);
|
|
2880
|
+
}).filter((entry) => {
|
|
2881
|
+
if (!entry) return false;
|
|
2882
|
+
if (expectedDeploymentToken && entry.deploymentToken !== expectedDeploymentToken) return false;
|
|
2883
|
+
if (expectedOwnerAddress && entry.ownerAddress.toLowerCase() !== expectedOwnerAddress) return false;
|
|
2884
|
+
if (expectedInstanceItemHash && entry.instanceItemHash !== expectedInstanceItemHash) return false;
|
|
2885
|
+
return true;
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
async function waitForVmBootstrapConfigSignal(args) {
|
|
2889
|
+
const attempts = Math.max(1, Number(args.attempts ?? 24));
|
|
2890
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 2500));
|
|
2891
|
+
const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2892
|
+
const expectedStatus = args.expectedStatus ?? "applied";
|
|
2893
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2894
|
+
const signals = await fetchVmBootstrapConfigSignals({
|
|
2895
|
+
deploymentToken: args.deploymentToken,
|
|
2896
|
+
ownerAddress: args.ownerAddress,
|
|
2897
|
+
instanceItemHash: args.instanceItemHash,
|
|
2898
|
+
fetch: args.fetch,
|
|
2899
|
+
apiHost: args.apiHost,
|
|
2900
|
+
ref: args.ref,
|
|
2901
|
+
postType: args.postType
|
|
2902
|
+
}).catch(() => []);
|
|
2903
|
+
const match = signals.find((entry) => entry.status === expectedStatus) ?? null;
|
|
2904
|
+
if (match) return match;
|
|
2905
|
+
if (attempt < attempts - 1) {
|
|
2906
|
+
await sleep2(delayMs);
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
return null;
|
|
2910
|
+
}
|
|
2798
2911
|
|
|
2799
2912
|
// ../core/src/bootstrap-reconcile.ts
|
|
2800
2913
|
function createRelayBootstrapRegistrationId(profile, instanceName, itemHash) {
|
|
@@ -3280,11 +3393,13 @@ var SponsorRelayController = class {
|
|
|
3280
3393
|
if (!args.runtime.hostIpv4) {
|
|
3281
3394
|
throw new Error("Relay runtime did not expose a host IPv4 address.");
|
|
3282
3395
|
}
|
|
3283
|
-
|
|
3396
|
+
let runtime = args.runtime;
|
|
3397
|
+
let mappedPorts2 = runtime.mappedPorts ?? {};
|
|
3284
3398
|
const setupPort = mappedPorts2["80"]?.host ?? null;
|
|
3285
3399
|
if (!setupPort) {
|
|
3286
3400
|
throw new Error("Relay runtime did not expose the temporary setup endpoint.");
|
|
3287
3401
|
}
|
|
3402
|
+
let bootstrapSignalRelayMetadata = null;
|
|
3288
3403
|
if (profile === "orbitdb-relay-pinner") {
|
|
3289
3404
|
this.emitProgress({
|
|
3290
3405
|
stage: "deployment-confirmed",
|
|
@@ -3353,6 +3468,57 @@ var SponsorRelayController = class {
|
|
|
3353
3468
|
if (!args.deploymentToken) {
|
|
3354
3469
|
throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
|
|
3355
3470
|
}
|
|
3471
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3472
|
+
this.emitProgress({
|
|
3473
|
+
stage: "deployment-confirmed",
|
|
3474
|
+
label: "Waiting for HTTPS route",
|
|
3475
|
+
progress: 93,
|
|
3476
|
+
status: "warning",
|
|
3477
|
+
itemHash: args.itemHash,
|
|
3478
|
+
detail: "The relay received a 2n6 hostname, but it is not active yet. Waiting before handing HTTPS config to the guest."
|
|
3479
|
+
});
|
|
3480
|
+
let latestRuntime = runtime;
|
|
3481
|
+
for (let attempt = 0; attempt < UI_RUNTIME_WAIT_ATTEMPTS; attempt += 1) {
|
|
3482
|
+
if (latestRuntime.webAccess?.active === true) {
|
|
3483
|
+
break;
|
|
3484
|
+
}
|
|
3485
|
+
await new Promise(
|
|
3486
|
+
(resolve) => globalThis.setTimeout(resolve, UI_RUNTIME_WAIT_DELAY_MS)
|
|
3487
|
+
);
|
|
3488
|
+
latestRuntime = await fetchVmRuntime({
|
|
3489
|
+
itemHash: args.itemHash,
|
|
3490
|
+
fetch: (url, init) => fetch(url, init),
|
|
3491
|
+
crnHash: runtime.selectedCrn?.hash ?? runtime.allocation?.crnHash ?? void 0,
|
|
3492
|
+
crns: this.state.crns,
|
|
3493
|
+
crnListUrl: this.props.crnListUrl,
|
|
3494
|
+
requirePublicGuestIpv6ForProxy: true
|
|
3495
|
+
}).catch(() => latestRuntime);
|
|
3496
|
+
this.emitProgress({
|
|
3497
|
+
stage: "deployment-confirmed",
|
|
3498
|
+
label: "Waiting for HTTPS route",
|
|
3499
|
+
progress: 93,
|
|
3500
|
+
status: latestRuntime.webAccess?.active === true ? "info" : "warning",
|
|
3501
|
+
itemHash: args.itemHash,
|
|
3502
|
+
detail: `2n6 activation ${attempt + 1}/${UI_RUNTIME_WAIT_ATTEMPTS}: ${latestRuntime.webAccess?.active === true ? "active" : "still pending"} for ${latestRuntime.proxyUrl ?? runtime.proxyUrl ?? "reserved proxy URL"}.`
|
|
3503
|
+
});
|
|
3504
|
+
this.trace("deploy:bootstrap-proxy-activation", {
|
|
3505
|
+
itemHash: args.itemHash,
|
|
3506
|
+
attempt: attempt + 1,
|
|
3507
|
+
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
3508
|
+
active: latestRuntime.webAccess?.active === true,
|
|
3509
|
+
proxyUrl: latestRuntime.proxyUrl ?? null
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
runtime = latestRuntime;
|
|
3513
|
+
mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
|
|
3514
|
+
}
|
|
3515
|
+
if (!runtime.hostIpv4) {
|
|
3516
|
+
throw new Error(
|
|
3517
|
+
"Relay runtime lost its host IPv4 address while waiting for HTTPS activation."
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
const runtimeHostIpv4 = runtime.hostIpv4;
|
|
3521
|
+
const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
|
|
3356
3522
|
this.emitProgress({
|
|
3357
3523
|
stage: "deployment-confirmed",
|
|
3358
3524
|
label: "Publishing relay config",
|
|
@@ -3370,9 +3536,9 @@ var SponsorRelayController = class {
|
|
|
3370
3536
|
expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
|
|
3371
3537
|
status: "pending",
|
|
3372
3538
|
runtime: {
|
|
3373
|
-
publicIpv4:
|
|
3374
|
-
publicIpv6:
|
|
3375
|
-
proxyUrl:
|
|
3539
|
+
publicIpv4: runtimeHostIpv4,
|
|
3540
|
+
publicIpv6: runtime.ipv6 ?? null,
|
|
3541
|
+
proxyUrl: activeProxyUrl,
|
|
3376
3542
|
mappedPorts: mappedPorts2
|
|
3377
3543
|
},
|
|
3378
3544
|
bootstrap: {
|
|
@@ -3401,54 +3567,120 @@ var SponsorRelayController = class {
|
|
|
3401
3567
|
aggregateItemHash: bootstrapConfigPublication.aggregateItemHash,
|
|
3402
3568
|
aggregateStatus: bootstrapConfigPublication.aggregateStatus
|
|
3403
3569
|
});
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3570
|
+
this.emitProgress({
|
|
3571
|
+
stage: "deployment-confirmed",
|
|
3572
|
+
label: "Waiting for guest config",
|
|
3573
|
+
progress: 94,
|
|
3574
|
+
status: "info",
|
|
3575
|
+
itemHash: args.itemHash,
|
|
3576
|
+
detail: "Waiting for the relay VM to acknowledge that it consumed the Aleph bootstrap config."
|
|
3577
|
+
});
|
|
3578
|
+
const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
|
|
3579
|
+
deploymentToken: args.deploymentToken,
|
|
3580
|
+
ownerAddress: this.state.wallet.address,
|
|
3581
|
+
instanceItemHash: args.itemHash,
|
|
3582
|
+
fetch: asJsonFetch,
|
|
3583
|
+
apiHost: this.client.apiHost,
|
|
3584
|
+
attempts: 40,
|
|
3585
|
+
delayMs: 3e3
|
|
3586
|
+
});
|
|
3587
|
+
this.trace("deploy:bootstrap-config-signal-visible", {
|
|
3588
|
+
itemHash: args.itemHash,
|
|
3589
|
+
deploymentToken: args.deploymentToken,
|
|
3590
|
+
bootstrapConfigSignal
|
|
3591
|
+
});
|
|
3592
|
+
if (!bootstrapConfigSignal) {
|
|
3593
|
+
throw new Error(
|
|
3594
|
+
"The relay VM did not confirm that it applied the Aleph bootstrap config in time."
|
|
3595
|
+
);
|
|
3596
|
+
}
|
|
3597
|
+
bootstrapSignalRelayMetadata = typeof bootstrapConfigSignal.peerId === "string" && bootstrapConfigSignal.peerId.length > 0 && Array.isArray(bootstrapConfigSignal.probeMultiaddrs) && bootstrapConfigSignal.probeMultiaddrs.length > 0 ? {
|
|
3598
|
+
peerId: bootstrapConfigSignal.peerId,
|
|
3599
|
+
probeMultiaddrs: bootstrapConfigSignal.probeMultiaddrs,
|
|
3600
|
+
browserBootstrapMultiaddrs: bootstrapConfigSignal.browserBootstrapMultiaddrs ?? []
|
|
3601
|
+
} : null;
|
|
3602
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3603
|
+
this.trace("deploy:bootstrap-proxy-fallback", {
|
|
3424
3604
|
itemHash: args.itemHash,
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
metadataUrl: result.metadataUrl,
|
|
3429
|
-
hostIpv4: result.hostIpv4,
|
|
3430
|
-
setupPort: result.setupPort,
|
|
3431
|
-
ok: result.ok,
|
|
3432
|
-
status: result.status,
|
|
3433
|
-
ready: result.ready,
|
|
3434
|
-
error: result.error ?? null,
|
|
3435
|
-
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3436
|
-
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3437
|
-
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3438
|
-
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3439
|
-
metadata?.browser_bootstrap_multiaddrs
|
|
3440
|
-
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3605
|
+
deploymentToken: args.deploymentToken,
|
|
3606
|
+
proxyUrl: runtime.proxyUrl,
|
|
3607
|
+
bootstrapSignalRelayMetadata
|
|
3441
3608
|
});
|
|
3442
3609
|
this.emitProgress({
|
|
3443
|
-
stage: "
|
|
3444
|
-
label: "
|
|
3445
|
-
progress:
|
|
3446
|
-
status:
|
|
3610
|
+
stage: "deployment-confirmed",
|
|
3611
|
+
label: "Continuing without HTTPS route",
|
|
3612
|
+
progress: 94,
|
|
3613
|
+
status: "warning",
|
|
3447
3614
|
itemHash: args.itemHash,
|
|
3448
|
-
detail:
|
|
3615
|
+
detail: "The 2n6 HTTPS route stayed inactive, so guest setup continued without Caddy. Secure browser routing can be enabled later when the proxy becomes available."
|
|
3449
3616
|
});
|
|
3617
|
+
runtime = {
|
|
3618
|
+
...runtime,
|
|
3619
|
+
proxyUrl: null,
|
|
3620
|
+
webAccess: runtime.webAccess ? {
|
|
3621
|
+
...runtime.webAccess,
|
|
3622
|
+
active: false
|
|
3623
|
+
} : runtime.webAccess
|
|
3624
|
+
};
|
|
3450
3625
|
}
|
|
3451
|
-
}
|
|
3626
|
+
}
|
|
3627
|
+
let relayMetadata = bootstrapSignalRelayMetadata;
|
|
3628
|
+
if (relayMetadata) {
|
|
3629
|
+
this.trace("deploy:relay-metadata-from-bootstrap-signal", {
|
|
3630
|
+
itemHash: args.itemHash,
|
|
3631
|
+
peerId: relayMetadata.peerId,
|
|
3632
|
+
probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
|
|
3633
|
+
browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
|
|
3634
|
+
});
|
|
3635
|
+
} else {
|
|
3636
|
+
this.emitProgress({
|
|
3637
|
+
stage: "publishing-bootstrap",
|
|
3638
|
+
label: "Waiting for secure relay metadata",
|
|
3639
|
+
progress: 95,
|
|
3640
|
+
status: "warning",
|
|
3641
|
+
itemHash: args.itemHash,
|
|
3642
|
+
detail: "Runtime networking is available. Waiting for the secure relay metadata endpoint to report final public multiaddrs."
|
|
3643
|
+
});
|
|
3644
|
+
relayMetadata = await fetchRelayMetadataForRuntime({
|
|
3645
|
+
runtime,
|
|
3646
|
+
fetch: (url, init) => fetch(url, init),
|
|
3647
|
+
preferSecureMetadata: profile === "uc-go-peer" && Boolean(runtime.proxyUrl),
|
|
3648
|
+
attempts: 80,
|
|
3649
|
+
delayMs: 3e3,
|
|
3650
|
+
timeoutMs: 24e4,
|
|
3651
|
+
onAttempt: (result) => {
|
|
3652
|
+
const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
|
|
3653
|
+
const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
|
|
3654
|
+
this.trace("deploy:relay-metadata-attempt", {
|
|
3655
|
+
itemHash: args.itemHash,
|
|
3656
|
+
attempt: result.attempt,
|
|
3657
|
+
attempts: result.attempts,
|
|
3658
|
+
requestUrl: result.requestUrl,
|
|
3659
|
+
metadataUrl: result.metadataUrl,
|
|
3660
|
+
hostIpv4: result.hostIpv4,
|
|
3661
|
+
setupPort: result.setupPort,
|
|
3662
|
+
ok: result.ok,
|
|
3663
|
+
status: result.status,
|
|
3664
|
+
ready: result.ready,
|
|
3665
|
+
error: result.error ?? null,
|
|
3666
|
+
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3667
|
+
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3668
|
+
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3669
|
+
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3670
|
+
metadata?.browser_bootstrap_multiaddrs
|
|
3671
|
+
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3672
|
+
});
|
|
3673
|
+
this.emitProgress({
|
|
3674
|
+
stage: "publishing-bootstrap",
|
|
3675
|
+
label: result.ready ? "Secure relay metadata ready" : "Waiting for secure relay metadata",
|
|
3676
|
+
progress: 95,
|
|
3677
|
+
status: result.ready ? "success" : "warning",
|
|
3678
|
+
itemHash: args.itemHash,
|
|
3679
|
+
detail: result.ready ? `Relay metadata ${result.attempt}/${result.attempts}: secure endpoint responded and public multiaddrs are available.` : `Relay metadata ${result.attempt}/${result.attempts}: waiting for ${result.metadataUrl}.`
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3452
3684
|
if (!relayMetadata) {
|
|
3453
3685
|
throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
|
|
3454
3686
|
}
|
|
@@ -3589,6 +3821,15 @@ var SponsorRelayController = class {
|
|
|
3589
3821
|
const currentProgress = this.state.deploymentProgress;
|
|
3590
3822
|
const currentProgressIsForLatestHash = currentProgress.itemHash === itemHash || currentProgress.itemHash == null && currentProgress.stage !== "idle" && currentProgress.stage !== "error";
|
|
3591
3823
|
const keepTerminalSuccess = this.lastCompletedDeploymentHash === itemHash || currentProgressIsForLatestHash && currentProgress.stage === "completed" && currentProgress.status === "success";
|
|
3824
|
+
if (currentProgressIsForLatestHash && currentProgress.stage === "publishing-bootstrap") {
|
|
3825
|
+
this.trace("progress:retain-active-phase", {
|
|
3826
|
+
reason: "latest refresh would overwrite active relay metadata/bootstrap progress",
|
|
3827
|
+
itemHash,
|
|
3828
|
+
label: currentProgress.label,
|
|
3829
|
+
progress: currentProgress.progress
|
|
3830
|
+
});
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3592
3833
|
const status = latest.details.messageStatus;
|
|
3593
3834
|
if (status === "rejected") {
|
|
3594
3835
|
this.emitProgress({
|
|
@@ -4228,10 +4469,11 @@ var SponsorRelayController = class {
|
|
|
4228
4469
|
crnHash: candidateCrn.hash,
|
|
4229
4470
|
crns: this.state.crns,
|
|
4230
4471
|
crnListUrl: this.props.crnListUrl,
|
|
4472
|
+
requirePublicGuestIpv6ForProxy: true,
|
|
4231
4473
|
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
4232
4474
|
delayMs: UI_RUNTIME_WAIT_DELAY_MS,
|
|
4233
4475
|
onAttempt: (runtime2, attempt, attempts) => {
|
|
4234
|
-
if (runtime2.
|
|
4476
|
+
if (runtime2.diagnostics?.state === "ready") {
|
|
4235
4477
|
this.emitProgress({
|
|
4236
4478
|
stage: "deployment-confirmed",
|
|
4237
4479
|
label: "Runtime ready",
|
|
@@ -4242,6 +4484,17 @@ var SponsorRelayController = class {
|
|
|
4242
4484
|
});
|
|
4243
4485
|
return;
|
|
4244
4486
|
}
|
|
4487
|
+
if (runtime2.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
4488
|
+
this.emitProgress({
|
|
4489
|
+
stage: "deployment-confirmed",
|
|
4490
|
+
label: "Rejecting unusable runtime",
|
|
4491
|
+
progress: 91,
|
|
4492
|
+
status: "warning",
|
|
4493
|
+
itemHash: result.itemHash,
|
|
4494
|
+
detail: runtime2.diagnostics?.reason ? `Runtime ${attempt}/${attempts}: ${runtime2.diagnostics.reason} Cleaning up this CRN attempt before retrying another relay host.` : `Runtime ${attempt}/${attempts}: the CRN exposed unusable guest networking. Cleaning up this CRN attempt before retrying another relay host.`
|
|
4495
|
+
});
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4245
4498
|
this.emitProgress({
|
|
4246
4499
|
stage: "deployment-confirmed",
|
|
4247
4500
|
label: "Waiting for runtime",
|
|
@@ -4252,7 +4505,7 @@ var SponsorRelayController = class {
|
|
|
4252
4505
|
});
|
|
4253
4506
|
}
|
|
4254
4507
|
});
|
|
4255
|
-
if (
|
|
4508
|
+
if (runtime.diagnostics?.state !== "ready") {
|
|
4256
4509
|
throw new Error(
|
|
4257
4510
|
runtime.diagnostics?.reason ?? "Deployment was processed, but runtime networking never exposed mapped ports."
|
|
4258
4511
|
);
|
|
@@ -4301,6 +4554,7 @@ var SponsorRelayController = class {
|
|
|
4301
4554
|
} catch (error) {
|
|
4302
4555
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
4303
4556
|
const attemptLabel = crnDisplayName;
|
|
4557
|
+
const failedDueToInvalidIpv6 = /not globally routable|public guest IPv6/i.test(lastError.message);
|
|
4304
4558
|
attemptErrors.push(`${attemptLabel}: ${lastError.message}`);
|
|
4305
4559
|
this.trace("deploy:crn-attempt-error", {
|
|
4306
4560
|
crnHash: candidateCrn.hash,
|
|
@@ -4309,7 +4563,23 @@ var SponsorRelayController = class {
|
|
|
4309
4563
|
error: lastError.message
|
|
4310
4564
|
});
|
|
4311
4565
|
if (attemptItemHash) {
|
|
4566
|
+
this.emitProgress({
|
|
4567
|
+
stage: "deployment-confirmed",
|
|
4568
|
+
label: failedDueToInvalidIpv6 ? "Rejecting unusable runtime" : "Cleaning up failed attempt",
|
|
4569
|
+
progress: failedDueToInvalidIpv6 ? 91 : 16,
|
|
4570
|
+
status: "warning",
|
|
4571
|
+
itemHash: attemptItemHash,
|
|
4572
|
+
detail: failedDueToInvalidIpv6 ? `${attemptLabel} exposed runtime networking that cannot support proxy-backed HTTPS. Cleaning up this attempt before retrying another CRN.` : `${attemptLabel} failed: ${lastError.message} Cleaning up this attempt before retrying another CRN.`
|
|
4573
|
+
});
|
|
4312
4574
|
if (attemptDeploymentToken) {
|
|
4575
|
+
this.emitProgress({
|
|
4576
|
+
stage: "deployment-confirmed",
|
|
4577
|
+
label: "Cleaning up failed attempt",
|
|
4578
|
+
progress: 17,
|
|
4579
|
+
status: "warning",
|
|
4580
|
+
itemHash: attemptItemHash,
|
|
4581
|
+
detail: "Removing bootstrap configuration for the failed attempt. MetaMask may request a cleanup signature."
|
|
4582
|
+
});
|
|
4313
4583
|
this.trace("deploy:bootstrap-config-cleanup-start", {
|
|
4314
4584
|
itemHash: attemptItemHash,
|
|
4315
4585
|
deploymentToken: attemptDeploymentToken
|
|
@@ -4337,6 +4607,14 @@ var SponsorRelayController = class {
|
|
|
4337
4607
|
});
|
|
4338
4608
|
});
|
|
4339
4609
|
}
|
|
4610
|
+
this.emitProgress({
|
|
4611
|
+
stage: "deployment-confirmed",
|
|
4612
|
+
label: "Cleaning up failed attempt",
|
|
4613
|
+
progress: 18,
|
|
4614
|
+
status: "warning",
|
|
4615
|
+
itemHash: attemptItemHash,
|
|
4616
|
+
detail: "Forgetting the failed deployment attempt on Aleph. MetaMask may request another cleanup signature."
|
|
4617
|
+
});
|
|
4340
4618
|
this.trace("deploy:forget-failed-attempt-start", {
|
|
4341
4619
|
itemHash: attemptItemHash,
|
|
4342
4620
|
crnHash: candidateCrn.hash,
|
|
@@ -4369,11 +4647,11 @@ var SponsorRelayController = class {
|
|
|
4369
4647
|
if (candidateIndex < orderedCrns.length - 1) {
|
|
4370
4648
|
this.emitProgress({
|
|
4371
4649
|
stage: "selecting-crn",
|
|
4372
|
-
label:
|
|
4650
|
+
label: "Retrying on next CRN",
|
|
4373
4651
|
progress: 20,
|
|
4374
4652
|
status: "warning",
|
|
4375
4653
|
itemHash: attemptItemHash,
|
|
4376
|
-
detail: `${attemptLabel}
|
|
4654
|
+
detail: `${attemptLabel} was discarded: ${lastError.message}`,
|
|
4377
4655
|
error: lastError.message
|
|
4378
4656
|
});
|
|
4379
4657
|
continue;
|
|
@@ -4405,6 +4683,17 @@ var SponsorRelayController = class {
|
|
|
4405
4683
|
this.patch({ errorText: "Connect MetaMask before deleting instances." });
|
|
4406
4684
|
return;
|
|
4407
4685
|
}
|
|
4686
|
+
const linkedRegistrationHashes = Array.from(
|
|
4687
|
+
new Set(
|
|
4688
|
+
this.state.bootstrapRegistrations.filter((entry) => entry.instanceItemHash === instanceHash).map(
|
|
4689
|
+
(entry) => entry.messageHash?.trim() ?? entry.itemHash?.trim() ?? entry.hash?.trim() ?? null
|
|
4690
|
+
).filter((value) => Boolean(value))
|
|
4691
|
+
)
|
|
4692
|
+
);
|
|
4693
|
+
const hashesToForget = Array.from(
|
|
4694
|
+
/* @__PURE__ */ new Set([instanceHash, ...linkedRegistrationHashes])
|
|
4695
|
+
);
|
|
4696
|
+
const linkedRegistrationCount = linkedRegistrationHashes.length;
|
|
4408
4697
|
this.patch({
|
|
4409
4698
|
busy: { deletingInstanceHash: instanceHash },
|
|
4410
4699
|
errorText: null,
|
|
@@ -4417,12 +4706,12 @@ var SponsorRelayController = class {
|
|
|
4417
4706
|
progress: 6,
|
|
4418
4707
|
status: "warning",
|
|
4419
4708
|
itemHash: instanceHash,
|
|
4420
|
-
detail: "Preparing Aleph FORGET message."
|
|
4709
|
+
detail: linkedRegistrationCount > 0 ? `Preparing Aleph FORGET message for the instance and ${linkedRegistrationCount} linked bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"}.` : "Preparing Aleph FORGET message."
|
|
4421
4710
|
});
|
|
4422
4711
|
await forgetAlephMessages({
|
|
4423
4712
|
sender: this.state.wallet.address,
|
|
4424
|
-
hashes:
|
|
4425
|
-
reason: "Deleted from Sponsor Relay panel",
|
|
4713
|
+
hashes: hashesToForget,
|
|
4714
|
+
reason: linkedRegistrationCount > 0 ? `Deleted instance and ${linkedRegistrationCount} linked relay bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"} from Sponsor Relay panel` : "Deleted from Sponsor Relay panel",
|
|
4426
4715
|
signer: personalSign,
|
|
4427
4716
|
hasher: sha256Hex,
|
|
4428
4717
|
fetch: asJsonFetch,
|
|
@@ -4441,17 +4730,20 @@ var SponsorRelayController = class {
|
|
|
4441
4730
|
progress: 92,
|
|
4442
4731
|
status: "info",
|
|
4443
4732
|
itemHash: instanceHash,
|
|
4444
|
-
detail: "Reloading current deployments."
|
|
4733
|
+
detail: linkedRegistrationCount > 0 ? "Reloading current deployments and relay bootstrap registrations." : "Reloading current deployments."
|
|
4445
4734
|
});
|
|
4446
4735
|
await this.refresh();
|
|
4447
|
-
this.trace("delete:submitted", {
|
|
4736
|
+
this.trace("delete:submitted", {
|
|
4737
|
+
instanceHash,
|
|
4738
|
+
linkedRegistrationHashes
|
|
4739
|
+
});
|
|
4448
4740
|
this.emitProgress({
|
|
4449
4741
|
stage: "completed",
|
|
4450
4742
|
label: "Delete completed",
|
|
4451
4743
|
progress: 100,
|
|
4452
4744
|
status: "success",
|
|
4453
4745
|
itemHash: instanceHash,
|
|
4454
|
-
detail: "Delete request submitted and deployments refreshed.",
|
|
4746
|
+
detail: linkedRegistrationCount > 0 ? `Delete request submitted, including ${linkedRegistrationCount} linked bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"}, and state refreshed.` : "Delete request submitted and deployments refreshed.",
|
|
4455
4747
|
error: null
|
|
4456
4748
|
});
|
|
4457
4749
|
} catch (error) {
|
package/shared/index.d.ts
CHANGED
|
@@ -309,7 +309,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
|
|
|
309
309
|
declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
|
|
310
310
|
declare const DEPLOYMENT_PENDING_WARNING_MS: number;
|
|
311
311
|
|
|
312
|
-
declare const UI_PACKAGE_VERSION = "0.4.
|
|
312
|
+
declare const UI_PACKAGE_VERSION = "0.4.2";
|
|
313
313
|
|
|
314
314
|
interface RootfsRequiredPortForward {
|
|
315
315
|
port: number;
|
package/shared/index.js
CHANGED
|
@@ -27,7 +27,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
|
27
27
|
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
28
28
|
|
|
29
29
|
// src/shared/package-version.ts
|
|
30
|
-
var UI_PACKAGE_VERSION = "0.4.
|
|
30
|
+
var UI_PACKAGE_VERSION = "0.4.2";
|
|
31
31
|
|
|
32
32
|
// src/shared/format.ts
|
|
33
33
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -1236,16 +1236,6 @@ function normalizeBroadcastStatus(httpStatus, responseStatus) {
|
|
|
1236
1236
|
if (httpStatus === 202) return "pending";
|
|
1237
1237
|
return normalizeMessageStatus2(responseStatus);
|
|
1238
1238
|
}
|
|
1239
|
-
function isInvalidMessageFormatResponse2(response, payload) {
|
|
1240
|
-
if (response.status !== 422) return false;
|
|
1241
|
-
const details = payload?.details;
|
|
1242
|
-
if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
|
|
1243
|
-
if (details && typeof details === "object") {
|
|
1244
|
-
const detailMessage = details.message;
|
|
1245
|
-
if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
|
|
1246
|
-
}
|
|
1247
|
-
return false;
|
|
1248
|
-
}
|
|
1249
1239
|
function isRetryableBroadcastFailure(response, payload) {
|
|
1250
1240
|
if (response.status >= 500) return true;
|
|
1251
1241
|
const publicationStatus = payload?.publication_status?.status;
|
|
@@ -1267,25 +1257,22 @@ async function postBroadcastPayload2(body, options) {
|
|
|
1267
1257
|
};
|
|
1268
1258
|
}
|
|
1269
1259
|
async function broadcastAlephMessage2(message, options) {
|
|
1270
|
-
const
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
];
|
|
1275
|
-
for (let index = 0; index < attempts.length; index += 1) {
|
|
1276
|
-
const result = await postBroadcastPayload2(attempts[index], {
|
|
1260
|
+
const requestBody = { sync: options.sync ?? false, message };
|
|
1261
|
+
const maxAttempts = 3;
|
|
1262
|
+
for (let index = 0; index < maxAttempts; index += 1) {
|
|
1263
|
+
const result = await postBroadcastPayload2(requestBody, {
|
|
1277
1264
|
apiHost: options.apiHost,
|
|
1278
1265
|
fetch: options.fetch
|
|
1279
1266
|
});
|
|
1280
1267
|
if (result.httpStatus === 202 || normalizeBroadcastStatus(result.httpStatus, result.response?.message_status) !== "unknown" || result.httpStatus >= 200 && result.httpStatus < 300) {
|
|
1281
1268
|
return result;
|
|
1282
1269
|
}
|
|
1283
|
-
const canRetry = index <
|
|
1270
|
+
const canRetry = index < maxAttempts - 1 && isRetryableBroadcastFailure({ status: result.httpStatus }, result.response);
|
|
1284
1271
|
if (!canRetry) {
|
|
1285
1272
|
throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response ?? {})}`);
|
|
1286
1273
|
}
|
|
1287
1274
|
}
|
|
1288
|
-
throw new Error("Broadcast failed:
|
|
1275
|
+
throw new Error("Broadcast failed: retry budget exhausted");
|
|
1289
1276
|
}
|
|
1290
1277
|
|
|
1291
1278
|
// ../core/src/forget.ts
|
|
@@ -1652,6 +1639,26 @@ function asString3(value) {
|
|
|
1652
1639
|
function asNumber3(value) {
|
|
1653
1640
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1654
1641
|
}
|
|
1642
|
+
function normalizeIpv6(value) {
|
|
1643
|
+
if (typeof value !== "string") return null;
|
|
1644
|
+
const normalized = value.trim().toLowerCase();
|
|
1645
|
+
if (!normalized) return null;
|
|
1646
|
+
const zoneIndex = normalized.indexOf("%");
|
|
1647
|
+
return zoneIndex >= 0 ? normalized.slice(0, zoneIndex) : normalized;
|
|
1648
|
+
}
|
|
1649
|
+
function isLikelyGlobalIpv6(value) {
|
|
1650
|
+
const normalized = normalizeIpv6(value);
|
|
1651
|
+
if (!normalized || normalized === "::" || normalized === "::1") return false;
|
|
1652
|
+
const firstHextetText = normalized.split(":", 1)[0];
|
|
1653
|
+
if (!firstHextetText) return false;
|
|
1654
|
+
const firstHextet = Number.parseInt(firstHextetText, 16);
|
|
1655
|
+
if (!Number.isFinite(firstHextet)) return false;
|
|
1656
|
+
if ((firstHextet & 65024) === 64512) return false;
|
|
1657
|
+
if ((firstHextet & 65472) === 65152) return false;
|
|
1658
|
+
if ((firstHextet & 65280) === 65280) return false;
|
|
1659
|
+
if ((firstHextet & 65472) === 65216) return false;
|
|
1660
|
+
return true;
|
|
1661
|
+
}
|
|
1655
1662
|
function findCrnByHash(crns, crnHash) {
|
|
1656
1663
|
return crns.find((crn) => crn.hash === crnHash) ?? null;
|
|
1657
1664
|
}
|
|
@@ -1732,10 +1739,22 @@ function describeRuntimeAvailability(runtime) {
|
|
|
1732
1739
|
const execution = runtime.execution ?? null;
|
|
1733
1740
|
const mappedPorts2 = runtime.mappedPorts ?? {};
|
|
1734
1741
|
const hostIpv4 = runtime.hostIpv4 ?? null;
|
|
1742
|
+
const ipv6 = runtime.ipv6 ?? null;
|
|
1735
1743
|
const proxyUrl = runtime.proxyUrl ?? null;
|
|
1736
1744
|
const schedulerSource = runtime.allocation?.source ?? null;
|
|
1737
1745
|
const webAccessActive = runtime.webAccess?.active ?? null;
|
|
1738
1746
|
const mappedPortCount = Object.keys(mappedPorts2).length;
|
|
1747
|
+
if (hostIpv4 && mappedPortCount > 0 && runtime.requirePublicGuestIpv6ForProxy === true && proxyUrl && !isLikelyGlobalIpv6(ipv6)) {
|
|
1748
|
+
return {
|
|
1749
|
+
state: "execution-invalid-public-ipv6",
|
|
1750
|
+
reason: `The CRN exposed runtime networking, but the guest IPv6${ipv6 ? ` (${ipv6})` : ""} is not globally routable. Proxy-backed HTTPS activation cannot succeed until the CRN reports a public guest IPv6.`,
|
|
1751
|
+
schedulerSource,
|
|
1752
|
+
executionSeen: Boolean(execution),
|
|
1753
|
+
webAccessActive,
|
|
1754
|
+
mappedPortCount,
|
|
1755
|
+
proxyUrl
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1739
1758
|
if (hostIpv4 && mappedPortCount > 0) {
|
|
1740
1759
|
return {
|
|
1741
1760
|
state: "ready",
|
|
@@ -1852,7 +1871,8 @@ async function fetchVmRuntime(args) {
|
|
|
1852
1871
|
hostIpv4,
|
|
1853
1872
|
ipv6,
|
|
1854
1873
|
proxyUrl,
|
|
1855
|
-
mappedPorts: mappedPorts2
|
|
1874
|
+
mappedPorts: mappedPorts2,
|
|
1875
|
+
requirePublicGuestIpv6ForProxy: args.requirePublicGuestIpv6ForProxy
|
|
1856
1876
|
});
|
|
1857
1877
|
return {
|
|
1858
1878
|
allocation,
|
|
@@ -1876,7 +1896,7 @@ async function waitForVmRuntime(args) {
|
|
|
1876
1896
|
let lastRuntime = await fetchVmRuntime(args);
|
|
1877
1897
|
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1878
1898
|
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1879
|
-
if (lastRuntime.
|
|
1899
|
+
if (lastRuntime.diagnostics?.state === "ready" || lastRuntime.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
1880
1900
|
return lastRuntime;
|
|
1881
1901
|
}
|
|
1882
1902
|
await sleep2(delayMs);
|
|
@@ -2675,6 +2695,16 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
2675
2695
|
|
|
2676
2696
|
// ../core/src/bootstrap-config.ts
|
|
2677
2697
|
var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
|
|
2698
|
+
var VM_BOOTSTRAP_CONFIG_SIGNAL_REF = "vm-bootstrap-config";
|
|
2699
|
+
var VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE = "vm-bootstrap-config-status";
|
|
2700
|
+
function parseJsonObject(value) {
|
|
2701
|
+
try {
|
|
2702
|
+
const parsed = JSON.parse(value);
|
|
2703
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2704
|
+
} catch {
|
|
2705
|
+
return null;
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2678
2708
|
async function fetchVmBootstrapConfigAggregate(address, options) {
|
|
2679
2709
|
const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2680
2710
|
requestUrl.searchParams.set("keys", VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY);
|
|
@@ -2794,6 +2824,89 @@ async function deleteVmBootstrapConfig(args) {
|
|
|
2794
2824
|
aggregate: content
|
|
2795
2825
|
};
|
|
2796
2826
|
}
|
|
2827
|
+
function normalizeVmBootstrapConfigSignalRecord(value) {
|
|
2828
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2829
|
+
const candidate = value;
|
|
2830
|
+
const deploymentToken = typeof candidate.deploymentToken === "string" ? candidate.deploymentToken.trim() : "";
|
|
2831
|
+
const status = typeof candidate.status === "string" ? candidate.status.trim() : "";
|
|
2832
|
+
const profile = typeof candidate.profile === "string" ? candidate.profile.trim() : "";
|
|
2833
|
+
const ownerAddress = typeof candidate.ownerAddress === "string" ? candidate.ownerAddress.trim() : "";
|
|
2834
|
+
const instanceItemHash = typeof candidate.instanceItemHash === "string" ? candidate.instanceItemHash.trim() : "";
|
|
2835
|
+
const updatedAt = typeof candidate.updatedAt === "string" ? candidate.updatedAt.trim() : "";
|
|
2836
|
+
if (!deploymentToken || !status || !profile || !ownerAddress || !instanceItemHash || !updatedAt) {
|
|
2837
|
+
return null;
|
|
2838
|
+
}
|
|
2839
|
+
return {
|
|
2840
|
+
deploymentToken,
|
|
2841
|
+
status,
|
|
2842
|
+
profile,
|
|
2843
|
+
ownerAddress,
|
|
2844
|
+
instanceItemHash,
|
|
2845
|
+
updatedAt,
|
|
2846
|
+
publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
|
|
2847
|
+
authorization: candidate.authorization,
|
|
2848
|
+
peerId: typeof candidate.peerId === "string" && candidate.peerId.trim() ? candidate.peerId.trim() : null,
|
|
2849
|
+
probeMultiaddrs: Array.isArray(candidate.probeMultiaddrs) ? candidate.probeMultiaddrs.filter(
|
|
2850
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2851
|
+
) : [],
|
|
2852
|
+
browserBootstrapMultiaddrs: Array.isArray(candidate.browserBootstrapMultiaddrs) ? candidate.browserBootstrapMultiaddrs.filter(
|
|
2853
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2854
|
+
) : []
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
async function fetchVmBootstrapConfigSignals(args) {
|
|
2858
|
+
const requestUrl = new URL("/api/v0/posts.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2859
|
+
requestUrl.searchParams.set("refs", args.ref ?? VM_BOOTSTRAP_CONFIG_SIGNAL_REF);
|
|
2860
|
+
requestUrl.searchParams.set("types", args.postType ?? VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE);
|
|
2861
|
+
requestUrl.searchParams.set("pagination", String(Math.max(1, Number(args.pagination ?? 100))));
|
|
2862
|
+
requestUrl.searchParams.set("page", String(Math.max(1, Number(args.page ?? 1))));
|
|
2863
|
+
requestUrl.searchParams.set("sortOrder", "-1");
|
|
2864
|
+
const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
|
|
2865
|
+
if (!response.ok) {
|
|
2866
|
+
throw new Error(`VM bootstrap config signal request failed: ${response.status}`);
|
|
2867
|
+
}
|
|
2868
|
+
const payload = await response.json();
|
|
2869
|
+
const expectedDeploymentToken = args.deploymentToken?.trim() ?? "";
|
|
2870
|
+
const expectedOwnerAddress = args.ownerAddress?.trim().toLowerCase() ?? "";
|
|
2871
|
+
const expectedInstanceItemHash = args.instanceItemHash?.trim() ?? "";
|
|
2872
|
+
return (payload.posts ?? []).map((entry) => {
|
|
2873
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
2874
|
+
const parsed = "content" in entry && entry.content && typeof entry.content === "object" && !Array.isArray(entry.content) ? entry.content : "item_content" in entry && typeof entry.item_content === "string" ? parseJsonObject(entry.item_content) : null;
|
|
2875
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2876
|
+
const content = "deploymentToken" in parsed ? parsed : "content" in parsed && parsed.content && typeof parsed.content === "object" && !Array.isArray(parsed.content) ? parsed.content : null;
|
|
2877
|
+
if (!content || typeof content !== "object" || Array.isArray(content)) return null;
|
|
2878
|
+
return normalizeVmBootstrapConfigSignalRecord(content);
|
|
2879
|
+
}).filter((entry) => {
|
|
2880
|
+
if (!entry) return false;
|
|
2881
|
+
if (expectedDeploymentToken && entry.deploymentToken !== expectedDeploymentToken) return false;
|
|
2882
|
+
if (expectedOwnerAddress && entry.ownerAddress.toLowerCase() !== expectedOwnerAddress) return false;
|
|
2883
|
+
if (expectedInstanceItemHash && entry.instanceItemHash !== expectedInstanceItemHash) return false;
|
|
2884
|
+
return true;
|
|
2885
|
+
});
|
|
2886
|
+
}
|
|
2887
|
+
async function waitForVmBootstrapConfigSignal(args) {
|
|
2888
|
+
const attempts = Math.max(1, Number(args.attempts ?? 24));
|
|
2889
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 2500));
|
|
2890
|
+
const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2891
|
+
const expectedStatus = args.expectedStatus ?? "applied";
|
|
2892
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2893
|
+
const signals = await fetchVmBootstrapConfigSignals({
|
|
2894
|
+
deploymentToken: args.deploymentToken,
|
|
2895
|
+
ownerAddress: args.ownerAddress,
|
|
2896
|
+
instanceItemHash: args.instanceItemHash,
|
|
2897
|
+
fetch: args.fetch,
|
|
2898
|
+
apiHost: args.apiHost,
|
|
2899
|
+
ref: args.ref,
|
|
2900
|
+
postType: args.postType
|
|
2901
|
+
}).catch(() => []);
|
|
2902
|
+
const match = signals.find((entry) => entry.status === expectedStatus) ?? null;
|
|
2903
|
+
if (match) return match;
|
|
2904
|
+
if (attempt < attempts - 1) {
|
|
2905
|
+
await sleep2(delayMs);
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
return null;
|
|
2909
|
+
}
|
|
2797
2910
|
|
|
2798
2911
|
// ../core/src/bootstrap-reconcile.ts
|
|
2799
2912
|
function createRelayBootstrapRegistrationId(profile, instanceName, itemHash) {
|
|
@@ -3279,11 +3392,13 @@ var SponsorRelayController = class {
|
|
|
3279
3392
|
if (!args.runtime.hostIpv4) {
|
|
3280
3393
|
throw new Error("Relay runtime did not expose a host IPv4 address.");
|
|
3281
3394
|
}
|
|
3282
|
-
|
|
3395
|
+
let runtime = args.runtime;
|
|
3396
|
+
let mappedPorts2 = runtime.mappedPorts ?? {};
|
|
3283
3397
|
const setupPort = mappedPorts2["80"]?.host ?? null;
|
|
3284
3398
|
if (!setupPort) {
|
|
3285
3399
|
throw new Error("Relay runtime did not expose the temporary setup endpoint.");
|
|
3286
3400
|
}
|
|
3401
|
+
let bootstrapSignalRelayMetadata = null;
|
|
3287
3402
|
if (profile === "orbitdb-relay-pinner") {
|
|
3288
3403
|
this.emitProgress({
|
|
3289
3404
|
stage: "deployment-confirmed",
|
|
@@ -3352,6 +3467,57 @@ var SponsorRelayController = class {
|
|
|
3352
3467
|
if (!args.deploymentToken) {
|
|
3353
3468
|
throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
|
|
3354
3469
|
}
|
|
3470
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3471
|
+
this.emitProgress({
|
|
3472
|
+
stage: "deployment-confirmed",
|
|
3473
|
+
label: "Waiting for HTTPS route",
|
|
3474
|
+
progress: 93,
|
|
3475
|
+
status: "warning",
|
|
3476
|
+
itemHash: args.itemHash,
|
|
3477
|
+
detail: "The relay received a 2n6 hostname, but it is not active yet. Waiting before handing HTTPS config to the guest."
|
|
3478
|
+
});
|
|
3479
|
+
let latestRuntime = runtime;
|
|
3480
|
+
for (let attempt = 0; attempt < UI_RUNTIME_WAIT_ATTEMPTS; attempt += 1) {
|
|
3481
|
+
if (latestRuntime.webAccess?.active === true) {
|
|
3482
|
+
break;
|
|
3483
|
+
}
|
|
3484
|
+
await new Promise(
|
|
3485
|
+
(resolve) => globalThis.setTimeout(resolve, UI_RUNTIME_WAIT_DELAY_MS)
|
|
3486
|
+
);
|
|
3487
|
+
latestRuntime = await fetchVmRuntime({
|
|
3488
|
+
itemHash: args.itemHash,
|
|
3489
|
+
fetch: (url, init) => fetch(url, init),
|
|
3490
|
+
crnHash: runtime.selectedCrn?.hash ?? runtime.allocation?.crnHash ?? void 0,
|
|
3491
|
+
crns: this.state.crns,
|
|
3492
|
+
crnListUrl: this.props.crnListUrl,
|
|
3493
|
+
requirePublicGuestIpv6ForProxy: true
|
|
3494
|
+
}).catch(() => latestRuntime);
|
|
3495
|
+
this.emitProgress({
|
|
3496
|
+
stage: "deployment-confirmed",
|
|
3497
|
+
label: "Waiting for HTTPS route",
|
|
3498
|
+
progress: 93,
|
|
3499
|
+
status: latestRuntime.webAccess?.active === true ? "info" : "warning",
|
|
3500
|
+
itemHash: args.itemHash,
|
|
3501
|
+
detail: `2n6 activation ${attempt + 1}/${UI_RUNTIME_WAIT_ATTEMPTS}: ${latestRuntime.webAccess?.active === true ? "active" : "still pending"} for ${latestRuntime.proxyUrl ?? runtime.proxyUrl ?? "reserved proxy URL"}.`
|
|
3502
|
+
});
|
|
3503
|
+
this.trace("deploy:bootstrap-proxy-activation", {
|
|
3504
|
+
itemHash: args.itemHash,
|
|
3505
|
+
attempt: attempt + 1,
|
|
3506
|
+
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
3507
|
+
active: latestRuntime.webAccess?.active === true,
|
|
3508
|
+
proxyUrl: latestRuntime.proxyUrl ?? null
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
runtime = latestRuntime;
|
|
3512
|
+
mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
|
|
3513
|
+
}
|
|
3514
|
+
if (!runtime.hostIpv4) {
|
|
3515
|
+
throw new Error(
|
|
3516
|
+
"Relay runtime lost its host IPv4 address while waiting for HTTPS activation."
|
|
3517
|
+
);
|
|
3518
|
+
}
|
|
3519
|
+
const runtimeHostIpv4 = runtime.hostIpv4;
|
|
3520
|
+
const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
|
|
3355
3521
|
this.emitProgress({
|
|
3356
3522
|
stage: "deployment-confirmed",
|
|
3357
3523
|
label: "Publishing relay config",
|
|
@@ -3369,9 +3535,9 @@ var SponsorRelayController = class {
|
|
|
3369
3535
|
expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
|
|
3370
3536
|
status: "pending",
|
|
3371
3537
|
runtime: {
|
|
3372
|
-
publicIpv4:
|
|
3373
|
-
publicIpv6:
|
|
3374
|
-
proxyUrl:
|
|
3538
|
+
publicIpv4: runtimeHostIpv4,
|
|
3539
|
+
publicIpv6: runtime.ipv6 ?? null,
|
|
3540
|
+
proxyUrl: activeProxyUrl,
|
|
3375
3541
|
mappedPorts: mappedPorts2
|
|
3376
3542
|
},
|
|
3377
3543
|
bootstrap: {
|
|
@@ -3400,54 +3566,120 @@ var SponsorRelayController = class {
|
|
|
3400
3566
|
aggregateItemHash: bootstrapConfigPublication.aggregateItemHash,
|
|
3401
3567
|
aggregateStatus: bootstrapConfigPublication.aggregateStatus
|
|
3402
3568
|
});
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3569
|
+
this.emitProgress({
|
|
3570
|
+
stage: "deployment-confirmed",
|
|
3571
|
+
label: "Waiting for guest config",
|
|
3572
|
+
progress: 94,
|
|
3573
|
+
status: "info",
|
|
3574
|
+
itemHash: args.itemHash,
|
|
3575
|
+
detail: "Waiting for the relay VM to acknowledge that it consumed the Aleph bootstrap config."
|
|
3576
|
+
});
|
|
3577
|
+
const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
|
|
3578
|
+
deploymentToken: args.deploymentToken,
|
|
3579
|
+
ownerAddress: this.state.wallet.address,
|
|
3580
|
+
instanceItemHash: args.itemHash,
|
|
3581
|
+
fetch: asJsonFetch,
|
|
3582
|
+
apiHost: this.client.apiHost,
|
|
3583
|
+
attempts: 40,
|
|
3584
|
+
delayMs: 3e3
|
|
3585
|
+
});
|
|
3586
|
+
this.trace("deploy:bootstrap-config-signal-visible", {
|
|
3587
|
+
itemHash: args.itemHash,
|
|
3588
|
+
deploymentToken: args.deploymentToken,
|
|
3589
|
+
bootstrapConfigSignal
|
|
3590
|
+
});
|
|
3591
|
+
if (!bootstrapConfigSignal) {
|
|
3592
|
+
throw new Error(
|
|
3593
|
+
"The relay VM did not confirm that it applied the Aleph bootstrap config in time."
|
|
3594
|
+
);
|
|
3595
|
+
}
|
|
3596
|
+
bootstrapSignalRelayMetadata = typeof bootstrapConfigSignal.peerId === "string" && bootstrapConfigSignal.peerId.length > 0 && Array.isArray(bootstrapConfigSignal.probeMultiaddrs) && bootstrapConfigSignal.probeMultiaddrs.length > 0 ? {
|
|
3597
|
+
peerId: bootstrapConfigSignal.peerId,
|
|
3598
|
+
probeMultiaddrs: bootstrapConfigSignal.probeMultiaddrs,
|
|
3599
|
+
browserBootstrapMultiaddrs: bootstrapConfigSignal.browserBootstrapMultiaddrs ?? []
|
|
3600
|
+
} : null;
|
|
3601
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3602
|
+
this.trace("deploy:bootstrap-proxy-fallback", {
|
|
3423
3603
|
itemHash: args.itemHash,
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
metadataUrl: result.metadataUrl,
|
|
3428
|
-
hostIpv4: result.hostIpv4,
|
|
3429
|
-
setupPort: result.setupPort,
|
|
3430
|
-
ok: result.ok,
|
|
3431
|
-
status: result.status,
|
|
3432
|
-
ready: result.ready,
|
|
3433
|
-
error: result.error ?? null,
|
|
3434
|
-
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3435
|
-
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3436
|
-
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3437
|
-
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3438
|
-
metadata?.browser_bootstrap_multiaddrs
|
|
3439
|
-
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3604
|
+
deploymentToken: args.deploymentToken,
|
|
3605
|
+
proxyUrl: runtime.proxyUrl,
|
|
3606
|
+
bootstrapSignalRelayMetadata
|
|
3440
3607
|
});
|
|
3441
3608
|
this.emitProgress({
|
|
3442
|
-
stage: "
|
|
3443
|
-
label: "
|
|
3444
|
-
progress:
|
|
3445
|
-
status:
|
|
3609
|
+
stage: "deployment-confirmed",
|
|
3610
|
+
label: "Continuing without HTTPS route",
|
|
3611
|
+
progress: 94,
|
|
3612
|
+
status: "warning",
|
|
3446
3613
|
itemHash: args.itemHash,
|
|
3447
|
-
detail:
|
|
3614
|
+
detail: "The 2n6 HTTPS route stayed inactive, so guest setup continued without Caddy. Secure browser routing can be enabled later when the proxy becomes available."
|
|
3448
3615
|
});
|
|
3616
|
+
runtime = {
|
|
3617
|
+
...runtime,
|
|
3618
|
+
proxyUrl: null,
|
|
3619
|
+
webAccess: runtime.webAccess ? {
|
|
3620
|
+
...runtime.webAccess,
|
|
3621
|
+
active: false
|
|
3622
|
+
} : runtime.webAccess
|
|
3623
|
+
};
|
|
3449
3624
|
}
|
|
3450
|
-
}
|
|
3625
|
+
}
|
|
3626
|
+
let relayMetadata = bootstrapSignalRelayMetadata;
|
|
3627
|
+
if (relayMetadata) {
|
|
3628
|
+
this.trace("deploy:relay-metadata-from-bootstrap-signal", {
|
|
3629
|
+
itemHash: args.itemHash,
|
|
3630
|
+
peerId: relayMetadata.peerId,
|
|
3631
|
+
probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
|
|
3632
|
+
browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
|
|
3633
|
+
});
|
|
3634
|
+
} else {
|
|
3635
|
+
this.emitProgress({
|
|
3636
|
+
stage: "publishing-bootstrap",
|
|
3637
|
+
label: "Waiting for secure relay metadata",
|
|
3638
|
+
progress: 95,
|
|
3639
|
+
status: "warning",
|
|
3640
|
+
itemHash: args.itemHash,
|
|
3641
|
+
detail: "Runtime networking is available. Waiting for the secure relay metadata endpoint to report final public multiaddrs."
|
|
3642
|
+
});
|
|
3643
|
+
relayMetadata = await fetchRelayMetadataForRuntime({
|
|
3644
|
+
runtime,
|
|
3645
|
+
fetch: (url, init) => fetch(url, init),
|
|
3646
|
+
preferSecureMetadata: profile === "uc-go-peer" && Boolean(runtime.proxyUrl),
|
|
3647
|
+
attempts: 80,
|
|
3648
|
+
delayMs: 3e3,
|
|
3649
|
+
timeoutMs: 24e4,
|
|
3650
|
+
onAttempt: (result) => {
|
|
3651
|
+
const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
|
|
3652
|
+
const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
|
|
3653
|
+
this.trace("deploy:relay-metadata-attempt", {
|
|
3654
|
+
itemHash: args.itemHash,
|
|
3655
|
+
attempt: result.attempt,
|
|
3656
|
+
attempts: result.attempts,
|
|
3657
|
+
requestUrl: result.requestUrl,
|
|
3658
|
+
metadataUrl: result.metadataUrl,
|
|
3659
|
+
hostIpv4: result.hostIpv4,
|
|
3660
|
+
setupPort: result.setupPort,
|
|
3661
|
+
ok: result.ok,
|
|
3662
|
+
status: result.status,
|
|
3663
|
+
ready: result.ready,
|
|
3664
|
+
error: result.error ?? null,
|
|
3665
|
+
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3666
|
+
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3667
|
+
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3668
|
+
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3669
|
+
metadata?.browser_bootstrap_multiaddrs
|
|
3670
|
+
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3671
|
+
});
|
|
3672
|
+
this.emitProgress({
|
|
3673
|
+
stage: "publishing-bootstrap",
|
|
3674
|
+
label: result.ready ? "Secure relay metadata ready" : "Waiting for secure relay metadata",
|
|
3675
|
+
progress: 95,
|
|
3676
|
+
status: result.ready ? "success" : "warning",
|
|
3677
|
+
itemHash: args.itemHash,
|
|
3678
|
+
detail: result.ready ? `Relay metadata ${result.attempt}/${result.attempts}: secure endpoint responded and public multiaddrs are available.` : `Relay metadata ${result.attempt}/${result.attempts}: waiting for ${result.metadataUrl}.`
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3451
3683
|
if (!relayMetadata) {
|
|
3452
3684
|
throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
|
|
3453
3685
|
}
|
|
@@ -3588,6 +3820,15 @@ var SponsorRelayController = class {
|
|
|
3588
3820
|
const currentProgress = this.state.deploymentProgress;
|
|
3589
3821
|
const currentProgressIsForLatestHash = currentProgress.itemHash === itemHash || currentProgress.itemHash == null && currentProgress.stage !== "idle" && currentProgress.stage !== "error";
|
|
3590
3822
|
const keepTerminalSuccess = this.lastCompletedDeploymentHash === itemHash || currentProgressIsForLatestHash && currentProgress.stage === "completed" && currentProgress.status === "success";
|
|
3823
|
+
if (currentProgressIsForLatestHash && currentProgress.stage === "publishing-bootstrap") {
|
|
3824
|
+
this.trace("progress:retain-active-phase", {
|
|
3825
|
+
reason: "latest refresh would overwrite active relay metadata/bootstrap progress",
|
|
3826
|
+
itemHash,
|
|
3827
|
+
label: currentProgress.label,
|
|
3828
|
+
progress: currentProgress.progress
|
|
3829
|
+
});
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3591
3832
|
const status = latest.details.messageStatus;
|
|
3592
3833
|
if (status === "rejected") {
|
|
3593
3834
|
this.emitProgress({
|
|
@@ -4227,10 +4468,11 @@ var SponsorRelayController = class {
|
|
|
4227
4468
|
crnHash: candidateCrn.hash,
|
|
4228
4469
|
crns: this.state.crns,
|
|
4229
4470
|
crnListUrl: this.props.crnListUrl,
|
|
4471
|
+
requirePublicGuestIpv6ForProxy: true,
|
|
4230
4472
|
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
4231
4473
|
delayMs: UI_RUNTIME_WAIT_DELAY_MS,
|
|
4232
4474
|
onAttempt: (runtime2, attempt, attempts) => {
|
|
4233
|
-
if (runtime2.
|
|
4475
|
+
if (runtime2.diagnostics?.state === "ready") {
|
|
4234
4476
|
this.emitProgress({
|
|
4235
4477
|
stage: "deployment-confirmed",
|
|
4236
4478
|
label: "Runtime ready",
|
|
@@ -4241,6 +4483,17 @@ var SponsorRelayController = class {
|
|
|
4241
4483
|
});
|
|
4242
4484
|
return;
|
|
4243
4485
|
}
|
|
4486
|
+
if (runtime2.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
4487
|
+
this.emitProgress({
|
|
4488
|
+
stage: "deployment-confirmed",
|
|
4489
|
+
label: "Rejecting unusable runtime",
|
|
4490
|
+
progress: 91,
|
|
4491
|
+
status: "warning",
|
|
4492
|
+
itemHash: result.itemHash,
|
|
4493
|
+
detail: runtime2.diagnostics?.reason ? `Runtime ${attempt}/${attempts}: ${runtime2.diagnostics.reason} Cleaning up this CRN attempt before retrying another relay host.` : `Runtime ${attempt}/${attempts}: the CRN exposed unusable guest networking. Cleaning up this CRN attempt before retrying another relay host.`
|
|
4494
|
+
});
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4244
4497
|
this.emitProgress({
|
|
4245
4498
|
stage: "deployment-confirmed",
|
|
4246
4499
|
label: "Waiting for runtime",
|
|
@@ -4251,7 +4504,7 @@ var SponsorRelayController = class {
|
|
|
4251
4504
|
});
|
|
4252
4505
|
}
|
|
4253
4506
|
});
|
|
4254
|
-
if (
|
|
4507
|
+
if (runtime.diagnostics?.state !== "ready") {
|
|
4255
4508
|
throw new Error(
|
|
4256
4509
|
runtime.diagnostics?.reason ?? "Deployment was processed, but runtime networking never exposed mapped ports."
|
|
4257
4510
|
);
|
|
@@ -4300,6 +4553,7 @@ var SponsorRelayController = class {
|
|
|
4300
4553
|
} catch (error) {
|
|
4301
4554
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
4302
4555
|
const attemptLabel = crnDisplayName;
|
|
4556
|
+
const failedDueToInvalidIpv6 = /not globally routable|public guest IPv6/i.test(lastError.message);
|
|
4303
4557
|
attemptErrors.push(`${attemptLabel}: ${lastError.message}`);
|
|
4304
4558
|
this.trace("deploy:crn-attempt-error", {
|
|
4305
4559
|
crnHash: candidateCrn.hash,
|
|
@@ -4308,7 +4562,23 @@ var SponsorRelayController = class {
|
|
|
4308
4562
|
error: lastError.message
|
|
4309
4563
|
});
|
|
4310
4564
|
if (attemptItemHash) {
|
|
4565
|
+
this.emitProgress({
|
|
4566
|
+
stage: "deployment-confirmed",
|
|
4567
|
+
label: failedDueToInvalidIpv6 ? "Rejecting unusable runtime" : "Cleaning up failed attempt",
|
|
4568
|
+
progress: failedDueToInvalidIpv6 ? 91 : 16,
|
|
4569
|
+
status: "warning",
|
|
4570
|
+
itemHash: attemptItemHash,
|
|
4571
|
+
detail: failedDueToInvalidIpv6 ? `${attemptLabel} exposed runtime networking that cannot support proxy-backed HTTPS. Cleaning up this attempt before retrying another CRN.` : `${attemptLabel} failed: ${lastError.message} Cleaning up this attempt before retrying another CRN.`
|
|
4572
|
+
});
|
|
4311
4573
|
if (attemptDeploymentToken) {
|
|
4574
|
+
this.emitProgress({
|
|
4575
|
+
stage: "deployment-confirmed",
|
|
4576
|
+
label: "Cleaning up failed attempt",
|
|
4577
|
+
progress: 17,
|
|
4578
|
+
status: "warning",
|
|
4579
|
+
itemHash: attemptItemHash,
|
|
4580
|
+
detail: "Removing bootstrap configuration for the failed attempt. MetaMask may request a cleanup signature."
|
|
4581
|
+
});
|
|
4312
4582
|
this.trace("deploy:bootstrap-config-cleanup-start", {
|
|
4313
4583
|
itemHash: attemptItemHash,
|
|
4314
4584
|
deploymentToken: attemptDeploymentToken
|
|
@@ -4336,6 +4606,14 @@ var SponsorRelayController = class {
|
|
|
4336
4606
|
});
|
|
4337
4607
|
});
|
|
4338
4608
|
}
|
|
4609
|
+
this.emitProgress({
|
|
4610
|
+
stage: "deployment-confirmed",
|
|
4611
|
+
label: "Cleaning up failed attempt",
|
|
4612
|
+
progress: 18,
|
|
4613
|
+
status: "warning",
|
|
4614
|
+
itemHash: attemptItemHash,
|
|
4615
|
+
detail: "Forgetting the failed deployment attempt on Aleph. MetaMask may request another cleanup signature."
|
|
4616
|
+
});
|
|
4339
4617
|
this.trace("deploy:forget-failed-attempt-start", {
|
|
4340
4618
|
itemHash: attemptItemHash,
|
|
4341
4619
|
crnHash: candidateCrn.hash,
|
|
@@ -4368,11 +4646,11 @@ var SponsorRelayController = class {
|
|
|
4368
4646
|
if (candidateIndex < orderedCrns.length - 1) {
|
|
4369
4647
|
this.emitProgress({
|
|
4370
4648
|
stage: "selecting-crn",
|
|
4371
|
-
label:
|
|
4649
|
+
label: "Retrying on next CRN",
|
|
4372
4650
|
progress: 20,
|
|
4373
4651
|
status: "warning",
|
|
4374
4652
|
itemHash: attemptItemHash,
|
|
4375
|
-
detail: `${attemptLabel}
|
|
4653
|
+
detail: `${attemptLabel} was discarded: ${lastError.message}`,
|
|
4376
4654
|
error: lastError.message
|
|
4377
4655
|
});
|
|
4378
4656
|
continue;
|
|
@@ -4404,6 +4682,17 @@ var SponsorRelayController = class {
|
|
|
4404
4682
|
this.patch({ errorText: "Connect MetaMask before deleting instances." });
|
|
4405
4683
|
return;
|
|
4406
4684
|
}
|
|
4685
|
+
const linkedRegistrationHashes = Array.from(
|
|
4686
|
+
new Set(
|
|
4687
|
+
this.state.bootstrapRegistrations.filter((entry) => entry.instanceItemHash === instanceHash).map(
|
|
4688
|
+
(entry) => entry.messageHash?.trim() ?? entry.itemHash?.trim() ?? entry.hash?.trim() ?? null
|
|
4689
|
+
).filter((value) => Boolean(value))
|
|
4690
|
+
)
|
|
4691
|
+
);
|
|
4692
|
+
const hashesToForget = Array.from(
|
|
4693
|
+
/* @__PURE__ */ new Set([instanceHash, ...linkedRegistrationHashes])
|
|
4694
|
+
);
|
|
4695
|
+
const linkedRegistrationCount = linkedRegistrationHashes.length;
|
|
4407
4696
|
this.patch({
|
|
4408
4697
|
busy: { deletingInstanceHash: instanceHash },
|
|
4409
4698
|
errorText: null,
|
|
@@ -4416,12 +4705,12 @@ var SponsorRelayController = class {
|
|
|
4416
4705
|
progress: 6,
|
|
4417
4706
|
status: "warning",
|
|
4418
4707
|
itemHash: instanceHash,
|
|
4419
|
-
detail: "Preparing Aleph FORGET message."
|
|
4708
|
+
detail: linkedRegistrationCount > 0 ? `Preparing Aleph FORGET message for the instance and ${linkedRegistrationCount} linked bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"}.` : "Preparing Aleph FORGET message."
|
|
4420
4709
|
});
|
|
4421
4710
|
await forgetAlephMessages({
|
|
4422
4711
|
sender: this.state.wallet.address,
|
|
4423
|
-
hashes:
|
|
4424
|
-
reason: "Deleted from Sponsor Relay panel",
|
|
4712
|
+
hashes: hashesToForget,
|
|
4713
|
+
reason: linkedRegistrationCount > 0 ? `Deleted instance and ${linkedRegistrationCount} linked relay bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"} from Sponsor Relay panel` : "Deleted from Sponsor Relay panel",
|
|
4425
4714
|
signer: personalSign,
|
|
4426
4715
|
hasher: sha256Hex,
|
|
4427
4716
|
fetch: asJsonFetch,
|
|
@@ -4440,17 +4729,20 @@ var SponsorRelayController = class {
|
|
|
4440
4729
|
progress: 92,
|
|
4441
4730
|
status: "info",
|
|
4442
4731
|
itemHash: instanceHash,
|
|
4443
|
-
detail: "Reloading current deployments."
|
|
4732
|
+
detail: linkedRegistrationCount > 0 ? "Reloading current deployments and relay bootstrap registrations." : "Reloading current deployments."
|
|
4444
4733
|
});
|
|
4445
4734
|
await this.refresh();
|
|
4446
|
-
this.trace("delete:submitted", {
|
|
4735
|
+
this.trace("delete:submitted", {
|
|
4736
|
+
instanceHash,
|
|
4737
|
+
linkedRegistrationHashes
|
|
4738
|
+
});
|
|
4447
4739
|
this.emitProgress({
|
|
4448
4740
|
stage: "completed",
|
|
4449
4741
|
label: "Delete completed",
|
|
4450
4742
|
progress: 100,
|
|
4451
4743
|
status: "success",
|
|
4452
4744
|
itemHash: instanceHash,
|
|
4453
|
-
detail: "Delete request submitted and deployments refreshed.",
|
|
4745
|
+
detail: linkedRegistrationCount > 0 ? `Delete request submitted, including ${linkedRegistrationCount} linked bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"}, and state refreshed.` : "Delete request submitted and deployments refreshed.",
|
|
4454
4746
|
error: null
|
|
4455
4747
|
});
|
|
4456
4748
|
} catch (error) {
|