d80a5255ed
CI / lint (push) Successful in 3s
extension / lint (push) Successful in 10s
CI / frontend-build (push) Successful in 24s
CI / backend-lint-and-test (push) Successful in 39s
CI / integration (push) Successful in 3m51s
extension / lint (pull_request) Successful in 10s
The extension is installed per-instance from the operator's FC host, so Firefox's
static update_url can't apply (each instance has a different host) and updates
were fully manual. Add a self-hosted-friendly update surface that reuses the
existing public GET /api/extension/manifest ({version, latest_url, sha256}):
- lib/api.js: getExtensionManifest().
- background.js: checkForUpdateInfo() compares the instance's latest published
version against runtime.getManifest().version (dotted-numeric compare so
1.0.10 > 1.0.9); CHECK_UPDATE message handler; refreshUpdateBadge() sets a
toolbar badge via browser.action; a daily browser.alarms check plus on
startup/installed. New 'alarms' permission (non-prompting).
- popup: an 'Update available — vX' banner with an Update button that opens the
signed XPI (web root, /api stripped like OPEN_ARTIST_PAGE) → Firefox's native
install prompt. Never blocks the popup on a failed check.
No backend changes (endpoint already exists). Bump 1.0.8→1.0.9 so this ships;
from here on updates surface themselves instead of needing a manual reinstall.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
106 lines
2.9 KiB
JavaScript
106 lines
2.9 KiB
JavaScript
/**
|
|
* FC backend client. Talks to /api/credentials (FC-3b), /api/sources
|
|
* (FC-3a), and the new /api/extension/* endpoints (FC-3g).
|
|
*/
|
|
|
|
class FabledCuratorAPI {
|
|
constructor() {
|
|
this.baseUrl = null;
|
|
this.apiKey = null;
|
|
}
|
|
|
|
async init() {
|
|
const cfg = await browser.storage.local.get(['apiUrl', 'apiKey']);
|
|
this.baseUrl = cfg.apiUrl || null;
|
|
this.apiKey = cfg.apiKey || null;
|
|
return this.isConfigured();
|
|
}
|
|
|
|
isConfigured() {
|
|
return !!(this.baseUrl && this.apiKey);
|
|
}
|
|
|
|
async request(method, endpoint, data = null) {
|
|
if (!this.isConfigured()) {
|
|
throw new Error('Not configured. Set FC URL + API key in settings.');
|
|
}
|
|
const url = `${this.baseUrl.replace(/\/+$/, '')}${endpoint}`;
|
|
const options = {
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Extension-Key': this.apiKey,
|
|
},
|
|
};
|
|
if (data) options.body = JSON.stringify(data);
|
|
|
|
let response;
|
|
try {
|
|
response = await fetch(url, options);
|
|
} catch (e) {
|
|
throw new Error('Cannot connect to FabledCurator. Check URL.');
|
|
}
|
|
|
|
if (!response.ok) {
|
|
let message;
|
|
try {
|
|
const body = await response.json();
|
|
message = body.error || `HTTP ${response.status}`;
|
|
if (body.detail) message += `: ${body.detail}`;
|
|
} catch {
|
|
message = `HTTP ${response.status}: ${response.statusText}`;
|
|
}
|
|
const err = new Error(message);
|
|
err.status = response.status;
|
|
throw err;
|
|
}
|
|
|
|
if (response.status === 204) return { success: true };
|
|
return response.json();
|
|
}
|
|
|
|
// FC-3b — credentials.
|
|
uploadCredentials(platform, credentialType, data) {
|
|
return this.request('POST', '/credentials', {
|
|
platform,
|
|
credential_type: credentialType,
|
|
data,
|
|
});
|
|
}
|
|
getCredentials() {
|
|
return this.request('GET', '/credentials');
|
|
}
|
|
|
|
// FC-3a — sources.
|
|
listSources() {
|
|
return this.request('GET', '/sources');
|
|
}
|
|
triggerSourceCheck(sourceId) {
|
|
return this.request('POST', `/sources/${sourceId}/check`);
|
|
}
|
|
|
|
// FC-3g — extension-specific.
|
|
quickAddSource(url) {
|
|
return this.request('POST', '/extension/quick-add-source', { url });
|
|
}
|
|
probeSource(url) {
|
|
// Read-only existence check. Drives the content-script chip's
|
|
// color/copy BEFORE the operator clicks Add.
|
|
const qs = new URLSearchParams({ url }).toString();
|
|
return this.request('GET', `/extension/probe?${qs}`);
|
|
}
|
|
// Latest published extension version on this instance — drives the in-app
|
|
// update prompt. Public endpoint (no key needed, but request() sends it
|
|
// harmlessly). Returns {version, xpi_url, latest_url, sha256}.
|
|
getExtensionManifest() {
|
|
return this.request('GET', '/extension/manifest');
|
|
}
|
|
|
|
// Connection test = the cheapest read with auth.
|
|
testConnection() {
|
|
return this.request('GET', '/credentials');
|
|
}
|
|
}
|
|
|
|
const api = new FabledCuratorAPI();
|