diff --git a/src/background.ts b/src/background.ts index 911dbdf2..0370f35e 100644 --- a/src/background.ts +++ b/src/background.ts @@ -57,13 +57,20 @@ browser.runtime.onMessage.addListener( return true; case "fetchThemes": { - const url = `https://betterseqta.org/api/themes?type=betterseqta&limit=100&nocache=${Date.now()}`; - fetch(url, { cache: "no-store" }) + const apiUrl = `https://betterseqta.org/api/themes?type=betterseqta&limit=100&nocache=${Date.now()}`; + const githubUrl = `https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Themes/main/store/themes.json?nocache=${Date.now()}`; + fetch(apiUrl, { cache: "no-store" }) .then((r) => r.json()) .then(sendResponse) .catch((err) => { - console.error("[Background] fetchThemes error:", err); - sendResponse({ success: false, error: err?.message }); + console.warn("[Background] fetchThemes API failed, trying GitHub fallback:", err?.message); + fetch(githubUrl, { cache: "no-store" }) + .then((r) => r.json()) + .then((data) => sendResponse({ success: true, data: { themes: data.themes ?? [] } })) + .catch((fallbackErr) => { + console.error("[Background] fetchThemes GitHub fallback error:", fallbackErr); + sendResponse({ success: false, error: fallbackErr?.message }); + }); }); return true; } diff --git a/src/manifests/manifest.json b/src/manifests/manifest.json index 1ef79abb..04b9f344 100644 --- a/src/manifests/manifest.json +++ b/src/manifests/manifest.json @@ -21,7 +21,7 @@ "service_worker": "background.ts" }, "content_security_policy": { - "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://betterseqta.org" + "extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://betterseqta.org https://raw.githubusercontent.com" }, "content_scripts": [ { diff --git a/src/plugins/built-in/themes/theme-manager.ts b/src/plugins/built-in/themes/theme-manager.ts index d077491d..488e376b 100644 --- a/src/plugins/built-in/themes/theme-manager.ts +++ b/src/plugins/built-in/themes/theme-manager.ts @@ -472,6 +472,7 @@ export class ThemeManager { } private readonly THEME_API_BASE = 'https://betterseqta.org/api'; + private readonly GITHUB_THEMES_BASE = 'https://raw.githubusercontent.com/BetterSEQTA/BetterSEQTA-Themes/main/store/themes'; /** * Fetch JSON from a URL via background script (avoids CORS when running inside SEQTA page) @@ -487,7 +488,7 @@ export class ThemeManager { /** * Download and install a theme from the store. - * Always calls the download API first to increment download_count on the server. + * Uses API first (increments download_count), falls back to GitHub if unreachable. */ public async downloadTheme(themeContent: { id: string; @@ -500,16 +501,23 @@ export class ThemeManager { try { if (!themeContent.id) return; - // Always call download endpoint to increment download_count - const downloadData = (await this.fetchFromUrl( - `${this.THEME_API_BASE}/themes/${themeContent.id}/download` - )) as { success?: boolean; data?: { theme_json_url: string } }; - if (!downloadData?.success || !downloadData?.data?.theme_json_url) { - throw new Error("Failed to get theme download URL"); - } - const themeJsonUrl = downloadData.data.theme_json_url; + let themeData: ThemeContent; - const themeData = (await this.fetchFromUrl(themeJsonUrl)) as ThemeContent; + try { + // Try API first (increments download_count) + const downloadData = (await this.fetchFromUrl( + `${this.THEME_API_BASE}/themes/${themeContent.id}/download` + )) as { success?: boolean; data?: { theme_json_url: string } }; + if (!downloadData?.success || !downloadData?.data?.theme_json_url) { + throw new Error("Failed to get theme download URL"); + } + themeData = (await this.fetchFromUrl(downloadData.data.theme_json_url)) as ThemeContent; + } catch (apiError) { + // Fallback to GitHub if API is unreachable + console.warn("[ThemeManager] API failed, trying GitHub fallback:", apiError); + const githubUrl = `${this.GITHUB_THEMES_BASE}/${themeContent.id}/theme.json`; + themeData = (await this.fetchFromUrl(githubUrl)) as ThemeContent; + } await this.installTheme(themeData); } catch (error) {