From 732ee5e7145f464311719b5fa0a7059d326b8a1f Mon Sep 17 00:00:00 2001 From: ssmithx Date: Wed, 9 Sep 2026 07:49:30 +0000 Subject: [PATCH] feat(neode-ui): launch cuprate tiles on the Cuprate UI companion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../__tests__/appSessionConfig.test.ts | 22 ++++++++++++++++--- .../src/views/appSession/appSessionConfig.ts | 12 ++++++++++ .../appSession/generatedAppSessionConfig.ts | 2 ++ neode-ui/src/views/apps/appsConfig.ts | 4 ++++ neode-ui/src/views/discover/curatedApps.ts | 2 ++ neode-ui/vite.config.ts | 5 +++++ 6 files changed, 44 insertions(+), 3 deletions(-) diff --git a/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts b/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts index 0ef59457..d267c17c 100644 --- a/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts +++ b/neode-ui/src/views/appSession/__tests__/appSessionConfig.test.ts @@ -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') }) }) diff --git a/neode-ui/src/views/appSession/appSessionConfig.ts b/neode-ui/src/views/appSession/appSessionConfig.ts index bc4c7a2a..e55a8e7c 100644 --- a/neode-ui/src/views/appSession/appSessionConfig.ts +++ b/neode-ui/src/views/appSession/appSessionConfig.ts @@ -29,6 +29,9 @@ export const APP_PORTS: Record = { '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 diff --git a/neode-ui/src/views/appSession/generatedAppSessionConfig.ts b/neode-ui/src/views/appSession/generatedAppSessionConfig.ts index 4d555b91..c49a0542 100644 --- a/neode-ui/src/views/appSession/generatedAppSessionConfig.ts +++ b/neode-ui/src/views/appSession/generatedAppSessionConfig.ts @@ -9,6 +9,7 @@ export const GENERATED_APP_PORTS: Record = { "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 = { "btcpay-server": "BTCPay Server", "core-lightning": "Core Lightning (CLN)", "cuprate": "Cuprate", + "cuprate-ui": "Cuprate UI", "electrs-ui": "Electrs UI", "electrumx": "ElectrumX", "fedimint": "Fedimint Guardian", diff --git a/neode-ui/src/views/apps/appsConfig.ts b/neode-ui/src/views/apps/appsConfig.ts index ff8217da..566fc396 100644 --- a/neode-ui/src/views/apps/appsConfig.ts +++ b/neode-ui/src/views/apps/appsConfig.ts @@ -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 = { '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 = { '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 diff --git a/neode-ui/src/views/discover/curatedApps.ts b/neode-ui/src/views/discover/curatedApps.ts index 44b4cd4f..e0a50c45 100644 --- a/neode-ui/src/views/discover/curatedApps.ts +++ b/neode-ui/src/views/discover/curatedApps.ts @@ -97,6 +97,8 @@ const CATALOG_APP_ID_ALIASES: Record = { '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', } diff --git a/neode-ui/vite.config.ts b/neode-ui/vite.config.ts index afea9723..a1d7f13a 100644 --- a/neode-ui/vite.config.ts +++ b/neode-ui/vite.config.ts @@ -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 },