@le-space/ui 0.3.11 → 0.3.12

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
@@ -39,7 +39,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
39
39
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
40
40
 
41
41
  // src/shared/package-version.ts
42
- var UI_PACKAGE_VERSION = "0.3.11";
42
+ var UI_PACKAGE_VERSION = "0.3.12";
43
43
 
44
44
  // src/shared/format.ts
45
45
  function shortHash(value, head = 8, tail = 6) {
@@ -1471,6 +1471,24 @@ function normalizeSshPublicKey(value) {
1471
1471
  function isValidSshPublicKey(value) {
1472
1472
  return SSH_PUBLIC_KEY_PATTERN.test(normalizeSshPublicKey(value));
1473
1473
  }
1474
+ function createDeploymentToken() {
1475
+ const randomUuid = globalThis.crypto?.randomUUID?.();
1476
+ if (typeof randomUuid === "string" && randomUuid.trim()) {
1477
+ return randomUuid;
1478
+ }
1479
+ return `deploy-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
1480
+ }
1481
+ function appendDeploymentTokenToSshPublicKey(sshPublicKey, ownerAddress, deploymentToken) {
1482
+ const normalized = normalizeSshPublicKey(sshPublicKey);
1483
+ if (!normalized) return normalized;
1484
+ const [algorithm = "", key = ""] = normalized.split(/\s+/, 3);
1485
+ const owner = ownerAddress.trim().toLowerCase();
1486
+ const token = deploymentToken.trim();
1487
+ if (!algorithm || !key || !owner || !token) {
1488
+ return normalized;
1489
+ }
1490
+ return `${algorithm} ${key} aleph-bootstrap-config:${owner}:${token}`;
1491
+ }
1474
1492
  function createReleaseMetadata(name, rootfsVersion, deployer = "shared-aleph-tooling") {
1475
1493
  return {
1476
1494
  name,
@@ -2009,51 +2027,6 @@ async function waitForSetupEndpoint(args) {
2009
2027
  }
2010
2028
  return result;
2011
2029
  }
2012
- async function configureUcGoPeer(args) {
2013
- const controller = new AbortController();
2014
- const timeout = setTimeout(
2015
- () => controller.abort(),
2016
- Number(args.timeoutMs ?? 18e4)
2017
- );
2018
- const payload = {
2019
- public_ipv4: args.hostIpv4,
2020
- public_ipv6: args.publicIpv6 ?? void 0,
2021
- proxy_url: args.proxyUrl ?? void 0,
2022
- tcp_port: args.tcpPort ?? void 0,
2023
- ws_port: args.wsPort ?? void 0,
2024
- udp_port: args.udpPort ?? void 0,
2025
- quic_port: args.quicPort ?? void 0,
2026
- webrtc_port: args.webrtcPort ?? void 0,
2027
- bootstrap_publisher_private_key: args.bootstrapPublisherPrivateKey ?? void 0,
2028
- bootstrap_publisher_libp2p_identity_b64: args.bootstrapPublisherLibp2pIdentityBase64 ?? void 0,
2029
- bootstrap_owner_private_key: args.bootstrapOwnerPrivateKey ?? void 0,
2030
- bootstrap_owner_authorization_b64: args.bootstrapOwnerAuthorizationBase64 ?? void 0,
2031
- bootstrap_registration_id: args.bootstrapRegistrationId ?? void 0,
2032
- no_start: args.noStart === true ? true : void 0
2033
- };
2034
- try {
2035
- const response = await args.fetch(
2036
- `http://${args.hostIpv4}:${args.setupPort}/configure`,
2037
- {
2038
- method: "POST",
2039
- headers: {
2040
- "content-type": "application/json"
2041
- },
2042
- body: JSON.stringify(payload),
2043
- signal: controller.signal
2044
- }
2045
- );
2046
- const responsePayload = await response.json().catch(() => null);
2047
- if (!response.ok) {
2048
- throw new Error(
2049
- `Relay setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2050
- );
2051
- }
2052
- return responsePayload;
2053
- } finally {
2054
- clearTimeout(timeout);
2055
- }
2056
- }
2057
2030
  async function configureOrbitdbRelaySetup2(args) {
2058
2031
  const controller = new AbortController();
2059
2032
  const timeout = setTimeout(
@@ -2112,8 +2085,9 @@ async function fetchUcGoPeerMetadata(args) {
2112
2085
  Number(args.timeoutMs ?? 18e4)
2113
2086
  );
2114
2087
  try {
2088
+ const requestUrl = args.metadataUrl && args.metadataUrl.trim() ? args.metadataUrl : `http://${args.hostIpv4}:${args.setupPort}/metadata`;
2115
2089
  const response = await args.fetch(
2116
- `http://${args.hostIpv4}:${args.setupPort}/metadata`,
2090
+ requestUrl,
2117
2091
  {
2118
2092
  signal: controller.signal
2119
2093
  }
@@ -2195,6 +2169,9 @@ function asNumber4(value) {
2195
2169
  function normalizeHost(host) {
2196
2170
  return host.trim().toLowerCase();
2197
2171
  }
2172
+ function normalizeAddress(address) {
2173
+ return address.trim().toLowerCase();
2174
+ }
2198
2175
  function normalizeSignature(signature) {
2199
2176
  const trimmed = signature.trim();
2200
2177
  return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
@@ -2496,6 +2473,40 @@ async function fetchAlephBootstrapPosts(options = {}) {
2496
2473
  const payload = await response.json();
2497
2474
  return (payload.posts ?? []).map((entry) => normalizeRelayBootstrapPostRecord(entry)).filter((entry) => entry != null);
2498
2475
  }
2476
+ function compareRelayBootstrapPostRecency(left, right) {
2477
+ const leftUpdatedAt = left.content?.updatedAt ?? 0;
2478
+ const rightUpdatedAt = right.content?.updatedAt ?? 0;
2479
+ if (leftUpdatedAt !== rightUpdatedAt) return leftUpdatedAt - rightUpdatedAt;
2480
+ const leftTime = left.time ?? 0;
2481
+ const rightTime = right.time ?? 0;
2482
+ return leftTime - rightTime;
2483
+ }
2484
+ function relayBootstrapRecordIdentity(post) {
2485
+ const content = post.content;
2486
+ if (!content) return null;
2487
+ if (content.registrationId) return `registration:${content.registrationId}`;
2488
+ if (post.address) return `sender:${normalizeAddress(post.address)}`;
2489
+ return content.peerId ? `peer:${content.peerId}` : null;
2490
+ }
2491
+ function selectCurrentRelayBootstrapPosts(posts, options = {}) {
2492
+ const maxAgeMs = options.maxAgeMs ?? DEFAULT_BOOTSTRAP_MAX_AGE_MS;
2493
+ const now = options.now ?? Date.now();
2494
+ const selected = /* @__PURE__ */ new Map();
2495
+ for (const post of posts) {
2496
+ const content = post.content;
2497
+ if (!content) continue;
2498
+ if (now - content.updatedAt > maxAgeMs) continue;
2499
+ const identity = relayBootstrapRecordIdentity(post);
2500
+ if (!identity) continue;
2501
+ const previous = selected.get(identity);
2502
+ if (!previous || compareRelayBootstrapPostRecency(post, previous) > 0) {
2503
+ selected.set(identity, post);
2504
+ }
2505
+ }
2506
+ return [...selected.values()].sort(
2507
+ (left, right) => compareRelayBootstrapPostRecency(right, left)
2508
+ );
2509
+ }
2499
2510
 
2500
2511
  // ../core/src/bootstrap-registration.ts
2501
2512
  async function publishRelayBootstrapRegistration(args) {
@@ -2602,6 +2613,311 @@ async function publishRelayBootstrapRegistration(args) {
2602
2613
  };
2603
2614
  }
2604
2615
 
2616
+ // ../core/src/bootstrap-config.ts
2617
+ var VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY = "vm-bootstrap-config";
2618
+ async function fetchVmBootstrapConfigAggregate(address, options) {
2619
+ const requestUrl = new URL(`/api/v0/aggregates/${address}.json`, options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2620
+ requestUrl.searchParams.set("keys", VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY);
2621
+ const response = await options.fetch(requestUrl.toString(), { cache: "no-cache" });
2622
+ if (response.status === 404) return {};
2623
+ if (!response.ok) {
2624
+ throw new Error(`VM bootstrap config aggregate request failed: ${response.status}`);
2625
+ }
2626
+ const payload = await response.json();
2627
+ const aggregate = payload.data?.[VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY];
2628
+ if (!aggregate || typeof aggregate !== "object" || Array.isArray(aggregate)) return {};
2629
+ return aggregate;
2630
+ }
2631
+ function normalizeVmBootstrapConfigAggregate(aggregate) {
2632
+ if (!aggregate || typeof aggregate !== "object") return {};
2633
+ return Object.fromEntries(
2634
+ Object.entries(aggregate).filter(
2635
+ (entry) => Boolean(entry[1] && typeof entry[1] === "object" && !Array.isArray(entry[1]))
2636
+ )
2637
+ );
2638
+ }
2639
+ function createVmBootstrapConfigAggregateContent(args) {
2640
+ const content = normalizeVmBootstrapConfigAggregate(args.existingAggregate);
2641
+ content[args.record.deploymentToken] = args.record;
2642
+ return {
2643
+ address: args.sender,
2644
+ key: VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY,
2645
+ content,
2646
+ time: args.now ?? Date.now() / 1e3
2647
+ };
2648
+ }
2649
+ async function createUnsignedVmBootstrapConfigAggregateMessage(args) {
2650
+ const itemContent = JSON.stringify(args.content);
2651
+ const itemHash = await args.hasher(itemContent);
2652
+ return {
2653
+ sender: args.sender,
2654
+ chain: "ETH",
2655
+ type: "AGGREGATE",
2656
+ item_hash: itemHash,
2657
+ item_type: "inline",
2658
+ item_content: itemContent,
2659
+ time: args.now ?? Date.now() / 1e3,
2660
+ channel: args.channel ?? DEFAULT_ALEPH_CHANNEL
2661
+ };
2662
+ }
2663
+ async function publishVmBootstrapConfigAggregate(args) {
2664
+ const unsignedMessage = await createUnsignedVmBootstrapConfigAggregateMessage({
2665
+ sender: args.sender,
2666
+ content: args.content,
2667
+ hasher: args.hasher,
2668
+ channel: args.channel
2669
+ });
2670
+ const message = await signAlephMessage(unsignedMessage, args.signer);
2671
+ const { response, httpStatus } = await broadcastAlephMessage2(message, {
2672
+ apiHost: args.apiHost,
2673
+ sync: args.sync,
2674
+ fetch: args.fetch
2675
+ });
2676
+ const aggregateStatus = httpStatus >= 200 && httpStatus < 300 ? response.message_status ?? "processed" : "unknown";
2677
+ if (aggregateStatus === "rejected") {
2678
+ throw new Error(`VM bootstrap config aggregate was rejected by Aleph: ${JSON.stringify(response.details ?? response)}`);
2679
+ }
2680
+ return {
2681
+ aggregateItemHash: message.item_hash,
2682
+ aggregateStatus
2683
+ };
2684
+ }
2685
+ async function publishVmBootstrapConfig(args) {
2686
+ const aggregate = await fetchVmBootstrapConfigAggregate(args.sender, {
2687
+ apiHost: args.apiHost,
2688
+ fetch: args.fetch
2689
+ });
2690
+ const content = createVmBootstrapConfigAggregateContent({
2691
+ sender: args.sender,
2692
+ record: args.record,
2693
+ existingAggregate: aggregate
2694
+ });
2695
+ const result = await publishVmBootstrapConfigAggregate({
2696
+ sender: args.sender,
2697
+ content,
2698
+ signer: args.signer,
2699
+ hasher: args.hasher,
2700
+ fetch: args.fetch,
2701
+ channel: args.channel,
2702
+ apiHost: args.apiHost,
2703
+ sync: args.sync
2704
+ });
2705
+ return {
2706
+ ...result,
2707
+ aggregate: content.content
2708
+ };
2709
+ }
2710
+ async function deleteVmBootstrapConfig(args) {
2711
+ const aggregate = await fetchVmBootstrapConfigAggregate(args.sender, {
2712
+ apiHost: args.apiHost,
2713
+ fetch: args.fetch
2714
+ });
2715
+ const content = normalizeVmBootstrapConfigAggregate(aggregate);
2716
+ delete content[args.deploymentToken];
2717
+ const publishResult = await publishVmBootstrapConfigAggregate({
2718
+ sender: args.sender,
2719
+ content: {
2720
+ address: args.sender,
2721
+ key: VM_BOOTSTRAP_CONFIG_AGGREGATE_KEY,
2722
+ content,
2723
+ time: Date.now() / 1e3
2724
+ },
2725
+ signer: args.signer,
2726
+ hasher: args.hasher,
2727
+ fetch: args.fetch,
2728
+ channel: args.channel,
2729
+ apiHost: args.apiHost,
2730
+ sync: args.sync
2731
+ });
2732
+ return {
2733
+ ...publishResult,
2734
+ aggregate: content
2735
+ };
2736
+ }
2737
+
2738
+ // ../core/src/bootstrap-reconcile.ts
2739
+ function createRelayBootstrapRegistrationId(profile, instanceName, itemHash) {
2740
+ const normalizedName = String(instanceName ?? "").trim();
2741
+ const normalizedHash = String(itemHash ?? "").trim();
2742
+ if (!normalizedHash) {
2743
+ return `relay:${profile}:${normalizedName || "instance"}`;
2744
+ }
2745
+ return `relay:${profile}:${normalizedName || normalizedHash}:${normalizedHash}`;
2746
+ }
2747
+ function secureBootstrapMetadataUrl(proxyUrl) {
2748
+ const normalized = String(proxyUrl ?? "").trim();
2749
+ if (!normalized) return null;
2750
+ try {
2751
+ return new URL("/bootstrap/metadata", normalized).toString();
2752
+ } catch {
2753
+ return null;
2754
+ }
2755
+ }
2756
+ function extractRelayMetadata(payload) {
2757
+ const metadata = payload && typeof payload === "object" && payload.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
2758
+ const peerId = typeof metadata?.peer_id === "string" ? metadata.peer_id : null;
2759
+ const probeMultiaddrs = Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.filter((entry) => typeof entry === "string") : [];
2760
+ const browserBootstrapMultiaddrs = Array.isArray(metadata?.browser_bootstrap_multiaddrs) ? metadata.browser_bootstrap_multiaddrs.filter((entry) => typeof entry === "string") : [];
2761
+ if (!peerId || probeMultiaddrs.length === 0) return null;
2762
+ return {
2763
+ peerId,
2764
+ probeMultiaddrs,
2765
+ browserBootstrapMultiaddrs
2766
+ };
2767
+ }
2768
+ async function fetchOwnerInstances(address, options) {
2769
+ const url = new URL("/api/v0/messages.json", options.apiHost ?? DEFAULT_ALEPH_API_HOST2);
2770
+ url.searchParams.set("msgTypes", "INSTANCE");
2771
+ url.searchParams.set("addresses", address);
2772
+ url.searchParams.set("message_statuses", "processed,pending,rejected,removing");
2773
+ url.searchParams.set("pagination", "100");
2774
+ url.searchParams.set("page", "1");
2775
+ url.searchParams.set("sortOrder", "-1");
2776
+ const response = await options.fetch(url.toString(), { cache: "no-cache" });
2777
+ if (!response.ok) {
2778
+ throw new Error(`Instance list request failed: ${response.status}`);
2779
+ }
2780
+ const payload = await response.json();
2781
+ return (payload.messages ?? []).map((message) => ({
2782
+ ...message,
2783
+ status: typeof message.status === "string" && message.status.trim() ? message.status : message.confirmed ? "processed" : "pending"
2784
+ }));
2785
+ }
2786
+ async function fetchRelayMetadataForRuntime(args) {
2787
+ const setupPort = args.runtime.mappedPorts?.["80"]?.host ?? 80;
2788
+ const metadataUrl = args.preferSecureMetadata ? secureBootstrapMetadataUrl(args.runtime.proxyUrl) : null;
2789
+ if (args.preferSecureMetadata && !metadataUrl) {
2790
+ return null;
2791
+ }
2792
+ const payload = await fetchUcGoPeerMetadata({
2793
+ hostIpv4: args.runtime.hostIpv4 ?? "",
2794
+ setupPort,
2795
+ metadataUrl,
2796
+ fetch: args.fetch,
2797
+ attempts: args.attempts,
2798
+ delayMs: args.delayMs,
2799
+ timeoutMs: args.timeoutMs
2800
+ }).catch(() => null);
2801
+ return extractRelayMetadata(payload);
2802
+ }
2803
+ async function reconcileOwnerRelayBootstrapRegistrations(args) {
2804
+ const result = {
2805
+ refreshedRegistrations: [],
2806
+ forgottenHashes: [],
2807
+ skippedInstanceHashes: [],
2808
+ errors: []
2809
+ };
2810
+ const liveRegistrationIds = /* @__PURE__ */ new Set();
2811
+ if (args.current?.registrationId) {
2812
+ liveRegistrationIds.add(args.current.registrationId);
2813
+ result.refreshedRegistrations.push(args.current.registrationId);
2814
+ }
2815
+ const instances = await fetchOwnerInstances(args.instanceOwnerAddress, {
2816
+ apiHost: args.apiHost,
2817
+ fetch: args.fetch
2818
+ });
2819
+ const processedInstances = instances.filter((instance) => {
2820
+ const status = String(instance.status ?? "").trim().toLowerCase();
2821
+ return status === "processed";
2822
+ });
2823
+ for (const instance of processedInstances) {
2824
+ if (instance.item_hash === args.current?.itemHash) continue;
2825
+ const registrationId = createRelayBootstrapRegistrationId(
2826
+ args.profile,
2827
+ instance.content?.metadata?.name,
2828
+ instance.item_hash
2829
+ );
2830
+ try {
2831
+ const runtime = await fetchVmRuntime({
2832
+ itemHash: instance.item_hash,
2833
+ fetch: args.fetch,
2834
+ crns: args.crns,
2835
+ crnListUrl: args.crnListUrl
2836
+ });
2837
+ if (!runtime.hostIpv4 || Object.keys(runtime.mappedPorts ?? {}).length === 0) {
2838
+ result.skippedInstanceHashes.push(instance.item_hash);
2839
+ continue;
2840
+ }
2841
+ const relayMetadata = await fetchRelayMetadataForRuntime({
2842
+ runtime,
2843
+ fetch: args.fetch,
2844
+ preferSecureMetadata: args.preferSecureMetadata,
2845
+ attempts: 3,
2846
+ delayMs: 1e3,
2847
+ timeoutMs: 1e4
2848
+ });
2849
+ if (!relayMetadata) {
2850
+ result.skippedInstanceHashes.push(instance.item_hash);
2851
+ continue;
2852
+ }
2853
+ liveRegistrationIds.add(registrationId);
2854
+ await publishRelayBootstrapRegistration({
2855
+ sender: args.sender,
2856
+ signer: args.signer,
2857
+ hasher: args.hasher,
2858
+ fetch: args.fetch,
2859
+ apiHost: args.apiHost,
2860
+ channel: args.channel,
2861
+ ref: args.ref,
2862
+ postType: args.postType,
2863
+ peerId: relayMetadata.peerId,
2864
+ multiaddrs: relayMetadata.probeMultiaddrs,
2865
+ browserMultiaddrs: relayMetadata.browserBootstrapMultiaddrs,
2866
+ registrationId,
2867
+ ownerAddress: args.ownerAddress,
2868
+ publisherAddress: args.publisherAddress,
2869
+ ownerSigner: args.ownerSigner,
2870
+ publisherSigner: args.publisherSigner,
2871
+ forgetPrevious: true,
2872
+ profile: args.profile,
2873
+ version: args.version,
2874
+ instanceItemHash: instance.item_hash,
2875
+ sync: true
2876
+ });
2877
+ result.refreshedRegistrations.push(registrationId);
2878
+ } catch (error) {
2879
+ result.errors.push({
2880
+ phase: "publish",
2881
+ itemHash: instance.item_hash,
2882
+ registrationId,
2883
+ message: error instanceof Error ? error.message : String(error)
2884
+ });
2885
+ }
2886
+ }
2887
+ try {
2888
+ const bootstrapPosts = await fetchAlephBootstrapPosts({
2889
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST2,
2890
+ channel: args.channel,
2891
+ ref: args.ref,
2892
+ postType: args.postType,
2893
+ fetch: args.fetch
2894
+ });
2895
+ const currentPosts = selectCurrentRelayBootstrapPosts(bootstrapPosts);
2896
+ const staleHashes = currentPosts.filter(
2897
+ (post) => post.address?.toLowerCase() === args.sender.toLowerCase() && post.content?.profile === args.profile && post.content?.registrationId && !liveRegistrationIds.has(post.content.registrationId)
2898
+ ).map((post) => post.itemHash ?? post.hash ?? "").filter(Boolean);
2899
+ if (staleHashes.length > 0) {
2900
+ await forgetAlephMessages({
2901
+ sender: args.sender,
2902
+ hashes: staleHashes,
2903
+ reason: `Forget stale ${args.profile} relay bootstrap registrations after owner reconcile`,
2904
+ signer: args.signer,
2905
+ hasher: args.hasher,
2906
+ fetch: args.fetch,
2907
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST2,
2908
+ sync: true
2909
+ });
2910
+ result.forgottenHashes.push(...staleHashes);
2911
+ }
2912
+ } catch (error) {
2913
+ result.errors.push({
2914
+ phase: "forget",
2915
+ message: error instanceof Error ? error.message : String(error)
2916
+ });
2917
+ }
2918
+ return result;
2919
+ }
2920
+
2605
2921
  // src/shared/events.ts
2606
2922
  function createDeploymentProgressEmitter() {
2607
2923
  const listeners = /* @__PURE__ */ new Set();
@@ -2622,6 +2938,13 @@ function createDeploymentProgressEmitter() {
2622
2938
  }
2623
2939
 
2624
2940
  // src/shared/controller.ts
2941
+ function asJsonFetch(input, init) {
2942
+ return fetch(input, init).then(async (response) => ({
2943
+ ok: response.ok,
2944
+ status: response.status,
2945
+ json: async () => await response.json()
2946
+ }));
2947
+ }
2625
2948
  function isConfirmedRelaySetupResult(value) {
2626
2949
  return Boolean(
2627
2950
  value && typeof value === "object" && "status" in value && value.status === "configured"
@@ -3064,36 +3387,36 @@ var SponsorRelayController = class {
3064
3387
  if (!setupPort) {
3065
3388
  throw new Error("Relay runtime did not expose the temporary setup endpoint.");
3066
3389
  }
3067
- this.emitProgress({
3068
- stage: "deployment-confirmed",
3069
- label: "Configuring relay",
3070
- progress: 93,
3071
- status: "info",
3072
- itemHash: args.itemHash,
3073
- detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3074
- });
3075
- const setupHealth = await waitForSetupEndpoint({
3076
- hostIpv4: args.runtime.hostIpv4,
3077
- setupPort,
3078
- fetch: (url, init) => fetch(url, init),
3079
- attempts: 15,
3080
- delayMs: 4e3,
3081
- httpTimeoutMs: 1e4
3082
- });
3083
- if (!setupHealth.ok) {
3084
- throw new Error(
3085
- `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
3086
- );
3087
- }
3088
- this.emitProgress({
3089
- stage: "deployment-confirmed",
3090
- label: "Applying relay networking",
3091
- progress: 94,
3092
- status: "info",
3093
- itemHash: args.itemHash,
3094
- detail: "Publishing the host port mapping into the guest relay configuration."
3095
- });
3096
3390
  if (profile === "orbitdb-relay-pinner") {
3391
+ this.emitProgress({
3392
+ stage: "deployment-confirmed",
3393
+ label: "Configuring relay",
3394
+ progress: 93,
3395
+ status: "info",
3396
+ itemHash: args.itemHash,
3397
+ detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3398
+ });
3399
+ const setupHealth = await waitForSetupEndpoint({
3400
+ hostIpv4: args.runtime.hostIpv4,
3401
+ setupPort,
3402
+ fetch: (url, init) => fetch(url, init),
3403
+ attempts: 15,
3404
+ delayMs: 4e3,
3405
+ httpTimeoutMs: 1e4
3406
+ });
3407
+ if (!setupHealth.ok) {
3408
+ throw new Error(
3409
+ `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
3410
+ );
3411
+ }
3412
+ this.emitProgress({
3413
+ stage: "deployment-confirmed",
3414
+ label: "Applying relay networking",
3415
+ progress: 94,
3416
+ status: "info",
3417
+ itemHash: args.itemHash,
3418
+ detail: "Publishing the host port mapping into the guest relay configuration."
3419
+ });
3097
3420
  const tcpPort = mappedPorts2["9091"]?.host ?? 0;
3098
3421
  const wsPort = mappedPorts2["9092"]?.host ?? mappedPorts2["443"]?.host ?? 0;
3099
3422
  if (!tcpPort || !wsPort) {
@@ -3119,24 +3442,48 @@ var SponsorRelayController = class {
3119
3442
  );
3120
3443
  }
3121
3444
  } else {
3122
- const configureResult = await configureUcGoPeer({
3123
- hostIpv4: args.runtime.hostIpv4,
3124
- publicIpv6: args.runtime.ipv6,
3125
- setupPort,
3126
- tcpPort: mappedPorts2["9095"]?.host ?? null,
3127
- wsPort: mappedPorts2["9097"]?.host ?? mappedPorts2["9095"]?.host ?? null,
3128
- udpPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
3129
- quicPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
3130
- webrtcPort: mappedPorts2["9095"]?.udp === true ? mappedPorts2["9095"]?.host ?? null : null,
3131
- proxyUrl: args.runtime.proxyUrl ?? void 0,
3132
- fetch: (url, init) => fetch(url, init),
3133
- timeoutMs: 18e4
3134
- });
3135
- if (!isConfirmedRelaySetupResult(configureResult)) {
3136
- throw new Error(
3137
- `Relay guest configuration could not be confirmed at http://${args.runtime.hostIpv4}:${setupPort}/configure.`
3138
- );
3445
+ if (!args.deploymentToken) {
3446
+ throw new Error("Missing deployment token for Aleph bootstrap config handoff.");
3139
3447
  }
3448
+ this.emitProgress({
3449
+ stage: "deployment-confirmed",
3450
+ label: "Publishing relay config",
3451
+ progress: 94,
3452
+ status: "info",
3453
+ itemHash: args.itemHash,
3454
+ detail: "Publishing runtime networking into the Aleph guest bootstrap config aggregate."
3455
+ });
3456
+ const record = {
3457
+ deploymentToken: args.deploymentToken,
3458
+ profile,
3459
+ ownerAddress: this.state.wallet.address,
3460
+ instanceItemHash: args.itemHash,
3461
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3462
+ expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3463
+ status: "pending",
3464
+ runtime: {
3465
+ publicIpv4: args.runtime.hostIpv4,
3466
+ publicIpv6: args.runtime.ipv6 ?? null,
3467
+ proxyUrl: args.runtime.proxyUrl ?? null,
3468
+ mappedPorts: mappedPorts2
3469
+ },
3470
+ bootstrap: {
3471
+ registrationId: createRelayBootstrapRegistrationId(
3472
+ profile,
3473
+ this.state.instanceName.trim(),
3474
+ args.itemHash
3475
+ )
3476
+ }
3477
+ };
3478
+ await publishVmBootstrapConfig({
3479
+ sender: this.state.wallet.address,
3480
+ record,
3481
+ signer: personalSign,
3482
+ hasher: sha256Hex,
3483
+ fetch: asJsonFetch,
3484
+ apiHost: this.client.apiHost,
3485
+ sync: true
3486
+ });
3140
3487
  }
3141
3488
  this.emitProgress({
3142
3489
  stage: "publishing-bootstrap",
@@ -3146,27 +3493,23 @@ var SponsorRelayController = class {
3146
3493
  itemHash: args.itemHash,
3147
3494
  detail: "Waiting for the relay to report its final public multiaddrs."
3148
3495
  });
3149
- const metadataResult = await fetchUcGoPeerMetadata({
3150
- hostIpv4: args.runtime.hostIpv4,
3151
- setupPort,
3152
- fetch: (url, init) => fetch(url, init),
3496
+ const relayMetadata = await fetchRelayMetadataForRuntime({
3497
+ runtime: args.runtime,
3498
+ fetch,
3499
+ preferSecureMetadata: profile === "uc-go-peer",
3153
3500
  attempts: 80,
3154
3501
  delayMs: 3e3,
3155
3502
  timeoutMs: 24e4
3156
3503
  });
3157
- const metadata = metadataResult && typeof metadataResult === "object" && metadataResult.metadata && typeof metadataResult.metadata === "object" ? metadataResult.metadata : null;
3158
- const peerId = typeof metadata?.peer_id === "string" ? metadata.peer_id : null;
3159
- const probeMultiaddrs = Array.isArray(metadata?.probe_multiaddrs) ? metadata.probe_multiaddrs.filter(
3160
- (entry) => typeof entry === "string"
3161
- ) : [];
3162
- const browserBootstrapMultiaddrs = Array.isArray(
3163
- metadata?.browser_bootstrap_multiaddrs
3164
- ) ? metadata.browser_bootstrap_multiaddrs.filter(
3165
- (entry) => typeof entry === "string"
3166
- ) : [];
3167
- if (!peerId || probeMultiaddrs.length === 0) {
3504
+ if (!relayMetadata) {
3168
3505
  throw new Error("Relay metadata did not include a peer ID and public multiaddrs.");
3169
3506
  }
3507
+ const { peerId, probeMultiaddrs, browserBootstrapMultiaddrs } = relayMetadata;
3508
+ const registrationId = createRelayBootstrapRegistrationId(
3509
+ profile,
3510
+ this.state.instanceName.trim(),
3511
+ args.itemHash
3512
+ );
3170
3513
  this.emitProgress({
3171
3514
  stage: "publishing-bootstrap",
3172
3515
  label: "Publishing bootstrap registration",
@@ -3179,25 +3522,65 @@ var SponsorRelayController = class {
3179
3522
  sender: this.state.wallet.address,
3180
3523
  signer: personalSign,
3181
3524
  hasher: sha256Hex,
3182
- fetch: (url, init) => fetch(url, init).then(async (response) => ({
3183
- ok: response.ok,
3184
- status: response.status,
3185
- json: async () => await response.json()
3186
- })),
3525
+ fetch: asJsonFetch,
3187
3526
  apiHost: this.client.apiHost,
3188
3527
  peerId,
3189
3528
  multiaddrs: probeMultiaddrs,
3190
3529
  browserMultiaddrs: browserBootstrapMultiaddrs,
3530
+ registrationId,
3191
3531
  profile,
3192
3532
  version: this.state.manifest?.version,
3533
+ forgetPrevious: true,
3193
3534
  sync: true
3194
3535
  });
3195
3536
  this.trace("deploy:bootstrap-registration", {
3196
3537
  itemHash: args.itemHash,
3197
3538
  peerId,
3198
- metadata,
3539
+ metadata: relayMetadata,
3199
3540
  publication
3200
3541
  });
3542
+ const reconcileResult = await reconcileOwnerRelayBootstrapRegistrations({
3543
+ instanceOwnerAddress: this.state.wallet.address,
3544
+ sender: this.state.wallet.address,
3545
+ signer: personalSign,
3546
+ hasher: sha256Hex,
3547
+ fetch: asJsonFetch,
3548
+ profile,
3549
+ apiHost: this.client.apiHost,
3550
+ crns: this.state.crns,
3551
+ crnListUrl: this.props.crnListUrl,
3552
+ preferSecureMetadata: profile === "uc-go-peer",
3553
+ current: {
3554
+ itemHash: args.itemHash,
3555
+ registrationId,
3556
+ peerId,
3557
+ probeMultiaddrs,
3558
+ browserBootstrapMultiaddrs
3559
+ }
3560
+ });
3561
+ if (reconcileResult.errors.length > 0) {
3562
+ this.trace("deploy:owner-bootstrap-reconcile-errors", {
3563
+ itemHash: args.itemHash,
3564
+ errors: reconcileResult.errors
3565
+ });
3566
+ }
3567
+ if (profile === "uc-go-peer" && args.deploymentToken) {
3568
+ await deleteVmBootstrapConfig({
3569
+ sender: this.state.wallet.address,
3570
+ deploymentToken: args.deploymentToken,
3571
+ signer: personalSign,
3572
+ hasher: sha256Hex,
3573
+ fetch: asJsonFetch,
3574
+ apiHost: this.client.apiHost,
3575
+ sync: true
3576
+ }).catch((error) => {
3577
+ this.trace("deploy:bootstrap-config-cleanup-error", {
3578
+ itemHash: args.itemHash,
3579
+ deploymentToken: args.deploymentToken,
3580
+ error: error instanceof Error ? error.message : String(error)
3581
+ });
3582
+ });
3583
+ }
3201
3584
  }
3202
3585
  syncLatestDeploymentProgress(instances) {
3203
3586
  const itemHash = this.state.lastDeploymentHash;
@@ -3669,6 +4052,7 @@ var SponsorRelayController = class {
3669
4052
  let lastError = null;
3670
4053
  for (const [candidateIndex, candidateCrn] of orderedCrns.entries()) {
3671
4054
  let attemptItemHash = null;
4055
+ let attemptDeploymentToken = null;
3672
4056
  this.emitProgress({
3673
4057
  stage: "selecting-crn",
3674
4058
  label: "Selecting CRN",
@@ -3677,10 +4061,16 @@ var SponsorRelayController = class {
3677
4061
  detail: `Trying ${candidateCrn.name ?? candidateCrn.hash} (${candidateIndex + 1}/${orderedCrns.length})`
3678
4062
  });
3679
4063
  try {
4064
+ const profile = relayProfileForManifest(this.state.manifest);
4065
+ const sshPublicKey = profile === "uc-go-peer" ? appendDeploymentTokenToSshPublicKey(
4066
+ this.state.sshPublicKey.trim(),
4067
+ this.state.wallet.address,
4068
+ attemptDeploymentToken = createDeploymentToken()
4069
+ ) : this.state.sshPublicKey.trim();
3680
4070
  const content = createInstanceContent({
3681
4071
  address: this.state.wallet.address,
3682
4072
  name: this.state.instanceName.trim(),
3683
- sshPublicKey: this.state.sshPublicKey.trim(),
4073
+ sshPublicKey,
3684
4074
  rootfsItemHash: this.state.manifest.rootfsItemHash,
3685
4075
  rootfsSizeMiB: Math.max(
3686
4076
  this.state.manifest.rootfsSizeMiB,
@@ -3860,7 +4250,8 @@ var SponsorRelayController = class {
3860
4250
  });
3861
4251
  await this.configureRelayBootstrapRegistration({
3862
4252
  itemHash: result.itemHash,
3863
- runtime
4253
+ runtime,
4254
+ deploymentToken: attemptDeploymentToken
3864
4255
  });
3865
4256
  this.patch({
3866
4257
  busy: { deploying: false }
@@ -3886,6 +4277,17 @@ var SponsorRelayController = class {
3886
4277
  error: lastError.message
3887
4278
  });
3888
4279
  if (attemptItemHash) {
4280
+ if (attemptDeploymentToken) {
4281
+ await deleteVmBootstrapConfig({
4282
+ sender: this.state.wallet.address,
4283
+ deploymentToken: attemptDeploymentToken,
4284
+ signer: personalSign,
4285
+ hasher: sha256Hex,
4286
+ fetch: asJsonFetch,
4287
+ apiHost: this.client.apiHost,
4288
+ sync: true
4289
+ }).catch(() => void 0);
4290
+ }
3889
4291
  await forgetAlephMessages({
3890
4292
  sender: this.state.wallet.address,
3891
4293
  hashes: [attemptItemHash],
@@ -3964,16 +4366,35 @@ var SponsorRelayController = class {
3964
4366
  reason: "Deleted from Sponsor Relay panel",
3965
4367
  signer: personalSign,
3966
4368
  hasher: sha256Hex,
3967
- fetch: (url, init) => fetch(url, init).then(async (response) => ({
3968
- ok: response.ok,
3969
- status: response.status,
3970
- json: async () => await response.json()
3971
- })),
4369
+ fetch: asJsonFetch,
3972
4370
  apiHost: this.client.apiHost,
3973
4371
  onProgress: (event) => {
3974
4372
  this.emitProgress(event);
3975
4373
  }
3976
4374
  });
4375
+ const profile = relayProfileForManifest(this.state.manifest);
4376
+ if (profile) {
4377
+ await reconcileOwnerRelayBootstrapRegistrations({
4378
+ instanceOwnerAddress: this.state.wallet.address,
4379
+ sender: this.state.wallet.address,
4380
+ signer: personalSign,
4381
+ hasher: sha256Hex,
4382
+ fetch: asJsonFetch,
4383
+ profile,
4384
+ apiHost: this.client.apiHost,
4385
+ crns: this.state.crns,
4386
+ crnListUrl: this.props.crnListUrl,
4387
+ preferSecureMetadata: profile === "uc-go-peer"
4388
+ }).catch(
4389
+ (error) => {
4390
+ this.trace("delete:owner-bootstrap-reconcile-error", {
4391
+ instanceHash,
4392
+ profile,
4393
+ error: error instanceof Error ? error.message : String(error)
4394
+ });
4395
+ }
4396
+ );
4397
+ }
3977
4398
  this.patch({
3978
4399
  busy: { deletingInstanceHash: null },
3979
4400
  statusText: `Deletion submitted for ${instanceHash}`