Files
webmail/components/ui/mailto-link.tsx
T
dealerweb 04996a3af1 fix: open address links in the built-in composer
Clicking an address in the app's own chrome - the recipient card, the
contact sidebar, the contact details - handed a mailto: URL to the OS
mail handler. In a webmail client that goes nowhere: the browser asks
which application should open the link, and the message is composed
somewhere else, if at all. Only users who registered Bulwark itself as
their system mailto handler got a usable result, and even then via a
detour through the OS into a new tab.

A new MailtoLink component sends the click to the built-in composer
instead. It stays a real anchor with an href, so "Copy email address",
middle click and assistive technology keep working; only the plain
left click is redirected, and only when a listener takes it - otherwise
the browser's own behaviour still applies. Modified clicks are left
alone.

The request reaches the mail page through a window event, the way
`bulwark:rate-limit-blocked` already does, rather than threading a
callback through three component levels and five call sites. It goes to
openMailtoDraft rather than the protocol path: an in-app click carries
its own context - the account being read - so the account picker that an
external mailto: needs would only get in the way here.

Message content is deliberately untouched. Plain text bodies render into
the main document rather than the sandboxed frame and their sanitizer
permits mailto:, so a document-wide interceptor would have swept up
untrusted links too; a component cannot be reached from message HTML at
all.
2026-08-05 14:42:24 +02:00

42 lines
1.6 KiB
TypeScript

"use client";
import type { AnchorHTMLAttributes, MouseEvent, ReactNode } from "react";
import { requestInternalMailto } from "@/lib/protocol-handlers/mailto";
interface MailtoLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href" | "onClick"> {
/** Bare address, or a full mailto: URL when subject/body are needed. */
to: string;
children: ReactNode;
}
/**
* An address in the app's own chrome (contact cards, sidebars, contact
* details). Handing a `mailto:` URL to the OS mail handler goes nowhere in a
* webmail client - unless the user happened to register Bulwark as their
* system handler - so the click opens the built-in composer instead.
*
* Deliberately still an anchor with a real href: right-click "Copy email
* address", middle-click and assistive technology all keep working, and a
* plain click is simply redirected. Modified clicks are left alone.
*
* Only for app chrome. Links inside message content are untrusted and keep
* their existing behaviour.
*/
export function MailtoLink({ to, children, ...anchorProps }: MailtoLinkProps) {
const href = to.toLowerCase().startsWith("mailto:") ? to : `mailto:${to}`;
const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {
// Let the browser handle "open in new tab/window" style clicks.
if (event.defaultPrevented || event.button !== 0) return;
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
if (!requestInternalMailto(href)) return;
event.preventDefault();
};
return (
<a href={href} onClick={handleClick} {...anchorProps}>
{children}
</a>
);
}