@le-space/ui 0.3.27 → 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.27",
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.27",
40
- "@le-space/core": "0.3.27",
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.27";
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) {
@@ -1878,6 +1878,13 @@ async function waitForVmRuntime(args) {
1878
1878
  }
1879
1879
 
1880
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
+ }
1881
1888
  async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1882
1889
  try {
1883
1890
  const controller = new AbortController();
@@ -2119,6 +2126,59 @@ async function fetchUcGoPeerMetadata(args) {
2119
2126
  `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2120
2127
  );
2121
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
+ }
2122
2182
 
2123
2183
  // ../aleph-bootstrap/src/index.ts
2124
2184
  import { recoverMessageAddress } from "viem";
@@ -3219,6 +3279,8 @@ var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3219
3279
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3220
3280
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
3221
3281
  var UI_RUNTIME_WAIT_DELAY_MS = 5e3;
3282
+ var UI_REACHABILITY_VERIFY_ATTEMPTS = 25;
3283
+ var UI_REACHABILITY_VERIFY_DELAY_MS = 3e3;
3222
3284
  function hasUsableRuntime(details) {
3223
3285
  return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
3224
3286
  }
@@ -3325,15 +3387,16 @@ var SponsorRelayController = class {
3325
3387
  this.progressEmitter.emit(nextEvent);
3326
3388
  }
3327
3389
  async configureRelayBootstrapRegistration(args) {
3390
+ let runtime = args.runtime;
3328
3391
  const profile = relayProfileForManifest(this.state.manifest);
3329
3392
  if (!profile) return;
3330
3393
  if (!this.state.wallet.address) {
3331
3394
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3332
3395
  }
3333
- if (!args.runtime.hostIpv4) {
3396
+ if (!runtime.hostIpv4) {
3334
3397
  throw new Error("Relay runtime did not expose a host IPv4 address.");
3335
3398
  }
3336
- const mappedPorts2 = args.runtime.mappedPorts ?? {};
3399
+ let mappedPorts2 = runtime.mappedPorts ?? {};
3337
3400
  const setupPort = mappedPorts2["80"]?.host ?? null;
3338
3401
  if (!setupPort) {
3339
3402
  throw new Error("Relay runtime did not expose the temporary setup endpoint.");
@@ -3348,7 +3411,7 @@ var SponsorRelayController = class {
3348
3411
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3349
3412
  });
3350
3413
  const setupHealth = await waitForSetupEndpoint({
3351
- hostIpv4: args.runtime.hostIpv4,
3414
+ hostIpv4: runtime.hostIpv4,
3352
3415
  setupPort,
3353
3416
  fetch: (url, init) => fetch(url, init),
3354
3417
  attempts: 15,
@@ -3374,12 +3437,12 @@ var SponsorRelayController = class {
3374
3437
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3375
3438
  }
3376
3439
  const configureResult = await configureOrbitdbRelaySetup2({
3377
- hostIpv4: args.runtime.hostIpv4,
3378
- publicIpv6: args.runtime.ipv6,
3440
+ hostIpv4: runtime.hostIpv4,
3441
+ publicIpv6: runtime.ipv6,
3379
3442
  setupPort,
3380
3443
  tcpPort,
3381
3444
  wsPort,
3382
- proxyUrl: args.runtime.proxyUrl ?? void 0,
3445
+ proxyUrl: runtime.proxyUrl ?? void 0,
3383
3446
  metricsPort: mappedPorts2["9090"]?.host ?? null,
3384
3447
  metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
3385
3448
  webrtcPort: mappedPorts2["9093"]?.host ?? null,
@@ -3396,6 +3459,51 @@ var SponsorRelayController = class {
3396
3459
  if (!args.deploymentToken) {
3397
3460
  throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
3398
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;
3399
3507
  this.emitProgress({
3400
3508
  stage: "deployment-confirmed",
3401
3509
  label: "Publishing relay config",
@@ -3413,9 +3521,9 @@ var SponsorRelayController = class {
3413
3521
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3414
3522
  status: "pending",
3415
3523
  runtime: {
3416
- publicIpv4: args.runtime.hostIpv4,
3417
- publicIpv6: args.runtime.ipv6 ?? null,
3418
- proxyUrl: args.runtime.proxyUrl ?? null,
3524
+ publicIpv4: runtimeHostIpv4,
3525
+ publicIpv6: runtime.ipv6 ?? null,
3526
+ proxyUrl: activeProxyUrl,
3419
3527
  mappedPorts: mappedPorts2
3420
3528
  },
3421
3529
  bootstrap: {
@@ -3481,7 +3589,7 @@ var SponsorRelayController = class {
3481
3589
  detail: "Waiting for the relay to report its final public multiaddrs."
3482
3590
  });
3483
3591
  const relayMetadata = await fetchRelayMetadataForRuntime({
3484
- runtime: args.runtime,
3592
+ runtime,
3485
3593
  fetch: (url, init) => fetch(url, init),
3486
3594
  preferSecureMetadata: profile === "uc-go-peer",
3487
3595
  attempts: 80,
@@ -3514,6 +3622,55 @@ var SponsorRelayController = class {
3514
3622
  if (!relayMetadata) {
3515
3623
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3516
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
+ }
3517
3674
  const { peerId } = relayMetadata;
3518
3675
  const registrationId = createRelayBootstrapRegistrationId(
3519
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.27";
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.27";
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) {
@@ -1877,6 +1877,13 @@ async function waitForVmRuntime(args) {
1877
1877
  }
1878
1878
 
1879
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
+ }
1880
1887
  async function defaultHttpProbe(fetch2, url, timeoutMs = 1e4) {
1881
1888
  try {
1882
1889
  const controller = new AbortController();
@@ -2118,6 +2125,59 @@ async function fetchUcGoPeerMetadata(args) {
2118
2125
  `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2119
2126
  );
2120
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
+ }
2121
2181
 
2122
2182
  // ../aleph-bootstrap/src/index.ts
2123
2183
  import { recoverMessageAddress } from "viem";
@@ -3218,6 +3278,8 @@ var UI_DEPLOY_WAIT_ATTEMPTS = 60;
3218
3278
  var UI_DEPLOY_WAIT_DELAY_MS = 5e3;
3219
3279
  var UI_RUNTIME_WAIT_ATTEMPTS = 40;
3220
3280
  var UI_RUNTIME_WAIT_DELAY_MS = 5e3;
3281
+ var UI_REACHABILITY_VERIFY_ATTEMPTS = 25;
3282
+ var UI_REACHABILITY_VERIFY_DELAY_MS = 3e3;
3221
3283
  function hasUsableRuntime(details) {
3222
3284
  return Boolean(details.hostIpv4) || Boolean(details.vmIpv4) || details.mappedPorts.length > 0 || Boolean(details.webUrl) || Boolean(details.execution);
3223
3285
  }
@@ -3324,15 +3386,16 @@ var SponsorRelayController = class {
3324
3386
  this.progressEmitter.emit(nextEvent);
3325
3387
  }
3326
3388
  async configureRelayBootstrapRegistration(args) {
3389
+ let runtime = args.runtime;
3327
3390
  const profile = relayProfileForManifest(this.state.manifest);
3328
3391
  if (!profile) return;
3329
3392
  if (!this.state.wallet.address) {
3330
3393
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3331
3394
  }
3332
- if (!args.runtime.hostIpv4) {
3395
+ if (!runtime.hostIpv4) {
3333
3396
  throw new Error("Relay runtime did not expose a host IPv4 address.");
3334
3397
  }
3335
- const mappedPorts2 = args.runtime.mappedPorts ?? {};
3398
+ let mappedPorts2 = runtime.mappedPorts ?? {};
3336
3399
  const setupPort = mappedPorts2["80"]?.host ?? null;
3337
3400
  if (!setupPort) {
3338
3401
  throw new Error("Relay runtime did not expose the temporary setup endpoint.");
@@ -3347,7 +3410,7 @@ var SponsorRelayController = class {
3347
3410
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3348
3411
  });
3349
3412
  const setupHealth = await waitForSetupEndpoint({
3350
- hostIpv4: args.runtime.hostIpv4,
3413
+ hostIpv4: runtime.hostIpv4,
3351
3414
  setupPort,
3352
3415
  fetch: (url, init) => fetch(url, init),
3353
3416
  attempts: 15,
@@ -3373,12 +3436,12 @@ var SponsorRelayController = class {
3373
3436
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3374
3437
  }
3375
3438
  const configureResult = await configureOrbitdbRelaySetup2({
3376
- hostIpv4: args.runtime.hostIpv4,
3377
- publicIpv6: args.runtime.ipv6,
3439
+ hostIpv4: runtime.hostIpv4,
3440
+ publicIpv6: runtime.ipv6,
3378
3441
  setupPort,
3379
3442
  tcpPort,
3380
3443
  wsPort,
3381
- proxyUrl: args.runtime.proxyUrl ?? void 0,
3444
+ proxyUrl: runtime.proxyUrl ?? void 0,
3382
3445
  metricsPort: mappedPorts2["9090"]?.host ?? null,
3383
3446
  metricsHttpsPort: mappedPorts2["443"]?.host ?? null,
3384
3447
  webrtcPort: mappedPorts2["9093"]?.host ?? null,
@@ -3395,6 +3458,51 @@ var SponsorRelayController = class {
3395
3458
  if (!args.deploymentToken) {
3396
3459
  throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
3397
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;
3398
3506
  this.emitProgress({
3399
3507
  stage: "deployment-confirmed",
3400
3508
  label: "Publishing relay config",
@@ -3412,9 +3520,9 @@ var SponsorRelayController = class {
3412
3520
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3413
3521
  status: "pending",
3414
3522
  runtime: {
3415
- publicIpv4: args.runtime.hostIpv4,
3416
- publicIpv6: args.runtime.ipv6 ?? null,
3417
- proxyUrl: args.runtime.proxyUrl ?? null,
3523
+ publicIpv4: runtimeHostIpv4,
3524
+ publicIpv6: runtime.ipv6 ?? null,
3525
+ proxyUrl: activeProxyUrl,
3418
3526
  mappedPorts: mappedPorts2
3419
3527
  },
3420
3528
  bootstrap: {
@@ -3480,7 +3588,7 @@ var SponsorRelayController = class {
3480
3588
  detail: "Waiting for the relay to report its final public multiaddrs."
3481
3589
  });
3482
3590
  const relayMetadata = await fetchRelayMetadataForRuntime({
3483
- runtime: args.runtime,
3591
+ runtime,
3484
3592
  fetch: (url, init) => fetch(url, init),
3485
3593
  preferSecureMetadata: profile === "uc-go-peer",
3486
3594
  attempts: 80,
@@ -3513,6 +3621,55 @@ var SponsorRelayController = class {
3513
3621
  if (!relayMetadata) {
3514
3622
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3515
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
+ }
3516
3673
  const { peerId } = relayMetadata;
3517
3674
  const registrationId = createRelayBootstrapRegistrationId(
3518
3675
  profile,