/* ── IPFS Portal API Layer ── */ const API_BASE = ''; // nginx proxy /api/* → backend export interface IPFSNodeInfo { version: string; peerId: string; peers: number; storageUsed: string; storageMax: string; uptime: string; } export interface IPFSPin { cid: string; name: string; size: number; created: string; } export interface IPFSUser { username: string; created: string; active: boolean; } /* ── Generic fetch wrapper ── */ async function apiFetch(path: string, options?: RequestInit): Promise { const res = await fetch(`${API_BASE}/api${path}`, { headers: { 'Content-Type': 'application/json', ...options?.headers }, ...options, credentials: 'include', }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`${res.status} ${res.statusText}: ${text.slice(0, 200)}`); } return res.json(); } /* ── Node info ── */ export async function getNodeInfo(): Promise { return apiFetch('/node/info'); } export async function getPeers(): Promise<{ id: string; addr: string; latency: string }[]> { const raw = await apiFetch<{ Peers?: { Peer: string; Addr: string; Latency: string }[] }>('/peers'); return (raw.Peers || []).map((p: { Peer: string; Addr: string; Latency: string }) => ({ id: p.Peer, addr: p.Addr, latency: p.Latency || '—', })); } /* ── Storage ── */ export async function listPins(): Promise { const raw = await apiFetch<{ Keys?: Record }>('/pins'); if (!raw.Keys) return []; return Object.entries(raw.Keys).map(([cid, info]) => ({ cid, name: '', size: 0, created: info.Type || '', })); } export async function addPin(cid: string, name?: string): Promise<{ cid: string }> { return apiFetch('/pins', { method: 'POST', body: JSON.stringify({ cid, name }), }); } export async function removePin(cid: string): Promise { await apiFetch(`/pins/${cid}`, { method: 'DELETE' }); } /* ── Files ── */ export async function uploadFile(file: File): Promise<{ cid: string; size: number }> { const form = new FormData(); form.append('file', file); const res = await fetch(`${API_BASE}/api/files/upload`, { method: 'POST', body: form, credentials: 'include', }); if (!res.ok) { let errMsg = res.statusText; try { const d = await res.json(); errMsg = d.error || d.Message || errMsg; } catch {} throw new Error(`Upload failed: ${errMsg}`); } const data = await res.json(); // Kubo API returns { Hash, Size, Name }, normalize to { cid, size } return { cid: data.cid || data.Hash || data.hash || '', size: data.size || data.Size || 0, }; } export async function listFiles(): Promise<{ name: string; cid: string; size: number; modified: string }[]> { return apiFetch('/files'); } /* ── Users (admin) ── */ export async function listUsers(): Promise { const raw = await apiFetch('/users'); // Handle both [{...}] and {users: [{...}]} response formats if (Array.isArray(raw)) return raw; if (raw && Array.isArray(raw.users)) return raw.users; return []; } export async function createUser(username: string, password: string): Promise<{ username: string }> { return apiFetch('/users', { method: 'POST', body: JSON.stringify({ username, password }), }); } export async function deleteUser(username: string): Promise { await apiFetch(`/users/${encodeURIComponent(username)}`, { method: 'DELETE' }); } /* ── IPFS Explorer ── */ export interface IPFSEntry { name: string; type: 'file' | 'dir'; size: number; hash: string; } export async function explorerLs(cid: string): Promise { return apiFetch(`/explorer/ls/${encodeURIComponent(cid)}`, { method: 'POST' }); } export async function explorerCat(cid: string): Promise { const res = await fetch(`${API_BASE}/api/explorer/cat/${encodeURIComponent(cid)}`, { method: 'POST', credentials: 'include', }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(`${res.status} ${res.statusText}: ${text.slice(0, 200)}`); } return res.text(); } export async function explorerStat(cid: string): Promise> { return apiFetch(`/explorer/stat/${encodeURIComponent(cid)}`, { method: 'POST' }); } /* ── IPNS ── */ export interface IPNSKey { name: string; id: string; } export async function listIPNSKeys(): Promise { const res = await apiFetch<{ Keys: { Name?: string; Id?: string }[] }>('/ipns/keys'); if (!res.Keys) return []; return res.Keys.map((k: { Name?: string; Id?: string }) => ({ name: k.Name ?? '', id: k.Id ?? '', })); } export async function ipnsPublish(cid: string, key = 'self'): Promise<{ name: string; value: string }> { return apiFetch(`/ipns/publish?arg=${encodeURIComponent(cid)}&key=${encodeURIComponent(key)}`, { method: 'POST', }); } export async function ipnsResolve(name: string): Promise { const res = await apiFetch<{ Path: string }>(`/ipns/resolve/${encodeURIComponent(name)}`); return res.Path || ''; } export async function ipnsGenKey(name: string): Promise { return apiFetch(`/ipns/keys/gen/${encodeURIComponent(name)}`, { method: 'POST' }); } /* ── Repo / Bandwidth Stats ── */ export interface RepoStats { repoSize: number; storageMax: number; numObjects: number; repoPath: string; version: string; } export interface BWStats { totalIn: number; totalOut: number; rateIn: number; rateOut: number; } export async function getRepoStats(): Promise { const raw = await apiFetch<{ RepoSize?: number; StorageMax?: number; NumObjects?: number; RepoPath?: string; Version?: string; }>('/repo/stats'); return { repoSize: raw.RepoSize ?? 0, storageMax: raw.StorageMax ?? 0, numObjects: raw.NumObjects ?? 0, repoPath: raw.RepoPath ?? '', version: raw.Version ?? '', }; } export async function getBWStats(): Promise { const raw = await apiFetch<{ TotalIn?: number; TotalOut?: number; RateIn?: number; RateOut?: number; }>('/bw/stats'); return { totalIn: raw.TotalIn ?? 0, totalOut: raw.TotalOut ?? 0, rateIn: raw.RateIn ?? 0, rateOut: raw.RateOut ?? 0, }; } /* ── Health ── */ export async function checkHealth(): Promise<{ status: string; node: string }> { return apiFetch('/health'); }