Add ClearHistory & DeactivateChat methods
This commit is contained in:
parent
922e785512
commit
b331b61807
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue