Show peer availability with stale markers and add Sharing settings

This commit is contained in:
Claude
2026-09-30 07:45:13 +00:00
parent 780624347f
commit 61d0274896
8 changed files with 188 additions and 1 deletions

55
frontend/p2p-core.js Normal file
View File

@@ -0,0 +1,55 @@
/* ============================================================================
* 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);