mirror of
https://github.com/status-im/status-go.git
synced 2025-01-12 15:45:07 +00:00
dbed69d155
Fixes the slow login in mobile devices when users have joined large communities, such as the Status one. A user would get stuck for almost 20s in some devices. We identified that the step to set-up filters in the messenger is potentially expensive and that it is not critical to happen before the node.login signal is emitted. The solution presented in this PR is to set-up filters inside messenger.Start(), which the client already calls immediately after login. With this change, users of the mobile app can login pretty fast even when they joined large communities. They can immediately interact with other parts of the app even if filter initialization is running in the background, like Wallet, Activity Center, Settings, and Profile. Breaking changes: in the mobile repository, we had to change where the endpoint wakuext_startMessenger was called and the order of a few events to process chats. So essentially ordering, but no data changes. - Root issue https://github.com/status-im/status-mobile/issues/20059 - Related mobile PR https://github.com/status-im/status-mobile/pull/20173
428 lines
13 KiB
Go
428 lines
13 KiB
Go
package protocol
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"image"
|
|
"image/png"
|
|
"os"
|
|
"runtime"
|
|
"testing"
|
|
|
|
userimage "github.com/status-im/status-go/images"
|
|
"github.com/status-im/status-go/server"
|
|
"github.com/status-im/status-go/services/browsers"
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
|
|
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
|
"github.com/status-im/status-go/protocol/requests"
|
|
"github.com/status-im/status-go/protocol/tt"
|
|
)
|
|
|
|
const statusChatID = "status"
|
|
const removedChatID = "deactivated"
|
|
|
|
func TestMessengerInstallationSuite(t *testing.T) {
|
|
suite.Run(t, new(MessengerInstallationSuite))
|
|
}
|
|
|
|
type MessengerInstallationSuite struct {
|
|
MessengerBaseTestSuite
|
|
}
|
|
|
|
func (s *MessengerInstallationSuite) TestReceiveInstallation() {
|
|
theirMessenger, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
|
|
s.Require().NoError(err)
|
|
|
|
err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{
|
|
Name: "their-name",
|
|
DeviceType: "their-device-type",
|
|
})
|
|
s.Require().NoError(err)
|
|
response, err := theirMessenger.SendPairInstallation(context.Background(), nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().False(response.Chats()[0].Active)
|
|
|
|
// Wait for the message to reach its destination
|
|
response, err = WaitOnMessengerResponse(
|
|
s.m,
|
|
func(r *MessengerResponse) bool { return len(r.Installations) > 0 },
|
|
"installation not received",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
actualInstallation := response.Installations[0]
|
|
s.Require().Equal(theirMessenger.installationID, actualInstallation.ID)
|
|
s.Require().NotNil(actualInstallation.InstallationMetadata)
|
|
s.Require().Equal("their-name", actualInstallation.InstallationMetadata.Name)
|
|
s.Require().Equal("their-device-type", actualInstallation.InstallationMetadata.DeviceType)
|
|
|
|
err = s.m.EnableInstallation(theirMessenger.installationID)
|
|
s.Require().NoError(err)
|
|
|
|
contactKey, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
|
|
contact, err := BuildContactFromPublicKey(&contactKey.PublicKey)
|
|
s.Require().NoError(err)
|
|
response, err = s.m.AddContact(context.Background(), &requests.AddContact{ID: contact.ID, CustomizationColor: string(multiaccountscommon.CustomizationColorRed)})
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Len(response.Contacts, 1)
|
|
s.Require().Equal(response.Contacts[0].ID, contact.ID)
|
|
s.Require().Equal(response.Contacts[0].CustomizationColor, multiaccountscommon.CustomizationColorRed)
|
|
|
|
// Wait for the message to reach its destination
|
|
response, err = WaitOnMessengerResponse(
|
|
theirMessenger,
|
|
func(r *MessengerResponse) bool { return len(r.Contacts) == 1 && r.Contacts[0].ID == contact.ID },
|
|
"contact not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
actualContact := response.Contacts[0]
|
|
s.Require().Equal(contact.ID, actualContact.ID)
|
|
s.Require().True(actualContact.added())
|
|
|
|
// Simulate update from contact
|
|
contact.LastUpdated = 10
|
|
contact.DisplayName = "display-name"
|
|
contact.CustomizationColor = multiaccountscommon.CustomizationColorRed
|
|
|
|
s.Require().NoError(s.m.persistence.SaveContacts([]*Contact{contact}))
|
|
// Trigger syncing of contact
|
|
err = s.m.syncContact(context.Background(), contact, s.m.dispatchMessage)
|
|
s.Require().NoError(err)
|
|
|
|
// Wait for the message to reach its destination
|
|
_, err = WaitOnMessengerResponse(
|
|
theirMessenger,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Contacts) == 1 &&
|
|
r.Contacts[0].ID == contact.ID &&
|
|
// Make sure lastupdated is **not** synced
|
|
actualContact.LastUpdated == 0 &&
|
|
r.Contacts[0].DisplayName == "display-name" &&
|
|
r.Contacts[0].CustomizationColor == multiaccountscommon.CustomizationColorRed
|
|
},
|
|
"contact not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
chat := CreatePublicChat(statusChatID, s.m.transport)
|
|
err = s.m.SaveChat(chat)
|
|
s.Require().NoError(err)
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
theirMessenger,
|
|
func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
|
|
"sync chat not received",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
actualChat := response.Chats()[0]
|
|
s.Require().Equal(statusChatID, actualChat.ID)
|
|
s.Require().True(actualChat.Active)
|
|
s.Require().NoError(theirMessenger.Shutdown())
|
|
}
|
|
|
|
func (s *MessengerInstallationSuite) TestSyncInstallation() {
|
|
// add contact
|
|
contactKey, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
|
|
contact, err := BuildContactFromPublicKey(&contactKey.PublicKey)
|
|
s.Require().NoError(err)
|
|
|
|
// mock added as mutual contact
|
|
contact.LastUpdated = 1
|
|
contact.ContactRequestReceived(1)
|
|
s.m.allContacts.Store(contact.ID, contact)
|
|
|
|
contact.LocalNickname = "Test Nickname"
|
|
_, err = s.m.AddContact(context.Background(), &requests.AddContact{ID: contact.ID, CustomizationColor: string(multiaccountscommon.CustomizationColorRed)})
|
|
s.Require().NoError(err)
|
|
_, err = s.m.SetContactLocalNickname(&requests.SetContactLocalNickname{ID: types.Hex2Bytes(contact.ID), Nickname: contact.LocalNickname})
|
|
s.Require().NoError(err)
|
|
|
|
//add bookmark
|
|
bookmark := browsers.Bookmark{
|
|
Name: "status official site",
|
|
URL: "https://status.im",
|
|
Removed: false,
|
|
}
|
|
_, err = s.m.browserDatabase.StoreBookmark(bookmark)
|
|
s.Require().NoError(err)
|
|
|
|
// add chat
|
|
chat := CreatePublicChat(statusChatID, s.m.transport)
|
|
err = s.m.SaveChat(chat)
|
|
s.Require().NoError(err)
|
|
|
|
// Create group chat
|
|
response, err := s.m.CreateGroupChatWithMembers(context.Background(), "group", []string{})
|
|
s.NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
ourGroupChat := response.Chats()[0]
|
|
|
|
err = s.m.SaveChat(ourGroupChat)
|
|
s.NoError(err)
|
|
|
|
// Generate test image bigger than BannerDim
|
|
testImage := image.NewRGBA(image.Rect(0, 0, 20, 10))
|
|
|
|
tmpTestFilePath := s.T().TempDir() + "/test.png"
|
|
file, err := os.Create(tmpTestFilePath)
|
|
s.NoError(err)
|
|
defer file.Close()
|
|
|
|
err = png.Encode(file, testImage)
|
|
s.Require().NoError(err)
|
|
|
|
groupImg := userimage.CroppedImage{
|
|
ImagePath: tmpTestFilePath,
|
|
X: 1,
|
|
Y: 1,
|
|
Width: 10,
|
|
Height: 5,
|
|
}
|
|
|
|
// Add image to chat
|
|
response, err = s.m.EditGroupChat(context.Background(), ourGroupChat.ID, "test_admin_group", "#FF00FF", groupImg)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().Equal("test_admin_group", response.Chats()[0].Name)
|
|
s.Require().Equal("#FF00FF", response.Chats()[0].Color)
|
|
ourGroupChat = response.Chats()[0]
|
|
|
|
// Create second group chat and deactivate it
|
|
response, err = s.m.CreateGroupChatWithMembers(context.Background(), "deactivated-group", []string{})
|
|
s.NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
ourDeactivatedGroupChat := response.Chats()[0]
|
|
err = s.m.SaveChat(ourDeactivatedGroupChat)
|
|
s.NoError(err)
|
|
_, err = s.m.deactivateChat(ourDeactivatedGroupChat.ID, 0, true, true)
|
|
s.NoError(err)
|
|
|
|
// Create Alice for the 1-1 chat
|
|
alice := s.newMessenger()
|
|
defer TearDownMessenger(&s.Suite, alice)
|
|
|
|
// Create 1-1 chat
|
|
ourOneOneChat := CreateOneToOneChat("Our 1TO1", &alice.identity.PublicKey, alice.transport)
|
|
err = s.m.SaveChat(ourOneOneChat)
|
|
s.Require().NoError(err)
|
|
|
|
// add and deactivate chat
|
|
chat2 := CreatePublicChat(removedChatID, s.m.transport)
|
|
chat2.DeletedAtClockValue = 1
|
|
err = s.m.SaveChat(chat2)
|
|
s.Require().NoError(err)
|
|
_, err = s.m.deactivateChat(removedChatID, 0, true, true)
|
|
s.Require().NoError(err)
|
|
|
|
// pair
|
|
theirMessenger, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
|
|
s.Require().NoError(err)
|
|
err = theirMessenger.SaveChat(chat2)
|
|
s.Require().NoError(err)
|
|
|
|
err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{
|
|
Name: "their-name",
|
|
DeviceType: "their-device-type",
|
|
})
|
|
s.Require().NoError(err)
|
|
response, err = theirMessenger.SendPairInstallation(context.Background(), nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().False(response.Chats()[0].Active)
|
|
|
|
// Wait for the message to reach its destination
|
|
response, err = WaitOnMessengerResponse(
|
|
s.m,
|
|
func(r *MessengerResponse) bool { return len(r.Installations) > 0 },
|
|
"installation not received",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
actualInstallation := response.Installations[0]
|
|
s.Require().Equal(theirMessenger.installationID, actualInstallation.ID)
|
|
s.Require().NotNil(actualInstallation.InstallationMetadata)
|
|
s.Require().Equal("their-name", actualInstallation.InstallationMetadata.Name)
|
|
s.Require().Equal("their-device-type", actualInstallation.InstallationMetadata.DeviceType)
|
|
|
|
err = s.m.EnableInstallation(theirMessenger.installationID)
|
|
s.Require().NoError(err)
|
|
|
|
// sync
|
|
err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image", nil)
|
|
s.Require().NoError(err)
|
|
|
|
var allChats []*Chat
|
|
var actualContact *Contact
|
|
var bookmarks []*browsers.Bookmark
|
|
// Wait for the message to reach its destination
|
|
err = tt.RetryWithBackOff(func() error {
|
|
var err error
|
|
response, err = theirMessenger.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
allChats = append(allChats, response.Chats()...)
|
|
for _, c := range response.Contacts {
|
|
if c.LocalNickname == contact.LocalNickname {
|
|
actualContact = c
|
|
break
|
|
}
|
|
}
|
|
bookmarks = append(bookmarks, response.GetBookmarks()...)
|
|
|
|
if len(allChats) >= 5 && actualContact != nil && len(bookmarks) >= 1 {
|
|
return nil
|
|
}
|
|
|
|
return errors.New("not received all chats & contacts & bookmarks yet")
|
|
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
|
|
var statusChat *Chat
|
|
var groupChat *Chat
|
|
var removedGroupChat *Chat
|
|
var oneToOneChat *Chat
|
|
var removedChat *Chat
|
|
for _, c := range allChats {
|
|
if c.ID == statusChatID {
|
|
statusChat = c
|
|
}
|
|
if c.ID == ourGroupChat.ID {
|
|
groupChat = c
|
|
}
|
|
if c.ID == ourDeactivatedGroupChat.ID {
|
|
removedGroupChat = c
|
|
}
|
|
if c.ID == ourOneOneChat.ID {
|
|
oneToOneChat = c
|
|
}
|
|
if c.ID == removedChatID {
|
|
removedChat = c
|
|
}
|
|
}
|
|
|
|
s.Require().NotNil(statusChat)
|
|
s.Require().NotNil(groupChat)
|
|
s.Require().NotNil(removedGroupChat)
|
|
s.Require().NotNil(oneToOneChat)
|
|
|
|
s.Require().Equal(ourGroupChat.Name, groupChat.Name)
|
|
s.Require().True(ourGroupChat.Active)
|
|
|
|
s.Require().Equal(ourDeactivatedGroupChat.Name, removedGroupChat.Name)
|
|
s.Require().False(removedGroupChat.Active)
|
|
|
|
s.Require().Equal("", oneToOneChat.Name) // We set 1-1 chat names to "" because the name is not good
|
|
s.Require().True(oneToOneChat.Active)
|
|
|
|
s.Require().True(actualContact.added())
|
|
s.Require().Equal("Test Nickname", actualContact.LocalNickname)
|
|
s.Require().Equal(multiaccountscommon.CustomizationColorRed, actualContact.CustomizationColor)
|
|
s.Require().True(actualContact.hasAddedUs())
|
|
s.Require().True(actualContact.mutual())
|
|
|
|
bookmarks, err = theirMessenger.browserDatabase.GetBookmarks()
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(1, len(bookmarks))
|
|
|
|
s.Require().NoError(theirMessenger.Shutdown())
|
|
|
|
s.Require().NotNil(removedChat)
|
|
s.Require().False(removedChat.Active)
|
|
|
|
}
|
|
|
|
func (s *MessengerInstallationSuite) TestSyncInstallationNewMessages() {
|
|
|
|
bob1 := s.m
|
|
// pair
|
|
bob2, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
|
|
s.Require().NoError(err)
|
|
alice := s.newMessenger()
|
|
|
|
err = bob2.SetInstallationMetadata(bob2.installationID, &multidevice.InstallationMetadata{
|
|
Name: "their-name",
|
|
DeviceType: "their-device-type",
|
|
})
|
|
s.Require().NoError(err)
|
|
response, err := bob2.SendPairInstallation(context.Background(), nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().False(response.Chats()[0].Active)
|
|
|
|
// Wait for the message to reach its destination
|
|
response, err = WaitOnMessengerResponse(
|
|
bob1,
|
|
func(r *MessengerResponse) bool { return len(r.Installations) > 0 },
|
|
"installation not received",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
actualInstallation := response.Installations[0]
|
|
s.Require().Equal(bob2.installationID, actualInstallation.ID)
|
|
err = bob1.EnableInstallation(bob2.installationID)
|
|
s.Require().NoError(err)
|
|
|
|
// send a message from bob1 to alice, it should be received on both bob1 and bob2
|
|
|
|
alicePkString := types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey))
|
|
chat := CreateOneToOneChat(alicePkString, &alice.identity.PublicKey, bob1.transport)
|
|
s.Require().NoError(bob1.SaveChat(chat))
|
|
|
|
inputMessage := buildTestMessage(*chat)
|
|
_, err = s.m.SendChatMessage(context.Background(), inputMessage)
|
|
s.Require().NoError(err)
|
|
|
|
// Wait for the message to reach its destination
|
|
_, err = WaitOnMessengerResponse(
|
|
bob2,
|
|
func(r *MessengerResponse) bool { return len(r.Messages()) > 0 },
|
|
"message not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().NoError(bob2.Shutdown())
|
|
s.Require().NoError(alice.Shutdown())
|
|
}
|
|
|
|
func (s *MessengerInstallationSuite) TestInitInstallations() {
|
|
m, err := newMessengerWithKey(s.shh, s.privateKey, s.logger, nil)
|
|
s.Require().NoError(err)
|
|
|
|
// m.InitInstallations is already called when we set-up the messenger for
|
|
// testing, thus this test has no act phase.
|
|
// err = m.InitInstallations()
|
|
|
|
// We get one installation when the messenger initializes installations
|
|
// correctly.
|
|
s.Require().Equal(1, m.allInstallations.Len())
|
|
|
|
deviceName, err := server.GetDeviceName()
|
|
s.Require().NoError(err)
|
|
installation, ok := m.allInstallations.Load(m.installationID)
|
|
s.Require().True(ok)
|
|
s.Require().Equal(deviceName+" ", installation.InstallationMetadata.Name)
|
|
s.Require().Equal(runtime.GOOS, installation.InstallationMetadata.DeviceType)
|
|
}
|