Files
webmail/lib/pwa-notify/tab-reply.ts
T
Linus Rath 51c09a1a69 fix: never let a notification's buttons go round plugins, session limits or Trash
Review of the notification actions. With a plugin that may stop a move
or read change, or a silent tab holding the login, the message now
opens instead of the route acting. The route answers 401 under a
session limit, and neither path touches a message since moved to Trash
or Junk. A tab that thawed declines an old click, Archive follows the
gate of the notification's login and files into a shared-out Archive
as the app's button does, Reply opens a composer in the Pro shell, the
route retries once when another client made the folder first and
leaves no empty folder behind, and logins match however the server
address is written.

Two assertions of this feature's own tests change: the route answers
401, not 403, when a login's scopes can't be worked out, since that now
also covers session limits; a tab's message to the page now carries
`at`.

The Pro shell's mail surface now reads a parked link's query from the
handoff: the address bar may have none by its first render, which lost
`?slot=` and `?owner=` there too.
2026-11-03 06:28:04 +01:00

28 lines
1.1 KiB
TypeScript

import { create } from 'zustand';
/** A tab that hasn't got the message by then has been left behind. */
const MAX_AGE_MS = 2 * 60 * 1000;
interface TabReplyState {
pending: { emailId: string; text: string; at: number } | null;
}
/**
* A notification's Reply in the Pro shell, where a message lives in a tab of
* its own that fetches it. Waits here, not in the tab store: that one is kept
* in localStorage, and a reply being typed is not for keeping.
*/
export const useNotificationTabReply = create<TabReplyState>(() => ({ pending: null }));
export function requestTabReply(emailId: string, text: string): void {
useNotificationTabReply.setState({ pending: { emailId, text, at: Date.now() } });
}
/** The text for a reply to this message, once; null when none waits. */
export function takeTabReply(emailId: string): string | null {
const { pending } = useNotificationTabReply.getState();
if (!pending || pending.emailId !== emailId) return null;
useNotificationTabReply.setState({ pending: null });
return Date.now() - pending.at < MAX_AGE_MS ? pending.text : null;
}