harden: list and end site connections made without Remember in Settings (closes #406)
check / check (push) Successful in 2m25s
e2e / e2e-chrome (push) Successful in 3m15s
e2e / e2e-firefox (push) Successful in 2m21s

A site allowed without "Remember" lives only in the background's
in-memory connectedSites map. Settings never listed it, and
AUTISTMASK_REMOVE_SITE, sent on every remove, did nothing, so the user
could not end such a connection.

Settings now asks the background for those sites and lists them under
Connected Sites. Removing a site from Allowed Sites or Connected Sites
drops its remembered entry under every address and sends
AUTISTMASK_REMOVE_SITE with the hostname; the background deletes every
matching connectedSites entry and sends accountsChanged with an empty
list to the site's tabs. Only the extension's own pages may send either
message.

Model: opus-5-5
This commit was merged in pull request #419.
This commit is contained in:
2026-10-04 06:41:39 +02:00
parent 5f54fcbb24
commit 49a7da87e8
6 changed files with 308 additions and 39 deletions
+51 -23
View File
@@ -29,46 +29,63 @@ const {
GITEA_COMMIT_URL,
} = require("../../shared/buildInfo");
const { notify } = require("../../shared/browserApi");
const { notify, sendMessage } = require("../../shared/browserApi");
let versionClickCount = 0;
let versionClickTimer = null;
function renderSiteList(containerId, siteMap, stateKey) {
// One row per hostname, however many addresses or origins it appears under,
// each with an [x] that hands it to onRemove.
function renderSiteList(containerId, hostnames, onRemove) {
const container = $(containerId);
const hostnames = [...new Set(Object.values(siteMap).flat())];
if (hostnames.length === 0) {
const unique = [...new Set(hostnames)];
if (unique.length === 0) {
container.innerHTML = '<p class="text-xs text-muted">None</p>';
return;
}
let html = "";
hostnames.forEach((hostname) => {
unique.forEach((hostname) => {
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
// A hostname the URL parser produced cannot carry a delimiter, so
// this is escaped for the rule rather than for a known hole — the
// rule being that nothing reaches innerHTML unescaped.
html += `<span>${escapeHtml(hostname)}</span>`;
html += `<button class="btn-remove-site border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-key="${escapeHtml(stateKey)}" data-hostname="${escapeHtml(hostname)}">[x]</button>`;
html += `<button class="btn-remove-site border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-hostname="${escapeHtml(hostname)}">[x]</button>`;
html += `</div>`;
});
container.innerHTML = html;
container.querySelectorAll(".btn-remove-site").forEach((btn) => {
btn.addEventListener("click", async () => {
const key = btn.dataset.key;
const host = btn.dataset.hostname;
for (const addr of Object.keys(state[key])) {
state[key][addr] = state[key][addr].filter((h) => h !== host);
if (state[key][addr].length === 0) {
delete state[key][addr];
}
}
await saveState();
notify({ type: "AUTISTMASK_REMOVE_SITE" });
renderSiteList(containerId, state[key], key);
});
btn.addEventListener("click", () => onRemove(btn.dataset.hostname));
});
}
// Drop a hostname from a remembered site list under every address.
function forgetHostname(siteMap, hostname) {
for (const addr of Object.keys(siteMap)) {
siteMap[addr] = siteMap[addr].filter((h) => h !== hostname);
if (siteMap[addr].length === 0) {
delete siteMap[addr];
}
}
}
// Removing a site from Allowed Sites or Connected Sites disconnects it: it is
// no longer allowed under any address, and the background ends its
// connections approved without "Remember" and tells its open tabs.
async function removeAllowedSite(hostname) {
forgetHostname(state.allowedSites, hostname);
await saveState();
notify({ type: "AUTISTMASK_REMOVE_SITE", hostname });
await renderSiteLists();
}
// Removing a denied site only forgets the refusal; it connects nothing.
async function removeDeniedSite(hostname) {
forgetHostname(state.deniedSites, hostname);
await saveState();
await renderSiteLists();
}
function renderTrackedTokens() {
const container = $("settings-tracked-tokens");
if (state.trackedTokens.length === 0) {
@@ -202,13 +219,24 @@ function show() {
showView("settings");
}
function renderSiteLists() {
async function renderSiteLists() {
renderSiteList(
"settings-allowed-sites",
state.allowedSites,
"allowedSites",
Object.values(state.allowedSites).flat(),
removeAllowedSite,
);
renderSiteList(
"settings-denied-sites",
Object.values(state.deniedSites).flat(),
removeDeniedSite,
);
// Sites allowed without "Remember" are held only by the background, in
// memory, so it is asked for them.
renderSiteList(
"settings-connected-sites",
await sendMessage({ type: "AUTISTMASK_GET_CONNECTED_SITES" }),
removeAllowedSite,
);
renderSiteList("settings-denied-sites", state.deniedSites, "deniedSites");
}
function init(ctx) {