Add ClearHistory & DeactivateChat methods

This commit is contained in:
Andrea Maria Piana 2020-12-22 11:49:25 +01:00
parent 922e785512
commit b331b61807
12 changed files with 697 additions and 213 deletions

View File

@ -1 +1 @@
0.68.0 0.68.2

View File

@ -312,10 +312,14 @@ func CreatePublicChat(name string, timesource common.TimeSource) Chat {
} }
} }
func CreateProfileChat(name string, profile string, timesource common.TimeSource) Chat { func buildProfileChatID(publicKeyString string) string {
return "@" + publicKeyString
}
func CreateProfileChat(id string, profile string, timesource common.TimeSource) Chat {
return Chat{ return Chat{
ID: name, ID: id,
Name: name, Name: id,
Active: true, Active: true,
Timestamp: int64(timesource.GetCurrentTime()), Timestamp: int64(timesource.GetCurrentTime()),
Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec Color: chatColors[rand.Intn(len(chatColors))], // nolint: gosec

View File

@ -2,7 +2,6 @@ package protocol
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"encoding/hex"
"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"
@ -83,6 +82,18 @@ func (c Contact) IsBlocked() bool {
return existsInStringSlice(c.SystemTags, contactBlocked) return existsInStringSlice(c.SystemTags, contactBlocked)
} }
func (c *Contact) Remove() {
var newSystemTags []string
// Remove the newSystemTags system-tag, so that the contact is
// not considered "added" anymore
for _, tag := range newSystemTags {
if tag != contactAdded {
newSystemTags = append(newSystemTags, tag)
}
}
c.SystemTags = newSystemTags
}
func (c *Contact) ResetENSVerification(clock uint64, name string) { func (c *Contact) ResetENSVerification(clock uint64, name string) {
c.ENSVerifiedAt = 0 c.ENSVerifiedAt = 0
c.ENSVerified = false c.ENSVerified = false
@ -101,16 +112,33 @@ func existsInStringSlice(set []string, find string) bool {
return false return false
} }
func buildContact(publicKey *ecdsa.PublicKey) (*Contact, error) { func buildContactFromPkString(pkString string) (*Contact, error) {
id := "0x" + hex.EncodeToString(crypto.FromECDSAPub(publicKey)) publicKeyBytes, err := types.DecodeHex(pkString)
if err != nil {
return nil, err
}
identicon, err := identicon.GenerateBase64(id) publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes)
if err != nil {
return nil, err
}
return buildContact(pkString, publicKey)
}
func buildContactFromPublicKey(publicKey *ecdsa.PublicKey) (*Contact, error) {
id := types.EncodeHex(crypto.FromECDSAPub(publicKey))
return buildContact(id, publicKey)
}
func buildContact(publicKeyString string, publicKey *ecdsa.PublicKey) (*Contact, error) {
identicon, err := identicon.GenerateBase64(publicKeyString)
if err != nil { if err != nil {
return nil, err return nil, err
} }
contact := &Contact{ contact := &Contact{
ID: id, ID: publicKeyString,
Alias: alias.GenerateFromPublicKey(publicKey), Alias: alias.GenerateFromPublicKey(publicKey),
Identicon: identicon, Identicon: identicon,
} }

View File

@ -5,7 +5,8 @@ import (
) )
var ( var (
ErrChatIDEmpty = errors.New("chat ID is empty") ErrChatIDEmpty = errors.New("chat ID is empty")
ErrChatNotFound = errors.New("can't find chat") ErrChatNotFound = errors.New("can't find chat")
ErrNotImplemented = errors.New("not implemented") ErrNotImplemented = errors.New("not implemented")
ErrContactNotFound = errors.New("contact not found")
) )

View File

@ -221,15 +221,8 @@ func (m *MessageHandler) HandleSyncInstallationContact(state *ReceivedMessageSta
contact, ok := state.AllContacts[message.Id] contact, ok := state.AllContacts[message.Id]
if !ok { if !ok {
publicKeyBytes, err := hex.DecodeString(message.Id[2:]) var err error
if err != nil { contact, err = buildContactFromPkString(message.Id)
return err
}
publicKey, err := crypto.UnmarshalPubkey(publicKeyBytes)
if err != nil {
return err
}
contact, err = buildContact(publicKey)
if err != nil { if err != nil {
return err return err
} }

View File

@ -818,8 +818,27 @@ func (db sqlitePersistence) HideMessage(id string) error {
} }
func (db sqlitePersistence) DeleteMessagesByChatID(id string) error { func (db sqlitePersistence) DeleteMessagesByChatID(id string) error {
_, err := db.db.Exec(`DELETE FROM user_messages WHERE local_chat_id = ?`, id) return db.deleteMessagesByChatID(id, nil)
return err }
func (db sqlitePersistence) deleteMessagesByChatID(id string, tx *sql.Tx) (err error) {
if tx == nil {
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
}
_, err = tx.Exec(`DELETE FROM user_messages WHERE local_chat_id = ?`, id)
return
} }
func (db sqlitePersistence) MarkAllRead(chatID string) error { func (db sqlitePersistence) MarkAllRead(chatID string) error {
@ -1136,3 +1155,79 @@ func (db sqlitePersistence) InvitationByID(id string) (*GroupChatInvitation, err
return nil, err return nil, err
} }
} }
// ClearHistory deletes all the messages for a chat and updates it's values
func (db sqlitePersistence) ClearHistory(chat *Chat, currentClockValue uint64) (err error) {
var tx *sql.Tx
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
err = db.clearHistory(chat, currentClockValue, tx)
return
}
// Deactivate chat sets a chat as inactive and clear its history
func (db sqlitePersistence) DeactivateChat(chat *Chat, currentClockValue uint64) (err error) {
var tx *sql.Tx
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
err = db.deactivateChat(chat, currentClockValue, tx)
return
}
func (db sqlitePersistence) deactivateChat(chat *Chat, currentClockValue uint64, tx *sql.Tx) error {
chat.Active = false
err := db.saveChat(tx, *chat)
if err != nil {
return err
}
return db.clearHistory(chat, currentClockValue, tx)
}
func (db sqlitePersistence) clearHistory(chat *Chat, currentClockValue uint64, tx *sql.Tx) error {
// Set deleted at clock value if it's not a public chat so that
// old messages will be discarded
if !chat.Public() && !chat.ProfileUpdates() && !chat.Timeline() {
if chat.LastMessage != nil && chat.LastMessage.Clock != 0 {
chat.DeletedAtClockValue = chat.LastMessage.Clock
}
chat.DeletedAtClockValue = currentClockValue
}
chat.LastMessage = nil
chat.UnviewedMessagesCount = 0
err := db.deleteMessagesByChatID(chat.ID, tx)
if err != nil {
return err
}
err = db.saveChat(tx, *chat)
return err
}

View File

@ -2082,59 +2082,42 @@ func (m *Messenger) DeleteChat(chatID string) error {
return nil return nil
} }
func (m *Messenger) isNewContact(contact *Contact) bool { func (m *Messenger) DeactivateChat(chatID string) (*MessengerResponse, error) {
previousContact, ok := m.allContacts[contact.ID] m.mutex.Lock()
return contact.IsAdded() && (!ok || !previousContact.IsAdded()) defer m.mutex.Unlock()
return m.deactivateChat(chatID)
} }
func (m *Messenger) hasNicknameChanged(contact *Contact) bool { func (m *Messenger) deactivateChat(chatID string) (*MessengerResponse, error) {
previousContact, ok := m.allContacts[contact.ID] var response MessengerResponse
chat, ok := m.allChats[chatID]
if !ok { if !ok {
return false return nil, ErrChatNotFound
} }
return contact.LocalNickname != previousContact.LocalNickname
}
func (m *Messenger) removedContact(contact *Contact) bool { clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
previousContact, ok := m.allContacts[contact.ID]
if !ok { err := m.persistence.DeactivateChat(chat, clock)
return false
}
return previousContact.IsAdded() && !contact.IsAdded()
}
func (m *Messenger) saveContact(contact *Contact) error {
name, identicon, err := generateAliasAndIdenticon(contact.ID)
if err != nil { if err != nil {
return err return nil, err
} }
contact.Identicon = identicon // We re-register as our options have changed and we don't want to
contact.Alias = name // receive PN from mentions in this chat anymore
if chat.Public() {
if m.isNewContact(contact) || m.hasNicknameChanged(contact) { err := m.reregisterForPushNotifications()
err := m.syncContact(context.Background(), contact)
if err != nil { if err != nil {
return err return nil, err
} }
} }
// We check if it should re-register with the push notification server m.allChats[chatID] = chat
shouldReregisterForPushNotifications := (m.isNewContact(contact) || m.removedContact(contact))
err = m.persistence.SaveContact(contact, nil) response.Chats = []*Chat{chat}
if err != nil { // TODO: Remove filters
return err
}
m.allContacts[contact.ID] = contact return &response, nil
// Reregister only when data has changed
if shouldReregisterForPushNotifications {
return m.reregisterForPushNotifications()
}
return nil
} }
func (m *Messenger) reregisterForPushNotifications() error { func (m *Messenger) reregisterForPushNotifications() error {
@ -2146,55 +2129,6 @@ func (m *Messenger) reregisterForPushNotifications() error {
return m.pushNotificationClient.Reregister(m.pushNotificationOptions()) return m.pushNotificationClient.Reregister(m.pushNotificationOptions())
} }
func (m *Messenger) SaveContact(contact *Contact) error {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.saveContact(contact)
}
func (m *Messenger) BlockContact(contact *Contact) ([]*Chat, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
chats, err := m.persistence.BlockContact(contact)
if err != nil {
return nil, err
}
m.allContacts[contact.ID] = contact
for _, chat := range chats {
m.allChats[chat.ID] = chat
}
delete(m.allChats, contact.ID)
// re-register for push notifications
err = m.reregisterForPushNotifications()
if err != nil {
return nil, err
}
return chats, nil
}
func (m *Messenger) Contacts() []*Contact {
m.mutex.Lock()
defer m.mutex.Unlock()
var contacts []*Contact
for _, contact := range m.allContacts {
if contact.HasCustomFields() {
contacts = append(contacts, contact)
}
}
return contacts
}
// GetContactByID assumes pubKey includes 0x prefix
func (m *Messenger) GetContactByID(pubKey string) *Contact {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.allContacts[pubKey]
}
// pull a message from the database and send it again // pull a message from the database and send it again
func (m *Messenger) reSendRawMessage(ctx context.Context, messageID string) error { func (m *Messenger) reSendRawMessage(ctx context.Context, messageID string) error {
message, err := m.persistence.RawMessageByID(messageID) message, err := m.persistence.RawMessageByID(messageID)
@ -2524,106 +2458,6 @@ func (m *Messenger) sendChatMessage(ctx context.Context, message *common.Message
return &response, m.saveChat(chat) return &response, m.saveChat(chat)
} }
// Send contact updates to all contacts added by us
func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImage string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
myID := contactIDFromPublicKey(&m.identity.PublicKey)
if _, err := m.sendContactUpdate(ctx, myID, ensName, profileImage); err != nil {
return err
}
// TODO: This should not be sending paired messages, as we do it above
for _, contact := range m.allContacts {
if contact.IsAdded() {
if _, err := m.sendContactUpdate(ctx, contact.ID, ensName, profileImage); err != nil {
return err
}
}
}
return nil
}
// NOTE: this endpoint does not add the contact, the reason being is that currently
// that's left as a responsibility to the client, which will call both `SendContactUpdate`
// and `SaveContact` with the correct system tag.
// Ideally we have a single endpoint that does both, but probably best to bring `ENS` name
// on the messenger first.
// SendContactUpdate sends a contact update to a user and adds the user to contacts
func (m *Messenger) SendContactUpdate(ctx context.Context, chatID, ensName, profileImage string) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.sendContactUpdate(ctx, chatID, ensName, profileImage)
}
func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, profileImage string) (*MessengerResponse, error) {
var response MessengerResponse
contact, ok := m.allContacts[chatID]
if !ok {
pubkeyBytes, err := types.DecodeHex(chatID)
if err != nil {
return nil, err
}
publicKey, err := crypto.UnmarshalPubkey(pubkeyBytes)
if err != nil {
return nil, err
}
contact, err = buildContact(publicKey)
if err != nil {
return nil, err
}
}
chat, ok := m.allChats[chatID]
if !ok {
publicKey, err := contact.PublicKey()
if err != nil {
return nil, err
}
chat = OneToOneFromPublicKey(publicKey, m.getTimesource())
// We don't want to show the chat to the user
chat.Active = false
}
m.allChats[chat.ID] = chat
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
contactUpdate := &protobuf.ContactUpdate{
Clock: clock,
EnsName: ensName,
ProfileImage: profileImage}
encodedMessage, err := proto.Marshal(contactUpdate)
if err != nil {
return nil, err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_CONTACT_UPDATE,
ResendAutomatically: true,
})
if err != nil {
return nil, err
}
response.Contacts = []*Contact{contact}
response.Chats = []*Chat{chat}
chat.LastClockValue = clock
err = m.saveChat(chat)
if err != nil {
return nil, err
}
return &response, m.saveContact(contact)
}
// SyncDevices sends all public chats and contacts to paired devices // SyncDevices sends all public chats and contacts to paired devices
// TODO remove use of photoPath in contacts // TODO remove use of photoPath in contacts
func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string) error { func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string) error {
@ -2914,7 +2748,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
if c, ok := messageState.AllContacts[senderID]; ok { if c, ok := messageState.AllContacts[senderID]; ok {
contact = c contact = c
} else { } else {
c, err := buildContact(publicKey) c, err := buildContact(senderID, publicKey)
if err != nil { if err != nil {
logger.Info("failed to build contact", zap.Error(err)) logger.Info("failed to build contact", zap.Error(err))
continue continue
@ -3376,6 +3210,32 @@ func (m *Messenger) DeleteMessagesByChatID(id string) error {
return m.persistence.DeleteMessagesByChatID(id) return m.persistence.DeleteMessagesByChatID(id)
} }
func (m *Messenger) ClearHistory(id string) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.clearHistory(id)
}
func (m *Messenger) clearHistory(id string) (*MessengerResponse, error) {
chat, ok := m.allChats[id]
if !ok {
return nil, ErrChatNotFound
}
clock, _ := chat.NextClockAndTimestamp(m.transport)
err := m.persistence.ClearHistory(chat, clock)
if err != nil {
return nil, err
}
m.allChats[id] = chat
response := &MessengerResponse{Chats: []*Chat{chat}}
return response, nil
}
// MarkMessagesSeen marks messages with `ids` as seen in the chat `chatID`. // MarkMessagesSeen marks messages with `ids` as seen in the chat `chatID`.
// It returns the number of affected messages or error. If there is an error, // It returns the number of affected messages or error. If there is an error,
// the number of affected messages is always zero. // the number of affected messages is always zero.

View File

@ -119,3 +119,35 @@ func (s *MessengerContactUpdateSuite) TestReceiveContactUpdate() {
s.Require().NotEmpty(receivedContact.LastUpdated) s.Require().NotEmpty(receivedContact.LastUpdated)
s.Require().NoError(theirMessenger.Shutdown()) s.Require().NoError(theirMessenger.Shutdown())
} }
func (s *MessengerContactUpdateSuite) TestAddContact() {
contactID := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey))
theirMessenger := s.newMessenger(s.shh)
s.Require().NoError(theirMessenger.Start())
response, err := theirMessenger.AddContact(context.Background(), contactID)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Contacts, 1)
contact := response.Contacts[0]
// It adds the profile chat and the one to one chat
s.Require().Len(response.Chats, 2)
// It should add the contact
s.Require().True(contact.IsAdded())
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool { return len(r.Contacts) > 0 },
"contact request not received",
)
s.Require().NoError(err)
receivedContact := response.Contacts[0]
s.Require().True(receivedContact.HasBeenAdded())
s.Require().NotEmpty(receivedContact.LastUpdated)
}

View File

@ -0,0 +1,323 @@
package protocol
import (
"context"
"crypto/ecdsa"
"github.com/golang/protobuf/proto"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
func (m *Messenger) SaveContact(contact *Contact) error {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.saveContact(contact)
}
func (m *Messenger) AddContact(ctx context.Context, pubKey string) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
contact, ok := m.allContacts[pubKey]
if !ok {
var err error
contact, err = buildContactFromPkString(pubKey)
if err != nil {
return nil, err
}
}
if !contact.IsAdded() {
contact.SystemTags = append(contact.SystemTags, contactAdded)
}
// We sync the contact with the other devices
err := m.syncContact(context.Background(), contact)
if err != nil {
return nil, err
}
err = m.persistence.SaveContact(contact, nil)
if err != nil {
return nil, err
}
m.allContacts[contact.ID] = contact
// And we re-register for push notications
err = m.reregisterForPushNotifications()
if err != nil {
return nil, err
}
// Create the corresponding profile chat
profileChatID := buildProfileChatID(contact.ID)
profileChat, ok := m.allChats[profileChatID]
if !ok {
builtChat := CreateProfileChat(profileChatID, contact.ID, m.getTimesource())
profileChat = &builtChat
}
// TODO: return filters in messenger response
err = m.Join(*profileChat)
if err != nil {
return nil, err
}
// Finally we send a contact update so they are notified we added them
// TODO: ens and picture are both blank for now
response, err := m.sendContactUpdate(context.Background(), pubKey, "", "")
if err != nil {
return nil, err
}
response.Chats = append(response.Chats, profileChat)
publicKey, err := contact.PublicKey()
if err != nil {
return nil, err
}
// TODO: Add filters to response
_, err = m.transport.InitFilters([]string{profileChat.ID}, []*ecdsa.PublicKey{publicKey})
if err != nil {
return nil, err
}
return response, nil
}
func (m *Messenger) RemoveContact(ctx context.Context, pubKey string) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
var response *MessengerResponse
contact, ok := m.allContacts[pubKey]
if !ok {
return nil, ErrContactNotFound
}
contact.Remove()
err := m.persistence.SaveContact(contact, nil)
if err != nil {
return nil, err
}
m.allContacts[contact.ID] = contact
// And we re-register for push notications
err = m.reregisterForPushNotifications()
if err != nil {
return nil, err
}
// Create the corresponding profile chat
profileChatID := buildProfileChatID(contact.ID)
_, ok = m.allChats[profileChatID]
if ok {
chatResponse, err := m.deactivateChat(profileChatID)
if err != nil {
return nil, err
}
err = response.Merge(chatResponse)
if err != nil {
return nil, err
}
}
response.Contacts = []*Contact{contact}
return response, nil
}
func (m *Messenger) Contacts() []*Contact {
m.mutex.Lock()
defer m.mutex.Unlock()
var contacts []*Contact
for _, contact := range m.allContacts {
if contact.HasCustomFields() {
contacts = append(contacts, contact)
}
}
return contacts
}
// GetContactByID assumes pubKey includes 0x prefix
func (m *Messenger) GetContactByID(pubKey string) *Contact {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.allContacts[pubKey]
}
func (m *Messenger) BlockContact(contact *Contact) ([]*Chat, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
chats, err := m.persistence.BlockContact(contact)
if err != nil {
return nil, err
}
m.allContacts[contact.ID] = contact
for _, chat := range chats {
m.allChats[chat.ID] = chat
}
delete(m.allChats, contact.ID)
// re-register for push notifications
err = m.reregisterForPushNotifications()
if err != nil {
return nil, err
}
return chats, nil
}
func (m *Messenger) saveContact(contact *Contact) error {
name, identicon, err := generateAliasAndIdenticon(contact.ID)
if err != nil {
return err
}
contact.Identicon = identicon
contact.Alias = name
if m.isNewContact(contact) || m.hasNicknameChanged(contact) {
err := m.syncContact(context.Background(), contact)
if err != nil {
return err
}
}
// We check if it should re-register with the push notification server
shouldReregisterForPushNotifications := (m.isNewContact(contact) || m.removedContact(contact))
err = m.persistence.SaveContact(contact, nil)
if err != nil {
return err
}
m.allContacts[contact.ID] = contact
// Reregister only when data has changed
if shouldReregisterForPushNotifications {
return m.reregisterForPushNotifications()
}
return nil
}
// Send contact updates to all contacts added by us
func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImage string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
myID := contactIDFromPublicKey(&m.identity.PublicKey)
if _, err := m.sendContactUpdate(ctx, myID, ensName, profileImage); err != nil {
return err
}
// TODO: This should not be sending paired messages, as we do it above
for _, contact := range m.allContacts {
if contact.IsAdded() {
if _, err := m.sendContactUpdate(ctx, contact.ID, ensName, profileImage); err != nil {
return err
}
}
}
return nil
}
// NOTE: this endpoint does not add the contact, the reason being is that currently
// that's left as a responsibility to the client, which will call both `SendContactUpdate`
// and `SaveContact` with the correct system tag.
// Ideally we have a single endpoint that does both, but probably best to bring `ENS` name
// on the messenger first.
// SendContactUpdate sends a contact update to a user and adds the user to contacts
func (m *Messenger) SendContactUpdate(ctx context.Context, chatID, ensName, profileImage string) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.sendContactUpdate(ctx, chatID, ensName, profileImage)
}
func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, profileImage string) (*MessengerResponse, error) {
var response MessengerResponse
contact, ok := m.allContacts[chatID]
if !ok {
var err error
contact, err = buildContactFromPkString(chatID)
if err != nil {
return nil, err
}
}
chat, ok := m.allChats[chatID]
if !ok {
publicKey, err := contact.PublicKey()
if err != nil {
return nil, err
}
chat = OneToOneFromPublicKey(publicKey, m.getTimesource())
// We don't want to show the chat to the user
chat.Active = false
}
m.allChats[chat.ID] = chat
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
contactUpdate := &protobuf.ContactUpdate{
Clock: clock,
EnsName: ensName,
ProfileImage: profileImage}
encodedMessage, err := proto.Marshal(contactUpdate)
if err != nil {
return nil, err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_CONTACT_UPDATE,
ResendAutomatically: true,
})
if err != nil {
return nil, err
}
response.Contacts = []*Contact{contact}
response.Chats = []*Chat{chat}
chat.LastClockValue = clock
err = m.saveChat(chat)
if err != nil {
return nil, err
}
return &response, m.saveContact(contact)
}
func (m *Messenger) isNewContact(contact *Contact) bool {
previousContact, ok := m.allContacts[contact.ID]
return contact.IsAdded() && (!ok || !previousContact.IsAdded())
}
func (m *Messenger) hasNicknameChanged(contact *Contact) bool {
previousContact, ok := m.allContacts[contact.ID]
if !ok {
return false
}
return contact.LocalNickname != previousContact.LocalNickname
}
func (m *Messenger) removedContact(contact *Contact) bool {
previousContact, ok := m.allContacts[contact.ID]
if !ok {
return false
}
return previousContact.IsAdded() && !contact.IsAdded()
}

View File

@ -99,7 +99,7 @@ func (s *MessengerInstallationSuite) TestReceiveInstallation() {
contactKey, err := crypto.GenerateKey() contactKey, err := crypto.GenerateKey()
s.Require().NoError(err) s.Require().NoError(err)
contact, err := buildContact(&contactKey.PublicKey) contact, err := buildContactFromPublicKey(&contactKey.PublicKey)
s.Require().NoError(err) s.Require().NoError(err)
contact.SystemTags = append(contact.SystemTags, contactAdded) contact.SystemTags = append(contact.SystemTags, contactAdded)
err = s.m.SaveContact(contact) err = s.m.SaveContact(contact)
@ -141,7 +141,7 @@ func (s *MessengerInstallationSuite) TestSyncInstallation() {
contactKey, err := crypto.GenerateKey() contactKey, err := crypto.GenerateKey()
s.Require().NoError(err) s.Require().NoError(err)
contact, err := buildContact(&contactKey.PublicKey) contact, err := buildContactFromPublicKey(&contactKey.PublicKey)
s.Require().NoError(err) s.Require().NoError(err)
contact.SystemTags = append(contact.SystemTags, contactAdded) contact.SystemTags = append(contact.SystemTags, contactAdded)
contact.LocalNickname = "Test Nickname" contact.LocalNickname = "Test Nickname"

View File

@ -776,3 +776,135 @@ func TestHideMessage(t *testing.T) {
require.True(t, actualHidden) require.True(t, actualHidden)
require.True(t, actualSeen) require.True(t, actualSeen)
} }
func TestDeactivatePublicChat(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := sqlitePersistence{db: db}
publicChatID := "public-chat-id"
var currentClockValue uint64 = 10
timesource := &testTimeSource{}
lastMessage := common.Message{
ID: "0x01",
LocalChatID: publicChatID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: "me",
}
lastMessage.Clock = 20
require.NoError(t, p.SaveMessages([]*common.Message{&lastMessage}))
publicChat := CreatePublicChat(publicChatID, timesource)
publicChat.LastMessage = &lastMessage
publicChat.UnviewedMessagesCount = 1
err = p.DeactivateChat(&publicChat, currentClockValue)
// It does not set deleted at for a public chat
require.NoError(t, err)
require.Equal(t, uint64(0), publicChat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, publicChat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), publicChat.UnviewedMessagesCount)
// It sets active as false
require.False(t, publicChat.Active)
// It deletes messages
messages, _, err := p.MessageByChatID(publicChatID, "", 10)
require.NoError(t, err)
require.Len(t, messages, 0)
// Reload chat to make sure it has been save
dbChat, err := p.Chat(publicChatID)
require.NoError(t, err)
require.NotNil(t, dbChat)
// Same checks on the chat pulled from the db
// It does not set deleted at for a public chat
require.NoError(t, err)
require.Equal(t, uint64(0), dbChat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, dbChat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), dbChat.UnviewedMessagesCount)
// It sets active as false
require.False(t, dbChat.Active)
}
func TestDeactivateOneToOneChat(t *testing.T) {
key, err := crypto.GenerateKey()
require.NoError(t, err)
pkString := types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey))
db, err := openTestDB()
require.NoError(t, err)
p := sqlitePersistence{db: db}
var currentClockValue uint64 = 10
timesource := &testTimeSource{}
chat := CreateOneToOneChat(pkString, &key.PublicKey, timesource)
lastMessage := common.Message{
ID: "0x01",
LocalChatID: chat.ID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: "me",
}
lastMessage.Clock = 20
require.NoError(t, p.SaveMessages([]*common.Message{&lastMessage}))
chat.LastMessage = &lastMessage
chat.UnviewedMessagesCount = 1
err = p.DeactivateChat(&chat, currentClockValue)
// It does set deleted at for a public chat
require.NoError(t, err)
require.NotEqual(t, uint64(0), chat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, chat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), chat.UnviewedMessagesCount)
// It sets active as false
require.False(t, chat.Active)
// It deletes messages
messages, _, err := p.MessageByChatID(chat.ID, "", 10)
require.NoError(t, err)
require.Len(t, messages, 0)
// Reload chat to make sure it has been save
dbChat, err := p.Chat(chat.ID)
require.NoError(t, err)
require.NotNil(t, dbChat)
// Same checks on the chat pulled from the db
// It does set deleted at for a public chat
require.NoError(t, err)
require.NotEqual(t, uint64(0), dbChat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, dbChat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), dbChat.UnviewedMessagesCount)
// It sets active as false
require.False(t, dbChat.Active)
}

View File

@ -415,6 +415,22 @@ func (api *PublicAPI) MarkAllRead(chatID string) error {
return api.service.messenger.MarkAllRead(chatID) return api.service.messenger.MarkAllRead(chatID)
} }
func (api *PublicAPI) AddContact(ctx context.Context, pubKey string) (*protocol.MessengerResponse, error) {
return api.service.messenger.AddContact(ctx, pubKey)
}
func (api *PublicAPI) RemoveContact(ctx context.Context, pubKey string) (*protocol.MessengerResponse, error) {
return api.service.messenger.RemoveContact(ctx, pubKey)
}
func (api *PublicAPI) ClearHistory(chatID string) (*protocol.MessengerResponse, error) {
return api.service.messenger.ClearHistory(chatID)
}
func (api *PublicAPI) DeactivateChat(chatID string) (*protocol.MessengerResponse, error) {
return api.service.messenger.DeactivateChat(chatID)
}
func (api *PublicAPI) UpdateMessageOutgoingStatus(id, newOutgoingStatus string) error { func (api *PublicAPI) UpdateMessageOutgoingStatus(id, newOutgoingStatus string) error {
return api.service.messenger.UpdateMessageOutgoingStatus(id, newOutgoingStatus) return api.service.messenger.UpdateMessageOutgoingStatus(id, newOutgoingStatus)
} }