From af76a2606d8e72810a761b468db8537baf0ce04d Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Wed, 25 May 2022 21:39:46 -0400 Subject: [PATCH] Support for Firebase ~poll keepalive topic that wakes up iOS devices every 20 minutes --- docs/releases.md | 4 ++++ server/config.go | 5 ++++- server/server.go | 8 +++++++- server/server_firebase.go | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/releases.md b/docs/releases.md index 19db7a01..29c3deb9 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -38,6 +38,10 @@ some known issues, which will be addressed in follow-up releases). ## ntfy server v1.24.0 (UNRELEASED) +**Features:** + +* Regularly send Firebase keepalive messages to ~poll topic to support self-hosted servers (no ticket) + **Bugs:** * Support emails without `Content-Type` ([#265](https://github.com/binwiederhier/ntfy/issues/265), thanks to [@dmbonsall](https://github.com/dmbonsall)) diff --git a/server/config.go b/server/config.go index ea34c6af..d36d5c66 100644 --- a/server/config.go +++ b/server/config.go @@ -13,7 +13,8 @@ const ( DefaultAtSenderInterval = 10 * time.Second DefaultMinDelay = 10 * time.Second DefaultMaxDelay = 3 * 24 * time.Hour - DefaultFirebaseKeepaliveInterval = 3 * time.Hour // Not too frequently to save battery + DefaultFirebaseKeepaliveInterval = 3 * time.Hour // ~control topic (Android), not too frequently to save battery + DefaultFirebasePollInterval = 20 * time.Minute // ~poll topic (iOS), max. 2-3 times per hour (see docs) ) // Defines all global and per-visitor limits @@ -67,6 +68,7 @@ type Config struct { WebRootIsApp bool AtSenderInterval time.Duration FirebaseKeepaliveInterval time.Duration + FirebasePollInterval time.Duration SMTPSenderAddr string SMTPSenderUser string SMTPSenderPass string @@ -117,6 +119,7 @@ func NewConfig() *Config { MaxDelay: DefaultMaxDelay, AtSenderInterval: DefaultAtSenderInterval, FirebaseKeepaliveInterval: DefaultFirebaseKeepaliveInterval, + FirebasePollInterval: DefaultFirebasePollInterval, TotalTopicLimit: DefaultTotalTopicLimit, VisitorSubscriptionLimit: DefaultVisitorSubscriptionLimit, VisitorAttachmentTotalSizeLimit: DefaultVisitorAttachmentTotalSizeLimit, diff --git a/server/server.go b/server/server.go index 1a643c23..55562d37 100644 --- a/server/server.go +++ b/server/server.go @@ -91,6 +91,7 @@ var ( const ( firebaseControlTopic = "~control" // See Android if changed + firebasePollTopic = "~poll" // See iOS if changed emptyMessageBody = "triggered" // Used if message body is empty defaultAttachmentMessage = "You received a file: %s" // Used if message body is empty, and there is an attachment encodingBase64 = "base64" @@ -1074,7 +1075,12 @@ func (s *Server) runFirebaseKeepaliver() { select { case <-time.After(s.config.FirebaseKeepaliveInterval): if err := s.firebase(newKeepaliveMessage(firebaseControlTopic)); err != nil { - log.Printf("error sending Firebase keepalive message: %s", err.Error()) + log.Printf("error sending Firebase keepalive message to %s: %s", firebaseControlTopic, err.Error()) + } + case <-time.After(s.config.FirebasePollInterval): + log.Printf("Sending to timer topic %s", firebasePollTopic) + if err := s.firebase(newKeepaliveMessage(firebasePollTopic)); err != nil { + log.Printf("error sending Firebase keepalive message to %s: %s", firebasePollTopic, err.Error()) } case <-s.closeChan: return diff --git a/server/server_firebase.go b/server/server_firebase.go index 4bcbfd27..ad0da0e2 100644 --- a/server/server_firebase.go +++ b/server/server_firebase.go @@ -80,6 +80,24 @@ func toFirebaseMessage(m *message, auther auth.Auther) (*messaging.Message, erro "event": m.Event, "topic": m.Topic, } + // Silent notification; only 2-3 per hour are allowed; delivery not guaranteed + // See https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app + apnsData := make(map[string]interface{}) + for k, v := range data { + apnsData[k] = v + } + apnsConfig = &messaging.APNSConfig{ + Headers: map[string]string{ + "apns-push-type": "background", + "apns-priority": "5", + }, + Payload: &messaging.APNSPayload{ + Aps: &messaging.Aps{ + ContentAvailable: true, + }, + CustomData: apnsData, + }, + } case messageEvent: allowForward := true if auther != nil {