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