Add default contact request

If a contact update or a legacy contact request is sent, we create a
notification for the user in the activity center so it can be replied
to.
If at a later date a new contact request is received from the same user,
this will replace it, so the proper message can be displayed.
This commit is contained in:
Andrea Maria Piana 2022-06-13 11:57:51 +01:00
parent 4f722b6fe8
commit 22669d0423
7 changed files with 538 additions and 31 deletions

View File

@ -1 +1 @@
0.102.0
0.102.2

View File

@ -416,26 +416,30 @@ func (db sqlitePersistence) GetActivityCenterNotificationsByID(ids []types.HexBy
func (db sqlitePersistence) GetActivityCenterNotificationByID(id types.HexBytes) (*ActivityCenterNotification, error) {
row := db.db.QueryRow(`
SELECT
a.id,
a.timestamp,
a.notification_type,
a.chat_id,
a.read,
a.accepted,
a.dismissed,
a.message,
c.last_message,
a.reply_message,
c.name,
a.author
FROM activity_center_notifications a
LEFT JOIN chats c
ON
c.id = a.chat_id
WHERE a.id = ?`, id)
SELECT
a.id,
a.timestamp,
a.notification_type,
a.chat_id,
a.read,
a.accepted,
a.dismissed,
a.message,
c.last_message,
a.reply_message,
c.name,
a.author
FROM activity_center_notifications a
LEFT JOIN chats c
ON
c.id = a.chat_id
WHERE a.id = ?`, id)
return db.unmarshalActivityCenterNotificationRow(row)
notification, err := db.unmarshalActivityCenterNotificationRow(row)
if err == sql.ErrNoRows {
return nil, nil
}
return notification, err
}
func (db sqlitePersistence) ActivityCenterNotifications(currCursor string, limit uint64) (string, []*ActivityCenterNotification, error) {
@ -673,3 +677,40 @@ func (db sqlitePersistence) UnreadActivityCenterNotificationsCount() (uint64, er
err := db.db.QueryRow(`SELECT COUNT(1) FROM activity_center_notifications WHERE NOT read AND NOT dismissed AND NOT accepted`).Scan(&count)
return count, err
}
func (db sqlitePersistence) ActiveContactRequestNotification(contactID string) (*ActivityCenterNotification, error) {
row := db.db.QueryRow(`
SELECT
a.id,
a.timestamp,
a.notification_type,
a.chat_id,
a.read,
a.accepted,
a.dismissed,
a.message,
c.last_message,
a.reply_message,
c.name,
a.author
FROM activity_center_notifications a
LEFT JOIN chats c
ON
c.id = a.chat_id
WHERE NOT dismissed AND NOT a.accepted AND notification_type = ? AND author = ?`, ActivityCenterNotificationTypeContactRequest, contactID)
notification, err := db.unmarshalActivityCenterNotificationRow(row)
if err == sql.ErrNoRows {
return nil, nil
}
return notification, err
}
func (db sqlitePersistence) RemoveAllContactRequestActivityCenterNotifications(chatID string) error {
_, err := db.db.Exec(`
DELETE FROM activity_center_notifications
WHERE
chat_id = ?
AND notification_type = ?
`, chatID, ActivityCenterNotificationTypeContactRequest)
return err
}

View File

@ -330,8 +330,9 @@ func (s *MessengerContactRequestSuite) TestReceiveAcceptAndRetractContactRequest
s.Require().Len(contactRequests, 1)
s.Require().Equal(contactRequests[0].ContactRequestState, common.ContactRequestStatePending)
cid := resp.ActivityCenterNotifications()[0].Message.ID
// Accept contact request, receiver side
resp, err = theirMessenger.AcceptContactRequest(context.Background(), &requests.AcceptContactRequest{ID: types.Hex2Bytes(contactRequests[0].ID)})
resp, err = theirMessenger.AcceptContactRequest(context.Background(), &requests.AcceptContactRequest{ID: types.Hex2Bytes(cid)})
s.Require().NoError(err)
// Make sure the message is updated
@ -802,7 +803,6 @@ func (s *MessengerContactRequestSuite) TestDismissLatestContactRequestForContact
}
/* Disabling as currently there's an issue with duplicated contact requests
func (s *MessengerContactRequestSuite) TestReceiveAndAcceptLegacyContactRequest() {
theirMessenger := s.newMessenger(s.shh)
@ -873,4 +873,202 @@ func (s *MessengerContactRequestSuite) TestReceiveAndAcceptLegacyContactRequest(
// Make sure we consider them a mutual contact, receiver side
mutualContacts := theirMessenger.MutualContacts()
s.Require().Len(mutualContacts, 1)
} */
}
func (s *MessengerContactRequestSuite) TestLegacyContactRequestNotifications() {
theirMessenger := s.newMessenger(s.shh)
_, err := theirMessenger.Start()
s.Require().NoError(err)
contactID := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))
request := &requests.AddContact{
ID: types.Hex2Bytes(contactID),
}
// Send legacy contact request
resp, err := s.m.AddContact(context.Background(), request)
s.Require().NoError(err)
s.Require().NotNil(resp)
// 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.ActivityCenterNotifications()) == 1
},
"no messages",
)
s.Require().NoError(err)
notification := resp.ActivityCenterNotifications()[0]
// Check contact request has been received
s.Require().NoError(err)
// Check activity center notification is of the right type
s.Require().Equal(ActivityCenterNotificationTypeContactRequest, notification.Type)
s.Require().NotNil(notification.Type)
s.Require().Equal(common.ContactRequestStatePending, notification.Message.ContactRequestState)
// Check the contact state is correctly set
s.Require().Len(resp.Contacts, 1)
s.Require().Equal(ContactRequestStateReceived, resp.Contacts[0].ContactRequestState)
// Send new contact request
resp, err = s.m.AddContact(context.Background(), request)
s.Require().NoError(err)
s.Require().NotNil(resp)
crRequest := &requests.SendContactRequest{
ID: types.Hex2Bytes(contactID),
Message: "hello",
}
myID := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey))
// Send contact request
resp, err = s.m.SendContactRequest(context.Background(), crRequest)
s.Require().NoError(err)
paginationResponse, err := theirMessenger.ActivityCenterNotifications("", 10)
s.Require().NoError(err)
s.Require().Len(paginationResponse.Notifications, 1)
// Wait for the message to reach its destination
resp, err = WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool {
return len(r.ActivityCenterNotifications()) == 2
},
"no messages",
)
s.Require().NoError(err)
activityCenterNotifications := resp.ActivityCenterNotifications()
var newNotification, oldNotification *ActivityCenterNotification
if activityCenterNotifications[0].Message.ID == defaultContactRequestID(myID) {
oldNotification = activityCenterNotifications[0]
newNotification = activityCenterNotifications[1]
} else {
newNotification = activityCenterNotifications[0]
oldNotification = activityCenterNotifications[1]
}
s.Require().True(oldNotification.Dismissed)
s.Require().False(newNotification.Dismissed)
paginationResponse, err = theirMessenger.ActivityCenterNotifications("", 10)
s.Require().NoError(err)
s.Require().Len(paginationResponse.Notifications, 1)
}
func (s *MessengerContactRequestSuite) TestReceiveMultipleLegacy() {
theirMessenger := s.newMessenger(s.shh)
_, err := theirMessenger.Start()
s.Require().NoError(err)
s.Require().NoError(theirMessenger.settings.SaveSettingField(settings.MutualContactEnabled, true))
s.Require().NoError(s.m.settings.SaveSettingField(settings.MutualContactEnabled, true))
contactID := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))
request := &requests.AddContact{
ID: types.Hex2Bytes(contactID),
}
// Send legacy contact request
resp, err := s.m.AddContact(context.Background(), request)
s.Require().NoError(err)
s.Require().NotNil(resp)
// 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.ActivityCenterNotifications()) == 1
},
"no messages",
)
s.Require().NoError(err)
notification := resp.ActivityCenterNotifications()[0]
// Check contact request has been received
s.Require().NoError(err)
// Check activity center notification is of the right type
s.Require().Equal(ActivityCenterNotificationTypeContactRequest, notification.Type)
s.Require().NotNil(notification.Type)
s.Require().Equal(common.ContactRequestStatePending, notification.Message.ContactRequestState)
// Check the contact state is correctly set
s.Require().Len(resp.Contacts, 1)
s.Require().Equal(ContactRequestStateReceived, resp.Contacts[0].ContactRequestState)
// Remove contact
_, err = s.m.RetractContactRequest(&requests.RetractContactRequest{ContactID: types.Hex2Bytes(contactID)})
s.Require().NoError(err)
// Wait for the message to reach its destination
resp, err = WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool {
return len(r.Contacts) == 1
},
"no messages",
)
s.Require().NoError(err)
// Make sure it's not a contact anymore
s.Require().Equal(ContactRequestStateNone, resp.Contacts[0].ContactRequestState)
// Re-add user
resp, err = s.m.AddContact(context.Background(), request)
s.Require().NoError(err)
s.Require().NotNil(resp)
// Wait for the message to reach its destination
resp, err = WaitOnMessengerResponse(
theirMessenger,
func(r *MessengerResponse) bool {
return len(r.Contacts) > 0 && len(r.ActivityCenterNotifications()) == 1
},
"no messages",
)
s.Require().NoError(err)
notification = resp.ActivityCenterNotifications()[0]
// Check contact request has been received
s.Require().NoError(err)
// Check activity center notification is of the right type
s.Require().Equal(ActivityCenterNotificationTypeContactRequest, notification.Type)
s.Require().NotNil(notification.Type)
s.Require().Equal(common.ContactRequestStatePending, notification.Message.ContactRequestState)
// Check the contact state is correctly set
s.Require().Len(resp.Contacts, 1)
s.Require().Equal(ContactRequestStateReceived, resp.Contacts[0].ContactRequestState)
}

View File

@ -6,6 +6,7 @@ import (
"errors"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/common"
@ -22,6 +23,7 @@ func (m *Messenger) AcceptContactRequest(ctx context.Context, request *requests.
contactRequest, err := m.persistence.MessageByID(request.ID.String())
if err != nil {
m.logger.Error("could not find contact request message", zap.Error(err))
return nil, err
}
@ -848,3 +850,7 @@ func (m *Messenger) DismissLatestContactRequestForContact(ctx context.Context, r
func (m *Messenger) PendingContactRequests(cursor string, limit int) ([]*common.Message, string, error) {
return m.persistence.PendingContactRequests(cursor, limit)
}
func defaultContactRequestID(contactID string) string {
return "0x" + types.Bytes2Hex(append(types.Hex2Bytes(contactID), 0x20))
}

View File

@ -231,14 +231,68 @@ func (m *Messenger) createMessageNotification(chat *Chat, messageState *Received
}
}
func (m *Messenger) PendingNotificationContactRequest(contactID string) (*ActivityCenterNotification, error) {
return m.persistence.ActiveContactRequestNotification(contactID)
}
func (m *Messenger) createContactRequestNotification(contact *Contact, messageState *ReceivedMessageState, contactRequest *common.Message) error {
// Legacy contact request
if contactRequest == nil {
if contactRequest == nil || contactRequest.ContactRequestState == common.ContactRequestStatePending {
notification, err := m.PendingNotificationContactRequest(contact.ID)
if err != nil {
return err
}
// If there's already a notification, we will check whether is a default notification
// that has not been dismissed (nor accepted???)
// If it is, we replace it with a non-default, since it contains a message
if notification != nil {
// Check if it's the default notification
if notification.Message.ID == defaultContactRequestID(contact.ID) {
// Nothing to do, we already have a default notification
if contactRequest == nil {
return nil
}
// We first dismiss it in the database
err := m.persistence.DismissActivityCenterNotifications([]types.HexBytes{types.Hex2Bytes(notification.Message.ID)})
if err != nil {
return err
}
// we mark the notification as dismissed
notification.Dismissed = true
// We remove it from the response, since the client has never seen it, better to just remove it
found := messageState.Response.RemoveActivityCenterNotification(notification.Message.ID)
// Otherwise, it means we have already passed it to the client, so we add it with a `dismissed` flag
// so it can clean up
if !found {
messageState.Response.AddActivityCenterNotification(notification)
}
}
}
}
// Legacy//ContactUpdate contact request
if contactRequest == nil {
if messageState.CurrentMessageState == nil || messageState.CurrentMessageState.MessageID == "" {
return errors.New("no available id")
}
// We use a known id so that we can check if already in the database
defaultID := defaultContactRequestID(contact.ID)
// Pull one from the db if there
notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(defaultID))
if err != nil {
return err
}
// if the notification is accepted, we clear it, as this one will replace it
if notification != nil && notification.Accepted {
err = m.persistence.DeleteActivityCenterNotification(types.FromHex(defaultID))
if err != nil {
return err
}
}
contactRequest = &common.Message{}
contactRequest.WhisperTimestamp = messageState.CurrentMessageState.WhisperTimestamp
@ -247,9 +301,9 @@ func (m *Messenger) createContactRequestNotification(contact *Contact, messageSt
contactRequest.From = contact.ID
contactRequest.ContentType = protobuf.ChatMessage_CONTACT_REQUEST
contactRequest.Clock = messageState.CurrentMessageState.Message.Clock
contactRequest.ID = messageState.CurrentMessageState.MessageID
contactRequest.ID = defaultID
contactRequest.ContactRequestState = common.ContactRequestStatePending
err := contactRequest.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey))
err = contactRequest.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey))
if err != nil {
return err
}
@ -260,7 +314,6 @@ func (m *Messenger) createContactRequestNotification(contact *Contact, messageSt
if err != nil {
return err
}
}
notification := &ActivityCenterNotification{
@ -669,7 +722,6 @@ func (m *Messenger) HandleAcceptContactRequest(state *ReceivedMessageState, mess
state.Response.AddMessage(request)
return nil
}
func (m *Messenger) HandleRetractContactRequest(state *ReceivedMessageState, message protobuf.RetractContactRequest) error {
@ -687,7 +739,14 @@ func (m *Messenger) HandleRetractContactRequest(state *ReceivedMessageState, mes
// We remove from our old contacts only if mutual contacts are enabled
if mutualContactEnabled {
contact.Added = false
}
// We remove anything that's related to this contact request
err = m.persistence.RemoveAllContactRequestActivityCenterNotifications(contact.ID)
if err != nil {
return err
}
contact.HasAddedUs = false
contact.ContactRequestClock = message.Clock
contact.ContactRequestRetracted()
@ -738,7 +797,7 @@ func (m *Messenger) HandleContactUpdate(state *ReceivedMessageState, message pro
contact.LastUpdated = message.Clock
state.ModifiedContacts.Store(contact.ID, true)
state.AllContacts.Store(contact.ID, contact)
/* Disabling for now in order to avoid duplicated contact requests in activity center
// Has the user added us?
if contact.ContactRequestState == ContactRequestStateNone {
contact.ContactRequestState = ContactRequestStateReceived
err = m.createContactRequestNotification(contact, state, nil)
@ -746,7 +805,11 @@ func (m *Messenger) HandleContactUpdate(state *ReceivedMessageState, message pro
m.logger.Warn("could not create contact request notification", zap.Error(err))
}
}*/
// Has the user replied to a default contact request
} else if contact.ContactRequestState == ContactRequestStateSent {
contact.ContactRequestState = ContactRequestStateMutual
}
}
if chat.LastClockValue < message.Clock {

View File

@ -360,6 +360,19 @@ func (r *MessengerResponse) AddActivityCenterNotification(n *ActivityCenterNotif
r.activityCenterNotifications[n.ID.String()] = n
}
func (r *MessengerResponse) RemoveActivityCenterNotification(id string) bool {
if r.activityCenterNotifications == nil {
return false
}
if _, ok := r.activityCenterNotifications[id]; ok {
delete(r.activityCenterNotifications, id)
return true
}
return false
}
func (r *MessengerResponse) ActivityCenterNotifications() []*ActivityCenterNotification {
var ns []*ActivityCenterNotification
for _, n := range r.activityCenterNotifications {

View File

@ -977,3 +977,189 @@ func (s *MessengerPushNotificationSuite) TestReceivePushNotificationCommunityReq
s.Require().NoError(alice.Shutdown())
s.Require().NoError(server.Shutdown())
}
func (s *MessengerPushNotificationSuite) TestReceivePushNotificationPairedDevices() {
bob1 := s.m
bob2, err := newMessengerWithKey(s.shh, s.m.identity, s.logger, []Option{WithPushNotifications()})
s.Require().NoError(err)
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())
bobInstallationIDs := []string{bob1.installationID, bob2.installationID}
// Register bob1
err = bob1.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
s.Require().NoError(err)
err = bob1.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 = bob1.RetrieveAll()
if err != nil {
return err
}
registered, err := bob1.RegisteredForPushNotifications()
if err != nil {
return err
}
if !registered {
return errors.New("not registered")
}
bobServers, err := bob1.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)
bob1Servers, err := bob1.GetPushNotificationsServers()
s.Require().NoError(err)
// Register bob2
err = bob2.AddPushNotificationsServer(context.Background(), &server.identity.PublicKey, pushnotificationclient.ServerTypeCustom)
s.Require().NoError(err)
err = bob2.RegisterForPushNotifications(context.Background(), bob2DeviceToken, testAPNTopic, protobuf.PushNotificationRegistration_APN_TOKEN)
s.Require().NoError(err)
err = tt.RetryWithBackOff(func() error {
_, err = server.RetrieveAll()
if err != nil {
return err
}
_, err = bob2.RetrieveAll()
if err != nil {
return err
}
registered, err := bob2.RegisteredForPushNotifications()
if err != nil {
return err
}
if !registered {
return errors.New("not registered")
}
bobServers, err := bob2.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)
bob2Servers, err := bob2.GetPushNotificationsServers()
s.Require().NoError(err)
// Create one to one chat & send message
pkString := hex.EncodeToString(crypto.FromECDSAPub(&s.m.identity.PublicKey))
chat := CreateOneToOneChat(pkString, &s.m.identity.PublicKey, alice.transport)
s.Require().NoError(alice.SaveChat(chat))
inputMessage := buildTestMessage(*chat)
response, err := alice.SendChatMessage(context.Background(), inputMessage)
s.Require().NoError(err)
messageIDString := response.Messages()[0].ID
messageID, err := hex.DecodeString(messageIDString[2:])
s.Require().NoError(err)
infoMap := make(map[string]*pushnotificationclient.PushNotificationInfo)
err = tt.RetryWithBackOff(func() error {
_, err = server.RetrieveAll()
if err != nil {
return err
}
_, err = alice.RetrieveAll()
if err != nil {
return err
}
info, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs)
if err != nil {
return err
}
for _, i := range info {
infoMap[i.AccessToken] = i
}
// Check we have replies for both bob1 and bob2
if len(infoMap) != 2 {
return errors.New("info not fetched")
}
return nil
})
s.Require().Len(infoMap, 2)
// Check we have replies for both bob1 and bob2
var bob1Info, bob2Info *pushnotificationclient.PushNotificationInfo
bob1Info = infoMap[bob1Servers[0].AccessToken]
bob2Info = infoMap[bob2Servers[0].AccessToken]
s.Require().NotNil(bob1Info)
s.Require().Equal(bob1.installationID, bob1Info.InstallationID)
s.Require().Equal(bob1Servers[0].AccessToken, bob1Info.AccessToken)
s.Require().Equal(&bob1.identity.PublicKey, bob1Info.PublicKey)
s.Require().NotNil(bob2Info)
s.Require().Equal(bob2.installationID, bob2Info.InstallationID)
s.Require().Equal(bob2Servers[0].AccessToken, bob2Info.AccessToken)
s.Require().Equal(&bob2.identity.PublicKey, bob2Info.PublicKey)
retrievedNotificationInfo, err := alice.pushNotificationClient.GetPushNotificationInfo(&bob1.identity.PublicKey, bobInstallationIDs)
s.Require().NoError(err)
s.Require().NotNil(retrievedNotificationInfo)
s.Require().Len(retrievedNotificationInfo, 2)
var sentNotification *pushnotificationclient.SentNotification
err = tt.RetryWithBackOff(func() error {
_, err = server.RetrieveAll()
if err != nil {
return err
}
_, err = alice.RetrieveAll()
if err != nil {
return err
}
sentNotification, err = alice.pushNotificationClient.GetSentNotification(common.HashPublicKey(&bob1.identity.PublicKey), bob1.installationID, messageID)
if err != nil {
return err
}
if sentNotification == nil {
return errors.New("sent notification not found")
}
if !sentNotification.Success {
return errors.New("sent notification not successul")
}
return nil
})
s.Require().NoError(err)
s.Require().NoError(bob2.Shutdown())
s.Require().NoError(alice.Shutdown())
s.Require().NoError(server.Shutdown())
}