mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 08:15:38 +00:00
Settings > Backup & reset gets "Keep mail on this device", off until the user turns it on in this browser. While it is on: - the JMAP client hands the server's answers to an encrypted cache in IndexedDB (AES-GCM, non-extractable key) and answers from it when the network is gone: the folder list, the last 30 days of the Inbox and of the folders the user opened, bodies and attachments, at most 250 MB, oldest out first; a background sync keeps it current - read, flag, tags and moves made offline are queued and sent in order when the server answers again; deleting for good and emptying a folder are refused offline - the composer, quick reply and the S/MIME and PGP plugins' sends that complete the composer's go to an outbox once every step up to the transport ran (checks, label, signature, plugin send checks); only the finished send waits, with files attached offline - the service worker keeps this build's app shell, so the app also starts offline from the kept session and the last policy - a full sign-out, the idle sign-out and signing one account out delete it, a sign-out the user starts asks first when mail is unsent, and the login page names what an automatic sign-out deleted; a policy fetch that forbids it (devices.noOffline) wipes that account Not in Lite.
79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
/**
|
|
* What offline mode (#523) shows: which accounts keep mail on this device,
|
|
* whether the browser is offline, what waits in the outbox, how much the
|
|
* cache uses. The switch itself ("Keep mail on this device") is per account
|
|
* and per browser, in localStorage, never synced: it is off until the user
|
|
* turns it on here.
|
|
*/
|
|
|
|
import { create } from 'zustand';
|
|
import type { OutboxEntry } from './queue';
|
|
|
|
/** localStorage: the local account ids that keep mail on this device. */
|
|
export const OFFLINE_ACCOUNTS_KEY = 'bulwark-offline-accounts';
|
|
|
|
export interface OutboxRow {
|
|
id: number;
|
|
account: string;
|
|
createdAt: number;
|
|
subject: string;
|
|
to: string[];
|
|
status: OutboxEntry['status'];
|
|
error?: string;
|
|
}
|
|
|
|
interface OfflineState {
|
|
/** Accounts with "Keep mail on this device" on. */
|
|
enabled: string[];
|
|
/** The browser says it has no network. */
|
|
browserOffline: boolean;
|
|
/** The last request to the mail server failed on the network. */
|
|
networkDown: boolean;
|
|
/** The app started without the server, from the mail on this device. */
|
|
startedOffline: boolean;
|
|
outbox: OutboxRow[];
|
|
pendingChanges: number;
|
|
usageBytes: number;
|
|
/** Accounts whose mail is being brought onto the device right now. */
|
|
syncing: string[];
|
|
lastSyncedAt: Record<string, number>;
|
|
}
|
|
|
|
function readEnabled(): string[] {
|
|
try {
|
|
const raw = typeof localStorage === 'undefined' ? null : localStorage.getItem(OFFLINE_ACCOUNTS_KEY);
|
|
const parsed = raw ? JSON.parse(raw) : [];
|
|
return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === 'string') : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export const useOfflineStore = create<OfflineState>()(() => ({
|
|
enabled: readEnabled(),
|
|
browserOffline: typeof navigator !== 'undefined' && navigator.onLine === false,
|
|
networkDown: false,
|
|
startedOffline: false,
|
|
outbox: [],
|
|
pendingChanges: 0,
|
|
usageBytes: 0,
|
|
syncing: [],
|
|
lastSyncedAt: {},
|
|
}));
|
|
|
|
export function writeEnabled(enabled: string[]): void {
|
|
useOfflineStore.setState({ enabled });
|
|
try {
|
|
if (enabled.length) localStorage.setItem(OFFLINE_ACCOUNTS_KEY, JSON.stringify(enabled));
|
|
else localStorage.removeItem(OFFLINE_ACCOUNTS_KEY);
|
|
} catch {
|
|
/* storage unavailable */
|
|
}
|
|
}
|
|
|
|
/** The browser is offline, or the mail server couldn't be reached. */
|
|
export function isOfflineNow(): boolean {
|
|
const { browserOffline, networkDown } = useOfflineStore.getState();
|
|
return browserOffline || networkDown;
|
|
}
|