feat(demo): mock coverage for v1.7.117/118 features
Some checks failed
Demo images / Build & push demo images (push) Failing after 3m19s
Some checks failed
Demo images / Build & push demo images (push) Failing after 3m19s
- mesh.transport-advice: peer 1 federated (LoRa/FIPS/Tor pills in the image modal), others radio-only. - Scripted Flash LoRa job: flash-list-firmware + flash-device + flash-status advancing download → erase → write → done over ~35s with live log tail and percent, plus cancel. - Demo messages carry per-message transport (meshcore/reticulum/fips/ tor variety for the pills + animated route modal) and sender_pubkey/sender_seq so reactions/replies work. - Chat-action acks: send-reaction/reply/read-receipt, edit/delete/ forward, send-channel, mesh.refresh, reboot-radio. - Services classification demo: self-deployed "podsteadr" stack — main app launchable, its MediaMTX backend (ui:null) files under Services with no Launch button, mirroring ui_detection's verdicts. All verified against the running mock: 11/11 runtime checks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
14feb1feb9
commit
bea7f24a4f
@ -777,7 +777,7 @@ const SEED_MOCKDATA = {
|
||||
}
|
||||
|
||||
// Helper to build a static app entry
|
||||
function staticApp({ id, title, version, shortDesc, longDesc, license, state, lanPort, torHost, icon }) {
|
||||
function staticApp({ id, title, version, shortDesc, longDesc, license, state, lanPort, torHost, icon, noUi }) {
|
||||
return {
|
||||
title,
|
||||
version,
|
||||
@ -800,8 +800,13 @@ function staticApp({ id, title, version, shortDesc, longDesc, license, state, la
|
||||
'support-site': '#',
|
||||
'marketing-site': '#',
|
||||
'donation-url': null,
|
||||
// ui:null mirrors the real backend's ui_detection verdict for headless
|
||||
// services (databases/APIs/workers): address present, no Launch button,
|
||||
// files under the Services tab.
|
||||
interfaces: {
|
||||
main: { name: 'Web Interface', description: `${title} web interface`, ui: true },
|
||||
main: noUi
|
||||
? { name: 'Service', description: `${title} backend service`, ui: null }
|
||||
: { name: 'Web Interface', description: `${title} web interface`, ui: true },
|
||||
},
|
||||
},
|
||||
installed: {
|
||||
@ -891,6 +896,28 @@ const staticDevApps = {
|
||||
lanPort: 4403,
|
||||
icon: '/assets/img/app-icons/meshcore.svg',
|
||||
}),
|
||||
// Self-deployed compose stack (v1.7.117+ services classification demo):
|
||||
// the main app serves a real web page → launchable; its media backend has
|
||||
// an exposed port but no UI → Services tab, no Launch button.
|
||||
podsteadr: staticApp({
|
||||
id: 'podsteadr',
|
||||
title: 'Podsteadr',
|
||||
version: '0.4.1',
|
||||
shortDesc: 'Self-hosted podcast studio',
|
||||
longDesc: 'Record, host, and publish podcasts from your own node — deployed by hand outside the app store to demo third-party stacks.',
|
||||
state: 'running',
|
||||
lanPort: 8095,
|
||||
}),
|
||||
'podsteadr-mediamtx': staticApp({
|
||||
id: 'podsteadr-mediamtx',
|
||||
title: 'Podsteadr MediaMTX',
|
||||
version: '1.9.3',
|
||||
shortDesc: 'RTMP/WebRTC media backend',
|
||||
longDesc: 'Streaming backend for Podsteadr. Speaks RTMP and WebRTC on exposed ports — no web page of its own.',
|
||||
state: 'running',
|
||||
lanPort: 8889,
|
||||
noUi: true,
|
||||
}),
|
||||
filebrowser: staticApp({
|
||||
id: 'filebrowser',
|
||||
title: 'File Browser',
|
||||
@ -3030,18 +3057,18 @@ app.post('/rpc/v1', (req, res) => {
|
||||
const limit = params?.limit || 100
|
||||
const now = Date.now()
|
||||
const allMessages = [
|
||||
{ id: 1, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Node online. Bitcoin Knots synced to tip.', timestamp: new Date(now - 3600000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
|
||||
{ id: 2, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Good. Electrs index at 98%. Channel capacity 2.5M sats.', timestamp: new Date(now - 3540000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
|
||||
{ id: 3, direction: 'received', peer_contact_id: 2, peer_name: 'satoshi-relay', plaintext: 'Block #890,413 relayed. Fees avg 12 sat/vB.', timestamp: new Date(now - 3000000).toISOString(), delivered: true, encrypted: true, message_type: 'block_header', typed_payload: { alert_type: 'block_header', message: 'Block #890,413 — 2,847 txs, 12 sat/vB avg fee', signed: true } },
|
||||
{ id: 4, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice: 50,000 sats — Channel opening fee', timestamp: new Date(now - 1800000).toISOString(), delivered: true, encrypted: true, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: false } },
|
||||
{ id: 5, direction: 'sent', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Running mesh-only mode. No internet for 48h. All good.', timestamp: new Date(now - 900000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
|
||||
{ id: 6, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Copy. Block height 890,412 via compact headers.', timestamp: new Date(now - 840000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
|
||||
{ id: 7, direction: 'received', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'EMERGENCY: Solar array failure. Running on battery reserve.', timestamp: new Date(now - 600000).toISOString(), delivered: true, encrypted: false, message_type: 'alert', typed_payload: { alert_type: 'emergency', message: 'Solar array failure. Running on battery reserve. ETA 4h before shutdown.', coordinate: { lat: 39507400, lng: -106042800, label: 'Mountain relay site' }, signed: true } },
|
||||
{ id: 8, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Opening 1M sat channel to your node. Approve?', timestamp: new Date(now - 300000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
|
||||
{ id: 9, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Approved. Waiting for funding tx confirmation.', timestamp: new Date(now - 240000).toISOString(), delivered: true, encrypted: true, message_type: 'text' },
|
||||
{ id: 10, direction: 'sent', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'Location shared', timestamp: new Date(now - 120000).toISOString(), delivered: true, encrypted: false, message_type: 'coordinate', typed_payload: { lat: 30267200, lng: -97743100, label: 'Supply drop point' } },
|
||||
{ id: 11, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Dead man switch check-in. All systems nominal. Battery 78%.', timestamp: new Date(now - 60000).toISOString(), delivered: true, encrypted: true, message_type: 'alert', typed_payload: { alert_type: 'status', message: 'All systems nominal. Battery 78%. Mesh uptime 14d.', signed: true } },
|
||||
{ id: 12, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice paid: 50,000 sats', timestamp: new Date(now - 30000).toISOString(), delivered: true, encrypted: true, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: true, payment_hash: 'a1b2c3d4e5f6...' } },
|
||||
{ id: 1, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Node online. Bitcoin Knots synced to tip.', timestamp: new Date(now - 3600000).toISOString(), delivered: true, encrypted: true, transport: 'meshcore', sender_pubkey: 'demo01abababababababababababababababababababababababababababab', sender_seq: 1, message_type: 'text' },
|
||||
{ id: 2, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Good. Electrs index at 98%. Channel capacity 2.5M sats.', timestamp: new Date(now - 3540000).toISOString(), delivered: true, encrypted: true, transport: 'meshcore', sender_pubkey: 'demo02abababababababababababababababababababababababababababab', sender_seq: 2, message_type: 'text' },
|
||||
{ id: 3, direction: 'received', peer_contact_id: 2, peer_name: 'satoshi-relay', plaintext: 'Block #890,413 relayed. Fees avg 12 sat/vB.', timestamp: new Date(now - 3000000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo03abababababababababababababababababababababababababababab', sender_seq: 3, message_type: 'block_header', typed_payload: { alert_type: 'block_header', message: 'Block #890,413 — 2,847 txs, 12 sat/vB avg fee', signed: true } },
|
||||
{ id: 4, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice: 50,000 sats — Channel opening fee', timestamp: new Date(now - 1800000).toISOString(), delivered: true, encrypted: true, transport: 'fips', sender_pubkey: 'demo04abababababababababababababababababababababababababababab', sender_seq: 4, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: false } },
|
||||
{ id: 5, direction: 'sent', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Running mesh-only mode. No internet for 48h. All good.', timestamp: new Date(now - 900000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo05abababababababababababababababababababababababababababab', sender_seq: 5, message_type: 'text' },
|
||||
{ id: 6, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Copy. Block height 890,412 via compact headers.', timestamp: new Date(now - 840000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo06abababababababababababababababababababababababababababab', sender_seq: 6, message_type: 'text' },
|
||||
{ id: 7, direction: 'received', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'EMERGENCY: Solar array failure. Running on battery reserve.', timestamp: new Date(now - 600000).toISOString(), delivered: true, encrypted: false, transport: 'meshcore', sender_pubkey: 'demo07abababababababababababababababababababababababababababab', sender_seq: 7, message_type: 'alert', typed_payload: { alert_type: 'emergency', message: 'Solar array failure. Running on battery reserve. ETA 4h before shutdown.', coordinate: { lat: 39507400, lng: -106042800, label: 'Mountain relay site' }, signed: true } },
|
||||
{ id: 8, direction: 'sent', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Opening 1M sat channel to your node. Approve?', timestamp: new Date(now - 300000).toISOString(), delivered: true, encrypted: true, transport: 'fips', sender_pubkey: 'demo08abababababababababababababababababababababababababababab', sender_seq: 8, message_type: 'text' },
|
||||
{ id: 9, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Approved. Waiting for funding tx confirmation.', timestamp: new Date(now - 240000).toISOString(), delivered: true, encrypted: true, transport: 'tor', sender_pubkey: 'demo09abababababababababababababababababababababababababababab', sender_seq: 9, message_type: 'text' },
|
||||
{ id: 10, direction: 'sent', peer_contact_id: 3, peer_name: 'mountain-node', plaintext: 'Location shared', timestamp: new Date(now - 120000).toISOString(), delivered: true, encrypted: false, transport: 'meshcore', sender_pubkey: 'demo10abababababababababababababababababababababababababababab', sender_seq: 10, message_type: 'coordinate', typed_payload: { lat: 30267200, lng: -97743100, label: 'Supply drop point' } },
|
||||
{ id: 11, direction: 'received', peer_contact_id: 4, peer_name: 'bunker-alpha', plaintext: 'Dead man switch check-in. All systems nominal. Battery 78%.', timestamp: new Date(now - 60000).toISOString(), delivered: true, encrypted: true, transport: 'reticulum', sender_pubkey: 'demo11abababababababababababababababababababababababababababab', sender_seq: 11, message_type: 'alert', typed_payload: { alert_type: 'status', message: 'All systems nominal. Battery 78%. Mesh uptime 14d.', signed: true } },
|
||||
{ id: 12, direction: 'received', peer_contact_id: 1, peer_name: 'archy-198', plaintext: 'Invoice paid: 50,000 sats', timestamp: new Date(now - 30000).toISOString(), delivered: true, encrypted: true, transport: 'fips', sender_pubkey: 'demo12abababababababababababababababababababababababababababab', sender_seq: 12, message_type: 'invoice', typed_payload: { bolt11: 'lnbc500000n1pjmesh...truncated...', amount_sats: 50000, memo: 'Channel opening fee', paid: true, payment_hash: 'a1b2c3d4e5f6...' } },
|
||||
]
|
||||
return res.json({
|
||||
result: {
|
||||
@ -4243,6 +4270,100 @@ app.post('/rpc/v1', (req, res) => {
|
||||
return res.json({ result: { rejected: true, id: params?.id || '' } })
|
||||
}
|
||||
|
||||
// ── Mesh chat polish + flash flow (v1.7.117/118 demo coverage) ──────
|
||||
|
||||
// Image-modal "Send via" pills: peer 1 is federation-reachable (LoRa +
|
||||
// FIPS + Tor all offered), everyone else is radio-only.
|
||||
case 'mesh.transport-advice': {
|
||||
const federated = params?.contact_id === 1
|
||||
return res.json({
|
||||
result: {
|
||||
tier: federated ? 'choose' : 'auto-mesh',
|
||||
est_seconds: federated ? 4 : 90,
|
||||
has_tor: federated,
|
||||
has_fips: federated,
|
||||
last_transport: federated ? 'fips' : null,
|
||||
reason: federated
|
||||
? 'Peer reachable over the FIPS overlay — radio optional'
|
||||
: 'Radio-only peer',
|
||||
size: params?.size || 0,
|
||||
mesh_auto_max: 24576,
|
||||
mesh_hard_max: 262144,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
case 'mesh.flash-list-firmware': {
|
||||
const versions = {
|
||||
meshcore: ['v1.7.4', 'v1.7.2', 'v1.6.9'],
|
||||
meshtastic: ['2.5.15', '2.5.13', '2.4.2'],
|
||||
reticulum: ['latest (rnodeconf autoinstall)'],
|
||||
}
|
||||
return res.json({ result: { versions: versions[params?.family] || [] } })
|
||||
}
|
||||
case 'mesh.flash-device': {
|
||||
mockState.flashJob = {
|
||||
startedAt: Date.now(),
|
||||
board: params?.board || 'heltec-v3',
|
||||
family: params?.family || 'meshcore',
|
||||
path: params?.path || '/dev/ttyUSB0',
|
||||
cancelled: false,
|
||||
}
|
||||
return res.json({ result: { started: true } })
|
||||
}
|
||||
case 'mesh.flash-status': {
|
||||
const job = mockState.flashJob
|
||||
if (!job) return res.json({ result: { active: false, done: true, log_tail: [] } })
|
||||
const t = (Date.now() - job.startedAt) / 1000
|
||||
// Scripted 35s job: download → erase → write → done.
|
||||
let stage = 'downloading'
|
||||
let percent = Math.min(100, Math.round((t / 8) * 100))
|
||||
if (job.cancelled) stage = 'failed'
|
||||
else if (t > 30) { stage = 'done'; percent = 100 }
|
||||
else if (t > 14) { stage = 'writing'; percent = Math.min(100, Math.round(((t - 14) / 16) * 100)) }
|
||||
else if (t > 8) { stage = 'erasing'; percent = null }
|
||||
const log = [
|
||||
`Fetching ${job.family} firmware for ${job.board}...`,
|
||||
...(t > 4 ? ['Downloaded 2.1 MB firmware image'] : []),
|
||||
...(t > 8 ? ['esptool erase_flash: chip erase completed'] : []),
|
||||
...(t > 14 ? [`Writing at 0x00010000... (${percent || 0} %)`] : []),
|
||||
...(stage === 'done' ? ['Hash of data verified.', 'Hard resetting via RTS pin...'] : []),
|
||||
...(job.cancelled ? ['ERROR: flash cancelled by user'] : []),
|
||||
]
|
||||
const active = stage !== 'done' && stage !== 'failed'
|
||||
if (!active) mockState.flashJob = active ? job : job // keep for final poll
|
||||
return res.json({
|
||||
result: {
|
||||
active,
|
||||
board: job.board,
|
||||
family: job.family,
|
||||
path: job.path,
|
||||
stage,
|
||||
percent,
|
||||
log_tail: log,
|
||||
done: !active,
|
||||
error: job.cancelled ? 'Flash cancelled' : null,
|
||||
},
|
||||
})
|
||||
}
|
||||
case 'mesh.flash-cancel': {
|
||||
if (mockState.flashJob) mockState.flashJob.cancelled = true
|
||||
return res.json({ result: { cancelled: true } })
|
||||
}
|
||||
|
||||
// Chat actions the demo only needs to acknowledge.
|
||||
case 'mesh.send-reaction':
|
||||
case 'mesh.send-reply':
|
||||
case 'mesh.send-read-receipt':
|
||||
case 'mesh.edit-message':
|
||||
case 'mesh.delete-message':
|
||||
case 'mesh.forward-message':
|
||||
case 'mesh.send-channel':
|
||||
case 'mesh.refresh':
|
||||
case 'mesh.reboot-radio': {
|
||||
return res.json({ result: { ok: true, sent: true } })
|
||||
}
|
||||
|
||||
default: {
|
||||
console.log(`[RPC] Unknown method: ${method}`)
|
||||
return res.json({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user