mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
/**
|
|
* Composers that hold text the server may not have yet. An automatic
|
|
* sign-out (the organisation's idle timeout or maximum session length) saves
|
|
* them as drafts before the credentials go: each open composer registers a
|
|
* `flush` that runs its own draft save, and the host of a minimised composer
|
|
* (its state lives only in the host's memory) registers one that reopens it
|
|
* so the composer can save itself.
|
|
*
|
|
* A flush that hangs must not keep the user signed in, so flushComposers
|
|
* waits at most `timeoutMs` for all of them together.
|
|
*/
|
|
|
|
export type ComposeFlush = () => Promise<void>;
|
|
|
|
let nextId = 1;
|
|
const flushes = new Map<number, ComposeFlush>();
|
|
const waiters = new Set<(flush: ComposeFlush) => void>();
|
|
|
|
/** Register a composer's flush; returns the unregister function. */
|
|
export function registerComposeFlush(flush: ComposeFlush): () => void {
|
|
const id = nextId++;
|
|
flushes.set(id, flush);
|
|
for (const waiter of [...waiters]) waiter(flush);
|
|
return () => {
|
|
flushes.delete(id);
|
|
};
|
|
}
|
|
|
|
/**
|
|
* The flush of the next composer to register, or null after `timeoutMs`.
|
|
* A minimised composer's host reopens it and waits for it here.
|
|
*/
|
|
export function waitForComposeFlush(timeoutMs: number): Promise<ComposeFlush | null> {
|
|
return new Promise((resolve) => {
|
|
const waiter = (flush: ComposeFlush) => {
|
|
clearTimeout(timer);
|
|
waiters.delete(waiter);
|
|
resolve(flush);
|
|
};
|
|
const timer = setTimeout(() => {
|
|
waiters.delete(waiter);
|
|
resolve(null);
|
|
}, timeoutMs);
|
|
waiters.add(waiter);
|
|
});
|
|
}
|
|
|
|
export interface FlushResult {
|
|
/** Flushes that were registered when the sign-out started. */
|
|
total: number;
|
|
/** Flushes that finished (or failed) in time. */
|
|
settled: number;
|
|
/** Flushes that reported a save the server didn't take (threw). */
|
|
failed: number;
|
|
timedOut: boolean;
|
|
}
|
|
|
|
/** Run every registered flush and wait for them, but no longer than `timeoutMs`. */
|
|
export async function flushComposers(timeoutMs: number): Promise<FlushResult> {
|
|
const pending = [...flushes.values()];
|
|
if (pending.length === 0) return { total: 0, settled: 0, failed: 0, timedOut: false };
|
|
let settled = 0;
|
|
let failed = 0;
|
|
const all = Promise.all(pending.map(async (flush) => {
|
|
try {
|
|
await flush();
|
|
} catch {
|
|
// The sign-out goes on; the login page says a draft wasn't saved.
|
|
failed += 1;
|
|
}
|
|
settled += 1;
|
|
}));
|
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
const timedOut = await Promise.race([
|
|
all.then(() => false),
|
|
new Promise<boolean>((resolve) => { timer = setTimeout(() => resolve(true), timeoutMs); }),
|
|
]);
|
|
clearTimeout(timer);
|
|
return { total: pending.length, settled, failed, timedOut };
|
|
}
|
|
|
|
/** Test helper: forget every registration. */
|
|
export function resetComposeFlushRegistry(): void {
|
|
flushes.clear();
|
|
waiters.clear();
|
|
}
|