diff --git a/VERSION b/VERSION index e6227be88..cb45b67bf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.68.2 +0.68.3 diff --git a/cmd/statusd/main.go b/cmd/statusd/main.go index 1ffcc5400..abb68e004 100644 --- a/cmd/statusd/main.go +++ b/cmd/statusd/main.go @@ -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 } diff --git a/protocol/local_notifications.go b/protocol/local_notifications.go new file mode 100644 index 000000000..fdb859191 --- /dev/null +++ b/protocol/local_notifications.go @@ -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 +} diff --git a/protocol/message_handler.go b/protocol/message_handler.go index eb5d27be3..cb1ee4d9d 100644 --- a/protocol/message_handler.go +++ b/protocol/message_handler.go @@ -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 } diff --git a/protocol/messenger_response.go b/protocol/messenger_response.go index 33c3e92c5..8e6588803 100644 --- a/protocol/messenger_response.go +++ b/protocol/messenger_response.go @@ -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 diff --git a/services/ext/service.go b/services/ext/service.go index 4b73e3631..6b6515634 100644 --- a/services/ext/service.go +++ b/services/ext/service.go @@ -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 diff --git a/services/local-notifications/core.go b/services/local-notifications/core.go index 56b993dfe..5fe5686d9 100644 --- a/services/local-notifications/core.go +++ b/services/local-notifications/core.go @@ -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) +} diff --git a/services/local-notifications/core_test.go b/services/local-notifications/core_test.go index dc0326fcb..03b76c344 100644 --- a/services/local-notifications/core_test.go +++ b/services/local-notifications/core_test.go @@ -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))