From 807d2b0d9dbbe992b22473e06365d10d6b18d832 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Tue, 4 Jan 2022 20:43:37 +0100 Subject: [PATCH] Truncate FCM messages if they are too long; This was trickier than expected; relates to #84 --- server/server.go | 23 +++++++++++++++++++++-- server/server_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/server/server.go b/server/server.go index 1401796..035821d 100644 --- a/server/server.go +++ b/server/server.go @@ -138,6 +138,7 @@ var ( const ( firebaseControlTopic = "~control" // See Android if changed emptyMessageBody = "triggered" + fcmMessageLimitReal = 4100 // see maybeTruncateFCMMessage for details ) // New instantiates a new Server. It creates the cache and adds a Firebase @@ -219,15 +220,33 @@ func createFirebaseSubscriber(conf *Config) (subscriber, error) { Priority: "high", } } - _, err := msg.Send(context.Background(), &messaging.Message{ + _, err := msg.Send(context.Background(), maybeTruncateFCMMessage(&messaging.Message{ Topic: m.Topic, Data: data, Android: androidConfig, - }) + })) return err }, nil } +// maybeTruncateFCMMessage performs best-effort truncation of FCM messages. +// The docs says the limit is 4000 characters, but the real FCM message limit is 4100 of the +// serialized payload; I tested this diligently. +func maybeTruncateFCMMessage(m *messaging.Message) *messaging.Message { + s, err := json.Marshal(m) + if err != nil { + return m + } + if len(s) > fcmMessageLimitReal { + over := len(s) - fcmMessageLimitReal + message, ok := m.Data["message"] + if ok && len(message) > over { + m.Data["message"] = message[:len(message)-over] + } + } + return m +} + // Run executes the main server. It listens on HTTP (+ HTTPS, if configured), and starts // a manager go routine to print stats and prune messages. func (s *Server) Run() error { diff --git a/server/server_test.go b/server/server_test.go index e713e60..b9bcf8b 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "encoding/json" + "firebase.google.com/go/messaging" "fmt" "github.com/stretchr/testify/require" "net/http" @@ -591,6 +592,35 @@ func TestServer_UnifiedPushDiscovery(t *testing.T) { require.Equal(t, `{"unifiedpush":{"version":1}}`+"\n", response.Body.String()) } +func TestServer_MaybeTruncateFCMMessage(t *testing.T) { + origMessage := strings.Repeat("this is a long string", 300) + origFCMMessage := &messaging.Message{ + Topic: "mytopic", + Data: map[string]string{ + "id": "abcdefg", + "time": "1641324761", + "event": "message", + "topic": "mytopic", + "priority": "0", + "tags": "", + "title": "", + "message": origMessage, + }, + Android: &messaging.AndroidConfig{ + Priority: "high", + }, + } + origMessageLength := len(origFCMMessage.Data["message"]) + serializedOrigFCMMessage, _ := json.Marshal(origFCMMessage) + require.Greater(t, len(serializedOrigFCMMessage), fcmMessageLimitReal) // Pre-condition + + truncatedFCMMessage := maybeTruncateFCMMessage(origFCMMessage) + truncatedMessageLength := len(truncatedFCMMessage.Data["message"]) + serializedTruncatedFCMMessage, _ := json.Marshal(truncatedFCMMessage) + require.Equal(t, fcmMessageLimitReal, len(serializedTruncatedFCMMessage)) + require.NotEqual(t, origMessageLength, truncatedMessageLength) +} + func newTestConfig(t *testing.T) *Config { conf := NewConfig() conf.CacheFile = filepath.Join(t.TempDir(), "cache.db")