Compare commits

...

3 Commits

Author SHA1 Message Date
binwiederhier
0ad06e808f Self-review 2026-01-15 22:14:56 -05:00
binwiederhier
2cc4bf7d28 Fix webpush stuff 2026-01-15 21:55:20 -05:00
binwiederhier
5ae3774f19 Refine, docs 2026-01-15 19:06:05 -05:00
7 changed files with 53 additions and 30 deletions

View File

@@ -1603,22 +1603,28 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Features:**
* Support for [updating and deleting notifications](publish.md#updating-deleting-notifications): You can now update,
clear (mark as read), or delete notifications using a sequence ID. This enables use cases like progress updates,
replacing outdated notifications, or dismissing notifications from all clients.
* Support for [updating and deleting notifications](publish.md#updating-deleting-notifications)
([#303](https://github.com/binwiederhier/ntfy/issues/303), [#1536](https://github.com/binwiederhier/ntfy/pull/1536),
[ntfy-android#151](https://github.com/binwiederhier/ntfy-android/pull/151), thanks to [@wunter8](https://github.com/wunter8)
for the initial implementation)
### ntfy Android app v1.22.x (UNRELEASED)
**Features:**
* Support for self-signed certs and client certs for mTLS (#215, #530, ntfy-android#149)
* Support for [updating and deleting notifications](publish.md#updating-deleting-notifications)
([#303](https://github.com/binwiederhier/ntfy/issues/303), [#1536](https://github.com/binwiederhier/ntfy/pull/1536),
[ntfy-android#151](https://github.com/binwiederhier/ntfy-android/pull/151), thanks to [@wunter8](https://github.com/wunter8)
for the initial implementation)
* Support for self-signed certs and client certs for mTLS ([#215](https://github.com/binwiederhier/ntfy/issues/215),
[#530](https://github.com/binwiederhier/ntfy/issues/530), [ntfy-android#149](https://github.com/binwiederhier/ntfy-android/pull/149),
thanks to [@cyb3rko](https://github.com/cyb3rko) for reviewing)
* Connection error dialog to help diagnose connection issues
* Support for [updating and deleting notifications](publish.md#updating-deleting-notifications): Notifications with
the same sequence ID are updated in place, and `message_delete`/`message_clear` events dismiss notifications
**Bug fixes + maintenance:**
* Use server-specific user for attachment downloads (#1529, thanks to @ManInDark for reporting)
* Fix crash in sharing dialog (thanks to @rogeliodh)
* Use server-specific user for attachment downloads ([#1529](https://github.com/binwiederhier/ntfy/issues/1529),
thanks to [@ManInDark](https://github.com/ManInDark) for reporting and testing)
* Fix crash in sharing dialog (thanks to [@rogeliodh](https://github.com/rogeliodh))
* Fix crash when exiting multi-delete in detail view
* Fix potential crashes with icon downloader and backuper

View File

@@ -3,8 +3,9 @@ package server
import (
"encoding/json"
"fmt"
"heckel.io/ntfy/v2/log"
"net/http"
"heckel.io/ntfy/v2/log"
)
// errHTTP is a generic HTTP error for any non-200 HTTP error
@@ -125,7 +126,7 @@ var (
errHTTPBadRequestInvalidUsername = &errHTTP{40046, http.StatusBadRequest, "invalid request: invalid username", "", nil}
errHTTPBadRequestTemplateFileNotFound = &errHTTP{40047, http.StatusBadRequest, "invalid request: template file not found", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateFileInvalid = &errHTTP{40048, http.StatusBadRequest, "invalid request: template file invalid", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestSequenceIDInvalid = &errHTTP{40049, http.StatusBadRequest, "invalid request: sequence ID invalid", "https://ntfy.sh/docs/publish/#TODO", nil}
errHTTPBadRequestSequenceIDInvalid = &errHTTP{40049, http.StatusBadRequest, "invalid request: sequence ID invalid", "https://ntfy.sh/docs/publish/#updating-deleting-notifications", nil}
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}

View File

@@ -6,7 +6,13 @@ import { clientsClaim } from "workbox-core";
import { dbAsync } from "../src/app/db";
import { badge, icon, messageWithSequenceId, toNotificationParams } from "../src/app/notificationUtils";
import initI18n from "../src/app/i18n";
import { EVENT_MESSAGE, EVENT_MESSAGE_CLEAR, EVENT_MESSAGE_DELETE, EVENT_SUBSCRIPTION_EXPIRING } from "../src/app/events";
import {
EVENT_MESSAGE,
EVENT_MESSAGE_CLEAR,
EVENT_MESSAGE_DELETE,
WEBPUSH_EVENT_MESSAGE,
WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING,
} from "../src/app/events";
/**
* General docs for service workers and PWAs:
@@ -161,19 +167,26 @@ const handlePushUnknown = async (data) => {
* @param {object} data see server/types.go, type webPushPayload
*/
const handlePush = async (data) => {
const { message } = data;
// This logic is (partially) duplicated in
// - Android: SubscriberService::onNotificationReceived()
// - Android: FirebaseService::onMessageReceived()
// - Web app: hooks.js:handleNotification()
// - Web app: sw.js:handleMessage(), sw.js:handleMessageClear(), ...
if (message.event === EVENT_MESSAGE) {
await handlePushMessage(data);
} else if (message.event === EVENT_MESSAGE_DELETE) {
await handlePushMessageDelete(data);
} else if (message.event === EVENT_MESSAGE_CLEAR) {
await handlePushMessageClear(data);
} else if (message.event === EVENT_SUBSCRIPTION_EXPIRING) {
await handlePushSubscriptionExpiring(data);
} else {
await handlePushUnknown(data);
if (data.event === WEBPUSH_EVENT_MESSAGE) {
const { message } = data;
if (message.event === EVENT_MESSAGE) {
return await handlePushMessage(data);
} else if (message.event === EVENT_MESSAGE_DELETE) {
return await handlePushMessageDelete(data);
} else if (message.event === EVENT_MESSAGE_CLEAR) {
return await handlePushMessageClear(data);
}
} else if (data.event === WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING) {
return await handlePushSubscriptionExpiring(data);
}
return await handlePushUnknown(data);
};
/**

View File

@@ -26,7 +26,7 @@ class Notifier {
subscriptionId: subscription.id,
message: notification,
defaultTitle,
topicRoute: new URL(routes.forSubscription(subscription), window.location.origin).toString()
topicRoute: new URL(routes.forSubscription(subscription), window.location.origin).toString(),
})
);
}
@@ -40,7 +40,7 @@ class Notifier {
console.log(`[Notifier] Cancelling notification with ${tag}`);
const registration = await this.serviceWorkerRegistration();
const notifications = await registration.getNotifications({ tag });
notifications.forEach(n => n.close());
notifications.forEach((n) => n.close());
} catch (e) {
console.log(`[Notifier] Error cancelling notification`, e);
}
@@ -72,7 +72,7 @@ class Notifier {
if (hasWebPushTopics) {
return pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlB64ToUint8Array(config.web_push_public_key)
applicationServerKey: urlB64ToUint8Array(config.web_push_public_key),
});
}

View File

@@ -7,7 +7,9 @@ export const EVENT_MESSAGE = "message";
export const EVENT_MESSAGE_DELETE = "message_delete";
export const EVENT_MESSAGE_CLEAR = "message_clear";
export const EVENT_POLL_REQUEST = "poll_request";
export const EVENT_SUBSCRIPTION_EXPIRING = "subscription_expiring";
export const WEBPUSH_EVENT_MESSAGE = "message";
export const WEBPUSH_EVENT_SUBSCRIPTION_EXPIRING = "subscription_expiring";
// Check if an event is a notification event (message, delete, or read)
export const isNotificationEvent = (event) => event === EVENT_MESSAGE || event === EVENT_MESSAGE_DELETE || event === EVENT_MESSAGE_CLEAR;

View File

@@ -51,8 +51,11 @@ export const useConnectionListeners = (account, subscriptions, users, webPushTop
};
const handleNotification = async (subscriptionId, notification) => {
// Note: This logic is duplicated in the Android app in SubscriberService::onNotificationReceived()
// and FirebaseService::handleMessage().
// This logic is (partially) duplicated in
// - Android: SubscriberService::onNotificationReceived()
// - Android: FirebaseService::onMessageReceived()
// - Web app: hooks.js:handleNotification()
// - Web app: sw.js:handleMessage(), sw.js:handleMessageClear(), ...
if (notification.event === EVENT_MESSAGE_DELETE && notification.sequence_id) {
await subscriptionManager.deleteNotificationBySequenceId(subscriptionId, notification.sequence_id);

View File

@@ -7,8 +7,6 @@ const intervalMS = 60 * 60 * 1000;
// https://vite-pwa-org.netlify.app/guide/periodic-sw-updates.html
const registerSW = () => {
console.log("[ServiceWorker] Registering service worker");
console.log("[ServiceWorker] serviceWorker in navigator:", "serviceWorker" in navigator);
if (!("serviceWorker" in navigator)) {
console.warn("[ServiceWorker] Service workers not supported");
return;