diff --git a/protocol/common/message.go b/protocol/common/message.go index 04439b850..35201368f 100644 --- a/protocol/common/message.go +++ b/protocol/common/message.go @@ -465,6 +465,9 @@ func (m *Message) GetSimplifiedText(identity string, canonicalNames map[string]s if m.ContentType == protobuf.ChatMessage_COMMUNITY { return "Community", nil } + if m.ContentType == protobuf.ChatMessage_SYSTEM_MESSAGE_CONTENT_PRIVATE_GROUP { + return "Group", nil + } if m.ParsedTextAst == nil { err := m.PrepareContent(identity) diff --git a/protocol/local_notifications.go b/protocol/local_notifications.go index 0c1dbb317..e9fae6da5 100644 --- a/protocol/local_notifications.go +++ b/protocol/local_notifications.go @@ -74,6 +74,15 @@ func NewCommunityRequestToJoinNotification(id string, community *communities.Com return body.toCommunityRequestToJoinNotification(id) } +func NewPrivateGroupInviteNotification(id string, chat *Chat, contact *Contact) *localnotifications.Notification { + body := &NotificationBody{ + Chat: chat, + Contact: contact, + } + + return body.toPrivateGroupInviteNotification(id) +} + func (n NotificationBody) toMessageNotification(id string, contacts *contactMap) (*localnotifications.Notification, error) { var title string if n.Chat.PrivateGroupChat() || n.Chat.Public() || n.Chat.CommunityChat() { @@ -123,6 +132,24 @@ func (n NotificationBody) toMessageNotification(id string, contacts *contactMap) }, nil } +func (n NotificationBody) toPrivateGroupInviteNotification(id string) *localnotifications.Notification { + return &localnotifications.Notification{ + ID: gethcommon.HexToHash(id), + Body: n, + Title: n.Contact.CanonicalName() + " invited you to " + n.Chat.Name, + Message: n.Contact.CanonicalName() + " wants you to join group " + n.Chat.Name, + BodyType: localnotifications.TypeMessage, + Category: localnotifications.CategoryGroupInvite, + Deeplink: n.Chat.DeepLink(), + Author: localnotifications.NotificationAuthor{ + Name: n.Contact.CanonicalName(), + Icon: n.Contact.CanonicalImage(), + ID: n.Contact.ID, + }, + Image: "", + } +} + func (n NotificationBody) toCommunityRequestToJoinNotification(id string) *localnotifications.Notification { return &localnotifications.Notification{ ID: gethcommon.HexToHash(id), diff --git a/protocol/messenger_handler.go b/protocol/messenger_handler.go index ab4ee6a77..6a5f10ac8 100644 --- a/protocol/messenger_handler.go +++ b/protocol/messenger_handler.go @@ -60,6 +60,7 @@ func (m *Messenger) HandleMembershipUpdate(messageState *ReceivedMessageState, c //if chat.InvitationAdmin exists means we are waiting for invitation request approvement, and in that case //we need to create a new chat instance like we don't have a chat and just use a regular invitation flow waitingForApproval := chat != nil && len(chat.InvitationAdmin) > 0 + ourKey := contactIDFromPublicKey(&m.identity.PublicKey) if chat == nil || waitingForApproval { if len(message.Events) == 0 { @@ -96,7 +97,6 @@ func (m *Messenger) HandleMembershipUpdate(messageState *ReceivedMessageState, c return err } - ourKey := contactIDFromPublicKey(&m.identity.PublicKey) // A new chat must contain us if !group.IsMember(ourKey) { return errors.New("can't create a new group chat without us being a member") @@ -107,6 +107,16 @@ func (m *Messenger) HandleMembershipUpdate(messageState *ReceivedMessageState, c isActive := messageState.CurrentMessageState.Contact.IsAdded() || messageState.CurrentMessageState.Contact.ID == ourKey || waitingForApproval newChat.Active = isActive chat = &newChat + + chat.updateChatFromGroupMembershipChanges(group) + + if err != nil { + return errors.Wrap(err, "failed to get group creator") + } + + if chat.Active && messageState.CurrentMessageState.Contact.ID != ourKey { + messageState.Response.AddNotification(NewPrivateGroupInviteNotification(chat.ID, chat, messageState.CurrentMessageState.Contact)) + } } else { existingGroup, err := newProtocolGroupFromChat(chat) if err != nil { @@ -121,10 +131,9 @@ func (m *Messenger) HandleMembershipUpdate(messageState *ReceivedMessageState, c if err != nil { return errors.Wrap(err, "failed to create a group with new membership updates") } + chat.updateChatFromGroupMembershipChanges(group) } - chat.updateChatFromGroupMembershipChanges(group) - if !chat.Active { m.createMessageNotification(chat, messageState) } @@ -181,6 +190,7 @@ func (m *Messenger) createMessageNotification(chat *Chat, messageState *Received Timestamp: messageState.CurrentMessageState.WhisperTimestamp, ChatID: chat.ID, } + err := m.addActivityCenterNotification(messageState, notification) if err != nil { m.logger.Warn("failed to create activity center notification", zap.Error(err)) diff --git a/protocol/messenger_handler_test.go b/protocol/messenger_handler_test.go index 10ec3d7a1..7fcbbf119 100644 --- a/protocol/messenger_handler_test.go +++ b/protocol/messenger_handler_test.go @@ -1,11 +1,20 @@ package protocol import ( + "crypto/ecdsa" "testing" "github.com/stretchr/testify/suite" + "go.uber.org/zap" + gethbridge "github.com/status-im/status-go/eth-node/bridge/geth" + "github.com/status-im/status-go/eth-node/crypto" + "github.com/status-im/status-go/eth-node/types" + "github.com/status-im/status-go/protocol/protobuf" + "github.com/status-im/status-go/protocol/tt" v1protocol "github.com/status-im/status-go/protocol/v1" + localnotifications "github.com/status-im/status-go/services/local-notifications" + "github.com/status-im/status-go/waku" ) func TestEventToSystemMessageSuite(t *testing.T) { @@ -14,6 +23,37 @@ func TestEventToSystemMessageSuite(t *testing.T) { type EventToSystemMessageSuite struct { suite.Suite + m *Messenger // main instance of Messenger + privateKey *ecdsa.PrivateKey // private key for the main instance of Messenger + // If one wants to send messages between different instances of Messenger, + // a single waku service should be shared. + shh types.Waku + logger *zap.Logger +} + +func (s *EventToSystemMessageSuite) newMessenger() *Messenger { + privateKey, err := crypto.GenerateKey() + s.Require().NoError(err) + + messenger, err := newMessengerWithKey(s.shh, privateKey, s.logger, nil) + s.Require().NoError(err) + return messenger +} + +func (s *EventToSystemMessageSuite) SetupTest() { + s.logger = tt.MustCreateTestLogger() + + config := waku.DefaultConfig + config.MinimumAcceptedPoW = 0 + shh := waku.New(&config, s.logger) + s.shh = gethbridge.NewGethWakuWrapper(shh) + s.Require().NoError(shh.Start()) + + s.m = s.newMessenger() + s.privateKey = s.m.identity + + _, err := s.m.Start() + s.Require().NoError(err) } func (s *EventToSystemMessageSuite) TestRun() { @@ -78,3 +118,62 @@ func (s *EventToSystemMessageSuite) TestRun() { } } + +func (s *EventToSystemMessageSuite) TestHandleMembershipUpdate() { + adminPrivateKey, err := crypto.GenerateKey() + s.Require().NoError(err) + + adminPublicKey := types.EncodeHex(crypto.FromECDSAPub(&adminPrivateKey.PublicKey)) + ourPublicKey := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey)) + + event1 := v1protocol.MembershipUpdateEvent{ + Type: protobuf.MembershipUpdateEvent_CHAT_CREATED, + Name: "test", + ChatID: "test-" + adminPublicKey, + ClockValue: 100, + } + err = event1.Sign(adminPrivateKey) + s.Require().NoError(err) + + event2 := v1protocol.MembershipUpdateEvent{ + Type: protobuf.MembershipUpdateEvent_MEMBERS_ADDED, + Members: []string{adminPublicKey, ourPublicKey}, + ChatID: "test-" + adminPublicKey, + ClockValue: 100, + } + err = event2.Sign(adminPrivateKey) + s.Require().NoError(err) + + testMembershipUpdateMessageStruct2 := v1protocol.MembershipUpdateMessage{ + ChatID: "test-" + adminPublicKey, + Events: []v1protocol.MembershipUpdateEvent{ + event1, + event2, + }, + } + + rawMembershipUpdateMessage2, err := testMembershipUpdateMessageStruct2.ToProtobuf() + s.Require().NoError(err) + + contact, err := BuildContactFromPublicKey(&adminPrivateKey.PublicKey) + s.Require().NoError(err) + + contact.SystemTags = []string{contactAdded} + + currentMessageState := &CurrentMessageState{ + Contact: contact, + } + + state := &ReceivedMessageState{ + Response: &MessengerResponse{}, + Timesource: s.m.transport, + CurrentMessageState: currentMessageState, + ExistingMessagesMap: map[string]bool{}, + AllChats: s.m.allChats, + } + + err = s.m.HandleMembershipUpdate(state, nil, *rawMembershipUpdateMessage2, defaultSystemMessagesTranslations) + s.Require().NoError(err) + s.Require().Len(state.Response.Notifications(), 1) + s.Require().Equal(state.Response.Notifications()[0].Category, localnotifications.CategoryGroupInvite) +} diff --git a/services/local-notifications/types.go b/services/local-notifications/types.go index f6a126239..587685bf1 100644 --- a/services/local-notifications/types.go +++ b/services/local-notifications/types.go @@ -3,6 +3,7 @@ package localnotifications const ( CategoryTransaction PushCategory = "transaction" CategoryMessage PushCategory = "newMessage" + CategoryGroupInvite PushCategory = "groupInvite" CategoryCommunityRequestToJoin = "communityRequestToJoin" TypeTransaction NotificationType = "transaction"