Files
IPFS-portal/src/lib/api/explorer.ts
T
maikrolf 7c7854e08f feat: SIWE auth, auto-pin, pin search/share, explorer improvements
- SIWE server-side auth (siwe + jose JWT)
- Wallet provider fallback (MAOS -> MetaMask)
- Auto-pin after upload (quick + crypto)
- Pin search/filter in explorer
- Share button (gateway link) per pin
- Copy CID button per pin
- Breadcrumb root navigation fix
- Hydration mismatch fix (mounted state)
- Defensive truncateCid for empty values
- CSP fix (template literal)
- Error boundaries (upload, profile, usage)
- 191 tests passing
2026-09-19 12:25:36 +02:00

82 lines
2.0 KiB
TypeScript

/* ── IPFS Portal API — IPFS Explorer ── */
import { apiFetch, API_BASE } from './client';
export interface IPFSEntry {
name: string;
type: 'file' | 'dir';
size: number;
hash: string;
}
/** Raw Kubo API /api/v0/ls response */
interface KuboLink {
Name: string;
Type: number; // 1 = directory, 2 = file
Size: number;
Hash: string;
}
interface KuboObject {
Hash: string;
Links: KuboLink[];
}
interface KuboLsResponse {
Objects: KuboObject[];
}
/** Transform Kubo response to IPFSEntry[] */
function parseKuboLs(raw: unknown): IPFSEntry[] {
// Already an array — return as-is
if (Array.isArray(raw)) {
return raw as IPFSEntry[];
}
// Kubo format: { Objects: [{ Links: [...] }] }
const data = raw as KuboLsResponse;
if (data?.Objects?.[0]?.Links) {
return data.Objects[0].Links.map((link) => ({
name: link.Name,
type: link.Type === 1 ? 'dir' as const : 'file' as const,
size: link.Size,
hash: link.Hash,
}));
}
// Single file — Kubo wraps it in Objects but no Links
if (data?.Objects?.[0]?.Hash) {
const obj = data.Objects[0];
return [{
name: obj.Hash.slice(0, 12) + '…',
type: 'file',
size: 0,
hash: obj.Hash,
}];
}
// Empty or unrecognizable response
return [];
}
export async function explorerLs(cid: string): Promise<IPFSEntry[]> {
const raw = await apiFetch<unknown>(`/explorer/ls/${encodeURIComponent(cid)}`, { method: 'POST' });
return parseKuboLs(raw);
}
export async function explorerCat(cid: string): Promise<string> {
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<Record<string, unknown>> {
return apiFetch(`/explorer/stat/${encodeURIComponent(cid)}`, { method: 'POST' });
}