Files
webmail/components/offline/device-wipe-host.tsx
T
Linus Rath af6cdfb997 fix: keep unsent mail through the idle and session limits, and mark a signed-out account in the switcher
The timers used to delete everything an account kept, unsent mail included.
They now follow the refused-login rule: sign out and hide, the next sign-in
brings it back, otherwise it goes after 30 days. A login without "Remember
me" that ends with the browser, an admin wipe and a wipe from settings don't
change.

- `wipeOnRefusedLogin` is now `wipeOnForcedSignOut`, since it also covers the
  limits. The server's `SESSION_TOO_OLD` goes the same way, as `session`.
- A refused account beside one that stays signed in shows in the switcher as
  needing sign-in, with the count and the date, and gets one toast.

Existing tests changed because the rule did: the session timer tests expected
a plain `logoutAll`, and the auth store tests expected no hook at the session
limit.
2026-11-03 16:36:13 +01:00

50 lines
1.9 KiB
TypeScript

'use client';
import { useEffect, useRef } from 'react';
import { useTranslations } from 'next-intl';
import { consumeWipeNote, onRefusedLoginsChanged, takeSiblingToasts, waitDate, type WipeNote } from '@/lib/devices/wipe-state';
import { startDeviceWipe } from '@/lib/offline/device-wipe';
import { useAuthStore } from '@/stores/auth-store';
import { toast } from '@/stores/toast-store';
/** What a wipe left to say, as a sentence. Counts only. */
export function wipeNoteText(note: WipeNote, t: (key: string, values?: { count: number }) => string): string {
if (note.unknown) return t('note_unknown');
return note.unsent > 0 ? t('note_unsent', { count: note.unsent }) : t('note');
}
/**
* Runs the device's side of wipes on every page, the sign-in page too, so a
* device with nobody signed in still finishes a wipe and hears about a new one.
* When another account stays signed in, the note about lost mail is a toast here;
* otherwise the sign-in page shows it. Not mounted in Lite.
*/
export function DeviceWipeHost() {
const t = useTranslations('offlineWipe');
const tRef = useRef(t);
tRef.current = t;
const signedIn = useAuthStore((state) => state.isAuthenticated);
useEffect(() => startDeviceWipe(), []);
useEffect(() => {
if (!signedIn) return;
const note = consumeWipeNote();
if (note) toast.warning(wipeNoteText(note, tRef.current), { duration: 0 });
}, [signedIn]);
// An account beside this one signed out by itself: once, when it's known what waits.
useEffect(() => {
const look = () => {
for (const wait of takeSiblingToasts(useAuthStore.getState().activeAccountId)) {
const values = { name: wait.label, date: waitDate(wait.until) };
toast.warning(wait.unsent ? tRef.current('sibling_waiting', { ...values, count: wait.unsent }) : tRef.current('sibling_kept', values), { duration: 0 });
}
};
look();
return onRefusedLoginsChanged(look);
}, []);
return null;
}