@le-space/ui 0.2.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/shared/index.js CHANGED
@@ -29,6 +29,7 @@ var IDLE_DEPLOYMENT_PROGRESS = {
29
29
  };
30
30
  var REFRESH_INTERVAL_MS = 3e4;
31
31
  var RELAY_PING_INTERVAL_MS = 2e4;
32
+ var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
32
33
  var INSTANCE_RUNTIME_READY_REFRESH_MS = 3e4;
33
34
  var INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45e3;
34
35
  var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
@@ -56,6 +57,12 @@ function formatDateTime(value) {
56
57
  if (Number.isNaN(date.getTime())) return String(value);
57
58
  return date.toLocaleString();
58
59
  }
60
+ function formatTierSpecLabel(vcpus, memoryMiB, diskMiB) {
61
+ if (vcpus == null || !Number.isFinite(vcpus) || memoryMiB == null || !Number.isFinite(memoryMiB) || diskMiB == null || !Number.isFinite(diskMiB)) {
62
+ return "-";
63
+ }
64
+ return `(${formatNumber(vcpus, 0)} vCPU \xB7 ${formatNumber(memoryMiB / 1024, 1)} GiB RAM \xB7 ${formatNumber(diskMiB / 1024, 0)} GiB disk)`;
65
+ }
59
66
  function buildSshCommand(hostIpv4, mappedPorts2) {
60
67
  if (!hostIpv4) return null;
61
68
  const sshPort = mappedPorts2.find((entry) => entry.label.startsWith("22/"))?.hostPort ?? 22;
@@ -401,6 +408,18 @@ function extractReferenceHashes(details) {
401
408
  if (!Array.isArray(errors)) return [];
402
409
  return errors.filter((value) => typeof value === "string");
403
410
  }
411
+ function extractInsufficientBalanceMessage(details) {
412
+ if (!details || typeof details !== "object" || !("errors" in details)) return null;
413
+ const errors = details.errors;
414
+ const firstError = Array.isArray(errors) && errors[0] && typeof errors[0] === "object" ? errors[0] : null;
415
+ const accountBalance = typeof firstError?.account_balance === "number" || typeof firstError?.account_balance === "string" ? Number(firstError.account_balance) : Number.NaN;
416
+ const requiredBalance = typeof firstError?.required_balance === "number" || typeof firstError?.required_balance === "string" ? Number(firstError.required_balance) : Number.NaN;
417
+ if (!Number.isFinite(accountBalance) || !Number.isFinite(requiredBalance)) {
418
+ return null;
419
+ }
420
+ const shortfall = requiredBalance - accountBalance;
421
+ return shortfall > 0 ? `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short` : `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required`;
422
+ }
404
423
  function describeRejectedDeployment(payload, references, rootfsRef) {
405
424
  const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
406
425
  const pendingReferences = references.filter((reference) => reference.status === "pending");
@@ -415,6 +434,10 @@ function describeRejectedDeployment(payload, references, rootfsRef) {
415
434
  if (missingReferences.length > 0) {
416
435
  return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
417
436
  }
437
+ const insufficientBalanceMessage = extractInsufficientBalanceMessage(payload.details);
438
+ if (insufficientBalanceMessage) {
439
+ return `Aleph rejected this deployment due to ${insufficientBalanceMessage}.`;
440
+ }
418
441
  const referencedHashes = extractReferenceHashes(payload.details);
419
442
  if (referencedHashes.length > 0) {
420
443
  return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
@@ -1027,6 +1050,20 @@ function extractReferenceHashes2(details) {
1027
1050
  if (!Array.isArray(errors)) return [];
1028
1051
  return errors.filter((value) => typeof value === "string");
1029
1052
  }
1053
+ function extractInsufficientBalanceMessage2(details) {
1054
+ if (!details || typeof details !== "object" || !("errors" in details)) {
1055
+ return null;
1056
+ }
1057
+ const errors = details.errors;
1058
+ const firstError = Array.isArray(errors) && errors[0] && typeof errors[0] === "object" ? errors[0] : null;
1059
+ const accountBalance = typeof firstError?.account_balance === "number" || typeof firstError?.account_balance === "string" ? Number(firstError.account_balance) : Number.NaN;
1060
+ const requiredBalance = typeof firstError?.required_balance === "number" || typeof firstError?.required_balance === "string" ? Number(firstError.required_balance) : Number.NaN;
1061
+ if (!Number.isFinite(accountBalance) || !Number.isFinite(requiredBalance)) {
1062
+ return null;
1063
+ }
1064
+ const shortfall = requiredBalance - accountBalance;
1065
+ return shortfall > 0 ? `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short` : `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required`;
1066
+ }
1030
1067
  function describeRejectedDeployment2(payload, references, rootfsRef) {
1031
1068
  const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
1032
1069
  const pendingReferences = references.filter((reference) => reference.status === "pending");
@@ -1041,6 +1078,10 @@ function describeRejectedDeployment2(payload, references, rootfsRef) {
1041
1078
  if (missingReferences.length > 0) {
1042
1079
  return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
1043
1080
  }
1081
+ const insufficientBalanceMessage = extractInsufficientBalanceMessage2(payload.details);
1082
+ if (insufficientBalanceMessage) {
1083
+ return `Aleph rejected this deployment due to ${insufficientBalanceMessage}.`;
1084
+ }
1044
1085
  const referencedHashes = extractReferenceHashes2(payload.details);
1045
1086
  if (referencedHashes.length > 0) {
1046
1087
  return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
@@ -1453,6 +1494,14 @@ function buildPaymentQuote(tier, pricing, balance) {
1453
1494
  label: "credits"
1454
1495
  };
1455
1496
  }
1497
+ function describeRejectedInstanceBroadcast(response) {
1498
+ const balanceMessage = extractInsufficientBalanceMessage2(response.details);
1499
+ if (balanceMessage) {
1500
+ return `Aleph rejected this deployment due to ${balanceMessage}.`;
1501
+ }
1502
+ const errorCode = typeof response.error_code === "number" ? response.error_code : null;
1503
+ return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
1504
+ }
1456
1505
  function createInstanceContent(args) {
1457
1506
  const sshKey = normalizeSshPublicKey(args.sshPublicKey);
1458
1507
  if (!sshKey) {
@@ -1569,9 +1618,9 @@ async function deployInstance(args) {
1569
1618
  label: status === "processed" ? "Deployment accepted by Aleph" : status === "rejected" ? "Deployment rejected by Aleph" : "Deployment submitted to Aleph",
1570
1619
  progress: status === "processed" ? 88 : status === "rejected" ? 100 : 78,
1571
1620
  status: status === "processed" ? "success" : status === "rejected" ? "error" : "warning",
1572
- detail: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1621
+ detail: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
1573
1622
  itemHash: message.item_hash,
1574
- error: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1623
+ error: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
1575
1624
  timestamp: Date.now()
1576
1625
  });
1577
1626
  return {
@@ -1580,7 +1629,7 @@ async function deployInstance(args) {
1580
1629
  status,
1581
1630
  message,
1582
1631
  response,
1583
- rejectionReason: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null
1632
+ rejectionReason: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null
1584
1633
  };
1585
1634
  }
1586
1635
 
@@ -1831,6 +1880,28 @@ async function waitForVmRuntime(args) {
1831
1880
  }
1832
1881
 
1833
1882
  // ../core/src/guest.ts
1883
+ async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1884
+ try {
1885
+ const controller = new AbortController();
1886
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1887
+ const response = await fetch2(url, {
1888
+ method: "GET",
1889
+ redirect: "follow",
1890
+ signal: controller.signal
1891
+ });
1892
+ clearTimeout(timeout);
1893
+ return {
1894
+ ok: response.ok,
1895
+ status: response.status,
1896
+ url: response.url
1897
+ };
1898
+ } catch (error) {
1899
+ return {
1900
+ ok: false,
1901
+ error: error instanceof Error ? error.message : String(error)
1902
+ };
1903
+ }
1904
+ }
1834
1905
  async function notifyCrnAllocation2(args) {
1835
1906
  const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1836
1907
  if (!normalizedCrnUrl) {
@@ -1911,6 +1982,621 @@ async function notifyCrnAllocationWithRetry(args) {
1911
1982
  reason: "CRN allocation notify was not confirmed."
1912
1983
  };
1913
1984
  }
1985
+ async function waitForSetupEndpoint(args) {
1986
+ const attempts = Math.max(1, Number(args.attempts ?? 15));
1987
+ const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
1988
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1989
+ const url = `http://${args.hostIpv4}:${args.setupPort}/health`;
1990
+ let result = await defaultHttpProbe(
1991
+ args.fetch,
1992
+ url,
1993
+ Number(args.httpTimeoutMs ?? 1e4)
1994
+ );
1995
+ args.onAttempt?.(result, 1, attempts);
1996
+ for (let attempt = 1; attempt < attempts; attempt += 1) {
1997
+ if (result.ok) return result;
1998
+ await sleep(delayMs);
1999
+ result = await defaultHttpProbe(
2000
+ args.fetch,
2001
+ url,
2002
+ Number(args.httpTimeoutMs ?? 1e4)
2003
+ );
2004
+ args.onAttempt?.(result, attempt + 1, attempts);
2005
+ }
2006
+ return result;
2007
+ }
2008
+ async function configureUcGoPeer(args) {
2009
+ const controller = new AbortController();
2010
+ const timeout = setTimeout(
2011
+ () => controller.abort(),
2012
+ Number(args.timeoutMs ?? 18e4)
2013
+ );
2014
+ const payload = {
2015
+ public_ipv4: args.hostIpv4,
2016
+ public_ipv6: args.publicIpv6 ?? void 0,
2017
+ proxy_url: args.proxyUrl ?? void 0,
2018
+ tcp_port: args.tcpPort ?? void 0,
2019
+ ws_port: args.wsPort ?? void 0,
2020
+ udp_port: args.udpPort ?? void 0,
2021
+ quic_port: args.quicPort ?? void 0,
2022
+ webrtc_port: args.webrtcPort ?? void 0,
2023
+ bootstrap_publisher_private_key: args.bootstrapPublisherPrivateKey ?? void 0,
2024
+ bootstrap_publisher_libp2p_identity_b64: args.bootstrapPublisherLibp2pIdentityBase64 ?? void 0,
2025
+ bootstrap_owner_private_key: args.bootstrapOwnerPrivateKey ?? void 0,
2026
+ bootstrap_owner_authorization_b64: args.bootstrapOwnerAuthorizationBase64 ?? void 0,
2027
+ bootstrap_registration_id: args.bootstrapRegistrationId ?? void 0,
2028
+ no_start: args.noStart === true ? true : void 0
2029
+ };
2030
+ try {
2031
+ const response = await args.fetch(
2032
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
2033
+ {
2034
+ method: "POST",
2035
+ headers: {
2036
+ "content-type": "application/json"
2037
+ },
2038
+ body: JSON.stringify(payload),
2039
+ signal: controller.signal
2040
+ }
2041
+ );
2042
+ const responsePayload = await response.json().catch(() => null);
2043
+ if (!response.ok) {
2044
+ throw new Error(
2045
+ `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2046
+ );
2047
+ }
2048
+ return responsePayload;
2049
+ } finally {
2050
+ clearTimeout(timeout);
2051
+ }
2052
+ }
2053
+ async function configureOrbitdbRelaySetup2(args) {
2054
+ const controller = new AbortController();
2055
+ const timeout = setTimeout(
2056
+ () => controller.abort(),
2057
+ Number(args.timeoutMs ?? 18e4)
2058
+ );
2059
+ const payload = {
2060
+ public_ipv4: args.hostIpv4,
2061
+ public_ipv6: args.publicIpv6 ?? void 0,
2062
+ tcp_port: args.tcpPort,
2063
+ ws_port: args.wsPort,
2064
+ proxy_url: args.proxyUrl ?? void 0,
2065
+ metrics_port: args.metricsPort ?? void 0,
2066
+ metrics_https_port: args.metricsHttpsPort ?? void 0,
2067
+ webrtc_port: args.webrtcPort ?? void 0,
2068
+ quic_port: args.quicPort ?? void 0,
2069
+ bootstrap_publisher_private_key: args.bootstrapPublisherPrivateKey ?? void 0,
2070
+ bootstrap_publisher_libp2p_identity_hex: args.bootstrapPublisherLibp2pIdentityHex ?? void 0,
2071
+ bootstrap_owner_private_key: args.bootstrapOwnerPrivateKey ?? void 0,
2072
+ bootstrap_owner_authorization_b64: args.bootstrapOwnerAuthorizationBase64 ?? void 0,
2073
+ bootstrap_registration_id: args.bootstrapRegistrationId ?? void 0,
2074
+ no_start: args.noStart === true ? true : void 0
2075
+ };
2076
+ try {
2077
+ const response = await args.fetch(
2078
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
2079
+ {
2080
+ method: "POST",
2081
+ headers: {
2082
+ "content-type": "application/json"
2083
+ },
2084
+ body: JSON.stringify(payload),
2085
+ signal: controller.signal
2086
+ }
2087
+ );
2088
+ const responsePayload = await response.json().catch(() => null);
2089
+ if (!response.ok) {
2090
+ throw new Error(
2091
+ `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2092
+ );
2093
+ }
2094
+ return responsePayload;
2095
+ } finally {
2096
+ clearTimeout(timeout);
2097
+ }
2098
+ }
2099
+ async function fetchUcGoPeerMetadata(args) {
2100
+ const attempts = Math.max(1, Number(args.attempts ?? 60));
2101
+ const delayMs = Math.max(0, Number(args.delayMs ?? 3e3));
2102
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2103
+ let lastPayload = null;
2104
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
2105
+ const controller = new AbortController();
2106
+ const timeout = setTimeout(
2107
+ () => controller.abort(),
2108
+ Number(args.timeoutMs ?? 18e4)
2109
+ );
2110
+ try {
2111
+ const response = await args.fetch(
2112
+ `http://${args.hostIpv4}:${args.setupPort}/metadata`,
2113
+ {
2114
+ signal: controller.signal
2115
+ }
2116
+ );
2117
+ const payload = await response.json().catch(() => null);
2118
+ lastPayload = payload;
2119
+ args.onAttempt?.(
2120
+ payload,
2121
+ Boolean(
2122
+ response.ok && payload && typeof payload === "object" && payload.status === "ready"
2123
+ ),
2124
+ attempt + 1,
2125
+ attempts
2126
+ );
2127
+ if (response.ok && payload && typeof payload === "object" && payload.status === "ready") {
2128
+ return payload;
2129
+ }
2130
+ if (response.status >= 500) {
2131
+ throw new Error(
2132
+ `Relay metadata request failed: ${response.status} ${typeof payload === "string" ? payload : JSON.stringify(payload ?? {})}`
2133
+ );
2134
+ }
2135
+ } finally {
2136
+ clearTimeout(timeout);
2137
+ }
2138
+ if (attempt < attempts - 1) {
2139
+ await sleep(delayMs);
2140
+ }
2141
+ }
2142
+ throw new Error(
2143
+ `Relay metadata did not become ready after ${attempts} attempts: ${typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2144
+ );
2145
+ }
2146
+
2147
+ // ../aleph-bootstrap/src/index.ts
2148
+ import { recoverMessageAddress } from "viem";
2149
+ var DEFAULT_ALEPH_API_HOST3 = "https://api2.aleph.im";
2150
+ var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
2151
+ var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
2152
+ var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
2153
+ var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2154
+ var DEFAULT_BOOTSTRAP_PAGINATION = 50;
2155
+ var RELAY_BOOTSTRAP_SIGNATURE_SCHEME = "personal_sign";
2156
+ function serializeOwnerAuthorizationPayload(payload) {
2157
+ return JSON.stringify({
2158
+ ownerAddress: payload.ownerAddress,
2159
+ publisherAddress: payload.publisherAddress,
2160
+ peerId: payload.peerId,
2161
+ registrationId: payload.registrationId,
2162
+ profile: payload.profile,
2163
+ version: payload.version,
2164
+ instanceItemHash: payload.instanceItemHash,
2165
+ issuedAt: payload.issuedAt,
2166
+ expiresAt: payload.expiresAt
2167
+ });
2168
+ }
2169
+ function serializeRelayProofPayload(payload) {
2170
+ return JSON.stringify({
2171
+ peerId: payload.peerId,
2172
+ multiaddrs: dedupeMultiaddrs(payload.multiaddrs),
2173
+ browserMultiaddrs: payload.browserMultiaddrs ? dedupeMultiaddrs(payload.browserMultiaddrs) : void 0,
2174
+ registrationId: payload.registrationId,
2175
+ profile: payload.profile,
2176
+ version: payload.version,
2177
+ updatedAt: payload.updatedAt
2178
+ });
2179
+ }
2180
+ function asTrimmedString(value) {
2181
+ return typeof value === "string" && value.trim() ? value.trim() : null;
2182
+ }
2183
+ function asNumber4(value) {
2184
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2185
+ if (typeof value === "string" && value.trim()) {
2186
+ const parsed = Number(value);
2187
+ return Number.isFinite(parsed) ? parsed : null;
2188
+ }
2189
+ return null;
2190
+ }
2191
+ function normalizeHost(host) {
2192
+ return host.trim().toLowerCase();
2193
+ }
2194
+ function normalizeSignature(signature) {
2195
+ const trimmed = signature.trim();
2196
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
2197
+ }
2198
+ function splitMultiaddr(addr) {
2199
+ return addr.split("/").filter(Boolean);
2200
+ }
2201
+ function isPrivateIpv4(ip) {
2202
+ const parts = ip.split(".").map((part) => Number(part));
2203
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
2204
+ return false;
2205
+ }
2206
+ if (parts[0] === 10) return true;
2207
+ if (parts[0] === 127) return true;
2208
+ if (parts[0] === 0) return true;
2209
+ if (parts[0] === 169 && parts[1] === 254) return true;
2210
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
2211
+ if (parts[0] === 192 && parts[1] === 168) return true;
2212
+ if (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) return true;
2213
+ return false;
2214
+ }
2215
+ function isPrivateIpv6(ip) {
2216
+ const normalized = ip.trim().toLowerCase();
2217
+ return normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
2218
+ }
2219
+ function isLocalHostname(host) {
2220
+ const normalized = normalizeHost(host);
2221
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local");
2222
+ }
2223
+ function hasPeerId(addr) {
2224
+ return splitMultiaddr(addr).includes("p2p");
2225
+ }
2226
+ function isBrowserDialableMultiaddr(addr) {
2227
+ const normalized = addr.toLowerCase();
2228
+ return normalized.includes("/ws") || normalized.includes("/wss") || normalized.includes("/webtransport") || normalized.includes("/webrtc-direct");
2229
+ }
2230
+ function dedupeMultiaddrs(addrs) {
2231
+ const seen = /* @__PURE__ */ new Set();
2232
+ const result = [];
2233
+ for (const value of addrs) {
2234
+ const trimmed = value.trim();
2235
+ if (!trimmed || seen.has(trimmed)) continue;
2236
+ seen.add(trimmed);
2237
+ result.push(trimmed);
2238
+ }
2239
+ return result;
2240
+ }
2241
+ function isPublicMultiaddr(addr) {
2242
+ const parts = splitMultiaddr(addr);
2243
+ for (let index = 0; index < parts.length; index += 1) {
2244
+ const protocol = parts[index];
2245
+ const value = parts[index + 1];
2246
+ if (protocol === "ip4" && value) {
2247
+ return !isPrivateIpv4(value);
2248
+ }
2249
+ if (protocol === "ip6" && value) {
2250
+ return !isPrivateIpv6(value);
2251
+ }
2252
+ if ((protocol === "dns4" || protocol === "dns6" || protocol === "dnsaddr") && value) {
2253
+ return !isLocalHostname(value);
2254
+ }
2255
+ }
2256
+ return false;
2257
+ }
2258
+ function filterPublicMultiaddrs(addrs, options = {}) {
2259
+ return dedupeMultiaddrs(addrs).filter((addr) => {
2260
+ if (!isPublicMultiaddr(addr)) return false;
2261
+ if (options.requirePeerId !== false && !hasPeerId(addr)) return false;
2262
+ if (options.browserDialableOnly && !isBrowserDialableMultiaddr(addr)) {
2263
+ return false;
2264
+ }
2265
+ return true;
2266
+ });
2267
+ }
2268
+ function normalizeRelayBootstrapContent(value) {
2269
+ if (!value || typeof value !== "object") return null;
2270
+ const content = value;
2271
+ const peerId = asTrimmedString(content.peerId);
2272
+ const updatedAt = asNumber4(content.updatedAt);
2273
+ if (!peerId || updatedAt == null) return null;
2274
+ const multiaddrs = Array.isArray(content.multiaddrs) ? content.multiaddrs.filter((entry) => typeof entry === "string") : [];
2275
+ const browserMultiaddrs = Array.isArray(content.browserMultiaddrs) ? content.browserMultiaddrs.filter((entry) => typeof entry === "string") : void 0;
2276
+ return {
2277
+ peerId,
2278
+ multiaddrs: dedupeMultiaddrs(multiaddrs),
2279
+ browserMultiaddrs: browserMultiaddrs ? dedupeMultiaddrs(browserMultiaddrs) : void 0,
2280
+ registrationId: asTrimmedString(content.registrationId) ?? void 0,
2281
+ profile: asTrimmedString(content.profile) ?? void 0,
2282
+ version: asTrimmedString(content.version) ?? void 0,
2283
+ ownerAddress: asTrimmedString(content.ownerAddress) ?? void 0,
2284
+ publisherAddress: asTrimmedString(content.publisherAddress) ?? void 0,
2285
+ authorization: normalizeRelayBootstrapAuthorizationRecord(content.authorization) ?? void 0,
2286
+ relayProof: normalizeRelayBootstrapProofRecord(content.relayProof) ?? void 0,
2287
+ updatedAt
2288
+ };
2289
+ }
2290
+ function normalizeRelayBootstrapAuthorizationPayload(value) {
2291
+ if (!value || typeof value !== "object") return null;
2292
+ const payload = value;
2293
+ const ownerAddress = asTrimmedString(payload.ownerAddress);
2294
+ const publisherAddress = asTrimmedString(payload.publisherAddress);
2295
+ const peerId = asTrimmedString(payload.peerId);
2296
+ const issuedAt = asNumber4(payload.issuedAt);
2297
+ const expiresAt = asNumber4(payload.expiresAt) ?? void 0;
2298
+ if (!ownerAddress || !publisherAddress || !peerId || issuedAt == null) {
2299
+ return null;
2300
+ }
2301
+ return {
2302
+ ownerAddress,
2303
+ publisherAddress,
2304
+ peerId,
2305
+ registrationId: asTrimmedString(payload.registrationId) ?? void 0,
2306
+ profile: asTrimmedString(payload.profile) ?? void 0,
2307
+ version: asTrimmedString(payload.version) ?? void 0,
2308
+ instanceItemHash: asTrimmedString(payload.instanceItemHash) ?? void 0,
2309
+ issuedAt,
2310
+ expiresAt
2311
+ };
2312
+ }
2313
+ function normalizeRelayBootstrapAuthorizationRecord(value) {
2314
+ if (!value || typeof value !== "object") return null;
2315
+ const record = value;
2316
+ const scheme = asTrimmedString(record.scheme);
2317
+ const signature = asTrimmedString(record.signature);
2318
+ const payload = normalizeRelayBootstrapAuthorizationPayload(record.payload);
2319
+ if (!scheme || !signature || !payload) return null;
2320
+ return { scheme, signature, payload };
2321
+ }
2322
+ function normalizeRelayBootstrapProofPayload(value) {
2323
+ if (!value || typeof value !== "object") return null;
2324
+ const payload = value;
2325
+ const peerId = asTrimmedString(payload.peerId);
2326
+ const updatedAt = asNumber4(payload.updatedAt);
2327
+ if (!peerId || updatedAt == null) return null;
2328
+ const multiaddrs = Array.isArray(payload.multiaddrs) ? payload.multiaddrs.filter((entry) => typeof entry === "string") : [];
2329
+ const browserMultiaddrs = Array.isArray(payload.browserMultiaddrs) ? payload.browserMultiaddrs.filter((entry) => typeof entry === "string") : void 0;
2330
+ return {
2331
+ peerId,
2332
+ multiaddrs: dedupeMultiaddrs(multiaddrs),
2333
+ browserMultiaddrs: browserMultiaddrs ? dedupeMultiaddrs(browserMultiaddrs) : void 0,
2334
+ registrationId: asTrimmedString(payload.registrationId) ?? void 0,
2335
+ profile: asTrimmedString(payload.profile) ?? void 0,
2336
+ version: asTrimmedString(payload.version) ?? void 0,
2337
+ updatedAt
2338
+ };
2339
+ }
2340
+ function normalizeRelayBootstrapProofRecord(value) {
2341
+ if (!value || typeof value !== "object") return null;
2342
+ const record = value;
2343
+ const scheme = asTrimmedString(record.scheme);
2344
+ const signature = asTrimmedString(record.signature);
2345
+ const payload = normalizeRelayBootstrapProofPayload(record.payload);
2346
+ if (!scheme || !signature || !payload) return null;
2347
+ return { scheme, signature, payload };
2348
+ }
2349
+ function normalizeRelayBootstrapPostRecord(value) {
2350
+ if (!value || typeof value !== "object") return null;
2351
+ const entry = value;
2352
+ return {
2353
+ hash: asTrimmedString(entry.hash),
2354
+ itemHash: asTrimmedString(entry.item_hash),
2355
+ address: asTrimmedString(entry.address),
2356
+ ref: asTrimmedString(entry.ref),
2357
+ type: asTrimmedString(entry.type),
2358
+ time: asNumber4(entry.time),
2359
+ content: normalizeRelayBootstrapContent(entry.content)
2360
+ };
2361
+ }
2362
+ function buildRelayBootstrapPostContent(args) {
2363
+ const now = args.now ?? Date.now() / 1e3;
2364
+ const updatedAt = args.now ?? Date.now();
2365
+ return {
2366
+ type: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2367
+ address: args.sender,
2368
+ ...args.ref ? { ref: args.ref } : {},
2369
+ content: {
2370
+ peerId: args.peerId,
2371
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2372
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2373
+ browserDialableOnly: true
2374
+ }) : void 0,
2375
+ registrationId: args.registrationId,
2376
+ profile: args.profile,
2377
+ version: args.version,
2378
+ ownerAddress: args.ownerAddress,
2379
+ publisherAddress: args.publisherAddress,
2380
+ authorization: args.authorization,
2381
+ relayProof: args.relayProof,
2382
+ updatedAt: Math.round(updatedAt)
2383
+ },
2384
+ time: now
2385
+ };
2386
+ }
2387
+ async function createRelayBootstrapPost(args) {
2388
+ const nowMillis = args.now ?? Date.now();
2389
+ const nowSeconds = nowMillis / 1e3;
2390
+ const itemContent = buildRelayBootstrapPostContent({
2391
+ sender: args.sender,
2392
+ peerId: args.peerId,
2393
+ multiaddrs: args.multiaddrs,
2394
+ browserMultiaddrs: args.browserMultiaddrs,
2395
+ registrationId: args.registrationId,
2396
+ ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
2397
+ postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2398
+ profile: args.profile,
2399
+ version: args.version,
2400
+ ownerAddress: args.ownerAddress,
2401
+ publisherAddress: args.publisherAddress,
2402
+ authorization: args.authorization,
2403
+ relayProof: args.relayProof,
2404
+ now: nowMillis
2405
+ });
2406
+ itemContent.time = nowSeconds;
2407
+ const serialized = JSON.stringify(itemContent);
2408
+ const itemHash = await args.hasher(serialized);
2409
+ return {
2410
+ channel: args.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL,
2411
+ sender: args.sender,
2412
+ chain: "ETH",
2413
+ type: "POST",
2414
+ time: nowSeconds,
2415
+ item_type: "inline",
2416
+ item_content: serialized,
2417
+ item_hash: itemHash
2418
+ };
2419
+ }
2420
+ async function signRelayBootstrapAuthorization(args) {
2421
+ const payload = {
2422
+ ownerAddress: args.ownerAddress,
2423
+ publisherAddress: args.publisherAddress,
2424
+ peerId: args.peerId,
2425
+ registrationId: args.registrationId,
2426
+ profile: args.profile,
2427
+ version: args.version,
2428
+ instanceItemHash: args.instanceItemHash,
2429
+ issuedAt: args.issuedAt ?? Date.now(),
2430
+ expiresAt: args.expiresAt
2431
+ };
2432
+ const serialized = serializeOwnerAuthorizationPayload(payload);
2433
+ const signature = await args.signer(args.ownerAddress, serialized);
2434
+ return {
2435
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2436
+ payload,
2437
+ signature: normalizeSignature(signature)
2438
+ };
2439
+ }
2440
+ async function signRelayBootstrapProof(args) {
2441
+ const payload = {
2442
+ peerId: args.peerId,
2443
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2444
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2445
+ browserDialableOnly: true
2446
+ }) : void 0,
2447
+ registrationId: args.registrationId,
2448
+ profile: args.profile,
2449
+ version: args.version,
2450
+ updatedAt: args.updatedAt ?? Date.now()
2451
+ };
2452
+ const serialized = serializeRelayProofPayload(payload);
2453
+ const signature = await args.signer(args.publisherAddress, serialized);
2454
+ return {
2455
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2456
+ payload,
2457
+ signature: normalizeSignature(signature)
2458
+ };
2459
+ }
2460
+ async function fetchAlephBootstrapPosts(options = {}) {
2461
+ const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
2462
+ if (typeof fetchImpl !== "function") {
2463
+ throw new Error(
2464
+ "A fetch implementation is required to query Aleph bootstrap posts."
2465
+ );
2466
+ }
2467
+ const url = new URL(
2468
+ "/api/v0/posts.json",
2469
+ options.apiHost ?? DEFAULT_ALEPH_API_HOST3
2470
+ );
2471
+ url.searchParams.set(
2472
+ "channels",
2473
+ options.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL
2474
+ );
2475
+ url.searchParams.set("refs", options.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF);
2476
+ url.searchParams.set(
2477
+ "types",
2478
+ options.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE
2479
+ );
2480
+ url.searchParams.set(
2481
+ "pagination",
2482
+ String(options.pagination ?? DEFAULT_BOOTSTRAP_PAGINATION)
2483
+ );
2484
+ url.searchParams.set("page", String(options.page ?? 1));
2485
+ const response = await fetchImpl(url.toString(), {
2486
+ method: "GET",
2487
+ headers: { accept: "application/json" }
2488
+ });
2489
+ if (!response.ok) {
2490
+ throw new Error(`Aleph bootstrap post lookup failed: ${response.status}`);
2491
+ }
2492
+ const payload = await response.json();
2493
+ return (payload.posts ?? []).map((entry) => normalizeRelayBootstrapPostRecord(entry)).filter((entry) => entry != null);
2494
+ }
2495
+
2496
+ // ../core/src/bootstrap-registration.ts
2497
+ async function publishRelayBootstrapRegistration(args) {
2498
+ const publisherAddress = args.publisherAddress ?? args.sender;
2499
+ const ownerAddress = args.ownerAddress;
2500
+ let ownerAuthorization = args.ownerAuthorization;
2501
+ let relayProof = args.relayProof;
2502
+ if (!ownerAuthorization && ownerAddress && args.ownerSigner) {
2503
+ ownerAuthorization = await signRelayBootstrapAuthorization({
2504
+ ownerAddress,
2505
+ publisherAddress,
2506
+ peerId: args.peerId,
2507
+ registrationId: args.registrationId,
2508
+ profile: args.profile,
2509
+ version: args.version,
2510
+ instanceItemHash: args.instanceItemHash,
2511
+ issuedAt: args.authorizationIssuedAt ?? args.now ?? Date.now(),
2512
+ expiresAt: args.authorizationExpiresAt,
2513
+ signer: args.ownerSigner
2514
+ });
2515
+ }
2516
+ if (!relayProof && args.publisherSigner) {
2517
+ relayProof = await signRelayBootstrapProof({
2518
+ publisherAddress,
2519
+ peerId: args.peerId,
2520
+ multiaddrs: args.multiaddrs,
2521
+ browserMultiaddrs: args.browserMultiaddrs,
2522
+ registrationId: args.registrationId,
2523
+ profile: args.profile,
2524
+ version: args.version,
2525
+ updatedAt: args.now ?? Date.now(),
2526
+ signer: args.publisherSigner
2527
+ });
2528
+ }
2529
+ const unsigned = await createRelayBootstrapPost({
2530
+ sender: args.sender,
2531
+ peerId: args.peerId,
2532
+ multiaddrs: args.multiaddrs,
2533
+ browserMultiaddrs: args.browserMultiaddrs,
2534
+ registrationId: args.registrationId,
2535
+ channel: args.channel,
2536
+ ref: args.ref,
2537
+ postType: args.postType,
2538
+ profile: args.profile,
2539
+ version: args.version,
2540
+ ownerAddress,
2541
+ publisherAddress,
2542
+ authorization: ownerAuthorization,
2543
+ relayProof,
2544
+ now: args.now,
2545
+ hasher: args.hasher
2546
+ });
2547
+ const content = JSON.parse(unsigned.item_content);
2548
+ if ((content.content?.multiaddrs?.length ?? 0) === 0) {
2549
+ return {
2550
+ status: "skipped",
2551
+ reason: "No public relay multiaddrs remained after filtering.",
2552
+ publishedMultiaddrs: [],
2553
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2554
+ };
2555
+ }
2556
+ const message = await signAlephMessage(unsigned, args.signer);
2557
+ const { response, httpStatus } = await broadcastAlephMessage2(message, {
2558
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2559
+ sync: args.sync ?? true,
2560
+ fetch: args.fetch
2561
+ });
2562
+ let forgottenHashes = [];
2563
+ let forgetResult;
2564
+ if (args.forgetPrevious && args.registrationId) {
2565
+ const previousPosts = await fetchAlephBootstrapPosts({
2566
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2567
+ channel: args.channel,
2568
+ ref: args.ref,
2569
+ postType: args.postType,
2570
+ fetch: args.fetch
2571
+ });
2572
+ forgottenHashes = previousPosts.filter(
2573
+ (post) => post.address?.toLowerCase() === args.sender.toLowerCase() && post.content?.registrationId === args.registrationId && (post.itemHash ?? post.hash) != null && (post.itemHash ?? post.hash) !== unsigned.item_hash
2574
+ ).map((post) => post.itemHash ?? post.hash ?? "").filter(Boolean);
2575
+ if (forgottenHashes.length > 0) {
2576
+ forgetResult = await forgetAlephMessages({
2577
+ sender: args.sender,
2578
+ hashes: forgottenHashes,
2579
+ reason: args.forgetPreviousReason ?? `Replace older relay bootstrap records for ${args.registrationId}`,
2580
+ signer: args.signer,
2581
+ hasher: args.hasher,
2582
+ fetch: args.fetch,
2583
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2584
+ sync: args.sync ?? true
2585
+ });
2586
+ }
2587
+ }
2588
+ return {
2589
+ status: "published",
2590
+ itemHash: unsigned.item_hash,
2591
+ message,
2592
+ response,
2593
+ httpStatus,
2594
+ publishedMultiaddrs: content.content?.multiaddrs ?? [],
2595
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? [],
2596
+ forgottenHashes,
2597
+ forgetResult
2598
+ };
2599
+ }
1914
2600
 
1915
2601
  // src/shared/events.ts
1916
2602
  function createDeploymentProgressEmitter() {
@@ -1947,8 +2633,11 @@ function defaultState(props = {}) {
1947
2633
  sshPublicKey: props.sshPublicKey ?? "",
1948
2634
  instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
1949
2635
  tierId: DEFAULT_TIER_ID,
2636
+ showAdvanced: Boolean(
2637
+ props.manifestUrl && props.manifestUrl !== DEFAULT_MANIFEST_URL || props.manifestJson?.trim() || props.sshPublicKey?.trim()
2638
+ ),
1950
2639
  showInstances: props.showInstances ?? true,
1951
- showPasteManifest: false,
2640
+ showPasteManifest: Boolean(props.manifestJson?.trim()),
1952
2641
  busy: {
1953
2642
  connectingWallet: false,
1954
2643
  refreshing: false,
@@ -2069,6 +2758,13 @@ async function resolveManifest(args) {
2069
2758
  if (pasted) return pasted;
2070
2759
  return loadRootfsManifest(args.manifestUrl);
2071
2760
  }
2761
+ function relayProfileForManifest(manifest) {
2762
+ if (manifest?.profile === "uc-go-peer") return "uc-go-peer";
2763
+ if (manifest?.profile === "orbitdb-relay-pinner") {
2764
+ return "orbitdb-relay-pinner";
2765
+ }
2766
+ return null;
2767
+ }
2072
2768
  async function inspectInstanceRuntime(args) {
2073
2769
  const details = {
2074
2770
  messageStatus: String(
@@ -2259,6 +2955,7 @@ var SponsorRelayController = class {
2259
2955
  client;
2260
2956
  refreshTimer = null;
2261
2957
  pingTimer = null;
2958
+ manifestRefreshTimer = null;
2262
2959
  stopWalletWatch = null;
2263
2960
  props;
2264
2961
  progressEmitter = createDeploymentProgressEmitter();
@@ -2338,6 +3035,145 @@ var SponsorRelayController = class {
2338
3035
  });
2339
3036
  this.progressEmitter.emit(nextEvent);
2340
3037
  }
3038
+ async configureRelayBootstrapRegistration(args) {
3039
+ const profile = relayProfileForManifest(this.state.manifest);
3040
+ if (!profile) return;
3041
+ if (!this.state.wallet.address) {
3042
+ throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3043
+ }
3044
+ if (!args.runtime.hostIpv4) {
3045
+ throw new Error("Relay runtime did not expose a host IPv4 address.");
3046
+ }
3047
+ const mappedPorts2 = args.runtime.mappedPorts ?? {};
3048
+ const setupPort = mappedPorts2["80"]?.host ?? null;
3049
+ if (!setupPort) {
3050
+ throw new Error("Relay runtime did not expose the temporary setup endpoint.");
3051
+ }
3052
+ this.emitProgress({
3053
+ stage: "deployment-confirmed",
3054
+ label: "Configuring relay",
3055
+ progress: 93,
3056
+ status: "info",
3057
+ itemHash: args.itemHash,
3058
+ detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3059
+ });
3060
+ const setupHealth = await waitForSetupEndpoint({
3061
+ hostIpv4: args.runtime.hostIpv4,
3062
+ setupPort,
3063
+ fetch: (url, init) => fetch(url, init),
3064
+ attempts: 15,
3065
+ delayMs: 4e3,
3066
+ httpTimeoutMs: 1e4
3067
+ });
3068
+ if (!setupHealth.ok) {
3069
+ throw new Error(
3070
+ `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
3071
+ );
3072
+ }
3073
+ this.emitProgress({
3074
+ stage: "deployment-confirmed",
3075
+ label: "Applying relay networking",
3076
+ progress: 94,
3077
+ status: "info",
3078
+ itemHash: args.itemHash,
3079
+ detail: "Publishing the host port mapping into the guest relay configuration."
3080
+ });
3081
+ if (profile === "orbitdb-relay-pinner") {
3082
+ const tcpPort = mappedPorts2["9091"]?.host ?? 0;
3083
+ const wsPort = mappedPorts2["9092"]?.host ?? mappedPorts2["443"]?.host ?? 0;
3084
+ if (!tcpPort || !wsPort) {
3085
+ throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3086
+ }
3087
+ await configureOrbitdbRelaySetup2({
3088
+ hostIpv4: args.runtime.hostIpv4,
3089
+ publicIpv6: args.runtime.ipv6,
3090
+ setupPort,
3091
+ tcpPort,
3092
+ wsPort,
3093
+ proxyUrl: args.runtime.proxyUrl ?? void 0,
3094
+ metricsPort: mappedPorts2["9090"]?.host ?? null,
3095
+ metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
3096
+ webrtcPort: mappedPorts2["9093"]?.host ?? null,
3097
+ quicPort: mappedPorts2["9094"]?.host ?? null,
3098
+ fetch: (url, init) => fetch(url, init),
3099
+ timeoutMs: 18e4
3100
+ });
3101
+ } else {
3102
+ await configureUcGoPeer({
3103
+ hostIpv4: args.runtime.hostIpv4,
3104
+ publicIpv6: args.runtime.ipv6,
3105
+ setupPort,
3106
+ tcpPort: mappedPorts2["9095"]?.host ?? null,
3107
+ wsPort: mappedPorts2["9097"]?.host ?? mappedPorts2["9095"]?.host ?? null,
3108
+ udpPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
3109
+ quicPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
3110
+ webrtcPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
3111
+ proxyUrl: args.runtime.proxyUrl ?? void 0,
3112
+ fetch: (url, init) => fetch(url, init),
3113
+ timeoutMs: 18e4
3114
+ });
3115
+ }
3116
+ this.emitProgress({
3117
+ stage: "publishing-bootstrap",
3118
+ label: "Reading relay multiaddrs",
3119
+ progress: 95,
3120
+ status: "info",
3121
+ itemHash: args.itemHash,
3122
+ detail: "Waiting for the relay to report its final public multiaddrs."
3123
+ });
3124
+ const metadataResult = await fetchUcGoPeerMetadata({
3125
+ hostIpv4: args.runtime.hostIpv4,
3126
+ setupPort,
3127
+ fetch: (url, init) => fetch(url, init),
3128
+ attempts: 80,
3129
+ delayMs: 3e3,
3130
+ timeoutMs: 24e4
3131
+ });
3132
+ const metadata = metadataResult && typeof metadataResult === "object" && metadataResult.metadata && typeof metadataResult.metadata === "object" ? metadataResult.metadata : null;
3133
+ const peerId = typeof metadata?.peer_id === "string" ? metadata.peer_id : null;
3134
+ const probeMultiaddrs = Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.filter(
3135
+ (entry) => typeof entry === "string"
3136
+ ) : [];
3137
+ const browserBootstrapMultiaddrs = Array.isArray(
3138
+ metadata?.browser_bootstrap_multiaddrs
3139
+ ) ? metadata.browser_bootstrap_multiaddrs.filter(
3140
+ (entry) => typeof entry === "string"
3141
+ ) : [];
3142
+ if (!peerId || probeMultiaddrs.length === 0) {
3143
+ throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3144
+ }
3145
+ this.emitProgress({
3146
+ stage: "publishing-bootstrap",
3147
+ label: "Publishing bootstrap registration",
3148
+ progress: 96,
3149
+ status: "info",
3150
+ itemHash: args.itemHash,
3151
+ detail: "Writing the relay bootstrap registration to Aleph."
3152
+ });
3153
+ const publication = await publishRelayBootstrapRegistration({
3154
+ sender: this.state.wallet.address,
3155
+ signer: personalSign,
3156
+ hasher: sha256Hex,
3157
+ fetch: (url, init) => fetch(url, init).then(async (response) => ({
3158
+ ok: response.ok,
3159
+ status: response.status,
3160
+ json: async () => await response.json()
3161
+ })),
3162
+ apiHost: this.client.apiHost,
3163
+ peerId,
3164
+ multiaddrs: probeMultiaddrs,
3165
+ browserMultiaddrs: browserBootstrapMultiaddrs,
3166
+ profile,
3167
+ version: this.state.manifest?.version,
3168
+ sync: true
3169
+ });
3170
+ this.trace("deploy:bootstrap-registration", {
3171
+ itemHash: args.itemHash,
3172
+ peerId,
3173
+ metadata,
3174
+ publication
3175
+ });
3176
+ }
2341
3177
  syncLatestDeploymentProgress(instances) {
2342
3178
  const itemHash = this.state.lastDeploymentHash;
2343
3179
  if (!itemHash) {
@@ -2492,6 +3328,7 @@ var SponsorRelayController = class {
2492
3328
  destroy() {
2493
3329
  if (this.refreshTimer) clearInterval(this.refreshTimer);
2494
3330
  if (this.pingTimer) clearInterval(this.pingTimer);
3331
+ if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
2495
3332
  this.stopWalletWatch?.();
2496
3333
  }
2497
3334
  setOpen(open) {
@@ -2502,15 +3339,27 @@ var SponsorRelayController = class {
2502
3339
  }
2503
3340
  setManifestUrl(manifestUrl) {
2504
3341
  this.patch({ manifestUrl });
3342
+ this.queueManifestRefresh();
2505
3343
  }
2506
3344
  setManifestJson(manifestJson) {
2507
- this.patch({ manifestJson });
3345
+ this.patch({
3346
+ manifestJson,
3347
+ showAdvanced: this.state.showAdvanced || Boolean(manifestJson.trim()),
3348
+ showPasteManifest: this.state.showPasteManifest || Boolean(manifestJson.trim())
3349
+ });
3350
+ this.queueManifestRefresh();
3351
+ }
3352
+ setShowAdvanced(showAdvanced) {
3353
+ this.patch({ showAdvanced });
2508
3354
  }
2509
3355
  setShowPasteManifest(showPasteManifest) {
2510
3356
  this.patch({ showPasteManifest });
2511
3357
  }
2512
3358
  setSshPublicKey(sshPublicKey) {
2513
- this.patch({ sshPublicKey });
3359
+ this.patch({
3360
+ sshPublicKey,
3361
+ showAdvanced: this.state.showAdvanced || Boolean(sshPublicKey.trim())
3362
+ });
2514
3363
  }
2515
3364
  setInstanceName(instanceName) {
2516
3365
  this.patch({ instanceName });
@@ -2546,6 +3395,18 @@ var SponsorRelayController = class {
2546
3395
  selectedCrn
2547
3396
  });
2548
3397
  }
3398
+ queueManifestRefresh() {
3399
+ if (!this.state.ready) {
3400
+ return;
3401
+ }
3402
+ if (this.manifestRefreshTimer) {
3403
+ clearTimeout(this.manifestRefreshTimer);
3404
+ }
3405
+ this.manifestRefreshTimer = setTimeout(() => {
3406
+ this.manifestRefreshTimer = null;
3407
+ void this.refresh();
3408
+ }, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS);
3409
+ }
2549
3410
  async connectWallet() {
2550
3411
  this.patch({
2551
3412
  busy: { connectingWallet: true },
@@ -2946,6 +3807,10 @@ var SponsorRelayController = class {
2946
3807
  execution: runtime.execution ?? null,
2947
3808
  error: null
2948
3809
  });
3810
+ await this.configureRelayBootstrapRegistration({
3811
+ itemHash: result.itemHash,
3812
+ runtime
3813
+ });
2949
3814
  this.patch({
2950
3815
  busy: { deploying: false }
2951
3816
  });
@@ -3055,6 +3920,7 @@ export {
3055
3920
  INSTANCE_RUNTIME_PENDING_REFRESH_MS,
3056
3921
  INSTANCE_RUNTIME_READY_REFRESH_MS,
3057
3922
  INSTANCE_RUNTIME_STALE_REFRESH_MS,
3923
+ MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS,
3058
3924
  RECENT_INSTANCE_RUNTIME_GRACE_MS,
3059
3925
  REFRESH_INTERVAL_MS,
3060
3926
  RELAY_PING_IDLE_STATE,
@@ -3068,6 +3934,7 @@ export {
3068
3934
  createSponsorRelayController,
3069
3935
  formatDateTime,
3070
3936
  formatNumber,
3937
+ formatTierSpecLabel,
3071
3938
  getEthereumProvider,
3072
3939
  joinMappedPorts,
3073
3940
  joinRequiredPortForwards,