@le-space/ui 0.2.0 → 0.3.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Floating Aleph sponsor relay UI components for Svelte and React.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,8 +36,8 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@le-space/browser": "0.2.0",
40
- "@le-space/core": "0.2.0",
39
+ "@le-space/browser": "0.3.0",
40
+ "@le-space/core": "0.3.0",
41
41
  "viem": "^2.38.5"
42
42
  },
43
43
  "peerDependencies": {
package/react/index.d.ts CHANGED
@@ -174,7 +174,7 @@ interface RootfsResolution {
174
174
  gatewayError?: string | null;
175
175
  }
176
176
 
177
- type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
177
+ type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "publishing-bootstrap" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
178
178
  interface DeploymentProgressEvent {
179
179
  stage: DeploymentProgressStage;
180
180
  label: string;
@@ -310,6 +310,7 @@ declare class SponsorRelayController {
310
310
  private trace;
311
311
  private patch;
312
312
  private emitProgress;
313
+ private configureRelayBootstrapRegistration;
313
314
  private syncLatestDeploymentProgress;
314
315
  private canSkipRuntimeRefresh;
315
316
  private noteRuntimeRefreshResult;
package/react/index.js CHANGED
@@ -1832,6 +1832,28 @@ async function waitForVmRuntime(args) {
1832
1832
  }
1833
1833
 
1834
1834
  // ../core/src/guest.ts
1835
+ async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1836
+ try {
1837
+ const controller = new AbortController();
1838
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1839
+ const response = await fetch2(url, {
1840
+ method: "GET",
1841
+ redirect: "follow",
1842
+ signal: controller.signal
1843
+ });
1844
+ clearTimeout(timeout);
1845
+ return {
1846
+ ok: response.ok,
1847
+ status: response.status,
1848
+ url: response.url
1849
+ };
1850
+ } catch (error) {
1851
+ return {
1852
+ ok: false,
1853
+ error: error instanceof Error ? error.message : String(error)
1854
+ };
1855
+ }
1856
+ }
1835
1857
  async function notifyCrnAllocation2(args) {
1836
1858
  const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1837
1859
  if (!normalizedCrnUrl) {
@@ -1912,6 +1934,324 @@ async function notifyCrnAllocationWithRetry(args) {
1912
1934
  reason: "CRN allocation notify was not confirmed."
1913
1935
  };
1914
1936
  }
1937
+ async function waitForSetupEndpoint(args) {
1938
+ const attempts = Math.max(1, Number(args.attempts ?? 15));
1939
+ const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
1940
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1941
+ const url = `http://${args.hostIpv4}:${args.setupPort}/health`;
1942
+ let result = await defaultHttpProbe(
1943
+ args.fetch,
1944
+ url,
1945
+ Number(args.httpTimeoutMs ?? 1e4)
1946
+ );
1947
+ args.onAttempt?.(result, 1, attempts);
1948
+ for (let attempt = 1; attempt < attempts; attempt += 1) {
1949
+ if (result.ok) return result;
1950
+ await sleep(delayMs);
1951
+ result = await defaultHttpProbe(
1952
+ args.fetch,
1953
+ url,
1954
+ Number(args.httpTimeoutMs ?? 1e4)
1955
+ );
1956
+ args.onAttempt?.(result, attempt + 1, attempts);
1957
+ }
1958
+ return result;
1959
+ }
1960
+ async function configureUcGoPeer(args) {
1961
+ const controller = new AbortController();
1962
+ const timeout = setTimeout(
1963
+ () => controller.abort(),
1964
+ Number(args.timeoutMs ?? 18e4)
1965
+ );
1966
+ const payload = {
1967
+ public_ipv4: args.hostIpv4,
1968
+ public_ipv6: args.publicIpv6 ?? void 0,
1969
+ proxy_url: args.proxyUrl ?? void 0,
1970
+ tcp_port: args.tcpPort ?? void 0,
1971
+ ws_port: args.wsPort ?? void 0,
1972
+ udp_port: args.udpPort ?? void 0,
1973
+ quic_port: args.quicPort ?? void 0,
1974
+ webrtc_port: args.webrtcPort ?? void 0
1975
+ };
1976
+ try {
1977
+ const response = await args.fetch(
1978
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
1979
+ {
1980
+ method: "POST",
1981
+ headers: {
1982
+ "content-type": "application/json"
1983
+ },
1984
+ body: JSON.stringify(payload),
1985
+ signal: controller.signal
1986
+ }
1987
+ );
1988
+ const responsePayload = await response.json().catch(() => null);
1989
+ if (!response.ok) {
1990
+ throw new Error(
1991
+ `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
1992
+ );
1993
+ }
1994
+ return responsePayload;
1995
+ } finally {
1996
+ clearTimeout(timeout);
1997
+ }
1998
+ }
1999
+ async function configureOrbitdbRelaySetup2(args) {
2000
+ const controller = new AbortController();
2001
+ const timeout = setTimeout(
2002
+ () => controller.abort(),
2003
+ Number(args.timeoutMs ?? 18e4)
2004
+ );
2005
+ const payload = {
2006
+ public_ipv4: args.hostIpv4,
2007
+ public_ipv6: args.publicIpv6 ?? void 0,
2008
+ tcp_port: args.tcpPort,
2009
+ ws_port: args.wsPort,
2010
+ proxy_url: args.proxyUrl ?? void 0,
2011
+ metrics_port: args.metricsPort ?? void 0,
2012
+ metrics_https_port: args.metricsHttpsPort ?? void 0,
2013
+ webrtc_port: args.webrtcPort ?? void 0,
2014
+ quic_port: args.quicPort ?? void 0
2015
+ };
2016
+ try {
2017
+ const response = await args.fetch(
2018
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
2019
+ {
2020
+ method: "POST",
2021
+ headers: {
2022
+ "content-type": "application/json"
2023
+ },
2024
+ body: JSON.stringify(payload),
2025
+ signal: controller.signal
2026
+ }
2027
+ );
2028
+ const responsePayload = await response.json().catch(() => null);
2029
+ if (!response.ok) {
2030
+ throw new Error(
2031
+ `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2032
+ );
2033
+ }
2034
+ return responsePayload;
2035
+ } finally {
2036
+ clearTimeout(timeout);
2037
+ }
2038
+ }
2039
+ async function fetchUcGoPeerMetadata(args) {
2040
+ const attempts = Math.max(1, Number(args.attempts ?? 60));
2041
+ const delayMs = Math.max(0, Number(args.delayMs ?? 3e3));
2042
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2043
+ let lastPayload = null;
2044
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
2045
+ const controller = new AbortController();
2046
+ const timeout = setTimeout(
2047
+ () => controller.abort(),
2048
+ Number(args.timeoutMs ?? 18e4)
2049
+ );
2050
+ try {
2051
+ const response = await args.fetch(
2052
+ `http://${args.hostIpv4}:${args.setupPort}/metadata`,
2053
+ {
2054
+ signal: controller.signal
2055
+ }
2056
+ );
2057
+ const payload = await response.json().catch(() => null);
2058
+ lastPayload = payload;
2059
+ args.onAttempt?.(
2060
+ payload,
2061
+ Boolean(
2062
+ response.ok && payload && typeof payload === "object" && payload.status === "ready"
2063
+ ),
2064
+ attempt + 1,
2065
+ attempts
2066
+ );
2067
+ if (response.ok && payload && typeof payload === "object" && payload.status === "ready") {
2068
+ return payload;
2069
+ }
2070
+ if (response.status >= 500) {
2071
+ throw new Error(
2072
+ `Relay metadata request failed: ${response.status} ${typeof payload === "string" ? payload : JSON.stringify(payload ?? {})}`
2073
+ );
2074
+ }
2075
+ } finally {
2076
+ clearTimeout(timeout);
2077
+ }
2078
+ if (attempt < attempts - 1) {
2079
+ await sleep(delayMs);
2080
+ }
2081
+ }
2082
+ throw new Error(
2083
+ `Relay metadata did not become ready after ${attempts} attempts: ${typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2084
+ );
2085
+ }
2086
+
2087
+ // ../aleph-bootstrap/src/index.ts
2088
+ var DEFAULT_ALEPH_API_HOST3 = "https://api2.aleph.im";
2089
+ var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
2090
+ var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
2091
+ var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
2092
+ var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2093
+ function normalizeHost(host) {
2094
+ return host.trim().toLowerCase();
2095
+ }
2096
+ function splitMultiaddr(addr) {
2097
+ return addr.split("/").filter(Boolean);
2098
+ }
2099
+ function isPrivateIpv4(ip) {
2100
+ const parts = ip.split(".").map((part) => Number(part));
2101
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
2102
+ return false;
2103
+ }
2104
+ if (parts[0] === 10) return true;
2105
+ if (parts[0] === 127) return true;
2106
+ if (parts[0] === 0) return true;
2107
+ if (parts[0] === 169 && parts[1] === 254) return true;
2108
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
2109
+ if (parts[0] === 192 && parts[1] === 168) return true;
2110
+ if (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) return true;
2111
+ return false;
2112
+ }
2113
+ function isPrivateIpv6(ip) {
2114
+ const normalized = ip.trim().toLowerCase();
2115
+ return normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
2116
+ }
2117
+ function isLocalHostname(host) {
2118
+ const normalized = normalizeHost(host);
2119
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local");
2120
+ }
2121
+ function hasPeerId(addr) {
2122
+ return splitMultiaddr(addr).includes("p2p");
2123
+ }
2124
+ function isBrowserDialableMultiaddr(addr) {
2125
+ const normalized = addr.toLowerCase();
2126
+ return normalized.includes("/ws") || normalized.includes("/wss") || normalized.includes("/webtransport") || normalized.includes("/webrtc-direct");
2127
+ }
2128
+ function dedupeMultiaddrs(addrs) {
2129
+ const seen = /* @__PURE__ */ new Set();
2130
+ const result = [];
2131
+ for (const value of addrs) {
2132
+ const trimmed = value.trim();
2133
+ if (!trimmed || seen.has(trimmed)) continue;
2134
+ seen.add(trimmed);
2135
+ result.push(trimmed);
2136
+ }
2137
+ return result;
2138
+ }
2139
+ function isPublicMultiaddr(addr) {
2140
+ const parts = splitMultiaddr(addr);
2141
+ for (let index = 0; index < parts.length; index += 1) {
2142
+ const protocol = parts[index];
2143
+ const value = parts[index + 1];
2144
+ if (protocol === "ip4" && value) {
2145
+ return !isPrivateIpv4(value);
2146
+ }
2147
+ if (protocol === "ip6" && value) {
2148
+ return !isPrivateIpv6(value);
2149
+ }
2150
+ if ((protocol === "dns4" || protocol === "dns6" || protocol === "dnsaddr") && value) {
2151
+ return !isLocalHostname(value);
2152
+ }
2153
+ }
2154
+ return false;
2155
+ }
2156
+ function filterPublicMultiaddrs(addrs, options = {}) {
2157
+ return dedupeMultiaddrs(addrs).filter((addr) => {
2158
+ if (!isPublicMultiaddr(addr)) return false;
2159
+ if (options.requirePeerId !== false && !hasPeerId(addr)) return false;
2160
+ if (options.browserDialableOnly && !isBrowserDialableMultiaddr(addr)) {
2161
+ return false;
2162
+ }
2163
+ return true;
2164
+ });
2165
+ }
2166
+ function buildRelayBootstrapPostContent(args) {
2167
+ const now = args.now ?? Date.now() / 1e3;
2168
+ const updatedAt = args.now ?? Date.now();
2169
+ return {
2170
+ type: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2171
+ address: args.sender,
2172
+ ...args.ref ? { ref: args.ref } : {},
2173
+ content: {
2174
+ peerId: args.peerId,
2175
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2176
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2177
+ browserDialableOnly: true
2178
+ }) : void 0,
2179
+ profile: args.profile,
2180
+ version: args.version,
2181
+ updatedAt: Math.round(updatedAt)
2182
+ },
2183
+ time: now
2184
+ };
2185
+ }
2186
+ async function createRelayBootstrapPost(args) {
2187
+ const nowMillis = args.now ?? Date.now();
2188
+ const nowSeconds = nowMillis / 1e3;
2189
+ const itemContent = buildRelayBootstrapPostContent({
2190
+ sender: args.sender,
2191
+ peerId: args.peerId,
2192
+ multiaddrs: args.multiaddrs,
2193
+ browserMultiaddrs: args.browserMultiaddrs,
2194
+ ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
2195
+ postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2196
+ profile: args.profile,
2197
+ version: args.version,
2198
+ now: nowMillis
2199
+ });
2200
+ itemContent.time = nowSeconds;
2201
+ const serialized = JSON.stringify(itemContent);
2202
+ const itemHash = await args.hasher(serialized);
2203
+ return {
2204
+ channel: args.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL,
2205
+ sender: args.sender,
2206
+ chain: "ETH",
2207
+ type: "POST",
2208
+ time: nowSeconds,
2209
+ item_type: "inline",
2210
+ item_content: serialized,
2211
+ item_hash: itemHash
2212
+ };
2213
+ }
2214
+
2215
+ // ../core/src/bootstrap-registration.ts
2216
+ async function publishRelayBootstrapRegistration(args) {
2217
+ const unsigned = await createRelayBootstrapPost({
2218
+ sender: args.sender,
2219
+ peerId: args.peerId,
2220
+ multiaddrs: args.multiaddrs,
2221
+ browserMultiaddrs: args.browserMultiaddrs,
2222
+ channel: args.channel,
2223
+ ref: args.ref,
2224
+ postType: args.postType,
2225
+ profile: args.profile,
2226
+ version: args.version,
2227
+ now: args.now,
2228
+ hasher: args.hasher
2229
+ });
2230
+ const content = JSON.parse(unsigned.item_content);
2231
+ if ((content.content?.multiaddrs?.length ?? 0) === 0) {
2232
+ return {
2233
+ status: "skipped",
2234
+ reason: "No public relay multiaddrs remained after filtering.",
2235
+ publishedMultiaddrs: [],
2236
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2237
+ };
2238
+ }
2239
+ const message = await signAlephMessage(unsigned, args.signer);
2240
+ const { response, httpStatus } = await broadcastAlephMessage2(message, {
2241
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2242
+ sync: args.sync ?? true,
2243
+ fetch: args.fetch
2244
+ });
2245
+ return {
2246
+ status: "published",
2247
+ itemHash: unsigned.item_hash,
2248
+ message,
2249
+ response,
2250
+ httpStatus,
2251
+ publishedMultiaddrs: content.content?.multiaddrs ?? [],
2252
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2253
+ };
2254
+ }
1915
2255
 
1916
2256
  // src/shared/events.ts
1917
2257
  function createDeploymentProgressEmitter() {
@@ -2070,6 +2410,13 @@ async function resolveManifest(args) {
2070
2410
  if (pasted) return pasted;
2071
2411
  return loadRootfsManifest(args.manifestUrl);
2072
2412
  }
2413
+ function relayProfileForManifest(manifest) {
2414
+ if (manifest?.profile === "uc-go-peer") return "uc-go-peer";
2415
+ if (manifest?.profile === "orbitdb-relay-pinner") {
2416
+ return "orbitdb-relay-pinner";
2417
+ }
2418
+ return null;
2419
+ }
2073
2420
  async function inspectInstanceRuntime(args) {
2074
2421
  const details = {
2075
2422
  messageStatus: String(
@@ -2339,6 +2686,145 @@ var SponsorRelayController = class {
2339
2686
  });
2340
2687
  this.progressEmitter.emit(nextEvent);
2341
2688
  }
2689
+ async configureRelayBootstrapRegistration(args) {
2690
+ const profile = relayProfileForManifest(this.state.manifest);
2691
+ if (!profile) return;
2692
+ if (!this.state.wallet.address) {
2693
+ throw new Error("A connected wallet is required to register relay bootstrap addresses.");
2694
+ }
2695
+ if (!args.runtime.hostIpv4) {
2696
+ throw new Error("Relay runtime did not expose a host IPv4 address.");
2697
+ }
2698
+ const mappedPorts2 = args.runtime.mappedPorts ?? {};
2699
+ const setupPort = mappedPorts2["80"]?.host ?? null;
2700
+ if (!setupPort) {
2701
+ throw new Error("Relay runtime did not expose the temporary setup endpoint.");
2702
+ }
2703
+ this.emitProgress({
2704
+ stage: "deployment-confirmed",
2705
+ label: "Configuring relay",
2706
+ progress: 93,
2707
+ status: "info",
2708
+ itemHash: args.itemHash,
2709
+ detail: "Waiting for the guest setup endpoint before collecting relay metadata."
2710
+ });
2711
+ const setupHealth = await waitForSetupEndpoint({
2712
+ hostIpv4: args.runtime.hostIpv4,
2713
+ setupPort,
2714
+ fetch: (url, init) => fetch(url, init),
2715
+ attempts: 15,
2716
+ delayMs: 4e3,
2717
+ httpTimeoutMs: 1e4
2718
+ });
2719
+ if (!setupHealth.ok) {
2720
+ throw new Error(
2721
+ `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
2722
+ );
2723
+ }
2724
+ this.emitProgress({
2725
+ stage: "deployment-confirmed",
2726
+ label: "Applying relay networking",
2727
+ progress: 94,
2728
+ status: "info",
2729
+ itemHash: args.itemHash,
2730
+ detail: "Publishing the host port mapping into the guest relay configuration."
2731
+ });
2732
+ if (profile === "orbitdb-relay-pinner") {
2733
+ const tcpPort = mappedPorts2["9091"]?.host ?? 0;
2734
+ const wsPort = mappedPorts2["9092"]?.host ?? mappedPorts2["443"]?.host ?? 0;
2735
+ if (!tcpPort || !wsPort) {
2736
+ throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
2737
+ }
2738
+ await configureOrbitdbRelaySetup2({
2739
+ hostIpv4: args.runtime.hostIpv4,
2740
+ publicIpv6: args.runtime.ipv6,
2741
+ setupPort,
2742
+ tcpPort,
2743
+ wsPort,
2744
+ proxyUrl: args.runtime.proxyUrl ?? void 0,
2745
+ metricsPort: mappedPorts2["9090"]?.host ?? null,
2746
+ metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
2747
+ webrtcPort: mappedPorts2["9093"]?.host ?? null,
2748
+ quicPort: mappedPorts2["9094"]?.host ?? null,
2749
+ fetch: (url, init) => fetch(url, init),
2750
+ timeoutMs: 18e4
2751
+ });
2752
+ } else {
2753
+ await configureUcGoPeer({
2754
+ hostIpv4: args.runtime.hostIpv4,
2755
+ publicIpv6: args.runtime.ipv6,
2756
+ setupPort,
2757
+ tcpPort: mappedPorts2["9095"]?.host ?? null,
2758
+ wsPort: mappedPorts2["9097"]?.host ?? mappedPorts2["9095"]?.host ?? null,
2759
+ udpPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
2760
+ quicPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
2761
+ webrtcPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
2762
+ proxyUrl: args.runtime.proxyUrl ?? void 0,
2763
+ fetch: (url, init) => fetch(url, init),
2764
+ timeoutMs: 18e4
2765
+ });
2766
+ }
2767
+ this.emitProgress({
2768
+ stage: "publishing-bootstrap",
2769
+ label: "Reading relay multiaddrs",
2770
+ progress: 95,
2771
+ status: "info",
2772
+ itemHash: args.itemHash,
2773
+ detail: "Waiting for the relay to report its final public multiaddrs."
2774
+ });
2775
+ const metadataResult = await fetchUcGoPeerMetadata({
2776
+ hostIpv4: args.runtime.hostIpv4,
2777
+ setupPort,
2778
+ fetch: (url, init) => fetch(url, init),
2779
+ attempts: 80,
2780
+ delayMs: 3e3,
2781
+ timeoutMs: 24e4
2782
+ });
2783
+ const metadata = metadataResult && typeof metadataResult === "object" && metadataResult.metadata && typeof metadataResult.metadata === "object" ? metadataResult.metadata : null;
2784
+ const peerId = typeof metadata?.peer_id === "string" ? metadata.peer_id : null;
2785
+ const probeMultiaddrs = Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.filter(
2786
+ (entry) => typeof entry === "string"
2787
+ ) : [];
2788
+ const browserBootstrapMultiaddrs = Array.isArray(
2789
+ metadata?.browser_bootstrap_multiaddrs
2790
+ ) ? metadata.browser_bootstrap_multiaddrs.filter(
2791
+ (entry) => typeof entry === "string"
2792
+ ) : [];
2793
+ if (!peerId || probeMultiaddrs.length === 0) {
2794
+ throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
2795
+ }
2796
+ this.emitProgress({
2797
+ stage: "publishing-bootstrap",
2798
+ label: "Publishing bootstrap registration",
2799
+ progress: 96,
2800
+ status: "info",
2801
+ itemHash: args.itemHash,
2802
+ detail: "Writing the relay bootstrap registration to Aleph."
2803
+ });
2804
+ const publication = await publishRelayBootstrapRegistration({
2805
+ sender: this.state.wallet.address,
2806
+ signer: personalSign,
2807
+ hasher: sha256Hex,
2808
+ fetch: (url, init) => fetch(url, init).then(async (response) => ({
2809
+ ok: response.ok,
2810
+ status: response.status,
2811
+ json: async () => await response.json()
2812
+ })),
2813
+ apiHost: this.client.apiHost,
2814
+ peerId,
2815
+ multiaddrs: probeMultiaddrs,
2816
+ browserMultiaddrs: browserBootstrapMultiaddrs,
2817
+ profile,
2818
+ version: this.state.manifest?.version,
2819
+ sync: true
2820
+ });
2821
+ this.trace("deploy:bootstrap-registration", {
2822
+ itemHash: args.itemHash,
2823
+ peerId,
2824
+ metadata,
2825
+ publication
2826
+ });
2827
+ }
2342
2828
  syncLatestDeploymentProgress(instances) {
2343
2829
  const itemHash = this.state.lastDeploymentHash;
2344
2830
  if (!itemHash) {
@@ -2947,6 +3433,10 @@ var SponsorRelayController = class {
2947
3433
  execution: runtime.execution ?? null,
2948
3434
  error: null
2949
3435
  });
3436
+ await this.configureRelayBootstrapRegistration({
3437
+ itemHash: result.itemHash,
3438
+ runtime
3439
+ });
2950
3440
  this.patch({
2951
3441
  busy: { deploying: false }
2952
3442
  });
package/shared/index.d.ts CHANGED
@@ -172,7 +172,7 @@ interface RootfsResolution {
172
172
  gatewayError?: string | null;
173
173
  }
174
174
 
175
- type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
175
+ type DeploymentProgressStage = "idle" | "validating" | "loading-manifest" | "verifying-rootfs" | "loading-pricing" | "loading-balance" | "selecting-crn" | "building-message" | "signing-message" | "broadcasting" | "notifying-crn" | "waiting-for-aleph" | "deployment-confirmed" | "deployment-rejected" | "publishing-bootstrap" | "building-delete-message" | "signing-delete-message" | "broadcasting-delete" | "delete-completed" | "refreshing-instances" | "completed" | "error";
176
176
  interface DeploymentProgressEvent {
177
177
  stage: DeploymentProgressStage;
178
178
  label: string;
@@ -356,6 +356,7 @@ declare class SponsorRelayController {
356
356
  private trace;
357
357
  private patch;
358
358
  private emitProgress;
359
+ private configureRelayBootstrapRegistration;
359
360
  private syncLatestDeploymentProgress;
360
361
  private canSkipRuntimeRefresh;
361
362
  private noteRuntimeRefreshResult;
package/shared/index.js CHANGED
@@ -1831,6 +1831,28 @@ async function waitForVmRuntime(args) {
1831
1831
  }
1832
1832
 
1833
1833
  // ../core/src/guest.ts
1834
+ async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1835
+ try {
1836
+ const controller = new AbortController();
1837
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
1838
+ const response = await fetch2(url, {
1839
+ method: "GET",
1840
+ redirect: "follow",
1841
+ signal: controller.signal
1842
+ });
1843
+ clearTimeout(timeout);
1844
+ return {
1845
+ ok: response.ok,
1846
+ status: response.status,
1847
+ url: response.url
1848
+ };
1849
+ } catch (error) {
1850
+ return {
1851
+ ok: false,
1852
+ error: error instanceof Error ? error.message : String(error)
1853
+ };
1854
+ }
1855
+ }
1834
1856
  async function notifyCrnAllocation2(args) {
1835
1857
  const normalizedCrnUrl = typeof args.crnUrl === "string" ? args.crnUrl.trim().replace(/\/+$/, "") : "";
1836
1858
  if (!normalizedCrnUrl) {
@@ -1911,6 +1933,324 @@ async function notifyCrnAllocationWithRetry(args) {
1911
1933
  reason: "CRN allocation notify was not confirmed."
1912
1934
  };
1913
1935
  }
1936
+ async function waitForSetupEndpoint(args) {
1937
+ const attempts = Math.max(1, Number(args.attempts ?? 15));
1938
+ const delayMs = Math.max(0, Number(args.delayMs ?? 4e3));
1939
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1940
+ const url = `http://${args.hostIpv4}:${args.setupPort}/health`;
1941
+ let result = await defaultHttpProbe(
1942
+ args.fetch,
1943
+ url,
1944
+ Number(args.httpTimeoutMs ?? 1e4)
1945
+ );
1946
+ args.onAttempt?.(result, 1, attempts);
1947
+ for (let attempt = 1; attempt < attempts; attempt += 1) {
1948
+ if (result.ok) return result;
1949
+ await sleep(delayMs);
1950
+ result = await defaultHttpProbe(
1951
+ args.fetch,
1952
+ url,
1953
+ Number(args.httpTimeoutMs ?? 1e4)
1954
+ );
1955
+ args.onAttempt?.(result, attempt + 1, attempts);
1956
+ }
1957
+ return result;
1958
+ }
1959
+ async function configureUcGoPeer(args) {
1960
+ const controller = new AbortController();
1961
+ const timeout = setTimeout(
1962
+ () => controller.abort(),
1963
+ Number(args.timeoutMs ?? 18e4)
1964
+ );
1965
+ const payload = {
1966
+ public_ipv4: args.hostIpv4,
1967
+ public_ipv6: args.publicIpv6 ?? void 0,
1968
+ proxy_url: args.proxyUrl ?? void 0,
1969
+ tcp_port: args.tcpPort ?? void 0,
1970
+ ws_port: args.wsPort ?? void 0,
1971
+ udp_port: args.udpPort ?? void 0,
1972
+ quic_port: args.quicPort ?? void 0,
1973
+ webrtc_port: args.webrtcPort ?? void 0
1974
+ };
1975
+ try {
1976
+ const response = await args.fetch(
1977
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
1978
+ {
1979
+ method: "POST",
1980
+ headers: {
1981
+ "content-type": "application/json"
1982
+ },
1983
+ body: JSON.stringify(payload),
1984
+ signal: controller.signal
1985
+ }
1986
+ );
1987
+ const responsePayload = await response.json().catch(() => null);
1988
+ if (!response.ok) {
1989
+ throw new Error(
1990
+ `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
1991
+ );
1992
+ }
1993
+ return responsePayload;
1994
+ } finally {
1995
+ clearTimeout(timeout);
1996
+ }
1997
+ }
1998
+ async function configureOrbitdbRelaySetup2(args) {
1999
+ const controller = new AbortController();
2000
+ const timeout = setTimeout(
2001
+ () => controller.abort(),
2002
+ Number(args.timeoutMs ?? 18e4)
2003
+ );
2004
+ const payload = {
2005
+ public_ipv4: args.hostIpv4,
2006
+ public_ipv6: args.publicIpv6 ?? void 0,
2007
+ tcp_port: args.tcpPort,
2008
+ ws_port: args.wsPort,
2009
+ proxy_url: args.proxyUrl ?? void 0,
2010
+ metrics_port: args.metricsPort ?? void 0,
2011
+ metrics_https_port: args.metricsHttpsPort ?? void 0,
2012
+ webrtc_port: args.webrtcPort ?? void 0,
2013
+ quic_port: args.quicPort ?? void 0
2014
+ };
2015
+ try {
2016
+ const response = await args.fetch(
2017
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
2018
+ {
2019
+ method: "POST",
2020
+ headers: {
2021
+ "content-type": "application/json"
2022
+ },
2023
+ body: JSON.stringify(payload),
2024
+ signal: controller.signal
2025
+ }
2026
+ );
2027
+ const responsePayload = await response.json().catch(() => null);
2028
+ if (!response.ok) {
2029
+ throw new Error(
2030
+ `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2031
+ );
2032
+ }
2033
+ return responsePayload;
2034
+ } finally {
2035
+ clearTimeout(timeout);
2036
+ }
2037
+ }
2038
+ async function fetchUcGoPeerMetadata(args) {
2039
+ const attempts = Math.max(1, Number(args.attempts ?? 60));
2040
+ const delayMs = Math.max(0, Number(args.delayMs ?? 3e3));
2041
+ const sleep = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2042
+ let lastPayload = null;
2043
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
2044
+ const controller = new AbortController();
2045
+ const timeout = setTimeout(
2046
+ () => controller.abort(),
2047
+ Number(args.timeoutMs ?? 18e4)
2048
+ );
2049
+ try {
2050
+ const response = await args.fetch(
2051
+ `http://${args.hostIpv4}:${args.setupPort}/metadata`,
2052
+ {
2053
+ signal: controller.signal
2054
+ }
2055
+ );
2056
+ const payload = await response.json().catch(() => null);
2057
+ lastPayload = payload;
2058
+ args.onAttempt?.(
2059
+ payload,
2060
+ Boolean(
2061
+ response.ok && payload && typeof payload === "object" && payload.status === "ready"
2062
+ ),
2063
+ attempt + 1,
2064
+ attempts
2065
+ );
2066
+ if (response.ok && payload && typeof payload === "object" && payload.status === "ready") {
2067
+ return payload;
2068
+ }
2069
+ if (response.status >= 500) {
2070
+ throw new Error(
2071
+ `Relay metadata request failed: ${response.status} ${typeof payload === "string" ? payload : JSON.stringify(payload ?? {})}`
2072
+ );
2073
+ }
2074
+ } finally {
2075
+ clearTimeout(timeout);
2076
+ }
2077
+ if (attempt < attempts - 1) {
2078
+ await sleep(delayMs);
2079
+ }
2080
+ }
2081
+ throw new Error(
2082
+ `Relay metadata did not become ready after ${attempts} attempts: ${typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2083
+ );
2084
+ }
2085
+
2086
+ // ../aleph-bootstrap/src/index.ts
2087
+ var DEFAULT_ALEPH_API_HOST3 = "https://api2.aleph.im";
2088
+ var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
2089
+ var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
2090
+ var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
2091
+ var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2092
+ function normalizeHost(host) {
2093
+ return host.trim().toLowerCase();
2094
+ }
2095
+ function splitMultiaddr(addr) {
2096
+ return addr.split("/").filter(Boolean);
2097
+ }
2098
+ function isPrivateIpv4(ip) {
2099
+ const parts = ip.split(".").map((part) => Number(part));
2100
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
2101
+ return false;
2102
+ }
2103
+ if (parts[0] === 10) return true;
2104
+ if (parts[0] === 127) return true;
2105
+ if (parts[0] === 0) return true;
2106
+ if (parts[0] === 169 && parts[1] === 254) return true;
2107
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
2108
+ if (parts[0] === 192 && parts[1] === 168) return true;
2109
+ if (parts[0] === 100 && parts[1] >= 64 && parts[1] <= 127) return true;
2110
+ return false;
2111
+ }
2112
+ function isPrivateIpv6(ip) {
2113
+ const normalized = ip.trim().toLowerCase();
2114
+ return normalized === "::1" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
2115
+ }
2116
+ function isLocalHostname(host) {
2117
+ const normalized = normalizeHost(host);
2118
+ return normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local");
2119
+ }
2120
+ function hasPeerId(addr) {
2121
+ return splitMultiaddr(addr).includes("p2p");
2122
+ }
2123
+ function isBrowserDialableMultiaddr(addr) {
2124
+ const normalized = addr.toLowerCase();
2125
+ return normalized.includes("/ws") || normalized.includes("/wss") || normalized.includes("/webtransport") || normalized.includes("/webrtc-direct");
2126
+ }
2127
+ function dedupeMultiaddrs(addrs) {
2128
+ const seen = /* @__PURE__ */ new Set();
2129
+ const result = [];
2130
+ for (const value of addrs) {
2131
+ const trimmed = value.trim();
2132
+ if (!trimmed || seen.has(trimmed)) continue;
2133
+ seen.add(trimmed);
2134
+ result.push(trimmed);
2135
+ }
2136
+ return result;
2137
+ }
2138
+ function isPublicMultiaddr(addr) {
2139
+ const parts = splitMultiaddr(addr);
2140
+ for (let index = 0; index < parts.length; index += 1) {
2141
+ const protocol = parts[index];
2142
+ const value = parts[index + 1];
2143
+ if (protocol === "ip4" && value) {
2144
+ return !isPrivateIpv4(value);
2145
+ }
2146
+ if (protocol === "ip6" && value) {
2147
+ return !isPrivateIpv6(value);
2148
+ }
2149
+ if ((protocol === "dns4" || protocol === "dns6" || protocol === "dnsaddr") && value) {
2150
+ return !isLocalHostname(value);
2151
+ }
2152
+ }
2153
+ return false;
2154
+ }
2155
+ function filterPublicMultiaddrs(addrs, options = {}) {
2156
+ return dedupeMultiaddrs(addrs).filter((addr) => {
2157
+ if (!isPublicMultiaddr(addr)) return false;
2158
+ if (options.requirePeerId !== false && !hasPeerId(addr)) return false;
2159
+ if (options.browserDialableOnly && !isBrowserDialableMultiaddr(addr)) {
2160
+ return false;
2161
+ }
2162
+ return true;
2163
+ });
2164
+ }
2165
+ function buildRelayBootstrapPostContent(args) {
2166
+ const now = args.now ?? Date.now() / 1e3;
2167
+ const updatedAt = args.now ?? Date.now();
2168
+ return {
2169
+ type: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2170
+ address: args.sender,
2171
+ ...args.ref ? { ref: args.ref } : {},
2172
+ content: {
2173
+ peerId: args.peerId,
2174
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2175
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2176
+ browserDialableOnly: true
2177
+ }) : void 0,
2178
+ profile: args.profile,
2179
+ version: args.version,
2180
+ updatedAt: Math.round(updatedAt)
2181
+ },
2182
+ time: now
2183
+ };
2184
+ }
2185
+ async function createRelayBootstrapPost(args) {
2186
+ const nowMillis = args.now ?? Date.now();
2187
+ const nowSeconds = nowMillis / 1e3;
2188
+ const itemContent = buildRelayBootstrapPostContent({
2189
+ sender: args.sender,
2190
+ peerId: args.peerId,
2191
+ multiaddrs: args.multiaddrs,
2192
+ browserMultiaddrs: args.browserMultiaddrs,
2193
+ ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
2194
+ postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2195
+ profile: args.profile,
2196
+ version: args.version,
2197
+ now: nowMillis
2198
+ });
2199
+ itemContent.time = nowSeconds;
2200
+ const serialized = JSON.stringify(itemContent);
2201
+ const itemHash = await args.hasher(serialized);
2202
+ return {
2203
+ channel: args.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL,
2204
+ sender: args.sender,
2205
+ chain: "ETH",
2206
+ type: "POST",
2207
+ time: nowSeconds,
2208
+ item_type: "inline",
2209
+ item_content: serialized,
2210
+ item_hash: itemHash
2211
+ };
2212
+ }
2213
+
2214
+ // ../core/src/bootstrap-registration.ts
2215
+ async function publishRelayBootstrapRegistration(args) {
2216
+ const unsigned = await createRelayBootstrapPost({
2217
+ sender: args.sender,
2218
+ peerId: args.peerId,
2219
+ multiaddrs: args.multiaddrs,
2220
+ browserMultiaddrs: args.browserMultiaddrs,
2221
+ channel: args.channel,
2222
+ ref: args.ref,
2223
+ postType: args.postType,
2224
+ profile: args.profile,
2225
+ version: args.version,
2226
+ now: args.now,
2227
+ hasher: args.hasher
2228
+ });
2229
+ const content = JSON.parse(unsigned.item_content);
2230
+ if ((content.content?.multiaddrs?.length ?? 0) === 0) {
2231
+ return {
2232
+ status: "skipped",
2233
+ reason: "No public relay multiaddrs remained after filtering.",
2234
+ publishedMultiaddrs: [],
2235
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2236
+ };
2237
+ }
2238
+ const message = await signAlephMessage(unsigned, args.signer);
2239
+ const { response, httpStatus } = await broadcastAlephMessage2(message, {
2240
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2241
+ sync: args.sync ?? true,
2242
+ fetch: args.fetch
2243
+ });
2244
+ return {
2245
+ status: "published",
2246
+ itemHash: unsigned.item_hash,
2247
+ message,
2248
+ response,
2249
+ httpStatus,
2250
+ publishedMultiaddrs: content.content?.multiaddrs ?? [],
2251
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2252
+ };
2253
+ }
1914
2254
 
1915
2255
  // src/shared/events.ts
1916
2256
  function createDeploymentProgressEmitter() {
@@ -2069,6 +2409,13 @@ async function resolveManifest(args) {
2069
2409
  if (pasted) return pasted;
2070
2410
  return loadRootfsManifest(args.manifestUrl);
2071
2411
  }
2412
+ function relayProfileForManifest(manifest) {
2413
+ if (manifest?.profile === "uc-go-peer") return "uc-go-peer";
2414
+ if (manifest?.profile === "orbitdb-relay-pinner") {
2415
+ return "orbitdb-relay-pinner";
2416
+ }
2417
+ return null;
2418
+ }
2072
2419
  async function inspectInstanceRuntime(args) {
2073
2420
  const details = {
2074
2421
  messageStatus: String(
@@ -2338,6 +2685,145 @@ var SponsorRelayController = class {
2338
2685
  });
2339
2686
  this.progressEmitter.emit(nextEvent);
2340
2687
  }
2688
+ async configureRelayBootstrapRegistration(args) {
2689
+ const profile = relayProfileForManifest(this.state.manifest);
2690
+ if (!profile) return;
2691
+ if (!this.state.wallet.address) {
2692
+ throw new Error("A connected wallet is required to register relay bootstrap addresses.");
2693
+ }
2694
+ if (!args.runtime.hostIpv4) {
2695
+ throw new Error("Relay runtime did not expose a host IPv4 address.");
2696
+ }
2697
+ const mappedPorts2 = args.runtime.mappedPorts ?? {};
2698
+ const setupPort = mappedPorts2["80"]?.host ?? null;
2699
+ if (!setupPort) {
2700
+ throw new Error("Relay runtime did not expose the temporary setup endpoint.");
2701
+ }
2702
+ this.emitProgress({
2703
+ stage: "deployment-confirmed",
2704
+ label: "Configuring relay",
2705
+ progress: 93,
2706
+ status: "info",
2707
+ itemHash: args.itemHash,
2708
+ detail: "Waiting for the guest setup endpoint before collecting relay metadata."
2709
+ });
2710
+ const setupHealth = await waitForSetupEndpoint({
2711
+ hostIpv4: args.runtime.hostIpv4,
2712
+ setupPort,
2713
+ fetch: (url, init) => fetch(url, init),
2714
+ attempts: 15,
2715
+ delayMs: 4e3,
2716
+ httpTimeoutMs: 1e4
2717
+ });
2718
+ if (!setupHealth.ok) {
2719
+ throw new Error(
2720
+ `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
2721
+ );
2722
+ }
2723
+ this.emitProgress({
2724
+ stage: "deployment-confirmed",
2725
+ label: "Applying relay networking",
2726
+ progress: 94,
2727
+ status: "info",
2728
+ itemHash: args.itemHash,
2729
+ detail: "Publishing the host port mapping into the guest relay configuration."
2730
+ });
2731
+ if (profile === "orbitdb-relay-pinner") {
2732
+ const tcpPort = mappedPorts2["9091"]?.host ?? 0;
2733
+ const wsPort = mappedPorts2["9092"]?.host ?? mappedPorts2["443"]?.host ?? 0;
2734
+ if (!tcpPort || !wsPort) {
2735
+ throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
2736
+ }
2737
+ await configureOrbitdbRelaySetup2({
2738
+ hostIpv4: args.runtime.hostIpv4,
2739
+ publicIpv6: args.runtime.ipv6,
2740
+ setupPort,
2741
+ tcpPort,
2742
+ wsPort,
2743
+ proxyUrl: args.runtime.proxyUrl ?? void 0,
2744
+ metricsPort: mappedPorts2["9090"]?.host ?? null,
2745
+ metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
2746
+ webrtcPort: mappedPorts2["9093"]?.host ?? null,
2747
+ quicPort: mappedPorts2["9094"]?.host ?? null,
2748
+ fetch: (url, init) => fetch(url, init),
2749
+ timeoutMs: 18e4
2750
+ });
2751
+ } else {
2752
+ await configureUcGoPeer({
2753
+ hostIpv4: args.runtime.hostIpv4,
2754
+ publicIpv6: args.runtime.ipv6,
2755
+ setupPort,
2756
+ tcpPort: mappedPorts2["9095"]?.host ?? null,
2757
+ wsPort: mappedPorts2["9097"]?.host ?? mappedPorts2["9095"]?.host ?? null,
2758
+ udpPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
2759
+ quicPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
2760
+ webrtcPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
2761
+ proxyUrl: args.runtime.proxyUrl ?? void 0,
2762
+ fetch: (url, init) => fetch(url, init),
2763
+ timeoutMs: 18e4
2764
+ });
2765
+ }
2766
+ this.emitProgress({
2767
+ stage: "publishing-bootstrap",
2768
+ label: "Reading relay multiaddrs",
2769
+ progress: 95,
2770
+ status: "info",
2771
+ itemHash: args.itemHash,
2772
+ detail: "Waiting for the relay to report its final public multiaddrs."
2773
+ });
2774
+ const metadataResult = await fetchUcGoPeerMetadata({
2775
+ hostIpv4: args.runtime.hostIpv4,
2776
+ setupPort,
2777
+ fetch: (url, init) => fetch(url, init),
2778
+ attempts: 80,
2779
+ delayMs: 3e3,
2780
+ timeoutMs: 24e4
2781
+ });
2782
+ const metadata = metadataResult && typeof metadataResult === "object" && metadataResult.metadata && typeof metadataResult.metadata === "object" ? metadataResult.metadata : null;
2783
+ const peerId = typeof metadata?.peer_id === "string" ? metadata.peer_id : null;
2784
+ const probeMultiaddrs = Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.filter(
2785
+ (entry) => typeof entry === "string"
2786
+ ) : [];
2787
+ const browserBootstrapMultiaddrs = Array.isArray(
2788
+ metadata?.browser_bootstrap_multiaddrs
2789
+ ) ? metadata.browser_bootstrap_multiaddrs.filter(
2790
+ (entry) => typeof entry === "string"
2791
+ ) : [];
2792
+ if (!peerId || probeMultiaddrs.length === 0) {
2793
+ throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
2794
+ }
2795
+ this.emitProgress({
2796
+ stage: "publishing-bootstrap",
2797
+ label: "Publishing bootstrap registration",
2798
+ progress: 96,
2799
+ status: "info",
2800
+ itemHash: args.itemHash,
2801
+ detail: "Writing the relay bootstrap registration to Aleph."
2802
+ });
2803
+ const publication = await publishRelayBootstrapRegistration({
2804
+ sender: this.state.wallet.address,
2805
+ signer: personalSign,
2806
+ hasher: sha256Hex,
2807
+ fetch: (url, init) => fetch(url, init).then(async (response) => ({
2808
+ ok: response.ok,
2809
+ status: response.status,
2810
+ json: async () => await response.json()
2811
+ })),
2812
+ apiHost: this.client.apiHost,
2813
+ peerId,
2814
+ multiaddrs: probeMultiaddrs,
2815
+ browserMultiaddrs: browserBootstrapMultiaddrs,
2816
+ profile,
2817
+ version: this.state.manifest?.version,
2818
+ sync: true
2819
+ });
2820
+ this.trace("deploy:bootstrap-registration", {
2821
+ itemHash: args.itemHash,
2822
+ peerId,
2823
+ metadata,
2824
+ publication
2825
+ });
2826
+ }
2341
2827
  syncLatestDeploymentProgress(instances) {
2342
2828
  const itemHash = this.state.lastDeploymentHash;
2343
2829
  if (!itemHash) {
@@ -2946,6 +3432,10 @@ var SponsorRelayController = class {
2946
3432
  execution: runtime.execution ?? null,
2947
3433
  error: null
2948
3434
  });
3435
+ await this.configureRelayBootstrapRegistration({
3436
+ itemHash: result.itemHash,
3437
+ runtime
3438
+ });
2949
3439
  this.patch({
2950
3440
  busy: { deploying: false }
2951
3441
  });