@le-space/ui 0.6.1 → 0.6.3

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
@@ -5,6 +5,21 @@ import React from "react";
5
5
  var DEFAULT_INSTANCE_NAME = "sponsor-relay";
6
6
  var DEFAULT_MANIFEST_URL = "./rootfs-manifest.json";
7
7
  var DEFAULT_TIER_ID = "tier-1";
8
+ var DEFAULT_UCAN_STORE_PWA_ORIGIN = "https://ucan.nicokrause.com";
9
+ var DEFAULT_UCAN_STORE_SERVICE_ORIGIN = "https://ucan-api.nicokrause.com";
10
+ var DEFAULT_UCAN_STORE_SERVICE_DID = "did:web:ucan-api.nicokrause.com";
11
+ var DEFAULT_UCAN_STORE_ALLOWED_CAPABILITIES = [
12
+ "space/blob/add",
13
+ "space/blob/list",
14
+ "space/index/add",
15
+ "space/index/list",
16
+ "filecoin/offer",
17
+ "upload/add",
18
+ "upload/list",
19
+ "store/add"
20
+ ];
21
+ var DEFAULT_UCAN_STORE_USER_DELEGATION_EXPIRATION_SECONDS = "31536000";
22
+ var DEFAULT_UCAN_STORE_MAX_DELEGATION_EXPIRATION_SECONDS = "315360000";
8
23
  var ROOTFS_MISSING_STATE = {
9
24
  tone: "idle",
10
25
  label: "manifest missing",
@@ -28,7 +43,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
28
43
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
29
44
 
30
45
  // src/shared/package-version.ts
31
- var UI_PACKAGE_VERSION = "0.6.1";
46
+ var UI_PACKAGE_VERSION = "0.6.3";
32
47
 
33
48
  // src/shared/format.ts
34
49
  function shortHash(value, head = 8, tail = 6) {
@@ -871,7 +886,7 @@ async function fetchInstancePricing(apiHost = DEFAULT_ALEPH_API_HOST, aggregateA
871
886
  }
872
887
 
873
888
  // ../core/src/constants.ts
874
- var DEFAULT_ALEPH_CHANNEL = "TEST";
889
+ var DEFAULT_ALEPH_CHANNEL = "ALEPH-CLOUDSOLUTIONS";
875
890
 
876
891
  // ../core/src/manifests.ts
877
892
  var DEFAULT_ALEPH_API_HOST2 = "https://api.aleph.im";
@@ -1153,6 +1168,13 @@ function normalizeExecution2(item, crnUrl) {
1153
1168
  }
1154
1169
 
1155
1170
  // ../core/src/deployment-inspection.ts
1171
+ var TRANSIENT_MESSAGE_LOOKUP_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
1172
+ function isTransientMessageLookupError(error) {
1173
+ const message = error instanceof Error ? error.message : String(error);
1174
+ const match = message.match(/Message lookup failed: (\d{3})/);
1175
+ if (!match) return false;
1176
+ return TRANSIENT_MESSAGE_LOOKUP_STATUSES.has(Number(match[1]));
1177
+ }
1156
1178
  async function fetchMessageEnvelope2(itemHash, options) {
1157
1179
  const response = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST2}/api/v0/messages/${itemHash}`, {
1158
1180
  cache: "no-cache"
@@ -1209,15 +1231,32 @@ async function waitForDeploymentResult2(itemHash, options) {
1209
1231
  const attempts = Math.max(1, Number(options.attempts ?? 15));
1210
1232
  const delayMs = Math.max(0, Number(options.delayMs ?? 2e3));
1211
1233
  const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1212
- let lastResult = await inspectDeploymentResult2(itemHash, options);
1213
- options.onAttempt?.(lastResult, 1, attempts);
1214
- for (let attempt = 1; attempt < attempts; attempt += 1) {
1234
+ let lastResult = {
1235
+ status: "unknown",
1236
+ errorCode: null,
1237
+ details: null,
1238
+ rejectionReason: null,
1239
+ references: []
1240
+ };
1241
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
1242
+ try {
1243
+ lastResult = await inspectDeploymentResult2(itemHash, options);
1244
+ } catch (error) {
1245
+ if (!isTransientMessageLookupError(error)) {
1246
+ throw error;
1247
+ }
1248
+ lastResult = {
1249
+ ...lastResult,
1250
+ rejectionReason: error instanceof Error ? error.message : String(error)
1251
+ };
1252
+ }
1253
+ options.onAttempt?.(lastResult, attempt, attempts);
1215
1254
  if (lastResult.status === "processed" || lastResult.status === "rejected") {
1216
1255
  return lastResult;
1217
1256
  }
1218
- await sleep2(delayMs);
1219
- lastResult = await inspectDeploymentResult2(itemHash, options);
1220
- options.onAttempt?.(lastResult, attempt + 1, attempts);
1257
+ if (attempt < attempts) {
1258
+ await sleep2(delayMs);
1259
+ }
1221
1260
  }
1222
1261
  return lastResult;
1223
1262
  }
@@ -2274,6 +2313,45 @@ async function configureOrbitdbRelaySetup2(args) {
2274
2313
  clearTimeout(timeout);
2275
2314
  }
2276
2315
  }
2316
+ async function configureUcanStore(args) {
2317
+ const controller = new AbortController();
2318
+ const timeout = setTimeout(
2319
+ () => controller.abort(),
2320
+ Number(args.timeoutMs ?? 18e4)
2321
+ );
2322
+ const payload = {
2323
+ public_ipv4: args.hostIpv4,
2324
+ public_ipv6: args.publicIpv6 ?? void 0,
2325
+ proxy_url: args.proxyUrl ?? void 0,
2326
+ webauthn_origin: args.webauthnOrigin ?? void 0,
2327
+ webauthn_origin_fallbacks: args.webauthnOriginFallbacks ?? void 0,
2328
+ admin_did: args.adminDid ?? void 0,
2329
+ bootstrap_package: args.bootstrapPackage ?? void 0,
2330
+ no_start: args.noStart === true ? true : void 0
2331
+ };
2332
+ try {
2333
+ const response = await args.fetch(
2334
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
2335
+ {
2336
+ method: "POST",
2337
+ headers: {
2338
+ "content-type": "application/json"
2339
+ },
2340
+ body: JSON.stringify(payload),
2341
+ signal: controller.signal
2342
+ }
2343
+ );
2344
+ const responsePayload = await response.json().catch(() => null);
2345
+ if (!response.ok) {
2346
+ throw new Error(
2347
+ `Service setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2348
+ );
2349
+ }
2350
+ return responsePayload;
2351
+ } finally {
2352
+ clearTimeout(timeout);
2353
+ }
2354
+ }
2277
2355
  async function fetchUcGoPeerMetadata(args) {
2278
2356
  const attempts = Math.max(1, Number(args.attempts ?? 60));
2279
2357
  const delayMs = Math.max(0, Number(args.delayMs ?? 3e3));
@@ -2341,7 +2419,7 @@ async function fetchUcGoPeerMetadata(args) {
2341
2419
  }
2342
2420
  }
2343
2421
  throw new Error(
2344
- `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2422
+ `Guest metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2345
2423
  );
2346
2424
  }
2347
2425
 
@@ -3273,7 +3351,130 @@ function createDeploymentProgressEmitter() {
3273
3351
  };
3274
3352
  }
3275
3353
 
3354
+ // ../shared-types/src/ucan-store-bootstrap.ts
3355
+ function isRecord(value) {
3356
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3357
+ }
3358
+ function normalizeNonEmptyString(value) {
3359
+ if (typeof value !== "string") return null;
3360
+ const normalized = value.trim();
3361
+ return normalized ? normalized : null;
3362
+ }
3363
+ function normalizeDid(value) {
3364
+ const did = normalizeNonEmptyString(value);
3365
+ return did && did.startsWith("did:") ? did : null;
3366
+ }
3367
+ function normalizeOrigin(value) {
3368
+ const raw = normalizeNonEmptyString(value);
3369
+ if (!raw) return null;
3370
+ try {
3371
+ const parsed = new URL(raw);
3372
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname || parsed.pathname && parsed.pathname !== "/" || parsed.search || parsed.hash) {
3373
+ return null;
3374
+ }
3375
+ return parsed.origin;
3376
+ } catch {
3377
+ return null;
3378
+ }
3379
+ }
3380
+ function normalizeExpiration(value) {
3381
+ if (value == null) return void 0;
3382
+ if (value === null) return null;
3383
+ if (!Number.isInteger(value) || Number(value) < 0) return void 0;
3384
+ return Number(value);
3385
+ }
3386
+ function validateUcanStoreBootstrapPackage(value) {
3387
+ if (!isRecord(value)) {
3388
+ return {
3389
+ valid: false,
3390
+ errors: ["Bootstrap package must be a JSON object."],
3391
+ bootstrapPackage: null
3392
+ };
3393
+ }
3394
+ const errors = [];
3395
+ const operatorAddress = normalizeNonEmptyString(value.operatorAddress);
3396
+ if (!operatorAddress || !/^0x[a-fA-F0-9]{40}$/.test(operatorAddress)) {
3397
+ errors.push("operatorAddress must be a 0x-prefixed 20-byte Ethereum address.");
3398
+ }
3399
+ const adminDid = normalizeDid(value.adminDid);
3400
+ if (!adminDid) {
3401
+ errors.push("adminDid must be a non-empty DID string.");
3402
+ }
3403
+ const serviceDidRaw = value.serviceDid;
3404
+ const serviceDid = serviceDidRaw == null || serviceDidRaw === "" ? null : normalizeDid(serviceDidRaw);
3405
+ if (serviceDidRaw != null && serviceDidRaw !== "" && !serviceDid) {
3406
+ errors.push("serviceDid must be empty or a non-empty DID string.");
3407
+ }
3408
+ const spaceDid = normalizeDid(value.spaceDid);
3409
+ if (!spaceDid) {
3410
+ errors.push("spaceDid must be a non-empty DID string.");
3411
+ }
3412
+ const rootDelegationProof = normalizeNonEmptyString(value.rootDelegationProof);
3413
+ if (!rootDelegationProof) {
3414
+ errors.push("rootDelegationProof must be a non-empty proof string.");
3415
+ }
3416
+ const allowedCapabilities = Array.isArray(value.allowedCapabilities) ? value.allowedCapabilities.map((entry) => normalizeNonEmptyString(entry)).filter((entry) => Boolean(entry)) : [];
3417
+ if (allowedCapabilities.length === 0) {
3418
+ errors.push("allowedCapabilities must contain at least one capability string.");
3419
+ }
3420
+ const defaultUserDelegationExpiration = normalizeExpiration(
3421
+ value.defaultUserDelegationExpiration
3422
+ );
3423
+ if (value.defaultUserDelegationExpiration != null && defaultUserDelegationExpiration === void 0) {
3424
+ errors.push(
3425
+ "defaultUserDelegationExpiration must be null or a non-negative integer number of seconds."
3426
+ );
3427
+ }
3428
+ const maxUserDelegationExpiration = normalizeExpiration(
3429
+ value.maxUserDelegationExpiration
3430
+ );
3431
+ if (value.maxUserDelegationExpiration != null && maxUserDelegationExpiration === void 0) {
3432
+ errors.push(
3433
+ "maxUserDelegationExpiration must be null or a non-negative integer number of seconds."
3434
+ );
3435
+ }
3436
+ if (typeof defaultUserDelegationExpiration === "number" && typeof maxUserDelegationExpiration === "number" && defaultUserDelegationExpiration > maxUserDelegationExpiration) {
3437
+ errors.push(
3438
+ "defaultUserDelegationExpiration cannot exceed maxUserDelegationExpiration."
3439
+ );
3440
+ }
3441
+ const pwaOrigin = normalizeOrigin(value.pwaOrigin);
3442
+ if (!pwaOrigin) {
3443
+ errors.push("pwaOrigin must be an http(s) origin without path, query, or hash.");
3444
+ }
3445
+ const serviceOrigin = normalizeOrigin(value.serviceOrigin);
3446
+ if (!serviceOrigin) {
3447
+ errors.push("serviceOrigin must be an http(s) origin without path, query, or hash.");
3448
+ }
3449
+ if (errors.length > 0) {
3450
+ return {
3451
+ valid: false,
3452
+ errors,
3453
+ bootstrapPackage: null
3454
+ };
3455
+ }
3456
+ return {
3457
+ valid: true,
3458
+ errors: [],
3459
+ bootstrapPackage: {
3460
+ operatorAddress,
3461
+ adminDid,
3462
+ serviceDid,
3463
+ spaceDid,
3464
+ rootDelegationProof,
3465
+ allowedCapabilities,
3466
+ defaultUserDelegationExpiration,
3467
+ maxUserDelegationExpiration,
3468
+ pwaOrigin,
3469
+ serviceOrigin
3470
+ }
3471
+ };
3472
+ }
3473
+
3276
3474
  // src/shared/controller.ts
3475
+ function deploymentNoun(profile) {
3476
+ return profile === "ucan-store" ? "service" : "relay";
3477
+ }
3277
3478
  function asJsonFetch(input, init) {
3278
3479
  return fetch(input, init).then(async (response) => ({
3279
3480
  ok: response.ok,
@@ -3281,11 +3482,122 @@ function asJsonFetch(input, init) {
3281
3482
  json: async () => await response.json()
3282
3483
  }));
3283
3484
  }
3284
- function isConfirmedRelaySetupResult(value) {
3485
+ function isConfirmedGuestSetupResult(value) {
3285
3486
  return Boolean(
3286
3487
  value && typeof value === "object" && "status" in value && value.status === "configured"
3287
3488
  );
3288
3489
  }
3490
+ function defaultUcanStoreBootstrapInput(props) {
3491
+ return {
3492
+ adminDid: props.ucanStoreBootstrap?.adminDid ?? "",
3493
+ serviceDid: props.ucanStoreBootstrap?.serviceDid ?? DEFAULT_UCAN_STORE_SERVICE_DID,
3494
+ spaceDid: props.ucanStoreBootstrap?.spaceDid ?? "",
3495
+ rootDelegationProof: props.ucanStoreBootstrap?.rootDelegationProof ?? "",
3496
+ allowedCapabilities: props.ucanStoreBootstrap?.allowedCapabilities ?? DEFAULT_UCAN_STORE_ALLOWED_CAPABILITIES.join("\n"),
3497
+ defaultUserDelegationExpiration: props.ucanStoreBootstrap?.defaultUserDelegationExpiration ?? DEFAULT_UCAN_STORE_USER_DELEGATION_EXPIRATION_SECONDS,
3498
+ maxUserDelegationExpiration: props.ucanStoreBootstrap?.maxUserDelegationExpiration ?? DEFAULT_UCAN_STORE_MAX_DELEGATION_EXPIRATION_SECONDS,
3499
+ pwaOrigin: props.ucanStoreBootstrap?.pwaOrigin ?? DEFAULT_UCAN_STORE_PWA_ORIGIN,
3500
+ serviceOrigin: props.ucanStoreBootstrap?.serviceOrigin ?? DEFAULT_UCAN_STORE_SERVICE_ORIGIN
3501
+ };
3502
+ }
3503
+ function currentPageOrigin() {
3504
+ try {
3505
+ const origin = globalThis.location?.origin?.trim();
3506
+ return origin ? origin : null;
3507
+ } catch {
3508
+ return null;
3509
+ }
3510
+ }
3511
+ function parseOptionalExpiration(value) {
3512
+ const normalized = value.trim();
3513
+ if (!normalized) return void 0;
3514
+ const parsed = Number.parseInt(normalized, 10);
3515
+ if (!Number.isInteger(parsed) || parsed < 0) {
3516
+ return null;
3517
+ }
3518
+ return parsed;
3519
+ }
3520
+ function parseAllowedCapabilities(value) {
3521
+ return value.split(/[\n,]/).map((entry) => entry.trim()).filter(Boolean);
3522
+ }
3523
+ function draftUcanStoreBootstrapErrors(args) {
3524
+ const errors = [];
3525
+ const { input } = args;
3526
+ if (!args.operatorAddress) {
3527
+ errors.push("Connect MetaMask before configuring the ucan-store bootstrap package.");
3528
+ }
3529
+ if (!input.adminDid.trim().startsWith("did:")) {
3530
+ errors.push("Admin DID must be a non-empty DID string.");
3531
+ }
3532
+ if (input.serviceDid.trim() && !input.serviceDid.trim().startsWith("did:")) {
3533
+ errors.push("Service DID must be empty or a non-empty DID string.");
3534
+ }
3535
+ if (!input.spaceDid.trim().startsWith("did:")) {
3536
+ errors.push("Space DID must be a non-empty DID string.");
3537
+ }
3538
+ if (!input.rootDelegationProof.trim()) {
3539
+ errors.push("Root delegation proof is required.");
3540
+ }
3541
+ if (parseAllowedCapabilities(input.allowedCapabilities).length === 0) {
3542
+ errors.push("Add at least one allowed capability.");
3543
+ }
3544
+ const defaultExpiration = parseOptionalExpiration(
3545
+ input.defaultUserDelegationExpiration
3546
+ );
3547
+ if (defaultExpiration === null) {
3548
+ errors.push(
3549
+ "Default user delegation expiration must be a non-negative integer number of seconds."
3550
+ );
3551
+ }
3552
+ const maxExpiration = parseOptionalExpiration(
3553
+ input.maxUserDelegationExpiration
3554
+ );
3555
+ if (maxExpiration === null) {
3556
+ errors.push(
3557
+ "Max user delegation expiration must be a non-negative integer number of seconds."
3558
+ );
3559
+ }
3560
+ if (typeof defaultExpiration === "number" && typeof maxExpiration === "number" && defaultExpiration > maxExpiration) {
3561
+ errors.push(
3562
+ "Default user delegation expiration cannot exceed the max user delegation expiration."
3563
+ );
3564
+ }
3565
+ const pwaOrigin = input.pwaOrigin.trim() || currentPageOrigin() || "";
3566
+ if (!pwaOrigin) {
3567
+ errors.push("PWA origin is required for the ucan-store bootstrap package.");
3568
+ }
3569
+ return errors;
3570
+ }
3571
+ function buildUcanStoreBootstrapPackage(args) {
3572
+ const candidate = {
3573
+ operatorAddress: args.operatorAddress,
3574
+ adminDid: args.input.adminDid.trim(),
3575
+ serviceDid: args.input.serviceDid.trim() || null,
3576
+ spaceDid: args.input.spaceDid.trim(),
3577
+ rootDelegationProof: args.input.rootDelegationProof.trim(),
3578
+ allowedCapabilities: parseAllowedCapabilities(args.input.allowedCapabilities),
3579
+ defaultUserDelegationExpiration: parseOptionalExpiration(
3580
+ args.input.defaultUserDelegationExpiration
3581
+ ),
3582
+ maxUserDelegationExpiration: parseOptionalExpiration(
3583
+ args.input.maxUserDelegationExpiration
3584
+ ),
3585
+ pwaOrigin: args.input.pwaOrigin.trim() || args.pwaOriginFallback?.trim() || currentPageOrigin() || "",
3586
+ serviceOrigin: args.input.serviceOrigin.trim() || args.serviceOriginFallback?.trim() || ""
3587
+ };
3588
+ if (candidate.defaultUserDelegationExpiration === void 0) {
3589
+ delete candidate.defaultUserDelegationExpiration;
3590
+ }
3591
+ if (candidate.maxUserDelegationExpiration === void 0) {
3592
+ delete candidate.maxUserDelegationExpiration;
3593
+ }
3594
+ if (candidate.serviceDid === null) {
3595
+ delete candidate.serviceDid;
3596
+ }
3597
+ return validateUcanStoreBootstrapPackage(
3598
+ candidate
3599
+ );
3600
+ }
3289
3601
  function defaultState(props = {}) {
3290
3602
  return {
3291
3603
  ready: false,
@@ -3300,6 +3612,7 @@ function defaultState(props = {}) {
3300
3612
  manifestJson: props.manifestJson ?? "",
3301
3613
  sshPublicKey: props.sshPublicKey ?? "",
3302
3614
  instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
3615
+ ucanStoreBootstrap: defaultUcanStoreBootstrapInput(props),
3303
3616
  tierId: DEFAULT_TIER_ID,
3304
3617
  showAdvanced: Boolean(props.manifestJson?.trim() || props.sshPublicKey?.trim()),
3305
3618
  showInstances: props.showInstances ?? true,
@@ -3434,13 +3747,33 @@ async function resolveManifest(args) {
3434
3747
  if (pasted) return pasted;
3435
3748
  return loadRootfsManifest(args.manifestUrl);
3436
3749
  }
3437
- function relayProfileForManifest(manifest) {
3750
+ function deploymentProfileForManifest(manifest) {
3438
3751
  if (manifest?.profile === "uc-go-peer") return "uc-go-peer";
3439
3752
  if (manifest?.profile === "orbitdb-relay") {
3440
3753
  return manifest.profile;
3441
3754
  }
3755
+ if (manifest?.profile === "ucan-store") return "ucan-store";
3756
+ return null;
3757
+ }
3758
+ function bootstrapRelayProfileForManifest(manifest) {
3759
+ const profile = deploymentProfileForManifest(manifest);
3760
+ if (profile === "uc-go-peer" || profile === "orbitdb-relay") {
3761
+ return profile;
3762
+ }
3442
3763
  return null;
3443
3764
  }
3765
+ function supportsBootstrapRegistrationsForManifest(manifest) {
3766
+ return bootstrapRelayProfileForManifest(manifest) != null;
3767
+ }
3768
+ function deploymentReadyLabel(profile) {
3769
+ return profile === "ucan-store" ? "Service ready" : "Relay ready";
3770
+ }
3771
+ function deploymentReadyDetail(profile, fallback) {
3772
+ if (profile === "ucan-store") {
3773
+ return "The upload service runtime is available and the deployment finished successfully.";
3774
+ }
3775
+ return fallback;
3776
+ }
3444
3777
  function registrationIdInstanceItemHash(registrationId) {
3445
3778
  const normalized = String(registrationId ?? "").trim();
3446
3779
  if (!normalized) return null;
@@ -3689,19 +4022,148 @@ var SponsorRelayController = class {
3689
4022
  this.progressEmitter.emit(nextEvent);
3690
4023
  }
3691
4024
  async configureRelayBootstrapRegistration(args) {
3692
- const profile = relayProfileForManifest(this.state.manifest);
3693
- if (!profile) return;
3694
- if (!this.state.wallet.address) {
4025
+ const deploymentProfile = deploymentProfileForManifest(this.state.manifest);
4026
+ const profile = bootstrapRelayProfileForManifest(this.state.manifest);
4027
+ if (!profile && deploymentProfile !== "ucan-store") return;
4028
+ if (profile && !this.state.wallet.address) {
3695
4029
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3696
4030
  }
3697
- if (!args.runtime.hostIpv4) {
3698
- throw new Error("Relay runtime did not expose a host IPv4 address.");
4031
+ const runtimeHostIpv4 = args.runtime.hostIpv4;
4032
+ if (!runtimeHostIpv4) {
4033
+ throw new Error(
4034
+ `${deploymentProfile === "ucan-store" ? "Service" : "Relay"} runtime did not expose a host IPv4 address.`
4035
+ );
3699
4036
  }
3700
4037
  let runtime = args.runtime;
3701
4038
  let mappedPorts2 = runtime.mappedPorts ?? {};
3702
4039
  const setupPort = mappedPorts2["80"]?.host ?? null;
3703
4040
  if (!setupPort) {
3704
- throw new Error("Relay runtime did not expose the temporary setup endpoint.");
4041
+ throw new Error(
4042
+ `${deploymentProfile === "ucan-store" ? "Service" : "Relay"} runtime did not expose the temporary setup endpoint.`
4043
+ );
4044
+ }
4045
+ if (deploymentProfile === "ucan-store") {
4046
+ const serviceHostIpv4 = runtimeHostIpv4;
4047
+ const serviceProxyUrl = runtime.proxyUrl;
4048
+ if (!serviceProxyUrl) {
4049
+ throw new Error("Service runtime did not expose a proxy URL.");
4050
+ }
4051
+ const walletAddress2 = this.state.wallet.address;
4052
+ if (!walletAddress2) {
4053
+ throw new Error("A connected wallet is required to configure the ucan-store bootstrap package.");
4054
+ }
4055
+ const bootstrapValidation = buildUcanStoreBootstrapPackage({
4056
+ input: this.state.ucanStoreBootstrap,
4057
+ operatorAddress: walletAddress2,
4058
+ serviceOriginFallback: serviceProxyUrl,
4059
+ pwaOriginFallback: currentPageOrigin()
4060
+ });
4061
+ if (!bootstrapValidation.valid || !bootstrapValidation.bootstrapPackage) {
4062
+ throw new Error(
4063
+ bootstrapValidation.errors[0] ?? "The ucan-store bootstrap package is invalid."
4064
+ );
4065
+ }
4066
+ this.emitProgress({
4067
+ stage: "deployment-confirmed",
4068
+ label: "Configuring service",
4069
+ progress: 93,
4070
+ status: "info",
4071
+ itemHash: args.itemHash,
4072
+ detail: "Waiting for the guest setup endpoint before applying the upload service configuration."
4073
+ });
4074
+ const setupHealth = await waitForSetupEndpoint({
4075
+ hostIpv4: serviceHostIpv4,
4076
+ setupPort,
4077
+ fetch: (url, init) => fetch(url, init),
4078
+ attempts: 15,
4079
+ delayMs: 4e3,
4080
+ httpTimeoutMs: 1e4,
4081
+ onAttempt: (result, attempt, attempts) => {
4082
+ this.emitProgress({
4083
+ stage: "deployment-confirmed",
4084
+ label: "Waiting for guest setup",
4085
+ progress: 93,
4086
+ status: result.ok ? "success" : "info",
4087
+ itemHash: args.itemHash,
4088
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${serviceHostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${serviceHostIpv4}:${setupPort}/health.`
4089
+ });
4090
+ }
4091
+ });
4092
+ if (!setupHealth.ok) {
4093
+ throw new Error(
4094
+ `Service setup endpoint did not become reachable at http://${serviceHostIpv4}:${setupPort}/health.`
4095
+ );
4096
+ }
4097
+ this.emitProgress({
4098
+ stage: "deployment-confirmed",
4099
+ label: "Applying service config",
4100
+ progress: 94,
4101
+ status: "info",
4102
+ itemHash: args.itemHash,
4103
+ detail: "Publishing the public HTTPS origin into the guest upload service configuration."
4104
+ });
4105
+ const configureResult = await configureUcanStore({
4106
+ hostIpv4: serviceHostIpv4,
4107
+ publicIpv6: runtime.ipv6,
4108
+ setupPort,
4109
+ proxyUrl: serviceProxyUrl,
4110
+ webauthnOrigin: serviceProxyUrl,
4111
+ adminDid: bootstrapValidation.bootstrapPackage.adminDid,
4112
+ bootstrapPackage: bootstrapValidation.bootstrapPackage,
4113
+ fetch: (url, init) => fetch(url, init),
4114
+ timeoutMs: 18e4
4115
+ });
4116
+ if (!isConfirmedGuestSetupResult(configureResult)) {
4117
+ throw new Error(
4118
+ `Service guest configuration could not be confirmed at http://${serviceHostIpv4}:${setupPort}/configure.`
4119
+ );
4120
+ }
4121
+ this.emitProgress({
4122
+ stage: "deployment-confirmed",
4123
+ label: "Waiting for service metadata",
4124
+ progress: 95,
4125
+ status: "info",
4126
+ itemHash: args.itemHash,
4127
+ detail: "Waiting for the upload service metadata endpoint to report the public service DID and PWA environment."
4128
+ });
4129
+ const serviceMetadata = await fetchUcGoPeerMetadata({
4130
+ hostIpv4: serviceHostIpv4,
4131
+ setupPort,
4132
+ fetch: (url, init) => fetch(url, init),
4133
+ attempts: 80,
4134
+ delayMs: 3e3,
4135
+ timeoutMs: 24e4,
4136
+ isReady: ({ payload, ok }) => {
4137
+ if (!ok || !payload || typeof payload !== "object") return false;
4138
+ const metadata2 = payload.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
4139
+ const pwaEnv = metadata2?.pwa_env && typeof metadata2.pwa_env === "object" ? metadata2.pwa_env : null;
4140
+ return Boolean(
4141
+ typeof metadata2?.upload_service_did === "string" && metadata2.upload_service_did.length > 0 && typeof pwaEnv?.VITE_UPLOAD_SERVICE_URL === "string" && pwaEnv.VITE_UPLOAD_SERVICE_URL.length > 0
4142
+ );
4143
+ },
4144
+ onAttempt: ({ ready, attempt, attempts, requestUrl }) => {
4145
+ this.emitProgress({
4146
+ stage: "deployment-confirmed",
4147
+ label: ready ? "Service metadata ready" : "Waiting for service metadata",
4148
+ progress: 95,
4149
+ status: ready ? "success" : "info",
4150
+ itemHash: args.itemHash,
4151
+ detail: ready ? `Service metadata ${attempt}/${attempts}: public upload service wiring is ready.` : `Service metadata ${attempt}/${attempts}: waiting for ${requestUrl}.`
4152
+ });
4153
+ }
4154
+ });
4155
+ const metadata = serviceMetadata && typeof serviceMetadata === "object" ? serviceMetadata.metadata ?? null : null;
4156
+ if (!metadata || typeof metadata !== "object" || typeof metadata.upload_service_did !== "string") {
4157
+ throw new Error(
4158
+ "Service metadata did not include the public upload service DID."
4159
+ );
4160
+ }
4161
+ return;
4162
+ }
4163
+ if (!profile) return;
4164
+ const walletAddress = this.state.wallet.address;
4165
+ if (!walletAddress) {
4166
+ throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3705
4167
  }
3706
4168
  let bootstrapSignalRelayMetadata = null;
3707
4169
  if (profile === "orbitdb-relay") {
@@ -3714,7 +4176,7 @@ var SponsorRelayController = class {
3714
4176
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3715
4177
  });
3716
4178
  const setupHealth = await waitForSetupEndpoint({
3717
- hostIpv4: args.runtime.hostIpv4,
4179
+ hostIpv4: runtimeHostIpv4,
3718
4180
  setupPort,
3719
4181
  fetch: (url, init) => fetch(url, init),
3720
4182
  attempts: 15,
@@ -3727,13 +4189,13 @@ var SponsorRelayController = class {
3727
4189
  progress: 93,
3728
4190
  status: result.ok ? "success" : "info",
3729
4191
  itemHash: args.itemHash,
3730
- detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${args.runtime.hostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${args.runtime.hostIpv4}:${setupPort}/health.`
4192
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${runtimeHostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${runtimeHostIpv4}:${setupPort}/health.`
3731
4193
  });
3732
4194
  }
3733
4195
  });
3734
4196
  if (!setupHealth.ok) {
3735
4197
  throw new Error(
3736
- `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
4198
+ `Relay setup endpoint did not become reachable at http://${runtimeHostIpv4}:${setupPort}/health.`
3737
4199
  );
3738
4200
  }
3739
4201
  this.emitProgress({
@@ -3750,7 +4212,7 @@ var SponsorRelayController = class {
3750
4212
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3751
4213
  }
3752
4214
  const configureResult = await configureOrbitdbRelaySetup2({
3753
- hostIpv4: args.runtime.hostIpv4,
4215
+ hostIpv4: runtimeHostIpv4,
3754
4216
  publicIpv6: args.runtime.ipv6,
3755
4217
  setupPort,
3756
4218
  tcpPort,
@@ -3763,7 +4225,7 @@ var SponsorRelayController = class {
3763
4225
  fetch: (url, init) => fetch(url, init),
3764
4226
  timeoutMs: 18e4
3765
4227
  });
3766
- if (!isConfirmedRelaySetupResult(configureResult)) {
4228
+ if (!isConfirmedGuestSetupResult(configureResult)) {
3767
4229
  throw new Error(
3768
4230
  `Relay guest configuration could not be confirmed at http://${args.runtime.hostIpv4}:${setupPort}/configure.`
3769
4231
  );
@@ -3821,7 +4283,7 @@ var SponsorRelayController = class {
3821
4283
  "Relay runtime lost its host IPv4 address while waiting for HTTPS activation."
3822
4284
  );
3823
4285
  }
3824
- const runtimeHostIpv4 = runtime.hostIpv4;
4286
+ const runtimeHostIpv42 = runtime.hostIpv4;
3825
4287
  const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
3826
4288
  this.emitProgress({
3827
4289
  stage: "deployment-confirmed",
@@ -3834,13 +4296,13 @@ var SponsorRelayController = class {
3834
4296
  const record = {
3835
4297
  deploymentToken: args.deploymentToken,
3836
4298
  profile,
3837
- ownerAddress: this.state.wallet.address,
4299
+ ownerAddress: walletAddress,
3838
4300
  instanceItemHash: args.itemHash,
3839
4301
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3840
4302
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3841
4303
  status: "pending",
3842
4304
  runtime: {
3843
- publicIpv4: runtimeHostIpv4,
4305
+ publicIpv4: runtimeHostIpv42,
3844
4306
  publicIpv6: runtime.ipv6 ?? null,
3845
4307
  proxyUrl: activeProxyUrl,
3846
4308
  mappedPorts: mappedPorts2
@@ -3854,7 +4316,7 @@ var SponsorRelayController = class {
3854
4316
  }
3855
4317
  };
3856
4318
  const bootstrapConfigPublication = await publishVmBootstrapConfig({
3857
- sender: this.state.wallet.address,
4319
+ sender: walletAddress,
3858
4320
  record,
3859
4321
  signer: personalSign,
3860
4322
  hasher: sha256Hex,
@@ -3881,7 +4343,7 @@ var SponsorRelayController = class {
3881
4343
  });
3882
4344
  const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
3883
4345
  deploymentToken: args.deploymentToken,
3884
- ownerAddress: this.state.wallet.address,
4346
+ ownerAddress: walletAddress,
3885
4347
  instanceItemHash: args.itemHash,
3886
4348
  fetch: asJsonFetch,
3887
4349
  apiHost: this.client.apiHost,
@@ -4053,7 +4515,7 @@ var SponsorRelayController = class {
4053
4515
  detail: "Guest bootstrap registration was delayed. Publishing the relay bootstrap record from the browser."
4054
4516
  });
4055
4517
  const fallbackPublication = await publishRelayBootstrapRegistration({
4056
- sender: this.state.wallet.address,
4518
+ sender: walletAddress,
4057
4519
  signer: personalSign,
4058
4520
  hasher: sha256Hex,
4059
4521
  fetch: asJsonFetch,
@@ -4073,7 +4535,7 @@ var SponsorRelayController = class {
4073
4535
  fallbackPublication
4074
4536
  });
4075
4537
  const fallbackVisibleRegistration = await waitForRelayBootstrapRegistration({
4076
- sender: this.state.wallet.address,
4538
+ sender: walletAddress,
4077
4539
  registrationId,
4078
4540
  peerId,
4079
4541
  fetch: asJsonFetch,
@@ -4106,7 +4568,7 @@ var SponsorRelayController = class {
4106
4568
  if (profile === "uc-go-peer" && args.deploymentToken) {
4107
4569
  let cleanupAggregateItemHash = null;
4108
4570
  await deleteVmBootstrapConfig({
4109
- sender: this.state.wallet.address,
4571
+ sender: walletAddress,
4110
4572
  deploymentToken: args.deploymentToken,
4111
4573
  signer: personalSign,
4112
4574
  hasher: sha256Hex,
@@ -4272,17 +4734,35 @@ var SponsorRelayController = class {
4272
4734
  });
4273
4735
  return;
4274
4736
  }
4737
+ const currentProfile = deploymentProfileForManifest(this.state.manifest);
4275
4738
  const hasConfirmedBootstrapRegistration = this.state.bootstrapRegistrations.some(
4276
4739
  (entry) => entry.instanceItemHash === itemHash && entry.confirmed
4277
4740
  );
4741
+ if (currentProfile === "ucan-store") {
4742
+ this.emitProgress({
4743
+ stage: "completed",
4744
+ label: deploymentReadyLabel(currentProfile),
4745
+ progress: 100,
4746
+ status: "success",
4747
+ itemHash,
4748
+ detail: deploymentReadyDetail(
4749
+ currentProfile,
4750
+ "Runtime networking is available and the deployment finished successfully."
4751
+ )
4752
+ });
4753
+ return;
4754
+ }
4278
4755
  if (hasConfirmedBootstrapRegistration) {
4279
4756
  this.emitProgress({
4280
4757
  stage: "completed",
4281
- label: "Relay ready",
4758
+ label: deploymentReadyLabel(currentProfile),
4282
4759
  progress: 100,
4283
4760
  status: "success",
4284
4761
  itemHash,
4285
- detail: "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4762
+ detail: deploymentReadyDetail(
4763
+ currentProfile,
4764
+ "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4765
+ )
4286
4766
  });
4287
4767
  return;
4288
4768
  }
@@ -4292,7 +4772,7 @@ var SponsorRelayController = class {
4292
4772
  progress: 92,
4293
4773
  status: "info",
4294
4774
  itemHash,
4295
- detail: "Deployment processed and runtime networking is available. Finishing relay setup and bootstrap verification."
4775
+ detail: `Deployment processed and runtime networking is available. Finishing ${deploymentNoun(currentProfile)} setup and verification.`
4296
4776
  });
4297
4777
  }
4298
4778
  canSkipRuntimeRefresh(instance) {
@@ -4405,6 +4885,15 @@ var SponsorRelayController = class {
4405
4885
  setInstanceName(instanceName) {
4406
4886
  this.patch({ instanceName });
4407
4887
  }
4888
+ setUcanStoreBootstrapField(field, value) {
4889
+ this.patch({
4890
+ ucanStoreBootstrap: {
4891
+ ...this.state.ucanStoreBootstrap,
4892
+ [field]: value
4893
+ },
4894
+ showAdvanced: this.state.showAdvanced || Boolean(value.trim())
4895
+ });
4896
+ }
4408
4897
  setTierId(tierId) {
4409
4898
  this.patch({ tierId });
4410
4899
  this.recomputePricingSummary();
@@ -4490,6 +4979,7 @@ var SponsorRelayController = class {
4490
4979
  }
4491
4980
  }
4492
4981
  async refresh() {
4982
+ const currentProfile = deploymentProfileForManifest(this.state.manifest);
4493
4983
  this.trace("refresh:start", {
4494
4984
  wallet: this.state.wallet.address,
4495
4985
  manifestUrl: this.state.manifestUrl,
@@ -4498,7 +4988,7 @@ var SponsorRelayController = class {
4498
4988
  this.patch({
4499
4989
  busy: { refreshing: true },
4500
4990
  errorText: null,
4501
- statusText: "Refreshing relay sponsor data"
4991
+ statusText: currentProfile === "ucan-store" ? "Refreshing service deployment data" : "Refreshing relay deployment data"
4502
4992
  });
4503
4993
  try {
4504
4994
  const [pricingSummary, crns] = await Promise.all([
@@ -4510,6 +5000,8 @@ var SponsorRelayController = class {
4510
5000
  manifestJson: this.state.manifestJson
4511
5001
  }).catch((error) => manifestLoadErrorState(error));
4512
5002
  const manifest = manifestState.manifest;
5003
+ const resolvedProfile = deploymentProfileForManifest(manifest);
5004
+ const bootstrapEnabled = supportsBootstrapRegistrationsForManifest(manifest);
4513
5005
  let balance = this.state.balance;
4514
5006
  let instances = [];
4515
5007
  let bootstrapRegistrations = [];
@@ -4518,10 +5010,10 @@ var SponsorRelayController = class {
4518
5010
  const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
4519
5011
  this.client.fetchBalance(this.state.wallet.address),
4520
5012
  this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([]),
4521
- fetchAlephBootstrapPosts({
5013
+ bootstrapEnabled ? fetchAlephBootstrapPosts({
4522
5014
  apiHost: this.client.apiHost,
4523
5015
  fetch
4524
- }).catch(() => [])
5016
+ }).catch(() => []) : Promise.resolve([])
4525
5017
  ]);
4526
5018
  balance = nextBalance;
4527
5019
  instances = await Promise.all(
@@ -4651,7 +5143,7 @@ var SponsorRelayController = class {
4651
5143
  bootstrapRegistrations,
4652
5144
  orphanBootstrapRegistrations,
4653
5145
  busy: { refreshing: false },
4654
- statusText: "Relay sponsor data ready"
5146
+ statusText: resolvedProfile === "ucan-store" ? "Service deployment data ready" : "Relay deployment data ready"
4655
5147
  });
4656
5148
  this.trace("refresh:success", {
4657
5149
  manifestValid: manifestState.valid,
@@ -4688,6 +5180,16 @@ var SponsorRelayController = class {
4688
5180
  });
4689
5181
  return;
4690
5182
  }
5183
+ if (deploymentProfileForManifest(this.state.manifest) === "ucan-store") {
5184
+ const draftErrors = draftUcanStoreBootstrapErrors({
5185
+ input: this.state.ucanStoreBootstrap,
5186
+ operatorAddress: this.state.wallet.address
5187
+ });
5188
+ if (draftErrors.length > 0) {
5189
+ this.patch({ errorText: draftErrors[0] });
5190
+ return;
5191
+ }
5192
+ }
4691
5193
  this.patch({
4692
5194
  busy: { deploying: true },
4693
5195
  errorText: null,
@@ -4737,7 +5239,7 @@ var SponsorRelayController = class {
4737
5239
  detail: `Trying ${crnDisplayName}`
4738
5240
  });
4739
5241
  try {
4740
- const profile = relayProfileForManifest(this.state.manifest);
5242
+ const profile = deploymentProfileForManifest(this.state.manifest);
4741
5243
  attemptProfile = profile;
4742
5244
  const sshPublicKey = profile === "uc-go-peer" ? appendDeploymentTokenToSshPublicKey(
4743
5245
  this.state.sshPublicKey.trim(),
@@ -4956,11 +5458,14 @@ var SponsorRelayController = class {
4956
5458
  this.lastCompletedDeploymentHash = result.itemHash;
4957
5459
  this.emitProgress({
4958
5460
  stage: "completed",
4959
- label: "Relay ready",
5461
+ label: deploymentReadyLabel(attemptProfile),
4960
5462
  progress: 100,
4961
5463
  status: "success",
4962
5464
  itemHash: result.itemHash,
4963
- detail: "Relay runtime, setup, metadata, and bootstrap registration are confirmed."
5465
+ detail: deploymentReadyDetail(
5466
+ attemptProfile,
5467
+ "Relay runtime, setup, metadata, and bootstrap registration are confirmed."
5468
+ )
4964
5469
  });
4965
5470
  return;
4966
5471
  } catch (error) {
@@ -5034,11 +5539,14 @@ var SponsorRelayController = class {
5034
5539
  this.lastCompletedDeploymentHash = attemptItemHash;
5035
5540
  this.emitProgress({
5036
5541
  stage: "completed",
5037
- label: "Relay ready",
5542
+ label: deploymentReadyLabel(attemptProfile),
5038
5543
  progress: 100,
5039
5544
  status: "success",
5040
5545
  itemHash: attemptItemHash,
5041
- detail: "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
5546
+ detail: deploymentReadyDetail(
5547
+ attemptProfile,
5548
+ "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
5549
+ )
5042
5550
  });
5043
5551
  return;
5044
5552
  }
@@ -5065,11 +5573,14 @@ var SponsorRelayController = class {
5065
5573
  this.lastCompletedDeploymentHash = attemptItemHash;
5066
5574
  this.emitProgress({
5067
5575
  stage: "completed",
5068
- label: "Relay ready",
5576
+ label: deploymentReadyLabel(attemptProfile),
5069
5577
  progress: 100,
5070
5578
  status: "success",
5071
5579
  itemHash: attemptItemHash,
5072
- detail: "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
5580
+ detail: deploymentReadyDetail(
5581
+ attemptProfile,
5582
+ "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
5583
+ )
5073
5584
  });
5074
5585
  return;
5075
5586
  }
@@ -5384,7 +5895,8 @@ function useSponsorRelayController(props) {
5384
5895
  props.schedulerApiHost,
5385
5896
  props.showInstances,
5386
5897
  props.sshPublicKey,
5387
- props.twoN6ApiHost
5898
+ props.twoN6ApiHost,
5899
+ props.ucanStoreBootstrap
5388
5900
  ]
5389
5901
  );
5390
5902
  useEffect(() => {
@@ -5518,6 +6030,27 @@ var POLLING_STAGES = /* @__PURE__ */ new Set([
5518
6030
  "publishing-bootstrap",
5519
6031
  "refreshing-instances"
5520
6032
  ]);
6033
+ function stateDeploymentProfile(state) {
6034
+ return state.manifest?.profile === "ucan-store" ? "ucan-store" : "relay";
6035
+ }
6036
+ function supportsBootstrapUi(state) {
6037
+ return stateDeploymentProfile(state) !== "ucan-store";
6038
+ }
6039
+ function deploymentPanelTitle(state) {
6040
+ return stateDeploymentProfile(state) === "ucan-store" ? "Sponsor Service" : "Sponsor Relay";
6041
+ }
6042
+ function deploymentLauncherLabel(state, compactInlineLabel, launcherMode) {
6043
+ if (launcherMode === "inline" && compactInlineLabel) {
6044
+ return stateDeploymentProfile(state) === "ucan-store" ? "Service" : "Relay";
6045
+ }
6046
+ return deploymentPanelTitle(state);
6047
+ }
6048
+ function deploymentInstanceFallbackLabel(state) {
6049
+ return stateDeploymentProfile(state) === "ucan-store" ? "service" : "relay";
6050
+ }
6051
+ function deploymentButtonLabel(state) {
6052
+ return stateDeploymentProfile(state) === "ucan-store" ? "Deploy Service" : "Deploy Relay";
6053
+ }
5521
6054
  function isDeploymentProgressVisible(state) {
5522
6055
  const stage = state.deploymentProgress.stage;
5523
6056
  if (stage === "idle") {
@@ -5533,17 +6066,19 @@ function isDeploymentProgressVisible(state) {
5533
6066
  }
5534
6067
  function pollingIndicator(state) {
5535
6068
  if (state.busy.refreshing) {
6069
+ const isService2 = stateDeploymentProfile(state) === "ucan-store";
5536
6070
  return {
5537
- label: "Checking relay state",
5538
- detail: state.statusText || "Refreshing relay sponsor data from Aleph and the selected CRN."
6071
+ label: isService2 ? "Checking deployment state" : "Checking relay state",
6072
+ detail: state.statusText || (isService2 ? "Refreshing service deployment data from Aleph and the selected CRN." : "Refreshing relay deployment data from Aleph and the selected CRN.")
5539
6073
  };
5540
6074
  }
5541
6075
  if (!POLLING_STAGES.has(state.deploymentProgress.stage)) {
5542
6076
  return null;
5543
6077
  }
6078
+ const isService = stateDeploymentProfile(state) === "ucan-store";
5544
6079
  return {
5545
- label: state.deploymentProgress.label || "Polling relay state",
5546
- detail: state.deploymentProgress.detail || state.statusText || "Waiting for the next confirmed relay state from Aleph."
6080
+ label: state.deploymentProgress.label || (isService ? "Polling deployment state" : "Polling relay state"),
6081
+ detail: state.deploymentProgress.detail || state.statusText || (isService ? "Waiting for the next confirmed deployment state from Aleph." : "Waiting for the next confirmed relay state from Aleph.")
5547
6082
  };
5548
6083
  }
5549
6084
  function launcherIndicator(state) {
@@ -5592,7 +6127,7 @@ function launcherIndicator(state) {
5592
6127
  if (state.busy.refreshing) {
5593
6128
  return {
5594
6129
  label: "SYNC",
5595
- detail: "Refreshing relay state",
6130
+ detail: stateDeploymentProfile(state) === "ucan-store" ? "Refreshing deployment state" : "Refreshing relay state",
5596
6131
  tone: "info"
5597
6132
  };
5598
6133
  }
@@ -5609,6 +6144,7 @@ function SponsorRelayFab(props) {
5609
6144
  const versionLabel = resolvedVersion.startsWith("v") ? resolvedVersion : `v${resolvedVersion}`;
5610
6145
  const launcherMode = props.launcherMode ?? "floating";
5611
6146
  const indicator = launcherIndicator(state);
6147
+ const bootstrapUiEnabled = supportsBootstrapUi(state);
5612
6148
  const confirmedRegistrationByInstanceHash = new Map(
5613
6149
  (state.bootstrapRegistrations ?? []).filter((entry) => entry.confirmed && entry.instanceItemHash).map((entry) => [entry.instanceItemHash, entry])
5614
6150
  );
@@ -5710,7 +6246,8 @@ function SponsorRelayFab(props) {
5710
6246
  bottom: "11.5rem",
5711
6247
  maxHeight: "calc(100vh - 12.5rem)"
5712
6248
  };
5713
- const launcherLabel = launcherMode === "inline" && compactInlineLabel ? "Relay" : "Sponsor Relay";
6249
+ const launcherLabel = deploymentLauncherLabel(state, compactInlineLabel, launcherMode);
6250
+ const deployCallToAction = state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : deploymentButtonLabel(state) : "Connect MetaMask";
5714
6251
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5715
6252
  /* @__PURE__ */ jsx("style", { children: `@keyframes leSpaceRelayPulse { 0%, 100% { transform: scale(1); opacity: 0.74; } 50% { transform: scale(1.22); opacity: 1; } }` }),
5716
6253
  /* @__PURE__ */ jsxs(
@@ -5723,7 +6260,7 @@ function SponsorRelayFab(props) {
5723
6260
  onMouseLeave: () => setHovered(false),
5724
6261
  onFocus: () => setHovered(true),
5725
6262
  onBlur: () => setHovered(false),
5726
- title: indicator.detail ?? "Sponsor Relay",
6263
+ title: indicator.detail ?? deploymentPanelTitle(state),
5727
6264
  style: {
5728
6265
  position: launcherMode === "floating" ? "fixed" : "relative",
5729
6266
  right: launcherMode === "floating" ? "1.4rem" : void 0,
@@ -5880,7 +6417,7 @@ function SponsorRelayFab(props) {
5880
6417
  margin: "0.2rem 0 0",
5881
6418
  fontFamily: '"Epilogue", sans-serif'
5882
6419
  },
5883
- children: "Sponsor Relay"
6420
+ children: deploymentPanelTitle(state)
5884
6421
  }
5885
6422
  )
5886
6423
  ] }),
@@ -6217,7 +6754,156 @@ function SponsorRelayFab(props) {
6217
6754
  )
6218
6755
  ]
6219
6756
  }
6220
- )
6757
+ ),
6758
+ stateDeploymentProfile(state) === "ucan-store" ? /* @__PURE__ */ jsxs("details", { open: true, children: [
6759
+ /* @__PURE__ */ jsx("summary", { children: "UCAN Store Bootstrap" }),
6760
+ /* @__PURE__ */ jsxs(
6761
+ "div",
6762
+ {
6763
+ style: {
6764
+ display: "grid",
6765
+ gap: "0.75rem",
6766
+ marginTop: "0.65rem"
6767
+ },
6768
+ children: [
6769
+ /* @__PURE__ */ jsx(
6770
+ "input",
6771
+ {
6772
+ style: fieldStyle,
6773
+ value: state.ucanStoreBootstrap.adminDid,
6774
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6775
+ "adminDid",
6776
+ event.currentTarget.value
6777
+ ),
6778
+ placeholder: "Admin DID"
6779
+ }
6780
+ ),
6781
+ /* @__PURE__ */ jsx(
6782
+ "input",
6783
+ {
6784
+ style: fieldStyle,
6785
+ value: state.ucanStoreBootstrap.serviceDid,
6786
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6787
+ "serviceDid",
6788
+ event.currentTarget.value
6789
+ ),
6790
+ placeholder: "Service DID override (optional)"
6791
+ }
6792
+ ),
6793
+ /* @__PURE__ */ jsx(
6794
+ "input",
6795
+ {
6796
+ style: fieldStyle,
6797
+ value: state.ucanStoreBootstrap.spaceDid,
6798
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6799
+ "spaceDid",
6800
+ event.currentTarget.value
6801
+ ),
6802
+ placeholder: "Space DID"
6803
+ }
6804
+ ),
6805
+ /* @__PURE__ */ jsx(
6806
+ "textarea",
6807
+ {
6808
+ style: fieldStyle,
6809
+ rows: 5,
6810
+ value: state.ucanStoreBootstrap.rootDelegationProof,
6811
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6812
+ "rootDelegationProof",
6813
+ event.currentTarget.value
6814
+ ),
6815
+ placeholder: "Root delegation proof"
6816
+ }
6817
+ ),
6818
+ /* @__PURE__ */ jsx(
6819
+ "textarea",
6820
+ {
6821
+ style: fieldStyle,
6822
+ rows: 4,
6823
+ value: state.ucanStoreBootstrap.allowedCapabilities,
6824
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6825
+ "allowedCapabilities",
6826
+ event.currentTarget.value
6827
+ ),
6828
+ placeholder: "Allowed capabilities, one per line or comma-separated\nspace/blob/add\nspace/blob/list"
6829
+ }
6830
+ ),
6831
+ /* @__PURE__ */ jsxs(
6832
+ "div",
6833
+ {
6834
+ style: {
6835
+ display: "grid",
6836
+ gap: "0.75rem",
6837
+ gridTemplateColumns: "repeat(auto-fit, minmax(10rem, 1fr))"
6838
+ },
6839
+ children: [
6840
+ /* @__PURE__ */ jsx(
6841
+ "input",
6842
+ {
6843
+ style: fieldStyle,
6844
+ value: state.ucanStoreBootstrap.defaultUserDelegationExpiration,
6845
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6846
+ "defaultUserDelegationExpiration",
6847
+ event.currentTarget.value
6848
+ ),
6849
+ placeholder: "Default expiration (seconds)"
6850
+ }
6851
+ ),
6852
+ /* @__PURE__ */ jsx(
6853
+ "input",
6854
+ {
6855
+ style: fieldStyle,
6856
+ value: state.ucanStoreBootstrap.maxUserDelegationExpiration,
6857
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6858
+ "maxUserDelegationExpiration",
6859
+ event.currentTarget.value
6860
+ ),
6861
+ placeholder: "Max expiration (seconds)"
6862
+ }
6863
+ )
6864
+ ]
6865
+ }
6866
+ ),
6867
+ /* @__PURE__ */ jsx(
6868
+ "input",
6869
+ {
6870
+ style: fieldStyle,
6871
+ value: state.ucanStoreBootstrap.pwaOrigin,
6872
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6873
+ "pwaOrigin",
6874
+ event.currentTarget.value
6875
+ ),
6876
+ placeholder: "PWA origin override (optional, defaults to current page origin)"
6877
+ }
6878
+ ),
6879
+ /* @__PURE__ */ jsx(
6880
+ "input",
6881
+ {
6882
+ style: fieldStyle,
6883
+ value: state.ucanStoreBootstrap.serviceOrigin,
6884
+ onChange: (event) => controller.setUcanStoreBootstrapField(
6885
+ "serviceOrigin",
6886
+ event.currentTarget.value
6887
+ ),
6888
+ placeholder: "Service origin override (optional, defaults to runtime proxy URL)"
6889
+ }
6890
+ ),
6891
+ /* @__PURE__ */ jsx(
6892
+ "div",
6893
+ {
6894
+ style: {
6895
+ color: "#9fb2ca",
6896
+ fontSize: "0.74rem",
6897
+ lineHeight: 1.35,
6898
+ marginTop: "-0.15rem"
6899
+ },
6900
+ children: "Operator address comes from the connected MetaMask account. If you leave service origin empty, `relay-button` will use the runtime proxy URL during guest configuration."
6901
+ }
6902
+ )
6903
+ ]
6904
+ }
6905
+ )
6906
+ ] }) : null
6221
6907
  ]
6222
6908
  }
6223
6909
  )
@@ -6253,7 +6939,7 @@ function SponsorRelayFab(props) {
6253
6939
  onClick: () => void (state.wallet.connected ? controller.deploy() : controller.connectWallet()),
6254
6940
  disabled: state.wallet.connected ? state.busy.deploying || state.rootfsHealth.tone !== "ok" : state.busy.connectingWallet,
6255
6941
  style: { ...primaryButtonStyle, width: "100%", marginTop: "1rem" },
6256
- children: state.wallet.connected ? state.busy.deploying ? "Deploying\u2026" : "Deploy Relay" : "Connect MetaMask"
6942
+ children: deployCallToAction
6257
6943
  }
6258
6944
  ),
6259
6945
  state.lastDeploymentHash ? /* @__PURE__ */ jsxs("p", { children: [
@@ -6280,8 +6966,8 @@ function SponsorRelayFab(props) {
6280
6966
  flexWrap: "wrap"
6281
6967
  },
6282
6968
  children: [
6283
- (entry.instance.content?.metadata?.name ?? "relay") + " \xB7 " + shortHash(entry.instance.item_hash),
6284
- confirmedRegistration ? /* @__PURE__ */ jsxs(
6969
+ (entry.instance.content?.metadata?.name ?? deploymentInstanceFallbackLabel(state)) + " \xB7 " + shortHash(entry.instance.item_hash),
6970
+ bootstrapUiEnabled && confirmedRegistration ? /* @__PURE__ */ jsxs(
6285
6971
  "span",
6286
6972
  {
6287
6973
  style: {
@@ -6329,7 +7015,7 @@ function SponsorRelayFab(props) {
6329
7015
  "Status: ",
6330
7016
  entry.details.messageStatus
6331
7017
  ] }),
6332
- confirmedRegistration ? /* @__PURE__ */ jsxs("div", { children: [
7018
+ bootstrapUiEnabled && confirmedRegistration ? /* @__PURE__ */ jsxs("div", { children: [
6333
7019
  "Bootstrap registration:",
6334
7020
  " ",
6335
7021
  shortHash(
@@ -6381,7 +7067,7 @@ function SponsorRelayFab(props) {
6381
7067
  )
6382
7068
  ] }, entry.instance.item_hash);
6383
7069
  }),
6384
- orphanRegistrations.length > 0 ? /* @__PURE__ */ jsxs(
7070
+ bootstrapUiEnabled && orphanRegistrations.length > 0 ? /* @__PURE__ */ jsxs(
6385
7071
  "div",
6386
7072
  {
6387
7073
  style: {