/* ============================================================================ * p2p-core.js — pure helpers for peer-to-peer UI (window.P2PCore / node) * * formatAvailability(payload, now) turns GET /api/p2p/holders into the line * under the now-playing title, e.g. * "📡 On 3 devices · 1 online now · last checked 2d ago" * Holders are persistent (docs/p2p-architecture.md): they are never hidden * for age; when every holder is older than the server's staleDays the line * says so and is marked stale. * ========================================================================== */ (function (root) { 'use strict'; function ago(ms, now) { const s = Math.max(0, Math.round((now - ms) / 1000)); if (s < 90) return 'just now'; const m = Math.round(s / 60); if (m < 90) return m + 'm ago'; const h = Math.round(m / 60); if (h < 48) return h + 'h ago'; return Math.round(h / 24) + 'd ago'; } function formatAvailability(payload, now) { if (!payload || !payload.ok || !Array.isArray(payload.cids)) return null; const holders = []; for (const c of payload.cids) for (const h of c.holders || []) holders.push(h); if (!holders.length) return null; const online = holders.filter((h) => h.online).length; const fresh = holders.filter((h) => !h.stale).length; const newest = Math.max(...holders.map((h) => Number(h.lastVerifiedAt) || 0)); const n = holders.length; const allStale = fresh === 0; const text = `📡 On ${n} device${n === 1 ? '' : 's'} · ${online ? online + ' online now' : 'none online'}` + ` · last checked ${ago(newest, now)}` + (allStale ? ' (not checked recently)' : ''); const title = holders.map((h) => `${h.peer} · ${h.online ? 'online' : 'offline'} · checked ${ago(Number(h.lastVerifiedAt) || 0, now)}` + `${h.stale ? ' (stale)' : ''}${h.trust === 'challenged' ? ' · spot-checked' : ''}`).join('\n'); return { text, title, stale: allStale, online, holders: n }; } // Holders worth trying for a download: online first, then freshest check. function pickPeers(payload, cid) { if (!payload || !Array.isArray(payload.cids)) return []; const c = payload.cids.find((x) => x.cid === cid) || payload.cids[0]; if (!c) return []; return (c.holders || []).filter((h) => h.online) .sort((a, b) => (b.trust === 'challenged') - (a.trust === 'challenged') || b.lastVerifiedAt - a.lastVerifiedAt) .map((h) => ({ peer: h.peer, cid: c.cid, size: c.size })); } const P2PCore = { ago, formatAvailability, pickPeers }; if (typeof module !== 'undefined' && module.exports) module.exports = P2PCore; else root.P2PCore = P2PCore; })(typeof globalThis !== 'undefined' ? globalThis : this);