@le-space/ui 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/ui",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Floating Aleph sponsor relay UI components for Svelte and React.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,8 +36,8 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@le-space/browser": "0.4.3",
40
- "@le-space/core": "0.4.3",
39
+ "@le-space/browser": "0.4.4",
40
+ "@le-space/core": "0.4.4",
41
41
  "viem": "^2.38.5"
42
42
  },
43
43
  "peerDependencies": {
package/react/index.js CHANGED
@@ -28,7 +28,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
28
28
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
29
29
 
30
30
  // src/shared/package-version.ts
31
- var UI_PACKAGE_VERSION = "0.4.3";
31
+ var UI_PACKAGE_VERSION = "0.4.4";
32
32
 
33
33
  // src/shared/format.ts
34
34
  function shortHash(value, head = 8, tail = 6) {
@@ -3201,7 +3201,7 @@ function rootfsHealth(args) {
3201
3201
  return {
3202
3202
  tone: "error",
3203
3203
  label: "not found on Aleph",
3204
- detail: "The referenced rootfs STORE message is not available yet."
3204
+ 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."
3205
3205
  };
3206
3206
  }
3207
3207
  if (!args.resolution) {
@@ -3234,8 +3234,8 @@ function rootfsHealth(args) {
3234
3234
  }
3235
3235
  return {
3236
3236
  tone: "error",
3237
- label: "not deployable",
3238
- detail: args.resolution.rejectionReason ?? "Aleph rejected the rootfs reference."
3237
+ label: "manifest rootfs not deployable",
3238
+ 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.")
3239
3239
  };
3240
3240
  }
3241
3241
  async function resolveManifest(args) {
@@ -3795,6 +3795,16 @@ var SponsorRelayController = class {
3795
3795
  });
3796
3796
  }
3797
3797
  if (!relayMetadata) {
3798
+ await this.refresh().catch(() => void 0);
3799
+ const confirmedAfterRefresh = this.state.bootstrapRegistrations.some(
3800
+ (entry) => entry.instanceItemHash === args.itemHash && entry.confirmed
3801
+ );
3802
+ if (confirmedAfterRefresh) {
3803
+ this.trace("deploy:relay-metadata-recovered-from-refresh", {
3804
+ itemHash: args.itemHash
3805
+ });
3806
+ return;
3807
+ }
3798
3808
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3799
3809
  }
3800
3810
  const { peerId } = relayMetadata;
@@ -4071,6 +4081,20 @@ var SponsorRelayController = class {
4071
4081
  });
4072
4082
  return;
4073
4083
  }
4084
+ const hasConfirmedBootstrapRegistration = this.state.bootstrapRegistrations.some(
4085
+ (entry) => entry.instanceItemHash === itemHash && entry.confirmed
4086
+ );
4087
+ if (hasConfirmedBootstrapRegistration) {
4088
+ this.emitProgress({
4089
+ stage: "completed",
4090
+ label: "Relay ready",
4091
+ progress: 100,
4092
+ status: "success",
4093
+ itemHash,
4094
+ detail: "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4095
+ });
4096
+ return;
4097
+ }
4074
4098
  this.emitProgress({
4075
4099
  stage: "deployment-confirmed",
4076
4100
  label: "Runtime ready",
@@ -4467,9 +4491,9 @@ var SponsorRelayController = class {
4467
4491
  this.patch({ errorText: "Connect MetaMask before deploying." });
4468
4492
  return;
4469
4493
  }
4470
- if (!this.state.manifest || !this.state.rootfsVerified || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
4494
+ if (!this.state.manifest || this.state.rootfsHealth.tone !== "ok" || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
4471
4495
  this.patch({
4472
- errorText: "Manifest, rootfs, and pricing must be ready before deploying."
4496
+ errorText: this.state.rootfsHealth.tone === "error" ? `Cannot deploy: ${this.state.rootfsHealth.detail ?? this.state.rootfsHealth.label}` : "Manifest, rootfs, and pricing must be ready before deploying."
4473
4497
  });
4474
4498
  return;
4475
4499
  }
@@ -4511,6 +4535,7 @@ var SponsorRelayController = class {
4511
4535
  for (const [candidateIndex, candidateCrn] of orderedCrns.entries()) {
4512
4536
  let attemptItemHash = null;
4513
4537
  let attemptDeploymentToken = null;
4538
+ let attemptProfile = null;
4514
4539
  const crnAttemptLabel = `CRN ${candidateIndex + 1}/${orderedCrns.length}`;
4515
4540
  const crnDisplayName = candidateCrn.name ?? candidateCrn.hash;
4516
4541
  this.emitProgress({
@@ -4522,6 +4547,7 @@ var SponsorRelayController = class {
4522
4547
  });
4523
4548
  try {
4524
4549
  const profile = relayProfileForManifest(this.state.manifest);
4550
+ attemptProfile = profile;
4525
4551
  const sshPublicKey = profile === "uc-go-peer" ? appendDeploymentTokenToSshPublicKey(
4526
4552
  this.state.sshPublicKey.trim(),
4527
4553
  this.state.wallet.address,
@@ -4757,6 +4783,106 @@ var SponsorRelayController = class {
4757
4783
  itemHash: attemptItemHash,
4758
4784
  error: lastError.message
4759
4785
  });
4786
+ if (attemptItemHash && attemptDeploymentToken && attemptProfile && !failedDueToInvalidIpv6) {
4787
+ try {
4788
+ const latestRuntime = await fetchVmRuntime({
4789
+ itemHash: attemptItemHash,
4790
+ fetch: (url, init) => fetch(url, init),
4791
+ crnHash: candidateCrn.hash,
4792
+ crns: this.state.crns,
4793
+ crnListUrl: this.props.crnListUrl,
4794
+ requirePublicGuestIpv6ForProxy: true
4795
+ });
4796
+ if (latestRuntime.diagnostics?.state === "ready") {
4797
+ const reconciledRelayMetadata = await fetchRelayMetadataForRuntime({
4798
+ runtime: latestRuntime,
4799
+ fetch: (url, init) => fetch(url, init),
4800
+ preferSecureMetadata: attemptProfile === "uc-go-peer" && Boolean(latestRuntime.proxyUrl),
4801
+ attempts: 3,
4802
+ delayMs: 1e3,
4803
+ timeoutMs: 15e3
4804
+ }).catch(() => null);
4805
+ if (reconciledRelayMetadata) {
4806
+ const registrationId = createRelayBootstrapRegistrationId(
4807
+ attemptProfile,
4808
+ this.state.instanceName.trim(),
4809
+ attemptItemHash
4810
+ );
4811
+ const visibleRegistration = await waitForRelayBootstrapRegistration({
4812
+ registrationId,
4813
+ peerId: reconciledRelayMetadata.peerId,
4814
+ fetch: asJsonFetch,
4815
+ apiHost: this.client.apiHost,
4816
+ attempts: 3,
4817
+ delayMs: 1e3
4818
+ }).catch(() => null);
4819
+ if (visibleRegistration) {
4820
+ this.trace("deploy:recovered-after-error", {
4821
+ itemHash: attemptItemHash,
4822
+ registrationId,
4823
+ peerId: reconciledRelayMetadata.peerId,
4824
+ error: lastError.message
4825
+ });
4826
+ this.rememberRuntimeDetails(attemptItemHash, {
4827
+ messageStatus: "processed",
4828
+ allocationSource: latestRuntime.allocation?.source ?? null,
4829
+ crnUrl: latestRuntime.allocation?.crnUrl ?? latestRuntime.execution?.crnUrl ?? null,
4830
+ hostIpv4: latestRuntime.hostIpv4 ?? null,
4831
+ ipv6: latestRuntime.ipv6 ?? null,
4832
+ vmIpv4: latestRuntime.execution?.networking?.ipv4_ip ?? null,
4833
+ webUrl: latestRuntime.webAccessUrl ?? latestRuntime.proxyUrl ?? latestRuntime.execution?.networking?.proxy_url ?? null,
4834
+ sshCommand: latestRuntime.sshCommand ?? null,
4835
+ mappedPorts: latestRuntime.execution != null ? mappedPorts(latestRuntime.execution) : runtimeMappedPorts(latestRuntime.mappedPorts),
4836
+ execution: latestRuntime.execution ?? null,
4837
+ error: null
4838
+ });
4839
+ this.patch({
4840
+ busy: { deploying: false }
4841
+ });
4842
+ await this.refresh();
4843
+ this.lastCompletedDeploymentHash = attemptItemHash;
4844
+ this.emitProgress({
4845
+ stage: "completed",
4846
+ label: "Relay ready",
4847
+ progress: 100,
4848
+ status: "success",
4849
+ itemHash: attemptItemHash,
4850
+ detail: "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
4851
+ });
4852
+ return;
4853
+ }
4854
+ }
4855
+ }
4856
+ } catch (reconcileError) {
4857
+ this.trace("deploy:recovery-check-error", {
4858
+ itemHash: attemptItemHash,
4859
+ error: reconcileError instanceof Error ? reconcileError.message : String(reconcileError)
4860
+ });
4861
+ }
4862
+ await this.refresh().catch(() => void 0);
4863
+ const recoveredRegistration = this.state.bootstrapRegistrations.some(
4864
+ (entry) => entry.instanceItemHash === attemptItemHash && entry.confirmed
4865
+ );
4866
+ if (recoveredRegistration) {
4867
+ this.trace("deploy:recovered-from-refresh-state", {
4868
+ itemHash: attemptItemHash,
4869
+ error: lastError.message
4870
+ });
4871
+ this.patch({
4872
+ busy: { deploying: false }
4873
+ });
4874
+ this.lastCompletedDeploymentHash = attemptItemHash;
4875
+ this.emitProgress({
4876
+ stage: "completed",
4877
+ label: "Relay ready",
4878
+ progress: 100,
4879
+ status: "success",
4880
+ itemHash: attemptItemHash,
4881
+ detail: "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
4882
+ });
4883
+ return;
4884
+ }
4885
+ }
4760
4886
  if (attemptItemHash) {
4761
4887
  this.emitProgress({
4762
4888
  stage: "deployment-confirmed",
@@ -5912,6 +6038,7 @@ function SponsorRelayFab(props) {
5912
6038
  {
5913
6039
  type: "button",
5914
6040
  onClick: () => void (state.wallet.connected ? controller.deploy() : controller.connectWallet()),
6041
+ disabled: state.wallet.connected ? state.busy.deploying || state.rootfsHealth.tone !== "ok" : state.busy.connectingWallet,
5915
6042
  style: { ...primaryButtonStyle, width: "100%", marginTop: "1rem" },
5916
6043
  children: state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : "Deploy Relay" : "Connect MetaMask"
5917
6044
  }
package/shared/index.d.ts CHANGED
@@ -311,7 +311,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
311
311
  declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
312
312
  declare const DEPLOYMENT_PENDING_WARNING_MS: number;
313
313
 
314
- declare const UI_PACKAGE_VERSION = "0.4.3";
314
+ declare const UI_PACKAGE_VERSION = "0.4.4";
315
315
 
316
316
  interface RootfsRequiredPortForward {
317
317
  port: number;
package/shared/index.js CHANGED
@@ -27,7 +27,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
27
27
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
28
28
 
29
29
  // src/shared/package-version.ts
30
- var UI_PACKAGE_VERSION = "0.4.3";
30
+ var UI_PACKAGE_VERSION = "0.4.4";
31
31
 
32
32
  // src/shared/format.ts
33
33
  function shortHash(value, head = 8, tail = 6) {
@@ -3200,7 +3200,7 @@ function rootfsHealth(args) {
3200
3200
  return {
3201
3201
  tone: "error",
3202
3202
  label: "not found on Aleph",
3203
- detail: "The referenced rootfs STORE message is not available yet."
3203
+ 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."
3204
3204
  };
3205
3205
  }
3206
3206
  if (!args.resolution) {
@@ -3233,8 +3233,8 @@ function rootfsHealth(args) {
3233
3233
  }
3234
3234
  return {
3235
3235
  tone: "error",
3236
- label: "not deployable",
3237
- detail: args.resolution.rejectionReason ?? "Aleph rejected the rootfs reference."
3236
+ label: "manifest rootfs not deployable",
3237
+ 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.")
3238
3238
  };
3239
3239
  }
3240
3240
  async function resolveManifest(args) {
@@ -3794,6 +3794,16 @@ var SponsorRelayController = class {
3794
3794
  });
3795
3795
  }
3796
3796
  if (!relayMetadata) {
3797
+ await this.refresh().catch(() => void 0);
3798
+ const confirmedAfterRefresh = this.state.bootstrapRegistrations.some(
3799
+ (entry) => entry.instanceItemHash === args.itemHash && entry.confirmed
3800
+ );
3801
+ if (confirmedAfterRefresh) {
3802
+ this.trace("deploy:relay-metadata-recovered-from-refresh", {
3803
+ itemHash: args.itemHash
3804
+ });
3805
+ return;
3806
+ }
3797
3807
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3798
3808
  }
3799
3809
  const { peerId } = relayMetadata;
@@ -4070,6 +4080,20 @@ var SponsorRelayController = class {
4070
4080
  });
4071
4081
  return;
4072
4082
  }
4083
+ const hasConfirmedBootstrapRegistration = this.state.bootstrapRegistrations.some(
4084
+ (entry) => entry.instanceItemHash === itemHash && entry.confirmed
4085
+ );
4086
+ if (hasConfirmedBootstrapRegistration) {
4087
+ this.emitProgress({
4088
+ stage: "completed",
4089
+ label: "Relay ready",
4090
+ progress: 100,
4091
+ status: "success",
4092
+ itemHash,
4093
+ detail: "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4094
+ });
4095
+ return;
4096
+ }
4073
4097
  this.emitProgress({
4074
4098
  stage: "deployment-confirmed",
4075
4099
  label: "Runtime ready",
@@ -4466,9 +4490,9 @@ var SponsorRelayController = class {
4466
4490
  this.patch({ errorText: "Connect MetaMask before deploying." });
4467
4491
  return;
4468
4492
  }
4469
- if (!this.state.manifest || !this.state.rootfsVerified || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
4493
+ if (!this.state.manifest || this.state.rootfsHealth.tone !== "ok" || !this.state.pricingSummary.pricing || !this.state.pricingSummary.tier) {
4470
4494
  this.patch({
4471
- errorText: "Manifest, rootfs, and pricing must be ready before deploying."
4495
+ errorText: this.state.rootfsHealth.tone === "error" ? `Cannot deploy: ${this.state.rootfsHealth.detail ?? this.state.rootfsHealth.label}` : "Manifest, rootfs, and pricing must be ready before deploying."
4472
4496
  });
4473
4497
  return;
4474
4498
  }
@@ -4510,6 +4534,7 @@ var SponsorRelayController = class {
4510
4534
  for (const [candidateIndex, candidateCrn] of orderedCrns.entries()) {
4511
4535
  let attemptItemHash = null;
4512
4536
  let attemptDeploymentToken = null;
4537
+ let attemptProfile = null;
4513
4538
  const crnAttemptLabel = `CRN ${candidateIndex + 1}/${orderedCrns.length}`;
4514
4539
  const crnDisplayName = candidateCrn.name ?? candidateCrn.hash;
4515
4540
  this.emitProgress({
@@ -4521,6 +4546,7 @@ var SponsorRelayController = class {
4521
4546
  });
4522
4547
  try {
4523
4548
  const profile = relayProfileForManifest(this.state.manifest);
4549
+ attemptProfile = profile;
4524
4550
  const sshPublicKey = profile === "uc-go-peer" ? appendDeploymentTokenToSshPublicKey(
4525
4551
  this.state.sshPublicKey.trim(),
4526
4552
  this.state.wallet.address,
@@ -4756,6 +4782,106 @@ var SponsorRelayController = class {
4756
4782
  itemHash: attemptItemHash,
4757
4783
  error: lastError.message
4758
4784
  });
4785
+ if (attemptItemHash && attemptDeploymentToken && attemptProfile && !failedDueToInvalidIpv6) {
4786
+ try {
4787
+ const latestRuntime = await fetchVmRuntime({
4788
+ itemHash: attemptItemHash,
4789
+ fetch: (url, init) => fetch(url, init),
4790
+ crnHash: candidateCrn.hash,
4791
+ crns: this.state.crns,
4792
+ crnListUrl: this.props.crnListUrl,
4793
+ requirePublicGuestIpv6ForProxy: true
4794
+ });
4795
+ if (latestRuntime.diagnostics?.state === "ready") {
4796
+ const reconciledRelayMetadata = await fetchRelayMetadataForRuntime({
4797
+ runtime: latestRuntime,
4798
+ fetch: (url, init) => fetch(url, init),
4799
+ preferSecureMetadata: attemptProfile === "uc-go-peer" && Boolean(latestRuntime.proxyUrl),
4800
+ attempts: 3,
4801
+ delayMs: 1e3,
4802
+ timeoutMs: 15e3
4803
+ }).catch(() => null);
4804
+ if (reconciledRelayMetadata) {
4805
+ const registrationId = createRelayBootstrapRegistrationId(
4806
+ attemptProfile,
4807
+ this.state.instanceName.trim(),
4808
+ attemptItemHash
4809
+ );
4810
+ const visibleRegistration = await waitForRelayBootstrapRegistration({
4811
+ registrationId,
4812
+ peerId: reconciledRelayMetadata.peerId,
4813
+ fetch: asJsonFetch,
4814
+ apiHost: this.client.apiHost,
4815
+ attempts: 3,
4816
+ delayMs: 1e3
4817
+ }).catch(() => null);
4818
+ if (visibleRegistration) {
4819
+ this.trace("deploy:recovered-after-error", {
4820
+ itemHash: attemptItemHash,
4821
+ registrationId,
4822
+ peerId: reconciledRelayMetadata.peerId,
4823
+ error: lastError.message
4824
+ });
4825
+ this.rememberRuntimeDetails(attemptItemHash, {
4826
+ messageStatus: "processed",
4827
+ allocationSource: latestRuntime.allocation?.source ?? null,
4828
+ crnUrl: latestRuntime.allocation?.crnUrl ?? latestRuntime.execution?.crnUrl ?? null,
4829
+ hostIpv4: latestRuntime.hostIpv4 ?? null,
4830
+ ipv6: latestRuntime.ipv6 ?? null,
4831
+ vmIpv4: latestRuntime.execution?.networking?.ipv4_ip ?? null,
4832
+ webUrl: latestRuntime.webAccessUrl ?? latestRuntime.proxyUrl ?? latestRuntime.execution?.networking?.proxy_url ?? null,
4833
+ sshCommand: latestRuntime.sshCommand ?? null,
4834
+ mappedPorts: latestRuntime.execution != null ? mappedPorts(latestRuntime.execution) : runtimeMappedPorts(latestRuntime.mappedPorts),
4835
+ execution: latestRuntime.execution ?? null,
4836
+ error: null
4837
+ });
4838
+ this.patch({
4839
+ busy: { deploying: false }
4840
+ });
4841
+ await this.refresh();
4842
+ this.lastCompletedDeploymentHash = attemptItemHash;
4843
+ this.emitProgress({
4844
+ stage: "completed",
4845
+ label: "Relay ready",
4846
+ progress: 100,
4847
+ status: "success",
4848
+ itemHash: attemptItemHash,
4849
+ detail: "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
4850
+ });
4851
+ return;
4852
+ }
4853
+ }
4854
+ }
4855
+ } catch (reconcileError) {
4856
+ this.trace("deploy:recovery-check-error", {
4857
+ itemHash: attemptItemHash,
4858
+ error: reconcileError instanceof Error ? reconcileError.message : String(reconcileError)
4859
+ });
4860
+ }
4861
+ await this.refresh().catch(() => void 0);
4862
+ const recoveredRegistration = this.state.bootstrapRegistrations.some(
4863
+ (entry) => entry.instanceItemHash === attemptItemHash && entry.confirmed
4864
+ );
4865
+ if (recoveredRegistration) {
4866
+ this.trace("deploy:recovered-from-refresh-state", {
4867
+ itemHash: attemptItemHash,
4868
+ error: lastError.message
4869
+ });
4870
+ this.patch({
4871
+ busy: { deploying: false }
4872
+ });
4873
+ this.lastCompletedDeploymentHash = attemptItemHash;
4874
+ this.emitProgress({
4875
+ stage: "completed",
4876
+ label: "Relay ready",
4877
+ progress: 100,
4878
+ status: "success",
4879
+ itemHash: attemptItemHash,
4880
+ detail: "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
4881
+ });
4882
+ return;
4883
+ }
4884
+ }
4759
4885
  if (attemptItemHash) {
4760
4886
  this.emitProgress({
4761
4887
  stage: "deployment-confirmed",
@@ -193,7 +193,7 @@ export let apiHost = undefined
193
193
 
194
194
  <div class="actions">
195
195
  {#if state.wallet.connected}
196
- <button class="primary" type="button" on:click={() => controller.deploy()} disabled={state.busy.deploying || !state.rootfsVerified}>
196
+ <button class="primary" type="button" on:click={() => controller.deploy()} disabled={state.busy.deploying || state.rootfsHealth.tone !== 'ok'}>
197
197
  {state.busy.deploying ? 'Deploying…' : 'Deploy Relay'}
198
198
  </button>
199
199
  {:else}