fix: null guard on API array responses to prevent crash

This commit is contained in:
2026-07-06 08:51:59 -04:00
parent 50ed8abe95
commit 238ca23d5b
3 changed files with 27 additions and 22 deletions
+5 -2
View File
@@ -146,9 +146,12 @@ async function request<T>(method: string, path: string, body?: unknown): Promise
return undefined as T;
}
const text = await res.text();
const data = text ? JSON.parse(text) : {};
if (!text) {
throw new ApiError(res.status, "empty response");
}
const data = JSON.parse(text);
if (!res.ok) {
throw new ApiError(res.status, data.error || res.statusText);
throw new ApiError(res.status, (data && data.error) || res.statusText);
}
return data as T;
}
+9 -8
View File
@@ -47,9 +47,9 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
useEffect(() => {
api.fileCapabilities().then(setCapabilities).catch(() => {});
api.fileRoots().then(r => {
setRoots(r.roots);
if (!initialPath && r.roots.length > 0) {
setCurrentPath(r.roots[0]);
setRoots(r.roots ?? []);
if (!initialPath && (r.roots ?? []).length > 0) {
setCurrentPath(r.roots![0]);
}
}).catch(() => {});
}, []);
@@ -63,10 +63,11 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
setError(null);
try {
const res = await api.listFiles(path, pageNum, LIMIT);
const entries = res.entries ?? [];
if (pageNum === 1) {
setEntries(res.entries);
setEntries(entries);
} else {
setEntries(prev => [...prev, ...res.entries]);
setEntries(prev => [...prev, ...entries]);
}
setPage(pageNum);
setHasMore(res.has_more);
@@ -165,7 +166,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
)}
<div className="flex items-center gap-2 flex-wrap">
{roots.length > 1 && (
{(roots ?? []).length > 1 && (
<select
className="input w-auto"
value={roots.includes(currentPath) ? currentPath : roots[0]}
@@ -270,7 +271,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
</td>
</tr>
))}
{entries.length === 0 && !loading && (
{(entries ?? []).length === 0 && !loading && (
<tr>
<td colSpan={4} className="px-3 py-6 text-center text-slate-500">
Directorio vacío
@@ -290,7 +291,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
{hasMore && (
<button className="btn-ghost w-full text-xs" onClick={() => loadDir(currentPath, page + 1)}>
Cargar más ({total - entries.length} restantes)
Cargar más ({total - (entries ?? []).length} restantes)
</button>
)}
+13 -12
View File
@@ -65,10 +65,10 @@ export default function Files() {
useEffect(() => {
api.fileCapabilities().then(setCapabilities).catch(() => {});
api.fileRoots().then(r => {
setRoots(r.roots);
setRoots(r.roots ?? []);
setUnrestricted(r.unrestricted);
if (r.roots.length > 0) {
setCurrentPath(r.roots[0]);
if ((r.roots ?? []).length > 0) {
setCurrentPath(r.roots![0]);
} else if (!r.unrestricted) {
setCurrentPath("/");
}
@@ -88,10 +88,11 @@ export default function Files() {
setSearchQuery("");
try {
const res = await api.listFiles(path, pageNum, LIMIT);
const entries = res.entries ?? [];
if (pageNum === 1) {
setEntries(res.entries);
setEntries(entries);
} else {
setEntries(prev => [...prev, ...res.entries]);
setEntries(prev => [...prev, ...entries]);
}
setPage(pageNum);
setHasMore(res.has_more);
@@ -237,7 +238,7 @@ export default function Files() {
setSearching(true);
try {
const res = await api.searchFiles(currentPath, q, 100);
setSearchResults(res.results);
setSearchResults(res.results ?? []);
} catch (e) {
setError(e instanceof Error ? e.message : "Error searching");
} finally {
@@ -285,7 +286,7 @@ export default function Files() {
)}
<div className="flex items-center gap-3 flex-wrap">
{roots.length > 1 && (
{(roots ?? []).length > 1 && (
<select
className="input w-auto"
value={roots.includes(currentPath) ? currentPath : roots[0]}
@@ -328,9 +329,9 @@ export default function Files() {
</button>
</form>
{searchResults.length > 0 && (
<div className="card">
<div className="text-sm text-slate-400 mb-2">Resultados ({searchResults.length})</div>
{(searchResults ?? []).length > 0 && (
<div className="card">
<div className="text-sm text-slate-400 mb-2">Resultados ({(searchResults ?? []).length})</div>
<div className="max-h-48 overflow-y-auto">
{searchResults.map((hit, i) => (
<div
@@ -476,7 +477,7 @@ export default function Files() {
</td>
</tr>
))}
{entries.length === 0 && !loading && (
{(entries ?? []).length === 0 && !loading && (
<tr>
<td colSpan={5} className="px-4 py-8 text-center text-slate-500">
{searchQuery ? "Sin resultados" : "Directorio vacío"}
@@ -496,7 +497,7 @@ export default function Files() {
{hasMore && (
<button className="btn-ghost w-full text-xs" onClick={() => loadDir(currentPath, page + 1)}>
Cargar más ({total - entries.length} restantes)
Cargar más ({total - (entries ?? []).length} restantes)
</button>
)}