github.com/status-im/status-go@v1.1.0/protocol/messenger_chats.go (about) 1 package protocol 2 3 import ( 4 "context" 5 "errors" 6 "strings" 7 8 "github.com/status-im/status-go/deprecation" 9 "github.com/status-im/status-go/protocol/common" 10 "github.com/status-im/status-go/protocol/protobuf" 11 "github.com/status-im/status-go/protocol/requests" 12 "github.com/status-im/status-go/protocol/transport" 13 ) 14 15 func (m *Messenger) getOneToOneAndNextClock(contact *Contact) (*Chat, uint64, error) { 16 chat, ok := m.allChats.Load(contact.ID) 17 if !ok { 18 publicKey, err := contact.PublicKey() 19 if err != nil { 20 return nil, 0, err 21 } 22 23 chat = OneToOneFromPublicKey(publicKey, m.getTimesource()) 24 25 // We don't want to show the chat to the user by default 26 chat.Active = false 27 28 if err := m.saveChat(chat); err != nil { 29 return nil, 0, err 30 } 31 m.allChats.Store(chat.ID, chat) 32 } 33 clock, _ := chat.NextClockAndTimestamp(m.getTimesource()) 34 35 return chat, clock, nil 36 } 37 38 func (m *Messenger) Chats() []*Chat { 39 var chats []*Chat 40 41 m.allChats.Range(func(chatID string, chat *Chat) (shouldContinue bool) { 42 chats = append(chats, chat) 43 return true 44 }) 45 46 return chats 47 } 48 49 func (m *Messenger) ChatsPreview() []*ChatPreview { 50 var chats []*ChatPreview 51 52 m.allChats.Range(func(chatID string, chat *Chat) (shouldContinue bool) { 53 if chat.Active || chat.Muted { 54 chatPreview := &ChatPreview{ 55 ID: chat.ID, 56 Name: chat.Name, 57 Description: chat.Description, 58 Color: chat.Color, 59 Emoji: chat.Emoji, 60 Active: chat.Active, 61 ChatType: chat.ChatType, 62 Timestamp: chat.Timestamp, 63 LastClockValue: chat.LastClockValue, 64 DeletedAtClockValue: chat.DeletedAtClockValue, 65 UnviewedMessagesCount: chat.UnviewedMessagesCount, 66 UnviewedMentionsCount: chat.UnviewedMentionsCount, 67 Alias: chat.Alias, 68 Identicon: chat.Identicon, 69 Muted: chat.Muted, 70 MuteTill: chat.MuteTill, 71 Profile: chat.Profile, 72 CommunityID: chat.CommunityID, 73 CategoryID: chat.CategoryID, 74 Joined: chat.Joined, 75 SyncedTo: chat.SyncedTo, 76 SyncedFrom: chat.SyncedFrom, 77 Highlight: chat.Highlight, 78 Members: chat.Members, 79 Base64Image: chat.Base64Image, 80 } 81 82 if chat.LastMessage != nil { 83 84 chatPreview.OutgoingStatus = chat.LastMessage.OutgoingStatus 85 chatPreview.ResponseTo = chat.LastMessage.ResponseTo 86 chatPreview.ContentType = chat.LastMessage.ContentType 87 chatPreview.From = chat.LastMessage.From 88 chatPreview.Deleted = chat.LastMessage.Deleted 89 chatPreview.DeletedForMe = chat.LastMessage.DeletedForMe 90 91 if chat.LastMessage.ContentType == protobuf.ChatMessage_IMAGE { 92 chatPreview.ParsedText = chat.LastMessage.ParsedText 93 94 image := chat.LastMessage.GetImage() 95 if image != nil { 96 chatPreview.AlbumImagesCount = image.AlbumImagesCount 97 chatPreview.ParsedText = chat.LastMessage.ParsedText 98 } 99 } 100 101 if chat.LastMessage.ContentType == protobuf.ChatMessage_TEXT_PLAIN { 102 103 simplifiedText, err := chat.LastMessage.GetSimplifiedText("", nil) 104 105 if err == nil { 106 if len(simplifiedText) > 100 { 107 chatPreview.Text = simplifiedText[:100] 108 } else { 109 chatPreview.Text = simplifiedText 110 } 111 if strings.Contains(chatPreview.Text, "0x") { 112 //if there is a mention, we would like to send parsed text as well 113 chatPreview.ParsedText = chat.LastMessage.ParsedText 114 } 115 } 116 } else if chat.LastMessage.ContentType == protobuf.ChatMessage_EMOJI || 117 chat.LastMessage.ContentType == protobuf.ChatMessage_TRANSACTION_COMMAND { 118 119 chatPreview.Text = chat.LastMessage.Text 120 chatPreview.ParsedText = chat.LastMessage.ParsedText 121 } 122 if chat.LastMessage.ContentType == protobuf.ChatMessage_COMMUNITY { 123 chatPreview.ContentCommunityID = chat.LastMessage.CommunityID 124 } 125 } 126 127 chats = append(chats, chatPreview) 128 } 129 130 return true 131 }) 132 133 return chats 134 } 135 136 func (m *Messenger) Chat(chatID string) *Chat { 137 chat, _ := m.allChats.Load(chatID) 138 139 return chat 140 } 141 142 func (m *Messenger) ActiveChats() []*Chat { 143 m.mutex.Lock() 144 defer m.mutex.Unlock() 145 146 var chats []*Chat 147 148 m.allChats.Range(func(chatID string, c *Chat) bool { 149 if c.Active { 150 chats = append(chats, c) 151 } 152 return true 153 }) 154 155 return chats 156 } 157 158 func (m *Messenger) initChatSyncFields(chat *Chat) error { 159 defaultSyncPeriod, err := m.settings.GetDefaultSyncPeriod() 160 if err != nil { 161 return err 162 } 163 timestamp := uint32(m.getTimesource().GetCurrentTime()/1000) - defaultSyncPeriod 164 chat.SyncedTo = timestamp 165 chat.SyncedFrom = timestamp 166 167 return nil 168 } 169 170 func (m *Messenger) createPublicChat(chatID string, response *MessengerResponse) (*MessengerResponse, error) { 171 chat, ok := m.allChats.Load(chatID) 172 if !ok { 173 chat = CreatePublicChat(chatID, m.getTimesource()) 174 175 } 176 chat.Active = true 177 chat.DeletedAtClockValue = 0 178 179 // Save topics 180 _, err := m.Join(chat) 181 if err != nil { 182 return nil, err 183 } 184 185 // Store chat 186 m.allChats.Store(chat.ID, chat) 187 188 willSync, err := m.scheduleSyncChat(chat) 189 if err != nil { 190 return nil, err 191 } 192 193 // We set the synced to, synced from to the default time 194 if !willSync { 195 if err := m.initChatSyncFields(chat); err != nil { 196 return nil, err 197 } 198 } 199 200 err = m.saveChat(chat) 201 if err != nil { 202 return nil, err 203 } 204 205 err = m.reregisterForPushNotifications() 206 if err != nil { 207 return nil, err 208 } 209 210 response.AddChat(chat) 211 212 return response, nil 213 } 214 215 func (m *Messenger) CreatePublicChat(request *requests.CreatePublicChat) (*MessengerResponse, error) { 216 if err := request.Validate(); err != nil { 217 return nil, err 218 } 219 220 chatID := request.ID 221 response := &MessengerResponse{} 222 223 return m.createPublicChat(chatID, response) 224 } 225 226 // Deprecated: CreateProfileChat shouldn't be used 227 // and is only left here in case profile chat feature is re-introduced. 228 func (m *Messenger) CreateProfileChat(request *requests.CreateProfileChat) (*MessengerResponse, error) { 229 // Return error to prevent usage of deprecated function 230 if deprecation.ChatProfileDeprecated { 231 return nil, errors.New("profile chats are deprecated") 232 } 233 234 if err := request.Validate(); err != nil { 235 return nil, err 236 } 237 238 publicKey, err := common.HexToPubkey(request.ID) 239 if err != nil { 240 return nil, err 241 } 242 243 chat := m.buildProfileChat(request.ID) 244 245 chat.Active = true 246 247 // Save topics 248 _, err = m.Join(chat) 249 if err != nil { 250 return nil, err 251 } 252 253 // Check contact code 254 filter, err := m.transport.JoinPrivate(publicKey) 255 if err != nil { 256 return nil, err 257 } 258 259 // Store chat 260 m.allChats.Store(chat.ID, chat) 261 262 response := &MessengerResponse{} 263 response.AddChat(chat) 264 265 willSync, err := m.scheduleSyncChat(chat) 266 if err != nil { 267 return nil, err 268 } 269 270 // We set the synced to, synced from to the default time 271 if !willSync { 272 if err := m.initChatSyncFields(chat); err != nil { 273 return nil, err 274 } 275 } 276 277 _, err = m.scheduleSyncFilters([]*transport.Filter{filter}) 278 if err != nil { 279 return nil, err 280 } 281 282 err = m.saveChat(chat) 283 if err != nil { 284 return nil, err 285 } 286 287 return response, nil 288 } 289 290 func (m *Messenger) CreateOneToOneChat(request *requests.CreateOneToOneChat) (*MessengerResponse, error) { 291 if err := request.Validate(); err != nil { 292 return nil, err 293 } 294 295 chatID := request.ID.String() 296 pk, err := common.HexToPubkey(chatID) 297 if err != nil { 298 return nil, err 299 } 300 301 response := &MessengerResponse{} 302 303 ensName := request.ENSName 304 if ensName != "" { 305 clock := m.getTimesource().GetCurrentTime() 306 err := m.ensVerifier.ENSVerified(chatID, ensName, clock) 307 if err != nil { 308 return nil, err 309 } 310 contact, err := m.BuildContact(&requests.BuildContact{PublicKey: chatID}) 311 if err != nil { 312 return nil, err 313 } 314 315 contact.EnsName = ensName 316 contact.ENSVerified = true 317 err = m.persistence.SaveContact(contact, nil) 318 if err != nil { 319 return nil, err 320 } 321 response.Contacts = []*Contact{contact} 322 } 323 324 chat, ok := m.allChats.Load(chatID) 325 if !ok { 326 chat = CreateOneToOneChat(chatID, pk, m.getTimesource()) 327 } 328 chat.Active = true 329 330 filters, err := m.Join(chat) 331 if err != nil { 332 return nil, err 333 } 334 335 // TODO(Samyoul) remove storing of an updated reference pointer? 336 m.allChats.Store(chatID, chat) 337 338 response.AddChat(chat) 339 340 willSync, err := m.scheduleSyncFilters(filters) 341 if err != nil { 342 return nil, err 343 } 344 345 // We set the synced to, synced from to the default time 346 if !willSync { 347 if err := m.initChatSyncFields(chat); err != nil { 348 return nil, err 349 } 350 } 351 352 err = m.saveChat(chat) 353 if err != nil { 354 return nil, err 355 } 356 return response, nil 357 } 358 359 func (m *Messenger) DeleteChat(chatID string) error { 360 return m.deleteChat(chatID) 361 } 362 363 func (m *Messenger) deleteChat(chatID string) error { 364 err := m.persistence.DeleteChat(chatID) 365 if err != nil { 366 return err 367 } 368 369 // We clean the cache to be able to receive the messages again later 370 err = m.transport.ClearProcessedMessageIDsCache() 371 if err != nil { 372 return err 373 } 374 375 chat, ok := m.allChats.Load(chatID) 376 377 if ok && chat.Active && chat.Public() { 378 m.allChats.Delete(chatID) 379 return m.reregisterForPushNotifications() 380 } 381 382 return nil 383 } 384 385 func (m *Messenger) SaveChat(chat *Chat) error { 386 return m.saveChat(chat) 387 } 388 389 func (m *Messenger) DeactivateChat(request *requests.DeactivateChat) (*MessengerResponse, error) { 390 if err := request.Validate(); err != nil { 391 return nil, err 392 } 393 394 doClearHistory := !request.PreserveHistory 395 396 return m.deactivateChat(request.ID, 0, true, doClearHistory) 397 } 398 399 func (m *Messenger) deactivateChat(chatID string, deactivationClock uint64, shouldBeSynced bool, doClearHistory bool) (*MessengerResponse, error) { 400 var response MessengerResponse 401 chat, ok := m.allChats.Load(chatID) 402 if !ok { 403 return nil, ErrChatNotFound 404 } 405 406 // Reset mailserver last request to allow re-fetching messages if joining a chat again 407 filters, err := m.filtersForChat(chatID) 408 if err != nil && err != ErrNoFiltersForChat { 409 return nil, err 410 } 411 412 if m.mailserversDatabase != nil { 413 for _, filter := range filters { 414 if !filter.Listen || filter.Ephemeral { 415 continue 416 } 417 418 err := m.mailserversDatabase.ResetLastRequest(filter.PubsubTopic, filter.ContentTopic.String()) 419 if err != nil { 420 return nil, err 421 } 422 } 423 } 424 425 if deactivationClock == 0 { 426 deactivationClock, _ = chat.NextClockAndTimestamp(m.getTimesource()) 427 } 428 429 err = m.persistence.DeactivateChat(chat, deactivationClock, doClearHistory) 430 431 if err != nil { 432 return nil, err 433 } 434 435 // We re-register as our options have changed and we don't want to 436 // receive PN from mentions in this chat anymore 437 if chat.Public() || chat.ProfileUpdates() { 438 err := m.reregisterForPushNotifications() 439 if err != nil { 440 return nil, err 441 } 442 443 err = m.transport.ClearProcessedMessageIDsCache() 444 if err != nil { 445 return nil, err 446 } 447 } 448 449 // TODO(samyoul) remove storing of an updated reference pointer? 450 m.allChats.Store(chatID, chat) 451 452 response.AddChat(chat) 453 // TODO: Remove filters 454 455 if shouldBeSynced { 456 err := m.syncChatRemoving(context.Background(), chat.ID, m.dispatchMessage) 457 if err != nil { 458 return nil, err 459 } 460 } 461 462 return &response, nil 463 } 464 465 func (m *Messenger) saveChats(chats []*Chat) error { 466 err := m.persistence.SaveChats(chats) 467 if err != nil { 468 return err 469 } 470 for _, chat := range chats { 471 m.allChats.Store(chat.ID, chat) 472 } 473 474 return nil 475 476 } 477 478 func (m *Messenger) saveChat(chat *Chat) error { 479 _, ok := m.allChats.Load(chat.ID) 480 if chat.OneToOne() { 481 name, identicon, err := generateAliasAndIdenticon(chat.ID) 482 if err != nil { 483 return err 484 } 485 486 chat.Alias = name 487 chat.Identicon = identicon 488 } 489 490 // Sync chat if it's a new public, 1-1 or group chat, but not a timeline chat 491 if !ok && chat.shouldBeSynced() { 492 if err := m.syncChat(context.Background(), chat, m.dispatchMessage); err != nil { 493 return err 494 } 495 } 496 497 err := m.persistence.SaveChat(*chat) 498 if err != nil { 499 return err 500 } 501 // We store the chat has it might not have been in the store in the first place 502 m.allChats.Store(chat.ID, chat) 503 504 return nil 505 } 506 507 func (m *Messenger) Join(chat *Chat) ([]*transport.Filter, error) { 508 switch chat.ChatType { 509 case ChatTypeOneToOne: 510 pk, err := chat.PublicKey() 511 if err != nil { 512 return nil, err 513 } 514 515 f, err := m.transport.JoinPrivate(pk) 516 if err != nil { 517 return nil, err 518 } 519 520 return []*transport.Filter{f}, nil 521 case ChatTypePrivateGroupChat: 522 members, err := chat.MembersAsPublicKeys() 523 if err != nil { 524 return nil, err 525 } 526 return m.transport.JoinGroup(members) 527 case ChatTypePublic, ChatTypeProfile, ChatTypeTimeline: 528 f, err := m.transport.JoinPublic(chat.ID) 529 if err != nil { 530 return nil, err 531 } 532 return []*transport.Filter{f}, nil 533 default: 534 return nil, errors.New("chat is neither public nor private") 535 } 536 } 537 538 // Deprecated: buildProfileChat shouldn't be used 539 // and is only left here in case profile chat feature is re-introduced. 540 func (m *Messenger) buildProfileChat(id string) *Chat { 541 // Return nil to prevent usage of deprecated function 542 if deprecation.ChatProfileDeprecated { 543 return nil 544 } 545 546 // Create the corresponding profile chat 547 profileChatID := buildProfileChatID(id) 548 profileChat, ok := m.allChats.Load(profileChatID) 549 550 if !ok { 551 profileChat = CreateProfileChat(id, m.getTimesource()) 552 } 553 554 return profileChat 555 556 } 557 558 // Deprecated: ensureTimelineChat shouldn't be used 559 // and is only left here in case profile chat feature is re-introduced. 560 func (m *Messenger) ensureTimelineChat() error { 561 // Return error to prevent usage of deprecated function 562 if deprecation.ChatProfileDeprecated { 563 return errors.New("timeline chats are deprecated") 564 } 565 566 chat, err := m.persistence.Chat(timelineChatID) 567 if err != nil { 568 return err 569 } 570 571 if chat != nil { 572 return nil 573 } 574 575 chat = CreateTimelineChat(m.getTimesource()) 576 m.allChats.Store(timelineChatID, chat) 577 return m.saveChat(chat) 578 } 579 580 // Deprecated: ensureMyOwnProfileChat shouldn't be used 581 // and is only left here in case profile chat feature is re-introduced. 582 func (m *Messenger) ensureMyOwnProfileChat() error { 583 // Return error to prevent usage of deprecated function 584 if deprecation.ChatProfileDeprecated { 585 return errors.New("profile chats are deprecated") 586 } 587 588 chatID := common.PubkeyToHex(&m.identity.PublicKey) 589 _, ok := m.allChats.Load(chatID) 590 if ok { 591 return nil 592 } 593 594 chat := m.buildProfileChat(chatID) 595 596 chat.Active = true 597 598 // Save topics 599 _, err := m.Join(chat) 600 if err != nil { 601 return err 602 } 603 604 return m.saveChat(chat) 605 } 606 607 func (m *Messenger) ClearHistory(request *requests.ClearHistory) (*MessengerResponse, error) { 608 if err := request.Validate(); err != nil { 609 return nil, err 610 } 611 612 return m.clearHistory(request.ID) 613 } 614 615 func (m *Messenger) clearHistory(id string) (*MessengerResponse, error) { 616 chat, ok := m.allChats.Load(id) 617 if !ok { 618 return nil, ErrChatNotFound 619 } 620 621 clock, _ := chat.NextClockAndTimestamp(m.transport) 622 623 err := m.persistence.ClearHistory(chat, clock) 624 if err != nil { 625 return nil, err 626 } 627 628 if chat.Public() { 629 err = m.transport.ClearProcessedMessageIDsCache() 630 if err != nil { 631 return nil, err 632 } 633 } 634 635 err = m.syncClearHistory(context.Background(), chat, m.dispatchMessage) 636 if err != nil { 637 return nil, err 638 } 639 640 m.allChats.Store(id, chat) 641 642 response := &MessengerResponse{} 643 response.AddChat(chat) 644 return response, nil 645 } 646 647 func (m *Messenger) FetchMessages(request *requests.FetchMessages) error { 648 649 if err := request.Validate(); err != nil { 650 return err 651 } 652 653 id := request.ID 654 655 chat, ok := m.allChats.Load(id) 656 if !ok { 657 return ErrChatNotFound 658 } 659 660 _, err := m.fetchMessages(chat.ID, oneMonthDuration) 661 if err != nil { 662 return err 663 } 664 665 return nil 666 }