Files
webmail/lib/popout/support.ts
T
Linus Rath b79781cc94 fix: never lose a draft moving between windows and keep credentials off the window channel
Review of the pop-out work found ways to lose text, a wrong sign-out and
changes reaching ordinary browser tabs.

- Drafts: a closing window hands text over only to a main window holding
  its login, and asks instead when the last save failed, the browser is
  offline, a save is running or plugins could still have a say. A failed
  rescue goes to "Continue draft". The opening window lets go only once
  the composer is on screen, also for Edit draft in a message window.
  Send options travel with the draft. Closing during a send asks first.
- Sessions: answers are encrypted for the window that asked, and only a
  window holding the login answers. A renewal queued on the lock can be
  called off by a sign-out, signs nobody out once its account is gone, and
  takes the cached token after waiting (Rauthy `nbf`, #552).
- Ordinary tabs run none of the host. No lock is held for life.
- A message window hides after a refused session and waits for its
  folders; a composer stays on screen with its text. Another tab dropping a
  login it couldn't restore no longer closes pop-outs.
- Plain-text mail stays selectable in the desktop app. Folder save renames
  reserved and shell names and skips one refused name. All display-mode
  checks know the overlay. The attachment bar keeps to one row.

The earlier test that accepted text being gone after a failed rescue now
asserts that it reaches the "Continue draft" slot.
2026-11-03 08:37:10 +01:00

29 lines
1.3 KiB
TypeScript

/**
* Whether "Open in new window" is on offer: an installed app or the desktop
* app, on a device with windows. In a browser tab, a tab does the same job
* and the app behaves as it always has.
*/
import { IS_LITE } from '@/lib/lite';
import { isStandaloneDisplayMode } from '@/lib/standalone-display';
import '@/lib/desktop/types';
import { canSealSessions } from './sealed';
export function hasDesktopWindows(): boolean {
return typeof window !== 'undefined' && !!window.bulwarkDesktop?.windows;
}
/** Display mode is `browser` in Electron popups too, hence the bridge check. */
export function popoutsAvailable(): boolean {
// Lite has no such route: a static export can't list every message id.
if (IS_LITE || typeof window === 'undefined') return false;
// Sharing the session and renewing tokens safely across windows need these.
if (typeof BroadcastChannel === 'undefined' || !navigator.locks || !canSealSessions()) return false;
if (hasDesktopWindows()) return true;
if (!isStandaloneDisplayMode()) return false;
// A home-screen app on a phone or tablet has no second window: window.open
// leaves for the browser there.
if ((window.navigator as Navigator & { standalone?: boolean }).standalone === true) return false;
return typeof window.matchMedia === 'function' && window.matchMedia('(pointer: fine)').matches;
}