mirror of
https://github.com/multipleof4/ntfy.git
synced 2026-10-12 14:55:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e8787e70e |
@@ -492,6 +492,11 @@ You can set the priority with the header `X-Priority` (or any of its aliases: `P
|
|||||||
<figcaption>Detail view of priority notifications</figcaption>
|
<figcaption>Detail view of priority notifications</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
|
On **iOS**, max priority (`5`) messages are delivered as [critical alerts](https://developer.apple.com/documentation/usernotifications/unnotificationinterruptionlevel/critical),
|
||||||
|
which break through silent mode and Do Not Disturb and play a sound at full volume. You must grant ntfy
|
||||||
|
permission to send critical alerts (the app asks for it on first launch; it can also be toggled in
|
||||||
|
**iOS Settings > Notifications > ntfy**).
|
||||||
|
|
||||||
## Tags & emojis 🥳 🎉
|
## Tags & emojis 🥳 🎉
|
||||||
_Supported on:_ :material-android: :material-apple: :material-firefox:
|
_Supported on:_ :material-android: :material-apple: :material-firefox:
|
||||||
|
|
||||||
|
|||||||
@@ -1948,6 +1948,12 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
|
|||||||
|
|
||||||
## Not released yet
|
## Not released yet
|
||||||
|
|
||||||
|
### ntfy server v2.25.0 (UNRELEASED)
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
|
||||||
|
* Send priority 5 (max/urgent) messages as iOS critical alerts (APNs critical sound + `interruption-level`), so they bypass silent mode and Do Not Disturb ([ntfy-ios#44](https://github.com/binwiederhier/ntfy-ios/pull/44), thanks to [@am7590](https://github.com/am7590) for the iOS app contribution)
|
||||||
|
|
||||||
### ntfy Android v1.25.x (UNRELEASED)
|
### ntfy Android v1.25.x (UNRELEASED)
|
||||||
|
|
||||||
This release makes the "connection lost" alert configurable and turns it off by default. Folks did not like it and many reached out
|
This release makes the "connection lost" alert configurable and turns it off by default. Folks did not like it and many reached out
|
||||||
|
|||||||
@@ -241,16 +241,29 @@ func createAPNSAlertConfig(m *model.Message, data map[string]string) *messaging.
|
|||||||
for k, v := range data {
|
for k, v := range data {
|
||||||
apnsData[k] = v
|
apnsData[k] = v
|
||||||
}
|
}
|
||||||
|
aps := &messaging.Aps{
|
||||||
|
MutableContent: true,
|
||||||
|
Alert: &messaging.ApsAlert{
|
||||||
|
Title: m.Title,
|
||||||
|
Body: maybeTruncateAPNSBodyMessage(m.Message),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
headers := map[string]string{"apns-push-type": "alert"}
|
||||||
|
|
||||||
|
// Critical alerts (iOS): max priority messages bypass silent mode / Do Not Disturb. The iOS
|
||||||
|
// Notification Service Extension re-applies the critical sound based on priority, but we also
|
||||||
|
// flag the raw payload as critical (sound dict + interruption-level), so it stays critical even
|
||||||
|
// if the NSE never runs (e.g. when it exceeds its time budget or is dropped under memory pressure).
|
||||||
|
if m.Priority >= 5 {
|
||||||
|
aps.CriticalSound = &messaging.CriticalSound{Critical: true, Name: "default", Volume: 1.0}
|
||||||
|
aps.CustomData = map[string]any{"interruption-level": "critical"}
|
||||||
|
headers["apns-priority"] = "10"
|
||||||
|
}
|
||||||
return &messaging.APNSConfig{
|
return &messaging.APNSConfig{
|
||||||
|
Headers: headers,
|
||||||
Payload: &messaging.APNSPayload{
|
Payload: &messaging.APNSPayload{
|
||||||
CustomData: apnsData,
|
CustomData: apnsData,
|
||||||
Aps: &messaging.Aps{
|
Aps: aps,
|
||||||
MutableContent: true,
|
|
||||||
Alert: &messaging.ApsAlert{
|
|
||||||
Title: m.Title,
|
|
||||||
Body: maybeTruncateAPNSBodyMessage(m.Message),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,9 @@ func TestToFirebaseMessage_Message_Normal_Allowed(t *testing.T) {
|
|||||||
Priority: "high",
|
Priority: "high",
|
||||||
}, fbm.Android)
|
}, fbm.Android)
|
||||||
require.Equal(t, &messaging.APNSConfig{
|
require.Equal(t, &messaging.APNSConfig{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"apns-push-type": "alert",
|
||||||
|
},
|
||||||
Payload: &messaging.APNSPayload{
|
Payload: &messaging.APNSPayload{
|
||||||
Aps: &messaging.Aps{
|
Aps: &messaging.Aps{
|
||||||
MutableContent: true,
|
MutableContent: true,
|
||||||
@@ -248,6 +251,42 @@ func TestToFirebaseMessage_Message_Normal_Not_Allowed(t *testing.T) {
|
|||||||
}, fbm.Data)
|
}, fbm.Data)
|
||||||
require.Equal(t, "", fbm.APNS.Payload.Aps.Alert.Title)
|
require.Equal(t, "", fbm.APNS.Payload.Aps.Alert.Title)
|
||||||
require.Equal(t, "New message", fbm.APNS.Payload.Aps.Alert.Body)
|
require.Equal(t, "New message", fbm.APNS.Payload.Aps.Alert.Body)
|
||||||
|
// Priority is kept when downgrading to a poll request (see toPollRequest), so a priority 5
|
||||||
|
// message still wakes iOS as a critical alert to poll for the message.
|
||||||
|
require.Equal(t, "10", fbm.APNS.Headers["apns-priority"])
|
||||||
|
require.True(t, fbm.APNS.Payload.Aps.CriticalSound.Critical)
|
||||||
|
require.Equal(t, "critical", fbm.APNS.Payload.Aps.CustomData["interruption-level"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToFirebaseMessage_Message_Critical(t *testing.T) {
|
||||||
|
m := model.NewDefaultMessage("mytopic", "this is urgent")
|
||||||
|
m.Priority = 5
|
||||||
|
m.Title = "wake up"
|
||||||
|
fbm, err := toFirebaseMessage(m, &testAuther{Allow: true})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Critical alerts use apns-priority 10, a critical sound dict, and interruption-level "critical"
|
||||||
|
// so the iOS device treats the message as critical (bypassing silent mode / Do Not Disturb).
|
||||||
|
require.Equal(t, "alert", fbm.APNS.Headers["apns-push-type"])
|
||||||
|
require.Equal(t, "10", fbm.APNS.Headers["apns-priority"])
|
||||||
|
require.NotNil(t, fbm.APNS.Payload.Aps.CriticalSound)
|
||||||
|
require.True(t, fbm.APNS.Payload.Aps.CriticalSound.Critical)
|
||||||
|
require.Equal(t, "default", fbm.APNS.Payload.Aps.CriticalSound.Name)
|
||||||
|
require.Equal(t, 1.0, fbm.APNS.Payload.Aps.CriticalSound.Volume)
|
||||||
|
require.Equal(t, "critical", fbm.APNS.Payload.Aps.CustomData["interruption-level"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToFirebaseMessage_Message_NotCritical(t *testing.T) {
|
||||||
|
m := model.NewDefaultMessage("mytopic", "this is normal")
|
||||||
|
m.Priority = 4
|
||||||
|
fbm, err := toFirebaseMessage(m, &testAuther{Allow: true})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Priority < 5 is a regular alert: no critical sound and no interruption-level.
|
||||||
|
require.Equal(t, "alert", fbm.APNS.Headers["apns-push-type"])
|
||||||
|
require.Empty(t, fbm.APNS.Headers["apns-priority"])
|
||||||
|
require.Nil(t, fbm.APNS.Payload.Aps.CriticalSound)
|
||||||
|
require.Nil(t, fbm.APNS.Payload.Aps.CustomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToFirebaseMessage_PollRequest(t *testing.T) {
|
func TestToFirebaseMessage_PollRequest(t *testing.T) {
|
||||||
@@ -257,6 +296,9 @@ func TestToFirebaseMessage_PollRequest(t *testing.T) {
|
|||||||
require.Equal(t, "mytopic", fbm.Topic)
|
require.Equal(t, "mytopic", fbm.Topic)
|
||||||
require.Nil(t, fbm.Android)
|
require.Nil(t, fbm.Android)
|
||||||
require.Equal(t, &messaging.APNSConfig{
|
require.Equal(t, &messaging.APNSConfig{
|
||||||
|
Headers: map[string]string{
|
||||||
|
"apns-push-type": "alert",
|
||||||
|
},
|
||||||
Payload: &messaging.APNSPayload{
|
Payload: &messaging.APNSPayload{
|
||||||
Aps: &messaging.Aps{
|
Aps: &messaging.Aps{
|
||||||
MutableContent: true,
|
MutableContent: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user