@le-space/ui 0.3.23 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/ui",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
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.3.23",
40
- "@le-space/core": "0.3.23",
39
+ "@le-space/browser": "0.3.25",
40
+ "@le-space/core": "0.3.25",
41
41
  "viem": "^2.38.5"
42
42
  },
43
43
  "peerDependencies": {
package/react/index.d.ts CHANGED
@@ -189,7 +189,6 @@ type DeploymentProgressListener = (event: DeploymentProgressEvent) => void;
189
189
 
190
190
  type SponsorRelayHealthTone = 'ok' | 'caution' | 'error' | 'idle';
191
191
  interface SponsorRelayProps {
192
- libp2p?: unknown;
193
192
  debug?: boolean;
194
193
  manifestUrl?: string;
195
194
  manifestJson?: string;
@@ -224,16 +223,6 @@ interface SponsorRelayRootfsHealth {
224
223
  label: string;
225
224
  detail: string | null;
226
225
  }
227
- interface RelayPingState {
228
- tone: SponsorRelayHealthTone;
229
- sent: boolean;
230
- received: boolean;
231
- lastPeerId: string | null;
232
- lastLatencyMs: number | null;
233
- lastSentAt: number | null;
234
- lastReceivedAt: number | null;
235
- error: string | null;
236
- }
237
226
  interface CompactInstanceDetails {
238
227
  messageStatus: string;
239
228
  allocationSource: string | null;
@@ -284,7 +273,6 @@ interface SponsorRelayState {
284
273
  crns: Crn[];
285
274
  selectedCrn: Crn | null;
286
275
  instances: CompactInstanceRecord[];
287
- relayPing: RelayPingState;
288
276
  lastDeploymentHash: string | null;
289
277
  deploymentProgress: DeploymentProgressEvent;
290
278
  }
@@ -295,7 +283,6 @@ declare class SponsorRelayController {
295
283
  private subscribers;
296
284
  private client;
297
285
  private refreshTimer;
298
- private pingTimer;
299
286
  private manifestRefreshTimer;
300
287
  private stopWalletWatch;
301
288
  private props;
@@ -335,7 +322,6 @@ declare class SponsorRelayController {
335
322
  connectWallet(): Promise<void>;
336
323
  private refreshWalletDerivedState;
337
324
  refresh(): Promise<void>;
338
- refreshRelayPing(): Promise<void>;
339
325
  deploy(): Promise<void>;
340
326
  deleteInstance(instanceHash: string): Promise<void>;
341
327
  }
package/react/index.js CHANGED
@@ -10,16 +10,6 @@ var ROOTFS_MISSING_STATE = {
10
10
  label: "manifest missing",
11
11
  detail: "Provide a manifest URL or paste manifest JSON."
12
12
  };
13
- var RELAY_PING_IDLE_STATE = {
14
- tone: "idle",
15
- sent: false,
16
- received: false,
17
- lastPeerId: null,
18
- lastLatencyMs: null,
19
- lastSentAt: null,
20
- lastReceivedAt: null,
21
- error: null
22
- };
23
13
  var IDLE_DEPLOYMENT_PROGRESS = {
24
14
  stage: "idle",
25
15
  label: "Ready",
@@ -31,7 +21,6 @@ var IDLE_DEPLOYMENT_PROGRESS = {
31
21
  timestamp: Date.now()
32
22
  };
33
23
  var REFRESH_INTERVAL_MS = 3e4;
34
- var RELAY_PING_INTERVAL_MS = 2e4;
35
24
  var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
36
25
  var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
37
26
  var STALE_INSTANCE_ALLOCATION_COOLDOWN_MS = 5 * 60 * 1e3;
@@ -39,7 +28,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
39
28
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
40
29
 
41
30
  // src/shared/package-version.ts
42
- var UI_PACKAGE_VERSION = "0.3.23";
31
+ var UI_PACKAGE_VERSION = "0.3.25";
43
32
 
44
33
  // src/shared/format.ts
45
34
  function shortHash(value, head = 8, tail = 6) {
@@ -2684,6 +2673,16 @@ async function publishRelayBootstrapRegistration(args) {
2684
2673
 
2685
2674
  // ../core/src/bootstrap-config.ts
2686
2675
  var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
2676
+ var VM_BOOTSTRAP_CONFIG_SIGNAL_REF = "vm-bootstrap-config";
2677
+ var VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE = "vm-bootstrap-config-status";
2678
+ function parseJsonObject(value) {
2679
+ try {
2680
+ const parsed = JSON.parse(value);
2681
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2682
+ } catch {
2683
+ return null;
2684
+ }
2685
+ }
2687
2686
  async function fetchVmBootstrapConfigAggregate(address, options) {
2688
2687
  const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2689
2688
  requestUrl.searchParams.set("keys", VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY);
@@ -2803,6 +2802,82 @@ async function deleteVmBootstrapConfig(args) {
2803
2802
  aggregate: content
2804
2803
  };
2805
2804
  }
2805
+ function normalizeVmBootstrapConfigSignalRecord(value) {
2806
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2807
+ const candidate = value;
2808
+ const deploymentToken = typeof candidate.deploymentToken === "string" ? candidate.deploymentToken.trim() : "";
2809
+ const status = typeof candidate.status === "string" ? candidate.status.trim() : "";
2810
+ const profile = typeof candidate.profile === "string" ? candidate.profile.trim() : "";
2811
+ const ownerAddress = typeof candidate.ownerAddress === "string" ? candidate.ownerAddress.trim() : "";
2812
+ const instanceItemHash = typeof candidate.instanceItemHash === "string" ? candidate.instanceItemHash.trim() : "";
2813
+ const updatedAt = typeof candidate.updatedAt === "string" ? candidate.updatedAt.trim() : "";
2814
+ if (!deploymentToken || !status || !profile || !ownerAddress || !instanceItemHash || !updatedAt) {
2815
+ return null;
2816
+ }
2817
+ return {
2818
+ deploymentToken,
2819
+ status,
2820
+ profile,
2821
+ ownerAddress,
2822
+ instanceItemHash,
2823
+ updatedAt,
2824
+ publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
2825
+ authorization: candidate.authorization
2826
+ };
2827
+ }
2828
+ async function fetchVmBootstrapConfigSignals(args) {
2829
+ const requestUrl = new URL("/api/v0/posts.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2830
+ requestUrl.searchParams.set("refs", args.ref ?? VM_BOOTSTRAP_CONFIG_SIGNAL_REF);
2831
+ requestUrl.searchParams.set("types", args.postType ?? VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE);
2832
+ requestUrl.searchParams.set("pagination", String(Math.max(1, Number(args.pagination ?? 100))));
2833
+ requestUrl.searchParams.set("page", String(Math.max(1, Number(args.page ?? 1))));
2834
+ requestUrl.searchParams.set("sortOrder", "-1");
2835
+ const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
2836
+ if (!response.ok) {
2837
+ throw new Error(`VM bootstrap config signal request failed: ${response.status}`);
2838
+ }
2839
+ const payload = await response.json();
2840
+ const expectedDeploymentToken = args.deploymentToken?.trim() ?? "";
2841
+ const expectedOwnerAddress = args.ownerAddress?.trim().toLowerCase() ?? "";
2842
+ const expectedInstanceItemHash = args.instanceItemHash?.trim() ?? "";
2843
+ return (payload.posts ?? []).map((entry) => {
2844
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
2845
+ const parsed = "content" in entry && entry.content && typeof entry.content === "object" && !Array.isArray(entry.content) ? entry.content : "item_content" in entry && typeof entry.item_content === "string" ? parseJsonObject(entry.item_content) : null;
2846
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
2847
+ const content = "deploymentToken" in parsed ? parsed : "content" in parsed && parsed.content && typeof parsed.content === "object" && !Array.isArray(parsed.content) ? parsed.content : null;
2848
+ if (!content || typeof content !== "object" || Array.isArray(content)) return null;
2849
+ return normalizeVmBootstrapConfigSignalRecord(content);
2850
+ }).filter((entry) => {
2851
+ if (!entry) return false;
2852
+ if (expectedDeploymentToken && entry.deploymentToken !== expectedDeploymentToken) return false;
2853
+ if (expectedOwnerAddress && entry.ownerAddress.toLowerCase() !== expectedOwnerAddress) return false;
2854
+ if (expectedInstanceItemHash && entry.instanceItemHash !== expectedInstanceItemHash) return false;
2855
+ return true;
2856
+ });
2857
+ }
2858
+ async function waitForVmBootstrapConfigSignal(args) {
2859
+ const attempts = Math.max(1, Number(args.attempts ?? 24));
2860
+ const delayMs = Math.max(0, Number(args.delayMs ?? 2500));
2861
+ const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2862
+ const expectedStatus = args.expectedStatus ?? "applied";
2863
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
2864
+ const signals = await fetchVmBootstrapConfigSignals({
2865
+ deploymentToken: args.deploymentToken,
2866
+ ownerAddress: args.ownerAddress,
2867
+ instanceItemHash: args.instanceItemHash,
2868
+ fetch: args.fetch,
2869
+ apiHost: args.apiHost,
2870
+ ref: args.ref,
2871
+ postType: args.postType
2872
+ }).catch(() => []);
2873
+ const match = signals.find((entry) => entry.status === expectedStatus) ?? null;
2874
+ if (match) return match;
2875
+ if (attempt < attempts - 1) {
2876
+ await sleep2(delayMs);
2877
+ }
2878
+ }
2879
+ return null;
2880
+ }
2806
2881
 
2807
2882
  // ../core/src/bootstrap-reconcile.ts
2808
2883
  function createRelayBootstrapRegistrationId(profile, instanceName, itemHash) {
@@ -2941,7 +3016,6 @@ function defaultState(props = {}) {
2941
3016
  crns: [],
2942
3017
  selectedCrn: null,
2943
3018
  instances: [],
2944
- relayPing: RELAY_PING_IDLE_STATE,
2945
3019
  lastDeploymentHash: null,
2946
3020
  deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
2947
3021
  };
@@ -3154,57 +3228,6 @@ function compatibleCrnsForTier(crns, state) {
3154
3228
  );
3155
3229
  return filterDeployableCrns(crns, { spec });
3156
3230
  }
3157
- async function pingPeer(libp2p) {
3158
- if (!libp2p || typeof libp2p !== "object") {
3159
- return RELAY_PING_IDLE_STATE;
3160
- }
3161
- const candidate = libp2p;
3162
- const peers = candidate.getPeers?.() ?? [];
3163
- const firstPeer = peers[0];
3164
- if (!firstPeer) {
3165
- return {
3166
- ...RELAY_PING_IDLE_STATE,
3167
- tone: "caution",
3168
- error: "No connected relay peers available."
3169
- };
3170
- }
3171
- const sentAt = Date.now();
3172
- try {
3173
- const pingFn = candidate.services?.ping?.ping?.bind(candidate.services.ping) ?? candidate.ping?.bind(candidate);
3174
- if (!pingFn) {
3175
- return {
3176
- ...RELAY_PING_IDLE_STATE,
3177
- tone: "caution",
3178
- sent: true,
3179
- lastPeerId: String(firstPeer),
3180
- lastSentAt: sentAt,
3181
- error: "libp2p ping service not available."
3182
- };
3183
- }
3184
- const latency = await pingFn(firstPeer);
3185
- return {
3186
- tone: "ok",
3187
- sent: true,
3188
- received: true,
3189
- lastPeerId: String(firstPeer),
3190
- lastLatencyMs: Number(latency),
3191
- lastSentAt: sentAt,
3192
- lastReceivedAt: Date.now(),
3193
- error: null
3194
- };
3195
- } catch (error) {
3196
- return {
3197
- tone: "error",
3198
- sent: true,
3199
- received: false,
3200
- lastPeerId: String(firstPeer),
3201
- lastLatencyMs: null,
3202
- lastSentAt: sentAt,
3203
- lastReceivedAt: null,
3204
- error: error instanceof Error ? error.message : String(error)
3205
- };
3206
- }
3207
- }
3208
3231
  var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3209
3232
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3210
3233
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
@@ -3235,7 +3258,6 @@ var SponsorRelayController = class {
3235
3258
  subscribers = /* @__PURE__ */ new Set();
3236
3259
  client;
3237
3260
  refreshTimer = null;
3238
- pingTimer = null;
3239
3261
  manifestRefreshTimer = null;
3240
3262
  stopWalletWatch = null;
3241
3263
  props;
@@ -3291,8 +3313,7 @@ var SponsorRelayController = class {
3291
3313
  ...patch,
3292
3314
  busy: patch.busy ? { ...this.state.busy, ...patch.busy } : this.state.busy,
3293
3315
  wallet: patch.wallet ? { ...this.state.wallet, ...patch.wallet } : this.state.wallet,
3294
- pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary,
3295
- relayPing: patch.relayPing ? { ...this.state.relayPing, ...patch.relayPing } : this.state.relayPing
3316
+ pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary
3296
3317
  };
3297
3318
  this.emit();
3298
3319
  }
@@ -3436,6 +3457,33 @@ var SponsorRelayController = class {
3436
3457
  aggregateItemHash: bootstrapConfigPublication.aggregateItemHash,
3437
3458
  aggregateStatus: bootstrapConfigPublication.aggregateStatus
3438
3459
  });
3460
+ this.emitProgress({
3461
+ stage: "deployment-confirmed",
3462
+ label: "Waiting for guest config",
3463
+ progress: 94,
3464
+ status: "info",
3465
+ itemHash: args.itemHash,
3466
+ detail: "Waiting for the relay VM to acknowledge that it consumed the Aleph bootstrap config."
3467
+ });
3468
+ const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
3469
+ deploymentToken: args.deploymentToken,
3470
+ ownerAddress: this.state.wallet.address,
3471
+ instanceItemHash: args.itemHash,
3472
+ fetch: asJsonFetch,
3473
+ apiHost: this.client.apiHost,
3474
+ attempts: 40,
3475
+ delayMs: 3e3
3476
+ });
3477
+ this.trace("deploy:bootstrap-config-signal-visible", {
3478
+ itemHash: args.itemHash,
3479
+ deploymentToken: args.deploymentToken,
3480
+ bootstrapConfigSignal
3481
+ });
3482
+ if (!bootstrapConfigSignal) {
3483
+ throw new Error(
3484
+ "The relay VM did not confirm that it applied the Aleph bootstrap config in time."
3485
+ );
3486
+ }
3439
3487
  }
3440
3488
  this.emitProgress({
3441
3489
  stage: "publishing-bootstrap",
@@ -3726,16 +3774,11 @@ var SponsorRelayController = class {
3726
3774
  this.refreshTimer = setInterval(() => {
3727
3775
  void this.refresh();
3728
3776
  }, REFRESH_INTERVAL_MS);
3729
- this.pingTimer = setInterval(() => {
3730
- void this.refreshRelayPing();
3731
- }, RELAY_PING_INTERVAL_MS);
3732
- await this.refreshRelayPing();
3733
3777
  this.patch({ ready: true });
3734
3778
  this.trace("init:ready");
3735
3779
  }
3736
3780
  destroy() {
3737
3781
  if (this.refreshTimer) clearInterval(this.refreshTimer);
3738
- if (this.pingTimer) clearInterval(this.pingTimer);
3739
3782
  if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
3740
3783
  this.stopWalletWatch?.();
3741
3784
  }
@@ -3991,11 +4034,6 @@ var SponsorRelayController = class {
3991
4034
  });
3992
4035
  }
3993
4036
  }
3994
- async refreshRelayPing() {
3995
- const relayPing = await pingPeer(this.props.libp2p);
3996
- this.patch({ relayPing });
3997
- this.trace("libp2p:relay-ping", relayPing);
3998
- }
3999
4037
  async deploy() {
4000
4038
  this.trace("deploy:start", {
4001
4039
  wallet: this.state.wallet.address,
@@ -4467,10 +4505,9 @@ function useSponsorRelayController(props) {
4467
4505
  );
4468
4506
  useEffect(() => {
4469
4507
  controller.updateProps({
4470
- libp2p: props.libp2p,
4471
4508
  debug: props.debug
4472
4509
  });
4473
- }, [controller, props.debug, props.libp2p]);
4510
+ }, [controller, props.debug]);
4474
4511
  useEffect(() => {
4475
4512
  void controller.init();
4476
4513
  return () => {
package/shared/index.d.ts CHANGED
@@ -187,7 +187,6 @@ type DeploymentProgressListener = (event: DeploymentProgressEvent) => void;
187
187
 
188
188
  type SponsorRelayHealthTone = 'ok' | 'caution' | 'error' | 'idle';
189
189
  interface SponsorRelayProps {
190
- libp2p?: unknown;
191
190
  debug?: boolean;
192
191
  manifestUrl?: string;
193
192
  manifestJson?: string;
@@ -222,16 +221,6 @@ interface SponsorRelayRootfsHealth {
222
221
  label: string;
223
222
  detail: string | null;
224
223
  }
225
- interface RelayPingState {
226
- tone: SponsorRelayHealthTone;
227
- sent: boolean;
228
- received: boolean;
229
- lastPeerId: string | null;
230
- lastLatencyMs: number | null;
231
- lastSentAt: number | null;
232
- lastReceivedAt: number | null;
233
- error: string | null;
234
- }
235
224
  interface CompactInstanceDetails {
236
225
  messageStatus: string;
237
226
  allocationSource: string | null;
@@ -282,7 +271,6 @@ interface SponsorRelayState {
282
271
  crns: Crn[];
283
272
  selectedCrn: Crn | null;
284
273
  instances: CompactInstanceRecord[];
285
- relayPing: RelayPingState;
286
274
  lastDeploymentHash: string | null;
287
275
  deploymentProgress: DeploymentProgressEvent;
288
276
  }
@@ -292,10 +280,8 @@ declare const DEFAULT_INSTANCE_NAME = "sponsor-relay";
292
280
  declare const DEFAULT_MANIFEST_URL = "./rootfs-manifest.json";
293
281
  declare const DEFAULT_TIER_ID = "tier-1";
294
282
  declare const ROOTFS_MISSING_STATE: SponsorRelayRootfsHealth;
295
- declare const RELAY_PING_IDLE_STATE: RelayPingState;
296
283
  declare const IDLE_DEPLOYMENT_PROGRESS: DeploymentProgressEvent;
297
284
  declare const REFRESH_INTERVAL_MS = 30000;
298
- declare const RELAY_PING_INTERVAL_MS = 20000;
299
285
  declare const MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
300
286
  declare const INSTANCE_RUNTIME_READY_REFRESH_MS = 30000;
301
287
  declare const INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45000;
@@ -304,7 +290,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
304
290
  declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
305
291
  declare const DEPLOYMENT_PENDING_WARNING_MS: number;
306
292
 
307
- declare const UI_PACKAGE_VERSION = "0.3.23";
293
+ declare const UI_PACKAGE_VERSION = "0.3.25";
308
294
 
309
295
  interface RootfsRequiredPortForward {
310
296
  port: number;
@@ -345,7 +331,6 @@ declare class SponsorRelayController {
345
331
  private subscribers;
346
332
  private client;
347
333
  private refreshTimer;
348
- private pingTimer;
349
334
  private manifestRefreshTimer;
350
335
  private stopWalletWatch;
351
336
  private props;
@@ -385,7 +370,6 @@ declare class SponsorRelayController {
385
370
  connectWallet(): Promise<void>;
386
371
  private refreshWalletDerivedState;
387
372
  refresh(): Promise<void>;
388
- refreshRelayPing(): Promise<void>;
389
373
  deploy(): Promise<void>;
390
374
  deleteInstance(instanceHash: string): Promise<void>;
391
375
  }
@@ -398,4 +382,4 @@ interface DeploymentProgressEmitter {
398
382
  }
399
383
  declare function createDeploymentProgressEmitter(): DeploymentProgressEmitter;
400
384
 
401
- export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, 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, RELAY_PING_IDLE_STATE, RELAY_PING_INTERVAL_MS, ROOTFS_MISSING_STATE, type RelayPingState, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
385
+ export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, 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 SponsorRelayWalletState, UI_PACKAGE_VERSION, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
package/shared/index.js CHANGED
@@ -7,16 +7,6 @@ var ROOTFS_MISSING_STATE = {
7
7
  label: "manifest missing",
8
8
  detail: "Provide a manifest URL or paste manifest JSON."
9
9
  };
10
- var RELAY_PING_IDLE_STATE = {
11
- tone: "idle",
12
- sent: false,
13
- received: false,
14
- lastPeerId: null,
15
- lastLatencyMs: null,
16
- lastSentAt: null,
17
- lastReceivedAt: null,
18
- error: null
19
- };
20
10
  var IDLE_DEPLOYMENT_PROGRESS = {
21
11
  stage: "idle",
22
12
  label: "Ready",
@@ -28,7 +18,6 @@ var IDLE_DEPLOYMENT_PROGRESS = {
28
18
  timestamp: Date.now()
29
19
  };
30
20
  var REFRESH_INTERVAL_MS = 3e4;
31
- var RELAY_PING_INTERVAL_MS = 2e4;
32
21
  var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
33
22
  var INSTANCE_RUNTIME_READY_REFRESH_MS = 3e4;
34
23
  var INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45e3;
@@ -38,7 +27,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
38
27
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
39
28
 
40
29
  // src/shared/package-version.ts
41
- var UI_PACKAGE_VERSION = "0.3.23";
30
+ var UI_PACKAGE_VERSION = "0.3.25";
42
31
 
43
32
  // src/shared/format.ts
44
33
  function shortHash(value, head = 8, tail = 6) {
@@ -2683,6 +2672,16 @@ async function publishRelayBootstrapRegistration(args) {
2683
2672
 
2684
2673
  // ../core/src/bootstrap-config.ts
2685
2674
  var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
2675
+ var VM_BOOTSTRAP_CONFIG_SIGNAL_REF = "vm-bootstrap-config";
2676
+ var VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE = "vm-bootstrap-config-status";
2677
+ function parseJsonObject(value) {
2678
+ try {
2679
+ const parsed = JSON.parse(value);
2680
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2681
+ } catch {
2682
+ return null;
2683
+ }
2684
+ }
2686
2685
  async function fetchVmBootstrapConfigAggregate(address, options) {
2687
2686
  const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2688
2687
  requestUrl.searchParams.set("keys", VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY);
@@ -2802,6 +2801,82 @@ async function deleteVmBootstrapConfig(args) {
2802
2801
  aggregate: content
2803
2802
  };
2804
2803
  }
2804
+ function normalizeVmBootstrapConfigSignalRecord(value) {
2805
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2806
+ const candidate = value;
2807
+ const deploymentToken = typeof candidate.deploymentToken === "string" ? candidate.deploymentToken.trim() : "";
2808
+ const status = typeof candidate.status === "string" ? candidate.status.trim() : "";
2809
+ const profile = typeof candidate.profile === "string" ? candidate.profile.trim() : "";
2810
+ const ownerAddress = typeof candidate.ownerAddress === "string" ? candidate.ownerAddress.trim() : "";
2811
+ const instanceItemHash = typeof candidate.instanceItemHash === "string" ? candidate.instanceItemHash.trim() : "";
2812
+ const updatedAt = typeof candidate.updatedAt === "string" ? candidate.updatedAt.trim() : "";
2813
+ if (!deploymentToken || !status || !profile || !ownerAddress || !instanceItemHash || !updatedAt) {
2814
+ return null;
2815
+ }
2816
+ return {
2817
+ deploymentToken,
2818
+ status,
2819
+ profile,
2820
+ ownerAddress,
2821
+ instanceItemHash,
2822
+ updatedAt,
2823
+ publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
2824
+ authorization: candidate.authorization
2825
+ };
2826
+ }
2827
+ async function fetchVmBootstrapConfigSignals(args) {
2828
+ const requestUrl = new URL("/api/v0/posts.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2829
+ requestUrl.searchParams.set("refs", args.ref ?? VM_BOOTSTRAP_CONFIG_SIGNAL_REF);
2830
+ requestUrl.searchParams.set("types", args.postType ?? VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE);
2831
+ requestUrl.searchParams.set("pagination", String(Math.max(1, Number(args.pagination ?? 100))));
2832
+ requestUrl.searchParams.set("page", String(Math.max(1, Number(args.page ?? 1))));
2833
+ requestUrl.searchParams.set("sortOrder", "-1");
2834
+ const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
2835
+ if (!response.ok) {
2836
+ throw new Error(`VM bootstrap config signal request failed: ${response.status}`);
2837
+ }
2838
+ const payload = await response.json();
2839
+ const expectedDeploymentToken = args.deploymentToken?.trim() ?? "";
2840
+ const expectedOwnerAddress = args.ownerAddress?.trim().toLowerCase() ?? "";
2841
+ const expectedInstanceItemHash = args.instanceItemHash?.trim() ?? "";
2842
+ return (payload.posts ?? []).map((entry) => {
2843
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
2844
+ const parsed = "content" in entry && entry.content && typeof entry.content === "object" && !Array.isArray(entry.content) ? entry.content : "item_content" in entry && typeof entry.item_content === "string" ? parseJsonObject(entry.item_content) : null;
2845
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
2846
+ const content = "deploymentToken" in parsed ? parsed : "content" in parsed && parsed.content && typeof parsed.content === "object" && !Array.isArray(parsed.content) ? parsed.content : null;
2847
+ if (!content || typeof content !== "object" || Array.isArray(content)) return null;
2848
+ return normalizeVmBootstrapConfigSignalRecord(content);
2849
+ }).filter((entry) => {
2850
+ if (!entry) return false;
2851
+ if (expectedDeploymentToken && entry.deploymentToken !== expectedDeploymentToken) return false;
2852
+ if (expectedOwnerAddress && entry.ownerAddress.toLowerCase() !== expectedOwnerAddress) return false;
2853
+ if (expectedInstanceItemHash && entry.instanceItemHash !== expectedInstanceItemHash) return false;
2854
+ return true;
2855
+ });
2856
+ }
2857
+ async function waitForVmBootstrapConfigSignal(args) {
2858
+ const attempts = Math.max(1, Number(args.attempts ?? 24));
2859
+ const delayMs = Math.max(0, Number(args.delayMs ?? 2500));
2860
+ const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2861
+ const expectedStatus = args.expectedStatus ?? "applied";
2862
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
2863
+ const signals = await fetchVmBootstrapConfigSignals({
2864
+ deploymentToken: args.deploymentToken,
2865
+ ownerAddress: args.ownerAddress,
2866
+ instanceItemHash: args.instanceItemHash,
2867
+ fetch: args.fetch,
2868
+ apiHost: args.apiHost,
2869
+ ref: args.ref,
2870
+ postType: args.postType
2871
+ }).catch(() => []);
2872
+ const match = signals.find((entry) => entry.status === expectedStatus) ?? null;
2873
+ if (match) return match;
2874
+ if (attempt < attempts - 1) {
2875
+ await sleep2(delayMs);
2876
+ }
2877
+ }
2878
+ return null;
2879
+ }
2805
2880
 
2806
2881
  // ../core/src/bootstrap-reconcile.ts
2807
2882
  function createRelayBootstrapRegistrationId(profile, instanceName, itemHash) {
@@ -2940,7 +3015,6 @@ function defaultState(props = {}) {
2940
3015
  crns: [],
2941
3016
  selectedCrn: null,
2942
3017
  instances: [],
2943
- relayPing: RELAY_PING_IDLE_STATE,
2944
3018
  lastDeploymentHash: null,
2945
3019
  deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
2946
3020
  };
@@ -3153,57 +3227,6 @@ function compatibleCrnsForTier(crns, state) {
3153
3227
  );
3154
3228
  return filterDeployableCrns(crns, { spec });
3155
3229
  }
3156
- async function pingPeer(libp2p) {
3157
- if (!libp2p || typeof libp2p !== "object") {
3158
- return RELAY_PING_IDLE_STATE;
3159
- }
3160
- const candidate = libp2p;
3161
- const peers = candidate.getPeers?.() ?? [];
3162
- const firstPeer = peers[0];
3163
- if (!firstPeer) {
3164
- return {
3165
- ...RELAY_PING_IDLE_STATE,
3166
- tone: "caution",
3167
- error: "No connected relay peers available."
3168
- };
3169
- }
3170
- const sentAt = Date.now();
3171
- try {
3172
- const pingFn = candidate.services?.ping?.ping?.bind(candidate.services.ping) ?? candidate.ping?.bind(candidate);
3173
- if (!pingFn) {
3174
- return {
3175
- ...RELAY_PING_IDLE_STATE,
3176
- tone: "caution",
3177
- sent: true,
3178
- lastPeerId: String(firstPeer),
3179
- lastSentAt: sentAt,
3180
- error: "libp2p ping service not available."
3181
- };
3182
- }
3183
- const latency = await pingFn(firstPeer);
3184
- return {
3185
- tone: "ok",
3186
- sent: true,
3187
- received: true,
3188
- lastPeerId: String(firstPeer),
3189
- lastLatencyMs: Number(latency),
3190
- lastSentAt: sentAt,
3191
- lastReceivedAt: Date.now(),
3192
- error: null
3193
- };
3194
- } catch (error) {
3195
- return {
3196
- tone: "error",
3197
- sent: true,
3198
- received: false,
3199
- lastPeerId: String(firstPeer),
3200
- lastLatencyMs: null,
3201
- lastSentAt: sentAt,
3202
- lastReceivedAt: null,
3203
- error: error instanceof Error ? error.message : String(error)
3204
- };
3205
- }
3206
- }
3207
3230
  var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3208
3231
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3209
3232
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
@@ -3234,7 +3257,6 @@ var SponsorRelayController = class {
3234
3257
  subscribers = /* @__PURE__ */ new Set();
3235
3258
  client;
3236
3259
  refreshTimer = null;
3237
- pingTimer = null;
3238
3260
  manifestRefreshTimer = null;
3239
3261
  stopWalletWatch = null;
3240
3262
  props;
@@ -3290,8 +3312,7 @@ var SponsorRelayController = class {
3290
3312
  ...patch,
3291
3313
  busy: patch.busy ? { ...this.state.busy, ...patch.busy } : this.state.busy,
3292
3314
  wallet: patch.wallet ? { ...this.state.wallet, ...patch.wallet } : this.state.wallet,
3293
- pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary,
3294
- relayPing: patch.relayPing ? { ...this.state.relayPing, ...patch.relayPing } : this.state.relayPing
3315
+ pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary
3295
3316
  };
3296
3317
  this.emit();
3297
3318
  }
@@ -3435,6 +3456,33 @@ var SponsorRelayController = class {
3435
3456
  aggregateItemHash: bootstrapConfigPublication.aggregateItemHash,
3436
3457
  aggregateStatus: bootstrapConfigPublication.aggregateStatus
3437
3458
  });
3459
+ this.emitProgress({
3460
+ stage: "deployment-confirmed",
3461
+ label: "Waiting for guest config",
3462
+ progress: 94,
3463
+ status: "info",
3464
+ itemHash: args.itemHash,
3465
+ detail: "Waiting for the relay VM to acknowledge that it consumed the Aleph bootstrap config."
3466
+ });
3467
+ const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
3468
+ deploymentToken: args.deploymentToken,
3469
+ ownerAddress: this.state.wallet.address,
3470
+ instanceItemHash: args.itemHash,
3471
+ fetch: asJsonFetch,
3472
+ apiHost: this.client.apiHost,
3473
+ attempts: 40,
3474
+ delayMs: 3e3
3475
+ });
3476
+ this.trace("deploy:bootstrap-config-signal-visible", {
3477
+ itemHash: args.itemHash,
3478
+ deploymentToken: args.deploymentToken,
3479
+ bootstrapConfigSignal
3480
+ });
3481
+ if (!bootstrapConfigSignal) {
3482
+ throw new Error(
3483
+ "The relay VM did not confirm that it applied the Aleph bootstrap config in time."
3484
+ );
3485
+ }
3438
3486
  }
3439
3487
  this.emitProgress({
3440
3488
  stage: "publishing-bootstrap",
@@ -3725,16 +3773,11 @@ var SponsorRelayController = class {
3725
3773
  this.refreshTimer = setInterval(() => {
3726
3774
  void this.refresh();
3727
3775
  }, REFRESH_INTERVAL_MS);
3728
- this.pingTimer = setInterval(() => {
3729
- void this.refreshRelayPing();
3730
- }, RELAY_PING_INTERVAL_MS);
3731
- await this.refreshRelayPing();
3732
3776
  this.patch({ ready: true });
3733
3777
  this.trace("init:ready");
3734
3778
  }
3735
3779
  destroy() {
3736
3780
  if (this.refreshTimer) clearInterval(this.refreshTimer);
3737
- if (this.pingTimer) clearInterval(this.pingTimer);
3738
3781
  if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
3739
3782
  this.stopWalletWatch?.();
3740
3783
  }
@@ -3990,11 +4033,6 @@ var SponsorRelayController = class {
3990
4033
  });
3991
4034
  }
3992
4035
  }
3993
- async refreshRelayPing() {
3994
- const relayPing = await pingPeer(this.props.libp2p);
3995
- this.patch({ relayPing });
3996
- this.trace("libp2p:relay-ping", relayPing);
3997
- }
3998
4036
  async deploy() {
3999
4037
  this.trace("deploy:start", {
4000
4038
  wallet: this.state.wallet.address,
@@ -4456,8 +4494,6 @@ export {
4456
4494
  MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS,
4457
4495
  RECENT_INSTANCE_RUNTIME_GRACE_MS,
4458
4496
  REFRESH_INTERVAL_MS,
4459
- RELAY_PING_IDLE_STATE,
4460
- RELAY_PING_INTERVAL_MS,
4461
4497
  ROOTFS_MISSING_STATE,
4462
4498
  STALE_INSTANCE_ALLOCATION_COOLDOWN_MS,
4463
4499
  SponsorRelayController,
@@ -8,7 +8,6 @@
8
8
  import StatusLed from './components/StatusLed.svelte'
9
9
  import './styles/theme.css'
10
10
 
11
- export let libp2p = null
12
11
  export let manifestUrl = './rootfs-manifest.json'
13
12
  export let manifestJson = ''
14
13
  export let sshPublicKey = ''
@@ -23,7 +22,6 @@ export let apiHost = undefined
23
22
  export let twoN6ApiHost = undefined
24
23
 
25
24
  const controller = createSponsorRelayController({
26
- libp2p,
27
25
  manifestUrl,
28
26
  manifestJson,
29
27
  sshPublicKey,
@@ -106,17 +104,6 @@ export let apiHost = undefined
106
104
  </div>
107
105
  </div>
108
106
 
109
- <div class="ping-strip">
110
- <div>
111
- <span class="mini-label">relay ping sent</span>
112
- <div class="mini-row"><StatusLed tone={state.relayPing.sent ? 'caution' : state.relayPing.tone} pulse={state.relayPing.sent} /><strong>{state.relayPing.sent ? 'sent' : 'idle'}</strong></div>
113
- </div>
114
- <div>
115
- <span class="mini-label">relay pong received</span>
116
- <div class="mini-row"><StatusLed tone={state.relayPing.received ? 'ok' : state.relayPing.tone} pulse={state.relayPing.received} /><strong>{state.relayPing.received ? `${formatNumber(state.relayPing.lastLatencyMs, 0)} ms` : 'waiting'}</strong></div>
117
- </div>
118
- </div>
119
-
120
107
  {#if state.errorText}
121
108
  <p class="alert error">{state.errorText}</p>
122
109
  {/if}
@@ -327,7 +314,6 @@ export let apiHost = undefined
327
314
 
328
315
  .panel-head,
329
316
  .status-strip,
330
- .ping-strip,
331
317
  .actions,
332
318
  .section-head,
333
319
  .instance-topline,
@@ -364,7 +350,6 @@ export let apiHost = undefined
364
350
  }
365
351
 
366
352
  .eyebrow,
367
- .mini-label,
368
353
  .field span,
369
354
  .metric-card span,
370
355
  .mono-block span,
@@ -440,19 +425,6 @@ export let apiHost = undefined
440
425
  gap: 0.7rem;
441
426
  }
442
427
 
443
- .ping-strip {
444
- margin-top: 0.9rem;
445
- padding: 0.85rem;
446
- border-radius: 1rem;
447
- background: rgba(1, 118, 206, 0.08);
448
- }
449
-
450
- .mini-row {
451
- display: flex;
452
- gap: 0.5rem;
453
- align-items: center;
454
- }
455
-
456
428
  .alert {
457
429
  margin: 0.8rem 0 0;
458
430
  padding: 0.75rem 0.85rem;