Просмотр исходного кода

Truncate FCM messages if they are too long; This was trickier than expected; relates to #84

Philipp Heckel 4 лет назад
Родитель
Сommit
807d2b0d9d
2 измененных файлов с 51 добавлено и 2 удалено
  1. 21 2
      server/server.go
  2. 30 0
      server/server_test.go

+ 21 - 2
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 {

+ 30 - 0
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")