diff --git a/server/server.go b/server/server.go index bcee9bfe..39bef7d8 100644 --- a/server/server.go +++ b/server/server.go @@ -7,13 +7,10 @@ import ( "encoding/base64" "encoding/json" "errors" - firebase "firebase.google.com/go" - "firebase.google.com/go/messaging" "fmt" "github.com/emersion/go-smtp" "github.com/gorilla/websocket" "golang.org/x/sync/errgroup" - "google.golang.org/api/option" "heckel.io/ntfy/auth" "heckel.io/ntfy/util" "html/template" @@ -145,8 +142,11 @@ func New(conf *Config) (*Server, error) { } var firebaseSubscriber subscriber if conf.FirebaseKeyFile != "" { - var err error - firebaseSubscriber, err = createFirebaseSubscriber(conf, auther) + sender, err := createFirebaseSender(conf) + if err != nil { + return nil, err + } + firebaseSubscriber, err = createFirebaseSubscriber(auther, sender) if err != nil { return nil, err } @@ -172,76 +172,6 @@ func createCache(conf *Config) (cache, error) { return newMemCache(), nil } -func createFirebaseSubscriber(conf *Config, auther auth.Auther) (subscriber, error) { - fb, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsFile(conf.FirebaseKeyFile)) - if err != nil { - return nil, err - } - msg, err := fb.Messaging(context.Background()) - if err != nil { - return nil, err - } - return func(m *message) error { - var data map[string]string // Mostly matches https://ntfy.sh/docs/subscribe/api/#json-message-format - switch m.Event { - case keepaliveEvent, openEvent: - data = map[string]string{ - "id": m.ID, - "time": fmt.Sprintf("%d", m.Time), - "event": m.Event, - "topic": m.Topic, - } - case messageEvent: - allowForward := true - if auther != nil { - allowForward = auther.Authorize(nil, m.Topic, auth.PermissionRead) == nil - } - if allowForward { - data = map[string]string{ - "id": m.ID, - "time": fmt.Sprintf("%d", m.Time), - "event": m.Event, - "topic": m.Topic, - "priority": fmt.Sprintf("%d", m.Priority), - "tags": strings.Join(m.Tags, ","), - "click": m.Click, - "title": m.Title, - "message": m.Message, - "encoding": m.Encoding, - } - if m.Attachment != nil { - data["attachment_name"] = m.Attachment.Name - data["attachment_type"] = m.Attachment.Type - data["attachment_size"] = fmt.Sprintf("%d", m.Attachment.Size) - data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires) - data["attachment_url"] = m.Attachment.URL - } - } else { - // If anonymous read for a topic is not allowed, we cannot send the message along - // via Firebase. Instead, we send a "poll_request" message, asking the client to poll. - data = map[string]string{ - "id": m.ID, - "time": fmt.Sprintf("%d", m.Time), - "event": pollRequestEvent, - "topic": m.Topic, - } - } - } - var androidConfig *messaging.AndroidConfig - if m.Priority >= 4 { - androidConfig = &messaging.AndroidConfig{ - Priority: "high", - } - } - _, err := msg.Send(context.Background(), maybeTruncateFCMMessage(&messaging.Message{ - Topic: m.Topic, - Data: data, - Android: androidConfig, - })) - return err - }, nil -} - // 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_firebase.go b/server/server_firebase.go new file mode 100644 index 00000000..dea8c1cf --- /dev/null +++ b/server/server_firebase.go @@ -0,0 +1,89 @@ +package server + +import ( + "context" + firebase "firebase.google.com/go" + "firebase.google.com/go/messaging" + "fmt" + "google.golang.org/api/option" + "heckel.io/ntfy/auth" + "strings" +) + +type firebaseSender func(m *messaging.Message) error + +func createFirebaseSender(conf *Config) (firebaseSender, error) { + fb, err := firebase.NewApp(context.Background(), nil, option.WithCredentialsFile(conf.FirebaseKeyFile)) + if err != nil { + return nil, err + } + msg, err := fb.Messaging(context.Background()) + if err != nil { + return nil, err + } + return func(m *messaging.Message) error { + _, err := msg.Send(context.Background(), m) + return err + }, nil +} + +func createFirebaseSubscriber(auther auth.Auther, sender firebaseSender) (subscriber, error) { + return func(m *message) error { + var data map[string]string // Mostly matches https://ntfy.sh/docs/subscribe/api/#json-message-format + switch m.Event { + case keepaliveEvent, openEvent: + data = map[string]string{ + "id": m.ID, + "time": fmt.Sprintf("%d", m.Time), + "event": m.Event, + "topic": m.Topic, + } + case messageEvent: + allowForward := true + if auther != nil { + allowForward = auther.Authorize(nil, m.Topic, auth.PermissionRead) == nil + } + if allowForward { + data = map[string]string{ + "id": m.ID, + "time": fmt.Sprintf("%d", m.Time), + "event": m.Event, + "topic": m.Topic, + "priority": fmt.Sprintf("%d", m.Priority), + "tags": strings.Join(m.Tags, ","), + "click": m.Click, + "title": m.Title, + "message": m.Message, + "encoding": m.Encoding, + } + if m.Attachment != nil { + data["attachment_name"] = m.Attachment.Name + data["attachment_type"] = m.Attachment.Type + data["attachment_size"] = fmt.Sprintf("%d", m.Attachment.Size) + data["attachment_expires"] = fmt.Sprintf("%d", m.Attachment.Expires) + data["attachment_url"] = m.Attachment.URL + } + } else { + // If anonymous read for a topic is not allowed, we cannot send the message along + // via Firebase. Instead, we send a "poll_request" message, asking the client to poll. + data = map[string]string{ + "id": m.ID, + "time": fmt.Sprintf("%d", m.Time), + "event": pollRequestEvent, + "topic": m.Topic, + } + } + } + var androidConfig *messaging.AndroidConfig + if m.Priority >= 4 { + androidConfig = &messaging.AndroidConfig{ + Priority: "high", + } + } + return sender(maybeTruncateFCMMessage(&messaging.Message{ + Topic: m.Topic, + Data: data, + Android: androidConfig, + })) + }, nil +}