github.com/status-im/status-go@v1.1.0/protocol/messenger_contacts.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "crypto/ecdsa" 6 "errors" 7 "fmt" 8 9 "github.com/golang/protobuf/proto" 10 "go.uber.org/zap" 11 12 "github.com/ethereum/go-ethereum/log" 13 14 "github.com/status-im/status-go/deprecation" 15 "github.com/status-im/status-go/eth-node/crypto" 16 "github.com/status-im/status-go/eth-node/types" 17 multiaccountscommon "github.com/status-im/status-go/multiaccounts/common" 18 "github.com/status-im/status-go/protocol/common" 19 "github.com/status-im/status-go/protocol/protobuf" 20 "github.com/status-im/status-go/protocol/requests" 21 "github.com/status-im/status-go/protocol/transport" 22 ) 23 24 const outgoingMutualStateEventSentDefaultText = "You sent a contact request to @%s" 25 const outgoingMutualStateEventAcceptedDefaultText = "You accepted @%s's contact request" 26 const outgoingMutualStateEventRemovedDefaultText = "You removed @%s as a contact" 27 const incomingMutualStateEventSentDefaultText = "@%s sent you a contact request" 28 const incomingMutualStateEventAcceptedDefaultText = "@%s accepted your contact request" 29 const incomingMutualStateEventRemovedDefaultText = "@%s removed you as a contact" 30 31 var ErrGetLatestContactRequestForContactInvalidID = errors.New("get-latest-contact-request-for-contact: invalid id") 32 33 type SelfContactChangeEvent struct { 34 DisplayNameChanged bool 35 PreferredNameChanged bool 36 BioChanged bool 37 SocialLinksChanged bool 38 ImagesChanged bool 39 } 40 41 func (m *Messenger) prepareMutualStateUpdateMessage(contactID string, updateType MutualStateUpdateType, clock uint64, timestamp uint64, outgoing bool) (*common.Message, error) { 42 var text string 43 var to string 44 var from string 45 var contentType protobuf.ChatMessage_ContentType 46 if outgoing { 47 to = contactID 48 from = m.myHexIdentity() 49 50 switch updateType { 51 case MutualStateUpdateTypeSent: 52 text = fmt.Sprintf(outgoingMutualStateEventSentDefaultText, contactID) 53 contentType = protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_SENT 54 case MutualStateUpdateTypeAdded: 55 text = fmt.Sprintf(outgoingMutualStateEventAcceptedDefaultText, contactID) 56 contentType = protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_ACCEPTED 57 case MutualStateUpdateTypeRemoved: 58 text = fmt.Sprintf(outgoingMutualStateEventRemovedDefaultText, contactID) 59 contentType = protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_REMOVED 60 default: 61 return nil, fmt.Errorf("unhandled outgoing MutualStateUpdateType = %d", updateType) 62 } 63 } else { 64 to = m.myHexIdentity() 65 from = contactID 66 67 switch updateType { 68 case MutualStateUpdateTypeSent: 69 text = fmt.Sprintf(incomingMutualStateEventSentDefaultText, contactID) 70 contentType = protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_SENT 71 case MutualStateUpdateTypeAdded: 72 text = fmt.Sprintf(incomingMutualStateEventAcceptedDefaultText, contactID) 73 contentType = protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_ACCEPTED 74 case MutualStateUpdateTypeRemoved: 75 text = fmt.Sprintf(incomingMutualStateEventRemovedDefaultText, contactID) 76 contentType = protobuf.ChatMessage_SYSTEM_MESSAGE_MUTUAL_EVENT_REMOVED 77 default: 78 return nil, fmt.Errorf("unhandled incoming MutualStateUpdateType = %d", updateType) 79 } 80 } 81 82 message := &common.Message{ 83 ChatMessage: &protobuf.ChatMessage{ 84 ChatId: contactID, 85 Text: text, 86 MessageType: protobuf.MessageType_ONE_TO_ONE, 87 ContentType: contentType, 88 Clock: clock, 89 Timestamp: timestamp, 90 }, 91 From: from, 92 WhisperTimestamp: timestamp, 93 LocalChatID: contactID, 94 Seen: true, 95 ID: types.EncodeHex(crypto.Keccak256([]byte(fmt.Sprintf("%s%s%d%d", from, to, updateType, clock)))), 96 } 97 98 return message, nil 99 } 100 101 func (m *Messenger) acceptContactRequest(ctx context.Context, requestID string, fromSyncing bool) (*MessengerResponse, error) { 102 contactRequest, err := m.persistence.MessageByID(requestID) 103 if err != nil { 104 m.logger.Error("could not find contact request message", zap.Error(err)) 105 return nil, err 106 } 107 108 m.logger.Info("acceptContactRequest") 109 110 var ensName, nickname, displayName string 111 customizationColor := multiaccountscommon.IDToColorFallbackToBlue(contactRequest.CustomizationColor) 112 113 if contact, ok := m.allContacts.Load(contactRequest.From); ok { 114 ensName = contact.EnsName 115 nickname = contact.LocalNickname 116 displayName = contact.DisplayName 117 customizationColor = contact.CustomizationColor 118 } 119 120 response, err := m.addContact(ctx, contactRequest.From, ensName, nickname, displayName, customizationColor, contactRequest.ID, "", fromSyncing, false, false) 121 if err != nil { 122 return nil, err 123 } 124 125 // Force activate chat 126 chat, ok := m.allChats.Load(contactRequest.From) 127 if !ok { 128 publicKey, err := common.HexToPubkey(contactRequest.From) 129 if err != nil { 130 return nil, err 131 } 132 133 chat = OneToOneFromPublicKey(publicKey, m.getTimesource()) 134 } 135 136 chat.Active = true 137 if err := m.saveChat(chat); err != nil { 138 return nil, err 139 } 140 response.AddChat(chat) 141 142 return response, nil 143 } 144 145 func (m *Messenger) AcceptContactRequest(ctx context.Context, request *requests.AcceptContactRequest) (*MessengerResponse, error) { 146 err := request.Validate() 147 if err != nil { 148 return nil, err 149 } 150 151 response, err := m.acceptContactRequest(ctx, request.ID.String(), false) 152 if err != nil { 153 return nil, err 154 } 155 156 err = m.syncContactRequestDecision(ctx, request.ID.String(), "", true, m.dispatchMessage) 157 if err != nil { 158 return nil, err 159 } 160 161 return response, nil 162 } 163 164 func (m *Messenger) declineContactRequest(requestID, contactID string, fromSyncing bool) (*MessengerResponse, error) { 165 m.logger.Info("declineContactRequest") 166 167 contactRequest, err := m.persistence.MessageByID(requestID) 168 if err == common.ErrRecordNotFound && fromSyncing { 169 // original requestID(Message ID) is useless since we don't sync UserMessage in this case 170 requestID = defaultContactRequestID(contactID) 171 contactRequest, err = m.persistence.MessageByID(requestID) 172 } 173 if err != nil { 174 return nil, err 175 } 176 177 response := &MessengerResponse{} 178 var contact *Contact 179 if contactRequest != nil { 180 contact, err = m.BuildContact(&requests.BuildContact{PublicKey: contactRequest.From}) 181 if err != nil { 182 return nil, err 183 } 184 contactRequest.ContactRequestState = common.ContactRequestStateDismissed 185 err = m.persistence.SetContactRequestState(contactRequest.ID, contactRequest.ContactRequestState) 186 if err != nil { 187 return nil, err 188 } 189 response.AddMessage(contactRequest) 190 } 191 192 if !fromSyncing { 193 _, clock, err := m.getOneToOneAndNextClock(contact) 194 if err != nil { 195 return nil, err 196 } 197 198 contact.DismissContactRequest(clock) 199 err = m.persistence.SaveContact(contact, nil) 200 if err != nil { 201 return nil, err 202 } 203 204 response.AddContact(contact) 205 } 206 207 // update notification with the correct status 208 notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(requestID)) 209 if err != nil { 210 return nil, err 211 } 212 if notification != nil { 213 notification.Name = contact.PrimaryName() 214 notification.Message = contactRequest 215 notification.Read = true 216 notification.Dismissed = true 217 notification.UpdatedAt = m.GetCurrentTimeInMillis() 218 219 err = m.addActivityCenterNotification(response, notification, m.syncActivityCenterDismissedByIDs) 220 if err != nil { 221 m.logger.Error("failed to save notification", zap.Error(err)) 222 return nil, err 223 } 224 } 225 return response, nil 226 } 227 228 func (m *Messenger) DeclineContactRequest(ctx context.Context, request *requests.DeclineContactRequest) (*MessengerResponse, error) { 229 err := request.Validate() 230 if err != nil { 231 return nil, err 232 } 233 234 response, err := m.declineContactRequest(request.ID.String(), "", false) 235 if err != nil { 236 return nil, err 237 } 238 239 err = m.syncContactRequestDecision(ctx, request.ID.String(), "", false, m.dispatchMessage) 240 if err != nil { 241 return nil, err 242 } 243 244 return response, nil 245 } 246 247 func (m *Messenger) SendContactRequest(ctx context.Context, request *requests.SendContactRequest) (*MessengerResponse, error) { 248 err := request.Validate() 249 if err != nil { 250 return nil, err 251 } 252 253 chatID, err := request.HexID() 254 if err != nil { 255 return nil, err 256 } 257 258 var ensName, nickname, displayName string 259 customizationColor := multiaccountscommon.CustomizationColorBlue 260 261 if contact, ok := m.allContacts.Load(chatID); ok { 262 ensName = contact.EnsName 263 nickname = contact.LocalNickname 264 displayName = contact.DisplayName 265 customizationColor = contact.CustomizationColor 266 } 267 268 return m.addContact( 269 ctx, 270 chatID, 271 ensName, 272 nickname, 273 displayName, 274 customizationColor, 275 "", 276 request.Message, 277 false, 278 false, 279 true, 280 ) 281 } 282 283 func (m *Messenger) updateAcceptedContactRequest(response *MessengerResponse, contactRequestID, contactID string, fromSyncing bool) (*MessengerResponse, error) { 284 m.logger.Debug("updateAcceptedContactRequest", zap.String("contactRequestID", contactRequestID), zap.String("contactID", contactID), zap.Bool("fromSyncing", fromSyncing)) 285 286 contactRequest, err := m.persistence.MessageByID(contactRequestID) 287 if err == common.ErrRecordNotFound && fromSyncing { 288 // original requestID(Message ID) is useless since we don't sync UserMessage in this case 289 contactRequestID = defaultContactRequestID(contactID) 290 contactRequest, err = m.persistence.MessageByID(contactRequestID) 291 } 292 if err != nil { 293 m.logger.Error("contact request not found", zap.String("contactRequestID", contactRequestID), zap.Error(err)) 294 return nil, err 295 } 296 297 contactRequest.ContactRequestState = common.ContactRequestStateAccepted 298 299 err = m.persistence.SetContactRequestState(contactRequest.ID, contactRequest.ContactRequestState) 300 if err != nil { 301 return nil, err 302 } 303 304 contact, ok := m.allContacts.Load(contactRequest.From) 305 if !ok { 306 m.logger.Error("failed to update contact request: contact not found", zap.String("contact id", contactRequest.From)) 307 return nil, errors.New("failed to update contact request: contact not found") 308 } 309 310 chat, ok := m.allChats.Load(contact.ID) 311 if !ok { 312 return nil, errors.New("no chat found for accepted contact request") 313 } 314 315 notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(contactRequest.ID)) 316 if err != nil { 317 return nil, err 318 } 319 320 clock, _ := chat.NextClockAndTimestamp(m.transport) 321 contact.AcceptContactRequest(clock) 322 323 if !fromSyncing { 324 acceptContactRequest := &protobuf.AcceptContactRequest{ 325 Id: contactRequest.ID, 326 Clock: clock, 327 } 328 encodedMessage, err := proto.Marshal(acceptContactRequest) 329 if err != nil { 330 return nil, err 331 } 332 _, err = m.dispatchMessage(context.Background(), common.RawMessage{ 333 LocalChatID: contactRequest.From, 334 Payload: encodedMessage, 335 MessageType: protobuf.ApplicationMetadataMessage_ACCEPT_CONTACT_REQUEST, 336 ResendType: common.ResendTypeDataSync, 337 }) 338 if err != nil { 339 return nil, err 340 } 341 342 // Dispatch profile message to add a contact to the encrypted profile part 343 err = m.DispatchProfileShowcase() 344 if err != nil { 345 return nil, err 346 } 347 } 348 349 if response == nil { 350 response = &MessengerResponse{} 351 } 352 353 if notification != nil { 354 notification.Name = contact.PrimaryName() 355 notification.Message = contactRequest 356 notification.Read = true 357 notification.Accepted = true 358 notification.UpdatedAt = m.GetCurrentTimeInMillis() 359 360 err = m.addActivityCenterNotification(response, notification, nil) 361 if err != nil { 362 m.logger.Error("failed to save notification", zap.Error(err)) 363 return nil, err 364 } 365 } 366 367 response.AddMessage(contactRequest) 368 response.AddContact(contact) 369 370 // Add mutual state update message for incoming contact request 371 clock, timestamp := chat.NextClockAndTimestamp(m.transport) 372 updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, MutualStateUpdateTypeAdded, clock, timestamp, true) 373 if err != nil { 374 return nil, err 375 } 376 377 err = m.prepareMessage(updateMessage, m.httpServer) 378 if err != nil { 379 return nil, err 380 } 381 err = m.persistence.SaveMessages([]*common.Message{updateMessage}) 382 if err != nil { 383 return nil, err 384 } 385 response.AddMessage(updateMessage) 386 response.AddChat(chat) 387 388 return response, nil 389 } 390 391 func (m *Messenger) addContact(ctx context.Context, 392 pubKey, ensName, nickname, displayName string, 393 customizationColor multiaccountscommon.CustomizationColor, 394 contactRequestID, contactRequestText string, 395 fromSyncing, sendContactUpdate, createOutgoingContactRequestNotification bool) (*MessengerResponse, error) { 396 contact, err := m.BuildContact(&requests.BuildContact{PublicKey: pubKey}) 397 if err != nil { 398 return nil, err 399 } 400 401 response := &MessengerResponse{} 402 403 chat, clock, err := m.getOneToOneAndNextClock(contact) 404 if err != nil { 405 return nil, err 406 } 407 408 if ensName != "" { 409 err := m.ensVerifier.ENSVerified(pubKey, ensName, clock) 410 if err != nil { 411 return nil, err 412 } 413 } 414 if err := m.addENSNameToContact(contact); err != nil { 415 return nil, err 416 } 417 418 if len(nickname) != 0 { 419 contact.LocalNickname = nickname 420 } 421 422 if len(displayName) != 0 { 423 contact.DisplayName = displayName 424 } 425 426 contact.CustomizationColor = customizationColor 427 428 contact.LastUpdatedLocally = clock 429 contact.ContactRequestSent(clock) 430 431 if !fromSyncing { 432 // We sync the contact with the other devices 433 err := m.syncContact(context.Background(), contact, m.dispatchMessage) 434 if err != nil { 435 return nil, err 436 } 437 } 438 439 err = m.persistence.SaveContact(contact, nil) 440 if err != nil { 441 return nil, err 442 } 443 444 // TODO(samyoul) remove storing of an updated reference pointer? 445 m.allContacts.Store(contact.ID, contact) 446 447 // And we re-register for push notications 448 err = m.reregisterForPushNotifications() 449 if err != nil { 450 return nil, err 451 } 452 453 // Reset last published time for ChatIdentity so new contact can receive data 454 err = m.resetLastPublishedTimeForChatIdentity() 455 if err != nil { 456 return nil, err 457 } 458 459 // Profile chats are deprecated. 460 // Code below can be removed after some reasonable time. 461 462 //Create the corresponding chat 463 var profileChat *Chat 464 if !deprecation.ChatProfileDeprecated { 465 profileChat = m.buildProfileChat(contact.ID) 466 467 _, err = m.Join(profileChat) 468 if err != nil { 469 return nil, err 470 } 471 472 if err := m.saveChat(profileChat); err != nil { 473 return nil, err 474 } 475 } 476 477 publicKey, err := contact.PublicKey() 478 if err != nil { 479 return nil, err 480 } 481 482 // Fetch contact code 483 _, err = m.scheduleSyncFiltersForContact(publicKey) 484 if err != nil { 485 return nil, err 486 } 487 488 if sendContactUpdate { 489 // Get ENS name of a current user 490 ensName, err = m.settings.ENSName() 491 if err != nil { 492 return nil, err 493 } 494 495 // Get display name of a current user 496 displayName, err = m.settings.DisplayName() 497 if err != nil { 498 return nil, err 499 } 500 response, err = m.sendContactUpdate(context.Background(), pubKey, displayName, ensName, "", m.account.GetCustomizationColor(), m.dispatchMessage) 501 if err != nil { 502 return nil, err 503 } 504 } 505 506 if len(contactRequestID) != 0 { 507 updatedResponse, err := m.updateAcceptedContactRequest(response, contactRequestID, "", false) 508 if err != nil { 509 return nil, err 510 } 511 err = response.Merge(updatedResponse) 512 if err != nil { 513 return nil, err 514 } 515 } 516 517 // Sends a standalone ChatIdentity message 518 err = m.handleStandaloneChatIdentity(chat) 519 if err != nil { 520 return nil, err 521 } 522 523 // Profile chats are deprecated. 524 // Code below can be removed after some reasonable time. 525 526 // Add chat 527 if !deprecation.ChatProfileDeprecated { 528 response.AddChat(profileChat) 529 530 _, err = m.transport.InitFilters([]transport.FiltersToInitialize{{ChatID: profileChat.ID}}, []*ecdsa.PublicKey{publicKey}) 531 if err != nil { 532 return nil, err 533 } 534 } 535 536 // Publish contact code 537 err = m.publishContactCode() 538 if err != nil { 539 return nil, err 540 } 541 542 // Add mutual state update message for outgoing contact request 543 if len(contactRequestID) == 0 { 544 clock, timestamp := chat.NextClockAndTimestamp(m.transport) 545 updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, MutualStateUpdateTypeSent, clock, timestamp, true) 546 if err != nil { 547 return nil, err 548 } 549 550 err = m.prepareMessage(updateMessage, m.httpServer) 551 if err != nil { 552 return nil, err 553 } 554 err = m.persistence.SaveMessages([]*common.Message{updateMessage}) 555 if err != nil { 556 return nil, err 557 } 558 response.AddMessage(updateMessage) 559 err = chat.UpdateFromMessage(updateMessage, m.getTimesource()) 560 if err != nil { 561 return nil, err 562 } 563 response.AddChat(chat) 564 } 565 566 // Add outgoing contact request notification 567 if createOutgoingContactRequestNotification { 568 clock, timestamp := chat.NextClockAndTimestamp(m.transport) 569 contactRequest, err := m.generateContactRequest(clock, timestamp, contact, contactRequestText, true) 570 if err != nil { 571 return nil, err 572 } 573 574 // Send contact request as a plain chat message 575 messageResponse, err := m.sendChatMessage(ctx, contactRequest) 576 if err != nil { 577 return nil, err 578 } 579 580 err = response.Merge(messageResponse) 581 if err != nil { 582 return nil, err 583 } 584 585 notification := m.generateOutgoingContactRequestNotification(contact, contactRequest) 586 err = m.addActivityCenterNotification(response, notification, nil) 587 if err != nil { 588 return nil, err 589 } 590 } 591 592 // Add contact 593 response.AddContact(contact) 594 return response, nil 595 } 596 597 func (m *Messenger) generateContactRequest(clock uint64, timestamp uint64, contact *Contact, text string, outgoing bool) (*common.Message, error) { 598 if contact == nil { 599 return nil, errors.New("contact cannot be nil") 600 } 601 602 contactRequest := common.NewMessage() 603 contactRequest.ChatId = contact.ID 604 contactRequest.WhisperTimestamp = timestamp 605 contactRequest.Seen = true 606 contactRequest.Text = text 607 if outgoing { 608 contactRequest.From = m.myHexIdentity() 609 contactRequest.CustomizationColor = m.account.GetCustomizationColorID() 610 } else { 611 contactRequest.From = contact.ID 612 contactRequest.CustomizationColor = multiaccountscommon.ColorToIDFallbackToBlue(contact.CustomizationColor) 613 } 614 contactRequest.LocalChatID = contact.ID 615 contactRequest.ContentType = protobuf.ChatMessage_CONTACT_REQUEST 616 contactRequest.Clock = clock 617 if contact.mutual() { 618 contactRequest.ContactRequestState = common.ContactRequestStateAccepted 619 } else { 620 contactRequest.ContactRequestState = common.ContactRequestStatePending 621 } 622 err := contactRequest.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey)) 623 return contactRequest, err 624 } 625 626 func (m *Messenger) generateOutgoingContactRequestNotification(contact *Contact, contactRequest *common.Message) *ActivityCenterNotification { 627 return &ActivityCenterNotification{ 628 ID: types.FromHex(contactRequest.ID), 629 Type: ActivityCenterNotificationTypeContactRequest, 630 Name: contact.PrimaryName(), 631 Author: m.myHexIdentity(), 632 Message: contactRequest, 633 Timestamp: m.getTimesource().GetCurrentTime(), 634 ChatID: contact.ID, 635 Read: contactRequest.ContactRequestState == common.ContactRequestStateAccepted || 636 contactRequest.ContactRequestState == common.ContactRequestStateDismissed || 637 contactRequest.ContactRequestState == common.ContactRequestStatePending, 638 Accepted: contactRequest.ContactRequestState == common.ContactRequestStateAccepted, 639 Dismissed: contactRequest.ContactRequestState == common.ContactRequestStateDismissed, 640 UpdatedAt: m.GetCurrentTimeInMillis(), 641 } 642 } 643 644 func (m *Messenger) AddContact(ctx context.Context, request *requests.AddContact) (*MessengerResponse, error) { 645 err := request.Validate() 646 if err != nil { 647 return nil, err 648 } 649 650 id, err := request.HexID() 651 if err != nil { 652 return nil, err 653 } 654 655 return m.addContact( 656 ctx, 657 id, 658 request.ENSName, 659 request.Nickname, 660 request.DisplayName, 661 multiaccountscommon.CustomizationColor(request.CustomizationColor), 662 "", 663 defaultContactRequestText(), 664 false, 665 true, 666 true, 667 ) 668 } 669 670 func (m *Messenger) resetLastPublishedTimeForChatIdentity() error { 671 // Reset last published time for ChatIdentity so new contact can receive data 672 contactCodeTopic := transport.ContactCodeTopic(&m.identity.PublicKey) 673 m.logger.Debug("contact state changed ResetWhenChatIdentityLastPublished") 674 return m.persistence.ResetWhenChatIdentityLastPublished(contactCodeTopic) 675 } 676 677 func (m *Messenger) removeContact(ctx context.Context, response *MessengerResponse, pubKey string, sync bool) error { 678 contact, ok := m.allContacts.Load(pubKey) 679 if !ok { 680 return ErrContactNotFound 681 } 682 683 // System message for mutual state update 684 chat, clock, err := m.getOneToOneAndNextClock(contact) 685 if err != nil { 686 return err 687 } 688 timestamp := m.getTimesource().GetCurrentTime() 689 updateMessage, err := m.prepareMutualStateUpdateMessage(contact.ID, MutualStateUpdateTypeRemoved, clock, timestamp, true) 690 if err != nil { 691 return err 692 } 693 694 err = m.prepareMessage(updateMessage, m.httpServer) 695 if err != nil { 696 return err 697 } 698 err = m.persistence.SaveMessages([]*common.Message{updateMessage}) 699 if err != nil { 700 return err 701 } 702 response.AddMessage(updateMessage) 703 err = chat.UpdateFromMessage(updateMessage, m.getTimesource()) 704 if err != nil { 705 return err 706 } 707 response.AddChat(chat) 708 709 // Next we retract a contact request 710 contact.RetractContactRequest(clock) 711 contact.LastUpdatedLocally = m.getTimesource().GetCurrentTime() 712 713 err = m.persistence.SaveContact(contact, nil) 714 if err != nil { 715 return err 716 } 717 718 if sync { 719 err = m.syncContact(context.Background(), contact, m.dispatchMessage) 720 if err != nil { 721 return err 722 } 723 } 724 725 // TODO(samyoul) remove storing of an updated reference pointer? 726 m.allContacts.Store(contact.ID, contact) 727 728 // And we re-register for push notications 729 err = m.reregisterForPushNotifications() 730 if err != nil { 731 return err 732 } 733 734 // Dispatch profile message to remove a contact from the encrypted profile part 735 err = m.DispatchProfileShowcase() 736 if err != nil { 737 return err 738 } 739 740 // Profile chats are deprecated. 741 // Code below can be removed after some reasonable time. 742 743 //Create the corresponding profile chat 744 if !deprecation.ChatProfileDeprecated { 745 profileChatID := buildProfileChatID(contact.ID) 746 _, ok = m.allChats.Load(profileChatID) 747 748 if ok { 749 chatResponse, err := m.deactivateChat(profileChatID, 0, false, true) 750 if err != nil { 751 return err 752 } 753 err = response.Merge(chatResponse) 754 if err != nil { 755 return err 756 } 757 } 758 } 759 760 response.Contacts = []*Contact{contact} 761 return nil 762 } 763 764 func (m *Messenger) RemoveContact(ctx context.Context, pubKey string) (*MessengerResponse, error) { 765 response := new(MessengerResponse) 766 767 err := m.removeContact(ctx, response, pubKey, true) 768 if err != nil { 769 return nil, err 770 } 771 772 return response, nil 773 } 774 775 func (m *Messenger) updateContactImagesURL(contact *Contact) error { 776 if m.httpServer != nil { 777 for k, v := range contact.Images { 778 publicKey, err := contact.PublicKey() 779 if err != nil { 780 return err 781 } 782 v.LocalURL = m.httpServer.MakeContactImageURL(common.PubkeyToHex(publicKey), k) 783 contact.Images[k] = v 784 } 785 } 786 return nil 787 } 788 789 func (m *Messenger) Contacts() []*Contact { 790 var contacts []*Contact 791 m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) { 792 contacts = append(contacts, contact) 793 return true 794 }) 795 return contacts 796 } 797 798 func (m *Messenger) AddedContacts() []*Contact { 799 var contacts []*Contact 800 m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) { 801 if contact.added() { 802 contacts = append(contacts, contact) 803 } 804 return true 805 }) 806 return contacts 807 } 808 809 func (m *Messenger) MutualContacts() []*Contact { 810 var contacts []*Contact 811 m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) { 812 if contact.mutual() { 813 contacts = append(contacts, contact) 814 } 815 return true 816 }) 817 return contacts 818 } 819 820 func (m *Messenger) BlockedContacts() []*Contact { 821 var contacts []*Contact 822 m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) { 823 if contact.Blocked { 824 contacts = append(contacts, contact) 825 } 826 return true 827 }) 828 return contacts 829 } 830 831 // GetContactByID returns a Contact for given pubKey, if it's known. 832 // This function automatically checks if pubKey is self identity key and returns a Contact 833 // filled with self information. 834 // pubKey is assumed to include `0x` prefix 835 func (m *Messenger) GetContactByID(pubKey string) *Contact { 836 if pubKey == m.IdentityPublicKeyString() { 837 return m.selfContact 838 } 839 contact, _ := m.allContacts.Load(pubKey) 840 return contact 841 } 842 843 func (m *Messenger) GetSelfContact() *Contact { 844 return m.selfContact 845 } 846 847 func (m *Messenger) SetContactLocalNickname(request *requests.SetContactLocalNickname) (*MessengerResponse, error) { 848 849 if err := request.Validate(); err != nil { 850 return nil, err 851 } 852 853 pubKey := request.ID.String() 854 nickname := request.Nickname 855 856 contact, err := m.BuildContact(&requests.BuildContact{PublicKey: pubKey}) 857 if err != nil { 858 return nil, err 859 } 860 861 if err := m.addENSNameToContact(contact); err != nil { 862 return nil, err 863 } 864 865 clock := m.getTimesource().GetCurrentTime() 866 contact.LocalNickname = nickname 867 contact.LastUpdatedLocally = clock 868 869 err = m.persistence.SaveContact(contact, nil) 870 if err != nil { 871 return nil, err 872 } 873 874 m.allContacts.Store(contact.ID, contact) 875 876 response := &MessengerResponse{} 877 response.Contacts = []*Contact{contact} 878 879 err = m.syncContact(context.Background(), contact, m.dispatchMessage) 880 if err != nil { 881 return nil, err 882 } 883 884 return response, nil 885 } 886 887 func (m *Messenger) blockContact(ctx context.Context, response *MessengerResponse, contactID string, isDesktopFunc bool, fromSyncing bool) error { 888 contact, err := m.BuildContact(&requests.BuildContact{PublicKey: contactID}) 889 if err != nil { 890 return err 891 } 892 893 response.AddContact(contact) 894 895 _, clock, err := m.getOneToOneAndNextClock(contact) 896 if err != nil { 897 return err 898 } 899 900 contactWasAdded := contact.added() 901 contact.Block(clock) 902 903 contact.LastUpdatedLocally = m.getTimesource().GetCurrentTime() 904 905 chats, err := m.persistence.BlockContact(contact, isDesktopFunc) 906 if err != nil { 907 return err 908 } 909 910 response.AddChats(chats) 911 912 m.allContacts.Store(contact.ID, contact) 913 for _, chat := range chats { 914 m.allChats.Store(chat.ID, chat) 915 } 916 917 if !isDesktopFunc { 918 m.allChats.Delete(contact.ID) 919 m.allChats.Delete(buildProfileChatID(contact.ID)) 920 } 921 922 if !fromSyncing { 923 if contactWasAdded { 924 err = m.sendRetractContactRequest(contact) 925 if err != nil { 926 return err 927 } 928 } 929 930 err = m.syncContact(context.Background(), contact, m.dispatchMessage) 931 if err != nil { 932 return err 933 } 934 935 // We remove anything that's related to this contact request 936 updatedAt := m.GetCurrentTimeInMillis() 937 notifications, err := m.persistence.DeleteChatContactRequestActivityCenterNotifications(contact.ID, updatedAt) 938 if err != nil { 939 return err 940 } 941 err = m.syncActivityCenterDeleted(ctx, notifications, updatedAt) 942 if err != nil { 943 m.logger.Error("BlockContact, error syncing activity center notifications as deleted", zap.Error(err)) 944 return err 945 } 946 } 947 948 // re-register for push notifications 949 err = m.reregisterForPushNotifications() 950 if err != nil { 951 return err 952 } 953 954 return nil 955 } 956 957 func (m *Messenger) BlockContact(ctx context.Context, contactID string, fromSyncing bool) (*MessengerResponse, error) { 958 response := &MessengerResponse{} 959 960 err := m.blockContact(ctx, response, contactID, false, fromSyncing) 961 if err != nil { 962 return nil, err 963 } 964 965 response, err = m.DeclineAllPendingGroupInvitesFromUser(ctx, response, contactID) 966 if err != nil { 967 return nil, err 968 } 969 970 // AC notifications are synced separately 971 // NOTE: Should we still do the local part (persistence.dismiss...) and only skip the syncing? 972 // This would make the solution more reliable even in case AC notification sync is not recevied. 973 // This should be considered separately, I'm not sure if that's safe. 974 // https://github.com/status-im/status-go/issues/3720 975 if !fromSyncing { 976 updatedAt := m.GetCurrentTimeInMillis() 977 _, err = m.DismissAllActivityCenterNotificationsFromUser(ctx, contactID, updatedAt) 978 if err != nil { 979 return nil, err 980 } 981 } 982 983 return response, nil 984 } 985 986 // The same function as the one above. 987 // Should be removed with https://github.com/status-im/status-desktop/issues/8805 988 func (m *Messenger) BlockContactDesktop(ctx context.Context, contactID string) (*MessengerResponse, error) { 989 response := &MessengerResponse{} 990 991 err := m.blockContact(ctx, response, contactID, true, false) 992 if err != nil { 993 return nil, err 994 } 995 996 response, err = m.DeclineAllPendingGroupInvitesFromUser(ctx, response, contactID) 997 if err != nil { 998 return nil, err 999 } 1000 1001 notifications, err := m.DismissAllActivityCenterNotificationsFromUser(ctx, contactID, m.GetCurrentTimeInMillis()) 1002 if err != nil { 1003 return nil, err 1004 } 1005 response.AddActivityCenterNotifications(notifications) 1006 return response, nil 1007 } 1008 1009 func (m *Messenger) UnblockContact(contactID string) (*MessengerResponse, error) { 1010 response := &MessengerResponse{} 1011 contact, ok := m.allContacts.Load(contactID) 1012 if !ok || !contact.Blocked { 1013 return response, nil 1014 } 1015 1016 _, clock, err := m.getOneToOneAndNextClock(contact) 1017 if err != nil { 1018 return nil, err 1019 } 1020 1021 contact.Unblock(clock) 1022 1023 contact.LastUpdatedLocally = m.getTimesource().GetCurrentTime() 1024 1025 err = m.persistence.SaveContact(contact, nil) 1026 if err != nil { 1027 return nil, err 1028 } 1029 1030 m.allContacts.Store(contact.ID, contact) 1031 1032 response.AddContact(contact) 1033 1034 err = m.syncContact(context.Background(), contact, m.dispatchMessage) 1035 if err != nil { 1036 return nil, err 1037 } 1038 1039 // re-register for push notifications 1040 err = m.reregisterForPushNotifications() 1041 if err != nil { 1042 return nil, err 1043 } 1044 1045 return response, nil 1046 } 1047 1048 // Send contact updates to all contacts added by us 1049 func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImage string, customizationColor multiaccountscommon.CustomizationColor) (err error) { 1050 myID := contactIDFromPublicKey(&m.identity.PublicKey) 1051 1052 displayName, err := m.settings.DisplayName() 1053 if err != nil { 1054 return err 1055 } 1056 1057 if len(customizationColor) == 0 && m.account != nil { 1058 customizationColor = m.account.GetCustomizationColor() 1059 } 1060 1061 if _, err = m.sendContactUpdate(ctx, myID, displayName, ensName, profileImage, customizationColor, m.dispatchMessage); err != nil { 1062 return err 1063 } 1064 1065 // TODO: This should not be sending paired messages, as we do it above 1066 m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) { 1067 if contact.added() { 1068 if _, err = m.sendContactUpdate(ctx, contact.ID, displayName, ensName, profileImage, customizationColor, m.dispatchMessage); err != nil { 1069 return false 1070 } 1071 } 1072 return true 1073 }) 1074 return err 1075 } 1076 1077 // NOTE: this endpoint does not add the contact, the reason being is that currently 1078 // that's left as a responsibility to the client, which will call both `SendContactUpdate` 1079 // and `SaveContact` with the correct system tag. 1080 // Ideally we have a single endpoint that does both, but probably best to bring `ENS` name 1081 // on the messenger first. 1082 1083 // SendContactUpdate sends a contact update to a user and adds the user to contacts 1084 func (m *Messenger) SendContactUpdate(ctx context.Context, chatID, ensName, profileImage string, customizationColor multiaccountscommon.CustomizationColor) (*MessengerResponse, error) { 1085 displayName, err := m.settings.DisplayName() 1086 if err != nil { 1087 return nil, err 1088 } 1089 1090 return m.sendContactUpdate(ctx, chatID, displayName, ensName, profileImage, customizationColor, m.dispatchMessage) 1091 } 1092 1093 func (m *Messenger) sendContactUpdate(ctx context.Context, 1094 chatID, displayName, ensName, profileImage string, 1095 customizationColor multiaccountscommon.CustomizationColor, 1096 rawMessageHandler RawMessageHandler) (*MessengerResponse, error) { 1097 var response MessengerResponse 1098 1099 contact, ok := m.allContacts.Load(chatID) 1100 if !ok || !contact.added() { 1101 return nil, nil 1102 } 1103 1104 chat, clock, err := m.getOneToOneAndNextClock(contact) 1105 if err != nil { 1106 return nil, err 1107 } 1108 1109 contactUpdate := &protobuf.ContactUpdate{ 1110 Clock: clock, 1111 DisplayName: displayName, 1112 EnsName: ensName, 1113 ProfileImage: profileImage, 1114 ContactRequestClock: contact.ContactRequestLocalClock, 1115 ContactRequestPropagatedState: contact.ContactRequestPropagatedState(), 1116 PublicKey: contact.ID, 1117 CustomizationColor: multiaccountscommon.ColorToIDFallbackToBlue(customizationColor), 1118 } 1119 1120 encodedMessage, err := proto.Marshal(contactUpdate) 1121 if err != nil { 1122 return nil, err 1123 } 1124 1125 rawMessage := common.RawMessage{ 1126 LocalChatID: chatID, 1127 Payload: encodedMessage, 1128 MessageType: protobuf.ApplicationMetadataMessage_CONTACT_UPDATE, 1129 ResendType: common.ResendTypeDataSync, 1130 } 1131 1132 _, err = rawMessageHandler(ctx, rawMessage) 1133 if err != nil { 1134 return nil, err 1135 } 1136 1137 response.Contacts = []*Contact{contact} 1138 response.AddChat(chat) 1139 1140 chat.LastClockValue = clock 1141 err = m.saveChat(chat) 1142 if err != nil { 1143 return nil, err 1144 } 1145 return &response, nil 1146 } 1147 1148 func (m *Messenger) addENSNameToContact(contact *Contact) error { 1149 1150 // Check if there's already a verified record 1151 ensRecord, err := m.ensVerifier.GetVerifiedRecord(contact.ID) 1152 if err != nil { 1153 return err 1154 } 1155 if ensRecord == nil { 1156 return nil 1157 } 1158 1159 contact.EnsName = ensRecord.Name 1160 contact.ENSVerified = true 1161 1162 return nil 1163 } 1164 1165 func (m *Messenger) RetractContactRequest(request *requests.RetractContactRequest) (*MessengerResponse, error) { 1166 err := request.Validate() 1167 if err != nil { 1168 return nil, err 1169 } 1170 contact, ok := m.allContacts.Load(request.ID.String()) 1171 if !ok { 1172 return nil, errors.New("contact not found") 1173 } 1174 response := &MessengerResponse{} 1175 err = m.removeContact(context.Background(), response, contact.ID, true) 1176 if err != nil { 1177 return nil, err 1178 } 1179 1180 err = m.sendRetractContactRequest(contact) 1181 if err != nil { 1182 return nil, err 1183 } 1184 1185 return response, err 1186 } 1187 1188 // Send message to remote account to remove our contact from their end. 1189 func (m *Messenger) sendRetractContactRequest(contact *Contact) error { 1190 _, clock, err := m.getOneToOneAndNextClock(contact) 1191 if err != nil { 1192 return err 1193 } 1194 retractContactRequest := &protobuf.RetractContactRequest{ 1195 Clock: clock, 1196 } 1197 1198 encodedMessage, err := proto.Marshal(retractContactRequest) 1199 if err != nil { 1200 return err 1201 } 1202 1203 _, err = m.dispatchMessage(context.Background(), common.RawMessage{ 1204 LocalChatID: contact.ID, 1205 Payload: encodedMessage, 1206 MessageType: protobuf.ApplicationMetadataMessage_RETRACT_CONTACT_REQUEST, 1207 ResendType: common.ResendTypeDataSync, 1208 }) 1209 if err != nil { 1210 return err 1211 } 1212 1213 return err 1214 } 1215 1216 func (m *Messenger) GetLatestContactRequestForContact(contactID string) (*MessengerResponse, error) { 1217 if len(contactID) == 0 { 1218 return nil, ErrGetLatestContactRequestForContactInvalidID 1219 } 1220 1221 contactRequestID, err := m.persistence.LatestPendingContactRequestIDForContact(contactID) 1222 if err != nil { 1223 return nil, err 1224 } 1225 1226 contactRequest, err := m.persistence.MessageByID(contactRequestID) 1227 if err != nil { 1228 m.logger.Error("contact request not found", zap.String("contactRequestID", contactRequestID), zap.Error(err)) 1229 return nil, err 1230 } 1231 1232 response := &MessengerResponse{} 1233 response.AddMessage(contactRequest) 1234 1235 return response, nil 1236 } 1237 1238 func (m *Messenger) AcceptLatestContactRequestForContact(ctx context.Context, request *requests.AcceptLatestContactRequestForContact) (*MessengerResponse, error) { 1239 if err := request.Validate(); err != nil { 1240 return nil, err 1241 } 1242 1243 contactRequestID, err := m.persistence.LatestPendingContactRequestIDForContact(request.ID.String()) 1244 if err != nil { 1245 return nil, err 1246 } 1247 1248 return m.AcceptContactRequest(ctx, &requests.AcceptContactRequest{ID: types.Hex2Bytes(contactRequestID)}) 1249 } 1250 1251 func (m *Messenger) DismissLatestContactRequestForContact(ctx context.Context, request *requests.DismissLatestContactRequestForContact) (*MessengerResponse, error) { 1252 if err := request.Validate(); err != nil { 1253 return nil, err 1254 } 1255 1256 contactRequestID, err := m.persistence.LatestPendingContactRequestIDForContact(request.ID.String()) 1257 if err != nil { 1258 return nil, err 1259 } 1260 1261 return m.DeclineContactRequest(ctx, &requests.DeclineContactRequest{ID: types.Hex2Bytes(contactRequestID)}) 1262 } 1263 1264 func (m *Messenger) PendingContactRequests(cursor string, limit int) ([]*common.Message, string, error) { 1265 return m.persistence.PendingContactRequests(cursor, limit) 1266 } 1267 1268 func defaultContactRequestID(contactID string) string { 1269 return "0x" + types.Bytes2Hex(append(types.Hex2Bytes(contactID), 0x20)) 1270 } 1271 1272 func defaultContactRequestText() string { 1273 return "Please add me to your contacts" 1274 } 1275 1276 func (m *Messenger) BuildContact(request *requests.BuildContact) (*Contact, error) { 1277 contact, ok := m.allContacts.Load(request.PublicKey) 1278 if !ok { 1279 var err error 1280 contact, err = buildContactFromPkString(request.PublicKey) 1281 if err != nil { 1282 return nil, err 1283 } 1284 1285 if request.ENSName != "" { 1286 contact.ENSVerified = true 1287 contact.EnsName = request.ENSName 1288 } 1289 1290 if len(contact.CustomizationColor) == 0 { 1291 contact.CustomizationColor = multiaccountscommon.CustomizationColorBlue 1292 } 1293 } 1294 1295 // Schedule sync filter to fetch information about the contact 1296 publicKey, err := contact.PublicKey() 1297 if err != nil { 1298 return nil, err 1299 } 1300 1301 _, err = m.scheduleSyncFiltersForContact(publicKey) 1302 if err != nil { 1303 return nil, err 1304 } 1305 1306 return contact, nil 1307 } 1308 1309 func (m *Messenger) scheduleSyncFiltersForContact(publicKey *ecdsa.PublicKey) (*transport.Filter, error) { 1310 filter, err := m.transport.JoinPrivate(publicKey) 1311 if err != nil { 1312 return nil, err 1313 } 1314 _, err = m.scheduleSyncFilters([]*transport.Filter{filter}) 1315 if err != nil { 1316 return filter, err 1317 } 1318 return filter, nil 1319 } 1320 1321 func (m *Messenger) FetchContact(contactID string, waitForResponse bool) (*Contact, error) { 1322 options := []StoreNodeRequestOption{ 1323 WithWaitForResponseOption(waitForResponse), 1324 } 1325 contact, _, err := m.storeNodeRequestsManager.FetchContact(contactID, options) 1326 return contact, err 1327 } 1328 1329 func (m *Messenger) SubscribeToSelfContactChanges() chan *SelfContactChangeEvent { 1330 s := make(chan *SelfContactChangeEvent, 10) 1331 m.selfContactSubscriptions = append(m.selfContactSubscriptions, s) 1332 return s 1333 } 1334 1335 func (m *Messenger) publishSelfContactSubscriptions(event *SelfContactChangeEvent) { 1336 for _, s := range m.selfContactSubscriptions { 1337 select { 1338 case s <- event: 1339 default: 1340 log.Warn("self contact subscription channel full, dropping message") 1341 } 1342 } 1343 }