History

fix(web): filter unavailable theme packages

t3-code[bot] committed 3 months ago

Commit 7bcd7561 parent 4b789603 files · +47 −7Browse files

Unified · Split

3 files
  1. apps/web/src/components/settings/ThemeSearchSection.tsx +11 −6
  2. apps/web/src/openVsxThemes.test.ts +24 −0
  3. apps/web/src/openVsxThemes.ts +12 −1
apps/web/src/components/settings/ThemeSearchSection.tsx+11−6
@@ -39,12 +39,17 @@ export function ThemeSearchSection({ · 38 unmodified lines
39 useEffect(() => {39 useEffect(() => {
40 requestRef.current?.abort();40 requestRef.current?.abort();
41 requestRef.current = null;41 requestRef.current = null;
42 if (!open) return;42 if (open) {
43 setQuery("");43 setQuery("");
44 setResults(null);44 setResults(null);
45 setError(null);45 setError(null);
46 setIsSearching(false);46 setIsSearching(false);
47 setInstallingId(null);47 setInstallingId(null);
48 }
49 return () => {
50 requestRef.current?.abort();
51 requestRef.current = null;
52 };
48 }, [open]);53 }, [open]);
49 54
50 const runSearch = useCallback(async (searchText: string) => {55 const runSearch = useCallback(async (searchText: string) => {
apps/web/src/openVsxThemes.test.ts+24−0
@@ -44,6 +44,7 @@ describe("Open VSX themes", () => { · 43 unmodified lines
44 extensions: [44 extensions: [
45 { namespace: "demo", name: "theme" },45 { namespace: "demo", name: "theme" },
46 { namespace: "icons", name: "theme" },46 { namespace: "icons", name: "theme" },
47 { namespace: "oversized", name: "theme" },
47 { namespace: "closed", name: "theme" },48 { namespace: "closed", name: "theme" },
48 { namespace: "huge", name: "theme" },49 { namespace: "huge", name: "theme" },
49 ],50 ],
@@ -65,6 +66,20 @@ describe("Open VSX themes", () => { · 15 unmodified lines
65 { status: 200 },66 { status: 200 },
66 );67 );
67 }68 }
69 if (url.endsWith("/oversized/theme")) {
70 return new Response(
71 JSON.stringify(
72 extensionDetail({
73 namespace: "oversized",
74 files: {
75 ...extensionDetail().files,
76 download: `${ASSET_ROOT.replace("demo/theme", "oversized/theme")}/oversized.vsix`,
77 },
78 }),
79 ),
80 { status: 200 },
81 );
82 }
68 if (url.endsWith("/closed/theme")) {83 if (url.endsWith("/closed/theme")) {
69 return new Response(84 return new Response(
70 JSON.stringify(extensionDetail({ namespace: "closed", license: "All Rights Reserved" })),85 JSON.stringify(extensionDetail({ namespace: "closed", license: "All Rights Reserved" })),
@@ -74,6 +89,15 @@ describe("Open VSX themes", () => { · 3 unmodified lines
74 if (url.endsWith("/huge/theme")) {89 if (url.endsWith("/huge/theme")) {
75 return new Response(JSON.stringify({ padding: "x".repeat(256 * 1024) }), { status: 200 });90 return new Response(JSON.stringify({ padding: "x".repeat(256 * 1024) }), { status: 200 });
76 }91 }
92 if (url.endsWith("/oversized/theme/1.0.0/file/oversized.vsix")) {
93 return new Response(null, {
94 headers: { "content-length": String(20 * 1024 * 1024 + 1) },
95 status: 200,
96 });
97 }
98 if (url.endsWith(".vsix")) {
99 return new Response(null, { headers: { "content-length": "1024" }, status: 200 });
100 }
77 if (url.endsWith("/icons/theme/1.0.0/file/package.json")) {101 if (url.endsWith("/icons/theme/1.0.0/file/package.json")) {
78 return new Response(JSON.stringify({ contributes: { iconThemes: [{}] } }), { status: 200 });102 return new Response(JSON.stringify({ contributes: { iconThemes: [{}] } }), { status: 200 });
79 }103 }
apps/web/src/openVsxThemes.ts+12−1
@@ -197,8 +197,19 @@ export async function searchOpenVsxThemes( · 196 unmodified lines
197 try {197 try {
198 const extension = extensionFromDetail(JSON.parse(new TextDecoder().decode(detailBytes)));198 const extension = extensionFromDetail(JSON.parse(new TextDecoder().decode(detailBytes)));
199 if (!extension) return null;199 if (!extension) return null;
200 const manifestResponse = await fetch(extension.manifestUrl, signal ? { signal } : {});200 const [manifestResponse, packageResponse] = await Promise.all([
201 fetch(extension.manifestUrl, signal ? { signal } : {}),
202 fetch(extension.vsixUrl, { method: "HEAD", ...(signal ? { signal } : {}) }),
203 ]);
201 if (!manifestResponse.ok) throw new Error("manifest unavailable");204 if (!manifestResponse.ok) throw new Error("manifest unavailable");
205 const packageLength = Number(packageResponse.headers.get("content-length"));
206 if (
207 packageResponse.ok &&
208 Number.isFinite(packageLength) &&
209 packageLength > MAX_VSIX_BYTES
210 ) {
211 return null;
212 }
202 const manifestBytes = await readCappedResponse(213 const manifestBytes = await readCappedResponse(
203 manifestResponse,214 manifestResponse,
204 MAX_MANIFEST_BYTES,215 MAX_MANIFEST_BYTES,