diff --git a/demo/content/music/Zazawowow - Michael Michael Saylor.mp3 b/demo/content/music/Zazawowow - Michael Michael Saylor.mp3 new file mode 100644 index 00000000..d5d7e141 Binary files /dev/null and b/demo/content/music/Zazawowow - Michael Michael Saylor.mp3 differ diff --git a/demo/content/music/Zazawowow - WEBFIVEFOURTHREETWOONE.mp3 b/demo/content/music/Zazawowow - WEBFIVEFOURTHREETWOONE.mp3 new file mode 100644 index 00000000..efc27013 Binary files /dev/null and b/demo/content/music/Zazawowow - WEBFIVEFOURTHREETWOONE.mp3 differ diff --git a/demo/peer-media/artwork-michael-saylor.jpg b/demo/peer-media/artwork-michael-saylor.jpg new file mode 100644 index 00000000..9b0d80c4 Binary files /dev/null and b/demo/peer-media/artwork-michael-saylor.jpg differ diff --git a/demo/peer-media/artwork-webfive.jpg b/demo/peer-media/artwork-webfive.jpg new file mode 100644 index 00000000..98ea5894 Binary files /dev/null and b/demo/peer-media/artwork-webfive.jpg differ diff --git a/demo/peer-media/photo-alpine-ridge.jpg b/demo/peer-media/photo-alpine-ridge.jpg index 2fdd3eb7..cea58d8b 100644 Binary files a/demo/peer-media/photo-alpine-ridge.jpg and b/demo/peer-media/photo-alpine-ridge.jpg differ diff --git a/demo/peer-media/photo-aurora-fjord.jpg b/demo/peer-media/photo-aurora-fjord.jpg index 5282b779..d1da76ce 100644 Binary files a/demo/peer-media/photo-aurora-fjord.jpg and b/demo/peer-media/photo-aurora-fjord.jpg differ diff --git a/demo/peer-media/photo-autumn-valley.jpg b/demo/peer-media/photo-autumn-valley.jpg index 23c66ef1..92988633 100644 Binary files a/demo/peer-media/photo-autumn-valley.jpg and b/demo/peer-media/photo-autumn-valley.jpg differ diff --git a/demo/peer-media/photo-city-neon.jpg b/demo/peer-media/photo-city-neon.jpg index da19c66e..29284864 100644 Binary files a/demo/peer-media/photo-city-neon.jpg and b/demo/peer-media/photo-city-neon.jpg differ diff --git a/demo/peer-media/photo-desert-dunes.jpg b/demo/peer-media/photo-desert-dunes.jpg index c4686f00..bc0c2717 100644 Binary files a/demo/peer-media/photo-desert-dunes.jpg and b/demo/peer-media/photo-desert-dunes.jpg differ diff --git a/demo/peer-media/photo-forest-mist.jpg b/demo/peer-media/photo-forest-mist.jpg index 6be316e0..8522efd0 100644 Binary files a/demo/peer-media/photo-forest-mist.jpg and b/demo/peer-media/photo-forest-mist.jpg differ diff --git a/demo/peer-media/photo-harbor-dawn.jpg b/demo/peer-media/photo-harbor-dawn.jpg index 7ee0d193..153b7afc 100644 Binary files a/demo/peer-media/photo-harbor-dawn.jpg and b/demo/peer-media/photo-harbor-dawn.jpg differ diff --git a/demo/peer-media/photo-mountain-lake.jpg b/demo/peer-media/photo-mountain-lake.jpg index 39ffa9bd..77595cc4 100644 Binary files a/demo/peer-media/photo-mountain-lake.jpg and b/demo/peer-media/photo-mountain-lake.jpg differ diff --git a/demo/peer-media/photo-northern-road.jpg b/demo/peer-media/photo-northern-road.jpg index be284b50..524d51f7 100644 Binary files a/demo/peer-media/photo-northern-road.jpg and b/demo/peer-media/photo-northern-road.jpg differ diff --git a/demo/peer-media/photo-ocean-cliff.jpg b/demo/peer-media/photo-ocean-cliff.jpg index fa5cf139..bf50f348 100644 Binary files a/demo/peer-media/photo-ocean-cliff.jpg and b/demo/peer-media/photo-ocean-cliff.jpg differ diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index b3f1b13e..0e255954 100755 --- a/neode-ui/mock-backend.js +++ b/neode-ui/mock-backend.js @@ -1249,8 +1249,10 @@ function demoAppShell(title, sub, iconPath, bodyHtml) { // series, books, music, photography and documents with real metadata. Music, // photos and documents are backed by REAL committed files (they play/open); // films/series/books are paid so the buy-flow poster/cover previews carry the -// visuals. Preview images live in demo/peer-media (sourced from the web via -// picsum.photos — Unsplash-licensed — seeds pinned in git history). +// visuals. Photographs in demo/peer-media are real free-license images from +// Wikimedia Commons (per-file credit in each description); the two Zazawowow +// tracks are real songs published on Wavlake (wavlake.com), bytes + artwork +// committed from the public catalog CDN. const PEER_MEDIA = (f) => path.join(__dirname, '..', 'demo', 'peer-media', f) const PEER_CONTENT = (...p) => path.join(__dirname, '..', 'demo', 'content', ...p) const PEER_LIBRARY = [ @@ -1270,7 +1272,13 @@ const PEER_LIBRARY = [ description: 'EPUB · 348 pp · 2025 anthology — privacy engineering, remailers, digital cash history.' }, { id: 'book-broadcast-power', filename: 'Broadcast Power — pirate radio to mesh.pdf', mime_type: 'application/pdf', size_bytes: 18_874_368, access: { paid: { price_sats: 150 } }, preview: PEER_MEDIA('cover-book-broadcast.jpg'), description: 'PDF · 214 pp · illustrated 2024 history of community-run transmission networks.' }, - // Music — free, streams the real committed files + // Music — real committed files. The two Wavlake tracks are real releases by + // Zazawowow (metadata from catalog.wavlake.com); the first is the demo's + // showcase PAID track — buying it delivers the actual mp3 and autoplays it. + { id: 'song-webfive', filename: 'Zazawowow - WEBFIVEFOURTHREETWOONE.mp3', mime_type: 'audio/mpeg', size_bytes: 6_041_903, access: { paid: { price_sats: 21 } }, disk: PEER_CONTENT('music', 'Zazawowow - WEBFIVEFOURTHREETWOONE.mp3'), + description: 'MP3 128 kbps · WEBFIVE · Zazawowow · 4:09 · published on Wavlake.' }, + { id: 'song-michael-saylor', filename: 'Zazawowow - Michael Michael Saylor.mp3', mime_type: 'audio/mpeg', size_bytes: 4_517_590, access: 'free', disk: PEER_CONTENT('music', 'Zazawowow - Michael Michael Saylor.mp3'), + description: 'MP3 128 kbps · Michael Michael Saylor (single) · Zazawowow · 3:03 · published on Wavlake.' }, { id: 'song-leaves-brown', filename: 'All the leaves are brown.mp3', mime_type: 'audio/mpeg', size_bytes: 2_936_012, access: 'free', disk: PEER_CONTENT('music', 'All the leaves are brown.mp3'), description: 'MP3 320 kbps · Archy Sessions, 2026 · folk cover, one-take living-room recording.' }, { id: 'song-exploited-substrate', filename: 'An Exploited Substrate.mp3', mime_type: 'audio/mpeg', size_bytes: 4_194_304, access: 'free', disk: PEER_CONTENT('music', 'An Exploited Substrate.mp3'), @@ -1279,29 +1287,30 @@ const PEER_LIBRARY = [ description: 'WAV 16-bit/44.1 kHz lossless master · Node Noise EP, 2025.' }, { id: 'song-bitcoin-salad', filename: 'Bitcoin Salad.MP3', mime_type: 'audio/mpeg', size_bytes: 3_984_588, access: 'free', disk: PEER_CONTENT('music', 'Bitcoin Salad.MP3'), description: 'MP3 256 kbps · Kitchen Mix Vol. 3, 2024 · novelty electro.' }, - { id: 'song-builders', filename: 'Builders, not talkers (Remastered).mp3', mime_type: 'audio/mpeg', size_bytes: 4_508_876, access: 'free', disk: PEER_CONTENT('music', 'Builders, not talkers (Remastered).mp3'), + { id: 'song-builders', filename: 'Builders, not talkers (Remastered).mp3', mime_type: 'audio/mpeg', size_bytes: 4_508_876, access: { paid: { price_sats: 100 } }, disk: PEER_CONTENT('music', 'Builders, not talkers (Remastered).mp3'), description: 'MP3 320 kbps · 2026 remaster · hip-hop instrumental.' }, - // Photography — free, the committed JPEGs themselves (preview = full image) - { id: 'photo-aurora-fjord', filename: 'aurora-over-the-fjord.jpg', mime_type: 'image/jpeg', size_bytes: 79_422, access: 'free', disk: PEER_MEDIA('photo-aurora-fjord.jpg'), - description: '800×533 · f/1.8 · 8s · ISO 1600 · Sony A7 IV 24mm · Lofoten, Norway · free license, sourced via picsum.photos.' }, - { id: 'photo-mountain-lake', filename: 'mountain-lake-still.jpg', mime_type: 'image/jpeg', size_bytes: 29_200, access: 'free', disk: PEER_MEDIA('photo-mountain-lake.jpg'), - description: '800×533 · f/8 · 1/250s · ISO 100 · Fuji X-T5 23mm · Dolomites, Italy · free license, sourced via picsum.photos.' }, - { id: 'photo-city-neon', filename: 'city-neon-rain.jpg', mime_type: 'image/jpeg', size_bytes: 59_855, access: 'free', disk: PEER_MEDIA('photo-city-neon.jpg'), - description: '800×533 · f/1.4 · 1/60s · ISO 800 · Leica Q3 28mm · Osaka, Japan · free license, sourced via picsum.photos.' }, - { id: 'photo-desert-dunes', filename: 'desert-dunes-dawn.jpg', mime_type: 'image/jpeg', size_bytes: 24_646, access: 'free', disk: PEER_MEDIA('photo-desert-dunes.jpg'), - description: '800×533 · f/11 · 1/125s · ISO 64 · Nikon Z7 II 70-200mm · Erg Chebbi, Morocco · free license, sourced via picsum.photos.' }, - { id: 'photo-forest-mist', filename: 'forest-mist-morning.jpg', mime_type: 'image/jpeg', size_bytes: 48_504, access: 'free', disk: PEER_MEDIA('photo-forest-mist.jpg'), - description: '800×533 · f/4 · 1/200s · ISO 400 · Canon R5 85mm · Black Forest, Germany · free license, sourced via picsum.photos.' }, - { id: 'photo-ocean-cliff', filename: 'ocean-cliff-long-exposure.jpg', mime_type: 'image/jpeg', size_bytes: 30_517, access: 'free', disk: PEER_MEDIA('photo-ocean-cliff.jpg'), - description: '800×533 · f/16 · 30s ND1000 · ISO 50 · Sony A7R V 16-35mm · Moher, Ireland · free license, sourced via picsum.photos.' }, - { id: 'photo-northern-road', filename: 'northern-road-solitude.jpg', mime_type: 'image/jpeg', size_bytes: 72_053, access: 'free', disk: PEER_MEDIA('photo-northern-road.jpg'), - description: '800×533 · f/5.6 · 1/500s · ISO 200 · drone DJI Mavic 3 · Iceland Ring Road · free license, sourced via picsum.photos.' }, - { id: 'photo-autumn-valley', filename: 'autumn-valley-patchwork.jpg', mime_type: 'image/jpeg', size_bytes: 51_148, access: 'free', disk: PEER_MEDIA('photo-autumn-valley.jpg'), - description: '800×533 · f/9 · 1/160s · ISO 100 · Fuji GFX 100S 110mm · Vermont, USA · free license, sourced via picsum.photos.' }, - { id: 'photo-harbor-dawn', filename: 'harbor-dawn-fishing-boats.jpg', mime_type: 'image/jpeg', size_bytes: 36_271, access: 'free', disk: PEER_MEDIA('photo-harbor-dawn.jpg'), - description: '800×533 · f/2.8 · 1/80s · ISO 320 · Leica M11 35mm · Cornwall, UK · free license, sourced via picsum.photos.' }, - { id: 'photo-alpine-ridge', filename: 'alpine-ridge-hiker.jpg', mime_type: 'image/jpeg', size_bytes: 91_938, access: 'free', disk: PEER_MEDIA('photo-alpine-ridge.jpg'), - description: '800×533 · f/7.1 · 1/320s · ISO 100 · Canon R6 II 24-70mm · Bernese Oberland, Switzerland · free license, sourced via picsum.photos.' }, + // Photography — free, the committed JPEGs themselves (preview = full image). + // All real photographs from Wikimedia Commons (free licenses, credited below). + { id: 'photo-aurora-fjord', filename: 'aurora-over-the-fjord.jpg', mime_type: 'image/jpeg', size_bytes: 482_891, access: 'free', disk: PEER_MEDIA('photo-aurora-fjord.jpg'), + description: '1920×1280 · Aurora borealis over a Lofoten fjord, snowy peaks mirrored in the sea · Norway · Wikimedia Commons, CC BY 3.0.' }, + { id: 'photo-mountain-lake', filename: 'mountain-lake-still.jpg', mime_type: 'image/jpeg', size_bytes: 680_877, access: 'free', disk: PEER_MEDIA('photo-mountain-lake.jpg'), + description: '1920×1474 · Lago di Limides at sunset, Dolomite spires reflected in still water · Italy · Wikimedia Commons, CC BY 4.0.' }, + { id: 'photo-city-neon', filename: 'city-neon-rain.jpg', mime_type: 'image/jpeg', size_bytes: 1_173_378, access: 'free', disk: PEER_MEDIA('photo-city-neon.jpg'), + description: '1920×2349 · Dotonbori canal at night, neon signs bleeding into the water · Osaka, Japan · Wikimedia Commons, CC BY-SA 4.0.' }, + { id: 'photo-desert-dunes', filename: 'desert-dunes-dawn.jpg', mime_type: 'image/jpeg', size_bytes: 489_931, access: 'free', disk: PEER_MEDIA('photo-desert-dunes.jpg'), + description: '1920×1281 · Low sun raking across rippled sand, Erg Chebbi · Morocco · Wikimedia Commons, CC BY-SA 4.0.' }, + { id: 'photo-forest-mist', filename: 'forest-mist-morning.jpg', mime_type: 'image/jpeg', size_bytes: 423_000, access: 'free', disk: PEER_MEDIA('photo-forest-mist.jpg'), + description: '1920×1204 · Sunrise mist in the valleys above Schonach, Black Forest · Germany · Wikimedia Commons, Free Art License.' }, + { id: 'photo-ocean-cliff', filename: 'ocean-cliff-atlantic.jpg', mime_type: 'image/jpeg', size_bytes: 683_730, access: 'free', disk: PEER_MEDIA('photo-ocean-cliff.jpg'), + description: '1920×1440 · Atlantic swell breaking under the Cliffs of Moher · Ireland · Wikimedia Commons, CC BY 2.0.' }, + { id: 'photo-northern-road', filename: 'northern-road-solitude.jpg', mime_type: 'image/jpeg', size_bytes: 643_159, access: 'free', disk: PEER_MEDIA('photo-northern-road.jpg'), + description: '1920×1280 · Empty winter road toward a backlit peak · Iceland · Wikimedia Commons, CC0.' }, + { id: 'photo-autumn-valley', filename: 'autumn-valley-patchwork.jpg', mime_type: 'image/jpeg', size_bytes: 872_237, access: 'free', disk: PEER_MEDIA('photo-autumn-valley.jpg'), + description: '1920×1205 · White steeple against peak fall foliage, Stowe · Vermont, USA · Wikimedia Commons, CC BY 2.0.' }, + { id: 'photo-harbor-dawn', filename: 'harbor-golden-hour.jpg', mime_type: 'image/jpeg', size_bytes: 655_311, access: 'free', disk: PEER_MEDIA('photo-harbor-dawn.jpg'), + description: '1920×1080 · St Ives harbour wall at low tide, golden evening light · Cornwall, UK · Wikimedia Commons, CC BY-SA 4.0.' }, + { id: 'photo-alpine-ridge', filename: 'alpine-ridge-hiker.jpg', mime_type: 'image/jpeg', size_bytes: 642_889, access: 'free', disk: PEER_MEDIA('photo-alpine-ridge.jpg'), + description: '1920×1280 · Hiker crossing a snowfield above Grindelwald, Bernese Oberland · Switzerland · Wikimedia Commons, CC BY 2.0.' }, // Documents — free, real committed files { id: 'doc-whitepaper-notes', filename: 'bitcoin-whitepaper-notes.md', mime_type: 'text/markdown', size_bytes: 8_192, access: 'free', disk: PEER_CONTENT('documents', 'bitcoin-whitepaper-notes.md'), description: 'Markdown study notes on the Bitcoin whitepaper, section by section.' }, @@ -1316,15 +1325,28 @@ const PEER_LIBRARY = [ description: 'Archipelago Node OS installer ISO · amd64 · SHA256 in release notes.' }, ] -/** Deterministic per-peer slice of the library — every peer shares a different - * handful, popular items appear on more than one node (it's a network). */ +/** Deterministic per-peer slice of the library. Each item lives on exactly ONE + * peer (slot = library index % 12, matched to the peer's position in this + * session's federation list), except a small POPULAR set that also appears on + * 1-2 more nodes — a little duplication is realistic, a catalog where every + * file exists on a third of the network is not. */ +const POPULAR_EXTRA_SLOTS = { + 'song-webfive': [3], // the showcase paid Wavlake track — easy to find + 'film-block-height': [5], + 'photo-aurora-fjord': [8], +} function peerCatalogFor(onion) { - let seed = 0 - for (const c of String(onion)) seed = (seed * 31 + c.charCodeAt(0)) >>> 0 - const slot = seed % 12 + const nodes = sessionFederationNodes() + let slot = nodes.findIndex(n => n.onion === onion) + if (slot < 0) { + // Unknown onion (e.g. a stale deep-link) — stable hash fallback. + let seed = 0 + for (const c of String(onion)) seed = (seed * 31 + c.charCodeAt(0)) >>> 0 + slot = seed % 12 + } return PEER_LIBRARY .map((item, i) => ({ item, i })) - .filter(({ i }) => i % 12 === slot || ((seed ^ (i * 2654435761)) >>> 0) % 12 < 3) + .filter(({ item, i }) => i % 12 === slot || (POPULAR_EXTRA_SLOTS[item.id] || []).includes(slot)) .map(({ item }) => ({ id: item.id, filename: item.filename, @@ -1335,7 +1357,50 @@ function peerCatalogFor(onion) { })) } -// 12 trusted/federated nodes for the demo Federation view. +/** Purchase history ("Paid Files" tab + Owned badges). Lazily seeded per + * session with a few plausible past purchases whose onions/content ids match + * THIS session's federation nodes and their catalog slices (onions are random + * per session, so this must be computed, never hardcoded). Every successful + * purchase path appends here so Owned state survives loadOwned() refreshes. */ +function sessionOwnedContent() { + if (!mockState.ownedContent) { + const nodes = sessionFederationNodes() + const seeds = [] + const wanted = [ + // At least one audio item with real disk bytes: the Paid Files tab click + // must actually play music in the bottom bar. + { id: 'song-builders', daysAgo: 4 }, + { id: 'film-the-signal', daysAgo: 11 }, + { id: 'book-cypherpunk-essays', daysAgo: 23 }, + ] + for (const w of wanted) { + for (const n of nodes) { + const item = peerCatalogFor(n.onion).find(it => it.id === w.id) + if (!item) continue + seeds.push({ + onion: n.onion, + content_id: item.id, + filename: item.filename, + mime_type: item.mime_type, + size_bytes: item.size_bytes, + paid_sats: (typeof item.access === 'object' && item.access.paid?.price_sats) || 0, + purchased_at: new Date(Date.now() - w.daysAgo * 86400000).toISOString(), + }) + break + } + } + mockState.ownedContent = seeds + } + return mockState.ownedContent +} + +// 12 trusted/federated nodes for the demo Federation view. Onions are random, +// so the list is memoised per session — peer catalogs, owned-content records +// and the Federation view must all agree on the same onions for a visitor. +function sessionFederationNodes() { + if (!mockState.federationNodes) mockState.federationNodes = demoFederationNodes() + return mockState.federationNodes +} function demoFederationNodes() { const names = [ ['archy-198', 'trusted', ['bitcoin-knots', 'lnd', 'mempool', 'electrs']], @@ -1516,6 +1581,19 @@ app.post('/api/blob', express.raw({ type: () => true, limit: '25mb' }), (req, re } res.json({ cid }) }) +// Free peer-content streaming (Range-capable). The frontend streams free +// audio/video and the free-image lightbox from this URL; on a real node nginx +// proxies /api/* to the daemon. content_id resolves strictly against +// PEER_LIBRARY (whitelist — never a filesystem path built from the request), +// and paid items 403 so full paid bytes are only reachable via a purchase. +app.get('/api/peer-content/:onion/:content_id', (req, res) => { + const entry = PEER_LIBRARY.find(it => it.id === req.params.content_id) + if (!entry || !entry.disk) return res.status(404).json({ error: 'Content not found on this peer' }) + if (typeof entry.access === 'object') return res.status(403).json({ error: 'This file requires purchase' }) + res.type(entry.mime_type) + // Express sendFile handles Range requests (the frontend probes bytes=0-0). + res.sendFile(entry.disk) +}) app.get('/api/container/logs', (_req, res) => res.json({ logs: [ '[INF] LND: Version 0.18.3-beta commit=v0.18.3-beta', @@ -2356,9 +2434,20 @@ app.post('/rpc/v1', (req, res) => { } case 'content.preview-peer': { + const entry = PEER_LIBRARY.find(it => it.id === params?.content_id) + // Audio preview: the frontend's Preview button PLAYS these bytes (the + // real backend proxies the seller's audio preview too), so serve a + // leading slice of the actual track — mp3 frames tolerate truncation — + // never an image. ~10% matches the "10% preview" badge. + if (entry?.mime_type?.startsWith('audio/') && entry.disk) { + try { + const data = fsSync.readFileSync(entry.disk) + const slice = data.subarray(0, Math.max(96 * 1024, Math.floor(data.length / 10))) + return res.json({ result: { data: slice.toString('base64'), content_type: entry.mime_type } }) + } catch { /* fall through to no preview (icon fallback) */ } + } // Serve the item's committed preview image (film poster, book cover, or // the photo itself) so the demo NEVER renders a broken thumbnail. - const entry = PEER_LIBRARY.find(it => it.id === params?.content_id) const previewAbs = entry && (entry.preview || (entry.mime_type.startsWith('image/') && entry.disk)) if (previewAbs) { try { @@ -2400,12 +2489,16 @@ app.post('/rpc/v1', (req, res) => { // Every payment path resolves to success so visitors can experience the // full buy → unlock journey with testnet addresses and invoices. case 'content.owned-list': { - return res.json({ result: { items: [] } }) + return res.json({ result: { items: sessionOwnedContent() } }) } case 'content.owned-get': case 'content.download-peer-paid': case 'content.download-peer-invoice': case 'content.download-peer-onchain': { + const entry = PEER_LIBRARY.find(it => it.id === params?.content_id) + const price = params?.price_sats + || (entry && typeof entry.access === 'object' && entry.access.paid?.price_sats) + || 0 // Deduct the price from the chosen rail so demo balances react. const paid = params?.price_sats || 0 if (paid > 0 && method === 'content.download-peer-paid') { @@ -2415,7 +2508,33 @@ app.post('/rpc/v1', (req, res) => { if (fed) fed.balance_sats = Math.max(0, (fed.balance_sats || 0) - paid) } else walletState.ecash_sats = Math.max(0, walletState.ecash_sats - paid) } - const filename = params?.filename || 'demo-content' + // Record the purchase (not owned-get — that's re-opening a past buy) so + // it persists in owned-list: the frontend refreshes Owned state right + // after paying, and the Paid Files tab shows it with price + date. + if (method !== 'content.owned-get' && entry) { + const owned = sessionOwnedContent() + if (!owned.some(o => o.content_id === entry.id && o.onion === params?.onion)) { + owned.push({ + onion: params?.onion || '', + content_id: entry.id, + filename: entry.filename, + mime_type: entry.mime_type, + size_bytes: entry.size_bytes, + paid_sats: price, + purchased_at: new Date().toISOString(), + }) + } + } + // Real committed bytes when we have them (music, photos, docs) — this + // is what lets a just-bought track autoplay in the bottom bar. + if (entry?.disk) { + try { + const data = fsSync.readFileSync(entry.disk) + return res.json({ result: { data: data.toString('base64'), mime_type: entry.mime_type, ecash_backend: params?.method || 'cashu' } }) + } catch { /* fall through to placeholder */ } + } + // Fictional catalog entries (2GB films etc.) keep the placeholder note. + const filename = params?.filename || entry?.filename || 'demo-content' const body = Buffer.from( `Archipelago demo — "${filename}"\n\nThis is sample paid content delivered over the ` + `node-to-node content market. On a real node this would be the actual file you purchased.\n`, @@ -2900,7 +3019,7 @@ app.post('/rpc/v1', (req, res) => { // ===================================================================== case 'federation.list': case 'federation.list-nodes': { - return res.json({ result: { nodes: demoFederationNodes() } }) + return res.json({ result: { nodes: sessionFederationNodes() } }) } case 'federation.invite': { diff --git a/neode-ui/vite.config.ts b/neode-ui/vite.config.ts index 3606f9fa..e8a8f0dc 100644 --- a/neode-ui/vite.config.ts +++ b/neode-ui/vite.config.ts @@ -143,6 +143,13 @@ export default defineConfig({ changeOrigin: true, secure: false, }, + // Peer-content streaming, blob upload, app catalog — the real node's + // nginx proxies /api/* to the daemon; dev forwards it to the mock. + '/api': { + target: process.env.BACKEND_URL || 'http://localhost:5959', + changeOrigin: true, + secure: false, + }, // Mock filebrowser (Cloud page) — same backend as everything else. Point // BACKEND_URL at a real node to develop against its filebrowser instead. '/app/filebrowser': {