@le-space/ui 0.6.23 → 0.6.25
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/README.md +1 -1
- package/package.json +4 -4
- package/react/index.d.ts +2 -1
- package/react/index.js +161 -81
- package/shared/index.d.ts +3 -2
- package/shared/index.js +115 -78
- package/svelte/SponsorRelayFab.svelte +46 -3
- package/svelte/components/LauncherButton.svelte +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Floating Aleph relay deployment UI components for Svelte and React.
|
|
|
4
4
|
|
|
5
5
|
Primary goals:
|
|
6
6
|
|
|
7
|
-
- Svelte-first floating `
|
|
7
|
+
- Svelte-first floating `Relay Button` panel
|
|
8
8
|
- lightweight React wrapper
|
|
9
9
|
- shared controller and browser-safe Aleph helpers
|
|
10
10
|
- minimal host configuration beyond a rootfs manifest URL
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/ui",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Floating Aleph
|
|
3
|
+
"version": "0.6.25",
|
|
4
|
+
"description": "Floating Aleph Relay Button UI components for Svelte and React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"url": "https://github.com/NiKrause/relay-button/issues"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@le-space/browser": "0.6.
|
|
48
|
-
"@le-space/core": "0.6.
|
|
47
|
+
"@le-space/browser": "0.6.25",
|
|
48
|
+
"@le-space/core": "0.6.25",
|
|
49
49
|
"viem": "^2.53.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
package/react/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
|
-
type MessageStatus = 'processed' | 'pending' | 'rejected' | 'unknown';
|
|
3
|
+
type MessageStatus = 'processed' | 'pending' | 'rejected' | 'removing' | 'removed' | 'unknown';
|
|
4
4
|
interface BalanceResponse {
|
|
5
5
|
address: string;
|
|
6
6
|
balance: string;
|
|
@@ -235,6 +235,7 @@ interface SponsorRelayRootfsHealth {
|
|
|
235
235
|
tone: SponsorRelayHealthTone;
|
|
236
236
|
label: string;
|
|
237
237
|
detail: string | null;
|
|
238
|
+
code?: 'manifest-invalid' | 'rootfs-not-found' | 'rootfs-verifying' | 'rootfs-pending' | 'rootfs-unavailable' | 'rootfs-ready';
|
|
238
239
|
}
|
|
239
240
|
interface CompactInstanceDetails {
|
|
240
241
|
messageStatus: string;
|
package/react/index.js
CHANGED
|
@@ -43,7 +43,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
|
43
43
|
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
44
44
|
|
|
45
45
|
// src/shared/package-version.ts
|
|
46
|
-
var UI_PACKAGE_VERSION = "0.6.
|
|
46
|
+
var UI_PACKAGE_VERSION = "0.6.25";
|
|
47
47
|
|
|
48
48
|
// src/shared/format.ts
|
|
49
49
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -647,7 +647,7 @@ async function verifyRootfsExists(itemHash, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
|
647
647
|
function normalizeStatus(status) {
|
|
648
648
|
if (typeof status !== "string") return "unknown";
|
|
649
649
|
const normalized = status.toLowerCase();
|
|
650
|
-
if (normalized === "processed" || normalized === "pending" || normalized === "rejected") {
|
|
650
|
+
if (normalized === "processed" || normalized === "pending" || normalized === "rejected" || normalized === "removing" || normalized === "removed") {
|
|
651
651
|
return normalized;
|
|
652
652
|
}
|
|
653
653
|
return "unknown";
|
|
@@ -672,6 +672,7 @@ function parseCidFromPayload(payload) {
|
|
|
672
672
|
}
|
|
673
673
|
function parseRejectionReason(payload) {
|
|
674
674
|
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
675
|
+
const reason = typeof payload.reason === "string" ? payload.reason.trim() : "";
|
|
675
676
|
const details = payload.details && typeof payload.details === "object" ? payload.details : null;
|
|
676
677
|
const rawErrors = Array.isArray(details?.errors) ? details.errors : [];
|
|
677
678
|
const firstError = rawErrors[0] && typeof rawErrors[0] === "object" ? rawErrors[0] : null;
|
|
@@ -686,6 +687,18 @@ function parseRejectionReason(payload) {
|
|
|
686
687
|
};
|
|
687
688
|
}
|
|
688
689
|
}
|
|
690
|
+
if (reason === "balance_insufficient") {
|
|
691
|
+
return {
|
|
692
|
+
rejectionErrorCode: errorCode,
|
|
693
|
+
rejectionReason: "The rootfs STORE is being removed because its publisher no longer has enough Aleph credits. Your connected MetaMask balance may be sufficient, but this manifest must point to a newly published, processed rootfs STORE."
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
if (reason) {
|
|
697
|
+
return {
|
|
698
|
+
rejectionErrorCode: errorCode,
|
|
699
|
+
rejectionReason: `Aleph marked the rootfs STORE as unavailable: ${reason.replaceAll("_", " ")}.`
|
|
700
|
+
};
|
|
701
|
+
}
|
|
689
702
|
return {
|
|
690
703
|
rejectionErrorCode: errorCode,
|
|
691
704
|
rejectionReason: errorCode != null ? `Rejected by Aleph (error code ${errorCode}).` : null
|
|
@@ -727,11 +740,12 @@ async function resolveRootfsReference(itemHash, apiHost = DEFAULT_ALEPH_API_HOST
|
|
|
727
740
|
const firstMessage = Array.isArray(payload.messages) && payload.messages[0] && typeof payload.messages[0] === "object" ? payload.messages[0] : null;
|
|
728
741
|
const messageObject = payload.message && typeof payload.message === "object" ? payload.message : null;
|
|
729
742
|
const cid = parseCidFromPayload(payload);
|
|
730
|
-
const
|
|
743
|
+
const messageStatus = normalizeStatus(payload.status);
|
|
744
|
+
const rejection = messageStatus === "processed" || messageStatus === "pending" ? { rejectionErrorCode: null, rejectionReason: null } : parseRejectionReason(payload);
|
|
731
745
|
const gateway = cid ? await probeGateway(cid, gatewayBaseUrl) : { gatewayUrl: null, gatewayStatus: "unknown", gatewayError: null };
|
|
732
746
|
return {
|
|
733
747
|
itemHash,
|
|
734
|
-
messageStatus
|
|
748
|
+
messageStatus,
|
|
735
749
|
messageType: String(payload.type || messageObject?.type || firstMessage?.type || "").toUpperCase() || null,
|
|
736
750
|
cid,
|
|
737
751
|
receptionTime: typeof payload.reception_time === "string" ? payload.reception_time : null,
|
|
@@ -2472,8 +2486,7 @@ import { keccak256, recoverMessageAddress, stringToHex } from "viem";
|
|
|
2472
2486
|
var DEFAULT_ALEPH_API_HOST3 = "https://api.aleph.im";
|
|
2473
2487
|
var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
|
|
2474
2488
|
var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
|
|
2475
|
-
var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
|
|
2476
|
-
var DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE = "relay-bootstrap-v2";
|
|
2489
|
+
var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap-v2";
|
|
2477
2490
|
var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2478
2491
|
var DEFAULT_BOOTSTRAP_PAGINATION = 50;
|
|
2479
2492
|
var DEFAULT_BOOTSTRAP_COMPACT_MULTIADDR_LIMIT = 3;
|
|
@@ -2507,9 +2520,6 @@ function serializeRelayProofPayload(payload) {
|
|
|
2507
2520
|
function relayBootstrapMultiaddrsHash(addrs) {
|
|
2508
2521
|
return keccak256(stringToHex(JSON.stringify(dedupeMultiaddrs(addrs))));
|
|
2509
2522
|
}
|
|
2510
|
-
function compactPostType(postType) {
|
|
2511
|
-
return (postType ?? DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE) === DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2512
|
-
}
|
|
2513
2523
|
function browserMultiaddrPreference(addr) {
|
|
2514
2524
|
const normalized = addr.toLowerCase();
|
|
2515
2525
|
if (normalized.includes("/tcp/443/tls/ws/")) return 0;
|
|
@@ -2739,8 +2749,6 @@ function normalizeRelayBootstrapPostRecord(value) {
|
|
|
2739
2749
|
function buildRelayBootstrapPostContent(args) {
|
|
2740
2750
|
const now = args.now ?? Date.now() / 1e3;
|
|
2741
2751
|
const updatedAt = args.now ?? Date.now();
|
|
2742
|
-
const postType = args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE;
|
|
2743
|
-
const compact = args.compact ?? compactPostType(postType);
|
|
2744
2752
|
const fullMultiaddrs = filterPublicMultiaddrs(args.multiaddrs);
|
|
2745
2753
|
const fullBrowserMultiaddrs = args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
|
|
2746
2754
|
browserDialableOnly: true
|
|
@@ -2750,13 +2758,13 @@ function buildRelayBootstrapPostContent(args) {
|
|
|
2750
2758
|
args.compactMultiaddrLimit
|
|
2751
2759
|
);
|
|
2752
2760
|
return {
|
|
2753
|
-
type:
|
|
2761
|
+
type: DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
|
|
2754
2762
|
address: args.sender,
|
|
2755
2763
|
...args.ref ? { ref: args.ref } : {},
|
|
2756
2764
|
content: {
|
|
2757
2765
|
peerId: args.peerId,
|
|
2758
|
-
multiaddrs:
|
|
2759
|
-
browserMultiaddrs:
|
|
2766
|
+
multiaddrs: compactMultiaddrs,
|
|
2767
|
+
browserMultiaddrs: void 0,
|
|
2760
2768
|
registrationId: args.registrationId,
|
|
2761
2769
|
profile: args.profile,
|
|
2762
2770
|
version: args.version,
|
|
@@ -2779,14 +2787,12 @@ async function createRelayBootstrapPost(args) {
|
|
|
2779
2787
|
browserMultiaddrs: args.browserMultiaddrs,
|
|
2780
2788
|
registrationId: args.registrationId,
|
|
2781
2789
|
ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
|
|
2782
|
-
postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
|
|
2783
2790
|
profile: args.profile,
|
|
2784
2791
|
version: args.version,
|
|
2785
2792
|
ownerAddress: args.ownerAddress,
|
|
2786
2793
|
publisherAddress: args.publisherAddress,
|
|
2787
2794
|
authorization: args.authorization,
|
|
2788
2795
|
relayProof: args.relayProof,
|
|
2789
|
-
compact: args.compact,
|
|
2790
2796
|
compactMultiaddrLimit: args.compactMultiaddrLimit,
|
|
2791
2797
|
now: nowMillis
|
|
2792
2798
|
});
|
|
@@ -2833,12 +2839,11 @@ async function signRelayBootstrapProof(args) {
|
|
|
2833
2839
|
fullBrowserMultiaddrs && fullBrowserMultiaddrs.length > 0 ? fullBrowserMultiaddrs : fullMultiaddrs,
|
|
2834
2840
|
args.compactMultiaddrLimit
|
|
2835
2841
|
);
|
|
2836
|
-
const compact = args.compact ?? false;
|
|
2837
2842
|
const payload = {
|
|
2838
2843
|
peerId: args.peerId,
|
|
2839
|
-
multiaddrs:
|
|
2840
|
-
browserMultiaddrs:
|
|
2841
|
-
multiaddrsHash:
|
|
2844
|
+
multiaddrs: void 0,
|
|
2845
|
+
browserMultiaddrs: void 0,
|
|
2846
|
+
multiaddrsHash: relayBootstrapMultiaddrsHash(compactMultiaddrs),
|
|
2842
2847
|
registrationId: args.registrationId,
|
|
2843
2848
|
profile: args.profile,
|
|
2844
2849
|
version: args.version,
|
|
@@ -2868,10 +2873,7 @@ async function fetchAlephBootstrapPosts(options = {}) {
|
|
|
2868
2873
|
options.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL
|
|
2869
2874
|
);
|
|
2870
2875
|
url.searchParams.set("refs", options.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF);
|
|
2871
|
-
url.searchParams.set(
|
|
2872
|
-
"types",
|
|
2873
|
-
options.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE
|
|
2874
|
-
);
|
|
2876
|
+
url.searchParams.set("types", DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE);
|
|
2875
2877
|
url.searchParams.set(
|
|
2876
2878
|
"pagination",
|
|
2877
2879
|
String(options.pagination ?? DEFAULT_BOOTSTRAP_PAGINATION)
|
|
@@ -2885,7 +2887,21 @@ async function fetchAlephBootstrapPosts(options = {}) {
|
|
|
2885
2887
|
throw new Error(`Aleph bootstrap post lookup failed: ${response.status}`);
|
|
2886
2888
|
}
|
|
2887
2889
|
const payload = await response.json();
|
|
2888
|
-
|
|
2890
|
+
const posts = (payload.posts ?? []).map((entry) => normalizeRelayBootstrapPostRecord(entry)).filter((entry) => entry != null).sort((left, right) => compareRelayBootstrapPostRecency(right, left));
|
|
2891
|
+
posts.forEach(assertSupportedRelayBootstrapPost);
|
|
2892
|
+
return posts;
|
|
2893
|
+
}
|
|
2894
|
+
function assertSupportedRelayBootstrapPost(post) {
|
|
2895
|
+
if (post.type === "relay-bootstrap") {
|
|
2896
|
+
throw new Error(
|
|
2897
|
+
"Legacy relay-bootstrap record encountered. Only relay-bootstrap-v2 is supported."
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
if (post.type && post.type !== DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE) {
|
|
2901
|
+
throw new Error(
|
|
2902
|
+
`Unsupported relay bootstrap post type: ${post.type}. Expected ${DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE}.`
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2889
2905
|
}
|
|
2890
2906
|
function compareRelayBootstrapPostRecency(left, right) {
|
|
2891
2907
|
const leftUpdatedAt = left.content?.updatedAt ?? 0;
|
|
@@ -2907,6 +2923,7 @@ function selectCurrentRelayBootstrapPosts(posts, options = {}) {
|
|
|
2907
2923
|
const now = options.now ?? Date.now();
|
|
2908
2924
|
const selected = /* @__PURE__ */ new Map();
|
|
2909
2925
|
for (const post of posts) {
|
|
2926
|
+
assertSupportedRelayBootstrapPost(post);
|
|
2910
2927
|
const content = post.content;
|
|
2911
2928
|
if (!content) continue;
|
|
2912
2929
|
if (now - content.updatedAt > maxAgeMs) continue;
|
|
@@ -2932,14 +2949,12 @@ async function waitForRelayBootstrapRegistration(args) {
|
|
|
2932
2949
|
const expectedSender = args.sender?.trim().toLowerCase() || null;
|
|
2933
2950
|
const expectedRegistrationId = args.registrationId?.trim() || null;
|
|
2934
2951
|
const expectedPeerId = args.peerId?.trim() || null;
|
|
2935
|
-
const postType = args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2936
2952
|
const sleepImpl = args.sleep ?? sleep;
|
|
2937
2953
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2938
2954
|
const posts = await fetchAlephBootstrapPosts({
|
|
2939
2955
|
apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
|
|
2940
2956
|
channel: args.channel,
|
|
2941
2957
|
ref: args.ref,
|
|
2942
|
-
postType,
|
|
2943
2958
|
fetch: args.fetch
|
|
2944
2959
|
});
|
|
2945
2960
|
const currentPosts = selectCurrentRelayBootstrapPosts(posts);
|
|
@@ -2978,8 +2993,6 @@ async function waitForRelayBootstrapRegistration(args) {
|
|
|
2978
2993
|
async function publishRelayBootstrapRegistration(args) {
|
|
2979
2994
|
const publisherAddress = args.publisherAddress ?? args.sender;
|
|
2980
2995
|
const ownerAddress = args.ownerAddress;
|
|
2981
|
-
const postType = args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2982
|
-
const compact = postType === DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2983
2996
|
let ownerAuthorization = args.ownerAuthorization;
|
|
2984
2997
|
let relayProof = args.relayProof;
|
|
2985
2998
|
if (!ownerAuthorization && ownerAddress && args.ownerSigner) {
|
|
@@ -3006,7 +3019,6 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
3006
3019
|
profile: args.profile,
|
|
3007
3020
|
version: args.version,
|
|
3008
3021
|
updatedAt: args.now ?? Date.now(),
|
|
3009
|
-
compact,
|
|
3010
3022
|
compactMultiaddrLimit: args.compactMultiaddrLimit,
|
|
3011
3023
|
signer: args.publisherSigner
|
|
3012
3024
|
});
|
|
@@ -3019,14 +3031,12 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
3019
3031
|
registrationId: args.registrationId,
|
|
3020
3032
|
channel: args.channel,
|
|
3021
3033
|
ref: args.ref,
|
|
3022
|
-
postType,
|
|
3023
3034
|
profile: args.profile,
|
|
3024
3035
|
version: args.version,
|
|
3025
3036
|
ownerAddress,
|
|
3026
3037
|
publisherAddress,
|
|
3027
3038
|
authorization: ownerAuthorization,
|
|
3028
3039
|
relayProof,
|
|
3029
|
-
compact,
|
|
3030
3040
|
compactMultiaddrLimit: args.compactMultiaddrLimit,
|
|
3031
3041
|
now: args.now,
|
|
3032
3042
|
hasher: args.hasher
|
|
@@ -3049,18 +3059,12 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
3049
3059
|
let forgottenHashes = [];
|
|
3050
3060
|
let forgetResult;
|
|
3051
3061
|
if (args.forgetPrevious && args.registrationId) {
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
ref: args.ref,
|
|
3059
|
-
postType: previousPostType,
|
|
3060
|
-
fetch: args.fetch
|
|
3061
|
-
})
|
|
3062
|
-
)
|
|
3063
|
-
)).flat();
|
|
3062
|
+
const previousPosts = await fetchAlephBootstrapPosts({
|
|
3063
|
+
apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
|
|
3064
|
+
channel: args.channel,
|
|
3065
|
+
ref: args.ref,
|
|
3066
|
+
fetch: args.fetch
|
|
3067
|
+
});
|
|
3064
3068
|
const forgottenHashSet = new Set(
|
|
3065
3069
|
previousPosts.filter((post) => {
|
|
3066
3070
|
const hash = post.itemHash ?? post.hash;
|
|
@@ -3662,7 +3666,9 @@ function draftUcanStoreBootstrapErrors(args) {
|
|
|
3662
3666
|
const errors = [];
|
|
3663
3667
|
const { input } = args;
|
|
3664
3668
|
if (!args.operatorAddress) {
|
|
3665
|
-
errors.push(
|
|
3669
|
+
errors.push(
|
|
3670
|
+
"Connect MetaMask before configuring the ucan-store bootstrap package."
|
|
3671
|
+
);
|
|
3666
3672
|
}
|
|
3667
3673
|
if (!input.adminDid.trim().startsWith("did:")) {
|
|
3668
3674
|
errors.push("Admin DID must be a non-empty DID string.");
|
|
@@ -3713,7 +3719,9 @@ function buildUcanStoreBootstrapPackage(args) {
|
|
|
3713
3719
|
serviceDid: args.input.serviceDid.trim() || null,
|
|
3714
3720
|
spaceDid: args.input.spaceDid.trim(),
|
|
3715
3721
|
rootDelegationProof: args.input.rootDelegationProof.trim(),
|
|
3716
|
-
allowedCapabilities: parseAllowedCapabilities(
|
|
3722
|
+
allowedCapabilities: parseAllowedCapabilities(
|
|
3723
|
+
args.input.allowedCapabilities
|
|
3724
|
+
),
|
|
3717
3725
|
defaultUserDelegationExpiration: parseOptionalExpiration(
|
|
3718
3726
|
args.input.defaultUserDelegationExpiration
|
|
3719
3727
|
),
|
|
@@ -3752,7 +3760,9 @@ function defaultState(props = {}) {
|
|
|
3752
3760
|
instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
|
|
3753
3761
|
ucanStoreBootstrap: defaultUcanStoreBootstrapInput(props),
|
|
3754
3762
|
tierId: DEFAULT_TIER_ID,
|
|
3755
|
-
showAdvanced: Boolean(
|
|
3763
|
+
showAdvanced: Boolean(
|
|
3764
|
+
props.manifestJson?.trim() || props.sshPublicKey?.trim()
|
|
3765
|
+
),
|
|
3756
3766
|
showInstances: props.showInstances ?? true,
|
|
3757
3767
|
showPasteManifest: Boolean(props.manifestJson?.trim()),
|
|
3758
3768
|
busy: {
|
|
@@ -3836,48 +3846,55 @@ function rootfsHealth(args) {
|
|
|
3836
3846
|
return {
|
|
3837
3847
|
tone: "error",
|
|
3838
3848
|
label: "manifest invalid",
|
|
3839
|
-
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed."
|
|
3849
|
+
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed.",
|
|
3850
|
+
code: "manifest-invalid"
|
|
3840
3851
|
};
|
|
3841
3852
|
}
|
|
3842
3853
|
if (!args.rootfsVerified) {
|
|
3843
3854
|
return {
|
|
3844
3855
|
tone: "error",
|
|
3845
3856
|
label: "not found on Aleph",
|
|
3846
|
-
detail: "The manifest points to a rootfs STORE message that Aleph cannot find. Check that the manifest URL is correct and that the referenced rootfs was not forgotten."
|
|
3857
|
+
detail: "The manifest points to a rootfs STORE message that Aleph cannot find. Check that the manifest URL is correct and that the referenced rootfs was not forgotten.",
|
|
3858
|
+
code: "rootfs-not-found"
|
|
3847
3859
|
};
|
|
3848
3860
|
}
|
|
3849
3861
|
if (!args.resolution) {
|
|
3850
3862
|
return {
|
|
3851
3863
|
tone: "caution",
|
|
3852
3864
|
label: "verifying rootfs",
|
|
3853
|
-
detail: "The rootfs reference is still being resolved."
|
|
3865
|
+
detail: "The rootfs reference is still being resolved.",
|
|
3866
|
+
code: "rootfs-verifying"
|
|
3854
3867
|
};
|
|
3855
3868
|
}
|
|
3856
3869
|
if (args.resolution.messageStatus === "processed") {
|
|
3857
3870
|
return {
|
|
3858
3871
|
tone: "ok",
|
|
3859
3872
|
label: "deployable",
|
|
3860
|
-
detail: args.resolution.gatewayUrl ?? "Rootfs verified on Aleph."
|
|
3873
|
+
detail: args.resolution.gatewayUrl ?? "Rootfs verified on Aleph.",
|
|
3874
|
+
code: "rootfs-ready"
|
|
3861
3875
|
};
|
|
3862
3876
|
}
|
|
3863
3877
|
if (args.resolution.messageStatus === "pending" && args.resolution.gatewayStatus === "reachable") {
|
|
3864
3878
|
return {
|
|
3865
3879
|
tone: "caution",
|
|
3866
3880
|
label: "pending but reachable",
|
|
3867
|
-
detail: "Gateway probe succeeded even though Aleph still reports pending."
|
|
3881
|
+
detail: "Gateway probe succeeded even though Aleph still reports pending.",
|
|
3882
|
+
code: "rootfs-pending"
|
|
3868
3883
|
};
|
|
3869
3884
|
}
|
|
3870
3885
|
if (args.resolution.messageStatus === "pending") {
|
|
3871
3886
|
return {
|
|
3872
3887
|
tone: "caution",
|
|
3873
3888
|
label: "pending on Aleph",
|
|
3874
|
-
detail: "Wait until the STORE message is processed."
|
|
3889
|
+
detail: "Wait until the STORE message is processed.",
|
|
3890
|
+
code: "rootfs-pending"
|
|
3875
3891
|
};
|
|
3876
3892
|
}
|
|
3877
3893
|
return {
|
|
3878
3894
|
tone: "error",
|
|
3879
3895
|
label: "manifest rootfs not deployable",
|
|
3880
|
-
detail: args.resolution.rejectionReason ?? (args.resolution.messageType === "STORE" ? "This manifest points to a rootfs STORE message that exists but is not deployable anymore, for example because it was forgotten or replaced. Double-check the manifest URL." : "Aleph rejected the rootfs reference. Double-check that the manifest URL points to the intended current rootfs.")
|
|
3896
|
+
detail: args.resolution.rejectionReason ?? (args.resolution.messageType === "STORE" ? "This manifest points to a rootfs STORE message that exists but is not deployable anymore, for example because it was forgotten or replaced. Double-check the manifest URL." : "Aleph rejected the rootfs reference. Double-check that the manifest URL points to the intended current rootfs."),
|
|
3897
|
+
code: "rootfs-unavailable"
|
|
3881
3898
|
};
|
|
3882
3899
|
}
|
|
3883
3900
|
async function resolveManifest(args) {
|
|
@@ -4165,7 +4182,9 @@ var SponsorRelayController = class {
|
|
|
4165
4182
|
const profile = bootstrapRelayProfileForManifest(this.state.manifest);
|
|
4166
4183
|
if (!profile && deploymentProfile !== "ucan-store") return;
|
|
4167
4184
|
if (profile && !this.state.wallet.address) {
|
|
4168
|
-
throw new Error(
|
|
4185
|
+
throw new Error(
|
|
4186
|
+
"A connected wallet is required to register relay bootstrap addresses."
|
|
4187
|
+
);
|
|
4169
4188
|
}
|
|
4170
4189
|
const runtimeHostIpv4 = args.runtime.hostIpv4;
|
|
4171
4190
|
if (!runtimeHostIpv4) {
|
|
@@ -4189,7 +4208,9 @@ var SponsorRelayController = class {
|
|
|
4189
4208
|
}
|
|
4190
4209
|
const walletAddress2 = this.state.wallet.address;
|
|
4191
4210
|
if (!walletAddress2) {
|
|
4192
|
-
throw new Error(
|
|
4211
|
+
throw new Error(
|
|
4212
|
+
"A connected wallet is required to configure the ucan-store bootstrap package."
|
|
4213
|
+
);
|
|
4193
4214
|
}
|
|
4194
4215
|
const bootstrapValidation = buildUcanStoreBootstrapPackage({
|
|
4195
4216
|
input: this.state.ucanStoreBootstrap,
|
|
@@ -4304,7 +4325,9 @@ var SponsorRelayController = class {
|
|
|
4304
4325
|
if (!profile) return;
|
|
4305
4326
|
const walletAddress = this.state.wallet.address;
|
|
4306
4327
|
if (!walletAddress) {
|
|
4307
|
-
throw new Error(
|
|
4328
|
+
throw new Error(
|
|
4329
|
+
"A connected wallet is required to register relay bootstrap addresses."
|
|
4330
|
+
);
|
|
4308
4331
|
}
|
|
4309
4332
|
let bootstrapSignalRelayMetadata = null;
|
|
4310
4333
|
if (profile === "orbitdb-relay") {
|
|
@@ -4350,7 +4373,9 @@ var SponsorRelayController = class {
|
|
|
4350
4373
|
const tcpPort = mappedPorts2["9091"]?.host ?? 0;
|
|
4351
4374
|
const wsPort = mappedPorts2["9092"]?.host ?? mappedPorts2["443"]?.host ?? 0;
|
|
4352
4375
|
if (!tcpPort || !wsPort) {
|
|
4353
|
-
throw new Error(
|
|
4376
|
+
throw new Error(
|
|
4377
|
+
"OrbitDB relay runtime is missing required mapped TCP/WS ports."
|
|
4378
|
+
);
|
|
4354
4379
|
}
|
|
4355
4380
|
const configureResult = await configureOrbitdbRelaySetup2({
|
|
4356
4381
|
hostIpv4: runtimeHostIpv4,
|
|
@@ -4373,7 +4398,9 @@ var SponsorRelayController = class {
|
|
|
4373
4398
|
}
|
|
4374
4399
|
} else {
|
|
4375
4400
|
if (!args.deploymentToken) {
|
|
4376
|
-
throw new Error(
|
|
4401
|
+
throw new Error(
|
|
4402
|
+
"Missing deployment token for Aleph bootstrap config handoff."
|
|
4403
|
+
);
|
|
4377
4404
|
}
|
|
4378
4405
|
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
4379
4406
|
this.emitProgress({
|
|
@@ -4599,7 +4626,19 @@ var SponsorRelayController = class {
|
|
|
4599
4626
|
});
|
|
4600
4627
|
return;
|
|
4601
4628
|
}
|
|
4602
|
-
throw new Error(
|
|
4629
|
+
throw new Error(
|
|
4630
|
+
"Relay metadata did not include a peer ID and public multiaddrs."
|
|
4631
|
+
);
|
|
4632
|
+
}
|
|
4633
|
+
if (profile === "uc-go-peer") {
|
|
4634
|
+
const browserDialableMultiaddrs = relayMetadata.browserBootstrapMultiaddrs.filter(
|
|
4635
|
+
(multiaddr) => /\/(?:tls\/ws|wss)\/p2p\//.test(multiaddr) || /\/(?:webtransport|webrtc-direct)\//.test(multiaddr) && /\/certhash\//.test(multiaddr)
|
|
4636
|
+
);
|
|
4637
|
+
if (browserDialableMultiaddrs.length === 0) {
|
|
4638
|
+
throw new Error(
|
|
4639
|
+
"Relay metadata did not include a browser-dialable WSS address or a WebTransport/WebRTC Direct address with certhash."
|
|
4640
|
+
);
|
|
4641
|
+
}
|
|
4603
4642
|
}
|
|
4604
4643
|
const { peerId } = relayMetadata;
|
|
4605
4644
|
const registrationId = createRelayBootstrapRegistrationId(
|
|
@@ -5151,18 +5190,10 @@ var SponsorRelayController = class {
|
|
|
5151
5190
|
const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
|
|
5152
5191
|
this.client.fetchBalance(this.state.wallet.address),
|
|
5153
5192
|
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([]),
|
|
5154
|
-
bootstrapEnabled ?
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
].map(
|
|
5159
|
-
(postType) => fetchAlephBootstrapPosts({
|
|
5160
|
-
apiHost: this.client.apiHost,
|
|
5161
|
-
postType,
|
|
5162
|
-
fetch
|
|
5163
|
-
}).catch(() => [])
|
|
5164
|
-
)
|
|
5165
|
-
).then((pages) => pages.flat()) : Promise.resolve([])
|
|
5193
|
+
bootstrapEnabled ? fetchAlephBootstrapPosts({
|
|
5194
|
+
apiHost: this.client.apiHost,
|
|
5195
|
+
fetch
|
|
5196
|
+
}) : Promise.resolve([])
|
|
5166
5197
|
]);
|
|
5167
5198
|
balance = nextBalance;
|
|
5168
5199
|
instances = await Promise.all(
|
|
@@ -5226,7 +5257,9 @@ var SponsorRelayController = class {
|
|
|
5226
5257
|
const activeInstanceHashes = new Set(
|
|
5227
5258
|
instances.map((entry) => entry.instance.item_hash)
|
|
5228
5259
|
);
|
|
5229
|
-
bootstrapRegistrations = selectCurrentRelayBootstrapPosts(
|
|
5260
|
+
bootstrapRegistrations = selectCurrentRelayBootstrapPosts(
|
|
5261
|
+
rawBootstrapPosts
|
|
5262
|
+
).filter(
|
|
5230
5263
|
(entry) => relayBootstrapMatchesCurrentWallet(
|
|
5231
5264
|
entry,
|
|
5232
5265
|
normalizedWalletAddress,
|
|
@@ -5249,7 +5282,9 @@ var SponsorRelayController = class {
|
|
|
5249
5282
|
peerId: entry.content.peerId,
|
|
5250
5283
|
registrationId: entry.content.registrationId,
|
|
5251
5284
|
multiaddrs: Array.isArray(entry.content.multiaddrs) ? entry.content.multiaddrs : [],
|
|
5252
|
-
browserMultiaddrs: Array.isArray(
|
|
5285
|
+
browserMultiaddrs: Array.isArray(
|
|
5286
|
+
entry.content.browserMultiaddrs
|
|
5287
|
+
) ? entry.content.browserMultiaddrs : [],
|
|
5253
5288
|
ownerAddress: typeof entry.content.ownerAddress === "string" ? entry.content.ownerAddress : void 0,
|
|
5254
5289
|
publisherAddress: typeof entry.content.publisherAddress === "string" ? entry.content.publisherAddress : void 0,
|
|
5255
5290
|
updatedAt: entry.content.updatedAt
|
|
@@ -5370,7 +5405,9 @@ var SponsorRelayController = class {
|
|
|
5370
5405
|
...compatibleCrns2.filter((crn) => crn.hash !== preferredCrnHash)
|
|
5371
5406
|
].slice(0, 5);
|
|
5372
5407
|
if (orderedCrns.length === 0) {
|
|
5373
|
-
throw new Error(
|
|
5408
|
+
throw new Error(
|
|
5409
|
+
"No compatible CRN is currently available for this tier."
|
|
5410
|
+
);
|
|
5374
5411
|
}
|
|
5375
5412
|
const attemptErrors = [];
|
|
5376
5413
|
let lastError = null;
|
|
@@ -5803,7 +5840,7 @@ var SponsorRelayController = class {
|
|
|
5803
5840
|
await forgetAlephMessages({
|
|
5804
5841
|
sender: this.state.wallet.address,
|
|
5805
5842
|
hashes: [attemptItemHash],
|
|
5806
|
-
reason: `Discard failed
|
|
5843
|
+
reason: `Discard failed Relay Button deployment attempt on ${attemptLabel}`,
|
|
5807
5844
|
signer: personalSign,
|
|
5808
5845
|
hasher: sha256Hex,
|
|
5809
5846
|
fetch: (url, init) => fetch(url, init).then(async (response) => ({
|
|
@@ -5913,7 +5950,7 @@ var SponsorRelayController = class {
|
|
|
5913
5950
|
await forgetAlephMessages({
|
|
5914
5951
|
sender: this.state.wallet.address,
|
|
5915
5952
|
hashes: hashesToForget,
|
|
5916
|
-
reason: linkedRegistrationCount > 0 ? `Deleted instance and ${linkedRegistrationCount} linked relay bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"} from
|
|
5953
|
+
reason: linkedRegistrationCount > 0 ? `Deleted instance and ${linkedRegistrationCount} linked relay bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"} from Relay Button panel` : "Deleted from Relay Button panel",
|
|
5917
5954
|
signer: personalSign,
|
|
5918
5955
|
hasher: sha256Hex,
|
|
5919
5956
|
fetch: asJsonFetch,
|
|
@@ -5982,7 +6019,7 @@ var SponsorRelayController = class {
|
|
|
5982
6019
|
await forgetAlephMessages({
|
|
5983
6020
|
sender: this.state.wallet.address,
|
|
5984
6021
|
hashes: [registrationHash],
|
|
5985
|
-
reason: "Deleted orphan relay bootstrap registration from
|
|
6022
|
+
reason: "Deleted orphan relay bootstrap registration from Relay Button panel",
|
|
5986
6023
|
signer: personalSign,
|
|
5987
6024
|
hasher: sha256Hex,
|
|
5988
6025
|
fetch: asJsonFetch,
|
|
@@ -6187,7 +6224,7 @@ function supportsBootstrapUi(state) {
|
|
|
6187
6224
|
return stateDeploymentProfile(state) !== "ucan-store";
|
|
6188
6225
|
}
|
|
6189
6226
|
function deploymentPanelTitle(state) {
|
|
6190
|
-
return stateDeploymentProfile(state) === "ucan-store" ? "Sponsor Service" : "
|
|
6227
|
+
return stateDeploymentProfile(state) === "ucan-store" ? "Sponsor Service" : "Relay Button";
|
|
6191
6228
|
}
|
|
6192
6229
|
function deploymentLauncherLabel(state, compactInlineLabel, launcherMode) {
|
|
6193
6230
|
if (launcherMode === "inline" && compactInlineLabel) {
|
|
@@ -6397,7 +6434,8 @@ function SponsorRelayFab(props) {
|
|
|
6397
6434
|
maxHeight: "calc(100vh - 12.5rem)"
|
|
6398
6435
|
};
|
|
6399
6436
|
const launcherLabel = deploymentLauncherLabel(state, compactInlineLabel, launcherMode);
|
|
6400
|
-
const
|
|
6437
|
+
const rootfsBlocked = state.rootfsHealth.tone === "error";
|
|
6438
|
+
const deployCallToAction = state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : rootfsBlocked ? "Deployment blocked \u2014 Rootfs unavailable" : deploymentButtonLabel(state) : "Connect MetaMask";
|
|
6401
6439
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6402
6440
|
/* @__PURE__ */ jsx("style", { children: `@keyframes leSpaceRelayPulse { 0%, 100% { transform: scale(1); opacity: 0.74; } 50% { transform: scale(1.22); opacity: 1; } }` }),
|
|
6403
6441
|
/* @__PURE__ */ jsxs(
|
|
@@ -7082,13 +7120,55 @@ function SponsorRelayFab(props) {
|
|
|
7082
7120
|
)
|
|
7083
7121
|
] })
|
|
7084
7122
|
] }),
|
|
7123
|
+
rootfsBlocked ? /* @__PURE__ */ jsxs(
|
|
7124
|
+
"div",
|
|
7125
|
+
{
|
|
7126
|
+
role: "alert",
|
|
7127
|
+
id: "relay-rootfs-deployment-blocker",
|
|
7128
|
+
style: {
|
|
7129
|
+
display: "grid",
|
|
7130
|
+
gap: "0.55rem",
|
|
7131
|
+
marginTop: "1rem",
|
|
7132
|
+
padding: "0.9rem",
|
|
7133
|
+
borderRadius: "1rem",
|
|
7134
|
+
border: "1px solid rgba(248, 113, 113, 0.58)",
|
|
7135
|
+
background: "rgba(127, 29, 29, 0.34)",
|
|
7136
|
+
color: "#fee2e2"
|
|
7137
|
+
},
|
|
7138
|
+
children: [
|
|
7139
|
+
/* @__PURE__ */ jsx("strong", { children: "Rootfs unavailable \u2014 deployment blocked" }),
|
|
7140
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "0.82rem", lineHeight: 1.45 }, children: state.rootfsHealth.detail }),
|
|
7141
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: "0.75rem", lineHeight: 1.4 }, children: [
|
|
7142
|
+
"This is separate from the connected MetaMask balance. The manifest must reference an Aleph rootfs STORE with status ",
|
|
7143
|
+
/* @__PURE__ */ jsx("code", { children: "processed" }),
|
|
7144
|
+
"."
|
|
7145
|
+
] }),
|
|
7146
|
+
state.manifest?.rootfsItemHash ? /* @__PURE__ */ jsxs("code", { style: { fontSize: "0.7rem", overflowWrap: "anywhere" }, children: [
|
|
7147
|
+
"Rootfs: ",
|
|
7148
|
+
state.manifest.rootfsItemHash,
|
|
7149
|
+
state.rootfsResolution?.messageStatus ? ` \xB7 Aleph status: ${state.rootfsResolution.messageStatus}` : ""
|
|
7150
|
+
] }) : null,
|
|
7151
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.5rem" }, children: [
|
|
7152
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => void controller.refresh(), disabled: state.busy.refreshing, style: { ...secondaryButtonStyle, padding: "0.55rem 0.7rem" }, children: state.busy.refreshing ? "Checking\u2026" : "Retry validation" }),
|
|
7153
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => controller.setShowAdvanced(true), style: { ...secondaryButtonStyle, padding: "0.55rem 0.7rem" }, children: "Edit manifest URL" }),
|
|
7154
|
+
/^https?:\/\//.test(state.manifestUrl) ? /* @__PURE__ */ jsx("a", { href: state.manifestUrl, target: "_blank", rel: "noreferrer", style: { color: "#bfdbfe", alignSelf: "center", fontSize: "0.78rem" }, children: "Open manifest" }) : null
|
|
7155
|
+
] })
|
|
7156
|
+
]
|
|
7157
|
+
}
|
|
7158
|
+
) : null,
|
|
7085
7159
|
/* @__PURE__ */ jsx(
|
|
7086
7160
|
"button",
|
|
7087
7161
|
{
|
|
7088
7162
|
type: "button",
|
|
7089
7163
|
onClick: () => void (state.wallet.connected ? controller.deploy() : controller.connectWallet()),
|
|
7090
7164
|
disabled: state.wallet.connected ? state.busy.deploying || state.rootfsHealth.tone !== "ok" : state.busy.connectingWallet,
|
|
7091
|
-
|
|
7165
|
+
"aria-describedby": rootfsBlocked ? "relay-rootfs-deployment-blocker" : void 0,
|
|
7166
|
+
style: {
|
|
7167
|
+
...primaryButtonStyle,
|
|
7168
|
+
width: "100%",
|
|
7169
|
+
marginTop: "1rem",
|
|
7170
|
+
...rootfsBlocked ? { background: "rgba(148, 163, 184, 0.22)", borderColor: "rgba(248, 113, 113, 0.45)", color: "#fee2e2", boxShadow: "none", cursor: "not-allowed" } : {}
|
|
7171
|
+
},
|
|
7092
7172
|
children: deployCallToAction
|
|
7093
7173
|
}
|
|
7094
7174
|
),
|
package/shared/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type MessageStatus = 'processed' | 'pending' | 'rejected' | 'unknown';
|
|
1
|
+
type MessageStatus = 'processed' | 'pending' | 'rejected' | 'removing' | 'removed' | 'unknown';
|
|
2
2
|
interface BalanceResponse {
|
|
3
3
|
address: string;
|
|
4
4
|
balance: string;
|
|
@@ -233,6 +233,7 @@ interface SponsorRelayRootfsHealth {
|
|
|
233
233
|
tone: SponsorRelayHealthTone;
|
|
234
234
|
label: string;
|
|
235
235
|
detail: string | null;
|
|
236
|
+
code?: 'manifest-invalid' | 'rootfs-not-found' | 'rootfs-verifying' | 'rootfs-pending' | 'rootfs-unavailable' | 'rootfs-ready';
|
|
236
237
|
}
|
|
237
238
|
interface CompactInstanceDetails {
|
|
238
239
|
messageStatus: string;
|
|
@@ -331,7 +332,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
|
|
|
331
332
|
declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
|
|
332
333
|
declare const DEPLOYMENT_PENDING_WARNING_MS: number;
|
|
333
334
|
|
|
334
|
-
declare const UI_PACKAGE_VERSION = "0.6.
|
|
335
|
+
declare const UI_PACKAGE_VERSION = "0.6.25";
|
|
335
336
|
|
|
336
337
|
interface RootfsRequiredPortForward {
|
|
337
338
|
port: number;
|
package/shared/index.js
CHANGED
|
@@ -42,7 +42,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
|
|
|
42
42
|
var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
|
|
43
43
|
|
|
44
44
|
// src/shared/package-version.ts
|
|
45
|
-
var UI_PACKAGE_VERSION = "0.6.
|
|
45
|
+
var UI_PACKAGE_VERSION = "0.6.25";
|
|
46
46
|
|
|
47
47
|
// src/shared/format.ts
|
|
48
48
|
function shortHash(value, head = 8, tail = 6) {
|
|
@@ -646,7 +646,7 @@ async function verifyRootfsExists(itemHash, apiHost = DEFAULT_ALEPH_API_HOST) {
|
|
|
646
646
|
function normalizeStatus(status) {
|
|
647
647
|
if (typeof status !== "string") return "unknown";
|
|
648
648
|
const normalized = status.toLowerCase();
|
|
649
|
-
if (normalized === "processed" || normalized === "pending" || normalized === "rejected") {
|
|
649
|
+
if (normalized === "processed" || normalized === "pending" || normalized === "rejected" || normalized === "removing" || normalized === "removed") {
|
|
650
650
|
return normalized;
|
|
651
651
|
}
|
|
652
652
|
return "unknown";
|
|
@@ -671,6 +671,7 @@ function parseCidFromPayload(payload) {
|
|
|
671
671
|
}
|
|
672
672
|
function parseRejectionReason(payload) {
|
|
673
673
|
const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
|
|
674
|
+
const reason = typeof payload.reason === "string" ? payload.reason.trim() : "";
|
|
674
675
|
const details = payload.details && typeof payload.details === "object" ? payload.details : null;
|
|
675
676
|
const rawErrors = Array.isArray(details?.errors) ? details.errors : [];
|
|
676
677
|
const firstError = rawErrors[0] && typeof rawErrors[0] === "object" ? rawErrors[0] : null;
|
|
@@ -685,6 +686,18 @@ function parseRejectionReason(payload) {
|
|
|
685
686
|
};
|
|
686
687
|
}
|
|
687
688
|
}
|
|
689
|
+
if (reason === "balance_insufficient") {
|
|
690
|
+
return {
|
|
691
|
+
rejectionErrorCode: errorCode,
|
|
692
|
+
rejectionReason: "The rootfs STORE is being removed because its publisher no longer has enough Aleph credits. Your connected MetaMask balance may be sufficient, but this manifest must point to a newly published, processed rootfs STORE."
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
if (reason) {
|
|
696
|
+
return {
|
|
697
|
+
rejectionErrorCode: errorCode,
|
|
698
|
+
rejectionReason: `Aleph marked the rootfs STORE as unavailable: ${reason.replaceAll("_", " ")}.`
|
|
699
|
+
};
|
|
700
|
+
}
|
|
688
701
|
return {
|
|
689
702
|
rejectionErrorCode: errorCode,
|
|
690
703
|
rejectionReason: errorCode != null ? `Rejected by Aleph (error code ${errorCode}).` : null
|
|
@@ -726,11 +739,12 @@ async function resolveRootfsReference(itemHash, apiHost = DEFAULT_ALEPH_API_HOST
|
|
|
726
739
|
const firstMessage = Array.isArray(payload.messages) && payload.messages[0] && typeof payload.messages[0] === "object" ? payload.messages[0] : null;
|
|
727
740
|
const messageObject = payload.message && typeof payload.message === "object" ? payload.message : null;
|
|
728
741
|
const cid = parseCidFromPayload(payload);
|
|
729
|
-
const
|
|
742
|
+
const messageStatus = normalizeStatus(payload.status);
|
|
743
|
+
const rejection = messageStatus === "processed" || messageStatus === "pending" ? { rejectionErrorCode: null, rejectionReason: null } : parseRejectionReason(payload);
|
|
730
744
|
const gateway = cid ? await probeGateway(cid, gatewayBaseUrl) : { gatewayUrl: null, gatewayStatus: "unknown", gatewayError: null };
|
|
731
745
|
return {
|
|
732
746
|
itemHash,
|
|
733
|
-
messageStatus
|
|
747
|
+
messageStatus,
|
|
734
748
|
messageType: String(payload.type || messageObject?.type || firstMessage?.type || "").toUpperCase() || null,
|
|
735
749
|
cid,
|
|
736
750
|
receptionTime: typeof payload.reception_time === "string" ? payload.reception_time : null,
|
|
@@ -2471,8 +2485,7 @@ import { keccak256, recoverMessageAddress, stringToHex } from "viem";
|
|
|
2471
2485
|
var DEFAULT_ALEPH_API_HOST3 = "https://api.aleph.im";
|
|
2472
2486
|
var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
|
|
2473
2487
|
var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
|
|
2474
|
-
var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
|
|
2475
|
-
var DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE = "relay-bootstrap-v2";
|
|
2488
|
+
var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap-v2";
|
|
2476
2489
|
var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2477
2490
|
var DEFAULT_BOOTSTRAP_PAGINATION = 50;
|
|
2478
2491
|
var DEFAULT_BOOTSTRAP_COMPACT_MULTIADDR_LIMIT = 3;
|
|
@@ -2506,9 +2519,6 @@ function serializeRelayProofPayload(payload) {
|
|
|
2506
2519
|
function relayBootstrapMultiaddrsHash(addrs) {
|
|
2507
2520
|
return keccak256(stringToHex(JSON.stringify(dedupeMultiaddrs(addrs))));
|
|
2508
2521
|
}
|
|
2509
|
-
function compactPostType(postType) {
|
|
2510
|
-
return (postType ?? DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE) === DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2511
|
-
}
|
|
2512
2522
|
function browserMultiaddrPreference(addr) {
|
|
2513
2523
|
const normalized = addr.toLowerCase();
|
|
2514
2524
|
if (normalized.includes("/tcp/443/tls/ws/")) return 0;
|
|
@@ -2738,8 +2748,6 @@ function normalizeRelayBootstrapPostRecord(value) {
|
|
|
2738
2748
|
function buildRelayBootstrapPostContent(args) {
|
|
2739
2749
|
const now = args.now ?? Date.now() / 1e3;
|
|
2740
2750
|
const updatedAt = args.now ?? Date.now();
|
|
2741
|
-
const postType = args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE;
|
|
2742
|
-
const compact = args.compact ?? compactPostType(postType);
|
|
2743
2751
|
const fullMultiaddrs = filterPublicMultiaddrs(args.multiaddrs);
|
|
2744
2752
|
const fullBrowserMultiaddrs = args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
|
|
2745
2753
|
browserDialableOnly: true
|
|
@@ -2749,13 +2757,13 @@ function buildRelayBootstrapPostContent(args) {
|
|
|
2749
2757
|
args.compactMultiaddrLimit
|
|
2750
2758
|
);
|
|
2751
2759
|
return {
|
|
2752
|
-
type:
|
|
2760
|
+
type: DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
|
|
2753
2761
|
address: args.sender,
|
|
2754
2762
|
...args.ref ? { ref: args.ref } : {},
|
|
2755
2763
|
content: {
|
|
2756
2764
|
peerId: args.peerId,
|
|
2757
|
-
multiaddrs:
|
|
2758
|
-
browserMultiaddrs:
|
|
2765
|
+
multiaddrs: compactMultiaddrs,
|
|
2766
|
+
browserMultiaddrs: void 0,
|
|
2759
2767
|
registrationId: args.registrationId,
|
|
2760
2768
|
profile: args.profile,
|
|
2761
2769
|
version: args.version,
|
|
@@ -2778,14 +2786,12 @@ async function createRelayBootstrapPost(args) {
|
|
|
2778
2786
|
browserMultiaddrs: args.browserMultiaddrs,
|
|
2779
2787
|
registrationId: args.registrationId,
|
|
2780
2788
|
ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
|
|
2781
|
-
postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
|
|
2782
2789
|
profile: args.profile,
|
|
2783
2790
|
version: args.version,
|
|
2784
2791
|
ownerAddress: args.ownerAddress,
|
|
2785
2792
|
publisherAddress: args.publisherAddress,
|
|
2786
2793
|
authorization: args.authorization,
|
|
2787
2794
|
relayProof: args.relayProof,
|
|
2788
|
-
compact: args.compact,
|
|
2789
2795
|
compactMultiaddrLimit: args.compactMultiaddrLimit,
|
|
2790
2796
|
now: nowMillis
|
|
2791
2797
|
});
|
|
@@ -2832,12 +2838,11 @@ async function signRelayBootstrapProof(args) {
|
|
|
2832
2838
|
fullBrowserMultiaddrs && fullBrowserMultiaddrs.length > 0 ? fullBrowserMultiaddrs : fullMultiaddrs,
|
|
2833
2839
|
args.compactMultiaddrLimit
|
|
2834
2840
|
);
|
|
2835
|
-
const compact = args.compact ?? false;
|
|
2836
2841
|
const payload = {
|
|
2837
2842
|
peerId: args.peerId,
|
|
2838
|
-
multiaddrs:
|
|
2839
|
-
browserMultiaddrs:
|
|
2840
|
-
multiaddrsHash:
|
|
2843
|
+
multiaddrs: void 0,
|
|
2844
|
+
browserMultiaddrs: void 0,
|
|
2845
|
+
multiaddrsHash: relayBootstrapMultiaddrsHash(compactMultiaddrs),
|
|
2841
2846
|
registrationId: args.registrationId,
|
|
2842
2847
|
profile: args.profile,
|
|
2843
2848
|
version: args.version,
|
|
@@ -2867,10 +2872,7 @@ async function fetchAlephBootstrapPosts(options = {}) {
|
|
|
2867
2872
|
options.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL
|
|
2868
2873
|
);
|
|
2869
2874
|
url.searchParams.set("refs", options.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF);
|
|
2870
|
-
url.searchParams.set(
|
|
2871
|
-
"types",
|
|
2872
|
-
options.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE
|
|
2873
|
-
);
|
|
2875
|
+
url.searchParams.set("types", DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE);
|
|
2874
2876
|
url.searchParams.set(
|
|
2875
2877
|
"pagination",
|
|
2876
2878
|
String(options.pagination ?? DEFAULT_BOOTSTRAP_PAGINATION)
|
|
@@ -2884,7 +2886,21 @@ async function fetchAlephBootstrapPosts(options = {}) {
|
|
|
2884
2886
|
throw new Error(`Aleph bootstrap post lookup failed: ${response.status}`);
|
|
2885
2887
|
}
|
|
2886
2888
|
const payload = await response.json();
|
|
2887
|
-
|
|
2889
|
+
const posts = (payload.posts ?? []).map((entry) => normalizeRelayBootstrapPostRecord(entry)).filter((entry) => entry != null).sort((left, right) => compareRelayBootstrapPostRecency(right, left));
|
|
2890
|
+
posts.forEach(assertSupportedRelayBootstrapPost);
|
|
2891
|
+
return posts;
|
|
2892
|
+
}
|
|
2893
|
+
function assertSupportedRelayBootstrapPost(post) {
|
|
2894
|
+
if (post.type === "relay-bootstrap") {
|
|
2895
|
+
throw new Error(
|
|
2896
|
+
"Legacy relay-bootstrap record encountered. Only relay-bootstrap-v2 is supported."
|
|
2897
|
+
);
|
|
2898
|
+
}
|
|
2899
|
+
if (post.type && post.type !== DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE) {
|
|
2900
|
+
throw new Error(
|
|
2901
|
+
`Unsupported relay bootstrap post type: ${post.type}. Expected ${DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE}.`
|
|
2902
|
+
);
|
|
2903
|
+
}
|
|
2888
2904
|
}
|
|
2889
2905
|
function compareRelayBootstrapPostRecency(left, right) {
|
|
2890
2906
|
const leftUpdatedAt = left.content?.updatedAt ?? 0;
|
|
@@ -2906,6 +2922,7 @@ function selectCurrentRelayBootstrapPosts(posts, options = {}) {
|
|
|
2906
2922
|
const now = options.now ?? Date.now();
|
|
2907
2923
|
const selected = /* @__PURE__ */ new Map();
|
|
2908
2924
|
for (const post of posts) {
|
|
2925
|
+
assertSupportedRelayBootstrapPost(post);
|
|
2909
2926
|
const content = post.content;
|
|
2910
2927
|
if (!content) continue;
|
|
2911
2928
|
if (now - content.updatedAt > maxAgeMs) continue;
|
|
@@ -2931,14 +2948,12 @@ async function waitForRelayBootstrapRegistration(args) {
|
|
|
2931
2948
|
const expectedSender = args.sender?.trim().toLowerCase() || null;
|
|
2932
2949
|
const expectedRegistrationId = args.registrationId?.trim() || null;
|
|
2933
2950
|
const expectedPeerId = args.peerId?.trim() || null;
|
|
2934
|
-
const postType = args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2935
2951
|
const sleepImpl = args.sleep ?? sleep;
|
|
2936
2952
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
2937
2953
|
const posts = await fetchAlephBootstrapPosts({
|
|
2938
2954
|
apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
|
|
2939
2955
|
channel: args.channel,
|
|
2940
2956
|
ref: args.ref,
|
|
2941
|
-
postType,
|
|
2942
2957
|
fetch: args.fetch
|
|
2943
2958
|
});
|
|
2944
2959
|
const currentPosts = selectCurrentRelayBootstrapPosts(posts);
|
|
@@ -2977,8 +2992,6 @@ async function waitForRelayBootstrapRegistration(args) {
|
|
|
2977
2992
|
async function publishRelayBootstrapRegistration(args) {
|
|
2978
2993
|
const publisherAddress = args.publisherAddress ?? args.sender;
|
|
2979
2994
|
const ownerAddress = args.ownerAddress;
|
|
2980
|
-
const postType = args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2981
|
-
const compact = postType === DEFAULT_ALEPH_BOOTSTRAP_COMPACT_POST_TYPE;
|
|
2982
2995
|
let ownerAuthorization = args.ownerAuthorization;
|
|
2983
2996
|
let relayProof = args.relayProof;
|
|
2984
2997
|
if (!ownerAuthorization && ownerAddress && args.ownerSigner) {
|
|
@@ -3005,7 +3018,6 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
3005
3018
|
profile: args.profile,
|
|
3006
3019
|
version: args.version,
|
|
3007
3020
|
updatedAt: args.now ?? Date.now(),
|
|
3008
|
-
compact,
|
|
3009
3021
|
compactMultiaddrLimit: args.compactMultiaddrLimit,
|
|
3010
3022
|
signer: args.publisherSigner
|
|
3011
3023
|
});
|
|
@@ -3018,14 +3030,12 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
3018
3030
|
registrationId: args.registrationId,
|
|
3019
3031
|
channel: args.channel,
|
|
3020
3032
|
ref: args.ref,
|
|
3021
|
-
postType,
|
|
3022
3033
|
profile: args.profile,
|
|
3023
3034
|
version: args.version,
|
|
3024
3035
|
ownerAddress,
|
|
3025
3036
|
publisherAddress,
|
|
3026
3037
|
authorization: ownerAuthorization,
|
|
3027
3038
|
relayProof,
|
|
3028
|
-
compact,
|
|
3029
3039
|
compactMultiaddrLimit: args.compactMultiaddrLimit,
|
|
3030
3040
|
now: args.now,
|
|
3031
3041
|
hasher: args.hasher
|
|
@@ -3048,18 +3058,12 @@ async function publishRelayBootstrapRegistration(args) {
|
|
|
3048
3058
|
let forgottenHashes = [];
|
|
3049
3059
|
let forgetResult;
|
|
3050
3060
|
if (args.forgetPrevious && args.registrationId) {
|
|
3051
|
-
const
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
ref: args.ref,
|
|
3058
|
-
postType: previousPostType,
|
|
3059
|
-
fetch: args.fetch
|
|
3060
|
-
})
|
|
3061
|
-
)
|
|
3062
|
-
)).flat();
|
|
3061
|
+
const previousPosts = await fetchAlephBootstrapPosts({
|
|
3062
|
+
apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
|
|
3063
|
+
channel: args.channel,
|
|
3064
|
+
ref: args.ref,
|
|
3065
|
+
fetch: args.fetch
|
|
3066
|
+
});
|
|
3063
3067
|
const forgottenHashSet = new Set(
|
|
3064
3068
|
previousPosts.filter((post) => {
|
|
3065
3069
|
const hash = post.itemHash ?? post.hash;
|
|
@@ -3661,7 +3665,9 @@ function draftUcanStoreBootstrapErrors(args) {
|
|
|
3661
3665
|
const errors = [];
|
|
3662
3666
|
const { input } = args;
|
|
3663
3667
|
if (!args.operatorAddress) {
|
|
3664
|
-
errors.push(
|
|
3668
|
+
errors.push(
|
|
3669
|
+
"Connect MetaMask before configuring the ucan-store bootstrap package."
|
|
3670
|
+
);
|
|
3665
3671
|
}
|
|
3666
3672
|
if (!input.adminDid.trim().startsWith("did:")) {
|
|
3667
3673
|
errors.push("Admin DID must be a non-empty DID string.");
|
|
@@ -3712,7 +3718,9 @@ function buildUcanStoreBootstrapPackage(args) {
|
|
|
3712
3718
|
serviceDid: args.input.serviceDid.trim() || null,
|
|
3713
3719
|
spaceDid: args.input.spaceDid.trim(),
|
|
3714
3720
|
rootDelegationProof: args.input.rootDelegationProof.trim(),
|
|
3715
|
-
allowedCapabilities: parseAllowedCapabilities(
|
|
3721
|
+
allowedCapabilities: parseAllowedCapabilities(
|
|
3722
|
+
args.input.allowedCapabilities
|
|
3723
|
+
),
|
|
3716
3724
|
defaultUserDelegationExpiration: parseOptionalExpiration(
|
|
3717
3725
|
args.input.defaultUserDelegationExpiration
|
|
3718
3726
|
),
|
|
@@ -3751,7 +3759,9 @@ function defaultState(props = {}) {
|
|
|
3751
3759
|
instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
|
|
3752
3760
|
ucanStoreBootstrap: defaultUcanStoreBootstrapInput(props),
|
|
3753
3761
|
tierId: DEFAULT_TIER_ID,
|
|
3754
|
-
showAdvanced: Boolean(
|
|
3762
|
+
showAdvanced: Boolean(
|
|
3763
|
+
props.manifestJson?.trim() || props.sshPublicKey?.trim()
|
|
3764
|
+
),
|
|
3755
3765
|
showInstances: props.showInstances ?? true,
|
|
3756
3766
|
showPasteManifest: Boolean(props.manifestJson?.trim()),
|
|
3757
3767
|
busy: {
|
|
@@ -3835,48 +3845,55 @@ function rootfsHealth(args) {
|
|
|
3835
3845
|
return {
|
|
3836
3846
|
tone: "error",
|
|
3837
3847
|
label: "manifest invalid",
|
|
3838
|
-
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed."
|
|
3848
|
+
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed.",
|
|
3849
|
+
code: "manifest-invalid"
|
|
3839
3850
|
};
|
|
3840
3851
|
}
|
|
3841
3852
|
if (!args.rootfsVerified) {
|
|
3842
3853
|
return {
|
|
3843
3854
|
tone: "error",
|
|
3844
3855
|
label: "not found on Aleph",
|
|
3845
|
-
detail: "The manifest points to a rootfs STORE message that Aleph cannot find. Check that the manifest URL is correct and that the referenced rootfs was not forgotten."
|
|
3856
|
+
detail: "The manifest points to a rootfs STORE message that Aleph cannot find. Check that the manifest URL is correct and that the referenced rootfs was not forgotten.",
|
|
3857
|
+
code: "rootfs-not-found"
|
|
3846
3858
|
};
|
|
3847
3859
|
}
|
|
3848
3860
|
if (!args.resolution) {
|
|
3849
3861
|
return {
|
|
3850
3862
|
tone: "caution",
|
|
3851
3863
|
label: "verifying rootfs",
|
|
3852
|
-
detail: "The rootfs reference is still being resolved."
|
|
3864
|
+
detail: "The rootfs reference is still being resolved.",
|
|
3865
|
+
code: "rootfs-verifying"
|
|
3853
3866
|
};
|
|
3854
3867
|
}
|
|
3855
3868
|
if (args.resolution.messageStatus === "processed") {
|
|
3856
3869
|
return {
|
|
3857
3870
|
tone: "ok",
|
|
3858
3871
|
label: "deployable",
|
|
3859
|
-
detail: args.resolution.gatewayUrl ?? "Rootfs verified on Aleph."
|
|
3872
|
+
detail: args.resolution.gatewayUrl ?? "Rootfs verified on Aleph.",
|
|
3873
|
+
code: "rootfs-ready"
|
|
3860
3874
|
};
|
|
3861
3875
|
}
|
|
3862
3876
|
if (args.resolution.messageStatus === "pending" && args.resolution.gatewayStatus === "reachable") {
|
|
3863
3877
|
return {
|
|
3864
3878
|
tone: "caution",
|
|
3865
3879
|
label: "pending but reachable",
|
|
3866
|
-
detail: "Gateway probe succeeded even though Aleph still reports pending."
|
|
3880
|
+
detail: "Gateway probe succeeded even though Aleph still reports pending.",
|
|
3881
|
+
code: "rootfs-pending"
|
|
3867
3882
|
};
|
|
3868
3883
|
}
|
|
3869
3884
|
if (args.resolution.messageStatus === "pending") {
|
|
3870
3885
|
return {
|
|
3871
3886
|
tone: "caution",
|
|
3872
3887
|
label: "pending on Aleph",
|
|
3873
|
-
detail: "Wait until the STORE message is processed."
|
|
3888
|
+
detail: "Wait until the STORE message is processed.",
|
|
3889
|
+
code: "rootfs-pending"
|
|
3874
3890
|
};
|
|
3875
3891
|
}
|
|
3876
3892
|
return {
|
|
3877
3893
|
tone: "error",
|
|
3878
3894
|
label: "manifest rootfs not deployable",
|
|
3879
|
-
detail: args.resolution.rejectionReason ?? (args.resolution.messageType === "STORE" ? "This manifest points to a rootfs STORE message that exists but is not deployable anymore, for example because it was forgotten or replaced. Double-check the manifest URL." : "Aleph rejected the rootfs reference. Double-check that the manifest URL points to the intended current rootfs.")
|
|
3895
|
+
detail: args.resolution.rejectionReason ?? (args.resolution.messageType === "STORE" ? "This manifest points to a rootfs STORE message that exists but is not deployable anymore, for example because it was forgotten or replaced. Double-check the manifest URL." : "Aleph rejected the rootfs reference. Double-check that the manifest URL points to the intended current rootfs."),
|
|
3896
|
+
code: "rootfs-unavailable"
|
|
3880
3897
|
};
|
|
3881
3898
|
}
|
|
3882
3899
|
async function resolveManifest(args) {
|
|
@@ -4164,7 +4181,9 @@ var SponsorRelayController = class {
|
|
|
4164
4181
|
const profile = bootstrapRelayProfileForManifest(this.state.manifest);
|
|
4165
4182
|
if (!profile && deploymentProfile !== "ucan-store") return;
|
|
4166
4183
|
if (profile && !this.state.wallet.address) {
|
|
4167
|
-
throw new Error(
|
|
4184
|
+
throw new Error(
|
|
4185
|
+
"A connected wallet is required to register relay bootstrap addresses."
|
|
4186
|
+
);
|
|
4168
4187
|
}
|
|
4169
4188
|
const runtimeHostIpv4 = args.runtime.hostIpv4;
|
|
4170
4189
|
if (!runtimeHostIpv4) {
|
|
@@ -4188,7 +4207,9 @@ var SponsorRelayController = class {
|
|
|
4188
4207
|
}
|
|
4189
4208
|
const walletAddress2 = this.state.wallet.address;
|
|
4190
4209
|
if (!walletAddress2) {
|
|
4191
|
-
throw new Error(
|
|
4210
|
+
throw new Error(
|
|
4211
|
+
"A connected wallet is required to configure the ucan-store bootstrap package."
|
|
4212
|
+
);
|
|
4192
4213
|
}
|
|
4193
4214
|
const bootstrapValidation = buildUcanStoreBootstrapPackage({
|
|
4194
4215
|
input: this.state.ucanStoreBootstrap,
|
|
@@ -4303,7 +4324,9 @@ var SponsorRelayController = class {
|
|
|
4303
4324
|
if (!profile) return;
|
|
4304
4325
|
const walletAddress = this.state.wallet.address;
|
|
4305
4326
|
if (!walletAddress) {
|
|
4306
|
-
throw new Error(
|
|
4327
|
+
throw new Error(
|
|
4328
|
+
"A connected wallet is required to register relay bootstrap addresses."
|
|
4329
|
+
);
|
|
4307
4330
|
}
|
|
4308
4331
|
let bootstrapSignalRelayMetadata = null;
|
|
4309
4332
|
if (profile === "orbitdb-relay") {
|
|
@@ -4349,7 +4372,9 @@ var SponsorRelayController = class {
|
|
|
4349
4372
|
const tcpPort = mappedPorts2["9091"]?.host ?? 0;
|
|
4350
4373
|
const wsPort = mappedPorts2["9092"]?.host ?? mappedPorts2["443"]?.host ?? 0;
|
|
4351
4374
|
if (!tcpPort || !wsPort) {
|
|
4352
|
-
throw new Error(
|
|
4375
|
+
throw new Error(
|
|
4376
|
+
"OrbitDB relay runtime is missing required mapped TCP/WS ports."
|
|
4377
|
+
);
|
|
4353
4378
|
}
|
|
4354
4379
|
const configureResult = await configureOrbitdbRelaySetup2({
|
|
4355
4380
|
hostIpv4: runtimeHostIpv4,
|
|
@@ -4372,7 +4397,9 @@ var SponsorRelayController = class {
|
|
|
4372
4397
|
}
|
|
4373
4398
|
} else {
|
|
4374
4399
|
if (!args.deploymentToken) {
|
|
4375
|
-
throw new Error(
|
|
4400
|
+
throw new Error(
|
|
4401
|
+
"Missing deployment token for Aleph bootstrap config handoff."
|
|
4402
|
+
);
|
|
4376
4403
|
}
|
|
4377
4404
|
if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
|
|
4378
4405
|
this.emitProgress({
|
|
@@ -4598,7 +4625,19 @@ var SponsorRelayController = class {
|
|
|
4598
4625
|
});
|
|
4599
4626
|
return;
|
|
4600
4627
|
}
|
|
4601
|
-
throw new Error(
|
|
4628
|
+
throw new Error(
|
|
4629
|
+
"Relay metadata did not include a peer ID and public multiaddrs."
|
|
4630
|
+
);
|
|
4631
|
+
}
|
|
4632
|
+
if (profile === "uc-go-peer") {
|
|
4633
|
+
const browserDialableMultiaddrs = relayMetadata.browserBootstrapMultiaddrs.filter(
|
|
4634
|
+
(multiaddr) => /\/(?:tls\/ws|wss)\/p2p\//.test(multiaddr) || /\/(?:webtransport|webrtc-direct)\//.test(multiaddr) && /\/certhash\//.test(multiaddr)
|
|
4635
|
+
);
|
|
4636
|
+
if (browserDialableMultiaddrs.length === 0) {
|
|
4637
|
+
throw new Error(
|
|
4638
|
+
"Relay metadata did not include a browser-dialable WSS address or a WebTransport/WebRTC Direct address with certhash."
|
|
4639
|
+
);
|
|
4640
|
+
}
|
|
4602
4641
|
}
|
|
4603
4642
|
const { peerId } = relayMetadata;
|
|
4604
4643
|
const registrationId = createRelayBootstrapRegistrationId(
|
|
@@ -5150,18 +5189,10 @@ var SponsorRelayController = class {
|
|
|
5150
5189
|
const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
|
|
5151
5190
|
this.client.fetchBalance(this.state.wallet.address),
|
|
5152
5191
|
this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([]),
|
|
5153
|
-
bootstrapEnabled ?
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
].map(
|
|
5158
|
-
(postType) => fetchAlephBootstrapPosts({
|
|
5159
|
-
apiHost: this.client.apiHost,
|
|
5160
|
-
postType,
|
|
5161
|
-
fetch
|
|
5162
|
-
}).catch(() => [])
|
|
5163
|
-
)
|
|
5164
|
-
).then((pages) => pages.flat()) : Promise.resolve([])
|
|
5192
|
+
bootstrapEnabled ? fetchAlephBootstrapPosts({
|
|
5193
|
+
apiHost: this.client.apiHost,
|
|
5194
|
+
fetch
|
|
5195
|
+
}) : Promise.resolve([])
|
|
5165
5196
|
]);
|
|
5166
5197
|
balance = nextBalance;
|
|
5167
5198
|
instances = await Promise.all(
|
|
@@ -5225,7 +5256,9 @@ var SponsorRelayController = class {
|
|
|
5225
5256
|
const activeInstanceHashes = new Set(
|
|
5226
5257
|
instances.map((entry) => entry.instance.item_hash)
|
|
5227
5258
|
);
|
|
5228
|
-
bootstrapRegistrations = selectCurrentRelayBootstrapPosts(
|
|
5259
|
+
bootstrapRegistrations = selectCurrentRelayBootstrapPosts(
|
|
5260
|
+
rawBootstrapPosts
|
|
5261
|
+
).filter(
|
|
5229
5262
|
(entry) => relayBootstrapMatchesCurrentWallet(
|
|
5230
5263
|
entry,
|
|
5231
5264
|
normalizedWalletAddress,
|
|
@@ -5248,7 +5281,9 @@ var SponsorRelayController = class {
|
|
|
5248
5281
|
peerId: entry.content.peerId,
|
|
5249
5282
|
registrationId: entry.content.registrationId,
|
|
5250
5283
|
multiaddrs: Array.isArray(entry.content.multiaddrs) ? entry.content.multiaddrs : [],
|
|
5251
|
-
browserMultiaddrs: Array.isArray(
|
|
5284
|
+
browserMultiaddrs: Array.isArray(
|
|
5285
|
+
entry.content.browserMultiaddrs
|
|
5286
|
+
) ? entry.content.browserMultiaddrs : [],
|
|
5252
5287
|
ownerAddress: typeof entry.content.ownerAddress === "string" ? entry.content.ownerAddress : void 0,
|
|
5253
5288
|
publisherAddress: typeof entry.content.publisherAddress === "string" ? entry.content.publisherAddress : void 0,
|
|
5254
5289
|
updatedAt: entry.content.updatedAt
|
|
@@ -5369,7 +5404,9 @@ var SponsorRelayController = class {
|
|
|
5369
5404
|
...compatibleCrns2.filter((crn) => crn.hash !== preferredCrnHash)
|
|
5370
5405
|
].slice(0, 5);
|
|
5371
5406
|
if (orderedCrns.length === 0) {
|
|
5372
|
-
throw new Error(
|
|
5407
|
+
throw new Error(
|
|
5408
|
+
"No compatible CRN is currently available for this tier."
|
|
5409
|
+
);
|
|
5373
5410
|
}
|
|
5374
5411
|
const attemptErrors = [];
|
|
5375
5412
|
let lastError = null;
|
|
@@ -5802,7 +5839,7 @@ var SponsorRelayController = class {
|
|
|
5802
5839
|
await forgetAlephMessages({
|
|
5803
5840
|
sender: this.state.wallet.address,
|
|
5804
5841
|
hashes: [attemptItemHash],
|
|
5805
|
-
reason: `Discard failed
|
|
5842
|
+
reason: `Discard failed Relay Button deployment attempt on ${attemptLabel}`,
|
|
5806
5843
|
signer: personalSign,
|
|
5807
5844
|
hasher: sha256Hex,
|
|
5808
5845
|
fetch: (url, init) => fetch(url, init).then(async (response) => ({
|
|
@@ -5912,7 +5949,7 @@ var SponsorRelayController = class {
|
|
|
5912
5949
|
await forgetAlephMessages({
|
|
5913
5950
|
sender: this.state.wallet.address,
|
|
5914
5951
|
hashes: hashesToForget,
|
|
5915
|
-
reason: linkedRegistrationCount > 0 ? `Deleted instance and ${linkedRegistrationCount} linked relay bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"} from
|
|
5952
|
+
reason: linkedRegistrationCount > 0 ? `Deleted instance and ${linkedRegistrationCount} linked relay bootstrap registration${linkedRegistrationCount === 1 ? "" : "s"} from Relay Button panel` : "Deleted from Relay Button panel",
|
|
5916
5953
|
signer: personalSign,
|
|
5917
5954
|
hasher: sha256Hex,
|
|
5918
5955
|
fetch: asJsonFetch,
|
|
@@ -5981,7 +6018,7 @@ var SponsorRelayController = class {
|
|
|
5981
6018
|
await forgetAlephMessages({
|
|
5982
6019
|
sender: this.state.wallet.address,
|
|
5983
6020
|
hashes: [registrationHash],
|
|
5984
|
-
reason: "Deleted orphan relay bootstrap registration from
|
|
6021
|
+
reason: "Deleted orphan relay bootstrap registration from Relay Button panel",
|
|
5985
6022
|
signer: personalSign,
|
|
5986
6023
|
hasher: sha256Hex,
|
|
5987
6024
|
fetch: asJsonFetch,
|
|
@@ -51,7 +51,7 @@ export let apiHosts = undefined
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function deploymentTitle() {
|
|
54
|
-
return deploymentProfile() === 'ucan-store' ? '
|
|
54
|
+
return deploymentProfile() === 'ucan-store' ? 'Service Button' : 'Relay Button'
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function deploymentInstanceFallbackLabel() {
|
|
@@ -62,6 +62,12 @@ export let apiHosts = undefined
|
|
|
62
62
|
return deploymentProfile() === 'ucan-store' ? 'Deploy Service' : 'Deploy Relay'
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function deploymentCallToAction() {
|
|
66
|
+
if (state.busy.deploying) return 'Deploying…'
|
|
67
|
+
if (state.rootfsHealth.tone === 'error') return 'Deployment blocked — Rootfs unavailable'
|
|
68
|
+
return deploymentButtonLabel()
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
function bootstrapUiEnabled() {
|
|
66
72
|
return deploymentProfile() !== 'ucan-store'
|
|
67
73
|
}
|
|
@@ -264,10 +270,28 @@ export let apiHosts = undefined
|
|
|
264
270
|
</div>
|
|
265
271
|
</div>
|
|
266
272
|
|
|
273
|
+
{#if state.rootfsHealth.tone === 'error'}
|
|
274
|
+
<div class="rootfs-blocker" id="relay-rootfs-deployment-blocker" role="alert">
|
|
275
|
+
<strong>Rootfs unavailable — deployment blocked</strong>
|
|
276
|
+
<p>{state.rootfsHealth.detail}</p>
|
|
277
|
+
<small>This is separate from the connected MetaMask balance. The manifest must reference an Aleph rootfs STORE with status <code>processed</code>.</small>
|
|
278
|
+
{#if state.manifest?.rootfsItemHash}
|
|
279
|
+
<code class="rootfs-reference">Rootfs: {state.manifest.rootfsItemHash}{state.rootfsResolution?.messageStatus ? ` · Aleph status: ${state.rootfsResolution.messageStatus}` : ''}</code>
|
|
280
|
+
{/if}
|
|
281
|
+
<div class="rootfs-actions">
|
|
282
|
+
<button class="refresh" type="button" on:click={() => controller.refresh()} disabled={state.busy.refreshing}>{state.busy.refreshing ? 'Checking…' : 'Retry validation'}</button>
|
|
283
|
+
<button class="refresh" type="button" on:click={() => controller.setShowAdvanced(true)}>Edit manifest URL</button>
|
|
284
|
+
{#if /^https?:\/\//.test(state.manifestUrl)}
|
|
285
|
+
<a href={state.manifestUrl} target="_blank" rel="noreferrer">Open manifest</a>
|
|
286
|
+
{/if}
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
{/if}
|
|
290
|
+
|
|
267
291
|
<div class="actions">
|
|
268
292
|
{#if state.wallet.connected}
|
|
269
|
-
<button class="primary" type="button" on:click={() => controller.deploy()} disabled={state.busy.deploying || state.rootfsHealth.tone !== 'ok'}>
|
|
270
|
-
{
|
|
293
|
+
<button class:blocked={state.rootfsHealth.tone === 'error'} class="primary" type="button" aria-describedby={state.rootfsHealth.tone === 'error' ? 'relay-rootfs-deployment-blocker' : undefined} on:click={() => controller.deploy()} disabled={state.busy.deploying || state.rootfsHealth.tone !== 'ok'}>
|
|
294
|
+
{deploymentCallToAction()}
|
|
271
295
|
</button>
|
|
272
296
|
{:else}
|
|
273
297
|
<button class="primary" type="button" on:click={() => controller.connectWallet()} disabled={state.busy.connectingWallet}>
|
|
@@ -470,6 +494,25 @@ export let apiHosts = undefined
|
|
|
470
494
|
line-height: 1.35;
|
|
471
495
|
}
|
|
472
496
|
|
|
497
|
+
.rootfs-blocker {
|
|
498
|
+
display: grid;
|
|
499
|
+
gap: 0.55rem;
|
|
500
|
+
margin-top: 1rem;
|
|
501
|
+
padding: 0.9rem;
|
|
502
|
+
border: 1px solid rgba(248, 113, 113, 0.58);
|
|
503
|
+
border-radius: 1rem;
|
|
504
|
+
background: rgba(127, 29, 29, 0.34);
|
|
505
|
+
color: #fee2e2;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
.rootfs-blocker p,
|
|
509
|
+
.rootfs-blocker small { margin: 0; line-height: 1.45; }
|
|
510
|
+
.rootfs-reference { overflow-wrap: anywhere; font-size: 0.7rem; }
|
|
511
|
+
.rootfs-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
|
|
512
|
+
.rootfs-actions .refresh { padding: 0.55rem 0.7rem; }
|
|
513
|
+
.rootfs-actions a { color: #bfdbfe; font-size: 0.78rem; }
|
|
514
|
+
.primary.blocked { border-color: rgba(248, 113, 113, 0.45); background: rgba(148, 163, 184, 0.22); color: #fee2e2; box-shadow: none; cursor: not-allowed; }
|
|
515
|
+
|
|
473
516
|
.eyebrow,
|
|
474
517
|
.field span,
|
|
475
518
|
.metric-card span,
|