harden: list and end site connections made without Remember in Settings (closes #406)
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:
@@ -1582,8 +1582,14 @@ view would leave a wallet one click from deletion.
|
|||||||
a value carrying its unit, hex (`0x10`) or exponent (`1e3`) notation —
|
a value carrying its unit, hex (`0x10`) or exponent (`1e3`) notation —
|
||||||
is refused with a flash message and the field snaps back to the stored
|
is refused with a flash message and the field snaps back to the stored
|
||||||
threshold, so a number the user did not type is never stored.
|
threshold, so a number the user did not type is never stored.
|
||||||
- Allowed Sites: list with remove buttons
|
- Allowed Sites: the hostnames remembered as allowed, under any address,
|
||||||
- Denied Sites: list with remove buttons
|
with remove buttons
|
||||||
|
- Connected Sites: the hostnames of the sites allowed without "Remember my
|
||||||
|
choice" that are still connected, with remove buttons. Only the background
|
||||||
|
holds these, in memory, and Settings asks it for them with
|
||||||
|
`AUTISTMASK_GET_CONNECTED_SITES`
|
||||||
|
- Denied Sites: the hostnames remembered as denied, under any address, with
|
||||||
|
remove buttons
|
||||||
- About: project link, license, author, version, release date, and the
|
- About: project link, license, author, version, release date, and the
|
||||||
commit, which links to the commit in the repository
|
commit, which links to the commit in the repository
|
||||||
- Debug: hidden until revealed, then an "Enable debug mode" checkbox that
|
- Debug: hidden until revealed, then an "Enable debug mode" checkbox that
|
||||||
@@ -1594,8 +1600,15 @@ view would leave a wallet one click from deletion.
|
|||||||
- `[recovery phrase]` on an HD wallet → **ShowRecoveryPhrase**
|
- `[recovery phrase]` on an HD wallet → **ShowRecoveryPhrase**
|
||||||
- `[x]` on a wallet → **DeleteWallet**
|
- `[x]` on a wallet → **DeleteWallet**
|
||||||
- Tap wallet name → inline rename field (no screen change)
|
- Tap wallet name → inline rename field (no screen change)
|
||||||
- `[x]` on a tracked token or a site → removes it in place (no screen
|
- `[x]` on a tracked token → removes it in place (no screen change)
|
||||||
change)
|
- `[x]` on an allowed or connected site → disconnects that site, in place:
|
||||||
|
its hostname is dropped from Allowed Sites under every address, and
|
||||||
|
`AUTISTMASK_REMOVE_SITE` has the background end every connection approved
|
||||||
|
without "Remember" from an origin with that hostname, under any address,
|
||||||
|
and send `accountsChanged` with an empty list to the site's open tabs.
|
||||||
|
Only the extension's own pages may send either message
|
||||||
|
- `[x]` on a denied site → forgets the refusal, in place; it connects
|
||||||
|
nothing and tells the background nothing
|
||||||
- Ten clicks on the version → reveals the Debug well (no screen change)
|
- Ten clicks on the version → reveals the Debug well (no screen change)
|
||||||
- "Back" (or Settings gear again) → previous screen (Home)
|
- "Back" (or Settings gear again) → previous screen (Home)
|
||||||
|
|
||||||
@@ -1790,7 +1803,8 @@ view would leave a wallet one click from deletion.
|
|||||||
- **Transitions**:
|
- **Transitions**:
|
||||||
- "Allow" / "Deny" → closes popup (returns result to background script; the
|
- "Allow" / "Deny" → closes popup (returns result to background script; the
|
||||||
choice is persisted to the allowed or denied list when "Remember" is
|
choice is persisted to the allowed or denied list when "Remember" is
|
||||||
checked)
|
checked, and an "Allow" without it is listed under Connected Sites in
|
||||||
|
**Settings**)
|
||||||
- Popup closed without answering → treated as a denial
|
- Popup closed without answering → treated as a denial
|
||||||
|
|
||||||
#### TxApproval (`approve-tx`)
|
#### TxApproval (`approve-tx`)
|
||||||
|
|||||||
@@ -45,6 +45,19 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-04: Settings lists the sites connected without "Remember", and
|
||||||
|
removing a site there disconnects it
|
||||||
|
([#406](https://git.eeqj.de/sneak/AutistMask/issues/406)). Such a connection
|
||||||
|
lives only in the background's in-memory `connectedSites` map, so Settings
|
||||||
|
never showed it and the user could not end it; `AUTISTMASK_REMOVE_SITE`, sent
|
||||||
|
on every remove, did nothing. Settings now asks the background for those sites
|
||||||
|
(`AUTISTMASK_GET_CONNECTED_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 `connectedSites` entry for that
|
||||||
|
hostname and sends `accountsChanged` with an empty list to its open tabs. Only
|
||||||
|
the extension's own pages may send either message. Removing a denied site no
|
||||||
|
longer sends it, since forgetting a refusal ends no connection.
|
||||||
- 2026-10-04: Removing an address or deleting a wallet ends every site
|
- 2026-10-04: Removing an address or deleting a wallet ends every site
|
||||||
connection approved without "Remember" for the addresses removed
|
connection approved without "Remember" for the addresses removed
|
||||||
([#245](https://git.eeqj.de/sneak/AutistMask/issues/245)). Such a connection
|
([#245](https://git.eeqj.de/sneak/AutistMask/issues/245)). Such a connection
|
||||||
|
|||||||
+41
-1
@@ -1110,6 +1110,24 @@ async function broadcastAccountsChanged() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tell every open tab of a site Settings removed that it has no account.
|
||||||
|
async function broadcastSiteRemoved(hostname) {
|
||||||
|
let tabs;
|
||||||
|
try {
|
||||||
|
tabs = await tabsQuery({});
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const tab of tabs) {
|
||||||
|
if (!tab.url || extractHostname(tab.url) !== hostname) continue;
|
||||||
|
tabsSendMessage(tab.id, {
|
||||||
|
type: "AUTISTMASK_EVENT",
|
||||||
|
eventName: "accountsChanged",
|
||||||
|
data: [],
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Background balance refresh: every 60 seconds when the popup isn't open.
|
// Background balance refresh: every 60 seconds when the popup isn't open.
|
||||||
// When the popup IS open, its 10-second interval keeps lastBalanceRefresh
|
// When the popup IS open, its 10-second interval keeps lastBalanceRefresh
|
||||||
// fresh, so this naturally skips.
|
// fresh, so this naturally skips.
|
||||||
@@ -1306,6 +1324,8 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|||||||
"AUTISTMASK_TX_RESPONSE",
|
"AUTISTMASK_TX_RESPONSE",
|
||||||
"AUTISTMASK_SIGN_RESPONSE",
|
"AUTISTMASK_SIGN_RESPONSE",
|
||||||
"AUTISTMASK_ADDRESSES_REMOVED",
|
"AUTISTMASK_ADDRESSES_REMOVED",
|
||||||
|
"AUTISTMASK_GET_CONNECTED_SITES",
|
||||||
|
"AUTISTMASK_REMOVE_SITE",
|
||||||
];
|
];
|
||||||
if (POPUP_ONLY_TYPES.includes(msg.type) && !isExtensionSender(sender)) {
|
if (POPUP_ONLY_TYPES.includes(msg.type) && !isExtensionSender(sender)) {
|
||||||
sendResponse({ error: "Unauthorized sender" });
|
sendResponse({ error: "Unauthorized sender" });
|
||||||
@@ -1662,8 +1682,28 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settings lists the sites connected without "Remember", which only this
|
||||||
|
// worker knows. The origin is what precedes the key's last colon.
|
||||||
|
if (msg.type === "AUTISTMASK_GET_CONNECTED_SITES") {
|
||||||
|
sendResponse(
|
||||||
|
Object.keys(connectedSites).map((key) =>
|
||||||
|
extractHostname(key.slice(0, key.lastIndexOf(":"))),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings removed this site and has already dropped its remembered
|
||||||
|
// entries. Its connections approved without "Remember" end here, under
|
||||||
|
// every address, and its open tabs are told it has no account.
|
||||||
if (msg.type === "AUTISTMASK_REMOVE_SITE") {
|
if (msg.type === "AUTISTMASK_REMOVE_SITE") {
|
||||||
// Popup already saved state; nothing else needed
|
for (const key of Object.keys(connectedSites)) {
|
||||||
|
const origin = key.slice(0, key.lastIndexOf(":"));
|
||||||
|
if (extractHostname(origin) === msg.hostname) {
|
||||||
|
delete connectedSites[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
broadcastSiteRemoved(msg.hostname);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1064,6 +1064,15 @@
|
|||||||
<div id="settings-allowed-sites"></div>
|
<div id="settings-allowed-sites"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-well p-3 mx-1 mb-3">
|
||||||
|
<h3 class="font-bold mb-1">Connected Sites</h3>
|
||||||
|
<p class="text-xs text-muted mb-2">
|
||||||
|
Sites you allowed without "Remember my choice".
|
||||||
|
Switching address disconnects them.
|
||||||
|
</p>
|
||||||
|
<div id="settings-connected-sites"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="bg-well p-3 mx-1 mb-3">
|
<div class="bg-well p-3 mx-1 mb-3">
|
||||||
<h3 class="font-bold mb-1">Denied Sites</h3>
|
<h3 class="font-bold mb-1">Denied Sites</h3>
|
||||||
<p class="text-xs text-muted mb-2">
|
<p class="text-xs text-muted mb-2">
|
||||||
|
|||||||
+51
-23
@@ -29,46 +29,63 @@ const {
|
|||||||
GITEA_COMMIT_URL,
|
GITEA_COMMIT_URL,
|
||||||
} = require("../../shared/buildInfo");
|
} = require("../../shared/buildInfo");
|
||||||
|
|
||||||
const { notify } = require("../../shared/browserApi");
|
const { notify, sendMessage } = require("../../shared/browserApi");
|
||||||
|
|
||||||
let versionClickCount = 0;
|
let versionClickCount = 0;
|
||||||
let versionClickTimer = null;
|
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 container = $(containerId);
|
||||||
const hostnames = [...new Set(Object.values(siteMap).flat())];
|
const unique = [...new Set(hostnames)];
|
||||||
if (hostnames.length === 0) {
|
if (unique.length === 0) {
|
||||||
container.innerHTML = '<p class="text-xs text-muted">None</p>';
|
container.innerHTML = '<p class="text-xs text-muted">None</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let html = "";
|
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">`;
|
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
|
// A hostname the URL parser produced cannot carry a delimiter, so
|
||||||
// this is escaped for the rule rather than for a known hole — the
|
// this is escaped for the rule rather than for a known hole — the
|
||||||
// rule being that nothing reaches innerHTML unescaped.
|
// rule being that nothing reaches innerHTML unescaped.
|
||||||
html += `<span>${escapeHtml(hostname)}</span>`;
|
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>`;
|
html += `</div>`;
|
||||||
});
|
});
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
container.querySelectorAll(".btn-remove-site").forEach((btn) => {
|
container.querySelectorAll(".btn-remove-site").forEach((btn) => {
|
||||||
btn.addEventListener("click", async () => {
|
btn.addEventListener("click", () => onRemove(btn.dataset.hostname));
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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() {
|
function renderTrackedTokens() {
|
||||||
const container = $("settings-tracked-tokens");
|
const container = $("settings-tracked-tokens");
|
||||||
if (state.trackedTokens.length === 0) {
|
if (state.trackedTokens.length === 0) {
|
||||||
@@ -202,13 +219,24 @@ function show() {
|
|||||||
showView("settings");
|
showView("settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSiteLists() {
|
async function renderSiteLists() {
|
||||||
renderSiteList(
|
renderSiteList(
|
||||||
"settings-allowed-sites",
|
"settings-allowed-sites",
|
||||||
state.allowedSites,
|
Object.values(state.allowedSites).flat(),
|
||||||
"allowedSites",
|
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) {
|
function init(ctx) {
|
||||||
|
|||||||
@@ -2101,6 +2101,16 @@ describe("a site connection decided as the popup closes", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// What a site is told when it asks which account it may use.
|
||||||
|
async function siteAccounts(bg, origin) {
|
||||||
|
const { sendResponse } = bg.send(
|
||||||
|
{ type: "AUTISTMASK_RPC", method: "eth_accounts", params: [] },
|
||||||
|
{ origin: origin || FRESH_ORIGIN },
|
||||||
|
);
|
||||||
|
await settle();
|
||||||
|
return sendResponse.mock.calls[0][0];
|
||||||
|
}
|
||||||
|
|
||||||
// A site connected without "Remember" is held only in the background's memory,
|
// A site connected without "Remember" is held only in the background's memory,
|
||||||
// keyed to the address it was connected to. Removing that address, or the
|
// keyed to the address it was connected to. Removing that address, or the
|
||||||
// wallet holding it, must end the connection as part of the removal itself.
|
// wallet holding it, must end the connection as part of the removal itself.
|
||||||
@@ -2136,16 +2146,6 @@ describe("removing an address ends a site's connection to it", () => {
|
|||||||
return bg;
|
return bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// What FRESH_ORIGIN is told when it asks which account it may use.
|
|
||||||
async function siteAccounts(bg) {
|
|
||||||
const { sendResponse } = bg.send(
|
|
||||||
{ type: "AUTISTMASK_RPC", method: "eth_accounts", params: [] },
|
|
||||||
{ origin: FRESH_ORIGIN },
|
|
||||||
);
|
|
||||||
await settle();
|
|
||||||
return sendResponse.mock.calls[0][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
test("removing the connected address ends the connection", async () => {
|
test("removing the connected address ends the connection", async () => {
|
||||||
const bg = await connectedBackground();
|
const bg = await connectedBackground();
|
||||||
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
||||||
@@ -2192,3 +2192,168 @@ describe("removing an address ends a site's connection to it", () => {
|
|||||||
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Settings lists the sites allowed without "Remember", which only the
|
||||||
|
// background holds, and removing a site there, from either list, disconnects
|
||||||
|
// it. These drive the real Settings view against the real background and
|
||||||
|
// click the [x] the user clicks.
|
||||||
|
describe("removing a site in Settings disconnects it", () => {
|
||||||
|
// FRESH_ORIGIN on another port, so its hostname is FRESH_ORIGIN's.
|
||||||
|
const FRESH_OTHER_PORT = "https://fresh.example:8443";
|
||||||
|
|
||||||
|
// A site list's container. Its [x] buttons, data attributes and all, are
|
||||||
|
// read back out of the rows the view wrote into it, so clicking one runs
|
||||||
|
// the handler the view attached to it.
|
||||||
|
function fakeSiteList() {
|
||||||
|
const list = {
|
||||||
|
innerHTML: "",
|
||||||
|
buttons: [],
|
||||||
|
querySelectorAll() {
|
||||||
|
const tags = list.innerHTML.match(/<button[^>]*>/g) || [];
|
||||||
|
list.buttons = tags.map((tag) => ({
|
||||||
|
dataset: Object.fromEntries(
|
||||||
|
[...tag.matchAll(/data-(\w+)="([^"]*)"/g)].map(
|
||||||
|
(match) => [match[1], match[2]],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
addEventListener(event, handler) {
|
||||||
|
this[event] = handler;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
return list.buttons;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The hostnames a site list shows.
|
||||||
|
function listed(list) {
|
||||||
|
return [...list.innerHTML.matchAll(/data-hostname="([^"]*)"/g)].map(
|
||||||
|
(match) => match[1],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A site connected the way the user does it, in the approval popup.
|
||||||
|
async function connect(bg, origin, remember) {
|
||||||
|
const pending = bg.requestSite(origin);
|
||||||
|
await settle();
|
||||||
|
bg.connectApproval(pending.id()).decide(true, remember);
|
||||||
|
await settle();
|
||||||
|
expect(pending.result()).toEqual({ result: [signer.address] });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings, opened over the background's storage and wired to it the way
|
||||||
|
// the popup is: what Settings sends reaches the background from the
|
||||||
|
// extension's own page, and the answer comes back.
|
||||||
|
async function openSettings(bg) {
|
||||||
|
const lists = {};
|
||||||
|
const element = (id) => (lists[id] ||= fakeSiteList());
|
||||||
|
global.document = { getElementById: element };
|
||||||
|
global.chrome.runtime.sendMessage = (msg, callback) => {
|
||||||
|
const { sendResponse } = bg.send(msg, bg.fromPopup);
|
||||||
|
if (callback) callback(sendResponse.mock.calls[0]?.[0]);
|
||||||
|
};
|
||||||
|
await require("../src/shared/state").loadState();
|
||||||
|
await require("../src/popup/views/settings").renderSiteLists();
|
||||||
|
return {
|
||||||
|
allowed: element("settings-allowed-sites"),
|
||||||
|
connected: element("settings-connected-sites"),
|
||||||
|
// Click the [x] beside a site, and let what it sends run.
|
||||||
|
remove: async (list, hostname) => {
|
||||||
|
expect(listed(list)).toContain(hostname);
|
||||||
|
const button = list.buttons.find(
|
||||||
|
(b) => b.dataset.hostname === hostname,
|
||||||
|
);
|
||||||
|
await button.click();
|
||||||
|
await settle();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
delete global.document;
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Settings lists a site connected without Remember", async () => {
|
||||||
|
const bg = loadBackground({ actionPopup: true });
|
||||||
|
await connect(bg, FRESH_ORIGIN, false);
|
||||||
|
|
||||||
|
const settings = await openSettings(bg);
|
||||||
|
|
||||||
|
expect(listed(settings.connected)).toEqual(["fresh.example"]);
|
||||||
|
expect(listed(settings.allowed)).toEqual([HOSTNAME]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("removing a site connected without Remember disconnects it and tells its tabs", async () => {
|
||||||
|
const bg = loadBackground({ actionPopup: true });
|
||||||
|
await connect(bg, FRESH_ORIGIN, false);
|
||||||
|
const sentToTabs = [];
|
||||||
|
global.chrome.tabs = {
|
||||||
|
query: (q, cb) =>
|
||||||
|
cb([
|
||||||
|
{ id: 1, url: FRESH_ORIGIN + "/app" },
|
||||||
|
{ id: 2, url: ORIGIN + "/app" },
|
||||||
|
]),
|
||||||
|
sendMessage: (tabId, msg, cb) => {
|
||||||
|
sentToTabs.push({ tabId, msg });
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const settings = await openSettings(bg);
|
||||||
|
|
||||||
|
await settings.remove(settings.connected, "fresh.example");
|
||||||
|
|
||||||
|
expect(await siteAccounts(bg)).toEqual({ result: [] });
|
||||||
|
expect(sentToTabs).toEqual([
|
||||||
|
{
|
||||||
|
tabId: 1,
|
||||||
|
msg: {
|
||||||
|
type: "AUTISTMASK_EVENT",
|
||||||
|
eventName: "accountsChanged",
|
||||||
|
data: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(listed(settings.connected)).toEqual([]);
|
||||||
|
// The other site is untouched.
|
||||||
|
expect(await siteAccounts(bg, ORIGIN)).toEqual({
|
||||||
|
result: [signer.address],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// The same hostname can hold both kinds of connection: one origin allowed
|
||||||
|
// without Remember, then another, on a different port, allowed with it.
|
||||||
|
test("removing a remembered site also ends its connection made without Remember", async () => {
|
||||||
|
const bg = loadBackground({ actionPopup: true });
|
||||||
|
await connect(bg, FRESH_ORIGIN, false);
|
||||||
|
await connect(bg, FRESH_OTHER_PORT, true);
|
||||||
|
const settings = await openSettings(bg);
|
||||||
|
|
||||||
|
await settings.remove(settings.allowed, "fresh.example");
|
||||||
|
|
||||||
|
expect(await siteAccounts(bg, FRESH_OTHER_PORT)).toEqual({
|
||||||
|
result: [],
|
||||||
|
});
|
||||||
|
expect(await siteAccounts(bg, FRESH_ORIGIN)).toEqual({ result: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a page can neither remove a site nor list the connected ones", async () => {
|
||||||
|
const bg = loadBackground({ actionPopup: true });
|
||||||
|
await connect(bg, FRESH_ORIGIN, false);
|
||||||
|
const page = { url: FRESH_ORIGIN + "/index.html" };
|
||||||
|
|
||||||
|
const remove = bg.send(
|
||||||
|
{ type: "AUTISTMASK_REMOVE_SITE", hostname: "fresh.example" },
|
||||||
|
page,
|
||||||
|
);
|
||||||
|
const list = bg.send({ type: "AUTISTMASK_GET_CONNECTED_SITES" }, page);
|
||||||
|
|
||||||
|
expect(remove.sendResponse).toHaveBeenCalledWith({
|
||||||
|
error: "Unauthorized sender",
|
||||||
|
});
|
||||||
|
expect(list.sendResponse).toHaveBeenCalledWith({
|
||||||
|
error: "Unauthorized sender",
|
||||||
|
});
|
||||||
|
expect(await siteAccounts(bg)).toEqual({ result: [signer.address] });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user