@le-space/ui 0.3.0 → 0.3.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.d.ts CHANGED
@@ -260,6 +260,7 @@ interface SponsorRelayState {
260
260
  sshPublicKey: string;
261
261
  instanceName: string;
262
262
  tierId: string;
263
+ showAdvanced: boolean;
263
264
  showInstances: boolean;
264
265
  showPasteManifest: boolean;
265
266
  busy: {
@@ -294,6 +295,7 @@ declare const RELAY_PING_IDLE_STATE: RelayPingState;
294
295
  declare const IDLE_DEPLOYMENT_PROGRESS: DeploymentProgressEvent;
295
296
  declare const REFRESH_INTERVAL_MS = 30000;
296
297
  declare const RELAY_PING_INTERVAL_MS = 20000;
298
+ declare const MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
297
299
  declare const INSTANCE_RUNTIME_READY_REFRESH_MS = 30000;
298
300
  declare const INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45000;
299
301
  declare const INSTANCE_RUNTIME_STALE_REFRESH_MS: number;
@@ -311,6 +313,7 @@ interface RootfsRequiredPortForward {
311
313
  declare function shortHash(value: string | null | undefined, head?: number, tail?: number): string;
312
314
  declare function formatNumber(value: number | null | undefined, digits?: number): string;
313
315
  declare function formatDateTime(value: string | number | null | undefined): string;
316
+ declare function formatTierSpecLabel(vcpus: number | null | undefined, memoryMiB: number | null | undefined, diskMiB: number | null | undefined): string;
314
317
  declare function buildSshCommand(hostIpv4: string | null, mappedPorts: CompactInstanceRecord["details"]["mappedPorts"]): string | null;
315
318
  declare function joinMappedPorts(mappedPorts: CompactInstanceRecord["details"]["mappedPorts"]): string;
316
319
  declare function joinRequiredPortForwards(portForwards: ReadonlyArray<RootfsRequiredPortForward> | null | undefined): string;
@@ -340,6 +343,7 @@ declare class SponsorRelayController {
340
343
  private client;
341
344
  private refreshTimer;
342
345
  private pingTimer;
346
+ private manifestRefreshTimer;
343
347
  private stopWalletWatch;
344
348
  private props;
345
349
  private progressEmitter;
@@ -368,11 +372,13 @@ declare class SponsorRelayController {
368
372
  toggleOpen(): void;
369
373
  setManifestUrl(manifestUrl: string): void;
370
374
  setManifestJson(manifestJson: string): void;
375
+ setShowAdvanced(showAdvanced: boolean): void;
371
376
  setShowPasteManifest(showPasteManifest: boolean): void;
372
377
  setSshPublicKey(sshPublicKey: string): void;
373
378
  setInstanceName(instanceName: string): void;
374
379
  setTierId(tierId: string): void;
375
380
  private recomputePricingSummary;
381
+ private queueManifestRefresh;
376
382
  connectWallet(): Promise<void>;
377
383
  private refreshWalletDerivedState;
378
384
  refresh(): Promise<void>;
@@ -389,4 +395,4 @@ interface DeploymentProgressEmitter {
389
395
  }
390
396
  declare function createDeploymentProgressEmitter(): DeploymentProgressEmitter;
391
397
 
392
- export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, RELAY_PING_IDLE_STATE, RELAY_PING_INTERVAL_MS, ROOTFS_MISSING_STATE, type RelayPingState, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
398
+ export { type CompactInstanceDetails, type CompactInstanceRecord, DEFAULT_INSTANCE_NAME, DEFAULT_MANIFEST_URL, DEFAULT_TIER_ID, DEPLOYMENT_PENDING_WARNING_MS, type DeploymentProgressEmitter, type EthereumProviderLike, IDLE_DEPLOYMENT_PROGRESS, INSTANCE_RUNTIME_PENDING_REFRESH_MS, INSTANCE_RUNTIME_READY_REFRESH_MS, INSTANCE_RUNTIME_STALE_REFRESH_MS, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS, RECENT_INSTANCE_RUNTIME_GRACE_MS, REFRESH_INTERVAL_MS, RELAY_PING_IDLE_STATE, RELAY_PING_INTERVAL_MS, ROOTFS_MISSING_STATE, type RelayPingState, STALE_INSTANCE_ALLOCATION_COOLDOWN_MS, SponsorRelayController, type SponsorRelayHealthTone, type SponsorRelayPricingSummary, type SponsorRelayProps, type SponsorRelayRootfsHealth, type SponsorRelayState, type SponsorRelaySubscriber, type SponsorRelayWalletState, buildSshCommand, connectWallet, createDeploymentProgressEmitter, createSponsorRelayController, formatDateTime, formatNumber, formatTierSpecLabel, getEthereumProvider, joinMappedPorts, joinRequiredPortForwards, personalSign, resolveManifestSource, shortHash, toChecksumAddress, watchWallet };
package/shared/index.js CHANGED
@@ -29,6 +29,7 @@ var IDLE_DEPLOYMENT_PROGRESS = {
29
29
  };
30
30
  var REFRESH_INTERVAL_MS = 3e4;
31
31
  var RELAY_PING_INTERVAL_MS = 2e4;
32
+ var MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS = 400;
32
33
  var INSTANCE_RUNTIME_READY_REFRESH_MS = 3e4;
33
34
  var INSTANCE_RUNTIME_PENDING_REFRESH_MS = 45e3;
34
35
  var INSTANCE_RUNTIME_STALE_REFRESH_MS = 5 * 6e4;
@@ -56,6 +57,12 @@ function formatDateTime(value) {
56
57
  if (Number.isNaN(date.getTime())) return String(value);
57
58
  return date.toLocaleString();
58
59
  }
60
+ function formatTierSpecLabel(vcpus, memoryMiB, diskMiB) {
61
+ if (vcpus == null || !Number.isFinite(vcpus) || memoryMiB == null || !Number.isFinite(memoryMiB) || diskMiB == null || !Number.isFinite(diskMiB)) {
62
+ return "-";
63
+ }
64
+ return `(${formatNumber(vcpus, 0)} vCPU \xB7 ${formatNumber(memoryMiB / 1024, 1)} GiB RAM \xB7 ${formatNumber(diskMiB / 1024, 0)} GiB disk)`;
65
+ }
59
66
  function buildSshCommand(hostIpv4, mappedPorts2) {
60
67
  if (!hostIpv4) return null;
61
68
  const sshPort = mappedPorts2.find((entry) => entry.label.startsWith("22/"))?.hostPort ?? 22;
@@ -401,6 +408,18 @@ function extractReferenceHashes(details) {
401
408
  if (!Array.isArray(errors)) return [];
402
409
  return errors.filter((value) => typeof value === "string");
403
410
  }
411
+ function extractInsufficientBalanceMessage(details) {
412
+ if (!details || typeof details !== "object" || !("errors" in details)) return null;
413
+ const errors = details.errors;
414
+ const firstError = Array.isArray(errors) && errors[0] && typeof errors[0] === "object" ? errors[0] : null;
415
+ const accountBalance = typeof firstError?.account_balance === "number" || typeof firstError?.account_balance === "string" ? Number(firstError.account_balance) : Number.NaN;
416
+ const requiredBalance = typeof firstError?.required_balance === "number" || typeof firstError?.required_balance === "string" ? Number(firstError.required_balance) : Number.NaN;
417
+ if (!Number.isFinite(accountBalance) || !Number.isFinite(requiredBalance)) {
418
+ return null;
419
+ }
420
+ const shortfall = requiredBalance - accountBalance;
421
+ return shortfall > 0 ? `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short` : `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required`;
422
+ }
404
423
  function describeRejectedDeployment(payload, references, rootfsRef) {
405
424
  const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
406
425
  const pendingReferences = references.filter((reference) => reference.status === "pending");
@@ -415,6 +434,10 @@ function describeRejectedDeployment(payload, references, rootfsRef) {
415
434
  if (missingReferences.length > 0) {
416
435
  return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
417
436
  }
437
+ const insufficientBalanceMessage = extractInsufficientBalanceMessage(payload.details);
438
+ if (insufficientBalanceMessage) {
439
+ return `Aleph rejected this deployment due to ${insufficientBalanceMessage}.`;
440
+ }
418
441
  const referencedHashes = extractReferenceHashes(payload.details);
419
442
  if (referencedHashes.length > 0) {
420
443
  return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
@@ -1027,6 +1050,20 @@ function extractReferenceHashes2(details) {
1027
1050
  if (!Array.isArray(errors)) return [];
1028
1051
  return errors.filter((value) => typeof value === "string");
1029
1052
  }
1053
+ function extractInsufficientBalanceMessage2(details) {
1054
+ if (!details || typeof details !== "object" || !("errors" in details)) {
1055
+ return null;
1056
+ }
1057
+ const errors = details.errors;
1058
+ const firstError = Array.isArray(errors) && errors[0] && typeof errors[0] === "object" ? errors[0] : null;
1059
+ const accountBalance = typeof firstError?.account_balance === "number" || typeof firstError?.account_balance === "string" ? Number(firstError.account_balance) : Number.NaN;
1060
+ const requiredBalance = typeof firstError?.required_balance === "number" || typeof firstError?.required_balance === "string" ? Number(firstError.required_balance) : Number.NaN;
1061
+ if (!Number.isFinite(accountBalance) || !Number.isFinite(requiredBalance)) {
1062
+ return null;
1063
+ }
1064
+ const shortfall = requiredBalance - accountBalance;
1065
+ return shortfall > 0 ? `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required, ${shortfall.toFixed(3)} short` : `insufficient Aleph balance: ${accountBalance.toFixed(3)} available, ${requiredBalance.toFixed(3)} required`;
1066
+ }
1030
1067
  function describeRejectedDeployment2(payload, references, rootfsRef) {
1031
1068
  const errorCode = typeof payload.error_code === "number" ? payload.error_code : null;
1032
1069
  const pendingReferences = references.filter((reference) => reference.status === "pending");
@@ -1041,6 +1078,10 @@ function describeRejectedDeployment2(payload, references, rootfsRef) {
1041
1078
  if (missingReferences.length > 0) {
1042
1079
  return `Aleph rejected this deployment because referenced message(s) were not found on Aleph: ${missingReferences.map((reference) => reference.itemHash).join(", ")}.`;
1043
1080
  }
1081
+ const insufficientBalanceMessage = extractInsufficientBalanceMessage2(payload.details);
1082
+ if (insufficientBalanceMessage) {
1083
+ return `Aleph rejected this deployment due to ${insufficientBalanceMessage}.`;
1084
+ }
1044
1085
  const referencedHashes = extractReferenceHashes2(payload.details);
1045
1086
  if (referencedHashes.length > 0) {
1046
1087
  return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}. Referenced message(s): ${referencedHashes.join(", ")}.`;
@@ -1453,6 +1494,14 @@ function buildPaymentQuote(tier, pricing, balance) {
1453
1494
  label: "credits"
1454
1495
  };
1455
1496
  }
1497
+ function describeRejectedInstanceBroadcast(response) {
1498
+ const balanceMessage = extractInsufficientBalanceMessage2(response.details);
1499
+ if (balanceMessage) {
1500
+ return `Aleph rejected this deployment due to ${balanceMessage}.`;
1501
+ }
1502
+ const errorCode = typeof response.error_code === "number" ? response.error_code : null;
1503
+ return `Aleph rejected this deployment${errorCode ? ` (error ${errorCode})` : ""}.`;
1504
+ }
1456
1505
  function createInstanceContent(args) {
1457
1506
  const sshKey = normalizeSshPublicKey(args.sshPublicKey);
1458
1507
  if (!sshKey) {
@@ -1569,9 +1618,9 @@ async function deployInstance(args) {
1569
1618
  label: status === "processed" ? "Deployment accepted by Aleph" : status === "rejected" ? "Deployment rejected by Aleph" : "Deployment submitted to Aleph",
1570
1619
  progress: status === "processed" ? 88 : status === "rejected" ? 100 : 78,
1571
1620
  status: status === "processed" ? "success" : status === "rejected" ? "error" : "warning",
1572
- detail: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1621
+ detail: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
1573
1622
  itemHash: message.item_hash,
1574
- error: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null,
1623
+ error: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null,
1575
1624
  timestamp: Date.now()
1576
1625
  });
1577
1626
  return {
@@ -1580,7 +1629,7 @@ async function deployInstance(args) {
1580
1629
  status,
1581
1630
  message,
1582
1631
  response,
1583
- rejectionReason: status === "rejected" ? String(response.details ?? "Aleph rejected this deployment.") : null
1632
+ rejectionReason: status === "rejected" ? describeRejectedInstanceBroadcast(response) : null
1584
1633
  };
1585
1634
  }
1586
1635
 
@@ -1970,7 +2019,13 @@ async function configureUcGoPeer(args) {
1970
2019
  ws_port: args.wsPort ?? void 0,
1971
2020
  udp_port: args.udpPort ?? void 0,
1972
2021
  quic_port: args.quicPort ?? void 0,
1973
- webrtc_port: args.webrtcPort ?? void 0
2022
+ webrtc_port: args.webrtcPort ?? void 0,
2023
+ bootstrap_publisher_private_key: args.bootstrapPublisherPrivateKey ?? void 0,
2024
+ bootstrap_publisher_libp2p_identity_b64: args.bootstrapPublisherLibp2pIdentityBase64 ?? void 0,
2025
+ bootstrap_owner_private_key: args.bootstrapOwnerPrivateKey ?? void 0,
2026
+ bootstrap_owner_authorization_b64: args.bootstrapOwnerAuthorizationBase64 ?? void 0,
2027
+ bootstrap_registration_id: args.bootstrapRegistrationId ?? void 0,
2028
+ no_start: args.noStart === true ? true : void 0
1974
2029
  };
1975
2030
  try {
1976
2031
  const response = await args.fetch(
@@ -2010,7 +2065,13 @@ async function configureOrbitdbRelaySetup2(args) {
2010
2065
  metrics_port: args.metricsPort ?? void 0,
2011
2066
  metrics_https_port: args.metricsHttpsPort ?? void 0,
2012
2067
  webrtc_port: args.webrtcPort ?? void 0,
2013
- quic_port: args.quicPort ?? void 0
2068
+ quic_port: args.quicPort ?? void 0,
2069
+ bootstrap_publisher_private_key: args.bootstrapPublisherPrivateKey ?? void 0,
2070
+ bootstrap_publisher_libp2p_identity_hex: args.bootstrapPublisherLibp2pIdentityHex ?? void 0,
2071
+ bootstrap_owner_private_key: args.bootstrapOwnerPrivateKey ?? void 0,
2072
+ bootstrap_owner_authorization_b64: args.bootstrapOwnerAuthorizationBase64 ?? void 0,
2073
+ bootstrap_registration_id: args.bootstrapRegistrationId ?? void 0,
2074
+ no_start: args.noStart === true ? true : void 0
2014
2075
  };
2015
2076
  try {
2016
2077
  const response = await args.fetch(
@@ -2084,14 +2145,56 @@ async function fetchUcGoPeerMetadata(args) {
2084
2145
  }
2085
2146
 
2086
2147
  // ../aleph-bootstrap/src/index.ts
2148
+ import { recoverMessageAddress } from "viem";
2087
2149
  var DEFAULT_ALEPH_API_HOST3 = "https://api2.aleph.im";
2088
2150
  var DEFAULT_ALEPH_BOOTSTRAP_CHANNEL = "simple-todo";
2089
2151
  var DEFAULT_ALEPH_BOOTSTRAP_REF = "simple-todo-bootstrap";
2090
2152
  var DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE = "relay-bootstrap";
2091
2153
  var DEFAULT_BOOTSTRAP_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2154
+ var DEFAULT_BOOTSTRAP_PAGINATION = 50;
2155
+ var RELAY_BOOTSTRAP_SIGNATURE_SCHEME = "personal_sign";
2156
+ function serializeOwnerAuthorizationPayload(payload) {
2157
+ return JSON.stringify({
2158
+ ownerAddress: payload.ownerAddress,
2159
+ publisherAddress: payload.publisherAddress,
2160
+ peerId: payload.peerId,
2161
+ registrationId: payload.registrationId,
2162
+ profile: payload.profile,
2163
+ version: payload.version,
2164
+ instanceItemHash: payload.instanceItemHash,
2165
+ issuedAt: payload.issuedAt,
2166
+ expiresAt: payload.expiresAt
2167
+ });
2168
+ }
2169
+ function serializeRelayProofPayload(payload) {
2170
+ return JSON.stringify({
2171
+ peerId: payload.peerId,
2172
+ multiaddrs: dedupeMultiaddrs(payload.multiaddrs),
2173
+ browserMultiaddrs: payload.browserMultiaddrs ? dedupeMultiaddrs(payload.browserMultiaddrs) : void 0,
2174
+ registrationId: payload.registrationId,
2175
+ profile: payload.profile,
2176
+ version: payload.version,
2177
+ updatedAt: payload.updatedAt
2178
+ });
2179
+ }
2180
+ function asTrimmedString(value) {
2181
+ return typeof value === "string" && value.trim() ? value.trim() : null;
2182
+ }
2183
+ function asNumber4(value) {
2184
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2185
+ if (typeof value === "string" && value.trim()) {
2186
+ const parsed = Number(value);
2187
+ return Number.isFinite(parsed) ? parsed : null;
2188
+ }
2189
+ return null;
2190
+ }
2092
2191
  function normalizeHost(host) {
2093
2192
  return host.trim().toLowerCase();
2094
2193
  }
2194
+ function normalizeSignature(signature) {
2195
+ const trimmed = signature.trim();
2196
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
2197
+ }
2095
2198
  function splitMultiaddr(addr) {
2096
2199
  return addr.split("/").filter(Boolean);
2097
2200
  }
@@ -2162,6 +2265,100 @@ function filterPublicMultiaddrs(addrs, options = {}) {
2162
2265
  return true;
2163
2266
  });
2164
2267
  }
2268
+ function normalizeRelayBootstrapContent(value) {
2269
+ if (!value || typeof value !== "object") return null;
2270
+ const content = value;
2271
+ const peerId = asTrimmedString(content.peerId);
2272
+ const updatedAt = asNumber4(content.updatedAt);
2273
+ if (!peerId || updatedAt == null) return null;
2274
+ const multiaddrs = Array.isArray(content.multiaddrs) ? content.multiaddrs.filter((entry) => typeof entry === "string") : [];
2275
+ const browserMultiaddrs = Array.isArray(content.browserMultiaddrs) ? content.browserMultiaddrs.filter((entry) => typeof entry === "string") : void 0;
2276
+ return {
2277
+ peerId,
2278
+ multiaddrs: dedupeMultiaddrs(multiaddrs),
2279
+ browserMultiaddrs: browserMultiaddrs ? dedupeMultiaddrs(browserMultiaddrs) : void 0,
2280
+ registrationId: asTrimmedString(content.registrationId) ?? void 0,
2281
+ profile: asTrimmedString(content.profile) ?? void 0,
2282
+ version: asTrimmedString(content.version) ?? void 0,
2283
+ ownerAddress: asTrimmedString(content.ownerAddress) ?? void 0,
2284
+ publisherAddress: asTrimmedString(content.publisherAddress) ?? void 0,
2285
+ authorization: normalizeRelayBootstrapAuthorizationRecord(content.authorization) ?? void 0,
2286
+ relayProof: normalizeRelayBootstrapProofRecord(content.relayProof) ?? void 0,
2287
+ updatedAt
2288
+ };
2289
+ }
2290
+ function normalizeRelayBootstrapAuthorizationPayload(value) {
2291
+ if (!value || typeof value !== "object") return null;
2292
+ const payload = value;
2293
+ const ownerAddress = asTrimmedString(payload.ownerAddress);
2294
+ const publisherAddress = asTrimmedString(payload.publisherAddress);
2295
+ const peerId = asTrimmedString(payload.peerId);
2296
+ const issuedAt = asNumber4(payload.issuedAt);
2297
+ const expiresAt = asNumber4(payload.expiresAt) ?? void 0;
2298
+ if (!ownerAddress || !publisherAddress || !peerId || issuedAt == null) {
2299
+ return null;
2300
+ }
2301
+ return {
2302
+ ownerAddress,
2303
+ publisherAddress,
2304
+ peerId,
2305
+ registrationId: asTrimmedString(payload.registrationId) ?? void 0,
2306
+ profile: asTrimmedString(payload.profile) ?? void 0,
2307
+ version: asTrimmedString(payload.version) ?? void 0,
2308
+ instanceItemHash: asTrimmedString(payload.instanceItemHash) ?? void 0,
2309
+ issuedAt,
2310
+ expiresAt
2311
+ };
2312
+ }
2313
+ function normalizeRelayBootstrapAuthorizationRecord(value) {
2314
+ if (!value || typeof value !== "object") return null;
2315
+ const record = value;
2316
+ const scheme = asTrimmedString(record.scheme);
2317
+ const signature = asTrimmedString(record.signature);
2318
+ const payload = normalizeRelayBootstrapAuthorizationPayload(record.payload);
2319
+ if (!scheme || !signature || !payload) return null;
2320
+ return { scheme, signature, payload };
2321
+ }
2322
+ function normalizeRelayBootstrapProofPayload(value) {
2323
+ if (!value || typeof value !== "object") return null;
2324
+ const payload = value;
2325
+ const peerId = asTrimmedString(payload.peerId);
2326
+ const updatedAt = asNumber4(payload.updatedAt);
2327
+ if (!peerId || updatedAt == null) return null;
2328
+ const multiaddrs = Array.isArray(payload.multiaddrs) ? payload.multiaddrs.filter((entry) => typeof entry === "string") : [];
2329
+ const browserMultiaddrs = Array.isArray(payload.browserMultiaddrs) ? payload.browserMultiaddrs.filter((entry) => typeof entry === "string") : void 0;
2330
+ return {
2331
+ peerId,
2332
+ multiaddrs: dedupeMultiaddrs(multiaddrs),
2333
+ browserMultiaddrs: browserMultiaddrs ? dedupeMultiaddrs(browserMultiaddrs) : void 0,
2334
+ registrationId: asTrimmedString(payload.registrationId) ?? void 0,
2335
+ profile: asTrimmedString(payload.profile) ?? void 0,
2336
+ version: asTrimmedString(payload.version) ?? void 0,
2337
+ updatedAt
2338
+ };
2339
+ }
2340
+ function normalizeRelayBootstrapProofRecord(value) {
2341
+ if (!value || typeof value !== "object") return null;
2342
+ const record = value;
2343
+ const scheme = asTrimmedString(record.scheme);
2344
+ const signature = asTrimmedString(record.signature);
2345
+ const payload = normalizeRelayBootstrapProofPayload(record.payload);
2346
+ if (!scheme || !signature || !payload) return null;
2347
+ return { scheme, signature, payload };
2348
+ }
2349
+ function normalizeRelayBootstrapPostRecord(value) {
2350
+ if (!value || typeof value !== "object") return null;
2351
+ const entry = value;
2352
+ return {
2353
+ hash: asTrimmedString(entry.hash),
2354
+ itemHash: asTrimmedString(entry.item_hash),
2355
+ address: asTrimmedString(entry.address),
2356
+ ref: asTrimmedString(entry.ref),
2357
+ type: asTrimmedString(entry.type),
2358
+ time: asNumber4(entry.time),
2359
+ content: normalizeRelayBootstrapContent(entry.content)
2360
+ };
2361
+ }
2165
2362
  function buildRelayBootstrapPostContent(args) {
2166
2363
  const now = args.now ?? Date.now() / 1e3;
2167
2364
  const updatedAt = args.now ?? Date.now();
@@ -2175,8 +2372,13 @@ function buildRelayBootstrapPostContent(args) {
2175
2372
  browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2176
2373
  browserDialableOnly: true
2177
2374
  }) : void 0,
2375
+ registrationId: args.registrationId,
2178
2376
  profile: args.profile,
2179
2377
  version: args.version,
2378
+ ownerAddress: args.ownerAddress,
2379
+ publisherAddress: args.publisherAddress,
2380
+ authorization: args.authorization,
2381
+ relayProof: args.relayProof,
2180
2382
  updatedAt: Math.round(updatedAt)
2181
2383
  },
2182
2384
  time: now
@@ -2190,10 +2392,15 @@ async function createRelayBootstrapPost(args) {
2190
2392
  peerId: args.peerId,
2191
2393
  multiaddrs: args.multiaddrs,
2192
2394
  browserMultiaddrs: args.browserMultiaddrs,
2395
+ registrationId: args.registrationId,
2193
2396
  ref: args.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF,
2194
2397
  postType: args.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE,
2195
2398
  profile: args.profile,
2196
2399
  version: args.version,
2400
+ ownerAddress: args.ownerAddress,
2401
+ publisherAddress: args.publisherAddress,
2402
+ authorization: args.authorization,
2403
+ relayProof: args.relayProof,
2197
2404
  now: nowMillis
2198
2405
  });
2199
2406
  itemContent.time = nowSeconds;
@@ -2210,19 +2417,130 @@ async function createRelayBootstrapPost(args) {
2210
2417
  item_hash: itemHash
2211
2418
  };
2212
2419
  }
2420
+ async function signRelayBootstrapAuthorization(args) {
2421
+ const payload = {
2422
+ ownerAddress: args.ownerAddress,
2423
+ publisherAddress: args.publisherAddress,
2424
+ peerId: args.peerId,
2425
+ registrationId: args.registrationId,
2426
+ profile: args.profile,
2427
+ version: args.version,
2428
+ instanceItemHash: args.instanceItemHash,
2429
+ issuedAt: args.issuedAt ?? Date.now(),
2430
+ expiresAt: args.expiresAt
2431
+ };
2432
+ const serialized = serializeOwnerAuthorizationPayload(payload);
2433
+ const signature = await args.signer(args.ownerAddress, serialized);
2434
+ return {
2435
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2436
+ payload,
2437
+ signature: normalizeSignature(signature)
2438
+ };
2439
+ }
2440
+ async function signRelayBootstrapProof(args) {
2441
+ const payload = {
2442
+ peerId: args.peerId,
2443
+ multiaddrs: filterPublicMultiaddrs(args.multiaddrs),
2444
+ browserMultiaddrs: args.browserMultiaddrs ? filterPublicMultiaddrs(args.browserMultiaddrs, {
2445
+ browserDialableOnly: true
2446
+ }) : void 0,
2447
+ registrationId: args.registrationId,
2448
+ profile: args.profile,
2449
+ version: args.version,
2450
+ updatedAt: args.updatedAt ?? Date.now()
2451
+ };
2452
+ const serialized = serializeRelayProofPayload(payload);
2453
+ const signature = await args.signer(args.publisherAddress, serialized);
2454
+ return {
2455
+ scheme: RELAY_BOOTSTRAP_SIGNATURE_SCHEME,
2456
+ payload,
2457
+ signature: normalizeSignature(signature)
2458
+ };
2459
+ }
2460
+ async function fetchAlephBootstrapPosts(options = {}) {
2461
+ const fetchImpl = options.fetch ?? globalThis.fetch?.bind(globalThis);
2462
+ if (typeof fetchImpl !== "function") {
2463
+ throw new Error(
2464
+ "A fetch implementation is required to query Aleph bootstrap posts."
2465
+ );
2466
+ }
2467
+ const url = new URL(
2468
+ "/api/v0/posts.json",
2469
+ options.apiHost ?? DEFAULT_ALEPH_API_HOST3
2470
+ );
2471
+ url.searchParams.set(
2472
+ "channels",
2473
+ options.channel ?? DEFAULT_ALEPH_BOOTSTRAP_CHANNEL
2474
+ );
2475
+ url.searchParams.set("refs", options.ref ?? DEFAULT_ALEPH_BOOTSTRAP_REF);
2476
+ url.searchParams.set(
2477
+ "types",
2478
+ options.postType ?? DEFAULT_ALEPH_BOOTSTRAP_POST_TYPE
2479
+ );
2480
+ url.searchParams.set(
2481
+ "pagination",
2482
+ String(options.pagination ?? DEFAULT_BOOTSTRAP_PAGINATION)
2483
+ );
2484
+ url.searchParams.set("page", String(options.page ?? 1));
2485
+ const response = await fetchImpl(url.toString(), {
2486
+ method: "GET",
2487
+ headers: { accept: "application/json" }
2488
+ });
2489
+ if (!response.ok) {
2490
+ throw new Error(`Aleph bootstrap post lookup failed: ${response.status}`);
2491
+ }
2492
+ const payload = await response.json();
2493
+ return (payload.posts ?? []).map((entry) => normalizeRelayBootstrapPostRecord(entry)).filter((entry) => entry != null);
2494
+ }
2213
2495
 
2214
2496
  // ../core/src/bootstrap-registration.ts
2215
2497
  async function publishRelayBootstrapRegistration(args) {
2498
+ const publisherAddress = args.publisherAddress ?? args.sender;
2499
+ const ownerAddress = args.ownerAddress;
2500
+ let ownerAuthorization = args.ownerAuthorization;
2501
+ let relayProof = args.relayProof;
2502
+ if (!ownerAuthorization && ownerAddress && args.ownerSigner) {
2503
+ ownerAuthorization = await signRelayBootstrapAuthorization({
2504
+ ownerAddress,
2505
+ publisherAddress,
2506
+ peerId: args.peerId,
2507
+ registrationId: args.registrationId,
2508
+ profile: args.profile,
2509
+ version: args.version,
2510
+ instanceItemHash: args.instanceItemHash,
2511
+ issuedAt: args.authorizationIssuedAt ?? args.now ?? Date.now(),
2512
+ expiresAt: args.authorizationExpiresAt,
2513
+ signer: args.ownerSigner
2514
+ });
2515
+ }
2516
+ if (!relayProof && args.publisherSigner) {
2517
+ relayProof = await signRelayBootstrapProof({
2518
+ publisherAddress,
2519
+ peerId: args.peerId,
2520
+ multiaddrs: args.multiaddrs,
2521
+ browserMultiaddrs: args.browserMultiaddrs,
2522
+ registrationId: args.registrationId,
2523
+ profile: args.profile,
2524
+ version: args.version,
2525
+ updatedAt: args.now ?? Date.now(),
2526
+ signer: args.publisherSigner
2527
+ });
2528
+ }
2216
2529
  const unsigned = await createRelayBootstrapPost({
2217
2530
  sender: args.sender,
2218
2531
  peerId: args.peerId,
2219
2532
  multiaddrs: args.multiaddrs,
2220
2533
  browserMultiaddrs: args.browserMultiaddrs,
2534
+ registrationId: args.registrationId,
2221
2535
  channel: args.channel,
2222
2536
  ref: args.ref,
2223
2537
  postType: args.postType,
2224
2538
  profile: args.profile,
2225
2539
  version: args.version,
2540
+ ownerAddress,
2541
+ publisherAddress,
2542
+ authorization: ownerAuthorization,
2543
+ relayProof,
2226
2544
  now: args.now,
2227
2545
  hasher: args.hasher
2228
2546
  });
@@ -2241,6 +2559,32 @@ async function publishRelayBootstrapRegistration(args) {
2241
2559
  sync: args.sync ?? true,
2242
2560
  fetch: args.fetch
2243
2561
  });
2562
+ let forgottenHashes = [];
2563
+ let forgetResult;
2564
+ if (args.forgetPrevious && args.registrationId) {
2565
+ const previousPosts = await fetchAlephBootstrapPosts({
2566
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2567
+ channel: args.channel,
2568
+ ref: args.ref,
2569
+ postType: args.postType,
2570
+ fetch: args.fetch
2571
+ });
2572
+ forgottenHashes = previousPosts.filter(
2573
+ (post) => post.address?.toLowerCase() === args.sender.toLowerCase() && post.content?.registrationId === args.registrationId && (post.itemHash ?? post.hash) != null && (post.itemHash ?? post.hash) !== unsigned.item_hash
2574
+ ).map((post) => post.itemHash ?? post.hash ?? "").filter(Boolean);
2575
+ if (forgottenHashes.length > 0) {
2576
+ forgetResult = await forgetAlephMessages({
2577
+ sender: args.sender,
2578
+ hashes: forgottenHashes,
2579
+ reason: args.forgetPreviousReason ?? `Replace older relay bootstrap records for ${args.registrationId}`,
2580
+ signer: args.signer,
2581
+ hasher: args.hasher,
2582
+ fetch: args.fetch,
2583
+ apiHost: args.apiHost ?? DEFAULT_ALEPH_API_HOST3,
2584
+ sync: args.sync ?? true
2585
+ });
2586
+ }
2587
+ }
2244
2588
  return {
2245
2589
  status: "published",
2246
2590
  itemHash: unsigned.item_hash,
@@ -2248,7 +2592,9 @@ async function publishRelayBootstrapRegistration(args) {
2248
2592
  response,
2249
2593
  httpStatus,
2250
2594
  publishedMultiaddrs: content.content?.multiaddrs ?? [],
2251
- publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? []
2595
+ publishedBrowserMultiaddrs: content.content?.browserMultiaddrs ?? [],
2596
+ forgottenHashes,
2597
+ forgetResult
2252
2598
  };
2253
2599
  }
2254
2600
 
@@ -2287,8 +2633,9 @@ function defaultState(props = {}) {
2287
2633
  sshPublicKey: props.sshPublicKey ?? "",
2288
2634
  instanceName: props.instanceName ?? DEFAULT_INSTANCE_NAME,
2289
2635
  tierId: DEFAULT_TIER_ID,
2636
+ showAdvanced: Boolean(props.manifestJson?.trim() || props.sshPublicKey?.trim()),
2290
2637
  showInstances: props.showInstances ?? true,
2291
- showPasteManifest: false,
2638
+ showPasteManifest: Boolean(props.manifestJson?.trim()),
2292
2639
  busy: {
2293
2640
  connectingWallet: false,
2294
2641
  refreshing: false,
@@ -2324,6 +2671,14 @@ function defaultState(props = {}) {
2324
2671
  deploymentProgress: IDLE_DEPLOYMENT_PROGRESS
2325
2672
  };
2326
2673
  }
2674
+ function manifestLoadErrorState(error) {
2675
+ const message = error instanceof Error ? error.message : String(error);
2676
+ return {
2677
+ manifest: null,
2678
+ valid: false,
2679
+ errors: [message]
2680
+ };
2681
+ }
2327
2682
  function isDebugEnabled(props) {
2328
2683
  if (props.debug) {
2329
2684
  return true;
@@ -2606,6 +2961,7 @@ var SponsorRelayController = class {
2606
2961
  client;
2607
2962
  refreshTimer = null;
2608
2963
  pingTimer = null;
2964
+ manifestRefreshTimer = null;
2609
2965
  stopWalletWatch = null;
2610
2966
  props;
2611
2967
  progressEmitter = createDeploymentProgressEmitter();
@@ -2978,6 +3334,7 @@ var SponsorRelayController = class {
2978
3334
  destroy() {
2979
3335
  if (this.refreshTimer) clearInterval(this.refreshTimer);
2980
3336
  if (this.pingTimer) clearInterval(this.pingTimer);
3337
+ if (this.manifestRefreshTimer) clearTimeout(this.manifestRefreshTimer);
2981
3338
  this.stopWalletWatch?.();
2982
3339
  }
2983
3340
  setOpen(open) {
@@ -2988,15 +3345,27 @@ var SponsorRelayController = class {
2988
3345
  }
2989
3346
  setManifestUrl(manifestUrl) {
2990
3347
  this.patch({ manifestUrl });
3348
+ this.queueManifestRefresh();
2991
3349
  }
2992
3350
  setManifestJson(manifestJson) {
2993
- this.patch({ manifestJson });
3351
+ this.patch({
3352
+ manifestJson,
3353
+ showAdvanced: this.state.showAdvanced || Boolean(manifestJson.trim()),
3354
+ showPasteManifest: this.state.showPasteManifest || Boolean(manifestJson.trim())
3355
+ });
3356
+ this.queueManifestRefresh();
3357
+ }
3358
+ setShowAdvanced(showAdvanced) {
3359
+ this.patch({ showAdvanced });
2994
3360
  }
2995
3361
  setShowPasteManifest(showPasteManifest) {
2996
3362
  this.patch({ showPasteManifest });
2997
3363
  }
2998
3364
  setSshPublicKey(sshPublicKey) {
2999
- this.patch({ sshPublicKey });
3365
+ this.patch({
3366
+ sshPublicKey,
3367
+ showAdvanced: this.state.showAdvanced || Boolean(sshPublicKey.trim())
3368
+ });
3000
3369
  }
3001
3370
  setInstanceName(instanceName) {
3002
3371
  this.patch({ instanceName });
@@ -3032,6 +3401,18 @@ var SponsorRelayController = class {
3032
3401
  selectedCrn
3033
3402
  });
3034
3403
  }
3404
+ queueManifestRefresh() {
3405
+ if (!this.state.ready) {
3406
+ return;
3407
+ }
3408
+ if (this.manifestRefreshTimer) {
3409
+ clearTimeout(this.manifestRefreshTimer);
3410
+ }
3411
+ this.manifestRefreshTimer = setTimeout(() => {
3412
+ this.manifestRefreshTimer = null;
3413
+ void this.refresh();
3414
+ }, MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS);
3415
+ }
3035
3416
  async connectWallet() {
3036
3417
  this.patch({
3037
3418
  busy: { connectingWallet: true },
@@ -3085,15 +3466,15 @@ var SponsorRelayController = class {
3085
3466
  statusText: "Refreshing relay sponsor data"
3086
3467
  });
3087
3468
  try {
3088
- const manifestState = await resolveManifest({
3089
- manifestUrl: this.state.manifestUrl,
3090
- manifestJson: this.state.manifestJson
3091
- });
3092
- const manifest = manifestState.manifest;
3093
3469
  const [pricingSummary, crns] = await Promise.all([
3094
3470
  fetchInstancePricing(this.client.apiHost),
3095
3471
  this.client.fetchCrns()
3096
3472
  ]);
3473
+ const manifestState = await resolveManifest({
3474
+ manifestUrl: this.state.manifestUrl,
3475
+ manifestJson: this.state.manifestJson
3476
+ }).catch((error) => manifestLoadErrorState(error));
3477
+ const manifest = manifestState.manifest;
3097
3478
  let balance = this.state.balance;
3098
3479
  let instances = [];
3099
3480
  if (this.state.wallet.address) {
@@ -3545,6 +3926,7 @@ export {
3545
3926
  INSTANCE_RUNTIME_PENDING_REFRESH_MS,
3546
3927
  INSTANCE_RUNTIME_READY_REFRESH_MS,
3547
3928
  INSTANCE_RUNTIME_STALE_REFRESH_MS,
3929
+ MANIFEST_SOURCE_REFRESH_DEBOUNCE_MS,
3548
3930
  RECENT_INSTANCE_RUNTIME_GRACE_MS,
3549
3931
  REFRESH_INTERVAL_MS,
3550
3932
  RELAY_PING_IDLE_STATE,
@@ -3558,6 +3940,7 @@ export {
3558
3940
  createSponsorRelayController,
3559
3941
  formatDateTime,
3560
3942
  formatNumber,
3943
+ formatTierSpecLabel,
3561
3944
  getEthereumProvider,
3562
3945
  joinMappedPorts,
3563
3946
  joinRequiredPortForwards,