diff --git a/extension/lib/api.js b/extension/lib/api.js index a6b71b2..e66ea93 100644 --- a/extension/lib/api.js +++ b/extension/lib/api.js @@ -11,7 +11,10 @@ class FabledCuratorAPI { async init() { const cfg = await browser.storage.local.get(['apiUrl', 'apiKey']); - this.baseUrl = cfg.apiUrl || null; + // Normalize on READ, not just on save: configs stored before the options + // page started normalizing are missing the `/api` suffix, and this heals + // them without the operator having to reopen Settings. + this.baseUrl = normalizeApiUrl(cfg.apiUrl) || null; this.apiKey = cfg.apiKey || null; return this.isConfigured(); } @@ -50,6 +53,13 @@ class FabledCuratorAPI { } catch { message = `HTTP ${response.status}: ${response.statusText}`; } + // 404/405 from FC almost always means the request never reached the JSON + // API — it fell through to the SPA catch-all, which serves HTML on GET + // and rejects everything else. Say so, rather than making the operator + // decode "Method Not Allowed" on an endpoint that plainly allows POST. + if (response.status === 404 || response.status === 405) { + message += ` — ${url} isn't the FC API. Check the FC URL in settings.`; + } const err = new Error(message); err.status = response.status; throw err; @@ -96,11 +106,10 @@ class FabledCuratorAPI { return this.request('GET', '/extension/manifest'); } - // The web/SPA root: baseUrl with the trailing slash + `/api` suffix stripped. - // Where the Vue router (artist pages) and the served XPI live, NOT the JSON - // API. Used by OPEN_ARTIST_PAGE + the self-update check. + // The web/SPA root: where the Vue router (artist pages) and the served XPI + // live, NOT the JSON API. Used by OPEN_ARTIST_PAGE + the self-update check. webRoot() { - return (this.baseUrl || '').replace(/\/+$/, '').replace(/\/api$/, ''); + return webRootFromApiUrl(this.baseUrl); } // Connection test = the cheapest read with auth. diff --git a/extension/lib/url.js b/extension/lib/url.js new file mode 100644 index 0000000..2329adf --- /dev/null +++ b/extension/lib/url.js @@ -0,0 +1,32 @@ +/** + * Canonical FC endpoint derivation, shared by the background client and the + * options page so a URL entered either way behaves identically. + * + * FC serves two things on one origin: the JSON API under `/api`, and the Vue + * SPA from the root. `api.js` builds requests as `${baseUrl}/credentials`, so + * the stored base URL has to carry the `/api` suffix. + */ + +/** + * Accept what an operator would naturally type — the instance root + * (`http://curator.example.com`) or the API root (`.../api`) — and return the + * API root either way. + * + * Worth normalizing rather than validating: a root-form URL doesn't fail + * loudly, it lands on the SPA catch-all, which answers `GET /credentials` with + * 200 HTML and rejects `POST /credentials` with 405. The operator sees a + * working Test Connection and a broken export. + */ +function normalizeApiUrl(raw) { + const trimmed = (raw || '').trim().replace(/\/+$/, ''); + if (!trimmed) return ''; + return /\/api$/i.test(trimmed) ? trimmed : `${trimmed}/api`; +} + +/** + * The SPA root — where the Vue router (artist pages) and the served XPI live, + * NOT the JSON API. Accepts either input form, same as normalizeApiUrl. + */ +function webRootFromApiUrl(raw) { + return normalizeApiUrl(raw).replace(/\/api$/i, ''); +} diff --git a/extension/manifest.json b/extension/manifest.json index 4348ba1..e763e3f 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "FabledCurator", - "version": "1.0.9", + "version": "1.0.10", "description": "Export cookies from supported platforms to FabledCurator and add creators as sources in one click.", "browser_specific_settings": { @@ -46,7 +46,7 @@ }, "background": { - "scripts": ["lib/platforms.js", "lib/cookies.js", "lib/api.js", "background/background.js"] + "scripts": ["lib/platforms.js", "lib/cookies.js", "lib/url.js", "lib/api.js", "background/background.js"] }, "options_ui": { diff --git a/extension/options/options.html b/extension/options/options.html index 27e5136..9af858c 100644 --- a/extension/options/options.html +++ b/extension/options/options.html @@ -21,9 +21,12 @@
/api; both work.
+ Find it on FC → Settings → Maintenance → Browser extension.
+