feat(neode-ui): launch cuprate tiles on the Cuprate UI companion

cuprate publishes only raw JSON RPC (18090 restricted, 18183 p2p), so
launches must land on the companion on :18091, never on the running
node's runtimeUrl — same root-path special-case bitcoin uses, with the
dev vite proxy for /app/cuprate-ui/. Alias cuprate -> cuprate-ui so the
port-auth lookup finds the gated launch port on HTTPS nodes; pin the
companion icon to the cuprate mark.
This commit is contained in:
2026-09-09 07:49:30 +00:00
parent 37fde6cfa7
commit 732ee5e714
6 changed files with 44 additions and 3 deletions
@@ -1,4 +1,4 @@
import { describe, expect, it, beforeEach } from 'vitest'
import { describe, expect, it, beforeEach, afterEach, vi } from 'vitest'
import { NEW_TAB_APPS, directAppUrl, resolveAppUrl } from '../appSessionConfig'
import { GENERATED_NEW_TAB_APPS } from '../generatedAppSessionConfig'
import { __setSignedCatalogForTests } from '../../discover/curatedApps'
@@ -19,6 +19,8 @@ const SIGNED = {
tailscale: { version: 'stable' },
// auth:none ports are container-published too — https would fail.
cuprate: { version: '0.1.0-preview', manifest: { app: { ports: [{ host: 18090, auth: 'none' }] } } },
// Cuprate's companion dashboard owns the gated launch port.
'cuprate-ui': { version: '1.0.0', manifest: { app: { ports: [{ host: 18091, auth: 'gated' }] } } },
},
}
@@ -34,6 +36,9 @@ describe('appSessionConfig', () => {
beforeEach(() => {
__setSignedCatalogForTests(SIGNED as never)
})
afterEach(() => {
vi.unstubAllEnvs()
})
it('keeps manifest-owned new-tab apps marked on every viewport', () => {
expect(NEW_TAB_APPS.has('btcpay-server')).toBe(true)
expect(NEW_TAB_APPS.has('photoprism')).toBe(true)
@@ -144,7 +149,18 @@ describe('appSessionConfig', () => {
// A runtime port the gate does NOT front keeps plain http (https would
// fail to connect outright).
expect(resolveAppUrl('filebrowser', undefined, 'http://localhost:18083')).toBe('http://192.0.2.10:18083')
// Cuprate's UI port is auth:none — plain HTTP stays plain.
expect(resolveAppUrl('cuprate', undefined, 'http://localhost:18090')).toBe('http://192.0.2.10:18090')
})
it('launches cuprate on the companion UI, never the raw restricted RPC', () => {
// resolveAppUrl has a DEV branch (vite path proxy) — assert prod launch.
vi.stubEnv('DEV', false)
stubLocation({ hostname: '192.0.2.10', protocol: 'https:' })
// cuprate publishes only raw JSON RPC (18090 restricted, 18183 p2p).
// Launches must land on the companion dashboard (:18091, gate-fronted →
// page scheme), even when the backend hands back a running-node runtime
// URL pointing at the bare RPC.
expect(resolveAppUrl('cuprate', undefined, 'http://localhost:18090')).toBe('https://192.0.2.10:18091')
expect(resolveAppUrl('cuprate-ui')).toBe('https://192.0.2.10:18091')
expect(resolveAppUrl('archy-cuprate-ui')).toBe('https://192.0.2.10:18091')
})
})
@@ -29,6 +29,9 @@ export const APP_PORTS: Record<string, number> = {
'bitcoin-knots': 8334,
'bitcoin-core': 8334,
'bitcoin-ui': 8334,
'cuprate': 18091,
'cuprate-ui': 18091,
'archy-cuprate-ui': 18091,
'electrumx': 50002,
'electrs': 50002,
'archy-electrs-ui': 50002,
@@ -111,6 +114,15 @@ export function resolveAppUrl(id: string, routeQueryPath?: string, runtimeUrl?:
return appOrigin(8334, id)
}
// Cuprate UI is the same companion shape on :18091. The cuprate app itself
// publishes only the restricted RPC (18090) — a raw JSON endpoint, not a
// page — so cuprate launches must land on the companion, never on the
// runtimeUrl a running cuprate reports.
if (id === 'cuprate' || id === 'cuprate-ui' || id === 'archy-cuprate-ui') {
if (import.meta.env.DEV) return '/app/cuprate-ui/'
return appOrigin(18091, id)
}
if (runtimeUrl && id !== 'netbird') {
let base = runtimeUrl.replace(/localhost/i, window.location.hostname)
// The backend reports runtime URLs as http:// because that is how the app
@@ -9,6 +9,7 @@ export const GENERATED_APP_PORTS: Record<string, number> = {
"bitcoin-ui": 8334,
"botfights": 9100,
"btcpay-server": 23000,
"cuprate-ui": 18091,
"electrs-ui": 50002,
"electrumx": 50002,
"fedimint": 8175,
@@ -54,6 +55,7 @@ export const GENERATED_APP_TITLES: Record<string, string> = {
"btcpay-server": "BTCPay Server",
"core-lightning": "Core Lightning (CLN)",
"cuprate": "Cuprate",
"cuprate-ui": "Cuprate UI",
"electrs-ui": "Electrs UI",
"electrumx": "ElectrumX",
"fedimint": "Fedimint Guardian",
+4
View File
@@ -38,6 +38,7 @@ export function isServicePackage(id: string, pkg?: PackageDataEntry): boolean {
// Known app -> category mappings (matches App Store categorisation)
export const APP_CATEGORY_MAP: Record<string, string> = {
'bitcoin-core': 'money', 'bitcoin-knots': 'money', 'bitcoin-ui': 'money', 'electrumx': 'money', 'electrs': 'money',
'cuprate': 'money', 'cuprate-ui': 'money',
'lnd': 'money', 'mempool': 'money', 'mempool-web': 'money', 'btcpay-server': 'commerce',
'fedimint': 'money', 'fedimint-gateway': 'money',
'indeedhub': 'media', 'jellyfin': 'media', 'photoprism': 'media', 'immich': 'media',
@@ -187,6 +188,9 @@ const APP_ICON_FALLBACKS: Record<string, string> = {
'archy-bitcoin-ui': '/assets/img/app-icons/bitcoin-knots.webp',
'archy-lnd-ui': '/assets/img/app-icons/lnd.png',
'archy-electrs-ui': '/assets/img/app-icons/electrumx.png',
// Cuprate's companion dashboard shares the node's icon.
'cuprate-ui': '/assets/img/app-icons/cuprate.svg',
'archy-cuprate-ui': '/assets/img/app-icons/cuprate.svg',
// ElectrumX ships under a few historical ids (the backend was renamed
// electrs → electrumx). Pin the whole family to the ElectrumX icon so My
// Apps shows the right logo no matter which id the node has it installed
@@ -97,6 +97,8 @@ const CATALOG_APP_ID_ALIASES: Record<string, string> = {
'archy-lnd-ui': 'lnd-ui',
'bitcoin-knots': 'bitcoin-ui',
'bitcoin-core': 'bitcoin-ui',
'cuprate': 'cuprate-ui',
'archy-cuprate-ui': 'cuprate-ui',
'fedimintd': 'fedimint',
'immich_server': 'immich',
}
+5
View File
@@ -162,6 +162,11 @@ export default defineConfig({
changeOrigin: true,
secure: false,
},
'/app/cuprate-ui': {
target: process.env.BACKEND_URL || 'http://localhost:5959',
changeOrigin: true,
secure: false,
},
// Demo mock app UIs (electrumx, lnd, fedimint) + generic notice page.
'/app/electrumx': { target: process.env.BACKEND_URL || 'http://localhost:5959', changeOrigin: true, secure: false },
'/app/electrs': { target: process.env.BACKEND_URL || 'http://localhost:5959', changeOrigin: true, secure: false },