fix(extension): normalize FC URL so credential push doesn't 405
The stored apiUrl was required to already carry the `/api` suffix, since
api.js builds requests as `${baseUrl}/credentials`. The options label read
"FC base URL", so entering the instance root -- the natural reading --
sent every request one path segment short: POST /credentials hit the Vue
SPA catch-all and came back 405, and GET /extension/manifest 404'd.
Worse, Test Connection reported success on it: the catch-all answers GET
/credentials with 200 HTML, so `r.ok` was true and the only affordance
meant to catch this misconfiguration actively masked it.
Normalize instead of validate (rules 92, 26):
- New lib/url.js: normalizeApiUrl / webRootFromApiUrl, one source shared
by the background client and the options page. Accepts either the
instance root or the API root.
- api.js normalizes on read, so configs already stored in the broken form
heal themselves without the operator reopening Settings.
- options.js stores the canonical form, echoes back what it saved, and
the test now asserts a JSON content-type -- killing the false green.
- 404/405 in request() now names the URL and points at the setting.
- Options label/placeholder state that both forms work.
Version 1.0.9 -> 1.0.10 in BOTH manifest.json and package.json; build.yml
resolves the release version from package.json, and a stale value there
would hit the cached ext-1.0.9 asset and republish the old XPI unsigned
against the new code.
Refs #2393
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+14
-5
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user