/* MeshBay Web Client — Phase 4 * Communicates with: Hub API (auth, groups) + Node HTTP API (files, streaming) * Requires: hub at same origin, node at configured URL */ const HUB = ''; // same origin — hub serves this file // ── State ───────────────────────────────────────────────────────────────────── let state = { token: localStorage.getItem('mb_token') || null, refreshToken: localStorage.getItem('mb_rt') || null, username: localStorage.getItem('mb_user') || null, nodeUrl: localStorage.getItem('mb_node') || null, }; // ── Hub API helpers ─────────────────────────────────────────────────────────── async function hubGet(path) { const headers = state.token ? { Authorization: `Bearer ${state.token}` } : {}; const r = await fetch(HUB + path, { headers }); if (!r.ok) throw new Error(`${r.status} ${await r.text()}`); return r.json(); } async function hubPost(path, body) { const headers = { 'Content-Type': 'application/json', ...(state.token ? { Authorization: `Bearer ${state.token}` } : {}), }; const r = await fetch(HUB + path, { method: 'POST', headers, body: JSON.stringify(body) }); if (!r.ok) throw new Error(`${r.status} ${await r.text()}`); return r.json(); } // ── Auth ────────────────────────────────────────────────────────────────────── async function login(username, password) { const data = await hubPost('/v1/users/login', { username, password }); state.token = data.access_token; state.refreshToken = data.refresh_token; state.username = username; localStorage.setItem('mb_token', state.token); localStorage.setItem('mb_rt', state.refreshToken); localStorage.setItem('mb_user', username); return data; } async function register(username, email, password, pkEd, pkX) { return hubPost('/v1/users/register', { username, email, password, pk_user_ed25519: pkEd, pk_user_x25519: pkX, }); } function logout() { state = { token: null, refreshToken: null, username: null, nodeUrl: null }; localStorage.clear(); render(); } // ── Node API helpers ────────────────────────────────────────────────────────── async function nodeGet(path) { if (!state.nodeUrl) throw new Error('No node configured'); const sep = path.includes('?') ? '&' : '?'; const url = state.nodeUrl + path + (state.token ? `${sep}token=${state.token}` : ''); const r = await fetch(url); if (!r.ok) throw new Error(`Node ${r.status}`); return r.json(); } // ── Pages ───────────────────────────────────────────────────────────────────── async function pageHome() { const groups = await hubGet('/v1/groups'); const items = groups.groups.map(g => `
No public groups yet.
'} ${state.nodeUrl ? `Group ${groupId} — coming soon
`; } async function pageNodeBrowser() { const data = await nodeGet('/index'); const rows = data.entries.map(e => `${data.entries.length} files — index v${data.version}
| Name | Type | Size | Actions |
|---|
Loading…
'); setMain(await pageHome()); } else { nav.innerHTML = 'MeshBay'; setMain(`No account? Register via the API for now.
`); } } // ── Utils ───────────────────────────────────────────────────────────────────── function esc(s) { return String(s).replace(/&/g,'&').replace(//g,'>') .replace(/"/g,'"').replace(/'/g,'''); } function fmtSize(bytes) { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024**2) return (bytes/1024).toFixed(1) + ' KB'; if (bytes < 1024**3) return (bytes/1024**2).toFixed(1) + ' MB'; return (bytes/1024**3).toFixed(2) + ' GB'; } // ── Boot ────────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', render);