Expand Local Notifications to support multiple Notification types (#2100)

* Initial work on expanding Local Notifications

Adding functionality to support multiple notification types in Notification.Body. Currently have a bug that I think is caused by a the jsonMarshal func not working as intented, need to resolve this next before proceeding

* Fixed json.Marshaller issue and implemented json.Unmarshaller

* Tweak errors, go convention is errors don't begin with capital letters

* Added notificationMessageBody with un/marshalling

Also removed the Body interface

* Added check for bodyType mismatch

* Implement building and sending new message notifications

* Refactor to remove cycle imports

* Resolved linting issue ... Hopefully

* Resolving an implicit memory aliasing in a for loop

* version bump

* Added Notification.Category consts
This commit is contained in:
Samuel Hawksby-Robinson 2021-01-12 14:28:27 +00:00 committed by GitHub
parent b331b61807
commit 46157dc4dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 172 additions and 12 deletions

View File

@ -1 +1 @@
0.68.2
0.68.3

View File

@ -31,6 +31,7 @@ import (
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/profiling"
"github.com/status-im/status-go/protocol"
localnotifications "github.com/status-im/status-go/services/local-notifications"
)
const (
@ -399,11 +400,13 @@ func retrieveMessagesLoop(messenger *protocol.Messenger, tick time.Duration, can
for {
select {
case <-ticker.C:
_, err := messenger.RetrieveAll()
mr, err := messenger.RetrieveAll()
if err != nil {
logger.Error("failed to retrieve raw messages", "err", err)
continue
}
localnotifications.SendMessageNotifications(mr.Notifications)
case <-cancel:
return
}

View File

@ -0,0 +1,9 @@
package protocol
import "github.com/status-im/status-go/protocol/common"
type MessageNotificationBody struct {
Message *common.Message
Contact *Contact
Chat *Chat
}

View File

@ -483,6 +483,16 @@ func (m *MessageHandler) HandleChatMessage(state *ReceivedMessageState) error {
// Add to response
state.Response.Messages = append(state.Response.Messages, receivedMessage)
// Create notification body to be eventually passed to `localnotifications.SendMessageNotifications()`
state.Response.Notifications = append(
state.Response.Notifications,
MessageNotificationBody{
Message: receivedMessage,
Contact: contact,
Chat: chat,
},
)
return nil
}

View File

@ -19,11 +19,13 @@ type MessengerResponse struct {
CommunityChanges []*communities.CommunityChanges `json:"communitiesChanges,omitempty"`
Filters []*transport.Filter `json:"filters,omitempty"`
RemovedFilters []*transport.Filter `json:"removedFilters,omitempty"`
// Notifications a list of MessageNotificationBody derived from received messages that are useful to notify the user about
Notifications []MessageNotificationBody `json:"notifications"`
}
func (m *MessengerResponse) IsEmpty() bool {
return len(m.Chats)+len(m.Messages)+len(m.Contacts)+len(m.Installations)+len(m.Invitations)+len(m.EmojiReactions)+len(m.Communities)+len(m.CommunityChanges)+len(m.Filters)+len(m.RemovedFilters)+len(m.RemovedChats) == 0
return len(m.Chats)+len(m.Messages)+len(m.Contacts)+len(m.Installations)+len(m.Invitations)+len(m.EmojiReactions)+len(m.Communities)+len(m.CommunityChanges)+len(m.Filters)+len(m.RemovedFilters)+len(m.RemovedChats)+len(m.Notifications) == 0
}
// Merge takes another response and appends the new Chats & new Messages and replaces

View File

@ -31,6 +31,7 @@ import (
"github.com/status-im/status-go/protocol/pushnotificationserver"
"github.com/status-im/status-go/protocol/transport"
"github.com/status-im/status-go/services/ext/mailservers"
localnotifications "github.com/status-im/status-go/services/local-notifications"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/signal"
@ -196,6 +197,7 @@ func (s *Service) retrieveMessagesLoop(tick time.Duration, cancel <-chan struct{
}
if !response.IsEmpty() {
PublisherSignalHandler{}.NewMessages(response)
localnotifications.SendMessageNotifications(response.Notifications)
}
case <-cancel:
return

View File

@ -2,6 +2,8 @@ package localnotifications
import (
"database/sql"
"encoding/json"
"fmt"
"math/big"
"sync"
@ -13,6 +15,7 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/signal"
)
@ -21,12 +24,24 @@ type PushCategory string
type transactionState string
const walletDeeplinkPrefix = "status-im://wallet/"
type NotificationType string
const (
walletDeeplinkPrefix = "status-im://wallet/"
failed transactionState = "failed"
inbound transactionState = "inbound"
outbound transactionState = "outbound"
CategoryTransaction PushCategory = "transaction"
CategoryMessage PushCategory = "newMessage"
TypeTransaction NotificationType = "transaction"
TypeMessage NotificationType = "message"
)
var (
marshalTypeMismatchErr = "notification type mismatch, expected '%s', Body could not be marshalled into this type"
)
type notificationBody struct {
@ -42,9 +57,23 @@ type notificationBody struct {
}
type Notification struct {
ID common.Hash `json:"id"`
Platform float32 `json:"platform,omitempty"`
Body interface{}
BodyType NotificationType `json:"bodyType"`
Category PushCategory `json:"category,omitempty"`
Deeplink string `json:"deepLink,omitempty"`
Image string `json:"imageUrl,omitempty"`
IsScheduled bool `json:"isScheduled,omitempty"`
ScheduledTime string `json:"scheduleTime,omitempty"`
}
// notificationAlias is an interim struct used for json un/marshalling
type notificationAlias struct {
ID common.Hash `json:"id"`
Platform float32 `json:"platform,omitempty"`
Body notificationBody `json:"body"`
Body json.RawMessage `json:"body"`
BodyType NotificationType `json:"bodyType"`
Category PushCategory `json:"category,omitempty"`
Deeplink string `json:"deepLink,omitempty"`
Image string `json:"imageUrl,omitempty"`
@ -101,6 +130,94 @@ func NewService(appDB *sql.DB, network uint64) *Service {
}
}
func (n *Notification) MarshalJSON() ([]byte, error) {
var body json.RawMessage
var err error
switch n.BodyType {
case TypeTransaction:
if nb, ok := n.Body.(*notificationBody); ok {
body, err = json.Marshal(nb)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf(marshalTypeMismatchErr, n.BodyType)
}
case TypeMessage:
if nmb, ok := n.Body.(*protocol.MessageNotificationBody); ok {
body, err = json.Marshal(nmb)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf(marshalTypeMismatchErr, n.BodyType)
}
default:
return nil, fmt.Errorf("unknown NotificationType '%s'", n.BodyType)
}
alias := notificationAlias{
n.ID,
n.Platform,
body,
n.BodyType,
n.Category,
n.Deeplink,
n.Image,
n.IsScheduled,
n.ScheduledTime,
}
return json.Marshal(alias)
}
func (n *Notification) UnmarshalJSON(data []byte) error {
var alias notificationAlias
err := json.Unmarshal(data, &alias)
if err != nil {
return err
}
n.BodyType = alias.BodyType
n.Category = alias.Category
n.Platform = alias.Platform
n.ID = alias.ID
n.Image = alias.Image
n.Deeplink = alias.Deeplink
n.IsScheduled = alias.IsScheduled
n.ScheduledTime = alias.ScheduledTime
switch n.BodyType {
case TypeTransaction:
return n.unmarshalAndAttachBody(alias.Body, &notificationBody{})
case TypeMessage:
return n.unmarshalAndAttachBody(alias.Body, &protocol.MessageNotificationBody{})
default:
return fmt.Errorf("unknown NotificationType '%s'", n.BodyType)
}
}
func (n *Notification) unmarshalAndAttachBody(body json.RawMessage, bodyStruct interface{}) error {
err := json.Unmarshal(body, &bodyStruct)
if err != nil {
return err
}
n.Body = bodyStruct
return nil
}
func pushMessages(ns []*Notification) {
for _, n := range ns {
pushMessage(n)
}
}
func pushMessage(notification *Notification) {
log.Info("Pushing a new push notification", "info", notification)
signal.SendLocalNotifications(notification)
@ -153,10 +270,11 @@ func (s *Service) buildTransactionNotification(rawTransfer wallet.Transfer) *Not
}
return &Notification{
BodyType: TypeTransaction,
ID: transfer.ID,
Body: body,
Body: &body,
Deeplink: deeplink,
Category: "transaction",
Category: CategoryTransaction,
}
}
@ -354,3 +472,19 @@ func (s *Service) Protocols() []p2p.Protocol {
func (s *Service) IsStarted() bool {
return s.started
}
func SendMessageNotifications(mnb []protocol.MessageNotificationBody) {
var ns []*Notification
for _, n := range mnb {
ns = append(ns, &Notification{
Body: n,
BodyType: TypeMessage,
Category: CategoryMessage,
Deeplink: "", // TODO find what if any Deeplink should be used here
Image: "", // TODO do we want to attach any image data contained on the MessageBody{}?
})
}
// sends notifications messages to the OS level application
pushMessages(ns)
}

View File

@ -119,7 +119,7 @@ func TestTransactionNotification(t *testing.T) {
require.NoError(t, utils.Eventually(func() error {
if signalEvent == nil {
return fmt.Errorf("Signal was not handled")
return fmt.Errorf("signal was not handled")
}
notification := struct {
Type string
@ -129,10 +129,10 @@ func TestTransactionNotification(t *testing.T) {
require.NoError(t, json.Unmarshal(signalEvent, &notification))
if notification.Type != "local-notifications" {
return fmt.Errorf("Wrong signal was sent")
return fmt.Errorf("wrong signal was sent")
}
if notification.Event.Body.To != header.Address {
return fmt.Errorf("Transaction to address is wrong")
if notification.Event.Body.(*notificationBody).To != header.Address {
return fmt.Errorf("transaction to address is wrong")
}
return nil
}, 2*time.Second, 100*time.Millisecond))