Ship the Go/SQLite API and Web UI, Chrome/Brave capture addon, Docker Compose, Pangolin reverse-proxy support, and a user-crontab watchdog so the binary stays running without systemd.
199 lines
6.0 KiB
JavaScript
199 lines
6.0 KiB
JavaScript
const setupEl = document.getElementById("setup");
|
|
const statusEl = document.getElementById("status");
|
|
const backendUrlInput = document.getElementById("backendUrl");
|
|
const apiKeyInput = document.getElementById("apiKey");
|
|
const setupError = document.getElementById("setupError");
|
|
const statusLine = document.getElementById("statusLine");
|
|
const openBackend = document.getElementById("openBackend");
|
|
const blocklistEl = document.getElementById("blocklist");
|
|
const blockUserInput = document.getElementById("blockUserInput");
|
|
const blocklistStatus = document.getElementById("blocklistStatus");
|
|
|
|
function showSetup(settings) {
|
|
setupEl.hidden = false;
|
|
statusEl.hidden = true;
|
|
backendUrlInput.value = settings.backendUrl || "http://127.0.0.1:8080";
|
|
apiKeyInput.value = settings.apiKey || "";
|
|
}
|
|
|
|
function setBlocklistStatus(text, isError) {
|
|
if (!text) {
|
|
blocklistStatus.hidden = true;
|
|
blocklistStatus.textContent = "";
|
|
return;
|
|
}
|
|
blocklistStatus.hidden = false;
|
|
blocklistStatus.textContent = text;
|
|
blocklistStatus.classList.toggle("error", Boolean(isError));
|
|
}
|
|
|
|
function renderBlocklist(usernames) {
|
|
blocklistEl.innerHTML = "";
|
|
if (!usernames.length) {
|
|
const empty = document.createElement("li");
|
|
empty.className = "muted";
|
|
empty.textContent = "No blocked authors.";
|
|
blocklistEl.appendChild(empty);
|
|
return;
|
|
}
|
|
usernames.forEach((name) => {
|
|
const li = document.createElement("li");
|
|
const label = document.createElement("span");
|
|
label.textContent = name;
|
|
const remove = document.createElement("button");
|
|
remove.type = "button";
|
|
remove.className = "remove";
|
|
remove.textContent = "Remove";
|
|
remove.addEventListener("click", async () => {
|
|
setBlocklistStatus("Syncing…");
|
|
try {
|
|
const list = await rcsRemoveBlocked(name);
|
|
renderBlocklist(list);
|
|
setBlocklistStatus("Synced");
|
|
} catch (err) {
|
|
setBlocklistStatus(String(err && err.message ? err.message : err), true);
|
|
}
|
|
});
|
|
li.appendChild(label);
|
|
li.appendChild(remove);
|
|
blocklistEl.appendChild(li);
|
|
});
|
|
}
|
|
|
|
async function syncBlocklistFromBackend() {
|
|
setBlocklistStatus("Syncing…");
|
|
try {
|
|
const list = await rcsPullBlocklist();
|
|
renderBlocklist(list);
|
|
setBlocklistStatus(`Synced (${list.length})`);
|
|
} catch (err) {
|
|
const cached = await rcsGetCachedBlocklist();
|
|
renderBlocklist(cached);
|
|
setBlocklistStatus("Sync failed — showing cache. " + (err && err.message ? err.message : err), true);
|
|
}
|
|
}
|
|
|
|
function showStatus(settings, healthOk, healthHint) {
|
|
setupEl.hidden = true;
|
|
statusEl.hidden = false;
|
|
if (healthOk) {
|
|
statusLine.textContent = `Connected to ${settings.backendUrl}`;
|
|
} else if (healthHint) {
|
|
statusLine.textContent = healthHint;
|
|
} else {
|
|
statusLine.textContent = `Saved ${settings.backendUrl} (health check failed — is the backend running?)`;
|
|
}
|
|
openBackend.href = settings.backendUrl;
|
|
if (healthOk) {
|
|
syncBlocklistFromBackend();
|
|
} else {
|
|
rcsGetCachedBlocklist().then(renderBlocklist);
|
|
setBlocklistStatus("Connect to sync blocklist", true);
|
|
}
|
|
}
|
|
|
|
async function load() {
|
|
const settings = await chrome.storage.sync.get({ backendUrl: "", apiKey: "" });
|
|
if (!settings.backendUrl) {
|
|
showSetup(settings);
|
|
return;
|
|
}
|
|
try {
|
|
const headers = { Accept: "application/json" };
|
|
if (settings.apiKey) headers["X-API-Key"] = settings.apiKey;
|
|
const response = await fetch(`${settings.backendUrl.replace(/\/+$/, "")}/api/health`, {
|
|
headers,
|
|
credentials: "include",
|
|
});
|
|
const text = await response.text();
|
|
let data = null;
|
|
try {
|
|
data = text ? JSON.parse(text) : null;
|
|
} catch {
|
|
data = { raw: text };
|
|
}
|
|
const looksHtml = typeof text === "string" && /^\s*</.test(text);
|
|
if (response.ok && data && data.ok === true) {
|
|
showStatus(settings, true);
|
|
return;
|
|
}
|
|
if (response.status === 401 || response.status === 403 || looksHtml) {
|
|
showStatus(
|
|
settings,
|
|
false,
|
|
`Saved ${settings.backendUrl} — open the Web UI, log in through Pangolin, then retry.`
|
|
);
|
|
return;
|
|
}
|
|
showStatus(settings, false);
|
|
} catch {
|
|
showStatus(settings, false);
|
|
}
|
|
}
|
|
|
|
async function ensureHostPermission(backendUrl) {
|
|
try {
|
|
const origin = new URL(backendUrl).origin + "/*";
|
|
const already = await chrome.permissions.contains({ origins: [origin] });
|
|
if (already) return true;
|
|
return chrome.permissions.request({ origins: [origin] });
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
document.getElementById("save").addEventListener("click", async () => {
|
|
setupError.hidden = true;
|
|
const backendUrl = backendUrlInput.value.trim().replace(/\/+$/, "");
|
|
const apiKey = apiKeyInput.value.trim();
|
|
if (!backendUrl) {
|
|
setupError.textContent = "Backend URL is required.";
|
|
setupError.hidden = false;
|
|
return;
|
|
}
|
|
try {
|
|
new URL(backendUrl);
|
|
} catch {
|
|
setupError.textContent = "Invalid URL.";
|
|
setupError.hidden = false;
|
|
return;
|
|
}
|
|
|
|
const granted = await ensureHostPermission(backendUrl);
|
|
if (!granted) {
|
|
setupError.textContent = "Permission to reach the backend was denied.";
|
|
setupError.hidden = false;
|
|
return;
|
|
}
|
|
|
|
await chrome.storage.sync.set({ backendUrl, apiKey });
|
|
await load();
|
|
});
|
|
|
|
document.getElementById("edit").addEventListener("click", async () => {
|
|
const settings = await chrome.storage.sync.get({ backendUrl: "", apiKey: "" });
|
|
showSetup(settings);
|
|
});
|
|
|
|
document.getElementById("blockAdd").addEventListener("click", async () => {
|
|
const username = blockUserInput.value.trim();
|
|
if (!username) return;
|
|
setBlocklistStatus("Syncing…");
|
|
try {
|
|
const list = await rcsAddBlocked(username);
|
|
blockUserInput.value = "";
|
|
renderBlocklist(list);
|
|
setBlocklistStatus("Synced");
|
|
} catch (err) {
|
|
setBlocklistStatus(String(err && err.message ? err.message : err), true);
|
|
}
|
|
});
|
|
|
|
blockUserInput.addEventListener("keydown", (event) => {
|
|
if (event.key === "Enter") {
|
|
document.getElementById("blockAdd").click();
|
|
}
|
|
});
|
|
|
|
load();
|