/** * IPFS Client — Server-side pinning and gateway access via Kubo API. * * No encryption here — backups are already encrypted by the client, * and generated files are intentionally public. */ const IPFS_API_URL = process.env.IPFS_API_URL || "http://collab-server-ipfs-1:5001"; const IPFS_GATEWAY_URL = process.env.IPFS_GATEWAY_URL || "https://ipfs.jeffemmett.com"; /** Returns true if IPFS_API_URL is configured (always true with default). */ export function isIPFSEnabled(): boolean { return !!IPFS_API_URL; } /** Public gateway URL for a CID. */ export function ipfsGatewayUrl(cid: string): string { return `${IPFS_GATEWAY_URL}/ipfs/${cid}`; } /** * Pin a blob to IPFS via Kubo's /api/v0/add endpoint. * Returns the CID (content hash). */ export async function pinToIPFS( data: Uint8Array, filename: string, ): Promise { const formData = new FormData(); formData.append( "file", new Blob([data.buffer as ArrayBuffer]), filename, ); const res = await fetch(`${IPFS_API_URL}/api/v0/add?pin=true`, { method: "POST", body: formData, }); if (!res.ok) { const err = await res.text(); throw new Error(`IPFS pin failed (${res.status}): ${err}`); } const result = (await res.json()) as { Hash: string; Size: string }; return result.Hash; } /** * Unpin a CID from the local Kubo node. */ export async function unpinFromIPFS(cid: string): Promise { const res = await fetch( `${IPFS_API_URL}/api/v0/pin/rm?arg=${encodeURIComponent(cid)}`, { method: "POST" }, ); // 500 with "not pinned" is fine — idempotent unpin if (!res.ok) { const err = await res.text(); if (!err.includes("not pinned")) { throw new Error(`IPFS unpin failed (${res.status}): ${err}`); } } } /** * Fetch content from IPFS by CID (via gateway). */ export async function fetchFromIPFS(cid: string): Promise { return fetch(`${IPFS_GATEWAY_URL}/ipfs/${cid}`); } /** * Get IPFS node status — peer ID, repo size, number of objects. */ export async function getIPFSStatus(): Promise<{ peerId: string; repoSize: number; numObjects: number; }> { const [idRes, statRes] = await Promise.all([ fetch(`${IPFS_API_URL}/api/v0/id`, { method: "POST" }), fetch(`${IPFS_API_URL}/api/v0/repo/stat`, { method: "POST" }), ]); if (!idRes.ok) throw new Error(`IPFS id failed: ${idRes.status}`); if (!statRes.ok) throw new Error(`IPFS repo/stat failed: ${statRes.status}`); const id = (await idRes.json()) as { ID: string }; const stat = (await statRes.json()) as { RepoSize: number; NumObjects: number; }; return { peerId: id.ID, repoSize: stat.RepoSize, numObjects: stat.NumObjects, }; }