@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 +3 -3
- package/react/index.d.ts +2 -1
- package/react/index.js +490 -0
- package/shared/index.d.ts +2 -1
- package/shared/index.js +490 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/ui",
|
|
3
|
-
"version": "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.
|
|
40
|
-
"@le-space/core": "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
|
});
|