@le-space/ui 0.6.22 → 0.6.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/react/index.d.ts +2 -1
- package/react/index.js +77 -13
- package/shared/index.d.ts +9 -3
- package/shared/index.js +33 -11
- package/svelte/SponsorRelayFab.svelte +45 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@le-space/ui",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.24",
|
|
4
4
|
"description": "Floating Aleph sponsor relay UI components for Svelte and React.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -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.24",
|
|
48
|
+
"@le-space/core": "0.6.24",
|
|
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.24";
|
|
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,
|
|
@@ -3836,48 +3850,55 @@ function rootfsHealth(args) {
|
|
|
3836
3850
|
return {
|
|
3837
3851
|
tone: "error",
|
|
3838
3852
|
label: "manifest invalid",
|
|
3839
|
-
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed."
|
|
3853
|
+
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed.",
|
|
3854
|
+
code: "manifest-invalid"
|
|
3840
3855
|
};
|
|
3841
3856
|
}
|
|
3842
3857
|
if (!args.rootfsVerified) {
|
|
3843
3858
|
return {
|
|
3844
3859
|
tone: "error",
|
|
3845
3860
|
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."
|
|
3861
|
+
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.",
|
|
3862
|
+
code: "rootfs-not-found"
|
|
3847
3863
|
};
|
|
3848
3864
|
}
|
|
3849
3865
|
if (!args.resolution) {
|
|
3850
3866
|
return {
|
|
3851
3867
|
tone: "caution",
|
|
3852
3868
|
label: "verifying rootfs",
|
|
3853
|
-
detail: "The rootfs reference is still being resolved."
|
|
3869
|
+
detail: "The rootfs reference is still being resolved.",
|
|
3870
|
+
code: "rootfs-verifying"
|
|
3854
3871
|
};
|
|
3855
3872
|
}
|
|
3856
3873
|
if (args.resolution.messageStatus === "processed") {
|
|
3857
3874
|
return {
|
|
3858
3875
|
tone: "ok",
|
|
3859
3876
|
label: "deployable",
|
|
3860
|
-
detail: args.resolution.gatewayUrl
|
|
3877
|
+
detail: args.resolution.gatewayUrl ?? "Rootfs verified on Aleph.",
|
|
3878
|
+
code: "rootfs-ready"
|
|
3861
3879
|
};
|
|
3862
3880
|
}
|
|
3863
3881
|
if (args.resolution.messageStatus === "pending" && args.resolution.gatewayStatus === "reachable") {
|
|
3864
3882
|
return {
|
|
3865
3883
|
tone: "caution",
|
|
3866
3884
|
label: "pending but reachable",
|
|
3867
|
-
detail: "Gateway probe succeeded even though Aleph still reports pending."
|
|
3885
|
+
detail: "Gateway probe succeeded even though Aleph still reports pending.",
|
|
3886
|
+
code: "rootfs-pending"
|
|
3868
3887
|
};
|
|
3869
3888
|
}
|
|
3870
3889
|
if (args.resolution.messageStatus === "pending") {
|
|
3871
3890
|
return {
|
|
3872
3891
|
tone: "caution",
|
|
3873
3892
|
label: "pending on Aleph",
|
|
3874
|
-
detail: "Wait until the STORE message is processed."
|
|
3893
|
+
detail: "Wait until the STORE message is processed.",
|
|
3894
|
+
code: "rootfs-pending"
|
|
3875
3895
|
};
|
|
3876
3896
|
}
|
|
3877
3897
|
return {
|
|
3878
3898
|
tone: "error",
|
|
3879
3899
|
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.")
|
|
3900
|
+
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."),
|
|
3901
|
+
code: "rootfs-unavailable"
|
|
3881
3902
|
};
|
|
3882
3903
|
}
|
|
3883
3904
|
async function resolveManifest(args) {
|
|
@@ -6397,7 +6418,8 @@ function SponsorRelayFab(props) {
|
|
|
6397
6418
|
maxHeight: "calc(100vh - 12.5rem)"
|
|
6398
6419
|
};
|
|
6399
6420
|
const launcherLabel = deploymentLauncherLabel(state, compactInlineLabel, launcherMode);
|
|
6400
|
-
const
|
|
6421
|
+
const rootfsBlocked = state.rootfsHealth.tone === "error";
|
|
6422
|
+
const deployCallToAction = state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : rootfsBlocked ? "Deployment blocked \u2014 Rootfs unavailable" : deploymentButtonLabel(state) : "Connect MetaMask";
|
|
6401
6423
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
6402
6424
|
/* @__PURE__ */ jsx("style", { children: `@keyframes leSpaceRelayPulse { 0%, 100% { transform: scale(1); opacity: 0.74; } 50% { transform: scale(1.22); opacity: 1; } }` }),
|
|
6403
6425
|
/* @__PURE__ */ jsxs(
|
|
@@ -7082,13 +7104,55 @@ function SponsorRelayFab(props) {
|
|
|
7082
7104
|
)
|
|
7083
7105
|
] })
|
|
7084
7106
|
] }),
|
|
7107
|
+
rootfsBlocked ? /* @__PURE__ */ jsxs(
|
|
7108
|
+
"div",
|
|
7109
|
+
{
|
|
7110
|
+
role: "alert",
|
|
7111
|
+
id: "relay-rootfs-deployment-blocker",
|
|
7112
|
+
style: {
|
|
7113
|
+
display: "grid",
|
|
7114
|
+
gap: "0.55rem",
|
|
7115
|
+
marginTop: "1rem",
|
|
7116
|
+
padding: "0.9rem",
|
|
7117
|
+
borderRadius: "1rem",
|
|
7118
|
+
border: "1px solid rgba(248, 113, 113, 0.58)",
|
|
7119
|
+
background: "rgba(127, 29, 29, 0.34)",
|
|
7120
|
+
color: "#fee2e2"
|
|
7121
|
+
},
|
|
7122
|
+
children: [
|
|
7123
|
+
/* @__PURE__ */ jsx("strong", { children: "Rootfs unavailable \u2014 deployment blocked" }),
|
|
7124
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "0.82rem", lineHeight: 1.45 }, children: state.rootfsHealth.detail }),
|
|
7125
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: "0.75rem", lineHeight: 1.4 }, children: [
|
|
7126
|
+
"This is separate from the connected MetaMask balance. The manifest must reference an Aleph rootfs STORE with status ",
|
|
7127
|
+
/* @__PURE__ */ jsx("code", { children: "processed" }),
|
|
7128
|
+
"."
|
|
7129
|
+
] }),
|
|
7130
|
+
state.manifest?.rootfsItemHash ? /* @__PURE__ */ jsxs("code", { style: { fontSize: "0.7rem", overflowWrap: "anywhere" }, children: [
|
|
7131
|
+
"Rootfs: ",
|
|
7132
|
+
state.manifest.rootfsItemHash,
|
|
7133
|
+
state.rootfsResolution?.messageStatus ? ` \xB7 Aleph status: ${state.rootfsResolution.messageStatus}` : ""
|
|
7134
|
+
] }) : null,
|
|
7135
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.5rem" }, children: [
|
|
7136
|
+
/* @__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" }),
|
|
7137
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => controller.setShowAdvanced(true), style: { ...secondaryButtonStyle, padding: "0.55rem 0.7rem" }, children: "Edit manifest URL" }),
|
|
7138
|
+
/^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
|
|
7139
|
+
] })
|
|
7140
|
+
]
|
|
7141
|
+
}
|
|
7142
|
+
) : null,
|
|
7085
7143
|
/* @__PURE__ */ jsx(
|
|
7086
7144
|
"button",
|
|
7087
7145
|
{
|
|
7088
7146
|
type: "button",
|
|
7089
7147
|
onClick: () => void (state.wallet.connected ? controller.deploy() : controller.connectWallet()),
|
|
7090
7148
|
disabled: state.wallet.connected ? state.busy.deploying || state.rootfsHealth.tone !== "ok" : state.busy.connectingWallet,
|
|
7091
|
-
|
|
7149
|
+
"aria-describedby": rootfsBlocked ? "relay-rootfs-deployment-blocker" : void 0,
|
|
7150
|
+
style: {
|
|
7151
|
+
...primaryButtonStyle,
|
|
7152
|
+
width: "100%",
|
|
7153
|
+
marginTop: "1rem",
|
|
7154
|
+
...rootfsBlocked ? { background: "rgba(148, 163, 184, 0.22)", borderColor: "rgba(248, 113, 113, 0.45)", color: "#fee2e2", boxShadow: "none", cursor: "not-allowed" } : {}
|
|
7155
|
+
},
|
|
7092
7156
|
children: deployCallToAction
|
|
7093
7157
|
}
|
|
7094
7158
|
),
|
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.24";
|
|
335
336
|
|
|
336
337
|
interface RootfsRequiredPortForward {
|
|
337
338
|
port: number;
|
|
@@ -367,6 +368,11 @@ declare function connectWallet(provider?: EthereumProviderLike | null): Promise<
|
|
|
367
368
|
declare function personalSign(address: string, message: string, provider?: EthereumProviderLike | null): Promise<string>;
|
|
368
369
|
declare function watchWallet(onChange: () => void, provider?: EthereumProviderLike | null): () => void;
|
|
369
370
|
|
|
371
|
+
declare function rootfsHealth(args: {
|
|
372
|
+
manifestState: RootfsManifestState;
|
|
373
|
+
rootfsVerified: boolean;
|
|
374
|
+
resolution: SponsorRelayState["rootfsResolution"];
|
|
375
|
+
}): SponsorRelayRootfsHealth;
|
|
370
376
|
declare class SponsorRelayController {
|
|
371
377
|
private state;
|
|
372
378
|
private subscribers;
|
|
@@ -426,4 +432,4 @@ interface DeploymentProgressEmitter {
|
|
|
426
432
|
}
|
|
427
433
|
declare function createDeploymentProgressEmitter(): DeploymentProgressEmitter;
|
|
428
434
|
|
|
429
|
-
export { type CompactBootstrapRegistrationRecord, type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEFAULT_UCAN_STORE_ALLOWED_CAPABILITIES, DEFAULT_UCAN_STORE_MAX_DELEGATION_EXPIRATION_SECONDS, DEFAULT_UCAN_STORE_PWA_ORIGIN, DEFAULT_UCAN_STORE_SERVICE_DID, DEFAULT_UCAN_STORE_SERVICE_ORIGIN, DEFAULT_UCAN_STORE_USER_DELEGATION_EXPIRATION_SECONDS, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, ROOTFS_MISSING_STATE, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayUcanStoreBootstrapInput, type SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
|
|
435
|
+
export { type CompactBootstrapRegistrationRecord, type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEFAULT_UCAN_STORE_ALLOWED_CAPABILITIES, DEFAULT_UCAN_STORE_MAX_DELEGATION_EXPIRATION_SECONDS, DEFAULT_UCAN_STORE_PWA_ORIGIN, DEFAULT_UCAN_STORE_SERVICE_DID, DEFAULT_UCAN_STORE_SERVICE_ORIGIN, DEFAULT_UCAN_STORE_USER_DELEGATION_EXPIRATION_SECONDS, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, ROOTFS_MISSING_STATE, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayUcanStoreBootstrapInput, type SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, rootfsHealth, shortHash, toChecksumAddress, watchWallet };
|
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.24";
|
|
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,
|
|
@@ -3835,48 +3849,55 @@ function rootfsHealth(args) {
|
|
|
3835
3849
|
return {
|
|
3836
3850
|
tone: "error",
|
|
3837
3851
|
label: "manifest invalid",
|
|
3838
|
-
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed."
|
|
3852
|
+
detail: args.manifestState.errors[0] ?? "Manifest could not be parsed.",
|
|
3853
|
+
code: "manifest-invalid"
|
|
3839
3854
|
};
|
|
3840
3855
|
}
|
|
3841
3856
|
if (!args.rootfsVerified) {
|
|
3842
3857
|
return {
|
|
3843
3858
|
tone: "error",
|
|
3844
3859
|
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."
|
|
3860
|
+
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.",
|
|
3861
|
+
code: "rootfs-not-found"
|
|
3846
3862
|
};
|
|
3847
3863
|
}
|
|
3848
3864
|
if (!args.resolution) {
|
|
3849
3865
|
return {
|
|
3850
3866
|
tone: "caution",
|
|
3851
3867
|
label: "verifying rootfs",
|
|
3852
|
-
detail: "The rootfs reference is still being resolved."
|
|
3868
|
+
detail: "The rootfs reference is still being resolved.",
|
|
3869
|
+
code: "rootfs-verifying"
|
|
3853
3870
|
};
|
|
3854
3871
|
}
|
|
3855
3872
|
if (args.resolution.messageStatus === "processed") {
|
|
3856
3873
|
return {
|
|
3857
3874
|
tone: "ok",
|
|
3858
3875
|
label: "deployable",
|
|
3859
|
-
detail: args.resolution.gatewayUrl
|
|
3876
|
+
detail: args.resolution.gatewayUrl ?? "Rootfs verified on Aleph.",
|
|
3877
|
+
code: "rootfs-ready"
|
|
3860
3878
|
};
|
|
3861
3879
|
}
|
|
3862
3880
|
if (args.resolution.messageStatus === "pending" && args.resolution.gatewayStatus === "reachable") {
|
|
3863
3881
|
return {
|
|
3864
3882
|
tone: "caution",
|
|
3865
3883
|
label: "pending but reachable",
|
|
3866
|
-
detail: "Gateway probe succeeded even though Aleph still reports pending."
|
|
3884
|
+
detail: "Gateway probe succeeded even though Aleph still reports pending.",
|
|
3885
|
+
code: "rootfs-pending"
|
|
3867
3886
|
};
|
|
3868
3887
|
}
|
|
3869
3888
|
if (args.resolution.messageStatus === "pending") {
|
|
3870
3889
|
return {
|
|
3871
3890
|
tone: "caution",
|
|
3872
3891
|
label: "pending on Aleph",
|
|
3873
|
-
detail: "Wait until the STORE message is processed."
|
|
3892
|
+
detail: "Wait until the STORE message is processed.",
|
|
3893
|
+
code: "rootfs-pending"
|
|
3874
3894
|
};
|
|
3875
3895
|
}
|
|
3876
3896
|
return {
|
|
3877
3897
|
tone: "error",
|
|
3878
3898
|
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.")
|
|
3899
|
+
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."),
|
|
3900
|
+
code: "rootfs-unavailable"
|
|
3880
3901
|
};
|
|
3881
3902
|
}
|
|
3882
3903
|
async function resolveManifest(args) {
|
|
@@ -6060,6 +6081,7 @@ export {
|
|
|
6060
6081
|
joinRequiredPortForwards,
|
|
6061
6082
|
personalSign,
|
|
6062
6083
|
resolveManifestSource,
|
|
6084
|
+
rootfsHealth,
|
|
6063
6085
|
shortHash,
|
|
6064
6086
|
toChecksumAddress,
|
|
6065
6087
|
watchWallet
|
|
@@ -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,
|