Web Push works, confirmed end to end against the live 0.16.19: with Chrome open and every ihasmail tab closed, a notification arrives immediately and names the sender and subject. But "closed" means ihasmail, not the browser, and the switch did not say so. Web Push is delivered over a connection the browser holds, so something of it has to be running. Observed on 2026-08-26, with Chrome fully quit and "Continue running background apps" off: nothing arrived until Chrome was started again, at which point the queued notification was delivered. Turning that setting on keeps a process alive and restores immediate delivery. Worth knowing that the queue is not indefinite -- a Web Push message carries a TTL, and one that expires before the browser comes back is dropped rather than delivered late. Being an installed PWA does not change any of this on a desktop; it changes the window, not who holds the connection. On Android it would, since the push service can wake the browser from cold. None of this is ihasmail's to fix. It is what Web Push is, and the only thing worth doing about it is not implying otherwise -- which the notification switch was quietly doing.
86 lines
4.0 KiB
TypeScript
86 lines
4.0 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { useSettings } from "@/store/settings";
|
|
import { Switch } from "@/ui/misc";
|
|
import { requestNotificationPermission, showNotification, playNewMailSound } from "@/lib/notify";
|
|
import { useSession } from "@/store/session";
|
|
import { disableWebPush, enableWebPush, webPushActive } from "@/lib/webpushEnable";
|
|
import { supportsEmailPush, webPushAvailable } from "@/lib/webpush";
|
|
import { toast } from "@/ui/toast";
|
|
|
|
export function NotificationsSettings() {
|
|
const s = useSettings((st) => st.settings);
|
|
const update = useSettings((st) => st.update);
|
|
const pushConnected = useSession((st) => st.pushConnected);
|
|
const [perm, setPerm] = useState<NotificationPermission | "unsupported">("Notification" in window ? Notification.permission : "unsupported");
|
|
const [background, setBackground] = useState(false);
|
|
const [busy, setBusy] = useState(false);
|
|
const canBackground = webPushAvailable();
|
|
useEffect(() => {
|
|
void webPushActive().then(setBackground);
|
|
}, []);
|
|
useEffect(() => {
|
|
if ("Notification" in window) setPerm(Notification.permission);
|
|
}, [s.desktopNotifications]);
|
|
return (
|
|
<div>
|
|
<h1>Notifications</h1>
|
|
<p className="lead">Live updates are delivered via JMAP push ({pushConnected ? "connected" : "reconnecting…"}).</p>
|
|
<Switch
|
|
checked={s.desktopNotifications}
|
|
onChange={async (v) => {
|
|
if (v) {
|
|
const p = await requestNotificationPermission();
|
|
setPerm(p);
|
|
if (p !== "granted") return;
|
|
}
|
|
update({ desktopNotifications: v });
|
|
}}
|
|
label="Desktop notifications while ihasmail is open"
|
|
hint={perm === "denied" ? "Notifications are blocked in your browser settings." : perm === "unsupported" ? "Not supported in this browser." : "Shows a system notification when new mail arrives in your Inbox while the tab is in the background."}
|
|
disabled={perm === "denied" || perm === "unsupported"}
|
|
/>
|
|
{/*
|
|
The distinction worth drawing for the user: the switch above needs a tab
|
|
open, this one does not. Everything before this shipped only the first
|
|
kind, while calling it "desktop notifications".
|
|
*/}
|
|
<Switch
|
|
checked={background}
|
|
disabled={!canBackground || busy || perm === "denied"}
|
|
onChange={async (v) => {
|
|
setBusy(true);
|
|
try {
|
|
if (v) {
|
|
const p = await requestNotificationPermission();
|
|
setPerm(p);
|
|
if (p !== "granted") return;
|
|
const res = await enableWebPush();
|
|
if (!res.ok) { toast.error(res.reason); return; }
|
|
setBackground(true);
|
|
toast.success("Background notifications are on");
|
|
} else {
|
|
await disableWebPush();
|
|
setBackground(false);
|
|
}
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}}
|
|
label="Notify me even when ihasmail is closed"
|
|
hint={
|
|
!canBackground
|
|
? "Needs a browser with the Push API and a mail server that publishes a push key."
|
|
: supportsEmailPush()
|
|
? "Your mail server delivers these straight to your browser, so they arrive with no ihasmail tab open, naming the sender and subject. Your browser still has to be running — if you quit it completely, notifications wait and arrive when you open it again."
|
|
: "Your mail server can wake this browser, but will not include the sender or subject. Your browser still has to be running."
|
|
}
|
|
/>
|
|
<Switch checked={s.notificationSound} onChange={(v) => update({ notificationSound: v })} label="Play a sound for new mail" />
|
|
<div className="row mt-16">
|
|
<button className="btn" onClick={() => { showNotification("ihasmail test", { body: "This is what a new-mail notification looks like." }); playNewMailSound(); }}>Test notification</button>
|
|
</div>
|
|
<p className="hint mt-8">The tab title and favicon always show your unread Inbox count.</p>
|
|
</div>
|
|
);
|
|
}
|