diff --git a/neode-ui/Dockerfile.backend b/neode-ui/Dockerfile.backend index 94e3f487..05f69ce0 100644 --- a/neode-ui/Dockerfile.backend +++ b/neode-ui/Dockerfile.backend @@ -23,6 +23,10 @@ COPY docker/lnd-ui /docker/lnd-ui COPY docker/fedimint-ui /docker/fedimint-ui COPY demo/files /demo/files COPY demo/content /demo/content +# Peer catalog media (posters/covers/photos for content.browse-peer mocks) — +# deliberately OUTSIDE demo/content so it doesn't appear as the visitor's own +# cloud files. +COPY demo/peer-media /demo/peer-media # This image only ever serves the public demo — scrub the private release/registry # server address from everything it serves (mock data, catalog.json, demo assets) diff --git a/neode-ui/mock-backend.js b/neode-ui/mock-backend.js index b0882bf6..a24e0abb 100755 --- a/neode-ui/mock-backend.js +++ b/neode-ui/mock-backend.js @@ -1201,6 +1201,97 @@ function demoAppShell(title, sub, iconPath, bodyHtml) {
Archipelago demo · signet
` } +// ── Rich peer content library (demo) ───────────────────────────────────────── +// What a healthy decentralised network's shared catalog looks like: films, +// 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). +const PEER_MEDIA = (f) => path.join(__dirname, '..', 'demo', 'peer-media', f) +const PEER_CONTENT = (...p) => path.join(__dirname, '..', 'demo', 'content', ...p) +const PEER_LIBRARY = [ + // Films & series — paid, poster previews + { id: 'film-block-height', filename: 'Block Height (2025) 2160p.mp4', mime_type: 'video/mp4', size_bytes: 4_512_374_784, access: { paid: { price_sats: 2500 } }, preview: PEER_MEDIA('poster-film-block.jpg'), + description: 'Documentary · 2025 · 1h 42m · 3840×2160 HEVC 10-bit · dir. M. Castillo — three node operators ride out a halving epoch.' }, + { id: 'film-the-signal', filename: 'The Signal (2024) 1080p.mkv', mime_type: 'video/x-matroska', size_bytes: 2_147_483_648, access: { paid: { price_sats: 2100 } }, preview: PEER_MEDIA('poster-film-signal.jpg'), + description: 'Thriller · 2024 · 2h 08m · 1920×1080 x264 · A radio engineer discovers her mesh network is carrying more than messages.' }, + { id: 'film-meshwork', filename: 'Meshwork — infrastructure for the willing (720p).mp4', mime_type: 'video/mp4', size_bytes: 891_289_600, access: { paid: { price_sats: 800 } }, preview: PEER_MEDIA('poster-film-mesh.jpg'), + description: 'Documentary short · 2026 · 34m · 1280×720 · Community LoRa + Reticulum builds across the highlands.' }, + { id: 'series-node-runners-s01e01', filename: 'Node Runners S01E01 — Genesis.mkv', mime_type: 'video/x-matroska', size_bytes: 734_003_200, access: { paid: { price_sats: 500 } }, preview: PEER_MEDIA('poster-series-node.jpg'), + description: 'Series · S01E01 · 43m · 1080p · Docuseries following first-time sovereign node builders.' }, + // Books — paid, cover previews + { id: 'book-sovereign-notes', filename: 'The Sovereign Individual — annotated.epub', mime_type: 'application/epub+zip', size_bytes: 3_842_048, access: { paid: { price_sats: 300 } }, preview: PEER_MEDIA('cover-book-sovereign.jpg'), + description: 'EPUB · 512 pp · community margin-notes edition, 2026 · Davidson & Rees-Mogg.' }, + { id: 'book-cypherpunk-essays', filename: 'Cypherpunk Essays Vol. II.epub', mime_type: 'application/epub+zip', size_bytes: 2_097_152, access: { paid: { price_sats: 210 } }, preview: PEER_MEDIA('cover-book-cypher.jpg'), + 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 + { 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'), + description: 'MP3 320 kbps · Node Noise EP, 2025 · instrumental synthwave.' }, + { id: 'song-architects', filename: 'Architects of Tomorrow.wav', mime_type: 'audio/wav', size_bytes: 34_734_080, access: 'free', disk: PEER_CONTENT('music', 'Architects of Tomorrow.wav'), + 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'), + 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.' }, + // 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.' }, + { id: 'doc-sovereignty-manifesto', filename: 'sovereignty-manifesto.txt', mime_type: 'text/plain', size_bytes: 4_096, access: 'free', disk: PEER_CONTENT('documents', 'sovereignty-manifesto.txt'), + description: 'Plain-text manifesto on self-hosted infrastructure.' }, + { id: 'doc-node-checklist', filename: 'node-setup-checklist.md', mime_type: 'text/markdown', size_bytes: 6_144, access: 'free', disk: PEER_CONTENT('documents', 'node-setup-checklist.md'), + description: 'Step-by-step hardening checklist for a fresh Archipelago node.' }, + { id: 'doc-lightning-csv', filename: 'lightning-channels.csv', mime_type: 'text/csv', size_bytes: 2_048, access: 'free', disk: PEER_CONTENT('documents', 'lightning-channels.csv'), + description: 'CSV export — public channel set with capacities and fee policies.' }, + // Software + { id: 'sw-archy-iso', filename: 'archipelago-1.7.99-amd64.iso', mime_type: 'application/x-iso9660-image', size_bytes: 2_684_354_560, access: 'free', + 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). */ +function peerCatalogFor(onion) { + let seed = 0 + for (const c of String(onion)) seed = (seed * 31 + c.charCodeAt(0)) >>> 0 + const slot = seed % 12 + return PEER_LIBRARY + .map((item, i) => ({ item, i })) + .filter(({ i }) => i % 12 === slot || ((seed ^ (i * 2654435761)) >>> 0) % 12 < 3) + .map(({ item }) => ({ + id: item.id, + filename: item.filename, + mime_type: item.mime_type, + size_bytes: item.size_bytes, + description: item.description, + access: item.access, + })) +} + // 12 trusted/federated nodes for the demo Federation view. function demoFederationNodes() { const names = [ @@ -2166,15 +2257,40 @@ app.post('/rpc/v1', (req, res) => { // ========================================================================= case 'content.browse-peer': { const onion = params?.onion || '' - return res.json({ - result: { - items: [ - { id: 'peer-doc-1', filename: 'Bitcoin Whitepaper.pdf', mime_type: 'application/pdf', size_bytes: 184292, description: 'The original Bitcoin whitepaper by Satoshi Nakamoto', access: 'free' }, - { id: 'peer-img-1', filename: 'node-setup-guide.png', mime_type: 'image/png', size_bytes: 524800, description: 'Visual guide for setting up a Bitcoin node', access: 'free' }, - { id: 'peer-vid-1', filename: 'Lightning Demo.mp4', mime_type: 'video/mp4', size_bytes: 15728640, description: 'Lightning Network payment channel demo', access: { paid: { price_sats: 500 } } }, - ], - }, - }) + return res.json({ result: { items: peerCatalogFor(onion) } }) + } + + case 'content.preview-peer': { + // 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 { + const data = fsSync.readFileSync(typeof previewAbs === 'string' ? previewAbs : entry.disk) + return res.json({ result: { data: data.toString('base64'), content_type: 'image/jpeg' } }) + } catch { /* fall through to no preview (icon fallback) */ } + } + return res.json({ result: {} }) + } + + case 'content.download-peer': { + // Free items backed by a real committed file stream the actual bytes — + // music plays, photos open, documents read. Anything else gets the + // demo placeholder note. + const entry = PEER_LIBRARY.find(it => it.id === params?.content_id) + if (entry?.disk) { + try { + const data = fsSync.readFileSync(entry.disk) + return res.json({ result: { data: data.toString('base64'), content_type: entry.mime_type } }) + } catch { /* fall through to placeholder */ } + } + const placeholder = Buffer.from( + `Archipelago demo — "${entry?.filename || params?.content_id || 'file'}"\n\n` + + 'This is sample shared content. On a real node this would be the actual file.\n', + 'utf-8' + ).toString('base64') + return res.json({ result: { data: placeholder, content_type: 'text/plain' } }) } case 'content.remove': { @@ -2192,7 +2308,6 @@ app.post('/rpc/v1', (req, res) => { return res.json({ result: { items: [] } }) } case 'content.owned-get': - case 'content.preview-peer': case 'content.download-peer-paid': case 'content.download-peer-invoice': case 'content.download-peer-onchain': {