@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/shared/index.js CHANGED
@@ -2,6 +2,21 @@
2
2
  var DEFAULT_INSTANCE_NAME = "sponsor-relay";
3
3
  var DEFAULT_MANIFEST_URL = "./rootfs-manifest.json";
4
4
  var DEFAULT_TIER_ID = "tier-1";
5
+ var DEFAULT_UCAN_STORE_PWA_ORIGIN = "https://ucan.nicokrause.com";
6
+ var DEFAULT_UCAN_STORE_SERVICE_ORIGIN = "https://ucan-api.nicokrause.com";
7
+ var DEFAULT_UCAN_STORE_SERVICE_DID = "did:web:ucan-api.nicokrause.com";
8
+ var DEFAULT_UCAN_STORE_ALLOWED_CAPABILITIES = [
9
+ "space/blob/add",
10
+ "space/blob/list",
11
+ "space/index/add",
12
+ "space/index/list",
13
+ "filecoin/offer",
14
+ "upload/add",
15
+ "upload/list",
16
+ "store/add"
17
+ ];
18
+ var DEFAULT_UCAN_STORE_USER_DELEGATION_EXPIRATION_SECONDS = "31536000";
19
+ var DEFAULT_UCAN_STORE_MAX_DELEGATION_EXPIRATION_SECONDS = "315360000";
5
20
  var ROOTFS_MISSING_STATE = {
6
21
  tone: "idle",
7
22
  label: "manifest missing",
@@ -27,7 +42,7 @@ var RECENT_INSTANCE_RUNTIME_GRACE_MS = 10 * 60 * 1e3;
27
42
  var DEPLOYMENT_PENDING_WARNING_MS = 3 * 60 * 1e3;
28
43
 
29
44
  // src/shared/package-version.ts
30
- var UI_PACKAGE_VERSION = "0.6.1";
45
+ var UI_PACKAGE_VERSION = "0.6.3";
31
46
 
32
47
  // src/shared/format.ts
33
48
  function shortHash(value, head = 8, tail = 6) {
@@ -870,7 +885,7 @@ async function fetchInstancePricing(apiHost = DEFAULT_ALEPH_API_HOST, aggregateA
870
885
  }
871
886
 
872
887
  // ../core/src/constants.ts
873
- var DEFAULT_ALEPH_CHANNEL = "TEST";
888
+ var DEFAULT_ALEPH_CHANNEL = "ALEPH-CLOUDSOLUTIONS";
874
889
 
875
890
  // ../core/src/manifests.ts
876
891
  var DEFAULT_ALEPH_API_HOST2 = "https://api.aleph.im";
@@ -1152,6 +1167,13 @@ function normalizeExecution2(item, crnUrl) {
1152
1167
  }
1153
1168
 
1154
1169
  // ../core/src/deployment-inspection.ts
1170
+ var TRANSIENT_MESSAGE_LOOKUP_STATUSES = /* @__PURE__ */ new Set([408, 425, 429, 500, 502, 503, 504]);
1171
+ function isTransientMessageLookupError(error) {
1172
+ const message = error instanceof Error ? error.message : String(error);
1173
+ const match = message.match(/Message lookup failed: (\d{3})/);
1174
+ if (!match) return false;
1175
+ return TRANSIENT_MESSAGE_LOOKUP_STATUSES.has(Number(match[1]));
1176
+ }
1155
1177
  async function fetchMessageEnvelope2(itemHash, options) {
1156
1178
  const response = await options.fetch(`${options.apiHost ?? DEFAULT_ALEPH_API_HOST2}/api/v0/messages/${itemHash}`, {
1157
1179
  cache: "no-cache"
@@ -1208,15 +1230,32 @@ async function waitForDeploymentResult2(itemHash, options) {
1208
1230
  const attempts = Math.max(1, Number(options.attempts ?? 15));
1209
1231
  const delayMs = Math.max(0, Number(options.delayMs ?? 2e3));
1210
1232
  const sleep2 = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
1211
- let lastResult = await inspectDeploymentResult2(itemHash, options);
1212
- options.onAttempt?.(lastResult, 1, attempts);
1213
- for (let attempt = 1; attempt < attempts; attempt += 1) {
1233
+ let lastResult = {
1234
+ status: "unknown",
1235
+ errorCode: null,
1236
+ details: null,
1237
+ rejectionReason: null,
1238
+ references: []
1239
+ };
1240
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
1241
+ try {
1242
+ lastResult = await inspectDeploymentResult2(itemHash, options);
1243
+ } catch (error) {
1244
+ if (!isTransientMessageLookupError(error)) {
1245
+ throw error;
1246
+ }
1247
+ lastResult = {
1248
+ ...lastResult,
1249
+ rejectionReason: error instanceof Error ? error.message : String(error)
1250
+ };
1251
+ }
1252
+ options.onAttempt?.(lastResult, attempt, attempts);
1214
1253
  if (lastResult.status === "processed" || lastResult.status === "rejected") {
1215
1254
  return lastResult;
1216
1255
  }
1217
- await sleep2(delayMs);
1218
- lastResult = await inspectDeploymentResult2(itemHash, options);
1219
- options.onAttempt?.(lastResult, attempt + 1, attempts);
1256
+ if (attempt < attempts) {
1257
+ await sleep2(delayMs);
1258
+ }
1220
1259
  }
1221
1260
  return lastResult;
1222
1261
  }
@@ -2273,6 +2312,45 @@ async function configureOrbitdbRelaySetup2(args) {
2273
2312
  clearTimeout(timeout);
2274
2313
  }
2275
2314
  }
2315
+ async function configureUcanStore(args) {
2316
+ const controller = new AbortController();
2317
+ const timeout = setTimeout(
2318
+ () => controller.abort(),
2319
+ Number(args.timeoutMs ?? 18e4)
2320
+ );
2321
+ const payload = {
2322
+ public_ipv4: args.hostIpv4,
2323
+ public_ipv6: args.publicIpv6 ?? void 0,
2324
+ proxy_url: args.proxyUrl ?? void 0,
2325
+ webauthn_origin: args.webauthnOrigin ?? void 0,
2326
+ webauthn_origin_fallbacks: args.webauthnOriginFallbacks ?? void 0,
2327
+ admin_did: args.adminDid ?? void 0,
2328
+ bootstrap_package: args.bootstrapPackage ?? void 0,
2329
+ no_start: args.noStart === true ? true : void 0
2330
+ };
2331
+ try {
2332
+ const response = await args.fetch(
2333
+ `http://${args.hostIpv4}:${args.setupPort}/configure`,
2334
+ {
2335
+ method: "POST",
2336
+ headers: {
2337
+ "content-type": "application/json"
2338
+ },
2339
+ body: JSON.stringify(payload),
2340
+ signal: controller.signal
2341
+ }
2342
+ );
2343
+ const responsePayload = await response.json().catch(() => null);
2344
+ if (!response.ok) {
2345
+ throw new Error(
2346
+ `Service setup request failed: ${response.status} ${typeof responsePayload === "string" ? responsePayload : JSON.stringify(responsePayload ?? {})}`
2347
+ );
2348
+ }
2349
+ return responsePayload;
2350
+ } finally {
2351
+ clearTimeout(timeout);
2352
+ }
2353
+ }
2276
2354
  async function fetchUcGoPeerMetadata(args) {
2277
2355
  const attempts = Math.max(1, Number(args.attempts ?? 60));
2278
2356
  const delayMs = Math.max(0, Number(args.delayMs ?? 3e3));
@@ -2340,7 +2418,7 @@ async function fetchUcGoPeerMetadata(args) {
2340
2418
  }
2341
2419
  }
2342
2420
  throw new Error(
2343
- `Relay metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2421
+ `Guest metadata did not become ready after ${attempts} attempts: ${lastError ? lastError : typeof lastPayload === "string" ? lastPayload : JSON.stringify(lastPayload ?? {})}`
2344
2422
  );
2345
2423
  }
2346
2424
 
@@ -3272,7 +3350,130 @@ function createDeploymentProgressEmitter() {
3272
3350
  };
3273
3351
  }
3274
3352
 
3353
+ // ../shared-types/src/ucan-store-bootstrap.ts
3354
+ function isRecord(value) {
3355
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3356
+ }
3357
+ function normalizeNonEmptyString(value) {
3358
+ if (typeof value !== "string") return null;
3359
+ const normalized = value.trim();
3360
+ return normalized ? normalized : null;
3361
+ }
3362
+ function normalizeDid(value) {
3363
+ const did = normalizeNonEmptyString(value);
3364
+ return did && did.startsWith("did:") ? did : null;
3365
+ }
3366
+ function normalizeOrigin(value) {
3367
+ const raw = normalizeNonEmptyString(value);
3368
+ if (!raw) return null;
3369
+ try {
3370
+ const parsed = new URL(raw);
3371
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:" || !parsed.hostname || parsed.pathname && parsed.pathname !== "/" || parsed.search || parsed.hash) {
3372
+ return null;
3373
+ }
3374
+ return parsed.origin;
3375
+ } catch {
3376
+ return null;
3377
+ }
3378
+ }
3379
+ function normalizeExpiration(value) {
3380
+ if (value == null) return void 0;
3381
+ if (value === null) return null;
3382
+ if (!Number.isInteger(value) || Number(value) < 0) return void 0;
3383
+ return Number(value);
3384
+ }
3385
+ function validateUcanStoreBootstrapPackage(value) {
3386
+ if (!isRecord(value)) {
3387
+ return {
3388
+ valid: false,
3389
+ errors: ["Bootstrap package must be a JSON object."],
3390
+ bootstrapPackage: null
3391
+ };
3392
+ }
3393
+ const errors = [];
3394
+ const operatorAddress = normalizeNonEmptyString(value.operatorAddress);
3395
+ if (!operatorAddress || !/^0x[a-fA-F0-9]{40}$/.test(operatorAddress)) {
3396
+ errors.push("operatorAddress must be a 0x-prefixed 20-byte Ethereum address.");
3397
+ }
3398
+ const adminDid = normalizeDid(value.adminDid);
3399
+ if (!adminDid) {
3400
+ errors.push("adminDid must be a non-empty DID string.");
3401
+ }
3402
+ const serviceDidRaw = value.serviceDid;
3403
+ const serviceDid = serviceDidRaw == null || serviceDidRaw === "" ? null : normalizeDid(serviceDidRaw);
3404
+ if (serviceDidRaw != null && serviceDidRaw !== "" && !serviceDid) {
3405
+ errors.push("serviceDid must be empty or a non-empty DID string.");
3406
+ }
3407
+ const spaceDid = normalizeDid(value.spaceDid);
3408
+ if (!spaceDid) {
3409
+ errors.push("spaceDid must be a non-empty DID string.");
3410
+ }
3411
+ const rootDelegationProof = normalizeNonEmptyString(value.rootDelegationProof);
3412
+ if (!rootDelegationProof) {
3413
+ errors.push("rootDelegationProof must be a non-empty proof string.");
3414
+ }
3415
+ const allowedCapabilities = Array.isArray(value.allowedCapabilities) ? value.allowedCapabilities.map((entry) => normalizeNonEmptyString(entry)).filter((entry) => Boolean(entry)) : [];
3416
+ if (allowedCapabilities.length === 0) {
3417
+ errors.push("allowedCapabilities must contain at least one capability string.");
3418
+ }
3419
+ const defaultUserDelegationExpiration = normalizeExpiration(
3420
+ value.defaultUserDelegationExpiration
3421
+ );
3422
+ if (value.defaultUserDelegationExpiration != null && defaultUserDelegationExpiration === void 0) {
3423
+ errors.push(
3424
+ "defaultUserDelegationExpiration must be null or a non-negative integer number of seconds."
3425
+ );
3426
+ }
3427
+ const maxUserDelegationExpiration = normalizeExpiration(
3428
+ value.maxUserDelegationExpiration
3429
+ );
3430
+ if (value.maxUserDelegationExpiration != null && maxUserDelegationExpiration === void 0) {
3431
+ errors.push(
3432
+ "maxUserDelegationExpiration must be null or a non-negative integer number of seconds."
3433
+ );
3434
+ }
3435
+ if (typeof defaultUserDelegationExpiration === "number" && typeof maxUserDelegationExpiration === "number" && defaultUserDelegationExpiration > maxUserDelegationExpiration) {
3436
+ errors.push(
3437
+ "defaultUserDelegationExpiration cannot exceed maxUserDelegationExpiration."
3438
+ );
3439
+ }
3440
+ const pwaOrigin = normalizeOrigin(value.pwaOrigin);
3441
+ if (!pwaOrigin) {
3442
+ errors.push("pwaOrigin must be an http(s) origin without path, query, or hash.");
3443
+ }
3444
+ const serviceOrigin = normalizeOrigin(value.serviceOrigin);
3445
+ if (!serviceOrigin) {
3446
+ errors.push("serviceOrigin must be an http(s) origin without path, query, or hash.");
3447
+ }
3448
+ if (errors.length > 0) {
3449
+ return {
3450
+ valid: false,
3451
+ errors,
3452
+ bootstrapPackage: null
3453
+ };
3454
+ }
3455
+ return {
3456
+ valid: true,
3457
+ errors: [],
3458
+ bootstrapPackage: {
3459
+ operatorAddress,
3460
+ adminDid,
3461
+ serviceDid,
3462
+ spaceDid,
3463
+ rootDelegationProof,
3464
+ allowedCapabilities,
3465
+ defaultUserDelegationExpiration,
3466
+ maxUserDelegationExpiration,
3467
+ pwaOrigin,
3468
+ serviceOrigin
3469
+ }
3470
+ };
3471
+ }
3472
+
3275
3473
  // src/shared/controller.ts
3474
+ function deploymentNoun(profile) {
3475
+ return profile === "ucan-store" ? "service" : "relay";
3476
+ }
3276
3477
  function asJsonFetch(input, init) {
3277
3478
  return fetch(input, init).then(async (response) => ({
3278
3479
  ok: response.ok,
@@ -3280,11 +3481,122 @@ function asJsonFetch(input, init) {
3280
3481
  json: async () => await response.json()
3281
3482
  }));
3282
3483
  }
3283
- function isConfirmedRelaySetupResult(value) {
3484
+ function isConfirmedGuestSetupResult(value) {
3284
3485
  return Boolean(
3285
3486
  value && typeof value === "object" && "status" in value && value.status === "configured"
3286
3487
  );
3287
3488
  }
3489
+ function defaultUcanStoreBootstrapInput(props) {
3490
+ return {
3491
+ adminDid: props.ucanStoreBootstrap?.adminDid ?? "",
3492
+ serviceDid: props.ucanStoreBootstrap?.serviceDid ?? DEFAULT_UCAN_STORE_SERVICE_DID,
3493
+ spaceDid: props.ucanStoreBootstrap?.spaceDid ?? "",
3494
+ rootDelegationProof: props.ucanStoreBootstrap?.rootDelegationProof ?? "",
3495
+ allowedCapabilities: props.ucanStoreBootstrap?.allowedCapabilities ?? DEFAULT_UCAN_STORE_ALLOWED_CAPABILITIES.join("\n"),
3496
+ defaultUserDelegationExpiration: props.ucanStoreBootstrap?.defaultUserDelegationExpiration ?? DEFAULT_UCAN_STORE_USER_DELEGATION_EXPIRATION_SECONDS,
3497
+ maxUserDelegationExpiration: props.ucanStoreBootstrap?.maxUserDelegationExpiration ?? DEFAULT_UCAN_STORE_MAX_DELEGATION_EXPIRATION_SECONDS,
3498
+ pwaOrigin: props.ucanStoreBootstrap?.pwaOrigin ?? DEFAULT_UCAN_STORE_PWA_ORIGIN,
3499
+ serviceOrigin: props.ucanStoreBootstrap?.serviceOrigin ?? DEFAULT_UCAN_STORE_SERVICE_ORIGIN
3500
+ };
3501
+ }
3502
+ function currentPageOrigin() {
3503
+ try {
3504
+ const origin = globalThis.location?.origin?.trim();
3505
+ return origin ? origin : null;
3506
+ } catch {
3507
+ return null;
3508
+ }
3509
+ }
3510
+ function parseOptionalExpiration(value) {
3511
+ const normalized = value.trim();
3512
+ if (!normalized) return void 0;
3513
+ const parsed = Number.parseInt(normalized, 10);
3514
+ if (!Number.isInteger(parsed) || parsed < 0) {
3515
+ return null;
3516
+ }
3517
+ return parsed;
3518
+ }
3519
+ function parseAllowedCapabilities(value) {
3520
+ return value.split(/[\n,]/).map((entry) => entry.trim()).filter(Boolean);
3521
+ }
3522
+ function draftUcanStoreBootstrapErrors(args) {
3523
+ const errors = [];
3524
+ const { input } = args;
3525
+ if (!args.operatorAddress) {
3526
+ errors.push("Connect MetaMask before configuring the ucan-store bootstrap package.");
3527
+ }
3528
+ if (!input.adminDid.trim().startsWith("did:")) {
3529
+ errors.push("Admin DID must be a non-empty DID string.");
3530
+ }
3531
+ if (input.serviceDid.trim() && !input.serviceDid.trim().startsWith("did:")) {
3532
+ errors.push("Service DID must be empty or a non-empty DID string.");
3533
+ }
3534
+ if (!input.spaceDid.trim().startsWith("did:")) {
3535
+ errors.push("Space DID must be a non-empty DID string.");
3536
+ }
3537
+ if (!input.rootDelegationProof.trim()) {
3538
+ errors.push("Root delegation proof is required.");
3539
+ }
3540
+ if (parseAllowedCapabilities(input.allowedCapabilities).length === 0) {
3541
+ errors.push("Add at least one allowed capability.");
3542
+ }
3543
+ const defaultExpiration = parseOptionalExpiration(
3544
+ input.defaultUserDelegationExpiration
3545
+ );
3546
+ if (defaultExpiration === null) {
3547
+ errors.push(
3548
+ "Default user delegation expiration must be a non-negative integer number of seconds."
3549
+ );
3550
+ }
3551
+ const maxExpiration = parseOptionalExpiration(
3552
+ input.maxUserDelegationExpiration
3553
+ );
3554
+ if (maxExpiration === null) {
3555
+ errors.push(
3556
+ "Max user delegation expiration must be a non-negative integer number of seconds."
3557
+ );
3558
+ }
3559
+ if (typeof defaultExpiration === "number" && typeof maxExpiration === "number" && defaultExpiration > maxExpiration) {
3560
+ errors.push(
3561
+ "Default user delegation expiration cannot exceed the max user delegation expiration."
3562
+ );
3563
+ }
3564
+ const pwaOrigin = input.pwaOrigin.trim() || currentPageOrigin() || "";
3565
+ if (!pwaOrigin) {
3566
+ errors.push("PWA origin is required for the ucan-store bootstrap package.");
3567
+ }
3568
+ return errors;
3569
+ }
3570
+ function buildUcanStoreBootstrapPackage(args) {
3571
+ const candidate = {
3572
+ operatorAddress: args.operatorAddress,
3573
+ adminDid: args.input.adminDid.trim(),
3574
+ serviceDid: args.input.serviceDid.trim() || null,
3575
+ spaceDid: args.input.spaceDid.trim(),
3576
+ rootDelegationProof: args.input.rootDelegationProof.trim(),
3577
+ allowedCapabilities: parseAllowedCapabilities(args.input.allowedCapabilities),
3578
+ defaultUserDelegationExpiration: parseOptionalExpiration(
3579
+ args.input.defaultUserDelegationExpiration
3580
+ ),
3581
+ maxUserDelegationExpiration: parseOptionalExpiration(
3582
+ args.input.maxUserDelegationExpiration
3583
+ ),
3584
+ pwaOrigin: args.input.pwaOrigin.trim() || args.pwaOriginFallback?.trim() || currentPageOrigin() || "",
3585
+ serviceOrigin: args.input.serviceOrigin.trim() || args.serviceOriginFallback?.trim() || ""
3586
+ };
3587
+ if (candidate.defaultUserDelegationExpiration === void 0) {
3588
+ delete candidate.defaultUserDelegationExpiration;
3589
+ }
3590
+ if (candidate.maxUserDelegationExpiration === void 0) {
3591
+ delete candidate.maxUserDelegationExpiration;
3592
+ }
3593
+ if (candidate.serviceDid === null) {
3594
+ delete candidate.serviceDid;
3595
+ }
3596
+ return validateUcanStoreBootstrapPackage(
3597
+ candidate
3598
+ );
3599
+ }
3288
3600
  function defaultState(props = {}) {
3289
3601
  return {
3290
3602
  ready: false,
@@ -3299,6 +3611,7 @@ function defaultState(props = {}) {
3299
3611
  manifestJson: props.manifestJson ?? "",
3300
3612
  sshPublicKey: props.sshPublicKey ?? "",
3301
3613
  instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
3614
+ ucanStoreBootstrap: defaultUcanStoreBootstrapInput(props),
3302
3615
  tierId: DEFAULT_TIER_ID,
3303
3616
  showAdvanced: Boolean(props.manifestJson?.trim() || props.sshPublicKey?.trim()),
3304
3617
  showInstances: props.showInstances ?? true,
@@ -3433,13 +3746,33 @@ async function resolveManifest(args) {
3433
3746
  if (pasted) return pasted;
3434
3747
  return loadRootfsManifest(args.manifestUrl);
3435
3748
  }
3436
- function relayProfileForManifest(manifest) {
3749
+ function deploymentProfileForManifest(manifest) {
3437
3750
  if (manifest?.profile === "uc-go-peer") return "uc-go-peer";
3438
3751
  if (manifest?.profile === "orbitdb-relay") {
3439
3752
  return manifest.profile;
3440
3753
  }
3754
+ if (manifest?.profile === "ucan-store") return "ucan-store";
3441
3755
  return null;
3442
3756
  }
3757
+ function bootstrapRelayProfileForManifest(manifest) {
3758
+ const profile = deploymentProfileForManifest(manifest);
3759
+ if (profile === "uc-go-peer" || profile === "orbitdb-relay") {
3760
+ return profile;
3761
+ }
3762
+ return null;
3763
+ }
3764
+ function supportsBootstrapRegistrationsForManifest(manifest) {
3765
+ return bootstrapRelayProfileForManifest(manifest) != null;
3766
+ }
3767
+ function deploymentReadyLabel(profile) {
3768
+ return profile === "ucan-store" ? "Service ready" : "Relay ready";
3769
+ }
3770
+ function deploymentReadyDetail(profile, fallback) {
3771
+ if (profile === "ucan-store") {
3772
+ return "The upload service runtime is available and the deployment finished successfully.";
3773
+ }
3774
+ return fallback;
3775
+ }
3443
3776
  function registrationIdInstanceItemHash(registrationId) {
3444
3777
  const normalized = String(registrationId ?? "").trim();
3445
3778
  if (!normalized) return null;
@@ -3688,19 +4021,148 @@ var SponsorRelayController = class {
3688
4021
  this.progressEmitter.emit(nextEvent);
3689
4022
  }
3690
4023
  async configureRelayBootstrapRegistration(args) {
3691
- const profile = relayProfileForManifest(this.state.manifest);
3692
- if (!profile) return;
3693
- if (!this.state.wallet.address) {
4024
+ const deploymentProfile = deploymentProfileForManifest(this.state.manifest);
4025
+ const profile = bootstrapRelayProfileForManifest(this.state.manifest);
4026
+ if (!profile && deploymentProfile !== "ucan-store") return;
4027
+ if (profile && !this.state.wallet.address) {
3694
4028
  throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3695
4029
  }
3696
- if (!args.runtime.hostIpv4) {
3697
- throw new Error("Relay runtime did not expose a host IPv4 address.");
4030
+ const runtimeHostIpv4 = args.runtime.hostIpv4;
4031
+ if (!runtimeHostIpv4) {
4032
+ throw new Error(
4033
+ `${deploymentProfile === "ucan-store" ? "Service" : "Relay"} runtime did not expose a host IPv4 address.`
4034
+ );
3698
4035
  }
3699
4036
  let runtime = args.runtime;
3700
4037
  let mappedPorts2 = runtime.mappedPorts ?? {};
3701
4038
  const setupPort = mappedPorts2["80"]?.host ?? null;
3702
4039
  if (!setupPort) {
3703
- throw new Error("Relay runtime did not expose the temporary setup endpoint.");
4040
+ throw new Error(
4041
+ `${deploymentProfile === "ucan-store" ? "Service" : "Relay"} runtime did not expose the temporary setup endpoint.`
4042
+ );
4043
+ }
4044
+ if (deploymentProfile === "ucan-store") {
4045
+ const serviceHostIpv4 = runtimeHostIpv4;
4046
+ const serviceProxyUrl = runtime.proxyUrl;
4047
+ if (!serviceProxyUrl) {
4048
+ throw new Error("Service runtime did not expose a proxy URL.");
4049
+ }
4050
+ const walletAddress2 = this.state.wallet.address;
4051
+ if (!walletAddress2) {
4052
+ throw new Error("A connected wallet is required to configure the ucan-store bootstrap package.");
4053
+ }
4054
+ const bootstrapValidation = buildUcanStoreBootstrapPackage({
4055
+ input: this.state.ucanStoreBootstrap,
4056
+ operatorAddress: walletAddress2,
4057
+ serviceOriginFallback: serviceProxyUrl,
4058
+ pwaOriginFallback: currentPageOrigin()
4059
+ });
4060
+ if (!bootstrapValidation.valid || !bootstrapValidation.bootstrapPackage) {
4061
+ throw new Error(
4062
+ bootstrapValidation.errors[0] ?? "The ucan-store bootstrap package is invalid."
4063
+ );
4064
+ }
4065
+ this.emitProgress({
4066
+ stage: "deployment-confirmed",
4067
+ label: "Configuring service",
4068
+ progress: 93,
4069
+ status: "info",
4070
+ itemHash: args.itemHash,
4071
+ detail: "Waiting for the guest setup endpoint before applying the upload service configuration."
4072
+ });
4073
+ const setupHealth = await waitForSetupEndpoint({
4074
+ hostIpv4: serviceHostIpv4,
4075
+ setupPort,
4076
+ fetch: (url, init) => fetch(url, init),
4077
+ attempts: 15,
4078
+ delayMs: 4e3,
4079
+ httpTimeoutMs: 1e4,
4080
+ onAttempt: (result, attempt, attempts) => {
4081
+ this.emitProgress({
4082
+ stage: "deployment-confirmed",
4083
+ label: "Waiting for guest setup",
4084
+ progress: 93,
4085
+ status: result.ok ? "success" : "info",
4086
+ itemHash: args.itemHash,
4087
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${serviceHostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${serviceHostIpv4}:${setupPort}/health.`
4088
+ });
4089
+ }
4090
+ });
4091
+ if (!setupHealth.ok) {
4092
+ throw new Error(
4093
+ `Service setup endpoint did not become reachable at http://${serviceHostIpv4}:${setupPort}/health.`
4094
+ );
4095
+ }
4096
+ this.emitProgress({
4097
+ stage: "deployment-confirmed",
4098
+ label: "Applying service config",
4099
+ progress: 94,
4100
+ status: "info",
4101
+ itemHash: args.itemHash,
4102
+ detail: "Publishing the public HTTPS origin into the guest upload service configuration."
4103
+ });
4104
+ const configureResult = await configureUcanStore({
4105
+ hostIpv4: serviceHostIpv4,
4106
+ publicIpv6: runtime.ipv6,
4107
+ setupPort,
4108
+ proxyUrl: serviceProxyUrl,
4109
+ webauthnOrigin: serviceProxyUrl,
4110
+ adminDid: bootstrapValidation.bootstrapPackage.adminDid,
4111
+ bootstrapPackage: bootstrapValidation.bootstrapPackage,
4112
+ fetch: (url, init) => fetch(url, init),
4113
+ timeoutMs: 18e4
4114
+ });
4115
+ if (!isConfirmedGuestSetupResult(configureResult)) {
4116
+ throw new Error(
4117
+ `Service guest configuration could not be confirmed at http://${serviceHostIpv4}:${setupPort}/configure.`
4118
+ );
4119
+ }
4120
+ this.emitProgress({
4121
+ stage: "deployment-confirmed",
4122
+ label: "Waiting for service metadata",
4123
+ progress: 95,
4124
+ status: "info",
4125
+ itemHash: args.itemHash,
4126
+ detail: "Waiting for the upload service metadata endpoint to report the public service DID and PWA environment."
4127
+ });
4128
+ const serviceMetadata = await fetchUcGoPeerMetadata({
4129
+ hostIpv4: serviceHostIpv4,
4130
+ setupPort,
4131
+ fetch: (url, init) => fetch(url, init),
4132
+ attempts: 80,
4133
+ delayMs: 3e3,
4134
+ timeoutMs: 24e4,
4135
+ isReady: ({ payload, ok }) => {
4136
+ if (!ok || !payload || typeof payload !== "object") return false;
4137
+ const metadata2 = payload.metadata && typeof payload.metadata === "object" ? payload.metadata : null;
4138
+ const pwaEnv = metadata2?.pwa_env && typeof metadata2.pwa_env === "object" ? metadata2.pwa_env : null;
4139
+ return Boolean(
4140
+ 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
4141
+ );
4142
+ },
4143
+ onAttempt: ({ ready, attempt, attempts, requestUrl }) => {
4144
+ this.emitProgress({
4145
+ stage: "deployment-confirmed",
4146
+ label: ready ? "Service metadata ready" : "Waiting for service metadata",
4147
+ progress: 95,
4148
+ status: ready ? "success" : "info",
4149
+ itemHash: args.itemHash,
4150
+ detail: ready ? `Service metadata ${attempt}/${attempts}: public upload service wiring is ready.` : `Service metadata ${attempt}/${attempts}: waiting for ${requestUrl}.`
4151
+ });
4152
+ }
4153
+ });
4154
+ const metadata = serviceMetadata && typeof serviceMetadata === "object" ? serviceMetadata.metadata ?? null : null;
4155
+ if (!metadata || typeof metadata !== "object" || typeof metadata.upload_service_did !== "string") {
4156
+ throw new Error(
4157
+ "Service metadata did not include the public upload service DID."
4158
+ );
4159
+ }
4160
+ return;
4161
+ }
4162
+ if (!profile) return;
4163
+ const walletAddress = this.state.wallet.address;
4164
+ if (!walletAddress) {
4165
+ throw new Error("A connected wallet is required to register relay bootstrap addresses.");
3704
4166
  }
3705
4167
  let bootstrapSignalRelayMetadata = null;
3706
4168
  if (profile === "orbitdb-relay") {
@@ -3713,7 +4175,7 @@ var SponsorRelayController = class {
3713
4175
  detail: "Waiting for the guest setup endpoint before collecting relay metadata."
3714
4176
  });
3715
4177
  const setupHealth = await waitForSetupEndpoint({
3716
- hostIpv4: args.runtime.hostIpv4,
4178
+ hostIpv4: runtimeHostIpv4,
3717
4179
  setupPort,
3718
4180
  fetch: (url, init) => fetch(url, init),
3719
4181
  attempts: 15,
@@ -3726,13 +4188,13 @@ var SponsorRelayController = class {
3726
4188
  progress: 93,
3727
4189
  status: result.ok ? "success" : "info",
3728
4190
  itemHash: args.itemHash,
3729
- 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.`
4191
+ detail: result.ok ? `Setup endpoint ${attempt}/${attempts}: ready at http://${runtimeHostIpv4}:${setupPort}/health.` : `Setup endpoint ${attempt}/${attempts}: waiting for http://${runtimeHostIpv4}:${setupPort}/health.`
3730
4192
  });
3731
4193
  }
3732
4194
  });
3733
4195
  if (!setupHealth.ok) {
3734
4196
  throw new Error(
3735
- `Relay setup endpoint did not become reachable at http://${args.runtime.hostIpv4}:${setupPort}/health.`
4197
+ `Relay setup endpoint did not become reachable at http://${runtimeHostIpv4}:${setupPort}/health.`
3736
4198
  );
3737
4199
  }
3738
4200
  this.emitProgress({
@@ -3749,7 +4211,7 @@ var SponsorRelayController = class {
3749
4211
  throw new Error("OrbitDB relay runtime is missing required mapped TCP/WS ports.");
3750
4212
  }
3751
4213
  const configureResult = await configureOrbitdbRelaySetup2({
3752
- hostIpv4: args.runtime.hostIpv4,
4214
+ hostIpv4: runtimeHostIpv4,
3753
4215
  publicIpv6: args.runtime.ipv6,
3754
4216
  setupPort,
3755
4217
  tcpPort,
@@ -3762,7 +4224,7 @@ var SponsorRelayController = class {
3762
4224
  fetch: (url, init) => fetch(url, init),
3763
4225
  timeoutMs: 18e4
3764
4226
  });
3765
- if (!isConfirmedRelaySetupResult(configureResult)) {
4227
+ if (!isConfirmedGuestSetupResult(configureResult)) {
3766
4228
  throw new Error(
3767
4229
  `Relay guest configuration could not be confirmed at http://${args.runtime.hostIpv4}:${setupPort}/configure.`
3768
4230
  );
@@ -3820,7 +4282,7 @@ var SponsorRelayController = class {
3820
4282
  "Relay runtime lost its host IPv4 address while waiting for HTTPS activation."
3821
4283
  );
3822
4284
  }
3823
- const runtimeHostIpv4 = runtime.hostIpv4;
4285
+ const runtimeHostIpv42 = runtime.hostIpv4;
3824
4286
  const activeProxyUrl = runtime.webAccess?.active === true ? runtime.proxyUrl ?? null : null;
3825
4287
  this.emitProgress({
3826
4288
  stage: "deployment-confirmed",
@@ -3833,13 +4295,13 @@ var SponsorRelayController = class {
3833
4295
  const record = {
3834
4296
  deploymentToken: args.deploymentToken,
3835
4297
  profile,
3836
- ownerAddress: this.state.wallet.address,
4298
+ ownerAddress: walletAddress,
3837
4299
  instanceItemHash: args.itemHash,
3838
4300
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3839
4301
  expiresAt: new Date(Date.now() + 30 * 6e4).toISOString(),
3840
4302
  status: "pending",
3841
4303
  runtime: {
3842
- publicIpv4: runtimeHostIpv4,
4304
+ publicIpv4: runtimeHostIpv42,
3843
4305
  publicIpv6: runtime.ipv6 ?? null,
3844
4306
  proxyUrl: activeProxyUrl,
3845
4307
  mappedPorts: mappedPorts2
@@ -3853,7 +4315,7 @@ var SponsorRelayController = class {
3853
4315
  }
3854
4316
  };
3855
4317
  const bootstrapConfigPublication = await publishVmBootstrapConfig({
3856
- sender: this.state.wallet.address,
4318
+ sender: walletAddress,
3857
4319
  record,
3858
4320
  signer: personalSign,
3859
4321
  hasher: sha256Hex,
@@ -3880,7 +4342,7 @@ var SponsorRelayController = class {
3880
4342
  });
3881
4343
  const bootstrapConfigSignal = await waitForVmBootstrapConfigSignal({
3882
4344
  deploymentToken: args.deploymentToken,
3883
- ownerAddress: this.state.wallet.address,
4345
+ ownerAddress: walletAddress,
3884
4346
  instanceItemHash: args.itemHash,
3885
4347
  fetch: asJsonFetch,
3886
4348
  apiHost: this.client.apiHost,
@@ -4052,7 +4514,7 @@ var SponsorRelayController = class {
4052
4514
  detail: "Guest bootstrap registration was delayed. Publishing the relay bootstrap record from the browser."
4053
4515
  });
4054
4516
  const fallbackPublication = await publishRelayBootstrapRegistration({
4055
- sender: this.state.wallet.address,
4517
+ sender: walletAddress,
4056
4518
  signer: personalSign,
4057
4519
  hasher: sha256Hex,
4058
4520
  fetch: asJsonFetch,
@@ -4072,7 +4534,7 @@ var SponsorRelayController = class {
4072
4534
  fallbackPublication
4073
4535
  });
4074
4536
  const fallbackVisibleRegistration = await waitForRelayBootstrapRegistration({
4075
- sender: this.state.wallet.address,
4537
+ sender: walletAddress,
4076
4538
  registrationId,
4077
4539
  peerId,
4078
4540
  fetch: asJsonFetch,
@@ -4105,7 +4567,7 @@ var SponsorRelayController = class {
4105
4567
  if (profile === "uc-go-peer" && args.deploymentToken) {
4106
4568
  let cleanupAggregateItemHash = null;
4107
4569
  await deleteVmBootstrapConfig({
4108
- sender: this.state.wallet.address,
4570
+ sender: walletAddress,
4109
4571
  deploymentToken: args.deploymentToken,
4110
4572
  signer: personalSign,
4111
4573
  hasher: sha256Hex,
@@ -4271,17 +4733,35 @@ var SponsorRelayController = class {
4271
4733
  });
4272
4734
  return;
4273
4735
  }
4736
+ const currentProfile = deploymentProfileForManifest(this.state.manifest);
4274
4737
  const hasConfirmedBootstrapRegistration = this.state.bootstrapRegistrations.some(
4275
4738
  (entry) => entry.instanceItemHash === itemHash && entry.confirmed
4276
4739
  );
4740
+ if (currentProfile === "ucan-store") {
4741
+ this.emitProgress({
4742
+ stage: "completed",
4743
+ label: deploymentReadyLabel(currentProfile),
4744
+ progress: 100,
4745
+ status: "success",
4746
+ itemHash,
4747
+ detail: deploymentReadyDetail(
4748
+ currentProfile,
4749
+ "Runtime networking is available and the deployment finished successfully."
4750
+ )
4751
+ });
4752
+ return;
4753
+ }
4277
4754
  if (hasConfirmedBootstrapRegistration) {
4278
4755
  this.emitProgress({
4279
4756
  stage: "completed",
4280
- label: "Relay ready",
4757
+ label: deploymentReadyLabel(currentProfile),
4281
4758
  progress: 100,
4282
4759
  status: "success",
4283
4760
  itemHash,
4284
- detail: "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4761
+ detail: deploymentReadyDetail(
4762
+ currentProfile,
4763
+ "Runtime networking is available and the relay bootstrap registration is confirmed on Aleph."
4764
+ )
4285
4765
  });
4286
4766
  return;
4287
4767
  }
@@ -4291,7 +4771,7 @@ var SponsorRelayController = class {
4291
4771
  progress: 92,
4292
4772
  status: "info",
4293
4773
  itemHash,
4294
- detail: "Deployment processed and runtime networking is available. Finishing relay setup and bootstrap verification."
4774
+ detail: `Deployment processed and runtime networking is available. Finishing ${deploymentNoun(currentProfile)} setup and verification.`
4295
4775
  });
4296
4776
  }
4297
4777
  canSkipRuntimeRefresh(instance) {
@@ -4404,6 +4884,15 @@ var SponsorRelayController = class {
4404
4884
  setInstanceName(instanceName) {
4405
4885
  this.patch({ instanceName });
4406
4886
  }
4887
+ setUcanStoreBootstrapField(field, value) {
4888
+ this.patch({
4889
+ ucanStoreBootstrap: {
4890
+ ...this.state.ucanStoreBootstrap,
4891
+ [field]: value
4892
+ },
4893
+ showAdvanced: this.state.showAdvanced || Boolean(value.trim())
4894
+ });
4895
+ }
4407
4896
  setTierId(tierId) {
4408
4897
  this.patch({ tierId });
4409
4898
  this.recomputePricingSummary();
@@ -4489,6 +4978,7 @@ var SponsorRelayController = class {
4489
4978
  }
4490
4979
  }
4491
4980
  async refresh() {
4981
+ const currentProfile = deploymentProfileForManifest(this.state.manifest);
4492
4982
  this.trace("refresh:start", {
4493
4983
  wallet: this.state.wallet.address,
4494
4984
  manifestUrl: this.state.manifestUrl,
@@ -4497,7 +4987,7 @@ var SponsorRelayController = class {
4497
4987
  this.patch({
4498
4988
  busy: { refreshing: true },
4499
4989
  errorText: null,
4500
- statusText: "Refreshing relay sponsor data"
4990
+ statusText: currentProfile === "ucan-store" ? "Refreshing service deployment data" : "Refreshing relay deployment data"
4501
4991
  });
4502
4992
  try {
4503
4993
  const [pricingSummary, crns] = await Promise.all([
@@ -4509,6 +4999,8 @@ var SponsorRelayController = class {
4509
4999
  manifestJson: this.state.manifestJson
4510
5000
  }).catch((error) => manifestLoadErrorState(error));
4511
5001
  const manifest = manifestState.manifest;
5002
+ const resolvedProfile = deploymentProfileForManifest(manifest);
5003
+ const bootstrapEnabled = supportsBootstrapRegistrationsForManifest(manifest);
4512
5004
  let balance = this.state.balance;
4513
5005
  let instances = [];
4514
5006
  let bootstrapRegistrations = [];
@@ -4517,10 +5009,10 @@ var SponsorRelayController = class {
4517
5009
  const [nextBalance, rawInstances, rawBootstrapPosts] = await Promise.all([
4518
5010
  this.client.fetchBalance(this.state.wallet.address),
4519
5011
  this.state.showInstances ? this.client.fetchInstances(this.state.wallet.address) : Promise.resolve([]),
4520
- fetchAlephBootstrapPosts({
5012
+ bootstrapEnabled ? fetchAlephBootstrapPosts({
4521
5013
  apiHost: this.client.apiHost,
4522
5014
  fetch
4523
- }).catch(() => [])
5015
+ }).catch(() => []) : Promise.resolve([])
4524
5016
  ]);
4525
5017
  balance = nextBalance;
4526
5018
  instances = await Promise.all(
@@ -4650,7 +5142,7 @@ var SponsorRelayController = class {
4650
5142
  bootstrapRegistrations,
4651
5143
  orphanBootstrapRegistrations,
4652
5144
  busy: { refreshing: false },
4653
- statusText: "Relay sponsor data ready"
5145
+ statusText: resolvedProfile === "ucan-store" ? "Service deployment data ready" : "Relay deployment data ready"
4654
5146
  });
4655
5147
  this.trace("refresh:success", {
4656
5148
  manifestValid: manifestState.valid,
@@ -4687,6 +5179,16 @@ var SponsorRelayController = class {
4687
5179
  });
4688
5180
  return;
4689
5181
  }
5182
+ if (deploymentProfileForManifest(this.state.manifest) === "ucan-store") {
5183
+ const draftErrors = draftUcanStoreBootstrapErrors({
5184
+ input: this.state.ucanStoreBootstrap,
5185
+ operatorAddress: this.state.wallet.address
5186
+ });
5187
+ if (draftErrors.length > 0) {
5188
+ this.patch({ errorText: draftErrors[0] });
5189
+ return;
5190
+ }
5191
+ }
4690
5192
  this.patch({
4691
5193
  busy: { deploying: true },
4692
5194
  errorText: null,
@@ -4736,7 +5238,7 @@ var SponsorRelayController = class {
4736
5238
  detail: `Trying ${crnDisplayName}`
4737
5239
  });
4738
5240
  try {
4739
- const profile = relayProfileForManifest(this.state.manifest);
5241
+ const profile = deploymentProfileForManifest(this.state.manifest);
4740
5242
  attemptProfile = profile;
4741
5243
  const sshPublicKey = profile === "uc-go-peer" ? appendDeploymentTokenToSshPublicKey(
4742
5244
  this.state.sshPublicKey.trim(),
@@ -4955,11 +5457,14 @@ var SponsorRelayController = class {
4955
5457
  this.lastCompletedDeploymentHash = result.itemHash;
4956
5458
  this.emitProgress({
4957
5459
  stage: "completed",
4958
- label: "Relay ready",
5460
+ label: deploymentReadyLabel(attemptProfile),
4959
5461
  progress: 100,
4960
5462
  status: "success",
4961
5463
  itemHash: result.itemHash,
4962
- detail: "Relay runtime, setup, metadata, and bootstrap registration are confirmed."
5464
+ detail: deploymentReadyDetail(
5465
+ attemptProfile,
5466
+ "Relay runtime, setup, metadata, and bootstrap registration are confirmed."
5467
+ )
4963
5468
  });
4964
5469
  return;
4965
5470
  } catch (error) {
@@ -5033,11 +5538,14 @@ var SponsorRelayController = class {
5033
5538
  this.lastCompletedDeploymentHash = attemptItemHash;
5034
5539
  this.emitProgress({
5035
5540
  stage: "completed",
5036
- label: "Relay ready",
5541
+ label: deploymentReadyLabel(attemptProfile),
5037
5542
  progress: 100,
5038
5543
  status: "success",
5039
5544
  itemHash: attemptItemHash,
5040
- detail: "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
5545
+ detail: deploymentReadyDetail(
5546
+ attemptProfile,
5547
+ "The relay completed startup after a transient UI polling error. Runtime and bootstrap registration are confirmed."
5548
+ )
5041
5549
  });
5042
5550
  return;
5043
5551
  }
@@ -5064,11 +5572,14 @@ var SponsorRelayController = class {
5064
5572
  this.lastCompletedDeploymentHash = attemptItemHash;
5065
5573
  this.emitProgress({
5066
5574
  stage: "completed",
5067
- label: "Relay ready",
5575
+ label: deploymentReadyLabel(attemptProfile),
5068
5576
  progress: 100,
5069
5577
  status: "success",
5070
5578
  itemHash: attemptItemHash,
5071
- detail: "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
5579
+ detail: deploymentReadyDetail(
5580
+ attemptProfile,
5581
+ "The relay completed startup after a transient browser-side polling error. Runtime and bootstrap registration are confirmed."
5582
+ )
5072
5583
  });
5073
5584
  return;
5074
5585
  }
@@ -5370,6 +5881,12 @@ export {
5370
5881
  DEFAULT_INSTANCE_NAME,
5371
5882
  DEFAULT_MANIFEST_URL,
5372
5883
  DEFAULT_TIER_ID,
5884
+ DEFAULT_UCAN_STORE_ALLOWED_CAPABILITIES,
5885
+ DEFAULT_UCAN_STORE_MAX_DELEGATION_EXPIRATION_SECONDS,
5886
+ DEFAULT_UCAN_STORE_PWA_ORIGIN,
5887
+ DEFAULT_UCAN_STORE_SERVICE_DID,
5888
+ DEFAULT_UCAN_STORE_SERVICE_ORIGIN,
5889
+ DEFAULT_UCAN_STORE_USER_DELEGATION_EXPIRATION_SECONDS,
5373
5890
  DEPLOYMENT_PENDING_WARNING_MS,
5374
5891
  IDLE_DEPLOYMENT_PROGRESS,
5375
5892
  INSTANCE_RUNTIME_PENDING_REFRESH_MS,