324 lines
7.7 KiB
Go
324 lines
7.7 KiB
Go
|
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()
|
||
|
}
|