@le-space/ui 0.3.32 → 0.4.0

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/react/index.js CHANGED
@@ -10,6 +10,16 @@ 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
+ };
13
23
  var IDLE_DEPLOYMENT_PROGRESS = {
14
24
  stage: "idle",
15
25
  label: "Ready",
@@ -21,6 +31,7 @@ var IDLE_DEPLOYMENT_PROGRESS = {
21
31
  timestamp: Date.now()
22
32
  };
23
33
  var REFRESH_INTERVAL_MS = 3e4;
34
+ var RELAY_PING_INTERVAL_MS = 2e4;
24
35
  var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
25
36
  var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
26
37
  var STALE_INSTANCE_ALLOCATION_COOLDOWN_MS = 5 * 60 * 1e3;
@@ -28,7 +39,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
28
39
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
29
40
 
30
41
  // src/shared/package-version.ts
31
- var UI_PACKAGE_VERSION = "0.3.32";
42
+ var UI_PACKAGE_VERSION = "0.4.0";
32
43
 
33
44
  // src/shared/format.ts
34
45
  function shortHash(value, head = 8, tail = 6) {
@@ -1237,6 +1248,16 @@ function normalizeBroadcastStatus(httpStatus, responseStatus) {
1237
1248
  if (httpStatus === 202) return "pending";
1238
1249
  return normalizeMessageStatus2(responseStatus);
1239
1250
  }
1251
+ function isInvalidMessageFormatResponse2(response, payload) {
1252
+ if (response.status !== 422) return false;
1253
+ const details = payload?.details;
1254
+ if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
1255
+ if (details && typeof details === "object") {
1256
+ const detailMessage = details.message;
1257
+ if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
1258
+ }
1259
+ return false;
1260
+ }
1240
1261
  function isRetryableBroadcastFailure(response, payload) {
1241
1262
  if (response.status >= 500) return true;
1242
1263
  const publicationStatus = payload?.publication_status?.status;
@@ -1258,22 +1279,25 @@ async function postBroadcastPayload2(body, options) {
1258
1279
  };
1259
1280
  }
1260
1281
  async function broadcastAlephMessage2(message, options) {
1261
- const requestBody = { sync: options.sync ?? false, message };
1262
- const maxAttempts = 3;
1263
- for (let index = 0; index < maxAttempts; index += 1) {
1264
- const result = await postBroadcastPayload2(requestBody, {
1282
+ const attempts = [
1283
+ { sync: options.sync ?? false, message },
1284
+ { ...message, sync: options.sync ?? false },
1285
+ { ...message }
1286
+ ];
1287
+ for (let index = 0; index < attempts.length; index += 1) {
1288
+ const result = await postBroadcastPayload2(attempts[index], {
1265
1289
  apiHost: options.apiHost,
1266
1290
  fetch: options.fetch
1267
1291
  });
1268
1292
  if (result.httpStatus === 202 || normalizeBroadcastStatus(result.httpStatus, result.response?.message_status) !== "unknown" || result.httpStatus >= 200 && result.httpStatus < 300) {
1269
1293
  return result;
1270
1294
  }
1271
- const canRetry = index < maxAttempts - 1 && isRetryableBroadcastFailure({ status: result.httpStatus }, result.response);
1295
+ const canRetry = index < attempts.length - 1 && (isInvalidMessageFormatResponse2({ status: result.httpStatus }, result.response) || isRetryableBroadcastFailure({ status: result.httpStatus }, result.response));
1272
1296
  if (!canRetry) {
1273
1297
  throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response ?? {})}`);
1274
1298
  }
1275
1299
  }
1276
- throw new Error("Broadcast failed: retry budget exhausted");
1300
+ throw new Error("Broadcast failed: no compatible request format was accepted");
1277
1301
  }
1278
1302
 
1279
1303
  // ../core/src/forget.ts
@@ -1878,13 +1902,6 @@ async function waitForVmRuntime(args) {
1878
1902
  }
1879
1903
 
1880
1904
  // ../core/src/guest.ts
1881
- function proxyHostnameFromUrl(value) {
1882
- try {
1883
- return value ? new URL(value).hostname : null;
1884
- } catch {
1885
- return null;
1886
- }
1887
- }
1888
1905
  async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1889
1906
  try {
1890
1907
  const controller = new AbortController();
@@ -2126,59 +2143,6 @@ async function fetchUcGoPeerMetadata(args) {
2126
2143
  `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2127
2144
  );
2128
2145
  }
2129
- async function verifyUcGoPeerReachability(args) {
2130
- const checks = {};
2131
- const mappedPorts2 = args.mappedPorts ?? {};
2132
- const hostIpv4 = args.hostIpv4;
2133
- const skippedInternalPorts = new Set(
2134
- (args.skipInternalPorts ?? ["80"]).map((value) => String(value))
2135
- );
2136
- const httpProbe = args.httpProbe ?? ((url, timeoutMs) => defaultHttpProbe(args.fetch, url, timeoutMs));
2137
- if (hostIpv4) {
2138
- for (const [internalPort, mapping] of Object.entries(mappedPorts2)) {
2139
- if (skippedInternalPorts.has(String(internalPort))) continue;
2140
- if (mapping?.tcp === true && mapping?.host) {
2141
- checks[`tcp:${internalPort}`] = {
2142
- host: hostIpv4,
2143
- port: mapping.host,
2144
- ...await args.tcpProbe(
2145
- hostIpv4,
2146
- mapping.host,
2147
- Number(args.tcpTimeoutMs ?? 5e3)
2148
- )
2149
- };
2150
- } else if (mapping?.udp === true && mapping?.host) {
2151
- checks[`udp:${internalPort}`] = {
2152
- host: hostIpv4,
2153
- port: mapping.host,
2154
- ok: null,
2155
- note: "UDP mapping published; CI does not perform an application-level UDP handshake probe."
2156
- };
2157
- }
2158
- }
2159
- }
2160
- if (args.proxyUrl && args.verifyProxyHttp !== false) {
2161
- checks["https:proxy"] = await httpProbe(
2162
- args.proxyUrl,
2163
- Number(args.httpTimeoutMs ?? 1e4)
2164
- );
2165
- }
2166
- const proxyHostname = proxyHostnameFromUrl(args.proxyUrl);
2167
- if (proxyHostname) {
2168
- checks["tcp:proxy-443"] = await args.tcpProbe(
2169
- proxyHostname,
2170
- 443,
2171
- Number(args.tcpTimeoutMs ?? 5e3)
2172
- );
2173
- }
2174
- const failedChecks = Object.entries(checks).filter(
2175
- ([, value]) => value?.ok === false
2176
- );
2177
- return {
2178
- ok: failedChecks.length === 0,
2179
- checks
2180
- };
2181
- }
2182
2146
 
2183
2147
  // ../aleph-bootstrap/src/index.ts
2184
2148
  import { recoverMessageAddress } from "viem";
@@ -2720,16 +2684,6 @@ async function publishRelayBootstrapRegistration(args) {
2720
2684
 
2721
2685
  // ../core/src/bootstrap-config.ts
2722
2686
  var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
2723
- var VM_BOOTSTRAP_CONFIG_SIGNAL_REF = "vm-bootstrap-config";
2724
- var VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE = "vm-bootstrap-config-status";
2725
- function parseJsonObject(value) {
2726
- try {
2727
- const parsed = JSON.parse(value);
2728
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
2729
- } catch {
2730
- return null;
2731
- }
2732
- }
2733
2687
  async function fetchVmBootstrapConfigAggregate(address, options) {
2734
2688
  const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2735
2689
  requestUrl.searchParams.set("keys", VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY);
@@ -2849,89 +2803,6 @@ async function deleteVmBootstrapConfig(args) {
2849
2803
  aggregate: content
2850
2804
  };
2851
2805
  }
2852
- function normalizeVmBootstrapConfigSignalRecord(value) {
2853
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2854
- const candidate = value;
2855
- const deploymentToken = typeof candidate.deploymentToken === "string" ? candidate.deploymentToken.trim() : "";
2856
- const status = typeof candidate.status === "string" ? candidate.status.trim() : "";
2857
- const profile = typeof candidate.profile === "string" ? candidate.profile.trim() : "";
2858
- const ownerAddress = typeof candidate.ownerAddress === "string" ? candidate.ownerAddress.trim() : "";
2859
- const instanceItemHash = typeof candidate.instanceItemHash === "string" ? candidate.instanceItemHash.trim() : "";
2860
- const updatedAt = typeof candidate.updatedAt === "string" ? candidate.updatedAt.trim() : "";
2861
- if (!deploymentToken || !status || !profile || !ownerAddress || !instanceItemHash || !updatedAt) {
2862
- return null;
2863
- }
2864
- return {
2865
- deploymentToken,
2866
- status,
2867
- profile,
2868
- ownerAddress,
2869
- instanceItemHash,
2870
- updatedAt,
2871
- publisherAddress: typeof candidate.publisherAddress === "string" && candidate.publisherAddress.trim() ? candidate.publisherAddress.trim() : null,
2872
- authorization: candidate.authorization,
2873
- peerId: typeof candidate.peerId === "string" && candidate.peerId.trim() ? candidate.peerId.trim() : null,
2874
- probeMultiaddrs: Array.isArray(candidate.probeMultiaddrs) ? candidate.probeMultiaddrs.filter(
2875
- (entry) => typeof entry === "string" && entry.trim().length > 0
2876
- ) : [],
2877
- browserBootstrapMultiaddrs: Array.isArray(candidate.browserBootstrapMultiaddrs) ? candidate.browserBootstrapMultiaddrs.filter(
2878
- (entry) => typeof entry === "string" && entry.trim().length > 0
2879
- ) : []
2880
- };
2881
- }
2882
- async function fetchVmBootstrapConfigSignals(args) {
2883
- const requestUrl = new URL("/api/v0/posts.json", args.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2884
- requestUrl.searchParams.set("refs", args.ref ?? VM_BOOTSTRAP_CONFIG_SIGNAL_REF);
2885
- requestUrl.searchParams.set("types", args.postType ?? VM_BOOTSTRAP_CONFIG_SIGNAL_POST_TYPE);
2886
- requestUrl.searchParams.set("pagination", String(Math.max(1, Number(args.pagination ?? 100))));
2887
- requestUrl.searchParams.set("page", String(Math.max(1, Number(args.page ?? 1))));
2888
- requestUrl.searchParams.set("sortOrder", "-1");
2889
- const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
2890
- if (!response.ok) {
2891
- throw new Error(`VM bootstrap config signal request failed: ${response.status}`);
2892
- }
2893
- const payload = await response.json();
2894
- const expectedDeploymentToken = args.deploymentToken?.trim() ?? "";
2895
- const expectedOwnerAddress = args.ownerAddress?.trim().toLowerCase() ?? "";
2896
- const expectedInstanceItemHash = args.instanceItemHash?.trim() ?? "";
2897
- return (payload.posts ?? []).map((entry) => {
2898
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
2899
- 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;
2900
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
2901
- const content = "deploymentToken" in parsed ? parsed : "content" in parsed && parsed.content && typeof parsed.content === "object" && !Array.isArray(parsed.content) ? parsed.content : null;
2902
- if (!content || typeof content !== "object" || Array.isArray(content)) return null;
2903
- return normalizeVmBootstrapConfigSignalRecord(content);
2904
- }).filter((entry) => {
2905
- if (!entry) return false;
2906
- if (expectedDeploymentToken && entry.deploymentToken !== expectedDeploymentToken) return false;
2907
- if (expectedOwnerAddress && entry.ownerAddress.toLowerCase() !== expectedOwnerAddress) return false;
2908
- if (expectedInstanceItemHash && entry.instanceItemHash !== expectedInstanceItemHash) return false;
2909
- return true;
2910
- });
2911
- }
2912
- async function waitForVmBootstrapConfigSignal(args) {
2913
- const attempts = Math.max(1, Number(args.attempts ?? 24));
2914
- const delayMs = Math.max(0, Number(args.delayMs ?? 2500));
2915
- const sleep2 = args.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
2916
- const expectedStatus = args.expectedStatus ?? "applied";
2917
- for (let attempt = 0; attempt < attempts; attempt += 1) {
2918
- const signals = await fetchVmBootstrapConfigSignals({
2919
- deploymentToken: args.deploymentToken,
2920
- ownerAddress: args.ownerAddress,
2921
- instanceItemHash: args.instanceItemHash,
2922
- fetch: args.fetch,
2923
- apiHost: args.apiHost,
2924
- ref: args.ref,
2925
- postType: args.postType
2926
- }).catch(() => []);
2927
- const match = signals.find((entry) => entry.status === expectedStatus) ?? null;
2928
- if (match) return match;
2929
- if (attempt < attempts - 1) {
2930
- await sleep2(delayMs);
2931
- }
2932
- }
2933
- return null;
2934
- }
2935
2806
 
2936
2807
  // ../core/src/bootstrap-reconcile.ts
2937
2808
  function createRelayBootstrapRegistrationId(profile, instanceName, itemHash) {
@@ -3073,6 +2944,7 @@ function defaultState(props = {}) {
3073
2944
  instances: [],
3074
2945
  bootstrapRegistrations: [],
3075
2946
  orphanBootstrapRegistrations: [],
2947
+ relayPing: RELAY_PING_IDLE_STATE,
3076
2948
  lastDeploymentHash: null,
3077
2949
  deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
3078
2950
  };
@@ -3299,12 +3171,61 @@ function compatibleCrnsForTier(crns, state) {
3299
3171
  );
3300
3172
  return filterDeployableCrns(crns, { spec });
3301
3173
  }
3174
+ async function pingPeer(libp2p) {
3175
+ if (!libp2p || typeof libp2p !== "object") {
3176
+ return RELAY_PING_IDLE_STATE;
3177
+ }
3178
+ const candidate = libp2p;
3179
+ const peers = candidate.getPeers?.() ?? [];
3180
+ const firstPeer = peers[0];
3181
+ if (!firstPeer) {
3182
+ return {
3183
+ ...RELAY_PING_IDLE_STATE,
3184
+ tone: "caution",
3185
+ error: "No connected relay peers available."
3186
+ };
3187
+ }
3188
+ const sentAt = Date.now();
3189
+ try {
3190
+ const pingFn = candidate.services?.ping?.ping?.bind(candidate.services.ping) ?? candidate.ping?.bind(candidate);
3191
+ if (!pingFn) {
3192
+ return {
3193
+ ...RELAY_PING_IDLE_STATE,
3194
+ tone: "caution",
3195
+ sent: true,
3196
+ lastPeerId: String(firstPeer),
3197
+ lastSentAt: sentAt,
3198
+ error: "libp2p ping service not available."
3199
+ };
3200
+ }
3201
+ const latency = await pingFn(firstPeer);
3202
+ return {
3203
+ tone: "ok",
3204
+ sent: true,
3205
+ received: true,
3206
+ lastPeerId: String(firstPeer),
3207
+ lastLatencyMs: Number(latency),
3208
+ lastSentAt: sentAt,
3209
+ lastReceivedAt: Date.now(),
3210
+ error: null
3211
+ };
3212
+ } catch (error) {
3213
+ return {
3214
+ tone: "error",
3215
+ sent: true,
3216
+ received: false,
3217
+ lastPeerId: String(firstPeer),
3218
+ lastLatencyMs: null,
3219
+ lastSentAt: sentAt,
3220
+ lastReceivedAt: null,
3221
+ error: error instanceof Error ? error.message : String(error)
3222
+ };
3223
+ }
3224
+ }
3302
3225
  var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3303
3226
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3304
3227
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
3305
3228
  var UI_RUNTIME_WAIT_DELAY_MS = 5e3;
3306
- var UI_REACHABILITY_VERIFY_ATTEMPTS = 25;
3307
- var UI_REACHABILITY_VERIFY_DELAY_MS = 3e3;
3308
3229
  function hasUsableRuntime(details) {
3309
3230
  return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
3310
3231
  }
@@ -3331,6 +3252,7 @@ var SponsorRelayController = class {
3331
3252
  subscribers = /* @__PURE__ */ new Set();
3332
3253
  client;
3333
3254
  refreshTimer = null;
3255
+ pingTimer = null;
3334
3256
  manifestRefreshTimer = null;
3335
3257
  stopWalletWatch = null;
3336
3258
  props;
@@ -3386,7 +3308,8 @@ var SponsorRelayController = class {
3386
3308
  ...patch,
3387
3309
  busy: patch.busy ? { ...this.state.busy, ...patch.busy } : this.state.busy,
3388
3310
  wallet: patch.wallet ? { ...this.state.wallet, ...patch.wallet } : this.state.wallet,
3389
- pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary
3311
+ pricingSummary: patch.pricingSummary ? { ...this.state.pricingSummary, ...patch.pricingSummary } : this.state.pricingSummary,
3312
+ relayPing: patch.relayPing ? { ...this.state.relayPing, ...patch.relayPing } : this.state.relayPing
3390
3313
  };
3391
3314
  this.emit();
3392
3315
  }
@@ -3411,21 +3334,19 @@ var SponsorRelayController = class {
3411
3334
  this.progressEmitter.emit(nextEvent);
3412
3335
  }
3413
3336
  async configureRelayBootstrapRegistration(args) {
3414
- let runtime = args.runtime;
3415
3337
  const profile = relayProfileForManifest(this.state.manifest);
3416
3338
  if (!profile) return;
3417
3339
  if (!this.state.wallet.address) {
3418
3340
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3419
3341
  }
3420
- if (!runtime.hostIpv4) {
3342
+ if (!args.runtime.hostIpv4) {
3421
3343
  throw new Error("Relay runtime did not expose a host IPv4 address.");
3422
3344
  }
3423
- let mappedPorts2 = runtime.mappedPorts ?? {};
3345
+ const mappedPorts2 = args.runtime.mappedPorts ?? {};
3424
3346
  const setupPort = mappedPorts2["80"]?.host ?? null;
3425
3347
  if (!setupPort) {
3426
3348
  throw new Error("Relay runtime did not expose the temporary setup endpoint.");
3427
3349
  }
3428
- let bootstrapSignalRelayMetadata = null;
3429
3350
  if (profile === "orbitdb-relay-pinner") {
3430
3351
  this.emitProgress({
3431
3352
  stage: "deployment-confirmed",
@@ -3436,7 +3357,7 @@ var SponsorRelayController = class {
3436
3357
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3437
3358
  });
3438
3359
  const setupHealth = await waitForSetupEndpoint({
3439
- hostIpv4: runtime.hostIpv4,
3360
+ hostIpv4: args.runtime.hostIpv4,
3440
3361
  setupPort,
3441
3362
  fetch: (url, init) => fetch(url, init),
3442
3363
  attempts: 15,
@@ -3462,12 +3383,12 @@ var SponsorRelayController = class {
3462
3383
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3463
3384
  }
3464
3385
  const configureResult = await configureOrbitdbRelaySetup2({
3465
- hostIpv4: runtime.hostIpv4,
3466
- publicIpv6: runtime.ipv6,
3386
+ hostIpv4: args.runtime.hostIpv4,
3387
+ publicIpv6: args.runtime.ipv6,
3467
3388
  setupPort,
3468
3389
  tcpPort,
3469
3390
  wsPort,
3470
- proxyUrl: runtime.proxyUrl ?? void 0,
3391
+ proxyUrl: args.runtime.proxyUrl ?? void 0,
3471
3392
  metricsPort: mappedPorts2["9090"]?.host ?? null,
3472
3393
  metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
3473
3394
  webrtcPort: mappedPorts2["9093"]?.host ?? null,
@@ -3484,54 +3405,6 @@ var SponsorRelayController = class {
3484
3405
  if (!args.deploymentToken) {
3485
3406
  throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
3486
3407
  }
3487
- if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
3488
- this.emitProgress({
3489
- stage: "deployment-confirmed",
3490
- label: "Waiting for HTTPS route",
3491
- progress: 93,
3492
- status: "warning",
3493
- itemHash: args.itemHash,
3494
- detail: "The relay received a 2n6 hostname, but it is not active yet. Waiting before handing HTTPS config to the guest."
3495
- });
3496
- let latestRuntime = runtime;
3497
- for (let attempt = 0; attempt < UI_RUNTIME_WAIT_ATTEMPTS; attempt += 1) {
3498
- if (latestRuntime.webAccess?.active === true) {
3499
- break;
3500
- }
3501
- await new Promise(
3502
- (resolve) => globalThis.setTimeout(resolve, UI_RUNTIME_WAIT_DELAY_MS)
3503
- );
3504
- latestRuntime = await fetchVmRuntime({
3505
- itemHash: args.itemHash,
3506
- fetch: (url, init) => fetch(url, init),
3507
- crnHash: runtime.selectedCrn?.hash ?? runtime.allocation?.crnHash ?? void 0,
3508
- crns: this.state.crns,
3509
- crnListUrl: this.props.crnListUrl
3510
- }).catch(() => latestRuntime);
3511
- this.emitProgress({
3512
- stage: "deployment-confirmed",
3513
- label: "Waiting for HTTPS route",
3514
- progress: 93,
3515
- status: latestRuntime.webAccess?.active === true ? "info" : "warning",
3516
- itemHash: args.itemHash,
3517
- detail: `2n6 activation ${attempt + 1}/${UI_RUNTIME_WAIT_ATTEMPTS}: ${latestRuntime.webAccess?.active === true ? "active" : "still pending"} for ${latestRuntime.proxyUrl ?? runtime.proxyUrl ?? "reserved proxy URL"}.`
3518
- });
3519
- this.trace("deploy:bootstrap-proxy-activation", {
3520
- itemHash: args.itemHash,
3521
- attempt: attempt + 1,
3522
- attempts: UI_RUNTIME_WAIT_ATTEMPTS,
3523
- active: latestRuntime.webAccess?.active === true,
3524
- proxyUrl: latestRuntime.proxyUrl ?? null
3525
- });
3526
- }
3527
- runtime = latestRuntime;
3528
- mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
3529
- }
3530
- if (!runtime.hostIpv4) {
3531
- throw new Error("Relay runtime lost its host IPv4 address while waiting for HTTPS activation.");
3532
- }
3533
- const runtimeHostIpv4 = runtime.hostIpv4;
3534
- const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
3535
3408
  this.emitProgress({
3536
3409
  stage: "deployment-confirmed",
3537
3410
  label: "Publishing relay config",
@@ -3549,9 +3422,9 @@ var SponsorRelayController = class {
3549
3422
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3550
3423
  status: "pending",
3551
3424
  runtime: {
3552
- publicIpv4: runtimeHostIpv4,
3553
- publicIpv6: runtime.ipv6 ?? null,
3554
- proxyUrl: activeProxyUrl,
3425
+ publicIpv4: args.runtime.hostIpv4,
3426
+ publicIpv6: args.runtime.ipv6 ?? null,
3427
+ proxyUrl: args.runtime.proxyUrl ?? null,
3555
3428
  mappedPorts: mappedPorts2
3556
3429
  },
3557
3430
  bootstrap: {
@@ -3580,164 +3453,49 @@ var SponsorRelayController = class {
3580
3453
  aggregateItemHash: bootstrapConfigPublication.aggregateItemHash,
3581
3454
  aggregateStatus: bootstrapConfigPublication.aggregateStatus
3582
3455
  });
3583
- this.emitProgress({
3584
- stage: "deployment-confirmed",
3585
- label: "Waiting for guest config",
3586
- progress: 94,
3587
- status: "info",
3588
- itemHash: args.itemHash,
3589
- detail: "Waiting for the relay VM to acknowledge that it consumed the Aleph bootstrap config."
3590
- });
3591
- const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
3592
- deploymentToken: args.deploymentToken,
3593
- ownerAddress: this.state.wallet.address,
3594
- instanceItemHash: args.itemHash,
3595
- fetch: asJsonFetch,
3596
- apiHost: this.client.apiHost,
3597
- attempts: 40,
3598
- delayMs: 3e3
3599
- });
3600
- this.trace("deploy:bootstrap-config-signal-visible", {
3601
- itemHash: args.itemHash,
3602
- deploymentToken: args.deploymentToken,
3603
- bootstrapConfigSignal
3604
- });
3605
- if (!bootstrapConfigSignal) {
3606
- throw new Error(
3607
- "The relay VM did not confirm that it applied the Aleph bootstrap config in time."
3608
- );
3609
- }
3610
- bootstrapSignalRelayMetadata = typeof bootstrapConfigSignal.peerId === "string" && bootstrapConfigSignal.peerId.length > 0 && Array.isArray(bootstrapConfigSignal.probeMultiaddrs) && bootstrapConfigSignal.probeMultiaddrs.length > 0 ? {
3611
- peerId: bootstrapConfigSignal.peerId,
3612
- probeMultiaddrs: bootstrapConfigSignal.probeMultiaddrs,
3613
- browserBootstrapMultiaddrs: bootstrapConfigSignal.browserBootstrapMultiaddrs ?? []
3614
- } : null;
3615
- if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
3616
- this.trace("deploy:bootstrap-proxy-fallback", {
3617
- itemHash: args.itemHash,
3618
- deploymentToken: args.deploymentToken,
3619
- proxyUrl: runtime.proxyUrl,
3620
- bootstrapSignalRelayMetadata
3621
- });
3622
- this.emitProgress({
3623
- stage: "deployment-confirmed",
3624
- label: "Continuing without HTTPS route",
3625
- progress: 94,
3626
- status: "warning",
3456
+ }
3457
+ this.emitProgress({
3458
+ stage: "publishing-bootstrap",
3459
+ label: "Reading relay multiaddrs",
3460
+ progress: 95,
3461
+ status: "info",
3462
+ itemHash: args.itemHash,
3463
+ detail: "Waiting for the relay to report its final public multiaddrs."
3464
+ });
3465
+ const relayMetadata = await fetchRelayMetadataForRuntime({
3466
+ runtime: args.runtime,
3467
+ fetch: (url, init) => fetch(url, init),
3468
+ preferSecureMetadata: profile === "uc-go-peer",
3469
+ attempts: 80,
3470
+ delayMs: 3e3,
3471
+ timeoutMs: 24e4,
3472
+ onAttempt: (result) => {
3473
+ const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
3474
+ const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
3475
+ this.trace("deploy:relay-metadata-attempt", {
3627
3476
  itemHash: args.itemHash,
3628
- detail: "The 2n6 HTTPS route stayed inactive, so guest setup continued without Caddy. Secure browser routing can be enabled later when the proxy becomes available."
3477
+ attempt: result.attempt,
3478
+ attempts: result.attempts,
3479
+ requestUrl: result.requestUrl,
3480
+ metadataUrl: result.metadataUrl,
3481
+ hostIpv4: result.hostIpv4,
3482
+ setupPort: result.setupPort,
3483
+ ok: result.ok,
3484
+ status: result.status,
3485
+ ready: result.ready,
3486
+ error: result.error ?? null,
3487
+ payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
3488
+ peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
3489
+ probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
3490
+ browserBootstrapMultiaddrCount: Array.isArray(
3491
+ metadata?.browser_bootstrap_multiaddrs
3492
+ ) ? metadata.browser_bootstrap_multiaddrs.length : 0
3629
3493
  });
3630
- runtime = {
3631
- ...runtime,
3632
- proxyUrl: null,
3633
- webAccess: runtime.webAccess ? {
3634
- ...runtime.webAccess,
3635
- active: false
3636
- } : runtime.webAccess
3637
- };
3638
3494
  }
3639
- }
3640
- let relayMetadata = bootstrapSignalRelayMetadata;
3641
- if (relayMetadata) {
3642
- this.trace("deploy:relay-metadata-from-bootstrap-signal", {
3643
- itemHash: args.itemHash,
3644
- peerId: relayMetadata.peerId,
3645
- probeMultiaddrCount: relayMetadata.probeMultiaddrs.length,
3646
- browserBootstrapMultiaddrCount: relayMetadata.browserBootstrapMultiaddrs.length
3647
- });
3648
- } else {
3649
- this.emitProgress({
3650
- stage: "publishing-bootstrap",
3651
- label: "Reading relay multiaddrs",
3652
- progress: 95,
3653
- status: "info",
3654
- itemHash: args.itemHash,
3655
- detail: "Waiting for the relay to report its final public multiaddrs."
3656
- });
3657
- relayMetadata = await fetchRelayMetadataForRuntime({
3658
- runtime,
3659
- fetch: (url, init) => fetch(url, init),
3660
- preferSecureMetadata: profile === "uc-go-peer" && Boolean(runtime.proxyUrl),
3661
- attempts: 80,
3662
- delayMs: 3e3,
3663
- timeoutMs: 24e4,
3664
- onAttempt: (result) => {
3665
- const payload = result.payload && typeof result.payload === "object" ? result.payload : null;
3666
- const metadata = payload?.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
3667
- this.trace("deploy:relay-metadata-attempt", {
3668
- itemHash: args.itemHash,
3669
- attempt: result.attempt,
3670
- attempts: result.attempts,
3671
- requestUrl: result.requestUrl,
3672
- metadataUrl: result.metadataUrl,
3673
- hostIpv4: result.hostIpv4,
3674
- setupPort: result.setupPort,
3675
- ok: result.ok,
3676
- status: result.status,
3677
- ready: result.ready,
3678
- error: result.error ?? null,
3679
- payloadStatus: payload && typeof payload.status === "string" ? payload.status : null,
3680
- peerId: metadata && typeof metadata.peer_id === "string" ? metadata.peer_id : null,
3681
- probeMultiaddrCount: Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.length : 0,
3682
- browserBootstrapMultiaddrCount: Array.isArray(
3683
- metadata?.browser_bootstrap_multiaddrs
3684
- ) ? metadata.browser_bootstrap_multiaddrs.length : 0
3685
- });
3686
- }
3687
- });
3688
- }
3495
+ });
3689
3496
  if (!relayMetadata) {
3690
3497
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3691
3498
  }
3692
- if (profile === "uc-go-peer" && runtime.proxyUrl) {
3693
- this.emitProgress({
3694
- stage: "publishing-bootstrap",
3695
- label: "Verifying reachability",
3696
- progress: 96,
3697
- status: "info",
3698
- itemHash: args.itemHash,
3699
- detail: "Checking that the secure relay endpoint is reachable before trusting the deployment as ready."
3700
- });
3701
- const verificationUrl = (() => {
3702
- try {
3703
- return new URL("/bootstrap/metadata", runtime.proxyUrl).toString();
3704
- } catch {
3705
- return runtime.proxyUrl ?? "";
3706
- }
3707
- })();
3708
- let verificationResult = null;
3709
- for (let attempt = 0; attempt < UI_REACHABILITY_VERIFY_ATTEMPTS; attempt += 1) {
3710
- verificationResult = await verifyUcGoPeerReachability({
3711
- proxyUrl: verificationUrl,
3712
- verifyProxyHttp: true,
3713
- fetch: (url, init) => fetch(url, init),
3714
- tcpProbe: async () => ({ ok: null })
3715
- });
3716
- this.trace("deploy:reachability-verification-attempt", {
3717
- itemHash: args.itemHash,
3718
- attempt: attempt + 1,
3719
- attempts: UI_REACHABILITY_VERIFY_ATTEMPTS,
3720
- verificationUrl,
3721
- ok: verificationResult.ok,
3722
- checks: verificationResult.checks
3723
- });
3724
- const proxyHttpCheck2 = verificationResult.checks["https:proxy"];
3725
- if (verificationResult.ok && proxyHttpCheck2?.ok === true) {
3726
- break;
3727
- }
3728
- if (attempt < UI_REACHABILITY_VERIFY_ATTEMPTS - 1) {
3729
- await new Promise(
3730
- (resolve) => globalThis.setTimeout(resolve, UI_REACHABILITY_VERIFY_DELAY_MS)
3731
- );
3732
- }
3733
- }
3734
- const proxyHttpCheck = verificationResult?.checks["https:proxy"];
3735
- if (!verificationResult?.ok || proxyHttpCheck?.ok !== true) {
3736
- throw new Error(
3737
- proxyHttpCheck?.error ? `Secure relay endpoint did not become reachable in time: ${proxyHttpCheck.error}` : "Secure relay endpoint did not become reachable in time."
3738
- );
3739
- }
3740
- }
3741
3499
  const { peerId } = relayMetadata;
3742
3500
  const registrationId = createRelayBootstrapRegistrationId(
3743
3501
  profile,
@@ -3855,7 +3613,6 @@ var SponsorRelayController = class {
3855
3613
  const currentProgress = this.state.deploymentProgress;
3856
3614
  const currentProgressIsForLatestHash = currentProgress.itemHash === itemHash || currentProgress.itemHash == null && currentProgress.stage !== "idle" && currentProgress.stage !== "error";
3857
3615
  const keepTerminalSuccess = this.lastCompletedDeploymentHash === itemHash || currentProgressIsForLatestHash && currentProgress.stage === "completed" && currentProgress.status === "success";
3858
- const keepSpecificInFlightProgress = currentProgressIsForLatestHash && currentProgress.stage !== "completed" && currentProgress.stage !== "error" && currentProgress.progress >= 93;
3859
3616
  const status = latest.details.messageStatus;
3860
3617
  if (status === "rejected") {
3861
3618
  this.emitProgress({
@@ -3898,16 +3655,6 @@ var SponsorRelayController = class {
3898
3655
  });
3899
3656
  return;
3900
3657
  }
3901
- if (keepSpecificInFlightProgress) {
3902
- this.trace("progress:retain-inflight", {
3903
- reason: "latest refresh would overwrite a more specific in-flight deployment phase",
3904
- itemHash,
3905
- currentStage: currentProgress.stage,
3906
- currentLabel: currentProgress.label,
3907
- currentProgress: currentProgress.progress
3908
- });
3909
- return;
3910
- }
3911
3658
  this.emitProgress({
3912
3659
  stage: "deployment-confirmed",
3913
3660
  label: "Waiting for runtime",
@@ -3918,16 +3665,6 @@ var SponsorRelayController = class {
3918
3665
  });
3919
3666
  return;
3920
3667
  }
3921
- if (keepSpecificInFlightProgress) {
3922
- this.trace("progress:retain-inflight", {
3923
- reason: "latest refresh would overwrite a more specific in-flight deployment phase",
3924
- itemHash,
3925
- currentStage: currentProgress.stage,
3926
- currentLabel: currentProgress.label,
3927
- currentProgress: currentProgress.progress
3928
- });
3929
- return;
3930
- }
3931
3668
  this.emitProgress({
3932
3669
  stage: "deployment-confirmed",
3933
3670
  label: "Runtime ready",
@@ -4006,11 +3743,16 @@ var SponsorRelayController = class {
4006
3743
  this.refreshTimer = setInterval(() => {
4007
3744
  void this.refresh();
4008
3745
  }, REFRESH_INTERVAL_MS);
3746
+ this.pingTimer = setInterval(() => {
3747
+ void this.refreshRelayPing();
3748
+ }, RELAY_PING_INTERVAL_MS);
3749
+ await this.refreshRelayPing();
4009
3750
  this.patch({ ready: true });
4010
3751
  this.trace("init:ready");
4011
3752
  }
4012
3753
  destroy() {
4013
3754
  if (this.refreshTimer) clearInterval(this.refreshTimer);
3755
+ if (this.pingTimer) clearInterval(this.pingTimer);
4014
3756
  if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
4015
3757
  this.stopWalletWatch?.();
4016
3758
  }
@@ -4305,6 +4047,11 @@ var SponsorRelayController = class {
4305
4047
  });
4306
4048
  }
4307
4049
  }
4050
+ async refreshRelayPing() {
4051
+ const relayPing = await pingPeer(this.props.libp2p);
4052
+ this.patch({ relayPing });
4053
+ this.trace("libp2p:relay-ping", relayPing);
4054
+ }
4308
4055
  async deploy() {
4309
4056
  this.trace("deploy:start", {
4310
4057
  wallet: this.state.wallet.address,
@@ -4597,15 +4344,6 @@ var SponsorRelayController = class {
4597
4344
  });
4598
4345
  if (attemptItemHash) {
4599
4346
  if (attemptDeploymentToken) {
4600
- this.emitProgress({
4601
- stage: "refreshing-instances",
4602
- label: "Cleaning up failed attempt",
4603
- progress: 16,
4604
- status: "warning",
4605
- itemHash: attemptItemHash,
4606
- detail: `Removing bootstrap handoff state for failed deployment on ${attemptLabel}.`,
4607
- error: lastError.message
4608
- });
4609
4347
  this.trace("deploy:bootstrap-config-cleanup-start", {
4610
4348
  itemHash: attemptItemHash,
4611
4349
  deploymentToken: attemptDeploymentToken
@@ -4631,26 +4369,8 @@ var SponsorRelayController = class {
4631
4369
  deploymentToken: attemptDeploymentToken,
4632
4370
  error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
4633
4371
  });
4634
- this.emitProgress({
4635
- stage: "refreshing-instances",
4636
- label: "Cleanup warning",
4637
- progress: 16,
4638
- status: "warning",
4639
- itemHash: attemptItemHash,
4640
- detail: `Bootstrap cleanup reported an issue for ${attemptLabel}, but retry cleanup will continue.`,
4641
- error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
4642
- });
4643
4372
  });
4644
4373
  }
4645
- this.emitProgress({
4646
- stage: "refreshing-instances",
4647
- label: "Discarding failed deployment",
4648
- progress: 17,
4649
- status: "warning",
4650
- itemHash: attemptItemHash,
4651
- detail: `Forgetting failed deployment ${attemptItemHash.slice(0, 12)} on ${attemptLabel} before retrying.`,
4652
- error: lastError.message
4653
- });
4654
4374
  this.trace("deploy:forget-failed-attempt-start", {
4655
4375
  itemHash: attemptItemHash,
4656
4376
  crnHash: candidateCrn.hash,
@@ -4673,30 +4393,12 @@ var SponsorRelayController = class {
4673
4393
  itemHash: attemptItemHash,
4674
4394
  error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
4675
4395
  });
4676
- this.emitProgress({
4677
- stage: "refreshing-instances",
4678
- label: "Cleanup warning",
4679
- progress: 17,
4680
- status: "warning",
4681
- itemHash: attemptItemHash,
4682
- detail: `Failed deployment cleanup reported an issue for ${attemptLabel}.`,
4683
- error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
4684
- });
4685
4396
  });
4686
4397
  this.trace("deploy:forget-failed-attempt-complete", {
4687
4398
  itemHash: attemptItemHash,
4688
4399
  crnHash: candidateCrn.hash,
4689
4400
  crnName: candidateCrn.name
4690
4401
  });
4691
- this.emitProgress({
4692
- stage: "refreshing-instances",
4693
- label: "Cleanup complete",
4694
- progress: 18,
4695
- status: "info",
4696
- itemHash: attemptItemHash,
4697
- detail: `Finished cleaning up failed deployment on ${attemptLabel}.`,
4698
- error: null
4699
- });
4700
4402
  }
4701
4403
  if (candidateIndex < orderedCrns.length - 1) {
4702
4404
  this.emitProgress({
@@ -4887,9 +4589,10 @@ function useSponsorRelayController(props) {
4887
4589
  );
4888
4590
  useEffect(() => {
4889
4591
  controller.updateProps({
4592
+ libp2p: props.libp2p,
4890
4593
  debug: props.debug
4891
4594
  });
4892
- }, [controller, props.debug]);
4595
+ }, [controller, props.debug, props.libp2p]);
4893
4596
  useEffect(() => {
4894
4597
  void controller.init();
4895
4598
  return () => {