Skip to content

Commit

Permalink
Add silent notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
dcadenas committed Oct 17, 2024
1 parent 21e7918 commit 51c4849
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 0 deletions.
98 changes: 98 additions & 0 deletions service/adapters/apns/apns.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,39 @@ func (a *APNS) SendFollowChangeNotification(followChange domain.FollowChangeBatc
return nil
}

func (a *APNS) SendSilentFollowChangeNotification(followChange domain.FollowChangeBatch, apnsToken domain.APNSToken) error {
if apnsToken.Hex() == "" {
return errors.New("invalid APNs token")
}
n, err := a.buildSilentFollowChangeNotification(followChange, apnsToken)
if err != nil {
return err
}
resp, err := a.client.Push(n)
//a.metrics.ReportCallToAPNS(resp.StatusCode, err)
if err != nil {
return errors.Wrap(err, "error pushing the silent follow change notification")
}

if resp.StatusCode == 200 {
a.logger.Debug().
WithField("uuid", n.ApnsID).
WithField("response.reason", resp.Reason).
WithField("response.statusCode", resp.StatusCode).
WithField("host", a.client.Host).
Message("sent a silent follow change notification")
} else {
a.logger.Error().
WithField("uuid", n.ApnsID).
WithField("response.reason", resp.Reason).
WithField("response.statusCode", resp.StatusCode).
WithField("host", a.client.Host).
Message("failed to send a silent follow change notification")
}

return nil
}

func (a *APNS) buildFollowChangeNotification(followChange domain.FollowChangeBatch, apnsToken domain.APNSToken) (*apns2.Notification, error) {
payload, err := FollowChangePayload(followChange)
if err != nil {
Expand All @@ -134,6 +167,24 @@ func (a *APNS) buildFollowChangeNotification(followChange domain.FollowChangeBat
return n, nil
}

func (a *APNS) buildSilentFollowChangeNotification(followChange domain.FollowChangeBatch, apnsToken domain.APNSToken) (*apns2.Notification, error) {
payload, err := SilentFollowChangePayload(followChange)
if err != nil {
return nil, errors.Wrap(err, "error creating a payload")
}

n := &apns2.Notification{
PushType: apns2.PushTypeAlert,
ApnsID: uuid.New().String(),
DeviceToken: apnsToken.Hex(),
Topic: a.cfg.APNSTopic(),
Payload: payload,
Priority: apns2.PriorityLow,
}

return n, nil
}

func FollowChangePayload(followChange domain.FollowChangeBatch) ([]byte, error) {
return FollowChangePayloadWithValidation(followChange, true)
}
Expand Down Expand Up @@ -203,6 +254,53 @@ func FollowChangePayloadWithValidation(followChange domain.FollowChangeBatch, va
return payloadBytes, nil
}

func SilentFollowChangePayload(followChange domain.FollowChangeBatch) ([]byte, error) {
return SilentFollowChangePayloadWithValidation(followChange, true)
}

func SilentFollowChangePayloadWithValidation(followChange domain.FollowChangeBatch, validate bool) ([]byte, error) {
totalNpubs := len(followChange.Follows)
if validate && totalNpubs > MAX_TOTAL_NPUBS {
return nil, errors.New("FollowChangeBatch for followee " + followChange.Followee.Hex() + " has too many npubs (" + fmt.Sprint(totalNpubs) + "). MAX_TOTAL_NPUBS is " + fmt.Sprint(MAX_TOTAL_NPUBS))
}

singleChange := totalNpubs == 1

npubFollows, error := pubkeysToNpubs(followChange.Follows)
if error != nil {
return nil, errors.Wrap(error, "error encoding follow npubs")
}

// See https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification

var data map[string]interface{}

if singleChange {
data = map[string]interface{}{
"follows": npubFollows,
"friendlyFollower": followChange.FriendlyFollower,
}
} else {
data = map[string]interface{}{
"follows": npubFollows,
}
}

payload := map[string]interface{}{
"aps": map[string]interface{}{
"content-available": 1,
},
"data": data,
}

payloadBytes, err := json.Marshal(payload)
if err != nil {
return nil, err
}

return payloadBytes, nil
}

func pubkeysToNpubs(pubkeys []domain.PublicKey) ([]string, error) {
npubs := make([]string, len(pubkeys))
for i, pubkey := range pubkeys {
Expand Down
6 changes: 6 additions & 0 deletions service/adapters/apns/apns_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ func (a *APNSMock) SendFollowChangeNotification(followChange domain.FollowChange
return a.SendNotification(notification)
}

func (a *APNSMock) SendSilentFollowChangeNotification(followChange domain.FollowChangeBatch, token domain.APNSToken) error {
notification := notifications.Notification{}

return a.SendNotification(notification)
}

func (a *APNSMock) SentNotifications() []notifications.Notification {
a.sentNotificationsLock.Lock()
defer a.sentNotificationsLock.Unlock()
Expand Down
28 changes: 28 additions & 0 deletions service/adapters/apns/apns_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,34 @@ func TestFollowChangePayload_BatchedFollow_WithNoFriendlyFollower(t *testing.T)

require.Equal(t, expectedPayload, actualPayload)
}

func TestSilentFollowChangePayload_BatchedFollow_WithNoFriendlyFollower(t *testing.T) {
pk1, _ := fixtures.PublicKeyAndNpub()
pk2, pk2Npub := fixtures.PublicKeyAndNpub()
pk3, pk3Npub := fixtures.PublicKeyAndNpub()

batch := domain.FollowChangeBatch{
Followee: pk1,
Follows: []domain.PublicKey{pk2, pk3},
}

payload, err := apns.SilentFollowChangePayload(batch)
require.NoError(t, err)

expectedPayload := map[string]interface{}{
"aps": map[string]interface{}{
"content-available": float64(1),
},
"data": map[string]interface{}{
"follows": []interface{}{pk2Npub, pk3Npub},
},
}

var actualPayload map[string]interface{}
err = json.Unmarshal(payload, &actualPayload)
require.NoError(t, err)
require.Equal(t, expectedPayload, actualPayload)
}
func TestFollowChangePayload_Exceeds4096Bytes_With60TotalNpubs(t *testing.T) {
pk1, _ := fixtures.PublicKeyAndNpub()

Expand Down
1 change: 1 addition & 0 deletions service/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type Queries struct {
type APNS interface {
SendNotification(notification notifications.Notification) error
SendFollowChangeNotification(followChange domain.FollowChangeBatch, apnsToken domain.APNSToken) error
SendSilentFollowChangeNotification(followChange domain.FollowChangeBatch, apnsToken domain.APNSToken) error
}

type EventOrError struct {
Expand Down
9 changes: 9 additions & 0 deletions service/app/follow_change_puller.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,15 @@ func (f *FollowChangePuller) Run(ctx context.Context) error {
Message("error sending follow change notification")
continue
}

if err := f.apns.SendSilentFollowChangeNotification(*followChangeAggregate, token); err != nil {
f.logger.Error().
WithField("token", token.Hex()).
WithField("followee", followChangeAggregate.Followee.Hex()).
WithError(err).
Message("error sending silent follow change notification")
continue
}
}

f.counter += 1
Expand Down

0 comments on commit 51c4849

Please sign in to comment.