Files
proton-bridge/internal/services/notifications/service.go

158 lines
4.9 KiB
Go

// Copyright (c) 2024 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package notifications
import (
"context"
"fmt"
"regexp"
"strings"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
"github.com/ProtonMail/proton-bridge/v3/internal/services/observability"
"github.com/ProtonMail/proton-bridge/v3/internal/services/orderedtasks"
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
"github.com/ProtonMail/proton-bridge/v3/internal/unleash"
"github.com/sirupsen/logrus"
)
type Service struct {
userID string
log *logrus.Entry
eventService userevents.Subscribable
subscription *userevents.EventChanneledSubscriber
eventPublisher events.EventPublisher
store *Store
getFlagValueFn unleash.GetFlagValueFn
observabilitySender observability.Sender
}
const bitfieldRegexPattern = `^\\\d+`
const disableNotificationsKillSwitch = "InboxBridgeEventLoopNotificationDisabled"
func NewService(userID string, service userevents.Subscribable, eventPublisher events.EventPublisher, store *Store,
getFlagFn unleash.GetFlagValueFn, observabilitySender observability.Sender) *Service {
return &Service{
userID: userID,
log: logrus.WithFields(logrus.Fields{
"user": userID,
"service": "notification",
}),
eventService: service,
subscription: userevents.NewEventSubscriber(
fmt.Sprintf("notifications-%v", userID)),
eventPublisher: eventPublisher,
store: store,
getFlagValueFn: getFlagFn,
observabilitySender: observabilitySender,
}
}
func (s *Service) Start(ctx context.Context, group *orderedtasks.OrderedCancelGroup) {
group.Go(ctx, s.userID, "notification-service", s.run)
}
func (s *Service) run(ctx context.Context) {
s.log.Info("Starting service main loop")
defer s.log.Info("Exiting service main loop")
eventHandler := userevents.EventHandler{
NotificationHandler: s,
}
s.eventService.Subscribe(s.subscription)
defer s.eventService.Unsubscribe(s.subscription)
for {
select {
case <-ctx.Done():
return
case e, ok := <-s.subscription.OnEventCh():
if !ok {
continue
}
e.Consume(func(event proton.Event) error { return eventHandler.OnEvent(ctx, event) })
}
}
}
func (s *Service) HandleNotificationEvents(ctx context.Context, notificationEvents []proton.NotificationEvent) error {
if s.getFlagValueFn(disableNotificationsKillSwitch) {
s.log.Info("Received notification events. Skipping as kill switch is enabled.")
return nil
}
s.log.Debug("Handling notification events")
// Publish observability metrics that we've received notifications
s.observabilitySender.AddMetrics(GenerateReceivedMetric(len(notificationEvents)))
for _, event := range notificationEvents {
ctx = logging.WithLogrusField(ctx, "notificationID", event.ID)
switch strings.ToLower(event.Type) {
case "bridge_modal":
{
// We currently don't support any notification types with bitfields in the body.
if isBodyBitfieldValid(event) {
continue
}
shouldSend := s.store.shouldSendAndStore(event)
if !shouldSend {
s.log.Info("Skipping notification event. Notification was displayed previously")
continue
}
s.log.Info("Publishing notification event. notificationID:", event.ID) // \todo BRIDGE-141 - change this to UID once it is available
s.eventPublisher.PublishEvent(ctx, events.UserNotification{UserID: s.userID, Title: event.Payload.Title,
Subtitle: event.Payload.Subtitle, Body: event.Payload.Body})
// Publish observability metric that we've successfully processed notifications
s.observabilitySender.AddMetrics(GenerateProcessedMetric(1))
}
default:
s.log.Debug("Skipping notification event. Notification type is not related to bridge:", event.Type)
continue
}
}
return nil
}
// We will (potentially) encode different notification functionalities based on a starting bitfield "\NUMBER" in the
// payload Body. Currently, we don't support this, but we might in the future. This is so versions of Bridge that don't
// support this functionality won't be display such a message.
func isBodyBitfieldValid(notification proton.NotificationEvent) bool {
match, err := regexp.MatchString(bitfieldRegexPattern, notification.Payload.Body)
if err != nil {
return false
}
return match
}