Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

92 lines
2.7 KiB
TypeScript

"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import {
createVirtualArchive,
type VirtualArchive,
} from "@/lib/xulux/virtual-archive";
export type ArchiveState =
| { status: "idle" }
| { status: "loading" }
| { status: "ready"; archive: VirtualArchive }
| { status: "error"; error: string };
function resolveDownloadFetchUrl(
downloadUrl: string,
templateId: string | undefined,
versionId: string | undefined,
): string {
if (downloadUrl.startsWith("/")) return downloadUrl;
if (!templateId) {
throw new Error("Template id is required to download hosted archives.");
}
const parsed = new URL(downloadUrl);
const params = new URLSearchParams({ templateId });
if (versionId) params.set("versionId", versionId);
if (parsed.search) params.set("downloadSearch", parsed.search);
return `/api/xulux/download-proxy?${params.toString()}`;
}
export function useVirtualArchive(
downloadUrl: string | undefined,
templateId?: string,
versionId?: string,
): ArchiveState {
const [state, setState] = useState<ArchiveState>({ status: "idle" });
const abortRef = useRef<AbortController | null>(null);
const cachedFetchUrlRef = useRef<string | null>(null);
const cachedArchiveRef = useRef<VirtualArchive | null>(null);
const load = useCallback(
async (url: string) => {
let controller: AbortController | null = null;
try {
const fetchUrl = resolveDownloadFetchUrl(url, templateId, versionId);
if (
cachedFetchUrlRef.current === fetchUrl &&
cachedArchiveRef.current
) {
setState({ status: "ready", archive: cachedArchiveRef.current });
return;
}
abortRef.current?.abort();
controller = new AbortController();
abortRef.current = controller;
setState({ status: "loading" });
const res = await fetch(fetchUrl, { signal: controller.signal });
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
const buffer = await res.arrayBuffer();
const archive = createVirtualArchive(new Uint8Array(buffer));
cachedFetchUrlRef.current = fetchUrl;
cachedArchiveRef.current = archive;
setState({ status: "ready", archive });
} catch (err) {
if (controller?.signal.aborted) return;
setState({
status: "error",
error: err instanceof Error ? err.message : String(err),
});
}
},
[templateId, versionId],
);
useEffect(() => {
if (!downloadUrl) {
setState({ status: "idle" });
return;
}
load(downloadUrl);
return () => abortRef.current?.abort();
}, [downloadUrl, load]);
return state;
}