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:
parent
b331b61807
commit
46157dc4dc
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, ¬ificationBody{})
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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, ¬ification))
|
||||
|
||||
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))
|
||||
|
|
Loading…
Reference in New Issue