@le-space/ui 0.4.1 → 0.4.3
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.d.ts +3 -0
- package/react/index.js +581 -86
- package/shared/index.d.ts +4 -1
- package/shared/index.js +581 -86
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.3";
|
|
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,23 @@ 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;
|
|
1744
|
+
const executionGuestIpv6 = execution?.networking?.ipv6_ip ?? null;
|
|
1736
1745
|
const proxyUrl = runtime.proxyUrl ?? null;
|
|
1737
1746
|
const schedulerSource = runtime.allocation?.source ?? null;
|
|
1738
1747
|
const webAccessActive = runtime.webAccess?.active ?? null;
|
|
1739
1748
|
const mappedPortCount = Object.keys(mappedPorts2).length;
|
|
1749
|
+
if (hostIpv4 && mappedPortCount > 0 && runtime.requirePublicGuestIpv6ForProxy === true && proxyUrl && executionGuestIpv6 && !isLikelyGlobalIpv6(executionGuestIpv6)) {
|
|
1750
|
+
return {
|
|
1751
|
+
state: "execution-invalid-public-ipv6",
|
|
1752
|
+
reason: `The CRN exposed runtime networking, but the guest IPv6 (${executionGuestIpv6}) is not globally routable. Proxy-backed HTTPS activation cannot succeed until the CRN reports a public guest IPv6.`,
|
|
1753
|
+
schedulerSource,
|
|
1754
|
+
executionSeen: Boolean(execution),
|
|
1755
|
+
webAccessActive,
|
|
1756
|
+
mappedPortCount,
|
|
1757
|
+
proxyUrl
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1740
1760
|
if (hostIpv4 && mappedPortCount > 0) {
|
|
1741
1761
|
return {
|
|
1742
1762
|
state: "ready",
|
|
@@ -1853,7 +1873,8 @@ async function fetchVmRuntime(args) {
|
|
|
1853
1873
|
hostIpv4,
|
|
1854
1874
|
ipv6,
|
|
1855
1875
|
proxyUrl,
|
|
1856
|
-
mappedPorts: mappedPorts2
|
|
1876
|
+
mappedPorts: mappedPorts2,
|
|
1877
|
+
requirePublicGuestIpv6ForProxy: args.requirePublicGuestIpv6ForProxy
|
|
1857
1878
|
});
|
|
1858
1879
|
return {
|
|
1859
1880
|
allocation,
|
|
@@ -1877,7 +1898,7 @@ async function waitForVmRuntime(args) {
|
|
|
1877
1898
|
let lastRuntime = await fetchVmRuntime(args);
|
|
1878
1899
|
args.onAttempt?.(lastRuntime, 1, attempts);
|
|
1879
1900
|
for (let attempt = 0; attempt < attempts - 1; attempt += 1) {
|
|
1880
|
-
if (lastRuntime.
|
|
1901
|
+
if (lastRuntime.diagnostics?.state === "ready" || lastRuntime.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
1881
1902
|
return lastRuntime;
|
|
1882
1903
|
}
|
|
1883
1904
|
await sleep2(delayMs);
|
|
@@ -2676,6 +2697,55 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
2676
2697
|
|
|
2677
2698
|
// ../core/src/bootstrap-config.ts
|
|
2678
2699
|
var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
|
|
2700
|
+
var VM_BOOTSTRAP_CONFIG_SIGNAL_REF = "vm-bootstrap-config";
|
|
2701
|
+
var VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE = "vm-bootstrap-config-status";
|
|
2702
|
+
var VM_BOOTSTRAP_CONFIG_HISTORY_RETENTION_MS = 6 * 60 * 60 * 1e3;
|
|
2703
|
+
function parseJsonObject(value) {
|
|
2704
|
+
try {
|
|
2705
|
+
const parsed = JSON.parse(value);
|
|
2706
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2707
|
+
} catch {
|
|
2708
|
+
return null;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function parseTimestampMs(value) {
|
|
2712
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
2713
|
+
return value > 1e12 ? value : value * 1e3;
|
|
2714
|
+
}
|
|
2715
|
+
if (typeof value === "string" && value.trim()) {
|
|
2716
|
+
const numeric = Number(value);
|
|
2717
|
+
if (Number.isFinite(numeric)) {
|
|
2718
|
+
return numeric > 1e12 ? numeric : numeric * 1e3;
|
|
2719
|
+
}
|
|
2720
|
+
const parsed = Date.parse(value);
|
|
2721
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
2722
|
+
}
|
|
2723
|
+
return null;
|
|
2724
|
+
}
|
|
2725
|
+
function normalizeVmBootstrapConfigRecord(deploymentTokenKey, value, options = {}) {
|
|
2726
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2727
|
+
const candidate = value;
|
|
2728
|
+
const deploymentToken = typeof candidate.deploymentToken === "string" ? candidate.deploymentToken.trim() : "";
|
|
2729
|
+
const profile = typeof candidate.profile === "string" ? candidate.profile.trim() : "";
|
|
2730
|
+
const ownerAddress = typeof candidate.ownerAddress === "string" ? candidate.ownerAddress.trim() : "";
|
|
2731
|
+
const instanceItemHash = typeof candidate.instanceItemHash === "string" ? candidate.instanceItemHash.trim() : "";
|
|
2732
|
+
const createdAt = typeof candidate.createdAt === "string" ? candidate.createdAt.trim() : "";
|
|
2733
|
+
const expiresAt = typeof candidate.expiresAt === "string" ? candidate.expiresAt.trim() : "";
|
|
2734
|
+
if (!deploymentTokenKey.trim() || !deploymentToken || deploymentToken !== deploymentTokenKey.trim() || !profile || !ownerAddress || !instanceItemHash || !createdAt || !expiresAt) {
|
|
2735
|
+
return null;
|
|
2736
|
+
}
|
|
2737
|
+
const nowMs = Math.max(0, Number(options.nowMs ?? Date.now()));
|
|
2738
|
+
const maxRecordAgeMs = Math.max(
|
|
2739
|
+
6e4,
|
|
2740
|
+
Number(options.maxRecordAgeMs ?? VM_BOOTSTRAP_CONFIG_HISTORY_RETENTION_MS)
|
|
2741
|
+
);
|
|
2742
|
+
const createdAtMs = parseTimestampMs(createdAt);
|
|
2743
|
+
const expiresAtMs = parseTimestampMs(expiresAt);
|
|
2744
|
+
if (createdAtMs == null || expiresAtMs == null) return null;
|
|
2745
|
+
if (expiresAtMs <= nowMs) return null;
|
|
2746
|
+
if (createdAtMs < nowMs - maxRecordAgeMs) return null;
|
|
2747
|
+
return candidate;
|
|
2748
|
+
}
|
|
2679
2749
|
async function fetchVmBootstrapConfigAggregate(address, options) {
|
|
2680
2750
|
const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2681
2751
|
requestUrl.searchParams.set("keys", VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY);
|
|
@@ -2689,16 +2759,21 @@ async function fetchVmBootstrapConfigAggregate(address, options) {
|
|
|
2689
2759
|
if (!aggregate || typeof aggregate !== "object" || Array.isArray(aggregate)) return {};
|
|
2690
2760
|
return aggregate;
|
|
2691
2761
|
}
|
|
2692
|
-
function normalizeVmBootstrapConfigAggregate(aggregate) {
|
|
2762
|
+
function normalizeVmBootstrapConfigAggregate(aggregate, options = {}) {
|
|
2693
2763
|
if (!aggregate || typeof aggregate !== "object") return {};
|
|
2694
2764
|
return Object.fromEntries(
|
|
2695
|
-
Object.entries(aggregate).
|
|
2696
|
-
|
|
2697
|
-
|
|
2765
|
+
Object.entries(aggregate).map(([deploymentToken, record]) => [
|
|
2766
|
+
deploymentToken,
|
|
2767
|
+
normalizeVmBootstrapConfigRecord(deploymentToken, record, options)
|
|
2768
|
+
]).filter((entry) => entry[1] != null)
|
|
2698
2769
|
);
|
|
2699
2770
|
}
|
|
2700
2771
|
function createVmBootstrapConfigAggregateContent(args) {
|
|
2701
|
-
const
|
|
2772
|
+
const nowMs = Math.max(0, Number(args.now ?? Date.now() / 1e3) * 1e3);
|
|
2773
|
+
const content = normalizeVmBootstrapConfigAggregate(args.existingAggregate, {
|
|
2774
|
+
nowMs,
|
|
2775
|
+
maxRecordAgeMs: args.maxRecordAgeMs
|
|
2776
|
+
});
|
|
2702
2777
|
content[args.record.deploymentToken] = args.record;
|
|
2703
2778
|
return {
|
|
2704
2779
|
address: args.sender,
|
|
@@ -2743,6 +2818,55 @@ async function publishVmBootstrapConfigAggregate(args) {
|
|
|
2743
2818
|
aggregateStatus
|
|
2744
2819
|
};
|
|
2745
2820
|
}
|
|
2821
|
+
function normalizeAggregateMessageHashRecord(value) {
|
|
2822
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2823
|
+
const candidate = value;
|
|
2824
|
+
const itemHash = typeof candidate.item_hash === "string" ? candidate.item_hash.trim() : "";
|
|
2825
|
+
if (!itemHash) return null;
|
|
2826
|
+
const content = candidate.content && typeof candidate.content === "object" && !Array.isArray(candidate.content) ? candidate.content : typeof candidate.item_content === "string" ? parseJsonObject(candidate.item_content) : null;
|
|
2827
|
+
if (!content || content.key !== VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY) return null;
|
|
2828
|
+
const timeMs = parseTimestampMs(candidate.time);
|
|
2829
|
+
if (timeMs == null) return null;
|
|
2830
|
+
return { itemHash, timeMs };
|
|
2831
|
+
}
|
|
2832
|
+
async function listStaleVmBootstrapConfigAggregateMessageHashes(args) {
|
|
2833
|
+
const address = args.address.trim();
|
|
2834
|
+
if (!address) return [];
|
|
2835
|
+
const currentAggregateItemHash = args.currentAggregateItemHash?.trim() ?? "";
|
|
2836
|
+
const olderThanMs = Math.max(
|
|
2837
|
+
6e4,
|
|
2838
|
+
Number(args.olderThanMs ?? VM_BOOTSTRAP_CONFIG_HISTORY_RETENTION_MS)
|
|
2839
|
+
);
|
|
2840
|
+
const cutoffMs = Math.max(0, Number(args.nowMs ?? Date.now()) - olderThanMs);
|
|
2841
|
+
const pagination = Math.max(1, Number(args.pagination ?? 100));
|
|
2842
|
+
const maxPages = Math.max(1, Number(args.maxPages ?? 10));
|
|
2843
|
+
const hashes = /* @__PURE__ */ new Set();
|
|
2844
|
+
for (let page = 1; page <= maxPages; page += 1) {
|
|
2845
|
+
const requestUrl = new URL("/api/v0/messages.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2846
|
+
requestUrl.searchParams.set("msgType", "AGGREGATE");
|
|
2847
|
+
requestUrl.searchParams.set("addresses", address);
|
|
2848
|
+
requestUrl.searchParams.set("message_statuses", "processed,pending,rejected,removing");
|
|
2849
|
+
requestUrl.searchParams.set("pagination", String(pagination));
|
|
2850
|
+
requestUrl.searchParams.set("page", String(page));
|
|
2851
|
+
requestUrl.searchParams.set("sortOrder", "-1");
|
|
2852
|
+
const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
|
|
2853
|
+
if (!response.ok) {
|
|
2854
|
+
throw new Error(`VM bootstrap config aggregate history request failed: ${response.status}`);
|
|
2855
|
+
}
|
|
2856
|
+
const payload = await response.json();
|
|
2857
|
+
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
2858
|
+
if (messages.length === 0) break;
|
|
2859
|
+
for (const message of messages) {
|
|
2860
|
+
const normalized = normalizeAggregateMessageHashRecord(message);
|
|
2861
|
+
if (!normalized) continue;
|
|
2862
|
+
if (normalized.itemHash === currentAggregateItemHash) continue;
|
|
2863
|
+
if (normalized.timeMs > cutoffMs) continue;
|
|
2864
|
+
hashes.add(normalized.itemHash);
|
|
2865
|
+
}
|
|
2866
|
+
if (messages.length < pagination) break;
|
|
2867
|
+
}
|
|
2868
|
+
return [...hashes];
|
|
2869
|
+
}
|
|
2746
2870
|
async function publishVmBootstrapConfig(args) {
|
|
2747
2871
|
const aggregate = await fetchVmBootstrapConfigAggregate(args.sender, {
|
|
2748
2872
|
apiHost: args.apiHost,
|
|
@@ -2751,7 +2875,8 @@ async function publishVmBootstrapConfig(args) {
|
|
|
2751
2875
|
const content = createVmBootstrapConfigAggregateContent({
|
|
2752
2876
|
sender: args.sender,
|
|
2753
2877
|
record: args.record,
|
|
2754
|
-
existingAggregate: aggregate
|
|
2878
|
+
existingAggregate: aggregate,
|
|
2879
|
+
maxRecordAgeMs: VM_BOOTSTRAP_CONFIG_HISTORY_RETENTION_MS
|
|
2755
2880
|
});
|
|
2756
2881
|
const result = await publishVmBootstrapConfigAggregate({
|
|
2757
2882
|
sender: args.sender,
|
|
@@ -2773,7 +2898,9 @@ async function deleteVmBootstrapConfig(args) {
|
|
|
2773
2898
|
apiHost: args.apiHost,
|
|
2774
2899
|
fetch: args.fetch
|
|
2775
2900
|
});
|
|
2776
|
-
const content = normalizeVmBootstrapConfigAggregate(aggregate
|
|
2901
|
+
const content = normalizeVmBootstrapConfigAggregate(aggregate, {
|
|
2902
|
+
maxRecordAgeMs: VM_BOOTSTRAP_CONFIG_HISTORY_RETENTION_MS
|
|
2903
|
+
});
|
|
2777
2904
|
delete content[args.deploymentToken];
|
|
2778
2905
|
const publishResult = await publishVmBootstrapConfigAggregate({
|
|
2779
2906
|
sender: args.sender,
|
|
@@ -2795,6 +2922,89 @@ async function deleteVmBootstrapConfig(args) {
|
|
|
2795
2922
|
aggregate: content
|
|
2796
2923
|
};
|
|
2797
2924
|
}
|
|
2925
|
+
function normalizeVmBootstrapConfigSignalRecord(value) {
|
|
2926
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
2927
|
+
const candidate = value;
|
|
2928
|
+
const deploymentToken = typeof candidate.deploymentToken === "string" ? candidate.deploymentToken.trim() : "";
|
|
2929
|
+
const status = typeof candidate.status === "string" ? candidate.status.trim() : "";
|
|
2930
|
+
const profile = typeof candidate.profile === "string" ? candidate.profile.trim() : "";
|
|
2931
|
+
const ownerAddress = typeof candidate.ownerAddress === "string" ? candidate.ownerAddress.trim() : "";
|
|
2932
|
+
const instanceItemHash = typeof candidate.instanceItemHash === "string" ? candidate.instanceItemHash.trim() : "";
|
|
2933
|
+
const updatedAt = typeof candidate.updatedAt === "string" ? candidate.updatedAt.trim() : "";
|
|
2934
|
+
if (!deploymentToken || !status || !profile || !ownerAddress || !instanceItemHash || !updatedAt) {
|
|
2935
|
+
return null;
|
|
2936
|
+
}
|
|
2937
|
+
return {
|
|
2938
|
+
deploymentToken,
|
|
2939
|
+
status,
|
|
2940
|
+
profile,
|
|
2941
|
+
ownerAddress,
|
|
2942
|
+
instanceItemHash,
|
|
2943
|
+
updatedAt,
|
|
2944
|
+
publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
|
|
2945
|
+
authorization: candidate.authorization,
|
|
2946
|
+
peerId: typeof candidate.peerId === "string" && candidate.peerId.trim() ? candidate.peerId.trim() : null,
|
|
2947
|
+
probeMultiaddrs: Array.isArray(candidate.probeMultiaddrs) ? candidate.probeMultiaddrs.filter(
|
|
2948
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2949
|
+
) : [],
|
|
2950
|
+
browserBootstrapMultiaddrs: Array.isArray(candidate.browserBootstrapMultiaddrs) ? candidate.browserBootstrapMultiaddrs.filter(
|
|
2951
|
+
(entry) => typeof entry === "string" && entry.trim().length > 0
|
|
2952
|
+
) : []
|
|
2953
|
+
};
|
|
2954
|
+
}
|
|
2955
|
+
async function fetchVmBootstrapConfigSignals(args) {
|
|
2956
|
+
const requestUrl = new URL("/api/v0/posts.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
|
|
2957
|
+
requestUrl.searchParams.set("refs", args.ref ?? VM_BOOTSTRAP_CONFIG_SIGNAL_REF);
|
|
2958
|
+
requestUrl.searchParams.set("types", args.postType ?? VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE);
|
|
2959
|
+
requestUrl.searchParams.set("pagination", String(Math.max(1, Number(args.pagination ?? 100))));
|
|
2960
|
+
requestUrl.searchParams.set("page", String(Math.max(1, Number(args.page ?? 1))));
|
|
2961
|
+
requestUrl.searchParams.set("sortOrder", "-1");
|
|
2962
|
+
const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
|
|
2963
|
+
if (!response.ok) {
|
|
2964
|
+
throw new Error(`VM bootstrap config signal request failed: ${response.status}`);
|
|
2965
|
+
}
|
|
2966
|
+
const payload = await response.json();
|
|
2967
|
+
const expectedDeploymentToken = args.deploymentToken?.trim() ?? "";
|
|
2968
|
+
const expectedOwnerAddress = args.ownerAddress?.trim().toLowerCase() ?? "";
|
|
2969
|
+
const expectedInstanceItemHash = args.instanceItemHash?.trim() ?? "";
|
|
2970
|
+
return (payload.posts ?? []).map((entry) => {
|
|
2971
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
2972
|
+
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;
|
|
2973
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
2974
|
+
const content = "deploymentToken" in parsed ? parsed : "content" in parsed && parsed.content && typeof parsed.content === "object" && !Array.isArray(parsed.content) ? parsed.content : null;
|
|
2975
|
+
if (!content || typeof content !== "object" || Array.isArray(content)) return null;
|
|
2976
|
+
return normalizeVmBootstrapConfigSignalRecord(content);
|
|
2977
|
+
}).filter((entry) => {
|
|
2978
|
+
if (!entry) return false;
|
|
2979
|
+
if (expectedDeploymentToken && entry.deploymentToken !== expectedDeploymentToken) return false;
|
|
2980
|
+
if (expectedOwnerAddress && entry.ownerAddress.toLowerCase() !== expectedOwnerAddress) return false;
|
|
2981
|
+
if (expectedInstanceItemHash && entry.instanceItemHash !== expectedInstanceItemHash) return false;
|
|
2982
|
+
return true;
|
|
2983
|
+
});
|
|
2984
|
+
}
|
|
2985
|
+
async function waitForVmBootstrapConfigSignal(args) {
|
|
2986
|
+
const attempts = Math.max(1, Number(args.attempts ?? 24));
|
|
2987
|
+
const delayMs = Math.max(0, Number(args.delayMs ?? 2500));
|
|
2988
|
+
const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
2989
|
+
const expectedStatus = args.expectedStatus ?? "applied";
|
|
2990
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2991
|
+
const signals = await fetchVmBootstrapConfigSignals({
|
|
2992
|
+
deploymentToken: args.deploymentToken,
|
|
2993
|
+
ownerAddress: args.ownerAddress,
|
|
2994
|
+
instanceItemHash: args.instanceItemHash,
|
|
2995
|
+
fetch: args.fetch,
|
|
2996
|
+
apiHost: args.apiHost,
|
|
2997
|
+
ref: args.ref,
|
|
2998
|
+
postType: args.postType
|
|
2999
|
+
}).catch(() => []);
|
|
3000
|
+
const match = signals.find((entry) => entry.status === expectedStatus) ?? null;
|
|
3001
|
+
if (match) return match;
|
|
3002
|
+
if (attempt < attempts - 1) {
|
|
3003
|
+
await sleep2(delayMs);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
return null;
|
|
3007
|
+
}
|
|
2798
3008
|
|
|
2799
3009
|
// ../core/src/bootstrap-reconcile.ts
|
|
2800
3010
|
function createRelayBootstrapRegistrationId(profile, instanceName, itemHash) {
|
|
@@ -3054,6 +3264,22 @@ function relayBootstrapInstanceItemHash(entry) {
|
|
|
3054
3264
|
}
|
|
3055
3265
|
return registrationIdInstanceItemHash(entry?.content?.registrationId);
|
|
3056
3266
|
}
|
|
3267
|
+
function relayBootstrapMatchesCurrentWallet(entry, normalizedWalletAddress, activeInstanceHashes) {
|
|
3268
|
+
const instanceItemHash = relayBootstrapInstanceItemHash(entry);
|
|
3269
|
+
if (instanceItemHash && activeInstanceHashes.has(instanceItemHash)) {
|
|
3270
|
+
return true;
|
|
3271
|
+
}
|
|
3272
|
+
const candidateAddresses = [
|
|
3273
|
+
entry?.address,
|
|
3274
|
+
entry?.content?.ownerAddress,
|
|
3275
|
+
entry?.content?.authorization?.payload?.ownerAddress,
|
|
3276
|
+
entry?.content?.publisherAddress,
|
|
3277
|
+
entry?.content?.authorization?.payload?.publisherAddress
|
|
3278
|
+
];
|
|
3279
|
+
return candidateAddresses.some(
|
|
3280
|
+
(value) => typeof value === "string" && value.trim().toLowerCase() === normalizedWalletAddress
|
|
3281
|
+
);
|
|
3282
|
+
}
|
|
3057
3283
|
async function inspectInstanceRuntime(args) {
|
|
3058
3284
|
const details = {
|
|
3059
3285
|
messageStatus: String(
|
|
@@ -3280,11 +3506,13 @@ var SponsorRelayController = class {
|
|
|
3280
3506
|
if (!args.runtime.hostIpv4) {
|
|
3281
3507
|
throw new Error("Relay runtime did not expose a host IPv4 address.");
|
|
3282
3508
|
}
|
|
3283
|
-
|
|
3509
|
+
let runtime = args.runtime;
|
|
3510
|
+
let mappedPorts2 = runtime.mappedPorts ?? {};
|
|
3284
3511
|
const setupPort = mappedPorts2["80"]?.host ?? null;
|
|
3285
3512
|
if (!setupPort) {
|
|
3286
3513
|
throw new Error("Relay runtime did not expose the temporary setup endpoint.");
|
|
3287
3514
|
}
|
|
3515
|
+
let bootstrapSignalRelayMetadata = null;
|
|
3288
3516
|
if (profile === "orbitdb-relay-pinner") {
|
|
3289
3517
|
this.emitProgress({
|
|
3290
3518
|
stage: "deployment-confirmed",
|
|
@@ -3353,6 +3581,57 @@ var SponsorRelayController = class {
|
|
|
3353
3581
|
if (!args.deploymentToken) {
|
|
3354
3582
|
throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
|
|
3355
3583
|
}
|
|
3584
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3585
|
+
this.emitProgress({
|
|
3586
|
+
stage: "deployment-confirmed",
|
|
3587
|
+
label: "Waiting for HTTPS route",
|
|
3588
|
+
progress: 93,
|
|
3589
|
+
status: "warning",
|
|
3590
|
+
itemHash: args.itemHash,
|
|
3591
|
+
detail: "The relay received a 2n6 hostname, but it is not active yet. Waiting before handing HTTPS config to the guest."
|
|
3592
|
+
});
|
|
3593
|
+
let latestRuntime = runtime;
|
|
3594
|
+
for (let attempt = 0; attempt < UI_RUNTIME_WAIT_ATTEMPTS; attempt += 1) {
|
|
3595
|
+
if (latestRuntime.webAccess?.active === true) {
|
|
3596
|
+
break;
|
|
3597
|
+
}
|
|
3598
|
+
await new Promise(
|
|
3599
|
+
(resolve) => globalThis.setTimeout(resolve, UI_RUNTIME_WAIT_DELAY_MS)
|
|
3600
|
+
);
|
|
3601
|
+
latestRuntime = await fetchVmRuntime({
|
|
3602
|
+
itemHash: args.itemHash,
|
|
3603
|
+
fetch: (url, init) => fetch(url, init),
|
|
3604
|
+
crnHash: runtime.selectedCrn?.hash ?? runtime.allocation?.crnHash ?? void 0,
|
|
3605
|
+
crns: this.state.crns,
|
|
3606
|
+
crnListUrl: this.props.crnListUrl,
|
|
3607
|
+
requirePublicGuestIpv6ForProxy: true
|
|
3608
|
+
}).catch(() => latestRuntime);
|
|
3609
|
+
this.emitProgress({
|
|
3610
|
+
stage: "deployment-confirmed",
|
|
3611
|
+
label: "Waiting for HTTPS route",
|
|
3612
|
+
progress: 93,
|
|
3613
|
+
status: latestRuntime.webAccess?.active === true ? "info" : "warning",
|
|
3614
|
+
itemHash: args.itemHash,
|
|
3615
|
+
detail: `2n6 activation ${attempt + 1}/${UI_RUNTIME_WAIT_ATTEMPTS}: ${latestRuntime.webAccess?.active === true ? "active" : "still pending"} for ${latestRuntime.proxyUrl ?? runtime.proxyUrl ?? "reserved proxy URL"}.`
|
|
3616
|
+
});
|
|
3617
|
+
this.trace("deploy:bootstrap-proxy-activation", {
|
|
3618
|
+
itemHash: args.itemHash,
|
|
3619
|
+
attempt: attempt + 1,
|
|
3620
|
+
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
3621
|
+
active: latestRuntime.webAccess?.active === true,
|
|
3622
|
+
proxyUrl: latestRuntime.proxyUrl ?? null
|
|
3623
|
+
});
|
|
3624
|
+
}
|
|
3625
|
+
runtime = latestRuntime;
|
|
3626
|
+
mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
|
|
3627
|
+
}
|
|
3628
|
+
if (!runtime.hostIpv4) {
|
|
3629
|
+
throw new Error(
|
|
3630
|
+
"Relay runtime lost its host IPv4 address while waiting for HTTPS activation."
|
|
3631
|
+
);
|
|
3632
|
+
}
|
|
3633
|
+
const runtimeHostIpv4 = runtime.hostIpv4;
|
|
3634
|
+
const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
|
|
3356
3635
|
this.emitProgress({
|
|
3357
3636
|
stage: "deployment-confirmed",
|
|
3358
3637
|
label: "Publishing relay config",
|
|
@@ -3370,9 +3649,9 @@ var SponsorRelayController = class {
|
|
|
3370
3649
|
expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
|
|
3371
3650
|
status: "pending",
|
|
3372
3651
|
runtime: {
|
|
3373
|
-
publicIpv4:
|
|
3374
|
-
publicIpv6:
|
|
3375
|
-
proxyUrl:
|
|
3652
|
+
publicIpv4: runtimeHostIpv4,
|
|
3653
|
+
publicIpv6: runtime.ipv6 ?? null,
|
|
3654
|
+
proxyUrl: activeProxyUrl,
|
|
3376
3655
|
mappedPorts: mappedPorts2
|
|
3377
3656
|
},
|
|
3378
3657
|
bootstrap: {
|
|
@@ -3401,54 +3680,120 @@ var SponsorRelayController = class {
|
|
|
3401
3680
|
aggregateItemHash: bootstrapConfigPublication.aggregateItemHash,
|
|
3402
3681
|
aggregateStatus: bootstrapConfigPublication.aggregateStatus
|
|
3403
3682
|
});
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3683
|
+
this.emitProgress({
|
|
3684
|
+
stage: "deployment-confirmed",
|
|
3685
|
+
label: "Waiting for guest config",
|
|
3686
|
+
progress: 94,
|
|
3687
|
+
status: "info",
|
|
3688
|
+
itemHash: args.itemHash,
|
|
3689
|
+
detail: "Waiting for the relay VM to acknowledge that it consumed the Aleph bootstrap config."
|
|
3690
|
+
});
|
|
3691
|
+
const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
|
|
3692
|
+
deploymentToken: args.deploymentToken,
|
|
3693
|
+
ownerAddress: this.state.wallet.address,
|
|
3694
|
+
instanceItemHash: args.itemHash,
|
|
3695
|
+
fetch: asJsonFetch,
|
|
3696
|
+
apiHost: this.client.apiHost,
|
|
3697
|
+
attempts: 40,
|
|
3698
|
+
delayMs: 3e3
|
|
3699
|
+
});
|
|
3700
|
+
this.trace("deploy:bootstrap-config-signal-visible", {
|
|
3701
|
+
itemHash: args.itemHash,
|
|
3702
|
+
deploymentToken: args.deploymentToken,
|
|
3703
|
+
bootstrapConfigSignal
|
|
3704
|
+
});
|
|
3705
|
+
if (!bootstrapConfigSignal) {
|
|
3706
|
+
throw new Error(
|
|
3707
|
+
"The relay VM did not confirm that it applied the Aleph bootstrap config in time."
|
|
3708
|
+
);
|
|
3709
|
+
}
|
|
3710
|
+
bootstrapSignalRelayMetadata = typeof bootstrapConfigSignal.peerId === "string" && bootstrapConfigSignal.peerId.length > 0 && Array.isArray(bootstrapConfigSignal.probeMultiaddrs) && bootstrapConfigSignal.probeMultiaddrs.length > 0 ? {
|
|
3711
|
+
peerId: bootstrapConfigSignal.peerId,
|
|
3712
|
+
probeMultiaddrs: bootstrapConfigSignal.probeMultiaddrs,
|
|
3713
|
+
browserBootstrapMultiaddrs: bootstrapConfigSignal.browserBootstrapMultiaddrs ?? []
|
|
3714
|
+
} : null;
|
|
3715
|
+
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
3716
|
+
this.trace("deploy:bootstrap-proxy-fallback", {
|
|
3424
3717
|
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
|
|
3718
|
+
deploymentToken: args.deploymentToken,
|
|
3719
|
+
proxyUrl: runtime.proxyUrl,
|
|
3720
|
+
bootstrapSignalRelayMetadata
|
|
3441
3721
|
});
|
|
3442
3722
|
this.emitProgress({
|
|
3443
|
-
stage: "
|
|
3444
|
-
label: "
|
|
3445
|
-
progress:
|
|
3446
|
-
status:
|
|
3723
|
+
stage: "deployment-confirmed",
|
|
3724
|
+
label: "Continuing without HTTPS route",
|
|
3725
|
+
progress: 94,
|
|
3726
|
+
status: "warning",
|
|
3447
3727
|
itemHash: args.itemHash,
|
|
3448
|
-
detail:
|
|
3728
|
+
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
3729
|
});
|
|
3730
|
+
runtime = {
|
|
3731
|
+
...runtime,
|
|
3732
|
+
proxyUrl: null,
|
|
3733
|
+
webAccess: runtime.webAccess ? {
|
|
3734
|
+
...runtime.webAccess,
|
|
3735
|
+
active: false
|
|
3736
|
+
} : runtime.webAccess
|
|
3737
|
+
};
|
|
3450
3738
|
}
|
|
3451
|
-
}
|
|
3739
|
+
}
|
|
3740
|
+
let relayMetadata = bootstrapSignalRelayMetadata;
|
|
3741
|
+
if (relayMetadata) {
|
|
3742
|
+
this.trace("deploy:relay-metadata-from-bootstrap-signal", {
|
|
3743
|
+
itemHash: args.itemHash,
|
|
3744
|
+
peerId: relayMetadata.peerId,
|
|
3745
|
+
probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
|
|
3746
|
+
browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
|
|
3747
|
+
});
|
|
3748
|
+
} else {
|
|
3749
|
+
this.emitProgress({
|
|
3750
|
+
stage: "publishing-bootstrap",
|
|
3751
|
+
label: "Waiting for secure relay metadata",
|
|
3752
|
+
progress: 95,
|
|
3753
|
+
status: "warning",
|
|
3754
|
+
itemHash: args.itemHash,
|
|
3755
|
+
detail: "Runtime networking is available. Waiting for the secure relay metadata endpoint to report final public multiaddrs."
|
|
3756
|
+
});
|
|
3757
|
+
relayMetadata = await fetchRelayMetadataForRuntime({
|
|
3758
|
+
runtime,
|
|
3759
|
+
fetch: (url, init) => fetch(url, init),
|
|
3760
|
+
preferSecureMetadata: profile === "uc-go-peer" && Boolean(runtime.proxyUrl),
|
|
3761
|
+
attempts: 80,
|
|
3762
|
+
delayMs: 3e3,
|
|
3763
|
+
timeoutMs: 24e4,
|
|
3764
|
+
onAttempt: (result) => {
|
|
3765
|
+
const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
|
|
3766
|
+
const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
|
|
3767
|
+
this.trace("deploy:relay-metadata-attempt", {
|
|
3768
|
+
itemHash: args.itemHash,
|
|
3769
|
+
attempt: result.attempt,
|
|
3770
|
+
attempts: result.attempts,
|
|
3771
|
+
requestUrl: result.requestUrl,
|
|
3772
|
+
metadataUrl: result.metadataUrl,
|
|
3773
|
+
hostIpv4: result.hostIpv4,
|
|
3774
|
+
setupPort: result.setupPort,
|
|
3775
|
+
ok: result.ok,
|
|
3776
|
+
status: result.status,
|
|
3777
|
+
ready: result.ready,
|
|
3778
|
+
error: result.error ?? null,
|
|
3779
|
+
payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
|
|
3780
|
+
peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
|
|
3781
|
+
probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
|
|
3782
|
+
browserBootstrapMultiaddrCount: Array.isArray(
|
|
3783
|
+
metadata?.browser_bootstrap_multiaddrs
|
|
3784
|
+
) ? metadata.browser_bootstrap_multiaddrs.length : 0
|
|
3785
|
+
});
|
|
3786
|
+
this.emitProgress({
|
|
3787
|
+
stage: "publishing-bootstrap",
|
|
3788
|
+
label: result.ready ? "Secure relay metadata ready" : "Waiting for secure relay metadata",
|
|
3789
|
+
progress: 95,
|
|
3790
|
+
status: result.ready ? "success" : "warning",
|
|
3791
|
+
itemHash: args.itemHash,
|
|
3792
|
+
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}.`
|
|
3793
|
+
});
|
|
3794
|
+
}
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3452
3797
|
if (!relayMetadata) {
|
|
3453
3798
|
throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
|
|
3454
3799
|
}
|
|
@@ -3558,6 +3903,7 @@ var SponsorRelayController = class {
|
|
|
3558
3903
|
}
|
|
3559
3904
|
}
|
|
3560
3905
|
if (profile === "uc-go-peer" && args.deploymentToken) {
|
|
3906
|
+
let cleanupAggregateItemHash = null;
|
|
3561
3907
|
await deleteVmBootstrapConfig({
|
|
3562
3908
|
sender: this.state.wallet.address,
|
|
3563
3909
|
deploymentToken: args.deploymentToken,
|
|
@@ -3566,6 +3912,14 @@ var SponsorRelayController = class {
|
|
|
3566
3912
|
fetch: asJsonFetch,
|
|
3567
3913
|
apiHost: this.client.apiHost,
|
|
3568
3914
|
sync: true
|
|
3915
|
+
}).then((cleanupResult) => {
|
|
3916
|
+
cleanupAggregateItemHash = cleanupResult.aggregateItemHash;
|
|
3917
|
+
this.trace("deploy:bootstrap-config-cleanup-complete", {
|
|
3918
|
+
itemHash: args.itemHash,
|
|
3919
|
+
deploymentToken: args.deploymentToken,
|
|
3920
|
+
aggregateItemHash: cleanupResult.aggregateItemHash,
|
|
3921
|
+
aggregateStatus: cleanupResult.aggregateStatus
|
|
3922
|
+
});
|
|
3569
3923
|
}).catch((error) => {
|
|
3570
3924
|
this.trace("deploy:bootstrap-config-cleanup-error", {
|
|
3571
3925
|
itemHash: args.itemHash,
|
|
@@ -3573,7 +3927,74 @@ var SponsorRelayController = class {
|
|
|
3573
3927
|
error: error instanceof Error ? error.message : String(error)
|
|
3574
3928
|
});
|
|
3575
3929
|
});
|
|
3930
|
+
await this.cleanupStaleVmBootstrapConfigHistory({
|
|
3931
|
+
itemHash: args.itemHash,
|
|
3932
|
+
currentAggregateItemHash: cleanupAggregateItemHash,
|
|
3933
|
+
stage: "publishing-bootstrap",
|
|
3934
|
+
progress: 99
|
|
3935
|
+
});
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
async cleanupStaleVmBootstrapConfigHistory(args) {
|
|
3939
|
+
if (!this.state.wallet.address || !args.currentAggregateItemHash) {
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
const staleHashes = await listStaleVmBootstrapConfigAggregateMessageHashes({
|
|
3943
|
+
address: this.state.wallet.address,
|
|
3944
|
+
currentAggregateItemHash: args.currentAggregateItemHash,
|
|
3945
|
+
olderThanMs: VM_BOOTSTRAP_CONFIG_HISTORY_RETENTION_MS,
|
|
3946
|
+
fetch: asJsonFetch,
|
|
3947
|
+
apiHost: this.client.apiHost
|
|
3948
|
+
}).catch((error) => {
|
|
3949
|
+
this.trace("deploy:bootstrap-config-history-scan-error", {
|
|
3950
|
+
itemHash: args.itemHash,
|
|
3951
|
+
currentAggregateItemHash: args.currentAggregateItemHash,
|
|
3952
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3953
|
+
});
|
|
3954
|
+
return [];
|
|
3955
|
+
});
|
|
3956
|
+
if (staleHashes.length === 0) {
|
|
3957
|
+
return;
|
|
3576
3958
|
}
|
|
3959
|
+
const staleCount = staleHashes.length;
|
|
3960
|
+
this.emitProgress({
|
|
3961
|
+
stage: args.stage,
|
|
3962
|
+
label: "Cleaning up stale handoff history",
|
|
3963
|
+
progress: args.progress,
|
|
3964
|
+
status: "warning",
|
|
3965
|
+
itemHash: args.itemHash,
|
|
3966
|
+
detail: `Forgetting ${staleCount} superseded Aleph handoff aggregate message${staleCount === 1 ? "" : "s"} older than 6 hours. MetaMask may request another cleanup signature.`
|
|
3967
|
+
});
|
|
3968
|
+
this.trace("deploy:bootstrap-config-history-cleanup-start", {
|
|
3969
|
+
itemHash: args.itemHash,
|
|
3970
|
+
currentAggregateItemHash: args.currentAggregateItemHash,
|
|
3971
|
+
staleHashes
|
|
3972
|
+
});
|
|
3973
|
+
await forgetAlephMessages({
|
|
3974
|
+
sender: this.state.wallet.address,
|
|
3975
|
+
hashes: staleHashes,
|
|
3976
|
+
reason: "Prune stale vm-bootstrap-config aggregate history older than 6 hours",
|
|
3977
|
+
signer: personalSign,
|
|
3978
|
+
hasher: sha256Hex,
|
|
3979
|
+
fetch: asJsonFetch,
|
|
3980
|
+
apiHost: this.client.apiHost,
|
|
3981
|
+
sync: true
|
|
3982
|
+
}).then((cleanupResult) => {
|
|
3983
|
+
this.trace("deploy:bootstrap-config-history-cleanup-complete", {
|
|
3984
|
+
itemHash: args.itemHash,
|
|
3985
|
+
currentAggregateItemHash: args.currentAggregateItemHash,
|
|
3986
|
+
staleHashes,
|
|
3987
|
+
forgetItemHash: cleanupResult.itemHash,
|
|
3988
|
+
forgetStatus: cleanupResult.status
|
|
3989
|
+
});
|
|
3990
|
+
}).catch((error) => {
|
|
3991
|
+
this.trace("deploy:bootstrap-config-history-cleanup-error", {
|
|
3992
|
+
itemHash: args.itemHash,
|
|
3993
|
+
currentAggregateItemHash: args.currentAggregateItemHash,
|
|
3994
|
+
staleHashes,
|
|
3995
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3996
|
+
});
|
|
3997
|
+
});
|
|
3577
3998
|
}
|
|
3578
3999
|
syncLatestDeploymentProgress(instances) {
|
|
3579
4000
|
const itemHash = this.state.lastDeploymentHash;
|
|
@@ -3589,6 +4010,15 @@ var SponsorRelayController = class {
|
|
|
3589
4010
|
const currentProgress = this.state.deploymentProgress;
|
|
3590
4011
|
const currentProgressIsForLatestHash = currentProgress.itemHash === itemHash || currentProgress.itemHash == null && currentProgress.stage !== "idle" && currentProgress.stage !== "error";
|
|
3591
4012
|
const keepTerminalSuccess = this.lastCompletedDeploymentHash === itemHash || currentProgressIsForLatestHash && currentProgress.stage === "completed" && currentProgress.status === "success";
|
|
4013
|
+
if (currentProgressIsForLatestHash && currentProgress.stage === "publishing-bootstrap") {
|
|
4014
|
+
this.trace("progress:retain-active-phase", {
|
|
4015
|
+
reason: "latest refresh would overwrite active relay metadata/bootstrap progress",
|
|
4016
|
+
itemHash,
|
|
4017
|
+
label: currentProgress.label,
|
|
4018
|
+
progress: currentProgress.progress
|
|
4019
|
+
});
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
3592
4022
|
const status = latest.details.messageStatus;
|
|
3593
4023
|
if (status === "rejected") {
|
|
3594
4024
|
this.emitProgress({
|
|
@@ -3941,7 +4371,11 @@ var SponsorRelayController = class {
|
|
|
3941
4371
|
instances.map((entry) => entry.instance.item_hash)
|
|
3942
4372
|
);
|
|
3943
4373
|
bootstrapRegistrations = selectCurrentRelayBootstrapPosts(rawBootstrapPosts).filter(
|
|
3944
|
-
(entry) =>
|
|
4374
|
+
(entry) => relayBootstrapMatchesCurrentWallet(
|
|
4375
|
+
entry,
|
|
4376
|
+
normalizedWalletAddress,
|
|
4377
|
+
activeInstanceHashes
|
|
4378
|
+
)
|
|
3945
4379
|
).map((entry) => {
|
|
3946
4380
|
const instanceItemHash = relayBootstrapInstanceItemHash(entry);
|
|
3947
4381
|
const messageHash = entry.itemHash ?? entry.hash ?? null;
|
|
@@ -3960,6 +4394,8 @@ var SponsorRelayController = class {
|
|
|
3960
4394
|
registrationId: entry.content.registrationId,
|
|
3961
4395
|
multiaddrs: Array.isArray(entry.content.multiaddrs) ? entry.content.multiaddrs : [],
|
|
3962
4396
|
browserMultiaddrs: Array.isArray(entry.content.browserMultiaddrs) ? entry.content.browserMultiaddrs : [],
|
|
4397
|
+
ownerAddress: typeof entry.content.ownerAddress === "string" ? entry.content.ownerAddress : void 0,
|
|
4398
|
+
publisherAddress: typeof entry.content.publisherAddress === "string" ? entry.content.publisherAddress : void 0,
|
|
3963
4399
|
updatedAt: entry.content.updatedAt
|
|
3964
4400
|
} : null
|
|
3965
4401
|
};
|
|
@@ -4228,10 +4664,11 @@ var SponsorRelayController = class {
|
|
|
4228
4664
|
crnHash: candidateCrn.hash,
|
|
4229
4665
|
crns: this.state.crns,
|
|
4230
4666
|
crnListUrl: this.props.crnListUrl,
|
|
4667
|
+
requirePublicGuestIpv6ForProxy: true,
|
|
4231
4668
|
attempts: UI_RUNTIME_WAIT_ATTEMPTS,
|
|
4232
4669
|
delayMs: UI_RUNTIME_WAIT_DELAY_MS,
|
|
4233
4670
|
onAttempt: (runtime2, attempt, attempts) => {
|
|
4234
|
-
if (runtime2.
|
|
4671
|
+
if (runtime2.diagnostics?.state === "ready") {
|
|
4235
4672
|
this.emitProgress({
|
|
4236
4673
|
stage: "deployment-confirmed",
|
|
4237
4674
|
label: "Runtime ready",
|
|
@@ -4242,6 +4679,17 @@ var SponsorRelayController = class {
|
|
|
4242
4679
|
});
|
|
4243
4680
|
return;
|
|
4244
4681
|
}
|
|
4682
|
+
if (runtime2.diagnostics?.state === "execution-invalid-public-ipv6") {
|
|
4683
|
+
this.emitProgress({
|
|
4684
|
+
stage: "deployment-confirmed",
|
|
4685
|
+
label: "Rejecting unusable runtime",
|
|
4686
|
+
progress: 91,
|
|
4687
|
+
status: "warning",
|
|
4688
|
+
itemHash: result.itemHash,
|
|
4689
|
+
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.`
|
|
4690
|
+
});
|
|
4691
|
+
return;
|
|
4692
|
+
}
|
|
4245
4693
|
this.emitProgress({
|
|
4246
4694
|
stage: "deployment-confirmed",
|
|
4247
4695
|
label: "Waiting for runtime",
|
|
@@ -4252,7 +4700,7 @@ var SponsorRelayController = class {
|
|
|
4252
4700
|
});
|
|
4253
4701
|
}
|
|
4254
4702
|
});
|
|
4255
|
-
if (
|
|
4703
|
+
if (runtime.diagnostics?.state !== "ready") {
|
|
4256
4704
|
throw new Error(
|
|
4257
4705
|
runtime.diagnostics?.reason ?? "Deployment was processed, but runtime networking never exposed mapped ports."
|
|
4258
4706
|
);
|
|
@@ -4301,6 +4749,7 @@ var SponsorRelayController = class {
|
|
|
4301
4749
|
} catch (error) {
|
|
4302
4750
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
4303
4751
|
const attemptLabel = crnDisplayName;
|
|
4752
|
+
const failedDueToInvalidIpv6 = /not globally routable|public guest IPv6/i.test(lastError.message);
|
|
4304
4753
|
attemptErrors.push(`${attemptLabel}: ${lastError.message}`);
|
|
4305
4754
|
this.trace("deploy:crn-attempt-error", {
|
|
4306
4755
|
crnHash: candidateCrn.hash,
|
|
@@ -4309,7 +4758,24 @@ var SponsorRelayController = class {
|
|
|
4309
4758
|
error: lastError.message
|
|
4310
4759
|
});
|
|
4311
4760
|
if (attemptItemHash) {
|
|
4761
|
+
this.emitProgress({
|
|
4762
|
+
stage: "deployment-confirmed",
|
|
4763
|
+
label: failedDueToInvalidIpv6 ? "Rejecting unusable runtime" : "Cleaning up failed attempt",
|
|
4764
|
+
progress: failedDueToInvalidIpv6 ? 91 : 16,
|
|
4765
|
+
status: "warning",
|
|
4766
|
+
itemHash: attemptItemHash,
|
|
4767
|
+
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.`
|
|
4768
|
+
});
|
|
4312
4769
|
if (attemptDeploymentToken) {
|
|
4770
|
+
let cleanupAggregateItemHash = null;
|
|
4771
|
+
this.emitProgress({
|
|
4772
|
+
stage: "deployment-confirmed",
|
|
4773
|
+
label: "Cleaning up failed attempt",
|
|
4774
|
+
progress: 17,
|
|
4775
|
+
status: "warning",
|
|
4776
|
+
itemHash: attemptItemHash,
|
|
4777
|
+
detail: "Removing bootstrap configuration for the failed attempt. MetaMask may request a cleanup signature."
|
|
4778
|
+
});
|
|
4313
4779
|
this.trace("deploy:bootstrap-config-cleanup-start", {
|
|
4314
4780
|
itemHash: attemptItemHash,
|
|
4315
4781
|
deploymentToken: attemptDeploymentToken
|
|
@@ -4323,6 +4789,7 @@ var SponsorRelayController = class {
|
|
|
4323
4789
|
apiHost: this.client.apiHost,
|
|
4324
4790
|
sync: true
|
|
4325
4791
|
}).then((cleanupResult) => {
|
|
4792
|
+
cleanupAggregateItemHash = cleanupResult.aggregateItemHash;
|
|
4326
4793
|
this.trace("deploy:bootstrap-config-cleanup-complete", {
|
|
4327
4794
|
itemHash: attemptItemHash,
|
|
4328
4795
|
deploymentToken: attemptDeploymentToken,
|
|
@@ -4336,7 +4803,21 @@ var SponsorRelayController = class {
|
|
|
4336
4803
|
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
4337
4804
|
});
|
|
4338
4805
|
});
|
|
4806
|
+
await this.cleanupStaleVmBootstrapConfigHistory({
|
|
4807
|
+
itemHash: attemptItemHash,
|
|
4808
|
+
currentAggregateItemHash: cleanupAggregateItemHash,
|
|
4809
|
+
stage: "deployment-confirmed",
|
|
4810
|
+
progress: 18
|
|
4811
|
+
});
|
|
4339
4812
|
}
|
|
4813
|
+
this.emitProgress({
|
|
4814
|
+
stage: "deployment-confirmed",
|
|
4815
|
+
label: "Cleaning up failed attempt",
|
|
4816
|
+
progress: 18,
|
|
4817
|
+
status: "warning",
|
|
4818
|
+
itemHash: attemptItemHash,
|
|
4819
|
+
detail: "Forgetting the failed deployment attempt on Aleph. MetaMask may request another cleanup signature."
|
|
4820
|
+
});
|
|
4340
4821
|
this.trace("deploy:forget-failed-attempt-start", {
|
|
4341
4822
|
itemHash: attemptItemHash,
|
|
4342
4823
|
crnHash: candidateCrn.hash,
|
|
@@ -4369,11 +4850,11 @@ var SponsorRelayController = class {
|
|
|
4369
4850
|
if (candidateIndex < orderedCrns.length - 1) {
|
|
4370
4851
|
this.emitProgress({
|
|
4371
4852
|
stage: "selecting-crn",
|
|
4372
|
-
label:
|
|
4853
|
+
label: "Retrying on next CRN",
|
|
4373
4854
|
progress: 20,
|
|
4374
4855
|
status: "warning",
|
|
4375
4856
|
itemHash: attemptItemHash,
|
|
4376
|
-
detail: `${attemptLabel}
|
|
4857
|
+
detail: `${attemptLabel} was discarded: ${lastError.message}`,
|
|
4377
4858
|
error: lastError.message
|
|
4378
4859
|
});
|
|
4379
4860
|
continue;
|
|
@@ -4405,6 +4886,17 @@ var SponsorRelayController = class {
|
|
|
4405
4886
|
this.patch({ errorText: "Connect MetaMask before deleting instances." });
|
|
4406
4887
|
return;
|
|
4407
4888
|
}
|
|
4889
|
+
const linkedRegistrationHashes = Array.from(
|
|
4890
|
+
new Set(
|
|
4891
|
+
this.state.bootstrapRegistrations.filter((entry) => entry.instanceItemHash === instanceHash).map(
|
|
4892
|
+
(entry) => entry.messageHash?.trim() ?? entry.itemHash?.trim() ?? entry.hash?.trim() ?? null
|
|
4893
|
+
).filter((value) => Boolean(value))
|
|
4894
|
+
)
|
|
4895
|
+
);
|
|
4896
|
+
const hashesToForget = Array.from(
|
|
4897
|
+
/* @__PURE__ */ new Set([instanceHash, ...linkedRegistrationHashes])
|
|
4898
|
+
);
|
|
4899
|
+
const linkedRegistrationCount = linkedRegistrationHashes.length;
|
|
4408
4900
|
this.patch({
|
|
4409
4901
|
busy: { deletingInstanceHash: instanceHash },
|
|
4410
4902
|
errorText: null,
|
|
@@ -4417,12 +4909,12 @@ var SponsorRelayController = class {
|
|
|
4417
4909
|
progress: 6,
|
|
4418
4910
|
status: "warning",
|
|
4419
4911
|
itemHash: instanceHash,
|
|
4420
|
-
detail: "Preparing Aleph FORGET message."
|
|
4912
|
+
detail: linkedRegistrationCount > 0 ? `Preparing Aleph FORGET message for the instance and ${linkedRegistrationCount} linked bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"}.` : "Preparing Aleph FORGET message."
|
|
4421
4913
|
});
|
|
4422
4914
|
await forgetAlephMessages({
|
|
4423
4915
|
sender: this.state.wallet.address,
|
|
4424
|
-
hashes:
|
|
4425
|
-
reason: "Deleted from Sponsor Relay panel",
|
|
4916
|
+
hashes: hashesToForget,
|
|
4917
|
+
reason: linkedRegistrationCount > 0 ? `Deleted instance and ${linkedRegistrationCount} linked relay bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"} from Sponsor Relay panel` : "Deleted from Sponsor Relay panel",
|
|
4426
4918
|
signer: personalSign,
|
|
4427
4919
|
hasher: sha256Hex,
|
|
4428
4920
|
fetch: asJsonFetch,
|
|
@@ -4441,17 +4933,20 @@ var SponsorRelayController = class {
|
|
|
4441
4933
|
progress: 92,
|
|
4442
4934
|
status: "info",
|
|
4443
4935
|
itemHash: instanceHash,
|
|
4444
|
-
detail: "Reloading current deployments."
|
|
4936
|
+
detail: linkedRegistrationCount > 0 ? "Reloading current deployments and relay bootstrap registrations." : "Reloading current deployments."
|
|
4445
4937
|
});
|
|
4446
4938
|
await this.refresh();
|
|
4447
|
-
this.trace("delete:submitted", {
|
|
4939
|
+
this.trace("delete:submitted", {
|
|
4940
|
+
instanceHash,
|
|
4941
|
+
linkedRegistrationHashes
|
|
4942
|
+
});
|
|
4448
4943
|
this.emitProgress({
|
|
4449
4944
|
stage: "completed",
|
|
4450
4945
|
label: "Delete completed",
|
|
4451
4946
|
progress: 100,
|
|
4452
4947
|
status: "success",
|
|
4453
4948
|
itemHash: instanceHash,
|
|
4454
|
-
detail: "Delete request submitted and deployments refreshed.",
|
|
4949
|
+
detail: linkedRegistrationCount > 0 ? `Delete request submitted, including ${linkedRegistrationCount} linked bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"}, and state refreshed.` : "Delete request submitted and deployments refreshed.",
|
|
4455
4950
|
error: null
|
|
4456
4951
|
});
|
|
4457
4952
|
} catch (error) {
|