Web: Fix clear=true on action buttons not clearing the notification

This commit is contained in:
binwiederhier
2026-02-05 09:40:18 -08:00
parent 6978fa69a8
commit 1b554d5b08
4 changed files with 44 additions and 5 deletions

View File

@@ -1688,6 +1688,7 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Bug fixes + maintenance:**
* Web: Fix `clear=true` on action buttons not clearing the notification ([#1029](https://github.com/binwiederhier/ntfy/issues/1029), thanks to [@ElFishi](https://github.com/ElFishi) for reporting)
* Fix crash when commit string is shorter than 7 characters in non-GitHub-Action builds ([#1493](https://github.com/binwiederhier/ntfy/issues/1493), thanks to [@cyrinux](https://github.com/cyrinux) for reporting)
* Fix log spam from `http: response.WriteHeader on hijacked connection` for WebSocket errors ([#1362](https://github.com/binwiederhier/ntfy/issues/1362), thanks to [@bonfiresh](https://github.com/bonfiresh) for reporting)
* Web: Fix Markdown message line height to match plain text (1.5 instead of 1.2) ([#1139](https://github.com/binwiederhier/ntfy/issues/1139), thanks to [@etfz](https://github.com/etfz) for reporting)

View File

@@ -237,8 +237,24 @@ const handleClick = async (event) => {
if (event.action) {
const action = event.notification.data.message.actions.find(({ label }) => event.action === label);
// Helper to clear notification and mark as read
const clearNotification = async () => {
event.notification.close();
const { subscriptionId, message: msg } = event.notification.data;
const seqId = msg.sequence_id || msg.id;
if (subscriptionId && seqId) {
const db = await dbAsync();
await db.notifications.where({ subscriptionId, sequenceId: seqId }).modify({ new: 0 });
const badgeCount = await db.notifications.where({ new: 1 }).count();
self.navigator.setAppBadge?.(badgeCount);
}
};
if (action.action === "view") {
self.clients.openWindow(action.url);
if (action.clear) {
await clearNotification();
}
} else if (action.action === "http") {
try {
const response = await fetch(action.url, {
@@ -250,6 +266,11 @@ const handleClick = async (event) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
// Only clear on success
if (action.clear) {
await clearNotification();
}
} catch (e) {
console.error("[ServiceWorker] Error performing http action", e);
self.registration.showNotification(`${t("notifications_actions_failed_notification")}: ${action.label} (${action.action})`, {
@@ -259,10 +280,6 @@ const handleClick = async (event) => {
});
}
}
if (action.clear) {
event.notification.close();
}
} else if (message.click) {
self.clients.openWindow(message.click);

View File

@@ -60,6 +60,7 @@ export const toNotificationParams = ({ message, defaultTitle, topicRoute, baseUr
const image = isImage(message.attachment) ? message.attachment.url : undefined;
const sequenceId = message.sequence_id || message.id;
const tag = notificationTag(baseUrl, topic, sequenceId);
const subscriptionId = `${baseUrl}/${topic}`;
// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API
return [
@@ -75,6 +76,7 @@ export const toNotificationParams = ({ message, defaultTitle, topicRoute, baseUr
silent: false,
// This is used by the notification onclick event
data: {
subscriptionId,
message,
topicRoute,
},

View File

@@ -39,6 +39,7 @@ import {
import { formatMessage, formatTitle, isImage } from "../app/notificationUtils";
import { LightboxBackdrop, Paragraph, VerticallyCenteredContainer } from "./styles";
import subscriptionManager from "../app/SubscriptionManager";
import notifier from "../app/Notifier";
import priority1 from "../img/priority-1.svg";
import priority2 from "../img/priority-2.svg";
import priority4 from "../img/priority-4.svg";
@@ -508,6 +509,15 @@ const updateActionStatus = (notification, action, progress, error) => {
});
};
const clearNotification = async (notification) => {
console.log(`[Notifications] Clearing notification ${notification.id}`);
const subscription = await subscriptionManager.get(notification.subscriptionId);
if (subscription) {
await notifier.cancel(subscription, notification);
}
await subscriptionManager.markNotificationRead(notification.id);
};
const performHttpAction = async (notification, action) => {
console.log(`[Notifications] Performing HTTP user action`, action);
try {
@@ -523,6 +533,9 @@ const performHttpAction = async (notification, action) => {
const success = response.status >= 200 && response.status <= 299;
if (success) {
updateActionStatus(notification, action, ACTION_PROGRESS_SUCCESS, null);
if (action.clear) {
await clearNotification(notification);
}
} else {
updateActionStatus(notification, action, ACTION_PROGRESS_FAILED, `${action.label}: Unexpected response HTTP ${response.status}`);
}
@@ -548,10 +561,16 @@ const UserAction = (props) => {
);
}
if (action.action === "view") {
const handleClick = () => {
openUrl(action.url);
if (action.clear) {
clearNotification(notification);
}
};
return (
<Tooltip title={t("notifications_actions_open_url_title", { url: action.url })}>
<Button
onClick={() => openUrl(action.url)}
onClick={handleClick}
aria-label={t("notifications_actions_open_url_title", {
url: action.url,
})}