mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-02 16:15:39 +00:00
The review of offline mode found ways to send a message twice, to lose unsent mail without a word, and to send mail without its signing, encryption or check plugin. This fixes them and the smaller findings: - each outbox message is taken for sending in one database transaction under a Web Lock per account; only a failure before the submission request (SendNotStartedError from the client) puts it back, any other one marks it "may have gone out"; read-backs after the submission no longer fail a send; a send of a closed tab turns uncertain after a minute - every sign-out the user didn't confirm names the deleted messages on the sign-in page, or in a toast when another account stays signed in - nothing is queued while an enabled plugin isn't running (after a start without the network none can); the composer says why - the outbox waits for a fresh policy and holds messages the rules have since made stricter (a required label, a required send check) - another tab switching offline mode off sticks: only switching it on may create the database - only attachments of kept messages are kept, filed with their message, deleted with it and under the cap between syncs; file names and types are sealed; folders that went away leave the scope, and a folder that couldn't be asked keeps its mail without holding the others back - a group scope that forbids offline mode waits until the account's groups are known; one server down no longer holds another's mail - an offline start only when the server is really unreachable, and the real sign-in follows by itself once it answers - the service worker files pages and chunks under the build that served them, and the shell list of another build isn't kept under this one - a raw send counts as the composer's only from the plugin running its takeover, for that identity - errors and refusals are in all languages; the banner tells "offline" from "server can't be reached"; outbox buttons name their message; changes and messages go in the order they were made
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
/**
|
|
* The app shell for offline mode (#523): tells the service worker
|
|
* (public/sw.js) to keep this build's pages and files while any account
|
|
* keeps mail on this device, and to drop them when none does.
|
|
*/
|
|
|
|
import { apiFetch, withBasePath } from '@/lib/browser-navigation';
|
|
import { currentBuildId, SW_SCOPE } from '@/lib/app-update';
|
|
|
|
/** The app's pages kept for a start without the network. */
|
|
const SHELL_PAGES = ['/', '/mail', '/settings'];
|
|
|
|
/**
|
|
* The files of this tab's build. After a deploy the server lists the new
|
|
* build's files: nothing then, rather than another build's files under this
|
|
* build's name.
|
|
*/
|
|
export async function shellFiles(): Promise<string[]> {
|
|
try {
|
|
const res = await apiFetch('/api/offline/shell', { cache: 'no-store' });
|
|
if (!res.ok) return [];
|
|
const data = (await res.json()) as { files?: unknown; buildId?: unknown };
|
|
if (data.buildId !== currentBuildId()) return [];
|
|
return Array.isArray(data.files) ? data.files.filter((f): f is string => typeof f === 'string') : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async function activeWorker(): Promise<ServiceWorker | null> {
|
|
if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) return null;
|
|
try {
|
|
const registration = await navigator.serviceWorker.getRegistration(SW_SCOPE);
|
|
return registration?.active ?? navigator.serviceWorker.controller ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function tellServiceWorker(on: boolean): Promise<void> {
|
|
const worker = await activeWorker();
|
|
if (!worker) return;
|
|
const files = on ? await shellFiles() : [];
|
|
// Without this build's files (a deploy happened), not its pages either:
|
|
// they would be the new build's.
|
|
const urls = on && files.length > 0 ? [...SHELL_PAGES.map((p) => withBasePath(p)), ...files] : [];
|
|
worker.postMessage({ type: 'offline-shell', on, urls, buildId: currentBuildId() });
|
|
}
|
|
|
|
/** The service worker's copies of the shell, from the page (also when no worker runs). */
|
|
export async function deleteShellCaches(): Promise<void> {
|
|
if (typeof caches === 'undefined') return;
|
|
try {
|
|
const keys = await caches.keys();
|
|
await Promise.all(keys.filter((key) => key.startsWith('bulwark-shell-')).map((key) => caches.delete(key)));
|
|
} catch {
|
|
/* no Cache API */
|
|
}
|
|
}
|