fix: review round 3 — a new tab keeps its account's notices, a revoked tab account lands on the default
Deep-review round 3 over main..improvement/watch-background-audio. Account boot - The notification history and settings, and the hints toggle, are read when their modules load, which in a new tab (bookmark, typed URL) is before BootGate pins the account: they read as nobody's, and the tab's first notice replaced the account's stored history (up to 100 entries) with a one-item list. They now read again when the tab is pinned (onAccountPinned, run by setActiveAccount); a notice raised before the pin stays, after the history. - A tab pinned to an account still in this browser's wallet but whose session ended (suspended, deleted, or revoked by "log out everywhere") got "signed out" from /api/me while the default account was still signed in, and signing in again as anyone else left it there. A refused per-tab account now leaves the wallet, and the probe answers as the default, so the tab reloads onto it. Share page (/watch) - The password unlock is limited per viewer (link + client IP), under a per-link ceiling that counts only wrong passwords. Keyed on the link alone, ten wrong guesses every five minutes locked out every viewer, the ones with the password included. client_ip moves to app.ratelimit so the public routes can key on it. - Its failure messages and the load-failure screen are announced (role=alert, the password input described by its error). The sign-in, passphrase, password-change and setup form errors get role=alert too. Comments that still described removed machinery are corrected: App's collapse flags, SettingsPanel's Save bar, ConfigPanel, authed_format_sig. Tests: the account's history and hints across the pin; a revoked and a suspended pinned account; the per-viewer limit and the ceiling; the limiter's exhausted(); the share page's announced failures; a share link's feed filters persisted. Each mutation-checked RED for its own reason.
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
// the Notification Center shows (info events, actions awaiting interaction, and app
|
||||
// errors). History is persisted to localStorage so it survives reloads; action
|
||||
// callbacks are live-only and dropped on reload.
|
||||
import { activeAccountId, onAccountPinned } from "./activeAccount";
|
||||
import { LS, readAccount, readAccountMerged, writeAccount } from "./storage";
|
||||
|
||||
export type NotifLevel = "info" | "success" | "warning" | "error" | "fatal";
|
||||
@@ -205,6 +206,22 @@ function emit() {
|
||||
|
||||
recompute();
|
||||
|
||||
// The history and settings above are read at import, which in a new tab is before BootGate pins its
|
||||
// account — so they read as nobody's. Read them again for the account once it is pinned: otherwise the
|
||||
// tab's first write would replace the account's stored history with its own empty one. A notice raised
|
||||
// before the pin (a failed boot probe) belongs to no account, so it stays, after the history.
|
||||
let loadedFor = activeAccountId();
|
||||
onAccountPinned(() => {
|
||||
const unpinned = loadedFor == null ? items : [];
|
||||
loadedFor = activeAccountId();
|
||||
config = loadSettings();
|
||||
// Fresh ids for the history: the notices kept may already hold the low ones (a timer, a producer).
|
||||
const history = load().map((n) => ({ ...n, id: counter++ }));
|
||||
items = [...history, ...unpinned].slice(-MAX_HISTORY);
|
||||
recompute();
|
||||
listeners.forEach((l) => l());
|
||||
});
|
||||
|
||||
export function subscribe(listener: () => void): () => void {
|
||||
listeners.push(listener);
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user