@le-space/ui 0.3.26 → 0.3.28

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.26",
3
+ "version": "0.3.28",
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.26",
40
- "@le-space/core": "0.3.26",
39
+ "@le-space/browser": "0.3.28",
40
+ "@le-space/core": "0.3.28",
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.3.26";
31
+ var UI_PACKAGE_VERSION = "0.3.28";
32
32
 
33
33
  // src/shared/format.ts
34
34
  function shortHash(value, head = 8, tail = 6) {
@@ -1237,16 +1237,6 @@ function normalizeBroadcastStatus(httpStatus, responseStatus) {
1237
1237
  if (httpStatus === 202) return "pending";
1238
1238
  return normalizeMessageStatus2(responseStatus);
1239
1239
  }
1240
- function isInvalidMessageFormatResponse2(response, payload) {
1241
- if (response.status !== 422) return false;
1242
- const details = payload?.details;
1243
- if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
1244
- if (details && typeof details === "object") {
1245
- const detailMessage = details.message;
1246
- if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
1247
- }
1248
- return false;
1249
- }
1250
1240
  function isRetryableBroadcastFailure(response, payload) {
1251
1241
  if (response.status >= 500) return true;
1252
1242
  const publicationStatus = payload?.publication_status?.status;
@@ -1268,25 +1258,22 @@ async function postBroadcastPayload2(body, options) {
1268
1258
  };
1269
1259
  }
1270
1260
  async function broadcastAlephMessage2(message, options) {
1271
- const attempts = [
1272
- { sync: options.sync ?? false, message },
1273
- { ...message, sync: options.sync ?? false },
1274
- { ...message }
1275
- ];
1276
- for (let index = 0; index < attempts.length; index += 1) {
1277
- const result = await postBroadcastPayload2(attempts[index], {
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, {
1278
1265
  apiHost: options.apiHost,
1279
1266
  fetch: options.fetch
1280
1267
  });
1281
1268
  if (result.httpStatus === 202 || normalizeBroadcastStatus(result.httpStatus, result.response?.message_status) !== "unknown" || result.httpStatus >= 200 && result.httpStatus < 300) {
1282
1269
  return result;
1283
1270
  }
1284
- const canRetry = index < attempts.length - 1 && (isInvalidMessageFormatResponse2({ status: result.httpStatus }, result.response) || isRetryableBroadcastFailure({ status: result.httpStatus }, result.response));
1271
+ const canRetry = index < maxAttempts - 1 && isRetryableBroadcastFailure({ status: result.httpStatus }, result.response);
1285
1272
  if (!canRetry) {
1286
1273
  throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response ?? {})}`);
1287
1274
  }
1288
1275
  }
1289
- throw new Error("Broadcast failed: no compatible request format was accepted");
1276
+ throw new Error("Broadcast failed: retry budget exhausted");
1290
1277
  }
1291
1278
 
1292
1279
  // ../core/src/forget.ts
@@ -1891,6 +1878,13 @@ async function waitForVmRuntime(args) {
1891
1878
  }
1892
1879
 
1893
1880
  // ../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
+ }
1894
1888
  async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1895
1889
  try {
1896
1890
  const controller = new AbortController();
@@ -2132,6 +2126,59 @@ async function fetchUcGoPeerMetadata(args) {
2132
2126
  `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2133
2127
  );
2134
2128
  }
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
+ }
2135
2182
 
2136
2183
  // ../aleph-bootstrap/src/index.ts
2137
2184
  import { recoverMessageAddress } from "viem";
@@ -3232,6 +3279,8 @@ var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3232
3279
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3233
3280
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
3234
3281
  var UI_RUNTIME_WAIT_DELAY_MS = 5e3;
3282
+ var UI_REACHABILITY_VERIFY_ATTEMPTS = 25;
3283
+ var UI_REACHABILITY_VERIFY_DELAY_MS = 3e3;
3235
3284
  function hasUsableRuntime(details) {
3236
3285
  return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
3237
3286
  }
@@ -3338,15 +3387,16 @@ var SponsorRelayController = class {
3338
3387
  this.progressEmitter.emit(nextEvent);
3339
3388
  }
3340
3389
  async configureRelayBootstrapRegistration(args) {
3390
+ let runtime = args.runtime;
3341
3391
  const profile = relayProfileForManifest(this.state.manifest);
3342
3392
  if (!profile) return;
3343
3393
  if (!this.state.wallet.address) {
3344
3394
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3345
3395
  }
3346
- if (!args.runtime.hostIpv4) {
3396
+ if (!runtime.hostIpv4) {
3347
3397
  throw new Error("Relay runtime did not expose a host IPv4 address.");
3348
3398
  }
3349
- const mappedPorts2 = args.runtime.mappedPorts ?? {};
3399
+ let mappedPorts2 = runtime.mappedPorts ?? {};
3350
3400
  const setupPort = mappedPorts2["80"]?.host ?? null;
3351
3401
  if (!setupPort) {
3352
3402
  throw new Error("Relay runtime did not expose the temporary setup endpoint.");
@@ -3361,7 +3411,7 @@ var SponsorRelayController = class {
3361
3411
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3362
3412
  });
3363
3413
  const setupHealth = await waitForSetupEndpoint({
3364
- hostIpv4: args.runtime.hostIpv4,
3414
+ hostIpv4: runtime.hostIpv4,
3365
3415
  setupPort,
3366
3416
  fetch: (url, init) => fetch(url, init),
3367
3417
  attempts: 15,
@@ -3387,12 +3437,12 @@ var SponsorRelayController = class {
3387
3437
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3388
3438
  }
3389
3439
  const configureResult = await configureOrbitdbRelaySetup2({
3390
- hostIpv4: args.runtime.hostIpv4,
3391
- publicIpv6: args.runtime.ipv6,
3440
+ hostIpv4: runtime.hostIpv4,
3441
+ publicIpv6: runtime.ipv6,
3392
3442
  setupPort,
3393
3443
  tcpPort,
3394
3444
  wsPort,
3395
- proxyUrl: args.runtime.proxyUrl ?? void 0,
3445
+ proxyUrl: runtime.proxyUrl ?? void 0,
3396
3446
  metricsPort: mappedPorts2["9090"]?.host ?? null,
3397
3447
  metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
3398
3448
  webrtcPort: mappedPorts2["9093"]?.host ?? null,
@@ -3409,6 +3459,51 @@ var SponsorRelayController = class {
3409
3459
  if (!args.deploymentToken) {
3410
3460
  throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
3411
3461
  }
3462
+ if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
3463
+ this.emitProgress({
3464
+ stage: "deployment-confirmed",
3465
+ label: "Waiting for HTTPS route",
3466
+ progress: 93,
3467
+ status: "warning",
3468
+ itemHash: args.itemHash,
3469
+ detail: "The relay received a 2n6 hostname, but it is not active yet. Waiting before handing HTTPS config to the guest."
3470
+ });
3471
+ let latestRuntime = runtime;
3472
+ for (let attempt = 0; attempt < UI_RUNTIME_WAIT_ATTEMPTS; attempt += 1) {
3473
+ if (latestRuntime.webAccess?.active === true) {
3474
+ break;
3475
+ }
3476
+ await new Promise(
3477
+ (resolve) => globalThis.setTimeout(resolve, UI_RUNTIME_WAIT_DELAY_MS)
3478
+ );
3479
+ latestRuntime = await fetchVmRuntime({
3480
+ itemHash: args.itemHash,
3481
+ fetch: (url, init) => fetch(url, init),
3482
+ crnHash: runtime.selectedCrn?.hash ?? runtime.allocation?.crnHash ?? void 0,
3483
+ crns: this.state.crns,
3484
+ crnListUrl: this.props.crnListUrl
3485
+ }).catch(() => latestRuntime);
3486
+ this.trace("deploy:bootstrap-proxy-activation", {
3487
+ itemHash: args.itemHash,
3488
+ attempt: attempt + 1,
3489
+ attempts: UI_RUNTIME_WAIT_ATTEMPTS,
3490
+ active: latestRuntime.webAccess?.active === true,
3491
+ proxyUrl: latestRuntime.proxyUrl ?? null
3492
+ });
3493
+ }
3494
+ runtime = latestRuntime;
3495
+ mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
3496
+ if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
3497
+ throw new Error(
3498
+ "Aleph reserved a 2n6 proxy URL, but it never became active in time. Aborting guest HTTPS configuration to avoid a broken Caddy setup."
3499
+ );
3500
+ }
3501
+ }
3502
+ if (!runtime.hostIpv4) {
3503
+ throw new Error("Relay runtime lost its host IPv4 address while waiting for HTTPS activation.");
3504
+ }
3505
+ const runtimeHostIpv4 = runtime.hostIpv4;
3506
+ const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
3412
3507
  this.emitProgress({
3413
3508
  stage: "deployment-confirmed",
3414
3509
  label: "Publishing relay config",
@@ -3426,9 +3521,9 @@ var SponsorRelayController = class {
3426
3521
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3427
3522
  status: "pending",
3428
3523
  runtime: {
3429
- publicIpv4: args.runtime.hostIpv4,
3430
- publicIpv6: args.runtime.ipv6 ?? null,
3431
- proxyUrl: args.runtime.proxyUrl ?? null,
3524
+ publicIpv4: runtimeHostIpv4,
3525
+ publicIpv6: runtime.ipv6 ?? null,
3526
+ proxyUrl: activeProxyUrl,
3432
3527
  mappedPorts: mappedPorts2
3433
3528
  },
3434
3529
  bootstrap: {
@@ -3494,7 +3589,7 @@ var SponsorRelayController = class {
3494
3589
  detail: "Waiting for the relay to report its final public multiaddrs."
3495
3590
  });
3496
3591
  const relayMetadata = await fetchRelayMetadataForRuntime({
3497
- runtime: args.runtime,
3592
+ runtime,
3498
3593
  fetch: (url, init) => fetch(url, init),
3499
3594
  preferSecureMetadata: profile === "uc-go-peer",
3500
3595
  attempts: 80,
@@ -3527,6 +3622,55 @@ var SponsorRelayController = class {
3527
3622
  if (!relayMetadata) {
3528
3623
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3529
3624
  }
3625
+ if (profile === "uc-go-peer" && runtime.proxyUrl) {
3626
+ this.emitProgress({
3627
+ stage: "publishing-bootstrap",
3628
+ label: "Verifying reachability",
3629
+ progress: 96,
3630
+ status: "info",
3631
+ itemHash: args.itemHash,
3632
+ detail: "Checking that the secure relay endpoint is reachable before trusting the deployment as ready."
3633
+ });
3634
+ const verificationUrl = (() => {
3635
+ try {
3636
+ return new URL("/bootstrap/metadata", runtime.proxyUrl).toString();
3637
+ } catch {
3638
+ return runtime.proxyUrl ?? "";
3639
+ }
3640
+ })();
3641
+ let verificationResult = null;
3642
+ for (let attempt = 0; attempt < UI_REACHABILITY_VERIFY_ATTEMPTS; attempt += 1) {
3643
+ verificationResult = await verifyUcGoPeerReachability({
3644
+ proxyUrl: verificationUrl,
3645
+ verifyProxyHttp: true,
3646
+ fetch: (url, init) => fetch(url, init),
3647
+ tcpProbe: async () => ({ ok: null })
3648
+ });
3649
+ this.trace("deploy:reachability-verification-attempt", {
3650
+ itemHash: args.itemHash,
3651
+ attempt: attempt + 1,
3652
+ attempts: UI_REACHABILITY_VERIFY_ATTEMPTS,
3653
+ verificationUrl,
3654
+ ok: verificationResult.ok,
3655
+ checks: verificationResult.checks
3656
+ });
3657
+ const proxyHttpCheck2 = verificationResult.checks["https:proxy"];
3658
+ if (verificationResult.ok && proxyHttpCheck2?.ok === true) {
3659
+ break;
3660
+ }
3661
+ if (attempt < UI_REACHABILITY_VERIFY_ATTEMPTS - 1) {
3662
+ await new Promise(
3663
+ (resolve) => globalThis.setTimeout(resolve, UI_REACHABILITY_VERIFY_DELAY_MS)
3664
+ );
3665
+ }
3666
+ }
3667
+ const proxyHttpCheck = verificationResult?.checks["https:proxy"];
3668
+ if (!verificationResult?.ok || proxyHttpCheck?.ok !== true) {
3669
+ throw new Error(
3670
+ proxyHttpCheck?.error ? `Secure relay endpoint did not become reachable in time: ${proxyHttpCheck.error}` : "Secure relay endpoint did not become reachable in time."
3671
+ );
3672
+ }
3673
+ }
3530
3674
  const { peerId } = relayMetadata;
3531
3675
  const registrationId = createRelayBootstrapRegistrationId(
3532
3676
  profile,
package/shared/index.d.ts CHANGED
@@ -290,7 +290,7 @@ declare const STALE_INSTANCE_ALLOCATION_COOLDOWN_MS: number;
290
290
  declare const RECENT_INSTANCE_RUNTIME_GRACE_MS: number;
291
291
  declare const DEPLOYMENT_PENDING_WARNING_MS: number;
292
292
 
293
- declare const UI_PACKAGE_VERSION = "0.3.26";
293
+ declare const UI_PACKAGE_VERSION = "0.3.28";
294
294
 
295
295
  interface RootfsRequiredPortForward {
296
296
  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.3.26";
30
+ var UI_PACKAGE_VERSION = "0.3.28";
31
31
 
32
32
  // src/shared/format.ts
33
33
  function shortHash(value, head = 8, tail = 6) {
@@ -1236,16 +1236,6 @@ function normalizeBroadcastStatus(httpStatus, responseStatus) {
1236
1236
  if (httpStatus === 202) return "pending";
1237
1237
  return normalizeMessageStatus2(responseStatus);
1238
1238
  }
1239
- function isInvalidMessageFormatResponse2(response, payload) {
1240
- if (response.status !== 422) return false;
1241
- const details = payload?.details;
1242
- if (typeof details === "string" && details.includes("InvalidMessageFormat")) return true;
1243
- if (details && typeof details === "object") {
1244
- const detailMessage = details.message;
1245
- if (typeof detailMessage === "string" && detailMessage.includes("InvalidMessageFormat")) return true;
1246
- }
1247
- return false;
1248
- }
1249
1239
  function isRetryableBroadcastFailure(response, payload) {
1250
1240
  if (response.status >= 500) return true;
1251
1241
  const publicationStatus = payload?.publication_status?.status;
@@ -1267,25 +1257,22 @@ async function postBroadcastPayload2(body, options) {
1267
1257
  };
1268
1258
  }
1269
1259
  async function broadcastAlephMessage2(message, options) {
1270
- const attempts = [
1271
- { sync: options.sync ?? false, message },
1272
- { ...message, sync: options.sync ?? false },
1273
- { ...message }
1274
- ];
1275
- for (let index = 0; index < attempts.length; index += 1) {
1276
- const result = await postBroadcastPayload2(attempts[index], {
1260
+ const requestBody = { sync: options.sync ?? false, message };
1261
+ const maxAttempts = 3;
1262
+ for (let index = 0; index < maxAttempts; index += 1) {
1263
+ const result = await postBroadcastPayload2(requestBody, {
1277
1264
  apiHost: options.apiHost,
1278
1265
  fetch: options.fetch
1279
1266
  });
1280
1267
  if (result.httpStatus === 202 || normalizeBroadcastStatus(result.httpStatus, result.response?.message_status) !== "unknown" || result.httpStatus >= 200 && result.httpStatus < 300) {
1281
1268
  return result;
1282
1269
  }
1283
- const canRetry = index < attempts.length - 1 && (isInvalidMessageFormatResponse2({ status: result.httpStatus }, result.response) || isRetryableBroadcastFailure({ status: result.httpStatus }, result.response));
1270
+ const canRetry = index < maxAttempts - 1 && isRetryableBroadcastFailure({ status: result.httpStatus }, result.response);
1284
1271
  if (!canRetry) {
1285
1272
  throw new Error(`Broadcast failed: ${result.httpStatus} ${JSON.stringify(result.response ?? {})}`);
1286
1273
  }
1287
1274
  }
1288
- throw new Error("Broadcast failed: no compatible request format was accepted");
1275
+ throw new Error("Broadcast failed: retry budget exhausted");
1289
1276
  }
1290
1277
 
1291
1278
  // ../core/src/forget.ts
@@ -1890,6 +1877,13 @@ async function waitForVmRuntime(args) {
1890
1877
  }
1891
1878
 
1892
1879
  // ../core/src/guest.ts
1880
+ function proxyHostnameFromUrl(value) {
1881
+ try {
1882
+ return value ? new URL(value).hostname : null;
1883
+ } catch {
1884
+ return null;
1885
+ }
1886
+ }
1893
1887
  async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1894
1888
  try {
1895
1889
  const controller = new AbortController();
@@ -2131,6 +2125,59 @@ async function fetchUcGoPeerMetadata(args) {
2131
2125
  `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2132
2126
  );
2133
2127
  }
2128
+ async function verifyUcGoPeerReachability(args) {
2129
+ const checks = {};
2130
+ const mappedPorts2 = args.mappedPorts ?? {};
2131
+ const hostIpv4 = args.hostIpv4;
2132
+ const skippedInternalPorts = new Set(
2133
+ (args.skipInternalPorts ?? ["80"]).map((value) => String(value))
2134
+ );
2135
+ const httpProbe = args.httpProbe ?? ((url, timeoutMs) => defaultHttpProbe(args.fetch, url, timeoutMs));
2136
+ if (hostIpv4) {
2137
+ for (const [internalPort, mapping] of Object.entries(mappedPorts2)) {
2138
+ if (skippedInternalPorts.has(String(internalPort))) continue;
2139
+ if (mapping?.tcp === true && mapping?.host) {
2140
+ checks[`tcp:${internalPort}`] = {
2141
+ host: hostIpv4,
2142
+ port: mapping.host,
2143
+ ...await args.tcpProbe(
2144
+ hostIpv4,
2145
+ mapping.host,
2146
+ Number(args.tcpTimeoutMs ?? 5e3)
2147
+ )
2148
+ };
2149
+ } else if (mapping?.udp === true && mapping?.host) {
2150
+ checks[`udp:${internalPort}`] = {
2151
+ host: hostIpv4,
2152
+ port: mapping.host,
2153
+ ok: null,
2154
+ note: "UDP mapping published; CI does not perform an application-level UDP handshake probe."
2155
+ };
2156
+ }
2157
+ }
2158
+ }
2159
+ if (args.proxyUrl && args.verifyProxyHttp !== false) {
2160
+ checks["https:proxy"] = await httpProbe(
2161
+ args.proxyUrl,
2162
+ Number(args.httpTimeoutMs ?? 1e4)
2163
+ );
2164
+ }
2165
+ const proxyHostname = proxyHostnameFromUrl(args.proxyUrl);
2166
+ if (proxyHostname) {
2167
+ checks["tcp:proxy-443"] = await args.tcpProbe(
2168
+ proxyHostname,
2169
+ 443,
2170
+ Number(args.tcpTimeoutMs ?? 5e3)
2171
+ );
2172
+ }
2173
+ const failedChecks = Object.entries(checks).filter(
2174
+ ([, value]) => value?.ok === false
2175
+ );
2176
+ return {
2177
+ ok: failedChecks.length === 0,
2178
+ checks
2179
+ };
2180
+ }
2134
2181
 
2135
2182
  // ../aleph-bootstrap/src/index.ts
2136
2183
  import { recoverMessageAddress } from "viem";
@@ -3231,6 +3278,8 @@ var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3231
3278
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3232
3279
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
3233
3280
  var UI_RUNTIME_WAIT_DELAY_MS = 5e3;
3281
+ var UI_REACHABILITY_VERIFY_ATTEMPTS = 25;
3282
+ var UI_REACHABILITY_VERIFY_DELAY_MS = 3e3;
3234
3283
  function hasUsableRuntime(details) {
3235
3284
  return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
3236
3285
  }
@@ -3337,15 +3386,16 @@ var SponsorRelayController = class {
3337
3386
  this.progressEmitter.emit(nextEvent);
3338
3387
  }
3339
3388
  async configureRelayBootstrapRegistration(args) {
3389
+ let runtime = args.runtime;
3340
3390
  const profile = relayProfileForManifest(this.state.manifest);
3341
3391
  if (!profile) return;
3342
3392
  if (!this.state.wallet.address) {
3343
3393
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3344
3394
  }
3345
- if (!args.runtime.hostIpv4) {
3395
+ if (!runtime.hostIpv4) {
3346
3396
  throw new Error("Relay runtime did not expose a host IPv4 address.");
3347
3397
  }
3348
- const mappedPorts2 = args.runtime.mappedPorts ?? {};
3398
+ let mappedPorts2 = runtime.mappedPorts ?? {};
3349
3399
  const setupPort = mappedPorts2["80"]?.host ?? null;
3350
3400
  if (!setupPort) {
3351
3401
  throw new Error("Relay runtime did not expose the temporary setup endpoint.");
@@ -3360,7 +3410,7 @@ var SponsorRelayController = class {
3360
3410
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3361
3411
  });
3362
3412
  const setupHealth = await waitForSetupEndpoint({
3363
- hostIpv4: args.runtime.hostIpv4,
3413
+ hostIpv4: runtime.hostIpv4,
3364
3414
  setupPort,
3365
3415
  fetch: (url, init) => fetch(url, init),
3366
3416
  attempts: 15,
@@ -3386,12 +3436,12 @@ var SponsorRelayController = class {
3386
3436
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3387
3437
  }
3388
3438
  const configureResult = await configureOrbitdbRelaySetup2({
3389
- hostIpv4: args.runtime.hostIpv4,
3390
- publicIpv6: args.runtime.ipv6,
3439
+ hostIpv4: runtime.hostIpv4,
3440
+ publicIpv6: runtime.ipv6,
3391
3441
  setupPort,
3392
3442
  tcpPort,
3393
3443
  wsPort,
3394
- proxyUrl: args.runtime.proxyUrl ?? void 0,
3444
+ proxyUrl: runtime.proxyUrl ?? void 0,
3395
3445
  metricsPort: mappedPorts2["9090"]?.host ?? null,
3396
3446
  metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
3397
3447
  webrtcPort: mappedPorts2["9093"]?.host ?? null,
@@ -3408,6 +3458,51 @@ var SponsorRelayController = class {
3408
3458
  if (!args.deploymentToken) {
3409
3459
  throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
3410
3460
  }
3461
+ if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
3462
+ this.emitProgress({
3463
+ stage: "deployment-confirmed",
3464
+ label: "Waiting for HTTPS route",
3465
+ progress: 93,
3466
+ status: "warning",
3467
+ itemHash: args.itemHash,
3468
+ detail: "The relay received a 2n6 hostname, but it is not active yet. Waiting before handing HTTPS config to the guest."
3469
+ });
3470
+ let latestRuntime = runtime;
3471
+ for (let attempt = 0; attempt < UI_RUNTIME_WAIT_ATTEMPTS; attempt += 1) {
3472
+ if (latestRuntime.webAccess?.active === true) {
3473
+ break;
3474
+ }
3475
+ await new Promise(
3476
+ (resolve) => globalThis.setTimeout(resolve, UI_RUNTIME_WAIT_DELAY_MS)
3477
+ );
3478
+ latestRuntime = await fetchVmRuntime({
3479
+ itemHash: args.itemHash,
3480
+ fetch: (url, init) => fetch(url, init),
3481
+ crnHash: runtime.selectedCrn?.hash ?? runtime.allocation?.crnHash ?? void 0,
3482
+ crns: this.state.crns,
3483
+ crnListUrl: this.props.crnListUrl
3484
+ }).catch(() => latestRuntime);
3485
+ this.trace("deploy:bootstrap-proxy-activation", {
3486
+ itemHash: args.itemHash,
3487
+ attempt: attempt + 1,
3488
+ attempts: UI_RUNTIME_WAIT_ATTEMPTS,
3489
+ active: latestRuntime.webAccess?.active === true,
3490
+ proxyUrl: latestRuntime.proxyUrl ?? null
3491
+ });
3492
+ }
3493
+ runtime = latestRuntime;
3494
+ mappedPorts2 = runtime.mappedPorts ?? mappedPorts2;
3495
+ if (runtime.proxyUrl && runtime.webAccess?.active !== true) {
3496
+ throw new Error(
3497
+ "Aleph reserved a 2n6 proxy URL, but it never became active in time. Aborting guest HTTPS configuration to avoid a broken Caddy setup."
3498
+ );
3499
+ }
3500
+ }
3501
+ if (!runtime.hostIpv4) {
3502
+ throw new Error("Relay runtime lost its host IPv4 address while waiting for HTTPS activation.");
3503
+ }
3504
+ const runtimeHostIpv4 = runtime.hostIpv4;
3505
+ const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
3411
3506
  this.emitProgress({
3412
3507
  stage: "deployment-confirmed",
3413
3508
  label: "Publishing relay config",
@@ -3425,9 +3520,9 @@ var SponsorRelayController = class {
3425
3520
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3426
3521
  status: "pending",
3427
3522
  runtime: {
3428
- publicIpv4: args.runtime.hostIpv4,
3429
- publicIpv6: args.runtime.ipv6 ?? null,
3430
- proxyUrl: args.runtime.proxyUrl ?? null,
3523
+ publicIpv4: runtimeHostIpv4,
3524
+ publicIpv6: runtime.ipv6 ?? null,
3525
+ proxyUrl: activeProxyUrl,
3431
3526
  mappedPorts: mappedPorts2
3432
3527
  },
3433
3528
  bootstrap: {
@@ -3493,7 +3588,7 @@ var SponsorRelayController = class {
3493
3588
  detail: "Waiting for the relay to report its final public multiaddrs."
3494
3589
  });
3495
3590
  const relayMetadata = await fetchRelayMetadataForRuntime({
3496
- runtime: args.runtime,
3591
+ runtime,
3497
3592
  fetch: (url, init) => fetch(url, init),
3498
3593
  preferSecureMetadata: profile === "uc-go-peer",
3499
3594
  attempts: 80,
@@ -3526,6 +3621,55 @@ var SponsorRelayController = class {
3526
3621
  if (!relayMetadata) {
3527
3622
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3528
3623
  }
3624
+ if (profile === "uc-go-peer" && runtime.proxyUrl) {
3625
+ this.emitProgress({
3626
+ stage: "publishing-bootstrap",
3627
+ label: "Verifying reachability",
3628
+ progress: 96,
3629
+ status: "info",
3630
+ itemHash: args.itemHash,
3631
+ detail: "Checking that the secure relay endpoint is reachable before trusting the deployment as ready."
3632
+ });
3633
+ const verificationUrl = (() => {
3634
+ try {
3635
+ return new URL("/bootstrap/metadata", runtime.proxyUrl).toString();
3636
+ } catch {
3637
+ return runtime.proxyUrl ?? "";
3638
+ }
3639
+ })();
3640
+ let verificationResult = null;
3641
+ for (let attempt = 0; attempt < UI_REACHABILITY_VERIFY_ATTEMPTS; attempt += 1) {
3642
+ verificationResult = await verifyUcGoPeerReachability({
3643
+ proxyUrl: verificationUrl,
3644
+ verifyProxyHttp: true,
3645
+ fetch: (url, init) => fetch(url, init),
3646
+ tcpProbe: async () => ({ ok: null })
3647
+ });
3648
+ this.trace("deploy:reachability-verification-attempt", {
3649
+ itemHash: args.itemHash,
3650
+ attempt: attempt + 1,
3651
+ attempts: UI_REACHABILITY_VERIFY_ATTEMPTS,
3652
+ verificationUrl,
3653
+ ok: verificationResult.ok,
3654
+ checks: verificationResult.checks
3655
+ });
3656
+ const proxyHttpCheck2 = verificationResult.checks["https:proxy"];
3657
+ if (verificationResult.ok && proxyHttpCheck2?.ok === true) {
3658
+ break;
3659
+ }
3660
+ if (attempt < UI_REACHABILITY_VERIFY_ATTEMPTS - 1) {
3661
+ await new Promise(
3662
+ (resolve) => globalThis.setTimeout(resolve, UI_REACHABILITY_VERIFY_DELAY_MS)
3663
+ );
3664
+ }
3665
+ }
3666
+ const proxyHttpCheck = verificationResult?.checks["https:proxy"];
3667
+ if (!verificationResult?.ok || proxyHttpCheck?.ok !== true) {
3668
+ throw new Error(
3669
+ proxyHttpCheck?.error ? `Secure relay endpoint did not become reachable in time: ${proxyHttpCheck.error}` : "Secure relay endpoint did not become reachable in time."
3670
+ );
3671
+ }
3672
+ }
3529
3673
  const { peerId } = relayMetadata;
3530
3674
  const registrationId = createRelayBootstrapRegistrationId(
3531
3675
  profile,