103 lines
2.6 KiB
TypeScript
103 lines
2.6 KiB
TypeScript
/**
|
|
* 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://kubo: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<string> {
|
|
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<void> {
|
|
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<Response> {
|
|
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,
|
|
};
|
|
}
|