diff --git a/protocol/message_persistence.go b/protocol/message_persistence.go index 6e819e20a..57735a9f5 100644 --- a/protocol/message_persistence.go +++ b/protocol/message_persistence.go @@ -670,6 +670,25 @@ func (db sqlitePersistence) PendingContactRequests(currCursor string, limit int) return result, newCursor, nil } +func (db sqlitePersistence) LatestPendingContactRequestIDForContact(contactID string) (string, error) { + var id string + err := db.db.QueryRow( + ` + SELECT + id + FROM + user_messages m1 + WHERE + m1.local_chat_id = ? AND m1.content_type = ? + ORDER BY substr('0000000000000000000000000000000000000000000000000000000000000000' || m1.clock_value, -64, 64) || m1.id DESC + LIMIT 1 + `, contactID, protobuf.ChatMessage_CONTACT_REQUEST).Scan(&id) + if err != nil { + return "", err + } + return id, nil +} + // AllMessageByChatIDWhichMatchPattern returns all messages which match the search // term, for a given chatID in descending order. // Ordering is accomplished using two concatenated values: ClockValue and ID. diff --git a/protocol/messenger_contact_requests_test.go b/protocol/messenger_contact_requests_test.go index f481b4fa2..9aeb030d4 100644 --- a/protocol/messenger_contact_requests_test.go +++ b/protocol/messenger_contact_requests_test.go @@ -604,6 +604,204 @@ func (s *MessengerContactRequestSuite) TestReceiveAndAcceptContactRequestTwice() s.Require().Len(mutualContacts, 1) } +func (s *MessengerContactRequestSuite) TestAcceptLatestContactRequestForContact() { + + messageText := "hello!" + + theirMessenger := s.newMessenger(s.shh) + _, err := theirMessenger.Start() + s.Require().NoError(err) + + contactID := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) + myID := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey)) + + request := &requests.SendContactRequest{ + ID: types.Hex2Bytes(contactID), + Message: messageText, + } + + // Send contact request + resp, err := s.m.SendContactRequest(context.Background(), request) + s.Require().NoError(err) + + s.Require().NotNil(resp) + s.Require().Len(resp.Messages(), 1) + s.Require().Equal(common.ContactRequestStatePending, resp.Messages()[0].ContactRequestState) + + // Make sure it's not returned as coming from us + contactRequests, _, err := s.m.PendingContactRequests("", 10) + s.Require().NoError(err) + s.Require().Len(contactRequests, 0) + + // Make sure contact is added on the sender side + contacts := s.m.AddedContacts() + s.Require().Len(contacts, 1) + s.Require().Equal(ContactRequestStateSent, contacts[0].ContactRequestState) + + // Wait for the message to reach its destination + resp, err = WaitOnMessengerResponse( + theirMessenger, + func(r *MessengerResponse) bool { + return len(r.Contacts) > 0 && len(r.Messages()) > 0 && len(r.ActivityCenterNotifications()) > 0 + }, + "no messages", + ) + + // Check contact request has been received + s.Require().NoError(err) + + // Check activity center notification is of the right type + s.Require().Len(resp.ActivityCenterNotifications(), 1) + s.Require().Equal(ActivityCenterNotificationTypeContactRequest, resp.ActivityCenterNotifications()[0].Type) + s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message) + s.Require().Equal(common.ContactRequestStatePending, resp.ActivityCenterNotifications()[0].Message.ContactRequestState) + + // Check the contact state is correctly set + s.Require().Len(resp.Contacts, 1) + s.Require().Equal(ContactRequestStateReceived, resp.Contacts[0].ContactRequestState) + + // Make sure it's the pending contact requests + contactRequests, _, err = theirMessenger.PendingContactRequests("", 10) + s.Require().NoError(err) + s.Require().Len(contactRequests, 1) + s.Require().Equal(contactRequests[0].ContactRequestState, common.ContactRequestStatePending) + + // Accept latest contact request, receiver side + resp, err = theirMessenger.AcceptLatestContactRequestForContact(context.Background(), &requests.AcceptLatestContactRequestForContact{ID: types.Hex2Bytes(myID)}) + s.Require().NoError(err) + + // Make sure the message is updated + s.Require().NotNil(resp) + s.Require().Len(resp.Messages(), 1) + s.Require().Equal(resp.Messages()[0].ID, contactRequests[0].ID) + s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState) + + s.Require().Len(resp.ActivityCenterNotifications(), 1) + s.Require().Equal(resp.ActivityCenterNotifications()[0].ID.String(), contactRequests[0].ID) + s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message) + s.Require().Equal(common.ContactRequestStateAccepted, resp.ActivityCenterNotifications()[0].Message.ContactRequestState) + + // Check the contact state is correctly set + s.Require().Len(resp.Contacts, 1) + s.Require().Equal(ContactRequestStateMutual, resp.Contacts[0].ContactRequestState) + + // Make sure the sender is added to our contacts + contacts = theirMessenger.AddedContacts() + s.Require().Len(contacts, 1) + + // Make sure we consider them a mutual contact, receiver side + mutualContacts := theirMessenger.MutualContacts() + s.Require().Len(mutualContacts, 1) + + // Wait for the message to reach its destination + resp, err = WaitOnMessengerResponse( + s.m, + func(r *MessengerResponse) bool { + return len(r.Contacts) > 0 && len(r.Messages()) > 0 && len(r.ActivityCenterNotifications()) > 0 + }, + "no messages", + ) + s.Require().NoError(err) + + // Check activity center notification is of the right type + s.Require().Equal(ActivityCenterNotificationTypeContactRequest, resp.ActivityCenterNotifications()[0].Type) + s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message) + s.Require().Equal(common.ContactRequestStateAccepted, resp.ActivityCenterNotifications()[0].Message.ContactRequestState) + + // Make sure the message is updated, sender s2de + s.Require().NotNil(resp) + s.Require().Len(resp.Messages(), 1) + s.Require().Equal(resp.Messages()[0].ID, contactRequests[0].ID) + s.Require().Equal(common.ContactRequestStateAccepted, resp.Messages()[0].ContactRequestState) + + // Make sure we consider them a mutual contact, sender side + mutualContacts = s.m.MutualContacts() + s.Require().Len(mutualContacts, 1) + + // Check the contact state is correctly set + s.Require().Len(resp.Contacts, 1) + s.Require().Equal(ContactRequestStateMutual, resp.Contacts[0].ContactRequestState) +} + +func (s *MessengerContactRequestSuite) TestDismissLatestContactRequestForContact() { + + messageText := "hello!" + + theirMessenger := s.newMessenger(s.shh) + _, err := theirMessenger.Start() + s.Require().NoError(err) + + contactID := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey)) + myID := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey)) + + request := &requests.SendContactRequest{ + ID: types.Hex2Bytes(contactID), + Message: messageText, + } + + // Send contact request + resp, err := s.m.SendContactRequest(context.Background(), request) + s.Require().NoError(err) + + s.Require().NotNil(resp) + s.Require().Len(resp.Messages(), 1) + s.Require().Equal(common.ContactRequestStatePending, resp.Messages()[0].ContactRequestState) + + // Make sure it's not returned as coming from us + contactRequests, _, err := s.m.PendingContactRequests("", 10) + s.Require().NoError(err) + s.Require().Len(contactRequests, 0) + + // Make sure contact is added on the sender side + contacts := s.m.AddedContacts() + s.Require().Len(contacts, 1) + s.Require().Equal(ContactRequestStateSent, contacts[0].ContactRequestState) + + // Wait for the message to reach its destination + resp, err = WaitOnMessengerResponse( + theirMessenger, + func(r *MessengerResponse) bool { + return len(r.Contacts) > 0 && len(r.Messages()) > 0 && len(r.ActivityCenterNotifications()) > 0 + }, + "no messages", + ) + + // Check contact request has been received + s.Require().NoError(err) + + // Check activity center notification is of the right type + s.Require().Len(resp.ActivityCenterNotifications(), 1) + s.Require().Equal(ActivityCenterNotificationTypeContactRequest, resp.ActivityCenterNotifications()[0].Type) + s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message) + s.Require().Equal(common.ContactRequestStatePending, resp.ActivityCenterNotifications()[0].Message.ContactRequestState) + + // Check the contact state is correctly set + s.Require().Len(resp.Contacts, 1) + s.Require().Equal(ContactRequestStateReceived, resp.Contacts[0].ContactRequestState) + + // Make sure it's the pending contact requests + contactRequests, _, err = theirMessenger.PendingContactRequests("", 10) + s.Require().NoError(err) + s.Require().Len(contactRequests, 1) + s.Require().Equal(contactRequests[0].ContactRequestState, common.ContactRequestStatePending) + + // Dismiss latest contact request, receiver side + resp, err = theirMessenger.DismissLatestContactRequestForContact(context.Background(), &requests.DismissLatestContactRequestForContact{ID: types.Hex2Bytes(myID)}) + s.Require().NoError(err) + + // Make sure the message is updated + s.Require().NotNil(resp) + s.Require().Len(resp.Messages(), 1) + s.Require().Equal(resp.Messages()[0].ID, contactRequests[0].ID) + s.Require().Equal(common.ContactRequestStateDismissed, resp.Messages()[0].ContactRequestState) + + s.Require().Len(resp.ActivityCenterNotifications(), 1) + s.Require().Equal(resp.ActivityCenterNotifications()[0].ID.String(), contactRequests[0].ID) + s.Require().NotNil(resp.ActivityCenterNotifications()[0].Message) + s.Require().Equal(common.ContactRequestStateDismissed, resp.ActivityCenterNotifications()[0].Message.ContactRequestState) + +} + /* Disabling as currently there's an issue with duplicated contact requests func (s *MessengerContactRequestSuite) TestReceiveAndAcceptLegacyContactRequest() { diff --git a/protocol/messenger_contacts.go b/protocol/messenger_contacts.go index d38e65882..fb45e7fe1 100644 --- a/protocol/messenger_contacts.go +++ b/protocol/messenger_contacts.go @@ -819,6 +819,32 @@ func (m *Messenger) RetractContactRequest(request *requests.RetractContactReques return response, err } +func (m *Messenger) AcceptLatestContactRequestForContact(ctx context.Context, request *requests.AcceptLatestContactRequestForContact) (*MessengerResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + contactRequestID, err := m.persistence.LatestPendingContactRequestIDForContact(request.ID.String()) + if err != nil { + return nil, err + } + + return m.AcceptContactRequest(ctx, &requests.AcceptContactRequest{ID: types.Hex2Bytes(contactRequestID)}) +} + +func (m *Messenger) DismissLatestContactRequestForContact(ctx context.Context, request *requests.DismissLatestContactRequestForContact) (*MessengerResponse, error) { + if err := request.Validate(); err != nil { + return nil, err + } + + contactRequestID, err := m.persistence.LatestPendingContactRequestIDForContact(request.ID.String()) + if err != nil { + return nil, err + } + + return m.DismissContactRequest(ctx, &requests.DismissContactRequest{ID: types.Hex2Bytes(contactRequestID)}) +} + func (m *Messenger) PendingContactRequests(cursor string, limit int) ([]*common.Message, string, error) { return m.persistence.PendingContactRequests(cursor, limit) } diff --git a/protocol/requests/accept_latest_contact_request_for_contact.go b/protocol/requests/accept_latest_contact_request_for_contact.go new file mode 100644 index 000000000..872da4904 --- /dev/null +++ b/protocol/requests/accept_latest_contact_request_for_contact.go @@ -0,0 +1,21 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrAcceptLatestContactRequestForContactInvalidID = errors.New("accept-latest-contact-request-for-contact: invalid id") + +type AcceptLatestContactRequestForContact struct { + ID types.HexBytes `json:"id"` +} + +func (a *AcceptLatestContactRequestForContact) Validate() error { + if len(a.ID) == 0 { + return ErrAcceptLatestContactRequestForContactInvalidID + } + + return nil +} diff --git a/protocol/requests/dismiss_latest_contact_request_for_contact.go b/protocol/requests/dismiss_latest_contact_request_for_contact.go new file mode 100644 index 000000000..c370bb7f9 --- /dev/null +++ b/protocol/requests/dismiss_latest_contact_request_for_contact.go @@ -0,0 +1,21 @@ +package requests + +import ( + "errors" + + "github.com/status-im/status-go/eth-node/types" +) + +var ErrDismissLatestContactRequestForContactInvalidID = errors.New("dismiss-latest-contact-request-for-contact: invalid id") + +type DismissLatestContactRequestForContact struct { + ID types.HexBytes `json:"id"` +} + +func (a *DismissLatestContactRequestForContact) Validate() error { + if len(a.ID) == 0 { + return ErrDismissLatestContactRequestForContactInvalidID + } + + return nil +} diff --git a/services/ext/api.go b/services/ext/api.go index 8bd5a4ce9..fd7b0a2e4 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -620,6 +620,14 @@ func (api *PublicAPI) AcceptContactRequest(ctx context.Context, request *request return api.service.messenger.AcceptContactRequest(ctx, request) } +func (api *PublicAPI) AcceptLatestContactRequestForContact(ctx context.Context, request *requests.AcceptLatestContactRequestForContact) (*protocol.MessengerResponse, error) { + return api.service.messenger.AcceptLatestContactRequestForContact(ctx, request) +} + +func (api *PublicAPI) DismissLatestContactRequestForContact(ctx context.Context, request *requests.DismissLatestContactRequestForContact) (*protocol.MessengerResponse, error) { + return api.service.messenger.DismissLatestContactRequestForContact(ctx, request) +} + func (api *PublicAPI) RetractContactRequest(ctx context.Context, request *requests.RetractContactRequest) (*protocol.MessengerResponse, error) { return api.service.messenger.RetractContactRequest(request) }