Add community notifications (#2160)

This commit introduces the following changes:

- `local-notifications` require as body an interface complying with
`json.Marshaler`
- removed unmarshaling of `Notifications` as not used (we only Marshal
notifications)
- `protocol/messenger.go` creates directly a `Notification` instead of
having an intermediate format
- add community notifications on request to join
- move parsing of text in status-go for notifications
This commit is contained in:
Andrea Maria Piana 2021-03-31 18:23:45 +02:00 committed by GitHub
parent 5a76e93063
commit c55659b4f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 995 additions and 502 deletions

View File

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

View File

@ -530,6 +530,15 @@ func (db *Database) DeleteAccount(address types.Address) error {
return err return err
} }
func (db *Database) GetNotificationsEnabled() (bool, error) {
var result bool
err := db.db.QueryRow("SELECT notifications_enabled FROM settings WHERE synthetic_id = 'id'").Scan(&result)
if err == sql.ErrNoRows {
return result, nil
}
return result, err
}
func (db *Database) GetWalletAddress() (rst types.Address, err error) { func (db *Database) GetWalletAddress() (rst types.Address, err error) {
err = db.db.QueryRow("SELECT address FROM accounts WHERE wallet = 1").Scan(&rst) err = db.db.QueryRow("SELECT address FROM accounts WHERE wallet = 1").Scan(&rst)
return return

View File

@ -108,6 +108,14 @@ func (c *Chat) OneToOne() bool {
return c.ChatType == ChatTypeOneToOne return c.ChatType == ChatTypeOneToOne
} }
func (c *Chat) CommunityChat() bool {
return c.ChatType == ChatTypeCommunityChat
}
func (c *Chat) PrivateGroupChat() bool {
return c.ChatType == ChatTypePrivateGroupChat
}
func (c *Chat) CommunityChatID() string { func (c *Chat) CommunityChatID() string {
if c.ChatType != ChatTypeCommunityChat { if c.ChatType != ChatTypeCommunityChat {
return c.ID return c.ID
@ -291,6 +299,25 @@ func CreateCommunityChat(orgID, chatID string, orgChat *protobuf.CommunityChat,
} }
} }
func (c *Chat) DeepLink() string {
if c.OneToOne() {
return "status-im://p/" + c.ID
}
if c.PrivateGroupChat() {
return "status-im://g/args?a2=" + c.ID
}
if c.CommunityChat() {
return "status-im://cc/" + c.ID
}
if c.Public() {
return "status-im://" + c.ID
}
return ""
}
func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []*Chat { func CreateCommunityChats(org *communities.Community, timesource common.TimeSource) []*Chat {
var chats []*Chat var chats []*Chat
orgID := org.IDString() orgID := org.IDString()

View File

@ -130,6 +130,7 @@ func (s *ChatTestSuite) TestSerializeJSON() {
message.Clock = 1 message.Clock = 1
message.Text = "`some markdown text`" message.Text = "`some markdown text`"
s.Require().NoError(message.PrepareContent()) s.Require().NoError(message.PrepareContent())
message.ParsedTextAst = nil
chat.LastMessage = message chat.LastMessage = message
encodedJSON, err := json.Marshal(chat) encodedJSON, err := json.Marshal(chat)

View File

@ -110,6 +110,8 @@ type Message struct {
RTL bool `json:"rtl"` RTL bool `json:"rtl"`
// ParsedText is the parsed markdown for displaying // ParsedText is the parsed markdown for displaying
ParsedText []byte `json:"parsedText,omitempty"` ParsedText []byte `json:"parsedText,omitempty"`
// ParsedTextAst is the ast of the parsed text
ParsedTextAst *ast.Node `json:"-"`
// LineCount is the count of newlines in the message // LineCount is the count of newlines in the message
LineCount int `json:"lineCount"` LineCount int `json:"lineCount"`
// Base64Image is the converted base64 image // Base64Image is the converted base64 image
@ -323,12 +325,52 @@ func (m *Message) parseAudio() error {
} }
// implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701 // implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701
type NodeVisitor struct { type SimplifiedTextVisitor struct {
text string
canonicalNames map[string]string
}
func (v *SimplifiedTextVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
// only on entering we fetch, otherwise we go on
if !entering {
return ast.GoToNext
}
switch n := node.(type) {
case *ast.Mention:
literal := string(n.Literal)
canonicalName, ok := v.canonicalNames[literal]
if ok {
v.text += canonicalName
} else {
v.text += literal
}
case *ast.Link:
destination := string(n.Destination)
v.text += destination
default:
var literal string
leaf := node.AsLeaf()
container := node.AsContainer()
if leaf != nil {
literal = string(leaf.Literal)
} else if container != nil {
literal = string(container.Literal)
}
v.text += literal
}
return ast.GoToNext
}
// implement interface of https://github.com/status-im/markdown/blob/b9fe921681227b1dace4b56364e15edb3b698308/ast/node.go#L701
type MentionsAndLinksVisitor struct {
mentions []string mentions []string
links []string links []string
} }
func (v *NodeVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus { func (v *MentionsAndLinksVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
// only on entering we fetch, otherwise we go on // only on entering we fetch, otherwise we go on
if !entering { if !entering {
return ast.GoToNext return ast.GoToNext
@ -344,7 +386,7 @@ func (v *NodeVisitor) Visit(node ast.Node, entering bool) ast.WalkStatus {
} }
func extractMentionsAndLinks(parsedText ast.Node) ([]string, []string) { func extractMentionsAndLinks(parsedText ast.Node) ([]string, []string) {
visitor := &NodeVisitor{} visitor := &MentionsAndLinksVisitor{}
ast.Walk(parsedText, visitor) ast.Walk(parsedText, visitor)
return visitor.mentions, visitor.links return visitor.mentions, visitor.links
} }
@ -358,6 +400,7 @@ func (m *Message) PrepareContent() error {
if err != nil { if err != nil {
return err return err
} }
m.ParsedTextAst = &parsedText
m.ParsedText = jsonParsedText m.ParsedText = jsonParsedText
m.LineCount = strings.Count(m.Text, "\n") m.LineCount = strings.Count(m.Text, "\n")
m.RTL = isRTL(m.Text) m.RTL = isRTL(m.Text)
@ -367,6 +410,34 @@ func (m *Message) PrepareContent() error {
return m.parseAudio() return m.parseAudio()
} }
// GetSimplifiedText returns a the text stripped of all the markdown and with mentions
// replaced by canonical names
func (m *Message) GetSimplifiedText(canonicalNames map[string]string) (string, error) {
if m.ContentType == protobuf.ChatMessage_AUDIO {
return "Audio", nil
}
if m.ContentType == protobuf.ChatMessage_STICKER {
return "Sticker", nil
}
if m.ContentType == protobuf.ChatMessage_IMAGE {
return "Image", nil
}
if m.ContentType == protobuf.ChatMessage_COMMUNITY {
return "Community", nil
}
if m.ParsedTextAst == nil {
err := m.PrepareContent()
if err != nil {
return "", err
}
}
visitor := &SimplifiedTextVisitor{canonicalNames: canonicalNames}
ast.Walk(*m.ParsedTextAst, visitor)
return visitor.text, nil
}
func getAudioMessageMIME(i *protobuf.AudioMessage) (string, error) { func getAudioMessageMIME(i *protobuf.AudioMessage) (string, error) {
switch i.Type { switch i.Type {
case protobuf.AudioMessage_AAC: case protobuf.AudioMessage_AAC:

View File

@ -98,3 +98,32 @@ func TestPrepareContentLinks(t *testing.T) {
require.Equal(t, message.Links[0], link1) require.Equal(t, message.Links[0], link1)
require.Equal(t, message.Links[1], link2) require.Equal(t, message.Links[1], link2)
} }
func TestPrepareSimplifiedText(t *testing.T) {
canonicalName1 := "canonical-name-1"
canonicalName2 := "canonical-name-2"
message := &Message{}
pk1, err := crypto.GenerateKey()
require.NoError(t, err)
pk1String := types.EncodeHex(crypto.FromECDSAPub(&pk1.PublicKey))
pk2, err := crypto.GenerateKey()
require.NoError(t, err)
pk2String := types.EncodeHex(crypto.FromECDSAPub(&pk2.PublicKey))
message.Text = "hey @" + pk1String + " @" + pk2String
require.NoError(t, message.PrepareContent())
require.Len(t, message.Mentions, 2)
require.Equal(t, message.Mentions[0], pk1String)
require.Equal(t, message.Mentions[1], pk2String)
canonicalNames := make(map[string]string)
canonicalNames[pk1String] = canonicalName1
canonicalNames[pk2String] = canonicalName2
simplifiedText, err := message.GetSimplifiedText(canonicalNames)
require.NoError(t, err)
require.Equal(t, "hey "+canonicalName1+" "+canonicalName2, simplifiedText)
}

View File

@ -116,7 +116,7 @@ func (o *Community) MarshalJSON() ([]byte, error) {
communityItem.Members = o.config.CommunityDescription.Members communityItem.Members = o.config.CommunityDescription.Members
communityItem.Permissions = o.config.CommunityDescription.Permissions communityItem.Permissions = o.config.CommunityDescription.Permissions
if o.config.CommunityDescription.Identity != nil { if o.config.CommunityDescription.Identity != nil {
communityItem.Name = o.config.CommunityDescription.Identity.DisplayName communityItem.Name = o.Name()
communityItem.Color = o.config.CommunityDescription.Identity.Color communityItem.Color = o.config.CommunityDescription.Identity.Color
communityItem.Description = o.config.CommunityDescription.Identity.Description communityItem.Description = o.config.CommunityDescription.Identity.Description
for t, i := range o.config.CommunityDescription.Identity.Images { for t, i := range o.config.CommunityDescription.Identity.Images {
@ -132,6 +132,10 @@ func (o *Community) MarshalJSON() ([]byte, error) {
return json.Marshal(communityItem) return json.Marshal(communityItem)
} }
func (o *Community) Name() string {
return o.config.CommunityDescription.Identity.DisplayName
}
func (o *Community) initialize() { func (o *Community) initialize() {
if o.config.CommunityDescription == nil { if o.config.CommunityDescription == nil {
o.config.CommunityDescription = &protobuf.CommunityDescription{} o.config.CommunityDescription = &protobuf.CommunityDescription{}
@ -339,19 +343,22 @@ func (o *Community) isBanned(pk *ecdsa.PublicKey) bool {
return false return false
} }
func (o *Community) hasPermission(pk *ecdsa.PublicKey, role protobuf.CommunityMember_Roles) bool { func (o *Community) hasMemberPermission(member *protobuf.CommunityMember, permissions map[protobuf.CommunityMember_Roles]bool) bool {
for _, r := range member.Roles {
if permissions[r] {
return true
}
}
return false
}
func (o *Community) hasPermission(pk *ecdsa.PublicKey, roles map[protobuf.CommunityMember_Roles]bool) bool {
member := o.getMember(pk) member := o.getMember(pk)
if member == nil { if member == nil {
return false return false
} }
for _, r := range member.Roles { return o.hasMemberPermission(member, roles)
if r == role {
return true
}
}
return false
} }
func (o *Community) HasMember(pk *ecdsa.PublicKey) bool { func (o *Community) HasMember(pk *ecdsa.PublicKey) bool {
@ -605,6 +612,22 @@ func (o *Community) IsAdmin() bool {
return o.config.PrivateKey != nil return o.config.PrivateKey != nil
} }
func (o *Community) IsMemberAdmin(publicKey *ecdsa.PublicKey) bool {
return o.hasPermission(publicKey, adminRolePermissions())
}
func canManageUsersRolePermissions() map[protobuf.CommunityMember_Roles]bool {
roles := adminRolePermissions()
roles[protobuf.CommunityMember_ROLE_MANAGE_USERS] = true
return roles
}
func adminRolePermissions() map[protobuf.CommunityMember_Roles]bool {
roles := make(map[protobuf.CommunityMember_Roles]bool)
roles[protobuf.CommunityMember_ROLE_ALL] = true
return roles
}
func (o *Community) validateRequestToJoinWithChatID(request *protobuf.CommunityRequestToJoin) error { func (o *Community) validateRequestToJoinWithChatID(request *protobuf.CommunityRequestToJoin) error {
chat, ok := o.config.CommunityDescription.Chats[request.ChatId] chat, ok := o.config.CommunityDescription.Chats[request.ChatId]
@ -901,7 +924,8 @@ func (o *Community) CanManageUsers(pk *ecdsa.PublicKey) bool {
return false return false
} }
return o.hasPermission(pk, protobuf.CommunityMember_ROLE_ALL) || o.hasPermission(pk, protobuf.CommunityMember_ROLE_MANAGE_USERS) roles := canManageUsersRolePermissions()
return o.hasPermission(pk, roles)
} }
func (o *Community) isMember() bool { func (o *Community) isMember() bool {
@ -933,6 +957,23 @@ func (o *Community) nextClock() uint64 {
return o.config.CommunityDescription.Clock + 1 return o.config.CommunityDescription.Clock + 1
} }
func (o *Community) CanManageUsersPublicKeys() ([]*ecdsa.PublicKey, error) {
var response []*ecdsa.PublicKey
roles := canManageUsersRolePermissions()
for pkString, member := range o.config.CommunityDescription.Members {
if o.hasMemberPermission(member, roles) {
pk, err := common.HexToPubkey(pkString)
if err != nil {
return nil, err
}
response = append(response, pk)
}
}
return response, nil
}
func emptyCommunityChanges() *CommunityChanges { func emptyCommunityChanges() *CommunityChanges {
return &CommunityChanges{ return &CommunityChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember), MembersAdded: make(map[string]*protobuf.CommunityMember),

View File

@ -309,6 +309,8 @@ func (s *MessengerCommunitiesSuite) TestInviteUsersToCommunity() {
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NotNil(response) s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1) s.Require().Len(response.Communities(), 1)
s.Require().True(response.Communities()[0].HasMember(&s.bob.identity.PublicKey))
s.Require().True(response.Communities()[0].IsMemberAdmin(&s.bob.identity.PublicKey))
community := response.Communities()[0] community := response.Communities()[0]
@ -787,6 +789,12 @@ func (s *MessengerCommunitiesSuite) TestRequestAccessAgain() {
s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID) s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID)
// Check that a notification is been added to messenger
notifications := response.Notifications()
s.Require().Len(notifications, 1)
s.Require().NotEqual(notifications[0].ID.Hex(), "0x0000000000000000000000000000000000000000000000000000000000000000")
// Accept request // Accept request
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID} acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID}

View File

@ -26,6 +26,22 @@ type ContactDeviceInfo struct {
FCMToken string `json:"fcmToken"` FCMToken string `json:"fcmToken"`
} }
func (c *Contact) CanonicalName() string {
if c.LocalNickname != "" {
return c.LocalNickname
}
if c.ENSVerified {
return c.Name
}
return c.Alias
}
func (c *Contact) CanonicalImage() string {
return c.Identicon
}
// Contact has information about a "Contact". A contact is not necessarily one // Contact has information about a "Contact". A contact is not necessarily one
// that we added or added us, that's based on SystemTags. // that we added or added us, that's based on SystemTags.
type Contact struct { type Contact struct {

View File

@ -1,9 +1,128 @@
package protocol package protocol
import "github.com/status-im/status-go/protocol/common" import (
"crypto/ecdsa"
"encoding/json"
type MessageNotificationBody struct { gethcommon "github.com/ethereum/go-ethereum/common"
Message *common.Message `json:"message"` "github.com/status-im/status-go/protocol/common"
Contact *Contact `json:"contact"` "github.com/status-im/status-go/protocol/communities"
Chat *Chat `json:"chat"` localnotifications "github.com/status-im/status-go/services/local-notifications"
)
type NotificationBody struct {
Message *common.Message `json:"message"`
Contact *Contact `json:"contact"`
Chat *Chat `json:"chat"`
Community *communities.Community `json:"community"`
}
func showMessageNotification(publicKey ecdsa.PublicKey, message *common.Message, chat *Chat, responseTo *common.Message) bool {
if chat != nil && (chat.OneToOne() || chat.PrivateGroupChat()) {
return true
}
publicKeyString := common.PubkeyToHex(&publicKey)
mentioned := false
for _, mention := range message.Mentions {
if publicKeyString == mention {
mentioned = true
}
}
if mentioned {
return true
}
if responseTo != nil {
return responseTo.From == publicKeyString
}
return false
}
func (n NotificationBody) MarshalJSON() ([]byte, error) {
type Alias NotificationBody
item := struct{ *Alias }{Alias: (*Alias)(&n)}
return json.Marshal(item)
}
func NewMessageNotification(id string, message *common.Message, chat *Chat, contact *Contact, contacts map[string]*Contact) (*localnotifications.Notification, error) {
body := &NotificationBody{
Message: message,
Chat: chat,
Contact: contact,
}
return body.toMessageNotification(id, contacts)
}
func NewCommunityRequestToJoinNotification(id string, community *communities.Community, contact *Contact) *localnotifications.Notification {
body := &NotificationBody{
Community: community,
Contact: contact,
}
return body.toCommunityRequestToJoinNotification(id)
}
func (n NotificationBody) toMessageNotification(id string, contacts map[string]*Contact) (*localnotifications.Notification, error) {
var title string
if n.Chat.PrivateGroupChat() || n.Chat.Public() || n.Chat.CommunityChat() {
title = n.Chat.Name
} else if n.Chat.OneToOne() {
title = n.Contact.CanonicalName()
}
canonicalNames := make(map[string]string)
for _, id := range n.Message.Mentions {
contact, ok := contacts[id]
if !ok {
var err error
contact, err = buildContactFromPkString(id)
if err != nil {
return nil, err
}
}
canonicalNames[id] = contact.CanonicalName()
}
simplifiedText, err := n.Message.GetSimplifiedText(canonicalNames)
if err != nil {
return nil, err
}
return &localnotifications.Notification{
Body: n,
ID: gethcommon.HexToHash(id),
BodyType: localnotifications.TypeMessage,
Category: localnotifications.CategoryMessage,
Deeplink: n.Chat.DeepLink(),
Title: title,
Message: simplifiedText,
IsConversation: true,
IsGroupConversation: true,
Author: localnotifications.NotificationAuthor{
Name: n.Contact.CanonicalName(),
Icon: n.Contact.CanonicalImage(),
ID: n.Contact.ID,
},
Timestamp: n.Message.WhisperTimestamp,
ConversationID: n.Chat.ID,
Image: "",
}, nil
}
func (n NotificationBody) toCommunityRequestToJoinNotification(id string) *localnotifications.Notification {
return &localnotifications.Notification{
ID: gethcommon.HexToHash(id),
Body: n,
Title: n.Contact.CanonicalName() + " wants to join " + n.Community.Name(),
Message: n.Contact.CanonicalName() + " wants to join message " + n.Community.Name(),
BodyType: localnotifications.TypeMessage,
Category: localnotifications.CategoryCommunityRequestToJoin,
Deeplink: "status-im://cr/" + n.Community.IDString(),
Image: "",
}
} }

View File

@ -415,6 +415,17 @@ func (m *MessageHandler) HandleCommunityRequestToJoin(state *ReceivedMessageStat
state.Response.RequestsToJoinCommunity = append(state.Response.RequestsToJoinCommunity, requestToJoin) state.Response.RequestsToJoinCommunity = append(state.Response.RequestsToJoinCommunity, requestToJoin)
community, err := m.communitiesManager.GetByID(requestToJoinProto.CommunityId)
if err != nil {
return err
}
contactID := contactIDFromPublicKey(signer)
contact := state.AllContacts[contactID]
state.Response.AddNotification(NewCommunityRequestToJoinNotification(requestToJoin.ID.String(), community, contact))
return nil return nil
} }

View File

@ -21,10 +21,12 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
userimage "github.com/status-im/status-go/images" userimage "github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts" "github.com/status-im/status-go/multiaccounts"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/protocol/audio" "github.com/status-im/status-go/protocol/audio"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/communities"
@ -99,6 +101,7 @@ type Messenger struct {
mailserver []byte mailserver []byte
database *sql.DB database *sql.DB
multiAccounts *multiaccounts.Database multiAccounts *multiaccounts.Database
settings *accounts.Database
account *multiaccounts.Account account *multiaccounts.Account
mailserversDatabase *mailservers.Database mailserversDatabase *mailservers.Database
quit chan struct{} quit chan struct{}
@ -186,7 +189,7 @@ func NewMessenger(
if c.db == nil { if c.db == nil {
logger.Info("opening a database", zap.String("dbPath", c.dbConfig.dbPath)) logger.Info("opening a database", zap.String("dbPath", c.dbConfig.dbPath))
var err error var err error
database, err = sqlite.Open(c.dbConfig.dbPath, c.dbConfig.dbKey) database, err = appdatabase.InitializeDB(c.dbConfig.dbPath, c.dbConfig.dbKey)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to initialize database from the db config") return nil, errors.Wrap(err, "failed to initialize database from the db config")
} }
@ -304,6 +307,7 @@ func NewMessenger(
verifyTransactionClient: c.verifyTransactionClient, verifyTransactionClient: c.verifyTransactionClient,
database: database, database: database,
multiAccounts: c.multiAccount, multiAccounts: c.multiAccount,
settings: accounts.NewDB(database),
mailserversDatabase: c.mailserversDatabase, mailserversDatabase: c.mailserversDatabase,
account: c.account, account: c.account,
quit: make(chan struct{}), quit: make(chan struct{}),
@ -2455,7 +2459,7 @@ func (m *Messenger) markDeliveredMessages(acks [][]byte) {
} }
} }
// addNewMessageNotification takes a common.Message and generates a new MessageNotificationBody and appends it to the // addNewMessageNotification takes a common.Message and generates a new NotificationBody and appends it to the
// []Response.Notifications if the message is m.New // []Response.Notifications if the message is m.New
func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicKey, m *common.Message, responseTo *common.Message) error { func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicKey, m *common.Message, responseTo *common.Message) error {
if !m.New { if !m.New {
@ -2469,17 +2473,14 @@ func (r *ReceivedMessageState) addNewMessageNotification(publicKey ecdsa.PublicK
contactID := contactIDFromPublicKey(pubKey) contactID := contactIDFromPublicKey(pubKey)
chat := r.AllChats[m.LocalChatID] chat := r.AllChats[m.LocalChatID]
notification := MessageNotificationBody{ contact := r.AllContacts[contactID]
Message: m,
Contact: r.AllContacts[contactID],
Chat: chat,
}
if showNotification(publicKey, notification, responseTo) { if showMessageNotification(publicKey, m, chat, responseTo) {
r.Response.Notifications = append( notification, err := NewMessageNotification(m.ID, m, chat, contact, r.AllContacts)
r.Response.Notifications, if err != nil {
notification, return err
) }
r.Response.AddNotification(notification)
} }
return nil return nil
@ -3009,13 +3010,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
} }
messageState.Response.Messages = messagesWithResponses messageState.Response.Messages = messagesWithResponses
notificationsEnabled, err := m.settings.GetNotificationsEnabled()
if err != nil {
return nil, err
}
for _, message := range messageState.Response.Messages { for _, message := range messageState.Response.Messages {
if _, ok := newMessagesIds[message.ID]; ok { if _, ok := newMessagesIds[message.ID]; ok {
message.New = true message.New = true
// Create notification body to be eventually passed to `localnotifications.SendMessageNotifications()` if notificationsEnabled {
if err = messageState.addNewMessageNotification(m.identity.PublicKey, message, messagesByID[message.ResponseTo]); err != nil { // Create notification body to be eventually passed to `localnotifications.SendMessageNotifications()`
return nil, err if err = messageState.addNewMessageNotification(m.identity.PublicKey, message, messagesByID[message.ResponseTo]); err != nil {
return nil, err
}
} }
} }
} }
@ -3026,30 +3033,6 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
return messageState.Response, nil return messageState.Response, nil
} }
func showNotification(publicKey ecdsa.PublicKey, n MessageNotificationBody, responseTo *common.Message) bool {
if n.Chat != nil && n.Chat.ChatType == ChatTypeOneToOne {
return true
}
publicKeyString := common.PubkeyToHex(&publicKey)
mentioned := false
for _, mention := range n.Message.Mentions {
if publicKeyString == mention {
mentioned = true
}
}
if mentioned {
return true
}
if responseTo != nil {
return responseTo.From == publicKeyString
}
return false
}
// SetMailserver sets the currently used mailserver // SetMailserver sets the currently used mailserver
func (m *Messenger) SetMailserver(peer []byte) { func (m *Messenger) SetMailserver(peer []byte) {
m.mailserver = peer m.mailserver = peer
@ -3941,11 +3924,18 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types.
if err != nil { if err != nil {
return nil, err return nil, err
} }
response.Notifications = append(response.Notifications, MessageNotificationBody{
Message: message, notificationsEnabled, err := m.settings.GetNotificationsEnabled()
Contact: contact, if err != nil {
Chat: chat, return nil, err
}) }
if notificationsEnabled {
notification, err := NewMessageNotification(message.ID, message, chat, contact, m.allContacts)
if err != nil {
return nil, err
}
response.AddNotification(notification)
}
} }

View File

@ -201,6 +201,25 @@ func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommun
response := &MessengerResponse{RequestsToJoinCommunity: []*communities.RequestToJoin{requestToJoin}} response := &MessengerResponse{RequestsToJoinCommunity: []*communities.RequestToJoin{requestToJoin}}
response.AddCommunity(community) response.AddCommunity(community)
// We send a push notification in the background
go func() {
if m.pushNotificationClient != nil {
pks, err := community.CanManageUsersPublicKeys()
if err != nil {
m.logger.Error("failed to get pks", zap.Error(err))
return
}
for _, publicKey := range pks {
pkString := common.PubkeyToHex(publicKey)
_, err = m.pushNotificationClient.SendNotification(publicKey, nil, requestToJoin.ID, pkString, protobuf.PushNotification_REQUEST_TO_JOIN_COMMUNITY)
if err != nil {
m.logger.Error("error sending notification", zap.Error(err))
return
}
}
}
}()
return response, nil return response, nil
} }
@ -314,7 +333,7 @@ func (m *Messenger) CreateCommunity(request *requests.CreateCommunity) (*Messeng
} }
description.Members = make(map[string]*protobuf.CommunityMember) description.Members = make(map[string]*protobuf.CommunityMember)
description.Members[common.PubkeyToHex(&m.identity.PublicKey)] = &protobuf.CommunityMember{} description.Members[common.PubkeyToHex(&m.identity.PublicKey)] = &protobuf.CommunityMember{Roles: []protobuf.CommunityMember_Roles{protobuf.CommunityMember_ROLE_ALL}}
community, err := m.communitiesManager.CreateCommunity(description) community, err := m.communitiesManager.CreateCommunity(description)
if err != nil { if err != nil {

View File

@ -7,6 +7,7 @@ import (
"github.com/status-im/status-go/protocol/communities" "github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/transport" "github.com/status-im/status-go/protocol/transport"
localnotifications "github.com/status-im/status-go/services/local-notifications"
"github.com/status-im/status-go/services/mailservers" "github.com/status-im/status-go/services/mailservers"
) )
@ -23,12 +24,13 @@ type MessengerResponse struct {
Mailservers []mailservers.Mailserver Mailservers []mailservers.Mailserver
MailserverTopics []mailservers.MailserverTopic MailserverTopics []mailservers.MailserverTopic
MailserverRanges []mailservers.ChatRequestRange MailserverRanges []mailservers.ChatRequestRange
// Notifications a list of MessageNotificationBody derived from received messages that are useful to notify the user about
Notifications []MessageNotificationBody
chats map[string]*Chat // notifications a list of notifications derived from messenger events
removedChats map[string]bool // that are useful to notify the user about
communities map[string]*communities.Community notifications map[string]*localnotifications.Notification
chats map[string]*Chat
removedChats map[string]bool
communities map[string]*communities.Community
} }
func (r *MessengerResponse) MarshalJSON() ([]byte, error) { func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
@ -47,9 +49,10 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"` Mailservers []mailservers.Mailserver `json:"mailservers,omitempty"`
MailserverTopics []mailservers.MailserverTopic `json:"mailserverTopics,omitempty"` MailserverTopics []mailservers.MailserverTopic `json:"mailserverTopics,omitempty"`
MailserverRanges []mailservers.ChatRequestRange `json:"mailserverRanges,omitempty"` MailserverRanges []mailservers.ChatRequestRange `json:"mailserverRanges,omitempty"`
// Notifications a list of MessageNotificationBody derived from received messages that are useful to notify the user about // Notifications a list of notifications derived from messenger events
Notifications []MessageNotificationBody `json:"notifications"` // that are useful to notify the user about
Communities []*communities.Community `json:"communities,omitempty"` Notifications []*localnotifications.Notification `json:"notifications"`
Communities []*communities.Community `json:"communities,omitempty"`
}{ }{
Messages: r.Messages, Messages: r.Messages,
Contacts: r.Contacts, Contacts: r.Contacts,
@ -63,9 +66,9 @@ func (r *MessengerResponse) MarshalJSON() ([]byte, error) {
Mailservers: r.Mailservers, Mailservers: r.Mailservers,
MailserverTopics: r.MailserverTopics, MailserverTopics: r.MailserverTopics,
MailserverRanges: r.MailserverRanges, MailserverRanges: r.MailserverRanges,
Notifications: r.Notifications,
} }
responseItem.Notifications = r.Notifications()
responseItem.Chats = r.Chats() responseItem.Chats = r.Chats()
responseItem.Communities = r.Communities() responseItem.Communities = r.Communities()
responseItem.RemovedChats = r.RemovedChats() responseItem.RemovedChats = r.RemovedChats()
@ -97,6 +100,14 @@ func (r *MessengerResponse) Communities() []*communities.Community {
return communities return communities
} }
func (r *MessengerResponse) Notifications() []*localnotifications.Notification {
var notifications []*localnotifications.Notification
for _, n := range r.notifications {
notifications = append(notifications, n)
}
return notifications
}
func (r *MessengerResponse) IsEmpty() bool { func (r *MessengerResponse) IsEmpty() bool {
return len(r.chats)+ return len(r.chats)+
len(r.Messages)+ len(r.Messages)+
@ -112,7 +123,7 @@ func (r *MessengerResponse) IsEmpty() bool {
len(r.MailserverTopics)+ len(r.MailserverTopics)+
len(r.Mailservers)+ len(r.Mailservers)+
len(r.MailserverRanges)+ len(r.MailserverRanges)+
len(r.Notifications)+ len(r.notifications)+
len(r.RequestsToJoinCommunity) == 0 len(r.RequestsToJoinCommunity) == 0
} }
@ -127,7 +138,6 @@ func (r *MessengerResponse) Merge(response *MessengerResponse) error {
len(response.Mailservers)+ len(response.Mailservers)+
len(response.MailserverTopics)+ len(response.MailserverTopics)+
len(response.MailserverRanges)+ len(response.MailserverRanges)+
len(response.Notifications)+
len(response.EmojiReactions)+ len(response.EmojiReactions)+
len(response.CommunityChanges) != 0 { len(response.CommunityChanges) != 0 {
return ErrNotImplemented return ErrNotImplemented
@ -135,6 +145,7 @@ func (r *MessengerResponse) Merge(response *MessengerResponse) error {
r.AddChats(response.Chats()) r.AddChats(response.Chats())
r.AddRemovedChats(response.RemovedChats()) r.AddRemovedChats(response.RemovedChats())
r.AddNotifications(response.Notifications())
r.overrideMessages(response.Messages) r.overrideMessages(response.Messages)
r.overrideFilters(response.Filters) r.overrideFilters(response.Filters)
r.overrideRemovedFilters(response.Filters) r.overrideRemovedFilters(response.Filters)
@ -219,6 +230,24 @@ func (r *MessengerResponse) AddChats(chats []*Chat) {
} }
} }
func (r *MessengerResponse) AddNotification(n *localnotifications.Notification) {
if r.notifications == nil {
r.notifications = make(map[string]*localnotifications.Notification)
}
r.notifications[n.ID.String()] = n
}
func (r *MessengerResponse) ClearNotifications() {
r.notifications = nil
}
func (r *MessengerResponse) AddNotifications(notifications []*localnotifications.Notification) {
for _, c := range notifications {
r.AddNotification(c)
}
}
func (r *MessengerResponse) AddRemovedChats(chats []string) { func (r *MessengerResponse) AddRemovedChats(chats []string) {
for _, c := range chats { for _, c := range chats {
r.AddRemovedChat(c) r.AddRemovedChat(c)

View File

@ -88,18 +88,21 @@ const (
PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE PushNotification_PushNotificationType = 0 PushNotification_UNKNOWN_PUSH_NOTIFICATION_TYPE PushNotification_PushNotificationType = 0
PushNotification_MESSAGE PushNotification_PushNotificationType = 1 PushNotification_MESSAGE PushNotification_PushNotificationType = 1
PushNotification_MENTION PushNotification_PushNotificationType = 2 PushNotification_MENTION PushNotification_PushNotificationType = 2
PushNotification_REQUEST_TO_JOIN_COMMUNITY PushNotification_PushNotificationType = 3
) )
var PushNotification_PushNotificationType_name = map[int32]string{ var PushNotification_PushNotificationType_name = map[int32]string{
0: "UNKNOWN_PUSH_NOTIFICATION_TYPE", 0: "UNKNOWN_PUSH_NOTIFICATION_TYPE",
1: "MESSAGE", 1: "MESSAGE",
2: "MENTION", 2: "MENTION",
3: "REQUEST_TO_JOIN_COMMUNITY",
} }
var PushNotification_PushNotificationType_value = map[string]int32{ var PushNotification_PushNotificationType_value = map[string]int32{
"UNKNOWN_PUSH_NOTIFICATION_TYPE": 0, "UNKNOWN_PUSH_NOTIFICATION_TYPE": 0,
"MESSAGE": 1, "MESSAGE": 1,
"MENTION": 2, "MENTION": 2,
"REQUEST_TO_JOIN_COMMUNITY": 3,
} }
func (x PushNotification_PushNotificationType) String() string { func (x PushNotification_PushNotificationType) String() string {
@ -833,70 +836,72 @@ func init() {
} }
var fileDescriptor_200acd86044eaa5d = []byte{ var fileDescriptor_200acd86044eaa5d = []byte{
// 1038 bytes of a gzipped FileDescriptorProto // 1064 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xdd, 0x6e, 0xe3, 0x44, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4d, 0x6f, 0xe3, 0xc4,
0x14, 0xc6, 0x4e, 0xda, 0x24, 0x27, 0x69, 0x9a, 0x0e, 0x6d, 0x6a, 0x0a, 0x5d, 0x82, 0x01, 0x11, 0x1b, 0xff, 0x3b, 0x49, 0x9b, 0xe4, 0x49, 0x9a, 0x7a, 0xe7, 0xdf, 0x4d, 0xbd, 0x85, 0x2e, 0xc1,
0xf5, 0xa2, 0xa0, 0x22, 0xb1, 0x2b, 0xf6, 0x86, 0x90, 0xba, 0x5d, 0xab, 0x8d, 0x1d, 0x26, 0x2e, 0x80, 0x88, 0x7a, 0x28, 0xa8, 0x48, 0xec, 0x8a, 0xbd, 0x10, 0x52, 0xb7, 0x6b, 0xda, 0xd8, 0xd9,
0xab, 0x95, 0x90, 0x2c, 0xd7, 0x9e, 0xb6, 0x56, 0x53, 0x8f, 0xf1, 0x4c, 0x8a, 0x72, 0xc7, 0x03, 0x89, 0xc3, 0xaa, 0x12, 0xd2, 0xc8, 0xb5, 0xa7, 0xad, 0xd5, 0xd4, 0x36, 0x9e, 0x49, 0x51, 0x6e,
0x70, 0xc3, 0x2d, 0x57, 0x3c, 0xc3, 0xbe, 0x12, 0x2f, 0x82, 0x3c, 0x1e, 0xa7, 0x6e, 0x93, 0xa6, 0x88, 0x33, 0x17, 0xae, 0x9c, 0xf8, 0x0c, 0xfb, 0x09, 0x91, 0xc7, 0xe3, 0xd4, 0x6d, 0xd2, 0x17,
0x45, 0xe2, 0x2a, 0x39, 0xdf, 0xf9, 0x99, 0xf3, 0xfb, 0x19, 0xb4, 0x78, 0xc2, 0xae, 0xdc, 0x88, 0x24, 0x4e, 0xc9, 0xf3, 0x7b, 0x5e, 0xe6, 0x79, 0xfd, 0x19, 0xb4, 0x78, 0xca, 0x2e, 0x48, 0x18,
0xf2, 0xf0, 0x22, 0xf4, 0x3d, 0x1e, 0xd2, 0x88, 0xed, 0xc7, 0x09, 0xe5, 0x14, 0x55, 0xc5, 0xcf, 0xf1, 0xe0, 0x2c, 0xf0, 0x5c, 0x1e, 0x44, 0x21, 0xdb, 0x8d, 0x93, 0x88, 0x47, 0xa8, 0x26, 0x7e,
0xf9, 0xe4, 0x62, 0xe7, 0x43, 0xff, 0xca, 0xe3, 0x6e, 0x18, 0x90, 0x88, 0x87, 0x7c, 0x9a, 0xa9, 0x4e, 0xa7, 0x67, 0x5b, 0xff, 0xf7, 0x2e, 0x5c, 0x4e, 0x02, 0x9f, 0x86, 0x3c, 0xe0, 0xb3, 0x4c,
0xf5, 0xbf, 0x57, 0xe0, 0x93, 0xe1, 0x84, 0x5d, 0x59, 0x05, 0x57, 0x4c, 0x2e, 0x43, 0xc6, 0x13, 0xad, 0xff, 0xbd, 0x02, 0x1f, 0x0f, 0xa7, 0xec, 0xc2, 0x2a, 0xb8, 0x62, 0x7a, 0x1e, 0x30, 0x9e,
0xf1, 0x1f, 0xd9, 0x00, 0x9c, 0x5e, 0x93, 0xc8, 0xe5, 0xd3, 0x98, 0x68, 0x4a, 0x47, 0xe9, 0x36, 0x88, 0xff, 0xc8, 0x06, 0xe0, 0xd1, 0x25, 0x0d, 0x09, 0x9f, 0xc5, 0x54, 0x53, 0x3a, 0x4a, 0xb7,
0x0f, 0xbe, 0xd9, 0xcf, 0x83, 0xee, 0x2f, 0xf3, 0xdd, 0x77, 0x52, 0x47, 0x67, 0x1a, 0x13, 0x5c, 0xb5, 0xf7, 0xf5, 0x6e, 0x1e, 0x74, 0xf7, 0x21, 0xdf, 0x5d, 0x27, 0x75, 0x74, 0x66, 0x31, 0xc5,
0xe3, 0xf9, 0x5f, 0xf4, 0x19, 0x34, 0x02, 0x72, 0x1b, 0xfa, 0xc4, 0x15, 0x98, 0xa6, 0x76, 0x94, 0x75, 0x9e, 0xff, 0x45, 0x9f, 0x42, 0xd3, 0xa7, 0xd7, 0x81, 0x47, 0x89, 0xc0, 0xb4, 0x52, 0x47,
0x6e, 0x0d, 0xd7, 0x33, 0x4c, 0x78, 0xa0, 0xaf, 0x60, 0x3d, 0x8c, 0x18, 0xf7, 0xc6, 0x63, 0x11, 0xe9, 0xd6, 0x71, 0x23, 0xc3, 0x84, 0x07, 0xfa, 0x12, 0xd6, 0x83, 0x90, 0x71, 0x77, 0x32, 0x11,
0xc7, 0x0d, 0x03, 0xad, 0x24, 0xac, 0x9a, 0x45, 0xd8, 0x0c, 0xd2, 0x58, 0x9e, 0xef, 0x13, 0xc6, 0x71, 0x48, 0xe0, 0x6b, 0x65, 0x61, 0xd5, 0x2a, 0xc2, 0xa6, 0x9f, 0xc6, 0x72, 0x3d, 0x8f, 0x32,
0x64, 0xac, 0x72, 0x16, 0x2b, 0xc3, 0xb2, 0x58, 0x1a, 0x54, 0x48, 0xe4, 0x9d, 0x8f, 0x49, 0xa0, 0x26, 0x63, 0x55, 0xb2, 0x58, 0x19, 0x96, 0xc5, 0xd2, 0xa0, 0x4a, 0x43, 0xf7, 0x74, 0x42, 0x7d,
0xad, 0x74, 0x94, 0x6e, 0x15, 0xe7, 0x62, 0xaa, 0xb9, 0x25, 0x09, 0x0b, 0x69, 0xa4, 0xad, 0x76, 0x6d, 0xa5, 0xa3, 0x74, 0x6b, 0x38, 0x17, 0x53, 0xcd, 0x35, 0x4d, 0x58, 0x10, 0x85, 0xda, 0x6a,
0x94, 0x6e, 0x19, 0xe7, 0x22, 0xea, 0x42, 0xcb, 0x1b, 0x8f, 0xe9, 0x6f, 0x24, 0x70, 0xaf, 0xc9, 0x47, 0xe9, 0x56, 0x70, 0x2e, 0xa2, 0x2e, 0xa8, 0xee, 0x64, 0x12, 0xfd, 0x4a, 0x7d, 0x72, 0x49,
0xd4, 0x1d, 0x87, 0x8c, 0x6b, 0x95, 0x4e, 0xa9, 0xdb, 0xc0, 0x4d, 0x89, 0x9f, 0x90, 0xe9, 0x69, 0x67, 0x64, 0x12, 0x30, 0xae, 0x55, 0x3b, 0xe5, 0x6e, 0x13, 0xb7, 0x24, 0x7e, 0x44, 0x67, 0xc7,
0xc8, 0x38, 0xda, 0x83, 0x8d, 0xf3, 0x31, 0xf5, 0xaf, 0x49, 0xe0, 0x8a, 0xee, 0x0a, 0xd3, 0xaa, 0x01, 0xe3, 0x68, 0x07, 0x9e, 0x9d, 0x4e, 0x22, 0xef, 0x92, 0xfa, 0x44, 0x74, 0x57, 0x98, 0xd6,
0x30, 0x5d, 0x97, 0x8a, 0xfe, 0x95, 0xc7, 0x85, 0xed, 0x0b, 0x80, 0x49, 0x94, 0x88, 0xfe, 0x90, 0x84, 0xe9, 0xba, 0x54, 0xf4, 0x2f, 0x5c, 0x2e, 0x6c, 0x5f, 0x02, 0x4c, 0xc3, 0x44, 0xf4, 0x87,
0x44, 0xab, 0x89, 0x64, 0x0a, 0x08, 0xda, 0x84, 0x95, 0xcb, 0xc4, 0x8b, 0xb8, 0x06, 0x1d, 0xa5, 0x26, 0x5a, 0x5d, 0x24, 0x53, 0x40, 0xd0, 0x06, 0xac, 0x9c, 0x27, 0x6e, 0xc8, 0x35, 0xe8, 0x28,
0xdb, 0xc0, 0x99, 0x80, 0x5e, 0x82, 0x26, 0xde, 0x74, 0x2f, 0x12, 0x7a, 0xe3, 0xfa, 0x34, 0xe2, 0xdd, 0x26, 0xce, 0x04, 0xf4, 0x0a, 0x34, 0xf1, 0x26, 0x39, 0x4b, 0xa2, 0x2b, 0xe2, 0x45, 0x21,
0x9e, 0xcf, 0x99, 0x4b, 0xa3, 0xf1, 0x54, 0xab, 0x8b, 0x18, 0x5b, 0x42, 0x7f, 0x94, 0xd0, 0x9b, 0x77, 0x3d, 0xce, 0x48, 0x14, 0x4e, 0x66, 0x5a, 0x43, 0xc4, 0x78, 0x2e, 0xf4, 0x07, 0x49, 0x74,
0xbe, 0xd4, 0xda, 0xd1, 0x78, 0x8a, 0x3e, 0x86, 0x9a, 0x17, 0x47, 0x2e, 0xa7, 0x71, 0xe8, 0x6b, 0xd5, 0x97, 0x5a, 0x3b, 0x9c, 0xcc, 0xd0, 0x47, 0x50, 0x77, 0xe3, 0x90, 0xf0, 0x28, 0x0e, 0x3c,
0x0d, 0xd1, 0x98, 0xaa, 0x17, 0x47, 0x4e, 0x2a, 0xa3, 0x2f, 0xa1, 0x29, 0xd2, 0x73, 0x6f, 0xd2, 0xad, 0x29, 0x1a, 0x53, 0x73, 0xe3, 0xd0, 0x49, 0x65, 0xf4, 0x05, 0xb4, 0x44, 0x7a, 0xe4, 0x2a,
0x6d, 0xa0, 0x11, 0xd3, 0xd6, 0x44, 0xac, 0x35, 0x81, 0x0e, 0x24, 0x88, 0x5e, 0xc3, 0x4e, 0xde, 0xdd, 0x86, 0x28, 0x64, 0xda, 0x9a, 0x88, 0xb5, 0x26, 0xd0, 0x81, 0x04, 0xd1, 0x1b, 0xd8, 0xca,
0x88, 0xdc, 0xb0, 0x50, 0x67, 0x53, 0xd4, 0xb9, 0x2d, 0x2d, 0x72, 0xa7, 0xbc, 0x5e, 0xfd, 0x08, 0x1b, 0x91, 0x1b, 0x16, 0xea, 0x6c, 0x89, 0x3a, 0x37, 0xa5, 0x45, 0xee, 0x94, 0xd7, 0xab, 0x1f,
0x6a, 0xb3, 0x05, 0x40, 0x6d, 0x40, 0x67, 0xd6, 0x89, 0x65, 0xbf, 0xb5, 0x5c, 0xc7, 0x3e, 0x31, 0x40, 0x7d, 0xbe, 0x00, 0xa8, 0x0d, 0x68, 0x6c, 0x1d, 0x59, 0xf6, 0x7b, 0x8b, 0x38, 0xf6, 0x91,
0x2c, 0xd7, 0x79, 0x37, 0x34, 0x5a, 0x1f, 0xa0, 0x35, 0xa8, 0xf5, 0x86, 0x12, 0x6b, 0x29, 0x08, 0x61, 0x11, 0xe7, 0x64, 0x68, 0xa8, 0xff, 0x43, 0x6b, 0x50, 0xef, 0x0d, 0x25, 0xa6, 0x2a, 0x08,
0x41, 0xf3, 0xc8, 0xc4, 0xc6, 0x8f, 0xbd, 0x91, 0x21, 0x31, 0x55, 0x7f, 0xaf, 0xc2, 0x17, 0xcb, 0x41, 0xeb, 0xc0, 0xc4, 0xc6, 0x0f, 0xbd, 0x91, 0x21, 0xb1, 0x92, 0xfe, 0xa1, 0x04, 0x9f, 0x3f,
0xd6, 0x0c, 0x13, 0x16, 0xd3, 0x88, 0x91, 0x74, 0xa0, 0x6c, 0x22, 0x46, 0x2f, 0xf6, 0xb4, 0x8a, 0xb4, 0x66, 0x98, 0xb2, 0x38, 0x0a, 0x19, 0x4d, 0x07, 0xca, 0xa6, 0x62, 0xf4, 0x62, 0x4f, 0x6b,
0x73, 0x11, 0x59, 0xb0, 0x42, 0x92, 0x84, 0x26, 0x62, 0xd9, 0x9a, 0x07, 0xaf, 0x9e, 0xb7, 0xbf, 0x38, 0x17, 0x91, 0x05, 0x2b, 0x34, 0x49, 0xa2, 0x44, 0x2c, 0x5b, 0x6b, 0xef, 0xf5, 0xd3, 0xf6,
0x79, 0xe0, 0x7d, 0x23, 0xf5, 0x15, 0x7b, 0x9c, 0x85, 0x41, 0xbb, 0x00, 0x09, 0xf9, 0x75, 0x42, 0x37, 0x0f, 0xbc, 0x6b, 0xa4, 0xbe, 0x62, 0x8f, 0xb3, 0x30, 0x68, 0x1b, 0x20, 0xa1, 0xbf, 0x4c,
0x18, 0xcf, 0x77, 0xb3, 0x81, 0x6b, 0x12, 0x31, 0x03, 0xfd, 0x77, 0x05, 0x6a, 0x33, 0x9f, 0x62, 0x29, 0xe3, 0xf9, 0x6e, 0x36, 0x71, 0x5d, 0x22, 0xa6, 0xaf, 0xff, 0xa6, 0x40, 0x7d, 0xee, 0x53,
0xe9, 0x06, 0xc6, 0x36, 0xce, 0x4b, 0xdf, 0x82, 0x8d, 0x41, 0xef, 0xf4, 0xc8, 0xc6, 0x03, 0xe3, 0x2c, 0xdd, 0xc0, 0xd8, 0xc6, 0x79, 0xe9, 0xcf, 0xe1, 0xd9, 0xa0, 0x77, 0x7c, 0x60, 0xe3, 0x81,
0xd0, 0x1d, 0x18, 0xa3, 0x51, 0xef, 0xd8, 0x68, 0x29, 0x68, 0x13, 0x5a, 0x3f, 0x1b, 0x78, 0x64, 0xb1, 0x4f, 0x06, 0xc6, 0x68, 0xd4, 0x3b, 0x34, 0x54, 0x05, 0x6d, 0x80, 0xfa, 0x93, 0x81, 0x47,
0xda, 0x96, 0x3b, 0x30, 0x47, 0x83, 0x9e, 0xd3, 0x7f, 0xd3, 0x52, 0xd1, 0x0e, 0xb4, 0xcf, 0xac, 0xa6, 0x6d, 0x91, 0x81, 0x39, 0x1a, 0xf4, 0x9c, 0xfe, 0x5b, 0xb5, 0x84, 0xb6, 0xa0, 0x3d, 0xb6,
0xd1, 0xd9, 0x70, 0x68, 0x63, 0xc7, 0x38, 0x2c, 0xf6, 0xb0, 0x94, 0x36, 0xcd, 0xb4, 0x1c, 0x03, 0x46, 0xe3, 0xe1, 0xd0, 0xc6, 0x8e, 0xb1, 0x5f, 0xec, 0x61, 0x39, 0x6d, 0x9a, 0x69, 0x39, 0x06,
0x5b, 0xbd, 0xd3, 0xec, 0x85, 0x56, 0x59, 0x7f, 0xaf, 0x80, 0x26, 0xd7, 0xa1, 0x4f, 0x03, 0xd2, 0xb6, 0x7a, 0xc7, 0xd9, 0x0b, 0x6a, 0x45, 0xff, 0xa0, 0x80, 0x26, 0xd7, 0xa1, 0x1f, 0xf9, 0xb4,
0x0b, 0x6e, 0x49, 0xc2, 0x43, 0x46, 0xd2, 0x31, 0xa2, 0x77, 0xd0, 0x9e, 0xe3, 0x0b, 0x37, 0x8c, 0xe7, 0x5f, 0xd3, 0x84, 0x07, 0x8c, 0xa6, 0x63, 0x44, 0x27, 0xd0, 0x5e, 0xe0, 0x0b, 0x12, 0x84,
0x2e, 0xa8, 0xa6, 0x74, 0x4a, 0xdd, 0xfa, 0xc1, 0xe7, 0x8f, 0xf7, 0xe7, 0xa7, 0x09, 0x49, 0xa6, 0x67, 0x91, 0xa6, 0x74, 0xca, 0xdd, 0xc6, 0xde, 0x67, 0xf7, 0xf7, 0xe7, 0xdd, 0x94, 0x26, 0x33,
0x66, 0x74, 0x41, 0xf1, 0x66, 0xfc, 0x40, 0x95, 0xa2, 0xe8, 0x35, 0xac, 0xdd, 0xa3, 0x19, 0xd1, 0x33, 0x3c, 0x8b, 0xf0, 0x46, 0x7c, 0x47, 0x95, 0xa2, 0xe8, 0x0d, 0xac, 0xdd, 0xa2, 0x19, 0xd1,
0xf1, 0xfa, 0x41, 0xfb, 0x2e, 0x62, 0xba, 0x1f, 0xa6, 0xd4, 0xe2, 0x86, 0x5f, 0x90, 0xf4, 0x57, 0xf1, 0xc6, 0x5e, 0xfb, 0x26, 0x62, 0xba, 0x1f, 0xa6, 0xd4, 0xe2, 0xa6, 0x57, 0x90, 0xf4, 0xd7,
0xb0, 0xb5, 0xf0, 0x3d, 0xf4, 0x29, 0xd4, 0xe3, 0xc9, 0xf9, 0x38, 0xf4, 0xd3, 0x7b, 0x64, 0x22, 0xf0, 0x7c, 0xe9, 0x7b, 0xe8, 0x13, 0x68, 0xc4, 0xd3, 0xd3, 0x49, 0xe0, 0xa5, 0xf7, 0xc8, 0x44,
0xcb, 0x06, 0x86, 0x0c, 0x3a, 0x21, 0x53, 0xa6, 0xff, 0xa1, 0xc2, 0x47, 0x8f, 0xa6, 0x3a, 0x47, 0x96, 0x4d, 0x0c, 0x19, 0x74, 0x44, 0x67, 0x4c, 0xff, 0xa3, 0x04, 0x2f, 0xee, 0x4d, 0x75, 0x81,
0x13, 0xca, 0x3c, 0x4d, 0x2c, 0xa0, 0x1c, 0x75, 0x21, 0xe5, 0xec, 0x02, 0xdc, 0xa5, 0x92, 0x8f, 0x26, 0x94, 0x45, 0x9a, 0x58, 0x42, 0x39, 0xa5, 0xa5, 0x94, 0xb3, 0x0d, 0x70, 0x93, 0x4a, 0x3e,
0x7e, 0x96, 0xc9, 0x42, 0xea, 0x28, 0x2f, 0xa4, 0x8e, 0xd9, 0xb9, 0xaf, 0x14, 0xcf, 0xfd, 0x71, 0xfa, 0x79, 0x26, 0x4b, 0xa9, 0xa3, 0xb2, 0x94, 0x3a, 0xe6, 0xe7, 0xbe, 0x52, 0x3c, 0xf7, 0xfb,
0x52, 0xda, 0x83, 0x0d, 0x46, 0x92, 0x5b, 0x92, 0xb8, 0x85, 0xf7, 0x2b, 0xc2, 0x77, 0x3d, 0x53, 0x49, 0x69, 0x07, 0x9e, 0x31, 0x9a, 0x5c, 0xd3, 0x84, 0x14, 0xde, 0xaf, 0x0a, 0xdf, 0xf5, 0x4c,
0x0c, 0xf3, 0x2c, 0xf4, 0x3f, 0x15, 0xd8, 0x5d, 0xd8, 0x8e, 0xd9, 0xad, 0xbc, 0x84, 0xf2, 0x7f, 0x31, 0xcc, 0xb3, 0xd0, 0xff, 0x54, 0x60, 0x7b, 0x69, 0x3b, 0xe6, 0xb7, 0xf2, 0x0a, 0x2a, 0xff,
0x1d, 0xb8, 0x70, 0x48, 0xeb, 0xbf, 0x21, 0x8c, 0x79, 0x97, 0x24, 0xef, 0x51, 0x03, 0xd7, 0x24, 0x76, 0xe0, 0xc2, 0x21, 0xad, 0xff, 0x8a, 0x32, 0xe6, 0x9e, 0xd3, 0xbc, 0x47, 0x4d, 0x5c, 0x97,
0x62, 0x06, 0xc5, 0x1b, 0x2c, 0xdd, 0xbb, 0x41, 0xfd, 0x1f, 0x15, 0x5a, 0x0f, 0x83, 0x3f, 0x67, 0x88, 0xe9, 0x17, 0x6f, 0xb0, 0x7c, 0xeb, 0x06, 0xf5, 0xdf, 0xcb, 0xa0, 0xde, 0x0d, 0xfe, 0x94,
0x32, 0xdb, 0x50, 0x91, 0x1b, 0x25, 0x5f, 0x5b, 0xcd, 0x76, 0xe6, 0xa9, 0x49, 0x2c, 0x98, 0x68, 0xc9, 0x6c, 0x42, 0x55, 0x6e, 0x94, 0x7c, 0x6d, 0x35, 0xdb, 0x99, 0xc7, 0x26, 0xb1, 0x64, 0xa2,
0x79, 0xe1, 0x44, 0x35, 0xa8, 0xc8, 0xfc, 0xe5, 0x28, 0x72, 0x11, 0xf5, 0xa1, 0x2c, 0xbe, 0x7a, 0x95, 0xa5, 0x13, 0xd5, 0xa0, 0x2a, 0xf3, 0x97, 0xa3, 0xc8, 0x45, 0xd4, 0x87, 0x8a, 0xf8, 0xea,
0xab, 0x82, 0x35, 0xbe, 0x7e, 0xbc, 0x49, 0x73, 0x80, 0x20, 0x0b, 0xe1, 0x8c, 0xda, 0xb0, 0xea, 0xad, 0x0a, 0xd6, 0xf8, 0xea, 0xfe, 0x26, 0x2d, 0x00, 0x82, 0x2c, 0x84, 0x33, 0x6a, 0xc3, 0xaa,
0x4d, 0xf8, 0x15, 0x4d, 0xe4, 0xb0, 0xa4, 0xa4, 0x3b, 0xb0, 0xb9, 0xc8, 0x0b, 0xe9, 0xf0, 0x22, 0x3b, 0xe5, 0x17, 0x51, 0x22, 0x87, 0x25, 0x25, 0x9d, 0xc1, 0xc6, 0x32, 0x2f, 0xa4, 0xc3, 0xcb,
0xa7, 0x8b, 0xe1, 0xd9, 0xe8, 0x8d, 0x6b, 0xd9, 0x8e, 0x79, 0x64, 0xf6, 0x7b, 0x4e, 0xca, 0x08, 0x9c, 0x2e, 0x86, 0xe3, 0xd1, 0x5b, 0x62, 0xd9, 0x8e, 0x79, 0x60, 0xf6, 0x7b, 0x4e, 0xca, 0x08,
0x92, 0x3a, 0xea, 0x50, 0xb9, 0x23, 0x0c, 0x21, 0x58, 0xa9, 0xba, 0xa5, 0xea, 0x31, 0x6c, 0xcf, 0x92, 0x3a, 0x1a, 0x50, 0xbd, 0x21, 0x0c, 0x21, 0x58, 0xa9, 0x5a, 0x2d, 0xa1, 0x6d, 0x78, 0x81,
0x53, 0x9a, 0xe0, 0x25, 0xf4, 0x1d, 0x54, 0x25, 0x45, 0x31, 0x39, 0xf6, 0x9d, 0x25, 0x3c, 0x38, 0x8d, 0x77, 0x63, 0x63, 0xe4, 0x10, 0xc7, 0x26, 0x3f, 0xda, 0xa6, 0x45, 0xfa, 0xf6, 0x60, 0x30,
0xb3, 0x7d, 0x62, 0xe2, 0xfa, 0x5f, 0x2a, 0xb4, 0xe7, 0x9f, 0x8c, 0x69, 0xc2, 0x97, 0x10, 0xf2, 0xb6, 0x4c, 0xe7, 0x44, 0x2d, 0xeb, 0x31, 0x6c, 0x2e, 0x32, 0x9e, 0xa0, 0x2d, 0xf4, 0x2d, 0xd4,
0x0f, 0xf7, 0x09, 0x79, 0x6f, 0x19, 0x21, 0xa7, 0xa1, 0x16, 0x52, 0xf0, 0xff, 0x31, 0x7d, 0xfd, 0x24, 0x83, 0x31, 0xb9, 0x15, 0x5b, 0x0f, 0xd0, 0xe4, 0xdc, 0xf6, 0x91, 0x85, 0xd0, 0xff, 0x2a,
0x97, 0xe7, 0x50, 0xf5, 0x3a, 0xd4, 0xdf, 0x62, 0xdb, 0x3a, 0x2e, 0x7e, 0xa7, 0x1e, 0x50, 0xae, 0x41, 0x7b, 0xf1, 0xc9, 0x38, 0x4a, 0xf8, 0x03, 0x7c, 0xfd, 0xfd, 0x6d, 0xbe, 0xde, 0x79, 0x88,
0x9a, 0x62, 0x96, 0xed, 0xb8, 0xd8, 0x38, 0x36, 0x47, 0x8e, 0x81, 0x8d, 0xc3, 0x56, 0x49, 0x9f, 0xaf, 0xd3, 0x50, 0x4b, 0x19, 0xfa, 0xbf, 0x58, 0x0e, 0xfd, 0xe7, 0xa7, 0x30, 0xf9, 0x3a, 0x34,
0x80, 0x36, 0x5f, 0x90, 0x3c, 0xc1, 0xfb, 0x7d, 0x55, 0x1e, 0x5e, 0xd2, 0xf7, 0x50, 0x49, 0x44, 0xde, 0x63, 0xdb, 0x3a, 0x2c, 0x7e, 0xc6, 0xee, 0x30, 0x72, 0x29, 0xc5, 0x2c, 0xdb, 0x21, 0xd8,
0xed, 0x4c, 0x53, 0xc5, 0xb4, 0x3a, 0x4f, 0x35, 0x09, 0xe7, 0x0e, 0xe7, 0xab, 0xc2, 0xf2, 0xdb, 0x38, 0x34, 0x47, 0x8e, 0x81, 0x8d, 0x7d, 0xb5, 0xac, 0x4f, 0x41, 0x5b, 0x2c, 0x48, 0x5e, 0xe8,
0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x30, 0x4e, 0x6f, 0x73, 0x17, 0x0a, 0x00, 0x00, 0xed, 0xbe, 0x2a, 0x77, 0x0f, 0xed, 0x3b, 0xa8, 0x26, 0xa2, 0x76, 0xa6, 0x95, 0xc4, 0xb4, 0x3a,
0x8f, 0x35, 0x09, 0xe7, 0x0e, 0xa7, 0xab, 0xc2, 0xf2, 0x9b, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff,
0x78, 0x43, 0x10, 0x6f, 0x36, 0x0a, 0x00, 0x00,
} }

View File

@ -76,6 +76,7 @@ message PushNotification {
UNKNOWN_PUSH_NOTIFICATION_TYPE = 0; UNKNOWN_PUSH_NOTIFICATION_TYPE = 0;
MESSAGE = 1; MESSAGE = 1;
MENTION = 2; MENTION = 2;
REQUEST_TO_JOIN_COMMUNITY = 3;
} }
bytes author = 7; bytes author = 7;
} }

View File

@ -14,9 +14,11 @@ import (
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/protobuf" "github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/pushnotificationclient" "github.com/status-im/status-go/protocol/pushnotificationclient"
"github.com/status-im/status-go/protocol/pushnotificationserver" "github.com/status-im/status-go/protocol/pushnotificationserver"
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/tt" "github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/waku" "github.com/status-im/status-go/waku"
) )
@ -843,3 +845,136 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationMention() {
s.Require().NoError(alice.Shutdown()) s.Require().NoError(alice.Shutdown())
s.Require().NoError(server.Shutdown()) s.Require().NoError(server.Shutdown())
} }
func (s *MessengerPushNotificationSuite) TestReceivePushNotificationCommunityRequest() {
bob := s.m
serverKey, err := crypto.GenerateKey()
s.Require().NoError(err)
server := s.newPushNotificationServer(s.shh, serverKey)
alice := s.newMessenger(s.shh)
// start alice and enable sending push notifications
_, err = alice.Start()
s.Require().NoError(err)
s.Require().NoError(alice.EnableSendingPushNotifications())
// Register bob
err = bob.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
s.Require().NoError(err)
err = bob.RegisterForPushNotifications(context.Background(), bob1DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)
// Pull servers and check we registered
err = tt.RetryWithBackOff(func() error {
_, err = server.RetrieveAll()
if err != nil {
return err
}
_, err = bob.RetrieveAll()
if err != nil {
return err
}
registered, err := bob.RegisteredForPushNotifications()
if err != nil {
return err
}
if !registered {
return errors.New("not registered")
}
bobServers, err := bob.GetPushNotificationsServers()
if err != nil {
return err
}
if len(bobServers) == 0 {
return errors.New("not registered")
}
return nil
})
// Make sure we receive it
s.Require().NoError(err)
_, err = bob.GetPushNotificationsServers()
s.Require().NoError(err)
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
response, err := bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community := response.Communities()[0]
// Send an community message
chat := CreateOneToOneChat(common.PubkeyToHex(&alice.identity.PublicKey), &alice.identity.PublicKey, alice.transport)
inputMessage := &common.Message{}
inputMessage.ChatId = chat.ID
inputMessage.Text = "some text"
inputMessage.CommunityID = community.IDString()
err = bob.SaveChat(chat)
s.NoError(err)
_, err = bob.SendChatMessage(context.Background(), inputMessage)
s.NoError(err)
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
// We try to join the org
response, err = alice.RequestToJoinCommunity(request)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin1 := response.RequestsToJoinCommunity[0]
s.Require().NotNil(requestToJoin1)
s.Require().Equal(community.ID(), requestToJoin1.CommunityID)
s.Require().True(requestToJoin1.Our)
s.Require().NotEmpty(requestToJoin1.ID)
s.Require().NotEmpty(requestToJoin1.Clock)
s.Require().Equal(requestToJoin1.PublicKey, common.PubkeyToHex(&alice.identity.PublicKey))
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)
err = tt.RetryWithBackOff(func() error {
_, err = server.RetrieveAll()
if err != nil {
return err
}
_, err = alice.RetrieveAll()
if err != nil {
return err
}
if server.pushNotificationServer.SentRequests != 1 {
return errors.New("request not sent")
}
return nil
})
s.Require().NoError(err)
s.Require().NoError(alice.Shutdown())
s.Require().NoError(server.Shutdown())
}

View File

@ -484,6 +484,7 @@ func (c *Client) HandlePushNotificationQueryResponse(serverPublicKey *ecdsa.Publ
// get the public key associated with this query // get the public key associated with this query
clientPublicKey, err := c.persistence.GetQueryPublicKey(response.MessageId) clientPublicKey, err := c.persistence.GetQueryPublicKey(response.MessageId)
if err != nil { if err != nil {
c.config.Logger.Error("failed to query client publicKey", zap.Error(err))
return err return err
} }
if clientPublicKey == nil { if clientPublicKey == nil {
@ -900,7 +901,7 @@ func (c *Client) handlePublicMessageSent(sentMessage *common.SentMessage) error
c.config.Logger.Debug("should no mention", zap.Any("publickey", shouldNotify)) c.config.Logger.Debug("should no mention", zap.Any("publickey", shouldNotify))
// we send the notifications and return the info of the devices notified // we send the notifications and return the info of the devices notified
infos, err := c.sendNotification(publicKey, nil, messageID, message.LocalChatID, protobuf.PushNotification_MENTION) infos, err := c.SendNotification(publicKey, nil, messageID, message.LocalChatID, protobuf.PushNotification_MENTION)
if err != nil { if err != nil {
return err return err
} }
@ -998,7 +999,7 @@ func (c *Client) handleDirectMessageSent(sentMessage *common.SentMessage) error
} }
// we send the notifications and return the info of the devices notified // we send the notifications and return the info of the devices notified
infos, err := c.sendNotification(publicKey, installationIDs, trackedMessageIDs[0], chatID, protobuf.PushNotification_MESSAGE) infos, err := c.SendNotification(publicKey, installationIDs, trackedMessageIDs[0], chatID, protobuf.PushNotification_MESSAGE)
if err != nil { if err != nil {
return err return err
} }
@ -1280,9 +1281,9 @@ func (c *Client) registerWithServer(registration *protobuf.PushNotificationRegis
return nil return nil
} }
// sendNotification sends an actual notification to the push notification server. // SendNotification sends an actual notification to the push notification server.
// the notification is sent using an ephemeral key to shield the real identity of the sender // the notification is sent using an ephemeral key to shield the real identity of the sender
func (c *Client) sendNotification(publicKey *ecdsa.PublicKey, installationIDs []string, messageID []byte, chatID string, notificationType protobuf.PushNotification_PushNotificationType) ([]*PushNotificationInfo, error) { func (c *Client) SendNotification(publicKey *ecdsa.PublicKey, installationIDs []string, messageID []byte, chatID string, notificationType protobuf.PushNotification_PushNotificationType) ([]*PushNotificationInfo, error) {
// get latest push notification infos // get latest push notification infos
err := c.queryNotificationInfo(publicKey, false) err := c.queryNotificationInfo(publicKey, false)
@ -1316,6 +1317,8 @@ func (c *Client) sendNotification(publicKey *ecdsa.PublicKey, installationIDs []
// one info per installation id, grouped by server // one info per installation id, grouped by server
actionableInfos := make(map[string][]*PushNotificationInfo) actionableInfos := make(map[string][]*PushNotificationInfo)
c.config.Logger.Info("INFOS", zap.Any("info", info))
for _, i := range info { for _, i := range info {
if !installationIDsMap[i.InstallationID] { if !installationIDsMap[i.InstallationID] {
@ -1406,7 +1409,7 @@ func (c *Client) resendNotification(pn *SentNotification) error {
return err return err
} }
_, err = c.sendNotification(pn.PublicKey, []string{pn.InstallationID}, pn.MessageID, pn.ChatID, pn.NotificationType) _, err = c.SendNotification(pn.PublicKey, []string{pn.InstallationID}, pn.MessageID, pn.ChatID, pn.NotificationType)
return err return err
} }

View File

@ -14,6 +14,7 @@ import (
const defaultNewMessageNotificationText = "You have a new message" const defaultNewMessageNotificationText = "You have a new message"
const defaultMentionNotificationText = "Someone mentioned you" const defaultMentionNotificationText = "Someone mentioned you"
const defaultRequestToJoinCommunityNotificationText = "Someone requested to join a community you are an admin of"
type GoRushRequestData struct { type GoRushRequestData struct {
EncryptedMessage string `json:"encryptedMessage"` EncryptedMessage string `json:"encryptedMessage"`
@ -56,6 +57,8 @@ func PushNotificationRegistrationToGoRushRequest(requestAndRegistrations []*Requ
var text string var text string
if request.Type == protobuf.PushNotification_MESSAGE { if request.Type == protobuf.PushNotification_MESSAGE {
text = defaultNewMessageNotificationText text = defaultNewMessageNotificationText
} else if request.Type == protobuf.PushNotification_REQUEST_TO_JOIN_COMMUNITY {
text = defaultRequestToJoinCommunityNotificationText
} else { } else {
text = defaultMentionNotificationText text = defaultMentionNotificationText
} }

View File

@ -37,6 +37,8 @@ type Server struct {
persistence Persistence persistence Persistence
config *Config config *Config
messageProcessor *common.MessageProcessor messageProcessor *common.MessageProcessor
// SentRequests keeps track of the requests sent to gorush, for testing only
SentRequests int64
} }
func New(config *Config, persistence Persistence, messageProcessor *common.MessageProcessor) *Server { func New(config *Config, persistence Persistence, messageProcessor *common.MessageProcessor) *Server {
@ -376,7 +378,7 @@ func (s *Server) buildPushNotificationReport(pn *protobuf.PushNotification, regi
} else if registration.AccessToken != pn.AccessToken { } else if registration.AccessToken != pn.AccessToken {
s.config.Logger.Debug("invalid token") s.config.Logger.Debug("invalid token")
report.Error = protobuf.PushNotificationReport_WRONG_TOKEN report.Error = protobuf.PushNotificationReport_WRONG_TOKEN
} else if (s.isMessageNotification(pn) && !s.isValidMessageNotification(pn, registration)) || (s.isMentionNotification(pn) && !s.isValidMentionNotification(pn, registration)) { } else if (s.isMessageNotification(pn) && !s.isValidMessageNotification(pn, registration)) || (s.isMentionNotification(pn) && !s.isValidMentionNotification(pn, registration)) || (s.isRequestToJoinCommunityNotification(pn) && !s.isValidRequestToJoinCommunityNotification(pn, registration)) {
s.config.Logger.Debug("filtered notification") s.config.Logger.Debug("filtered notification")
// We report as successful but don't send the notification // We report as successful but don't send the notification
// for privacy reasons, as otherwise we would disclose that // for privacy reasons, as otherwise we would disclose that
@ -450,6 +452,7 @@ func (s *Server) sendPushNotification(requestAndRegistrations []*RequestAndRegis
if len(requestAndRegistrations) == 0 { if len(requestAndRegistrations) == 0 {
return nil return nil
} }
s.SentRequests++
goRushRequest := PushNotificationRegistrationToGoRushRequest(requestAndRegistrations) goRushRequest := PushNotificationRegistrationToGoRushRequest(requestAndRegistrations)
return sendGoRushNotification(goRushRequest, s.config.GorushURL, s.config.Logger) return sendGoRushNotification(goRushRequest, s.config.GorushURL, s.config.Logger)
} }
@ -529,10 +532,21 @@ func (s *Server) isMessageNotification(pn *protobuf.PushNotification) bool {
return pn.Type == protobuf.PushNotification_MESSAGE return pn.Type == protobuf.PushNotification_MESSAGE
} }
// isValidMentionNotification checks: // isValidMessageNotification checks:
// this is a message // this is a message
// the chat is not muted // the chat is not muted
// the author is not blocked // the author is not blocked
func (s *Server) isValidMessageNotification(pn *protobuf.PushNotification, registration *protobuf.PushNotificationRegistration) bool { func (s *Server) isValidMessageNotification(pn *protobuf.PushNotification, registration *protobuf.PushNotificationRegistration) bool {
return s.isMessageNotification(pn) && !s.contains(registration.BlockedChatList, pn.ChatId) && !s.contains(registration.BlockedChatList, pn.Author) return s.isMessageNotification(pn) && !s.contains(registration.BlockedChatList, pn.ChatId) && !s.contains(registration.BlockedChatList, pn.Author)
} }
func (s *Server) isRequestToJoinCommunityNotification(pn *protobuf.PushNotification) bool {
return pn.Type == protobuf.PushNotification_REQUEST_TO_JOIN_COMMUNITY
}
// isValidRequestToJoinCommunityNotification checks:
// this is a request to join a community
// the author is not blocked
func (s *Server) isValidRequestToJoinCommunityNotification(pn *protobuf.PushNotification, registration *protobuf.PushNotificationRegistration) bool {
return s.isRequestToJoinCommunityNotification(pn) && !s.contains(registration.BlockedChatList, pn.Author)
}

View File

@ -185,10 +185,11 @@ func (s *Service) StartMessenger() (*protocol.MessengerResponse, error) {
func publishMessengerResponse(response *protocol.MessengerResponse) { func publishMessengerResponse(response *protocol.MessengerResponse) {
if !response.IsEmpty() { if !response.IsEmpty() {
PublisherSignalHandler{}.NewMessages(response) notifications := response.Notifications()
localnotifications.SendMessageNotifications(response.Notifications)
// Clear notifications as not used for now // Clear notifications as not used for now
response.Notifications = nil response.ClearNotifications()
PublisherSignalHandler{}.NewMessages(response)
localnotifications.PushMessages(notifications)
} }
} }

View File

@ -3,92 +3,69 @@ package localnotifications
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"math/big"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/rpc" "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/multiaccounts/accounts"
"github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/services/wallet" "github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/signal" "github.com/status-im/status-go/signal"
) )
type PushCategory string type PushCategory string
type transactionState string
type NotificationType string type NotificationType string
const ( type NotificationBody interface {
walletDeeplinkPrefix = "status-im://wallet/" json.Marshaler
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 {
State transactionState `json:"state"`
From common.Address `json:"from"`
To common.Address `json:"to"`
FromAccount *accounts.Account `json:"fromAccount,omitempty"`
ToAccount *accounts.Account `json:"toAccount,omitempty"`
Value *hexutil.Big `json:"value"`
ERC20 bool `json:"erc20"`
Contract common.Address `json:"contract"`
Network uint64 `json:"network"`
} }
type Notification struct { type Notification struct {
ID common.Hash `json:"id"` ID common.Hash
Platform float32 `json:"platform,omitempty"` Platform float32
Body interface{} Body NotificationBody
BodyType NotificationType `json:"bodyType"` BodyType NotificationType
Category PushCategory `json:"category,omitempty"` Title string
Deeplink string `json:"deepLink,omitempty"` Message string
Image string `json:"imageUrl,omitempty"` Category PushCategory
IsScheduled bool `json:"isScheduled,omitempty"` Deeplink string
ScheduledTime string `json:"scheduleTime,omitempty"` Image string
IsScheduled bool
ScheduledTime string
IsConversation bool
IsGroupConversation bool
ConversationID string
Timestamp uint64
Author NotificationAuthor
}
type NotificationAuthor struct {
ID string `json:"id"`
Icon string `json:"icon"`
Name string `json:"name"`
} }
// notificationAlias is an interim struct used for json un/marshalling // notificationAlias is an interim struct used for json un/marshalling
type notificationAlias struct { type notificationAlias struct {
ID common.Hash `json:"id"` ID common.Hash `json:"id"`
Platform float32 `json:"platform,omitempty"` Platform float32 `json:"platform,omitempty"`
Body json.RawMessage `json:"body"` Body json.RawMessage `json:"body"`
BodyType NotificationType `json:"bodyType"` BodyType NotificationType `json:"bodyType"`
Category PushCategory `json:"category,omitempty"` Title string `json:"title,omitempty"`
Deeplink string `json:"deepLink,omitempty"` Message string `json:"message,omitempty"`
Image string `json:"imageUrl,omitempty"` Category PushCategory `json:"category,omitempty"`
IsScheduled bool `json:"isScheduled,omitempty"` Deeplink string `json:"deepLink,omitempty"`
ScheduledTime string `json:"scheduleTime,omitempty"` Image string `json:"imageUrl,omitempty"`
} IsScheduled bool `json:"isScheduled,omitempty"`
ScheduledTime string `json:"scheduleTime,omitempty"`
// TransactionEvent - structure used to pass messages from wallet to bus IsConversation bool `json:"isConversation,omitempty"`
type TransactionEvent struct { IsGroupConversation bool `json:"isGroupConversation,omitempty"`
Type string `json:"type"` ConversationID string `json:"conversationId,omitempty"`
BlockNumber *big.Int `json:"block-number"` Timestamp uint64 `json:"timestamp,omitempty"`
Accounts []common.Address `json:"accounts"` Author NotificationAuthor `json:"notificationAuthor,omitempty"`
NewTransactionsPerAccount map[common.Address]int `json:"new-transactions"`
ERC20 bool `json:"erc20"`
MaxKnownBlocks map[common.Address]*big.Int `json:"max-known-blocks"`
} }
// MessageEvent - structure used to pass messages from chat to bus // MessageEvent - structure used to pass messages from chat to bus
@ -132,274 +109,49 @@ func NewService(appDB *sql.DB, network uint64) *Service {
} }
func (n *Notification) MarshalJSON() ([]byte, error) { func (n *Notification) MarshalJSON() ([]byte, error) {
var body json.RawMessage var body json.RawMessage
var err error if n.Body != nil {
encodedBody, err := n.Body.MarshalJSON()
switch n.BodyType { if err != nil {
case TypeTransaction: return nil, err
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)
} }
body = encodedBody
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{ alias := notificationAlias{
n.ID, ID: n.ID,
n.Platform, Platform: n.Platform,
body, Body: body,
n.BodyType, BodyType: n.BodyType,
n.Category, Category: n.Category,
n.Deeplink, Title: n.Title,
n.Image, Message: n.Message,
n.IsScheduled, Deeplink: n.Deeplink,
n.ScheduledTime, Image: n.Image,
IsScheduled: n.IsScheduled,
ScheduledTime: n.ScheduledTime,
IsConversation: n.IsConversation,
IsGroupConversation: n.IsGroupConversation,
ConversationID: n.ConversationID,
Timestamp: n.Timestamp,
Author: n.Author,
} }
return json.Marshal(alias) return json.Marshal(alias)
} }
func (n *Notification) UnmarshalJSON(data []byte) error { func PushMessages(ns []*Notification) {
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 { for _, n := range ns {
pushMessage(n) pushMessage(n)
} }
} }
func pushMessage(notification *Notification) { func pushMessage(notification *Notification) {
log.Info("Pushing a new push notification", "info", notification) log.Debug("Pushing a new push notification", "notification", notification)
signal.SendLocalNotifications(notification) signal.SendLocalNotifications(notification)
} }
func (s *Service) buildTransactionNotification(rawTransfer wallet.Transfer) *Notification {
log.Info("Handled a new transfer in buildTransactionNotification", "info", rawTransfer)
var deeplink string
var state transactionState
transfer := wallet.CastToTransferView(rawTransfer)
switch {
case transfer.TxStatus == hexutil.Uint64(0):
state = failed
case transfer.Address == transfer.To:
state = inbound
default:
state = outbound
}
from, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.From))
if err != nil {
log.Debug("Could not select From account by address", "error", err)
}
to, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.To))
if err != nil {
log.Debug("Could not select To account by address", "error", err)
}
if from != nil {
deeplink = walletDeeplinkPrefix + from.Address.String()
} else if to != nil {
deeplink = walletDeeplinkPrefix + to.Address.String()
}
body := notificationBody{
State: state,
From: transfer.From,
To: transfer.Address,
FromAccount: from,
ToAccount: to,
Value: transfer.Value,
ERC20: string(transfer.Type) == "erc20",
Contract: transfer.Contract,
Network: transfer.NetworkID,
}
return &Notification{
BodyType: TypeTransaction,
ID: transfer.ID,
Body: body,
Deeplink: deeplink,
Category: CategoryTransaction,
}
}
func (s *Service) transactionsHandler(payload TransactionEvent) {
log.Info("Handled a new transaction", "info", payload)
limit := 20
if payload.BlockNumber != nil {
for _, address := range payload.Accounts {
if payload.BlockNumber.Cmp(payload.MaxKnownBlocks[address]) >= 0 {
log.Info("Handled transfer for address", "info", address)
transfers, err := s.walletDB.GetTransfersByAddressAndBlock(address, payload.BlockNumber, int64(limit))
if err != nil {
log.Error("Could not fetch transfers", "error", err)
}
for _, transaction := range transfers {
n := s.buildTransactionNotification(transaction)
pushMessage(n)
}
}
}
}
}
// SubscribeWallet - Subscribes to wallet signals
func (s *Service) SubscribeWallet(publisher *event.Feed) error {
s.walletTransmitter.publisher = publisher
preference, err := s.db.GetWalletPreference()
if err != nil {
log.Error("Failed to get wallet preference", "error", err)
s.WatchingEnabled = false
} else {
s.WatchingEnabled = preference.Enabled
}
s.StartWalletWatcher()
return nil
}
// StartWalletWatcher - Forward wallet events to notifications
func (s *Service) StartWalletWatcher() {
if s.walletTransmitter.quit != nil {
// already running, nothing to do
return
}
if s.walletTransmitter.publisher == nil {
log.Error("wallet publisher was not initialized")
return
}
s.walletTransmitter.quit = make(chan struct{})
events := make(chan wallet.Event, 10)
sub := s.walletTransmitter.publisher.Subscribe(events)
s.walletTransmitter.wg.Add(1)
maxKnownBlocks := map[common.Address]*big.Int{}
go func() {
defer s.walletTransmitter.wg.Done()
for {
select {
case <-s.walletTransmitter.quit:
sub.Unsubscribe()
return
case err := <-sub.Err():
// technically event.Feed cannot send an error to subscription.Err channel.
// the only time we will get an event is when that channel is closed.
if err != nil {
log.Error("wallet signals transmitter failed with", "error", err)
}
return
case event := <-events:
if event.Type == wallet.EventNewBlock && len(maxKnownBlocks) > 0 {
newBlocks := false
for _, address := range event.Accounts {
if _, ok := maxKnownBlocks[address]; !ok {
newBlocks = true
maxKnownBlocks[address] = event.BlockNumber
} else if event.BlockNumber.Cmp(maxKnownBlocks[address]) == 1 {
maxKnownBlocks[address] = event.BlockNumber
newBlocks = true
}
}
if newBlocks && s.WatchingEnabled {
s.transmitter.publisher.Send(TransactionEvent{
Type: string(event.Type),
BlockNumber: event.BlockNumber,
Accounts: event.Accounts,
NewTransactionsPerAccount: event.NewTransactionsPerAccount,
ERC20: event.ERC20,
MaxKnownBlocks: maxKnownBlocks,
})
}
} else if event.Type == wallet.EventMaxKnownBlock {
for _, address := range event.Accounts {
if _, ok := maxKnownBlocks[address]; !ok {
maxKnownBlocks[address] = event.BlockNumber
}
}
}
}
}
}()
}
// StopWalletWatcher - stops watching for new wallet events
func (s *Service) StopWalletWatcher() {
if s.walletTransmitter.quit != nil {
close(s.walletTransmitter.quit)
s.walletTransmitter.wg.Wait()
s.walletTransmitter.quit = nil
}
}
// IsWatchingWallet - check if local-notifications are subscribed to wallet updates
func (s *Service) IsWatchingWallet() bool {
return s.walletTransmitter.quit != nil
}
// Start Worker which processes all incoming messages // Start Worker which processes all incoming messages
func (s *Service) Start(_ *p2p.Server) error { func (s *Service) Start(_ *p2p.Server) error {
s.started = true s.started = true
@ -473,19 +225,3 @@ func (s *Service) Protocols() []p2p.Protocol {
func (s *Service) IsStarted() bool { func (s *Service) IsStarted() bool {
return s.started 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

@ -2,9 +2,9 @@ package localnotifications
import ( import (
"database/sql" "database/sql"
"encoding/json"
"fmt" "fmt"
"math/big" "math/big"
"strings"
"testing" "testing"
"time" "time"
@ -128,19 +128,8 @@ func TestTransactionNotification(t *testing.T) {
if signalEvent == nil { if signalEvent == nil {
return fmt.Errorf("signal was not handled") return fmt.Errorf("signal was not handled")
} }
notification := struct { require.True(t, strings.Contains(string(signalEvent), `"type":"local-notifications"`))
Type string require.True(t, strings.Contains(string(signalEvent), `"to":"`+header.Address.Hex()))
Event Notification
}{}
require.NoError(t, json.Unmarshal(signalEvent, &notification))
if notification.Type != "local-notifications" {
return fmt.Errorf("wrong signal was sent")
}
if notification.Event.Body.(*notificationBody).To != header.Address {
return fmt.Errorf("transaction to address is wrong")
}
return nil return nil
}, 2*time.Second, 100*time.Millisecond)) }, 2*time.Second, 100*time.Millisecond))

View File

@ -0,0 +1,229 @@
package localnotifications
import (
"encoding/json"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/services/wallet"
)
type transactionState string
const (
walletDeeplinkPrefix = "status-im://wallet/"
failed transactionState = "failed"
inbound transactionState = "inbound"
outbound transactionState = "outbound"
)
// TransactionEvent - structure used to pass messages from wallet to bus
type TransactionEvent struct {
Type string `json:"type"`
BlockNumber *big.Int `json:"block-number"`
Accounts []common.Address `json:"accounts"`
NewTransactionsPerAccount map[common.Address]int `json:"new-transactions"`
ERC20 bool `json:"erc20"`
MaxKnownBlocks map[common.Address]*big.Int `json:"max-known-blocks"`
}
type transactionBody struct {
State transactionState `json:"state"`
From common.Address `json:"from"`
To common.Address `json:"to"`
FromAccount *accounts.Account `json:"fromAccount,omitempty"`
ToAccount *accounts.Account `json:"toAccount,omitempty"`
Value *hexutil.Big `json:"value"`
ERC20 bool `json:"erc20"`
Contract common.Address `json:"contract"`
Network uint64 `json:"network"`
}
func (t transactionBody) MarshalJSON() ([]byte, error) {
type Alias transactionBody
item := struct{ *Alias }{Alias: (*Alias)(&t)}
return json.Marshal(item)
}
func (s *Service) buildTransactionNotification(rawTransfer wallet.Transfer) *Notification {
log.Info("Handled a new transfer in buildTransactionNotification", "info", rawTransfer)
var deeplink string
var state transactionState
transfer := wallet.CastToTransferView(rawTransfer)
switch {
case transfer.TxStatus == hexutil.Uint64(0):
state = failed
case transfer.Address == transfer.To:
state = inbound
default:
state = outbound
}
from, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.From))
if err != nil {
log.Debug("Could not select From account by address", "error", err)
}
to, err := s.accountsDB.GetAccountByAddress(types.Address(transfer.To))
if err != nil {
log.Debug("Could not select To account by address", "error", err)
}
if from != nil {
deeplink = walletDeeplinkPrefix + from.Address.String()
} else if to != nil {
deeplink = walletDeeplinkPrefix + to.Address.String()
}
body := transactionBody{
State: state,
From: transfer.From,
To: transfer.Address,
FromAccount: from,
ToAccount: to,
Value: transfer.Value,
ERC20: string(transfer.Type) == "erc20",
Contract: transfer.Contract,
Network: transfer.NetworkID,
}
return &Notification{
BodyType: TypeTransaction,
ID: transfer.ID,
Body: body,
Deeplink: deeplink,
Category: CategoryTransaction,
}
}
func (s *Service) transactionsHandler(payload TransactionEvent) {
log.Info("Handled a new transaction", "info", payload)
limit := 20
if payload.BlockNumber != nil {
for _, address := range payload.Accounts {
if payload.BlockNumber.Cmp(payload.MaxKnownBlocks[address]) >= 0 {
log.Info("Handled transfer for address", "info", address)
transfers, err := s.walletDB.GetTransfersByAddressAndBlock(address, payload.BlockNumber, int64(limit))
if err != nil {
log.Error("Could not fetch transfers", "error", err)
}
for _, transaction := range transfers {
n := s.buildTransactionNotification(transaction)
pushMessage(n)
}
}
}
}
}
// SubscribeWallet - Subscribes to wallet signals
func (s *Service) SubscribeWallet(publisher *event.Feed) error {
s.walletTransmitter.publisher = publisher
preference, err := s.db.GetWalletPreference()
if err != nil {
log.Error("Failed to get wallet preference", "error", err)
s.WatchingEnabled = false
} else {
s.WatchingEnabled = preference.Enabled
}
s.StartWalletWatcher()
return nil
}
// StartWalletWatcher - Forward wallet events to notifications
func (s *Service) StartWalletWatcher() {
if s.walletTransmitter.quit != nil {
// already running, nothing to do
return
}
if s.walletTransmitter.publisher == nil {
log.Error("wallet publisher was not initialized")
return
}
s.walletTransmitter.quit = make(chan struct{})
events := make(chan wallet.Event, 10)
sub := s.walletTransmitter.publisher.Subscribe(events)
s.walletTransmitter.wg.Add(1)
maxKnownBlocks := map[common.Address]*big.Int{}
go func() {
defer s.walletTransmitter.wg.Done()
for {
select {
case <-s.walletTransmitter.quit:
sub.Unsubscribe()
return
case err := <-sub.Err():
// technically event.Feed cannot send an error to subscription.Err channel.
// the only time we will get an event is when that channel is closed.
if err != nil {
log.Error("wallet signals transmitter failed with", "error", err)
}
return
case event := <-events:
if event.Type == wallet.EventNewBlock && len(maxKnownBlocks) > 0 {
newBlocks := false
for _, address := range event.Accounts {
if _, ok := maxKnownBlocks[address]; !ok {
newBlocks = true
maxKnownBlocks[address] = event.BlockNumber
} else if event.BlockNumber.Cmp(maxKnownBlocks[address]) == 1 {
maxKnownBlocks[address] = event.BlockNumber
newBlocks = true
}
}
if newBlocks && s.WatchingEnabled {
s.transmitter.publisher.Send(TransactionEvent{
Type: string(event.Type),
BlockNumber: event.BlockNumber,
Accounts: event.Accounts,
NewTransactionsPerAccount: event.NewTransactionsPerAccount,
ERC20: event.ERC20,
MaxKnownBlocks: maxKnownBlocks,
})
}
} else if event.Type == wallet.EventMaxKnownBlock {
for _, address := range event.Accounts {
if _, ok := maxKnownBlocks[address]; !ok {
maxKnownBlocks[address] = event.BlockNumber
}
}
}
}
}
}()
}
// StopWalletWatcher - stops watching for new wallet events
func (s *Service) StopWalletWatcher() {
if s.walletTransmitter.quit != nil {
close(s.walletTransmitter.quit)
s.walletTransmitter.wg.Wait()
s.walletTransmitter.quit = nil
}
}
// IsWatchingWallet - check if local-notifications are subscribed to wallet updates
func (s *Service) IsWatchingWallet() bool {
return s.walletTransmitter.quit != nil
}

View File

@ -0,0 +1,10 @@
package localnotifications
const (
CategoryTransaction PushCategory = "transaction"
CategoryMessage PushCategory = "newMessage"
CategoryCommunityRequestToJoin = "communityRequestToJoin"
TypeTransaction NotificationType = "transaction"
TypeMessage NotificationType = "message"
)

View File

@ -2,11 +2,10 @@ package signal
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
statusproto "github.com/status-im/status-go/protocol"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
) )
@ -158,6 +157,6 @@ func SendWhisperFilterAdded(filters []*Filter) {
send(EventWhisperFilterAdded, WhisperFilterAddedSignal{Filters: filters}) send(EventWhisperFilterAdded, WhisperFilterAddedSignal{Filters: filters})
} }
func SendNewMessages(response *statusproto.MessengerResponse) { func SendNewMessages(obj json.Marshaler) {
send(EventNewMessages, response) send(EventNewMessages, obj)
} }

View File

@ -16,7 +16,8 @@ const (
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA // https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
kdfIterationsNumber = 3200 kdfIterationsNumber = 3200
// WALMode for sqlite. // WALMode for sqlite.
WALMode = "wal" WALMode = "wal"
inMemoryPath = ":memory:"
) )
// DecryptDB completely removes the encryption from the db // DecryptDB completely removes the encryption from the db
@ -97,7 +98,7 @@ func openDB(path, key string) (*sql.DB, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if mode != WALMode { if mode != WALMode && path != inMemoryPath {
return nil, fmt.Errorf("unable to set journal_mode to WAL. actual mode %s", mode) return nil, fmt.Errorf("unable to set journal_mode to WAL. actual mode %s", mode)
} }