Skip to content

Commit

Permalink
integrates an APNs pusher into data service
Browse files Browse the repository at this point in the history
If the necessary configuration isn't found, then push notifications will
instead be logged.

BACK-2554
  • Loading branch information
ewollesen committed Jul 8, 2024
1 parent b3fe600 commit c9a23e7
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 0 deletions.
8 changes: 8 additions & 0 deletions alerts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,11 @@ type Repository interface {

EnsureIndexes() error
}

// Note gathers information necessary for sending an alert notification.
type Note struct {
// Message communicates the alert to the recipient.
Message string
RecipientUserID string
FollowedUserID string
}
38 changes: 38 additions & 0 deletions data/service/service/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/Shopify/sarama"
"github.com/kelseyhightower/envconfig"
eventsCommon "github.com/tidepool-org/go-common/events"

"github.com/tidepool-org/platform/application"
Expand All @@ -17,13 +18,15 @@ import (
dataSourceStoreStructured "github.com/tidepool-org/platform/data/source/store/structured"
dataSourceStoreStructuredMongo "github.com/tidepool-org/platform/data/source/store/structured/mongo"
dataStoreMongo "github.com/tidepool-org/platform/data/store/mongo"
"github.com/tidepool-org/platform/devicetokens"
"github.com/tidepool-org/platform/errors"
"github.com/tidepool-org/platform/events"
"github.com/tidepool-org/platform/log"
metricClient "github.com/tidepool-org/platform/metric/client"
"github.com/tidepool-org/platform/permission"
permissionClient "github.com/tidepool-org/platform/permission/client"
"github.com/tidepool-org/platform/platform"
"github.com/tidepool-org/platform/push"
"github.com/tidepool-org/platform/service/server"
"github.com/tidepool-org/platform/service/service"
storeStructuredMongo "github.com/tidepool-org/platform/store/structured/mongo"
Expand All @@ -41,6 +44,7 @@ type Standard struct {
dataClient *Client
clinicsClient *clinics.Client
dataSourceClient *dataSourceServiceClient.Client
pusher Pusher
userEventsHandler events.Runner
api *api.Standard
server *server.Standard
Expand Down Expand Up @@ -87,6 +91,9 @@ func (s *Standard) Initialize(provider application.Provider) error {
if err := s.initializeSaramaLogger(); err != nil {
return err
}
if err := s.initializePusher(); err != nil {
return err
}
if err := s.initializeUserEventsHandler(); err != nil {
return err
}
Expand Down Expand Up @@ -426,3 +433,34 @@ func (s *Standard) initializeSaramaLogger() error {
sarama.Logger = log.NewSarama(s.Logger())
return nil
}

// Pusher is a service-agnostic interface for sending push notifications.
type Pusher interface {
// Push a notification to a device.
Push(context.Context, *devicetokens.DeviceToken, *push.Notification) error
}

func (s *Standard) initializePusher() error {
var err error

apns2Config := &struct {
SigningKey []byte `envconfig:"TIDEPOOL_DATA_SERVICE_PUSHER_APNS_SIGNING_KEY"`
KeyID string `envconfig:"TIDEPOOL_DATA_SERVICE_PUSHER_APNS_KEY_ID"`
BundleID string `envconfig:"TIDEPOOL_DATA_SERVICE_PUSHER_APNS_BUNDLE_ID"`
TeamID string `envconfig:"TIDEPOOL_DATA_SERVICE_PUSHER_APNS_TEAM_ID"`
}{}
if err := envconfig.Process("", apns2Config); err != nil {
return errors.Wrap(err, "Unable to process APNs pusher config")
}

var pusher Pusher
pusher, err = push.NewAPNSPusherFromKeyData(apns2Config.SigningKey, apns2Config.KeyID,
apns2Config.TeamID, apns2Config.BundleID)
if err != nil {
s.Logger().WithError(err).Warn("falling back to logging of push notifications")
pusher = push.NewLogPusher(s.Logger())
}
s.pusher = pusher

return nil
}
39 changes: 39 additions & 0 deletions push/logpush.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package push

import (
"context"
"os"

"github.com/tidepool-org/platform/devicetokens"
"github.com/tidepool-org/platform/log"
logjson "github.com/tidepool-org/platform/log/json"
lognull "github.com/tidepool-org/platform/log/null"
)

// LogPusher logs notifications instead of sending push notifications.
//
// Useful for dev or testing situations.
type LogPusher struct {
log.Logger
}

// NewLogPusher uses a [log.Logger] instead of pushing via APNs.
func NewLogPusher(l log.Logger) *LogPusher {
if l == nil {
var err error
l, err = logjson.NewLogger(os.Stderr, log.DefaultLevelRanks(), log.DefaultLevel())
if err != nil {
l = lognull.NewLogger()
}
}
return &LogPusher{Logger: l}
}

// Push implements [service.Pusher].
func (p *LogPusher) Push(ctx context.Context, deviceToken *devicetokens.DeviceToken, note *Notification) error {
p.Logger.WithFields(log.Fields{
"deviceToken": deviceToken,
"note": note,
}).Info("logging push notification")
return nil
}
28 changes: 28 additions & 0 deletions push/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/sideshow/apns2/payload"
"github.com/sideshow/apns2/token"

"github.com/tidepool-org/platform/alerts"
"github.com/tidepool-org/platform/devicetokens"
"github.com/tidepool-org/platform/errors"
"github.com/tidepool-org/platform/log"
Expand All @@ -21,6 +22,17 @@ type Notification struct {
Message string
}

// String implements fmt.Stringer.
func (n Notification) String() string {
return n.Message
}

func FromNote(note *alerts.Note) *Notification {
return &Notification{
Message: note.Message,
}
}

// APNSPusher implements push notifications via Apple APNs.
type APNSPusher struct {
BundleID string
Expand All @@ -47,6 +59,22 @@ func NewAPNSPusher(client APNS2Client, bundleID string) *APNSPusher {
//
// https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns
func NewAPNSPusherFromKeyData(signingKey []byte, keyID, teamID, bundleID string) (*APNSPusher, error) {
if len(signingKey) == 0 {
return nil, errors.New("Unable to build APNSPusher: APNs signing key is blank")
}

if bundleID == "" {
return nil, errors.New("Unable to build APNSPusher: bundleID is blank")
}

if keyID == "" {
return nil, errors.New("Unable to build APNSPusher: keyID is blank")
}

if teamID == "" {
return nil, errors.New("Unable to build APNSPusher: teamID is blank")
}

authKey, err := token.AuthKeyFromBytes(signingKey)
if err != nil {
return nil, err
Expand Down

0 comments on commit c9a23e7

Please sign in to comment.