7c7854e08f
- 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
82 lines
2.0 KiB
TypeScript
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' });
|
|
}
|