/** * 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, ''); }