status-go/protocol/messenger_handler.go

1607 lines
48 KiB
Go
Raw Normal View History

package protocol
2019-10-14 14:10:48 +00:00
import (
"context"
"crypto/ecdsa"
"encoding/hex"
"fmt"
2021-03-05 14:38:32 +00:00
2019-10-14 14:10:48 +00:00
"github.com/pkg/errors"
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
2020-08-07 13:49:37 +00:00
"github.com/status-im/status-go/eth-node/types"
2020-11-25 00:34:32 +00:00
"github.com/status-im/status-go/images"
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
"github.com/status-im/status-go/multiaccounts/settings"
2020-07-22 07:41:40 +00:00
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf"
v1protocol "github.com/status-im/status-go/protocol/v1"
2019-10-14 14:10:48 +00:00
)
const (
transactionRequestDeclinedMessage = "Transaction request declined"
requestAddressForTransactionAcceptedMessage = "Request address for transaction accepted"
requestAddressForTransactionDeclinedMessage = "Request address for transaction declined"
)
var ErrMessageNotAllowed = errors.New("message from a non-contact")
2021-09-08 11:10:53 +00:00
var ErrMessageForWrongChatType = errors.New("message for the wrong chat type")
2019-10-14 14:10:48 +00:00
// HandleMembershipUpdate updates a Chat instance according to the membership updates.
// It retrieves chat, if exists, and merges membership updates from the message.
// Finally, the Chat is updated with the new group events.
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleMembershipUpdate(messageState *ReceivedMessageState, chat *Chat, rawMembershipUpdate protobuf.MembershipUpdateMessage, translations *systemMessageTranslationsMap) error {
var group *v1protocol.Group
var err error
logger := m.logger.With(zap.String("site", "HandleMembershipUpdate"))
message, err := v1protocol.MembershipUpdateMessageFromProtobuf(&rawMembershipUpdate)
if err != nil {
return err
}
if err := ValidateMembershipUpdateMessage(message, messageState.Timesource.GetCurrentTime()); err != nil {
logger.Warn("failed to validate message", zap.Error(err))
return err
}
senderID := messageState.CurrentMessageState.Contact.ID
allowed, err := m.isMessageAllowedFrom(senderID, chat)
if err != nil {
return err
}
if !allowed {
return ErrMessageNotAllowed
}
2020-08-07 13:49:37 +00:00
//if chat.InvitationAdmin exists means we are waiting for invitation request approvement, and in that case
//we need to create a new chat instance like we don't have a chat and just use a regular invitation flow
waitingForApproval := chat != nil && len(chat.InvitationAdmin) > 0
ourKey := contactIDFromPublicKey(&m.identity.PublicKey)
isActive := messageState.CurrentMessageState.Contact.Added || messageState.CurrentMessageState.Contact.ID == ourKey || waitingForApproval
showPushNotification := isActive && messageState.CurrentMessageState.Contact.ID != ourKey
// wasUserAdded indicates whether the user has been added to the group with this update
wasUserAdded := false
if chat == nil || waitingForApproval {
if len(message.Events) == 0 {
return errors.New("can't create new group chat without events")
2019-10-14 14:10:48 +00:00
}
2020-08-07 13:49:37 +00:00
//approve invitations
if waitingForApproval {
2020-08-07 13:49:37 +00:00
groupChatInvitation := &GroupChatInvitation{
GroupChatInvitation: protobuf.GroupChatInvitation{
ChatId: message.ChatID,
},
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
}
groupChatInvitation, err = m.persistence.InvitationByID(groupChatInvitation.ID())
if err != nil && err != common.ErrRecordNotFound {
2020-08-07 13:49:37 +00:00
return err
}
if groupChatInvitation != nil {
groupChatInvitation.State = protobuf.GroupChatInvitation_APPROVED
err := m.persistence.SaveInvitation(groupChatInvitation)
if err != nil {
return err
}
messageState.GroupChatInvitations[groupChatInvitation.ID()] = groupChatInvitation
}
}
group, err = v1protocol.NewGroupWithEvents(message.ChatID, message.Events)
2019-10-14 14:10:48 +00:00
if err != nil {
return err
2019-10-14 14:10:48 +00:00
}
// A new chat must contain us
if !group.IsMember(ourKey) {
return errors.New("can't create a new group chat without us being a member")
2019-10-14 14:10:48 +00:00
}
// A new chat always adds us
wasUserAdded = true
newChat := CreateGroupChat(messageState.Timesource)
// We set group chat inactive and create a notification instead
// unless is coming from us or a contact or were waiting for approval
newChat.Active = isActive
newChat.ReceivedInvitationAdmin = senderID
chat = &newChat
chat.updateChatFromGroupMembershipChanges(group)
if err != nil {
return errors.Wrap(err, "failed to get group creator")
}
} else {
existingGroup, err := newProtocolGroupFromChat(chat)
if err != nil {
return errors.Wrap(err, "failed to create a Group from Chat")
}
updateGroup, err := v1protocol.NewGroupWithEvents(message.ChatID, message.Events)
if err != nil {
return errors.Wrap(err, "invalid membership update")
}
merged := v1protocol.MergeMembershipUpdateEvents(existingGroup.Events(), updateGroup.Events())
2020-03-09 06:19:23 +00:00
group, err = v1protocol.NewGroupWithEvents(chat.ID, merged)
if err != nil {
return errors.Wrap(err, "failed to create a group with new membership updates")
}
chat.updateChatFromGroupMembershipChanges(group)
wasUserAdded = !existingGroup.IsMember(ourKey) &&
updateGroup.IsMember(ourKey)
// Reactivate deleted group chat on re-invite from contact
chat.Active = chat.Active || (isActive && wasUserAdded)
// Show push notifications when our key is added to members list and chat is Active
showPushNotification = showPushNotification && wasUserAdded
}
// Only create a message notification when the user is added, not when removed
if !chat.Active && wasUserAdded {
chat.Highlight = true
m.createMessageNotification(chat, messageState)
}
profilePicturesVisibility, err := m.settings.GetProfilePicturesVisibility()
if err != nil {
return errors.Wrap(err, "failed to get profilePicturesVisibility setting")
}
if showPushNotification {
// chat is highlighted for new group invites or group re-invites
chat.Highlight = true
messageState.Response.AddNotification(NewPrivateGroupInviteNotification(chat.ID, chat, messageState.CurrentMessageState.Contact, profilePicturesVisibility))
}
systemMessages := buildSystemMessages(message.Events, translations)
for _, message := range systemMessages {
messageID := message.ID
exists, err := m.messageExists(messageID, messageState.ExistingMessagesMap)
if err != nil {
m.logger.Warn("failed to check message exists", zap.Error(err))
}
if exists {
continue
}
2021-06-03 13:11:55 +00:00
messageState.Response.AddMessage(message)
}
messageState.Response.AddChat(chat)
// Store in chats map as it might be a new one
2021-03-29 15:41:30 +00:00
messageState.AllChats.Store(chat.ID, chat)
if waitingForApproval {
_, err = m.ConfirmJoiningGroup(context.Background(), chat.ID)
if err != nil {
return err
}
}
2020-07-26 21:37:04 +00:00
if message.Message != nil {
messageState.CurrentMessageState.Message = *message.Message
return m.HandleChatMessage(messageState)
2020-07-28 14:34:49 +00:00
} else if message.EmojiReaction != nil {
return m.HandleEmojiReaction(messageState, *message.EmojiReaction)
2019-10-14 14:10:48 +00:00
}
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) createMessageNotification(chat *Chat, messageState *ReceivedMessageState) {
var notificationType ActivityCenterType
if chat.OneToOne() {
notificationType = ActivityCenterNotificationTypeNewOneToOne
} else {
notificationType = ActivityCenterNotificationTypeNewPrivateGroupChat
}
notification := &ActivityCenterNotification{
ID: types.FromHex(chat.ID),
Name: chat.Name,
LastMessage: chat.LastMessage,
Type: notificationType,
Author: messageState.CurrentMessageState.Contact.ID,
Timestamp: messageState.CurrentMessageState.WhisperTimestamp,
ChatID: chat.ID,
}
err := m.addActivityCenterNotification(messageState, notification)
if err != nil {
m.logger.Warn("failed to create activity center notification", zap.Error(err))
}
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) handleCommandMessage(state *ReceivedMessageState, message *common.Message) error {
message.ID = state.CurrentMessageState.MessageID
message.From = state.CurrentMessageState.Contact.ID
message.Alias = state.CurrentMessageState.Contact.Alias
message.SigPubKey = state.CurrentMessageState.PublicKey
message.Identicon = state.CurrentMessageState.Contact.Identicon
message.WhisperTimestamp = state.CurrentMessageState.WhisperTimestamp
if err := message.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey)); err != nil {
return fmt.Errorf("failed to prepare content: %v", err)
}
2021-06-08 06:07:45 +00:00
chat, err := m.matchChatEntity(message)
2019-10-14 14:10:48 +00:00
if err != nil {
return err
2019-10-14 14:10:48 +00:00
}
2021-06-08 06:07:45 +00:00
allowed, err := m.isMessageAllowedFrom(state.CurrentMessageState.Contact.ID, chat)
if err != nil {
return err
}
if !allowed {
return ErrMessageNotAllowed
}
// If deleted-at is greater, ignore message
if chat.DeletedAtClockValue >= message.Clock {
return nil
}
// Set the LocalChatID for the message
message.LocalChatID = chat.ID
2021-03-29 15:41:30 +00:00
if c, ok := state.AllChats.Load(chat.ID); ok {
chat = c
}
// Set the LocalChatID for the message
message.LocalChatID = chat.ID
// Increase unviewed count
if !common.IsPubKeyEqual(message.SigPubKey, &m.identity.PublicKey) {
m.updateUnviewedCounts(chat, message.Mentioned)
message.OutgoingStatus = ""
} else {
// Our own message, mark as sent
2020-09-01 13:27:01 +00:00
message.OutgoingStatus = common.OutgoingStatusSent
}
err = chat.UpdateFromMessage(message, state.Timesource)
if err != nil {
return err
2019-10-14 14:10:48 +00:00
}
if !chat.Active {
m.createMessageNotification(chat, state)
}
// Add to response
state.Response.AddChat(chat)
if message != nil {
2021-06-25 08:30:18 +00:00
message.New = true
2021-06-03 13:11:55 +00:00
state.Response.AddMessage(message)
}
// Set in the modified maps chat
state.AllChats.Store(chat.ID, chat)
return nil
}
func (m *Messenger) HandleBackup(state *ReceivedMessageState, message protobuf.Backup) error {
for _, contact := range message.Contacts {
err := m.HandleSyncInstallationContact(state, *contact)
if err != nil {
return err
}
}
2022-01-06 16:35:08 +00:00
for _, community := range message.Communities {
err := m.handleSyncCommunity(state, *community)
if err != nil {
return err
}
}
return nil
}
func (m *Messenger) HandleSyncInstallationContact(state *ReceivedMessageState, message protobuf.SyncInstallationContactV2) error {
removedOrBlcoked := message.Removed || message.Blocked
chat, ok := state.AllChats.Load(message.Id)
if !ok && (message.Added || message.Muted) && !removedOrBlcoked {
pubKey, err := common.HexToPubkey(message.Id)
if err != nil {
2021-11-05 15:11:10 +00:00
return err
}
chat = OneToOneFromPublicKey(pubKey, state.Timesource)
// We don't want to show the chat to the user
chat.Active = false
}
2021-03-29 15:41:30 +00:00
contact, ok := state.AllContacts.Load(message.Id)
if !ok {
if message.Removed {
// Nothing to do in case if contact doesn't exist
return nil
}
var err error
contact, err = buildContactFromPkString(message.Id)
if err != nil {
return err
}
}
if contact.LastUpdated < message.LastUpdated {
contact.HasAddedUs = message.HasAddedUs
}
if contact.LastUpdatedLocally < message.LastUpdatedLocally {
contact.IsSyncing = true
defer func() {
contact.IsSyncing = false
}()
if message.Added {
2021-03-24 08:04:03 +00:00
contact.Added = true
}
2022-02-17 15:13:10 +00:00
if message.EnsName != "" && contact.EnsName != message.EnsName {
contact.EnsName = message.EnsName
publicKey, err := contact.PublicKey()
if err != nil {
2021-11-05 15:11:10 +00:00
return err
}
err = m.ENSVerified(common.PubkeyToHex(publicKey), message.EnsName)
if err != nil {
contact.ENSVerified = false
}
contact.ENSVerified = true
}
contact.LastUpdatedLocally = message.LastUpdatedLocally
2020-08-20 14:06:38 +00:00
contact.LocalNickname = message.LocalNickname
2021-10-01 14:50:16 +00:00
if message.Blocked != contact.Blocked {
if message.Blocked {
2021-11-05 15:11:10 +00:00
state.AllContacts.Store(contact.ID, contact)
response, err := m.BlockContact(contact.ID)
if err != nil {
return err
}
err = state.Response.Merge(response)
if err != nil {
return err
}
} else {
contact.Unblock()
}
}
if chat != nil && message.Muted != chat.Muted {
if message.Muted {
err := m.muteChat(chat, contact)
if err != nil {
return err
}
} else {
err := m.unmuteChat(chat, contact)
if err != nil {
return err
}
}
state.Response.AddChat(chat)
}
if message.Removed {
err := m.removeContact(context.Background(), state.Response, contact.ID)
if err != nil {
return err
}
}
2021-03-29 15:41:30 +00:00
state.ModifiedContacts.Store(contact.ID, true)
state.AllContacts.Store(contact.ID, contact)
}
if chat != nil {
state.AllChats.Store(chat.ID, chat)
}
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleSyncInstallationPublicChat(state *ReceivedMessageState, message protobuf.SyncInstallationPublicChat) *Chat {
chatID := message.Id
2021-10-05 17:26:02 +00:00
existingChat, ok := state.AllChats.Load(chatID)
if ok && (existingChat.Active || uint32(message.GetClock()/1000) < existingChat.SyncedTo) {
2021-05-14 10:55:42 +00:00
return nil
}
2021-10-05 17:26:02 +00:00
chat := existingChat
if !ok {
chat = CreatePublicChat(chatID, state.Timesource)
chat.Joined = int64(message.Clock)
2021-10-05 17:26:02 +00:00
} else {
existingChat.Joined = int64(message.Clock)
2021-10-05 17:26:02 +00:00
}
2021-03-29 15:41:30 +00:00
state.AllChats.Store(chat.ID, chat)
2021-05-14 10:55:42 +00:00
state.Response.AddChat(chat)
return chat
}
2021-10-05 17:26:02 +00:00
func (m *Messenger) HandleSyncChatRemoved(state *ReceivedMessageState, message protobuf.SyncChatRemoved) error {
chat, ok := m.allChats.Load(message.Id)
if !ok {
return ErrChatNotFound
}
if chat.Joined > int64(message.Clock) {
return nil
}
if chat.PrivateGroupChat() {
_, err := m.leaveGroupChat(context.Background(), state.Response, message.Id, true, false)
if err != nil {
return err
}
}
response, err := m.deactivateChat(message.Id, message.Clock, false)
2021-10-05 17:26:02 +00:00
if err != nil {
return err
}
return state.Response.Merge(response)
}
2021-10-12 10:33:32 +00:00
func (m *Messenger) HandleSyncChatMessagesRead(state *ReceivedMessageState, message protobuf.SyncChatMessagesRead) error {
m.logger.Info("HANDLING SYNC MESSAGES READ", zap.Any("ID", message.Id))
chat, ok := m.allChats.Load(message.Id)
if !ok {
return ErrChatNotFound
}
if chat.ReadMessagesAtClockValue > message.Clock {
return nil
}
err := m.markAllRead(message.Id, message.Clock, false)
if err != nil {
return err
}
state.Response.AddChat(chat)
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandlePinMessage(state *ReceivedMessageState, message protobuf.PinMessage) error {
logger := m.logger.With(zap.String("site", "HandlePinMessage"))
logger.Info("Handling pin message")
pinMessage := &common.PinMessage{
PinMessage: message,
// MessageID: message.MessageId,
WhisperTimestamp: state.CurrentMessageState.WhisperTimestamp,
From: state.CurrentMessageState.Contact.ID,
SigPubKey: state.CurrentMessageState.PublicKey,
Identicon: state.CurrentMessageState.Contact.Identicon,
Alias: state.CurrentMessageState.Contact.Alias,
}
2021-06-08 06:07:45 +00:00
chat, err := m.matchChatEntity(pinMessage)
if err != nil {
return err // matchChatEntity returns a descriptive error message
}
pinMessage.ID, err = generatePinMessageID(&m.identity.PublicKey, pinMessage, chat)
if err != nil {
return err
}
// If deleted-at is greater, ignore message
if chat.DeletedAtClockValue >= pinMessage.Clock {
return nil
}
// Set the LocalChatID for the message
pinMessage.LocalChatID = chat.ID
if c, ok := state.AllChats.Load(chat.ID); ok {
chat = c
}
// Set the LocalChatID for the message
pinMessage.LocalChatID = chat.ID
if chat.LastClockValue < message.Clock {
chat.LastClockValue = message.Clock
}
state.Response.AddPinMessage(pinMessage)
// Set in the modified maps chat
state.Response.AddChat(chat)
state.AllChats.Store(chat.ID, chat)
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleContactUpdate(state *ReceivedMessageState, message protobuf.ContactUpdate) error {
logger := m.logger.With(zap.String("site", "HandleContactUpdate"))
contact := state.CurrentMessageState.Contact
2021-03-29 15:41:30 +00:00
chat, ok := state.AllChats.Load(contact.ID)
2021-06-08 06:07:45 +00:00
allowed, err := m.isMessageAllowedFrom(state.CurrentMessageState.Contact.ID, chat)
if err != nil {
return err
}
if !allowed {
return ErrMessageNotAllowed
}
2022-02-17 15:13:10 +00:00
if err = ValidateDisplayName(&message.DisplayName); err != nil {
return err
}
if !ok {
chat = OneToOneFromPublicKey(state.CurrentMessageState.PublicKey, state.Timesource)
// We don't want to show the chat to the user
chat.Active = false
}
logger.Info("Handling contact update")
if contact.LastUpdated < message.Clock {
logger.Info("Updating contact")
2022-02-17 15:13:10 +00:00
if contact.EnsName != message.EnsName {
contact.EnsName = message.EnsName
contact.ENSVerified = false
}
2022-02-17 15:13:10 +00:00
if len(message.DisplayName) != 0 {
contact.DisplayName = message.DisplayName
}
2021-10-01 14:50:16 +00:00
contact.HasAddedUs = true
contact.LastUpdated = message.Clock
2021-03-29 15:41:30 +00:00
state.ModifiedContacts.Store(contact.ID, true)
state.AllContacts.Store(contact.ID, contact)
}
if chat.LastClockValue < message.Clock {
chat.LastClockValue = message.Clock
}
2021-01-11 10:32:51 +00:00
state.Response.AddChat(chat)
2021-03-29 15:41:30 +00:00
// TODO(samyoul) remove storing of an updated reference pointer?
state.AllChats.Store(chat.ID, chat)
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandlePairInstallation(state *ReceivedMessageState, message protobuf.PairInstallation) error {
logger := m.logger.With(zap.String("site", "HandlePairInstallation"))
if err := ValidateReceivedPairInstallation(&message, state.CurrentMessageState.WhisperTimestamp); err != nil {
logger.Warn("failed to validate message", zap.Error(err))
return err
}
2021-03-29 15:41:30 +00:00
installation, ok := state.AllInstallations.Load(message.InstallationId)
if !ok {
return errors.New("installation not found")
}
metadata := &multidevice.InstallationMetadata{
Name: message.Name,
DeviceType: message.DeviceType,
}
installation.InstallationMetadata = metadata
2021-03-29 15:41:30 +00:00
// TODO(samyoul) remove storing of an updated reference pointer?
state.AllInstallations.Store(message.InstallationId, installation)
state.ModifiedInstallations.Store(message.InstallationId, true)
return nil
}
// HandleCommunityInvitation handles an community invitation
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleCommunityInvitation(state *ReceivedMessageState, signer *ecdsa.PublicKey, invitation protobuf.CommunityInvitation, rawPayload []byte) error {
if invitation.PublicKey == nil {
return errors.New("invalid pubkey")
}
pk, err := crypto.DecompressPubkey(invitation.PublicKey)
if err != nil {
return err
}
if !common.IsPubKeyEqual(pk, &m.identity.PublicKey) {
return errors.New("invitation not for us")
}
2021-01-11 10:32:51 +00:00
communityResponse, err := m.communitiesManager.HandleCommunityInvitation(signer, &invitation, rawPayload)
if err != nil {
return err
}
2021-01-11 10:32:51 +00:00
community := communityResponse.Community
state.Response.AddCommunity(community)
state.Response.CommunityChanges = append(state.Response.CommunityChanges, communityResponse.Changes)
return nil
}
2021-01-11 10:32:51 +00:00
// HandleCommunityRequestToJoin handles an community request to join
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleCommunityRequestToJoin(state *ReceivedMessageState, signer *ecdsa.PublicKey, requestToJoinProto protobuf.CommunityRequestToJoin) error {
2021-01-11 10:32:51 +00:00
if requestToJoinProto.CommunityId == nil {
return errors.New("invalid community id")
}
requestToJoin, err := m.communitiesManager.HandleCommunityRequestToJoin(signer, &requestToJoinProto)
if err != nil {
return err
}
state.Response.RequestsToJoinCommunity = append(state.Response.RequestsToJoinCommunity, requestToJoin)
community, err := m.communitiesManager.GetByID(requestToJoinProto.CommunityId)
if err != nil {
return err
}
contactID := contactIDFromPublicKey(signer)
2021-03-29 15:41:30 +00:00
contact, _ := state.AllContacts.Load(contactID)
state.Response.AddNotification(NewCommunityRequestToJoinNotification(requestToJoin.ID.String(), community, contact))
2021-01-11 10:32:51 +00:00
return nil
}
// handleWrappedCommunityDescriptionMessage handles a wrapped community description
2021-06-07 12:38:13 +00:00
func (m *Messenger) handleWrappedCommunityDescriptionMessage(payload []byte) (*communities.CommunityResponse, error) {
return m.communitiesManager.HandleWrappedCommunityDescriptionMessage(payload)
}
2021-06-07 11:45:06 +00:00
func (m *Messenger) HandleEditMessage(response *MessengerResponse, editMessage EditMessage) error {
2021-06-25 10:20:40 +00:00
if err := ValidateEditMessage(editMessage.EditMessage); err != nil {
return err
}
2021-06-07 11:45:06 +00:00
messageID := editMessage.MessageId
// Check if it's already in the response
originalMessage := response.GetMessage(messageID)
// otherwise pull from database
if originalMessage == nil {
var err error
originalMessage, err = m.persistence.MessageByID(messageID)
2021-06-08 06:07:45 +00:00
if err != nil && err != common.ErrRecordNotFound {
2021-06-07 11:45:06 +00:00
return err
}
}
// We don't have the original message, save the edited message
if originalMessage == nil {
return m.persistence.SaveEdit(editMessage)
}
chat, ok := m.allChats.Load(originalMessage.LocalChatID)
if !ok {
return errors.New("chat not found")
}
// Check edit is valid
if originalMessage.From != editMessage.From {
return errors.New("invalid edit, not the right author")
}
// Check that edit should be applied
if originalMessage.EditedAt >= editMessage.Clock {
return m.persistence.SaveEdit(editMessage)
}
// Update message and return it
err := m.applyEditMessage(&editMessage.EditMessage, originalMessage)
if err != nil {
return err
}
2021-06-23 10:07:26 +00:00
if chat.LastMessage != nil && chat.LastMessage.ID == originalMessage.ID {
chat.LastMessage = originalMessage
err := m.saveChat(chat)
if err != nil {
return err
}
}
2021-06-07 11:45:06 +00:00
response.AddMessage(originalMessage)
response.AddChat(chat)
2021-06-07 08:31:27 +00:00
return nil
}
func (m *Messenger) HandleDeleteMessage(state *ReceivedMessageState, deleteMessage DeleteMessage) error {
if err := ValidateDeleteMessage(deleteMessage.DeleteMessage); err != nil {
return err
}
messageID := deleteMessage.MessageId
// Check if it's already in the response
originalMessage := state.Response.GetMessage(messageID)
// otherwise pull from database
if originalMessage == nil {
var err error
originalMessage, err = m.persistence.MessageByID(messageID)
if err != nil && err != common.ErrRecordNotFound {
return err
}
}
if originalMessage == nil {
return m.persistence.SaveDelete(deleteMessage)
}
chat, ok := m.allChats.Load(originalMessage.LocalChatID)
if !ok {
return errors.New("chat not found")
}
// Check edit is valid
if originalMessage.From != deleteMessage.From {
return errors.New("invalid delete, not the right author")
}
// Update message and return it
originalMessage.Deleted = true
err := m.persistence.SaveMessages([]*common.Message{originalMessage})
if err != nil {
return err
}
2021-08-20 14:26:13 +00:00
err = m.persistence.SetHideOnMessage(deleteMessage.MessageId)
if err != nil {
return err
}
m.logger.Debug("deleting activity center notification for message", zap.String("chatID", chat.ID), zap.String("messageID", deleteMessage.MessageId))
err = m.persistence.DeleteActivityCenterNotificationForMessage(chat.ID, deleteMessage.MessageId)
if err != nil {
m.logger.Warn("failed to delete notifications for deleted message", zap.Error(err))
return err
}
if chat.LastMessage != nil && chat.LastMessage.ID == originalMessage.ID {
2021-08-17 12:40:51 +00:00
if err := m.updateLastMessage(chat); err != nil {
return err
}
}
2021-08-20 14:26:13 +00:00
state.Response.AddRemovedMessage(&RemovedMessage{MessageID: messageID, ChatID: chat.ID})
state.Response.AddChat(chat)
state.Response.AddNotification(DeletedMessageNotification(messageID, chat))
return nil
}
2021-08-17 12:40:51 +00:00
func (m *Messenger) updateLastMessage(chat *Chat) error {
// Get last message that is not hidden
messages, _, err := m.persistence.MessageByChatID(chat.ID, "", 1)
if err != nil {
return err
}
if len(messages) > 0 {
chat.LastMessage = messages[0]
} else {
chat.LastMessage = nil
}
return m.saveChat(chat)
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleChatMessage(state *ReceivedMessageState) error {
logger := m.logger.With(zap.String("site", "handleChatMessage"))
if err := ValidateReceivedChatMessage(&state.CurrentMessageState.Message, state.CurrentMessageState.WhisperTimestamp); err != nil {
logger.Warn("failed to validate message", zap.Error(err))
return err
}
2020-09-01 13:27:01 +00:00
receivedMessage := &common.Message{
ID: state.CurrentMessageState.MessageID,
ChatMessage: state.CurrentMessageState.Message,
From: state.CurrentMessageState.Contact.ID,
Alias: state.CurrentMessageState.Contact.Alias,
SigPubKey: state.CurrentMessageState.PublicKey,
Identicon: state.CurrentMessageState.Contact.Identicon,
WhisperTimestamp: state.CurrentMessageState.WhisperTimestamp,
}
2021-09-23 11:52:15 +00:00
if common.IsPubKeyEqual(state.CurrentMessageState.PublicKey, &m.identity.PublicKey) {
receivedMessage.Seen = true
}
err := receivedMessage.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey))
if err != nil {
return fmt.Errorf("failed to prepare message content: %v", err)
}
2021-06-08 06:07:45 +00:00
chat, err := m.matchChatEntity(receivedMessage)
if err != nil {
return err // matchChatEntity returns a descriptive error message
}
2021-10-12 10:33:32 +00:00
if chat.ReadMessagesAtClockValue > state.CurrentMessageState.WhisperTimestamp {
receivedMessage.Seen = true
}
2021-06-08 06:07:45 +00:00
allowed, err := m.isMessageAllowedFrom(state.CurrentMessageState.Contact.ID, chat)
if err != nil {
return err
}
if !allowed {
return ErrMessageNotAllowed
}
2021-03-04 13:02:08 +00:00
// It looks like status-react created profile chats as public chats
// so for now we need to check for the presence of "@" in their chatID
2021-04-13 03:01:37 +00:00
if chat.Public() && !chat.ProfileUpdates() {
switch receivedMessage.ContentType {
case protobuf.ChatMessage_IMAGE:
return errors.New("images are not allowed in public chats")
case protobuf.ChatMessage_AUDIO:
return errors.New("audio messages are not allowed in public chats")
}
}
2020-10-20 15:10:28 +00:00
// If profile updates check if author is the same as chat profile public key
if chat.ProfileUpdates() && receivedMessage.From != chat.Profile {
return nil
}
// If deleted-at is greater, ignore message
if chat.DeletedAtClockValue >= receivedMessage.Clock {
return nil
}
// Set the LocalChatID for the message
receivedMessage.LocalChatID = chat.ID
2021-06-08 06:07:45 +00:00
if c, ok := m.allChats.Load(chat.ID); ok {
chat = c
}
// Set the LocalChatID for the message
receivedMessage.LocalChatID = chat.ID
// Increase unviewed count
if !common.IsPubKeyEqual(receivedMessage.SigPubKey, &m.identity.PublicKey) {
2021-10-12 10:33:32 +00:00
if !receivedMessage.Seen {
m.updateUnviewedCounts(chat, receivedMessage.Mentioned)
}
} else {
// Our own message, mark as sent
2020-09-01 13:27:01 +00:00
receivedMessage.OutgoingStatus = common.OutgoingStatusSent
}
if receivedMessage.ContentType == protobuf.ChatMessage_COMMUNITY {
chat.Highlight = true
}
2021-06-08 06:07:45 +00:00
err = m.checkForEdits(receivedMessage)
if err != nil {
return err
}
err = m.checkForDeletes(receivedMessage)
if err != nil {
return err
}
if receivedMessage.Deleted && (chat.LastMessage == nil || chat.LastMessage.ID == receivedMessage.ID) {
// Get last message that is not hidden
messages, _, err := m.persistence.MessageByChatID(receivedMessage.LocalChatID, "", 1)
if err != nil {
return err
}
2021-09-08 11:10:53 +00:00
if len(messages) != 0 {
chat.LastMessage = messages[0]
2021-09-08 11:10:53 +00:00
} else {
chat.LastMessage = nil
}
} else {
err = chat.UpdateFromMessage(receivedMessage, m.getTimesource())
if err != nil {
return err
}
}
// If the chat is not active, create a notification in the center
if !receivedMessage.Deleted && chat.OneToOne() && !chat.Active {
m.createMessageNotification(chat, state)
}
// Set in the modified maps chat
2021-01-11 10:32:51 +00:00
state.Response.AddChat(chat)
2021-03-29 15:41:30 +00:00
// TODO(samyoul) remove storing of an updated reference pointer?
2021-06-08 06:07:45 +00:00
m.allChats.Store(chat.ID, chat)
contact := state.CurrentMessageState.Contact
2021-01-11 10:32:51 +00:00
if receivedMessage.EnsName != "" {
oldRecord, err := m.ensVerifier.Add(contact.ID, receivedMessage.EnsName, receivedMessage.Clock)
if err != nil {
m.logger.Warn("failed to verify ENS name", zap.Error(err))
} else if oldRecord == nil {
// If oldRecord is nil, a new verification process will take place
// so we reset the record
contact.ENSVerified = false
2021-03-29 15:41:30 +00:00
state.ModifiedContacts.Store(contact.ID, true)
state.AllContacts.Store(contact.ID, contact)
2021-01-11 10:32:51 +00:00
}
}
2022-02-17 15:13:10 +00:00
if contact.DisplayName != receivedMessage.DisplayName && len(receivedMessage.DisplayName) != 0 {
contact.DisplayName = receivedMessage.DisplayName
state.ModifiedContacts.Store(contact.ID, true)
}
if receivedMessage.ContentType == protobuf.ChatMessage_COMMUNITY {
m.logger.Debug("Handling community content type")
2021-01-11 10:32:51 +00:00
communityResponse, err := m.handleWrappedCommunityDescriptionMessage(receivedMessage.GetCommunity())
if err != nil {
return err
}
2021-01-11 10:32:51 +00:00
community := communityResponse.Community
receivedMessage.CommunityID = community.IDString()
2021-01-11 10:32:51 +00:00
state.Response.AddCommunity(community)
state.Response.CommunityChanges = append(state.Response.CommunityChanges, communityResponse.Changes)
}
2021-06-25 08:30:18 +00:00
receivedMessage.New = true
2021-06-03 13:11:55 +00:00
state.Response.AddMessage(receivedMessage)
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) addActivityCenterNotification(state *ReceivedMessageState, notification *ActivityCenterNotification) error {
err := m.persistence.SaveActivityCenterNotification(notification)
if err != nil {
m.logger.Warn("failed to save notification", zap.Error(err))
return err
}
state.Response.AddActivityCenterNotification(notification)
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleRequestAddressForTransaction(messageState *ReceivedMessageState, command protobuf.RequestAddressForTransaction) error {
err := ValidateReceivedRequestAddressForTransaction(&command, messageState.CurrentMessageState.WhisperTimestamp)
if err != nil {
return err
}
2020-09-01 13:27:01 +00:00
message := &common.Message{
ChatMessage: protobuf.ChatMessage{
Clock: command.Clock,
Timestamp: messageState.CurrentMessageState.WhisperTimestamp,
Text: "Request address for transaction",
// ChatId is only used as-is for messages sent to oneself (i.e: mostly sync) so no need to check it here
ChatId: command.GetChatId(),
2020-07-25 11:46:43 +00:00
MessageType: protobuf.MessageType_ONE_TO_ONE,
ContentType: protobuf.ChatMessage_TRANSACTION_COMMAND,
},
2020-09-01 13:27:01 +00:00
CommandParameters: &common.CommandParameters{
ID: messageState.CurrentMessageState.MessageID,
Value: command.Value,
Contract: command.Contract,
2020-09-01 13:27:01 +00:00
CommandState: common.CommandStateRequestAddressForTransaction,
},
}
return m.handleCommandMessage(messageState, message)
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleRequestTransaction(messageState *ReceivedMessageState, command protobuf.RequestTransaction) error {
err := ValidateReceivedRequestTransaction(&command, messageState.CurrentMessageState.WhisperTimestamp)
if err != nil {
return err
}
2020-09-01 13:27:01 +00:00
message := &common.Message{
ChatMessage: protobuf.ChatMessage{
Clock: command.Clock,
Timestamp: messageState.CurrentMessageState.WhisperTimestamp,
Text: "Request transaction",
// ChatId is only used for messages sent to oneself (i.e: mostly sync) so no need to check it here
ChatId: command.GetChatId(),
2020-07-25 11:46:43 +00:00
MessageType: protobuf.MessageType_ONE_TO_ONE,
ContentType: protobuf.ChatMessage_TRANSACTION_COMMAND,
},
2020-09-01 13:27:01 +00:00
CommandParameters: &common.CommandParameters{
ID: messageState.CurrentMessageState.MessageID,
Value: command.Value,
Contract: command.Contract,
2020-09-01 13:27:01 +00:00
CommandState: common.CommandStateRequestTransaction,
Address: command.Address,
},
}
return m.handleCommandMessage(messageState, message)
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleAcceptRequestAddressForTransaction(messageState *ReceivedMessageState, command protobuf.AcceptRequestAddressForTransaction) error {
err := ValidateReceivedAcceptRequestAddressForTransaction(&command, messageState.CurrentMessageState.WhisperTimestamp)
if err != nil {
return err
}
initialMessage, err := m.persistence.MessageByID(command.Id)
if err != nil {
return err
}
if initialMessage == nil {
return errors.New("message not found")
}
if initialMessage.LocalChatID != messageState.CurrentMessageState.Contact.ID {
return errors.New("From must match")
}
if initialMessage.OutgoingStatus == "" {
return errors.New("Initial message must originate from us")
}
2020-09-01 13:27:01 +00:00
if initialMessage.CommandParameters.CommandState != common.CommandStateRequestAddressForTransaction {
return errors.New("Wrong state for command")
}
initialMessage.Clock = command.Clock
initialMessage.Timestamp = messageState.CurrentMessageState.WhisperTimestamp
initialMessage.Text = requestAddressForTransactionAcceptedMessage
initialMessage.CommandParameters.Address = command.Address
initialMessage.Seen = false
2020-09-01 13:27:01 +00:00
initialMessage.CommandParameters.CommandState = common.CommandStateRequestAddressForTransactionAccepted
initialMessage.ChatId = command.GetChatId()
// Hide previous message
2020-01-17 12:39:09 +00:00
previousMessage, err := m.persistence.MessageByCommandID(messageState.CurrentMessageState.Contact.ID, command.Id)
if err != nil && err != common.ErrRecordNotFound {
return err
}
if previousMessage != nil {
err = m.persistence.HideMessage(previousMessage.ID)
if err != nil {
return err
}
initialMessage.Replace = previousMessage.ID
}
return m.handleCommandMessage(messageState, initialMessage)
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleSendTransaction(messageState *ReceivedMessageState, command protobuf.SendTransaction) error {
err := ValidateReceivedSendTransaction(&command, messageState.CurrentMessageState.WhisperTimestamp)
if err != nil {
return err
}
transactionToValidate := &TransactionToValidate{
MessageID: messageState.CurrentMessageState.MessageID,
CommandID: command.Id,
TransactionHash: command.TransactionHash,
FirstSeen: messageState.CurrentMessageState.WhisperTimestamp,
Signature: command.Signature,
Validate: true,
From: messageState.CurrentMessageState.PublicKey,
RetryCount: 0,
}
m.logger.Info("Saving transction to validate", zap.Any("transaction", transactionToValidate))
return m.persistence.SaveTransactionToValidate(transactionToValidate)
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleDeclineRequestAddressForTransaction(messageState *ReceivedMessageState, command protobuf.DeclineRequestAddressForTransaction) error {
err := ValidateReceivedDeclineRequestAddressForTransaction(&command, messageState.CurrentMessageState.WhisperTimestamp)
if err != nil {
return err
}
oldMessage, err := m.persistence.MessageByID(command.Id)
if err != nil {
return err
}
if oldMessage == nil {
return errors.New("message not found")
}
if oldMessage.LocalChatID != messageState.CurrentMessageState.Contact.ID {
return errors.New("From must match")
}
if oldMessage.OutgoingStatus == "" {
return errors.New("Initial message must originate from us")
}
2020-09-01 13:27:01 +00:00
if oldMessage.CommandParameters.CommandState != common.CommandStateRequestAddressForTransaction {
return errors.New("Wrong state for command")
}
oldMessage.Clock = command.Clock
oldMessage.Timestamp = messageState.CurrentMessageState.WhisperTimestamp
oldMessage.Text = requestAddressForTransactionDeclinedMessage
oldMessage.Seen = false
2020-09-01 13:27:01 +00:00
oldMessage.CommandParameters.CommandState = common.CommandStateRequestAddressForTransactionDeclined
oldMessage.ChatId = command.GetChatId()
// Hide previous message
err = m.persistence.HideMessage(command.Id)
if err != nil {
return err
}
oldMessage.Replace = command.Id
return m.handleCommandMessage(messageState, oldMessage)
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleDeclineRequestTransaction(messageState *ReceivedMessageState, command protobuf.DeclineRequestTransaction) error {
err := ValidateReceivedDeclineRequestTransaction(&command, messageState.CurrentMessageState.WhisperTimestamp)
if err != nil {
return err
}
oldMessage, err := m.persistence.MessageByID(command.Id)
if err != nil {
return err
}
if oldMessage == nil {
return errors.New("message not found")
}
if oldMessage.LocalChatID != messageState.CurrentMessageState.Contact.ID {
return errors.New("From must match")
}
if oldMessage.OutgoingStatus == "" {
return errors.New("Initial message must originate from us")
}
2020-09-01 13:27:01 +00:00
if oldMessage.CommandParameters.CommandState != common.CommandStateRequestTransaction {
return errors.New("Wrong state for command")
}
oldMessage.Clock = command.Clock
oldMessage.Timestamp = messageState.CurrentMessageState.WhisperTimestamp
oldMessage.Text = transactionRequestDeclinedMessage
oldMessage.Seen = false
2020-09-01 13:27:01 +00:00
oldMessage.CommandParameters.CommandState = common.CommandStateRequestTransactionDeclined
oldMessage.ChatId = command.GetChatId()
// Hide previous message
err = m.persistence.HideMessage(command.Id)
if err != nil {
return err
}
oldMessage.Replace = command.Id
return m.handleCommandMessage(messageState, oldMessage)
}
2021-06-08 06:07:45 +00:00
func (m *Messenger) matchChatEntity(chatEntity common.ChatEntity) (*Chat, error) {
if chatEntity.GetSigPubKey() == nil {
m.logger.Error("public key can't be empty")
return nil, errors.New("received a chatEntity with empty public key")
}
switch {
case chatEntity.GetMessageType() == protobuf.MessageType_PUBLIC_GROUP:
// For public messages, all outgoing and incoming messages have the same chatID
// equal to a public chat name.
chatID := chatEntity.GetChatId()
2021-06-08 06:07:45 +00:00
chat, ok := m.allChats.Load(chatID)
2021-03-29 15:41:30 +00:00
if !ok {
return nil, errors.New("received a public chatEntity from non-existing chat")
}
2021-09-21 10:14:54 +00:00
if !chat.Public() && !chat.ProfileUpdates() && !chat.Timeline() {
2021-09-08 11:10:53 +00:00
return nil, ErrMessageForWrongChatType
}
return chat, nil
case chatEntity.GetMessageType() == protobuf.MessageType_ONE_TO_ONE && common.IsPubKeyEqual(chatEntity.GetSigPubKey(), &m.identity.PublicKey):
// It's a private message coming from us so we rely on Message.ChatID
// If chat does not exist, it should be created to support multidevice synchronization.
chatID := chatEntity.GetChatId()
2021-06-08 06:07:45 +00:00
chat, ok := m.allChats.Load(chatID)
2021-03-29 15:41:30 +00:00
if !ok {
if len(chatID) != PubKeyStringLength {
return nil, errors.New("invalid pubkey length")
}
bytePubKey, err := hex.DecodeString(chatID[2:])
if err != nil {
return nil, errors.Wrap(err, "failed to decode hex chatID")
}
pubKey, err := crypto.UnmarshalPubkey(bytePubKey)
if err != nil {
return nil, errors.Wrap(err, "failed to decode pubkey")
}
2021-06-08 06:07:45 +00:00
chat = CreateOneToOneChat(chatID[:8], pubKey, m.getTimesource())
}
// if we are the sender, the chat must be active
chat.Active = true
return chat, nil
case chatEntity.GetMessageType() == protobuf.MessageType_ONE_TO_ONE:
// It's an incoming private chatEntity. ChatID is calculated from the signature.
// If a chat does not exist, a new one is created and saved.
chatID := contactIDFromPublicKey(chatEntity.GetSigPubKey())
2021-06-08 06:07:45 +00:00
chat, ok := m.allChats.Load(chatID)
2021-03-29 15:41:30 +00:00
if !ok {
// TODO: this should be a three-word name used in the mobile client
2021-06-08 06:07:45 +00:00
chat = CreateOneToOneChat(chatID[:8], chatEntity.GetSigPubKey(), m.getTimesource())
chat.Active = false
}
// We set the chat as inactive and will create a notification
// if it's not coming from a contact
2021-06-08 06:07:45 +00:00
contact, ok := m.allContacts.Load(chatID)
2021-10-01 14:50:16 +00:00
chat.Active = chat.Active || (ok && contact.Added)
return chat, nil
case chatEntity.GetMessageType() == protobuf.MessageType_COMMUNITY_CHAT:
chatID := chatEntity.GetChatId()
2021-06-08 06:07:45 +00:00
chat, ok := m.allChats.Load(chatID)
2021-03-29 15:41:30 +00:00
if !ok {
return nil, errors.New("received community chat chatEntity for non-existing chat")
}
if chat.CommunityID == "" || chat.ChatType != ChatTypeCommunityChat {
return nil, errors.New("not an community chat")
}
2020-12-22 16:20:12 +00:00
var emojiReaction bool
// We allow emoji reactions from anyone
switch chatEntity.(type) {
case *EmojiReaction:
emojiReaction = true
}
canPost, err := m.communitiesManager.CanPost(chatEntity.GetSigPubKey(), chat.CommunityID, chat.CommunityChatID(), chatEntity.GetGrant())
if err != nil {
return nil, err
}
2020-12-22 16:20:12 +00:00
if !emojiReaction && !canPost {
return nil, errors.New("user can't post")
}
return chat, nil
case chatEntity.GetMessageType() == protobuf.MessageType_PRIVATE_GROUP:
// In the case of a group chatEntity, ChatID is the same for all messages belonging to a group.
// It needs to be verified if the signature public key belongs to the chat.
chatID := chatEntity.GetChatId()
2021-06-08 06:07:45 +00:00
chat, ok := m.allChats.Load(chatID)
2021-03-29 15:41:30 +00:00
if !ok {
return nil, errors.New("received group chat chatEntity for non-existing chat")
}
theirKeyHex := contactIDFromPublicKey(chatEntity.GetSigPubKey())
myKeyHex := contactIDFromPublicKey(&m.identity.PublicKey)
var theyJoined bool
var iJoined bool
for _, member := range chat.Members {
if member.ID == theirKeyHex && member.Joined {
theyJoined = true
}
}
for _, member := range chat.Members {
if member.ID == myKeyHex && member.Joined {
iJoined = true
}
}
if theyJoined && iJoined {
return chat, nil
}
return nil, errors.New("did not find a matching group chat")
default:
return nil, errors.New("can not match a chat because there is no valid case")
}
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) messageExists(messageID string, existingMessagesMap map[string]bool) (bool, error) {
if _, ok := existingMessagesMap[messageID]; ok {
return true, nil
}
existingMessagesMap[messageID] = true
// Check against the database, this is probably a bit slow for
// each message, but for now might do, we'll make it faster later
existingMessage, err := m.persistence.MessageByID(messageID)
if err != nil && err != common.ErrRecordNotFound {
return false, err
}
if existingMessage != nil {
return true, nil
2019-10-14 14:10:48 +00:00
}
return false, nil
2019-10-14 14:10:48 +00:00
}
2020-07-24 13:47:58 +00:00
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleEmojiReaction(state *ReceivedMessageState, pbEmojiR protobuf.EmojiReaction) error {
2020-07-24 13:47:58 +00:00
logger := m.logger.With(zap.String("site", "HandleEmojiReaction"))
if err := ValidateReceivedEmojiReaction(&pbEmojiR, state.Timesource.GetCurrentTime()); err != nil {
logger.Error("invalid emoji reaction", zap.Error(err))
return err
}
from := state.CurrentMessageState.Contact.ID
emojiReaction := &EmojiReaction{
EmojiReaction: pbEmojiR,
From: from,
SigPubKey: state.CurrentMessageState.PublicKey,
}
existingEmoji, err := m.persistence.EmojiReactionByID(emojiReaction.ID())
if err != common.ErrRecordNotFound && err != nil {
return err
}
if existingEmoji != nil && existingEmoji.Clock >= pbEmojiR.Clock {
// this is not a valid emoji, ignoring
return nil
}
2021-06-08 06:07:45 +00:00
chat, err := m.matchChatEntity(emojiReaction)
if err != nil {
return err // matchChatEntity returns a descriptive error message
2020-07-24 13:47:58 +00:00
}
// Set local chat id
emojiReaction.LocalChatID = chat.ID
logger.Debug("Handling emoji reaction")
2020-07-24 13:47:58 +00:00
if chat.LastClockValue < pbEmojiR.Clock {
chat.LastClockValue = pbEmojiR.Clock
2020-07-24 13:47:58 +00:00
}
2021-01-11 10:32:51 +00:00
state.Response.AddChat(chat)
2021-03-29 15:41:30 +00:00
// TODO(samyoul) remove storing of an updated reference pointer?
state.AllChats.Store(chat.ID, chat)
2020-07-24 13:47:58 +00:00
// save emoji reaction
err = m.persistence.SaveEmojiReaction(emojiReaction)
if err != nil {
return err
}
state.EmojiReactions[emojiReaction.ID()] = emojiReaction
return nil
}
2020-08-07 13:49:37 +00:00
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleGroupChatInvitation(state *ReceivedMessageState, pbGHInvitations protobuf.GroupChatInvitation) error {
2021-06-08 06:07:45 +00:00
allowed, err := m.isMessageAllowedFrom(state.CurrentMessageState.Contact.ID, nil)
if err != nil {
return err
}
if !allowed {
return ErrMessageNotAllowed
}
2020-08-07 13:49:37 +00:00
logger := m.logger.With(zap.String("site", "HandleGroupChatInvitation"))
if err := ValidateReceivedGroupChatInvitation(&pbGHInvitations); err != nil {
logger.Error("invalid group chat invitation", zap.Error(err))
return err
}
groupChatInvitation := &GroupChatInvitation{
GroupChatInvitation: pbGHInvitations,
SigPubKey: state.CurrentMessageState.PublicKey,
}
//From is the PK of author of invitation request
if groupChatInvitation.State == protobuf.GroupChatInvitation_REJECTED {
//rejected so From is the current user who received this rejection
groupChatInvitation.From = types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
} else {
//invitation request, so From is the author of message
groupChatInvitation.From = state.CurrentMessageState.Contact.ID
}
existingInvitation, err := m.persistence.InvitationByID(groupChatInvitation.ID())
if err != common.ErrRecordNotFound && err != nil {
2020-08-07 13:49:37 +00:00
return err
}
if existingInvitation != nil && existingInvitation.Clock >= pbGHInvitations.Clock {
// this is not a valid invitation, ignoring
return nil
}
// save invitation
err = m.persistence.SaveInvitation(groupChatInvitation)
if err != nil {
return err
}
state.GroupChatInvitations[groupChatInvitation.ID()] = groupChatInvitation
return nil
}
// HandleChatIdentity handles an incoming protobuf.ChatIdentity
// extracts contact information stored in the protobuf and adds it to the user's contact for update.
2021-06-07 12:38:13 +00:00
func (m *Messenger) HandleChatIdentity(state *ReceivedMessageState, ci protobuf.ChatIdentity) error {
2021-02-17 23:14:48 +00:00
s, err := m.settings.GetSettings()
if err != nil {
return err
}
2022-02-17 15:13:10 +00:00
contact := state.CurrentMessageState.Contact
if err = ValidateDisplayName(&ci.DisplayName); err != nil {
return err
}
if contact.DisplayName != ci.DisplayName && len(ci.DisplayName) != 0 {
contact.DisplayName = ci.DisplayName
state.ModifiedContacts.Store(contact.ID, true)
state.AllContacts.Store(contact.ID, contact)
}
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
viewFromContacts := s.ProfilePicturesVisibility == settings.ProfilePicturesVisibilityContactsOnly
viewFromNoOne := s.ProfilePicturesVisibility == settings.ProfilePicturesVisibilityNone
2021-02-17 23:14:48 +00:00
m.logger.Debug("settings found",
zap.Bool("viewFromContacts", viewFromContacts),
zap.Bool("viewFromNoOne", viewFromNoOne),
)
// If we don't want to view profile images from anyone, don't process identity images.
// We don't want to store the profile images of other users, even if we don't display images.
2021-10-12 11:18:15 +00:00
inOurContacts, ok := m.allContacts.Load(state.CurrentMessageState.Contact.ID)
isContact := ok && inOurContacts.Added
if viewFromNoOne && !isContact {
2021-02-17 23:14:48 +00:00
return nil
}
// If there are no images attached to a ChatIdentity, check if message is allowed
// Or if there are images and visibility is set to from contacts only, check if message is allowed
// otherwise process the images without checking if the message is allowed
if len(ci.Images) == 0 || (len(ci.Images) > 0 && (viewFromContacts)) {
allowed, err := m.isMessageAllowedFrom(state.CurrentMessageState.Contact.ID, nil)
if err != nil {
return err
}
if !allowed {
return ErrMessageNotAllowed
}
}
2021-02-17 23:14:48 +00:00
err = DecryptIdentityImagesWithIdentityPrivateKey(ci.Images, m.identity, state.CurrentMessageState.PublicKey)
if err != nil {
return err
}
// Remove any images still encrypted after the decryption process
for name, image := range ci.Images {
if image.Encrypted {
delete(ci.Images, name)
}
}
newImages, err := m.persistence.SaveContactChatIdentity(contact.ID, &ci)
if err != nil {
return err
}
if newImages {
for imageType, image := range ci.Images {
if contact.Images == nil {
contact.Images = make(map[string]images.IdentityImage)
}
contact.Images[imageType] = images.IdentityImage{Name: imageType, Payload: image.Payload}
2020-12-02 09:31:48 +00:00
}
2021-03-29 15:41:30 +00:00
state.ModifiedContacts.Store(contact.ID, true)
state.AllContacts.Store(contact.ID, contact)
}
return nil
}
Anon Metrics Broadcast (#2198) * Protobufs and adapters * Added basic anon metric service and config init * Added fibonacci interval incrementer * Added basic Client.Start func and integrated interval incrementer * Added new processed field to app metrics table * Added id column to app metrics table * Added migration clean up * Added appmetrics GetUnprocessed and SetToProcessedByIDs and tests There was a wierd bug where metrics in the db that did not explicitly insert a value would be NULL, so could not be found by . In addition I've added a new primary id field to the app_metrics table so that updates could be done against very specific metric rows. * Updated adaptors and db to handle proto_id I need a way to distinguish individual metric items from each other so that I can ignore the ones that have been seen before. * Moved incrementer into dedicated file * Resolve incrementer test fail * Finalised the main loop functionality * Implemented delete loop framework * Updated adaptors file name * Added delete loop delay and quit, and tweak on RawMessage gen * Completed delete loop logic * Added DBLock to prevent deletion during mainLoop * Added postgres DB connection, integrated into anonmetrics.Server * Removed proto_id from SQL migration and model * Integrated postgres with Server and updated adaptors * Function name update * Added sample config files for client and server * Fixes and testing for low level e2e * make generate * Fix lint * Fix for receiving an anonMetricBatch not in server mode * Postgres test fixes * Tidy up, make vendor and make generate * delinting * Fixing database tests * Attempted fix of does: cannot open `does' (No such file or directory) not: cannot open `not' (No such file or directory) exist: cannot open `exist' (No such file or directory) error on sql resource loas * Moved all anon metric postgres migration logic and sources into a the protocol/anonmetrics package or sub packages. I don't know if this will fix the does: cannot open `does' (No such file or directory) not: cannot open `not' (No such file or directory) exist: cannot open `exist' (No such file or directory) error that happens in Jenkins but this could work * Lint for the lint god * Why doesn't the linter list all its problems at once? * test tweaks * Fix for wakuV2 change * DB reset change * Fix for postgres db migrations fails * More robust implementation of postgres test setup and teardown * Added block for anon metrics functionality * Version Bump to 0.84.0 * Added test to check anon metrics broadcast is deactivated * Protobufs and adapters * Added basic anon metric service and config init * Added new processed field to app metrics table * Added id column to app metrics table * Added migration clean up * Added appmetrics GetUnprocessed and SetToProcessedByIDs and tests There was a wierd bug where metrics in the db that did not explicitly insert a value would be NULL, so could not be found by . In addition I've added a new primary id field to the app_metrics table so that updates could be done against very specific metric rows. * Updated adaptors and db to handle proto_id I need a way to distinguish individual metric items from each other so that I can ignore the ones that have been seen before. * Added postgres DB connection, integrated into anonmetrics.Server * Removed proto_id from SQL migration and model * Integrated postgres with Server and updated adaptors * Added sample config files for client and server * Fix lint * Fix for receiving an anonMetricBatch not in server mode * Postgres test fixes * Tidy up, make vendor and make generate * Moved all anon metric postgres migration logic and sources into a the protocol/anonmetrics package or sub packages. I don't know if this will fix the does: cannot open `does' (No such file or directory) not: cannot open `not' (No such file or directory) exist: cannot open `exist' (No such file or directory) error that happens in Jenkins but this could work
2021-09-01 12:02:18 +00:00
func (m *Messenger) HandleAnonymousMetricBatch(amb protobuf.AnonymousMetricBatch) error {
// TODO
return nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) checkForEdits(message *common.Message) error {
2021-06-07 08:31:27 +00:00
// Check for any pending edit
// If any pending edits are available and valid, apply them
2021-06-08 06:07:45 +00:00
edits, err := m.persistence.GetEdits(message.ID, message.From)
if err != nil {
return err
}
if len(edits) == 0 {
return nil
}
// Apply the first edit that is valid
for _, e := range edits {
if e.Clock >= message.Clock {
// Update message and return it
err := m.applyEditMessage(&e.EditMessage, message)
if err != nil {
return err
}
return nil
}
}
2021-06-07 08:31:27 +00:00
return nil
}
func (m *Messenger) checkForDeletes(message *common.Message) error {
// Check for any pending deletes
// If any pending deletes are available and valid, apply them
messageDeletes, err := m.persistence.GetDeletes(message.ID, message.From)
if err != nil {
return err
}
if len(messageDeletes) == 0 {
return nil
}
return m.applyDeleteMessage(messageDeletes, message)
}
2021-06-08 06:07:45 +00:00
func (m *Messenger) isMessageAllowedFrom(publicKey string, chat *Chat) (bool, error) {
onlyFromContacts, err := m.settings.GetMessagesFromContactsOnly()
if err != nil {
return false, err
}
if !onlyFromContacts {
return true, nil
}
// if it's from us, it's allowed
if contactIDFromPublicKey(&m.identity.PublicKey) == publicKey {
return true, nil
}
// If the chat is active, we allow it
if chat != nil && chat.Active {
return true, nil
}
// If the chat is public, we allow it
if chat != nil && chat.Public() {
return true, nil
}
2021-06-08 06:07:45 +00:00
contact, ok := m.allContacts.Load(publicKey)
if !ok {
// If it's not in contacts, we don't allow it
return false, nil
}
// Otherwise we check if we added it
2021-10-01 14:50:16 +00:00
return contact.Added, nil
}
2021-06-07 12:38:13 +00:00
func (m *Messenger) updateUnviewedCounts(chat *Chat, mentioned bool) {
chat.UnviewedMessagesCount++
if mentioned {
chat.UnviewedMentionsCount++
}
}