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