fix(deferred): close frontend UI/UX tails from the hygiene sweep
- player YB2: keepalive pagehide beacon (api.saveProgressBeacon) so the resume position isn't lost on F5/close within 5s of the last checkpoint (mirrors Plex). - player YB4: bound consecutive unplayable items in auto-advance so an auto-advancing/loop=all queue can't spin over a run of dead videos. - downloads B6 (client): localise structured quota/edit errors centrally in api.req() via localizeDetail() + Intl.NumberFormat (fixes HU/DE + decimal sep); + 3-locale download error strings. - channels FB1: focusChannelToken bump so re-clicking the same channel re-seeds the search box; FB2: syncSubs invalidates feed/feed-count (set can change now). - config CB2: reseed the ConfigPanel draft on a key-set dataVersion + explicit post-save token, so a mid-edit refetch can't clobber an in-progress edit. - config AB3 (UI): a blank allow_empty field stores "" instead of resetting. - admin SB2/SC2: confirm + success toast on demo-whitelist remove and deny-invite (+ trilingual strings); SC1: disable only the acted-on row (pendingId), not all; SB3: pause the 1s scheduler countdown ticker while the tab is hidden. - messages CT4: only invalidate conversations/unread when the incoming-message count actually changed, not on every 20s poll; MB3 (client): react to the live unread ping (onUnread → invalidate); MC3: extract e2ee installKey (setup/unlock); CC2: hoist POLL_MS to messaging.ts; MB4: document the reload-scoped socket.
This commit is contained in:
parent
4e80e2b39b
commit
f0198f2e0a
19 changed files with 302 additions and 45 deletions
|
|
@ -112,6 +112,15 @@ export async function loadFromDevice(userId: number): Promise<boolean> {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Install a freshly-imported private key as the current identity: drop any derived conversation
|
||||
// keys, persist it to this device, and notify unlock subscribers. Shared by setup() and unlock().
|
||||
async function installKey(userId: number, key: CryptoKey): Promise<void> {
|
||||
privKey = key;
|
||||
convKeys.clear();
|
||||
await idbPut(userId, key);
|
||||
emitUnlock();
|
||||
}
|
||||
|
||||
// First-run setup: generate a keypair, wrap the private key with the passphrase, persist a
|
||||
// non-extractable copy locally, and return the bundle to upload to the server.
|
||||
export async function setup(userId: number, passphrase: string): Promise<KeySetup> {
|
||||
|
|
@ -128,10 +137,7 @@ export async function setup(userId: number, passphrase: string): Promise<KeySetu
|
|||
const keyCheck = await subtle.encrypt({ name: "AES-GCM", iv: bsrc(checkIv) }, wrapKey, bsrc(enc.encode("ok")));
|
||||
const spki = await subtle.exportKey("spki", kp.publicKey);
|
||||
|
||||
privKey = await importPrivate(pkcs8);
|
||||
convKeys.clear();
|
||||
await idbPut(userId, privKey);
|
||||
emitUnlock();
|
||||
await installKey(userId, await importPrivate(pkcs8));
|
||||
return {
|
||||
public_key: b64(spki),
|
||||
wrapped_private_key: b64(wrapped),
|
||||
|
|
@ -154,10 +160,7 @@ export async function unlock(userId: number, passphrase: string, bundle: MyKeyBu
|
|||
wrapKey,
|
||||
bsrc(ub64(bundle.wrapped_private_key))
|
||||
);
|
||||
privKey = await importPrivate(pkcs8);
|
||||
convKeys.clear();
|
||||
await idbPut(userId, privKey);
|
||||
emitUnlock();
|
||||
await installKey(userId, await importPrivate(pkcs8));
|
||||
}
|
||||
|
||||
// --- per-conversation encryption ---
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue