fix(anime): scope preferences, paginate sources, harden installs

- Preference store keys entries by extension package + bridge source id; legacy unscoped entries are discarded once instead of being handed to whichever extension asks first
- Source picker's "Load more" appends the next page without duplicating streamed results
- Repository index fetches and subtitle/APK downloads now time out and are size-bounded instead of hanging or growing unbounded
- APK installs are staged to a temp file and renamed into place
- Stream metadata lookup matches the requested path, not only the currently playing one
- Reworked animeui into browse-state/detail-panel/panels.css modules
- Reverted premature CHANGELOG unreleased entries; refreshed anime-browser docs
This commit is contained in:
2026-08-15 21:44:51 -07:00
parent f5369b8b24
commit 289c74da35
48 changed files with 2158 additions and 1288 deletions
+36 -1
View File
@@ -58,6 +58,7 @@ export interface SubtitleCacheResult {
interface FetchResponseLike {
ok: boolean;
status: number;
body?: ReadableStream<Uint8Array> | null;
arrayBuffer: () => Promise<ArrayBuffer>;
}
@@ -150,7 +151,7 @@ async function downloadTrack(
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
bytes = new Uint8Array(await response.arrayBuffer());
bytes = await readBounded(response, MAX_SUBTITLE_BYTES);
} finally {
clearTimeout(timeoutId);
}
@@ -171,6 +172,40 @@ async function downloadTrack(
return filePath;
}
async function readBounded(response: FetchResponseLike, maxBytes: number): Promise<Uint8Array> {
const reader = response.body?.getReader();
if (!reader) {
const bytes = new Uint8Array(await response.arrayBuffer());
if (bytes.byteLength > maxBytes) {
throw new Error(`response too large (${bytes.byteLength} bytes)`);
}
return bytes;
}
const chunks: Uint8Array[] = [];
let total = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
total += value.byteLength;
if (total > maxBytes) {
try {
await reader.cancel();
} catch {}
throw new Error(`response too large (${total} bytes)`);
}
chunks.push(value);
}
const bytes = new Uint8Array(total);
let offset = 0;
for (const chunk of chunks) {
bytes.set(chunk, offset);
offset += chunk.byteLength;
}
return bytes;
}
/**
* Download every subtitle track to a fresh temp directory.
*