fix: shared links and link previews contain full self information (#4169)
* fix(StatusUnfurler): allow contact without icon
This commit is contained in:
parent
311e463eed
commit
e83be20def
|
@ -95,9 +95,13 @@ type MultiAccountMarshaller interface {
|
|||
ToMultiAccount() *Account
|
||||
}
|
||||
|
||||
type IdentityImageSubscriptionChange struct {
|
||||
PublishExpected bool
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
identityImageSubscriptions []chan struct{}
|
||||
identityImageSubscriptions []chan *IdentityImageSubscriptionChange
|
||||
}
|
||||
|
||||
// InitializeDB creates db file at a given path and applies migrations.
|
||||
|
@ -422,24 +426,24 @@ func (db *Database) StoreIdentityImages(keyUID string, iis []images.IdentityImag
|
|||
}
|
||||
}
|
||||
|
||||
if publish {
|
||||
db.publishOnIdentityImageSubscriptions()
|
||||
}
|
||||
db.publishOnIdentityImageSubscriptions(&IdentityImageSubscriptionChange{
|
||||
PublishExpected: publish,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) SubscribeToIdentityImageChanges() chan struct{} {
|
||||
s := make(chan struct{}, 100)
|
||||
func (db *Database) SubscribeToIdentityImageChanges() chan *IdentityImageSubscriptionChange {
|
||||
s := make(chan *IdentityImageSubscriptionChange, 100)
|
||||
db.identityImageSubscriptions = append(db.identityImageSubscriptions, s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (db *Database) publishOnIdentityImageSubscriptions() {
|
||||
func (db *Database) publishOnIdentityImageSubscriptions(change *IdentityImageSubscriptionChange) {
|
||||
// Publish on channels, drop if buffer is full
|
||||
for _, s := range db.identityImageSubscriptions {
|
||||
select {
|
||||
case s <- struct{}{}:
|
||||
case s <- change:
|
||||
default:
|
||||
log.Warn("subscription channel full, dropping message")
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
"github.com/status-im/status-go/common/dbsetup"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/multiaccounts/errors"
|
||||
|
@ -28,9 +30,10 @@ var (
|
|||
|
||||
// Database sql wrapper for operations with browser objects.
|
||||
type Database struct {
|
||||
db *sql.DB
|
||||
SyncQueue chan SyncSettingField
|
||||
notifier Notifier
|
||||
db *sql.DB
|
||||
SyncQueue chan SyncSettingField
|
||||
changesSubscriptions []chan *SyncSettingField
|
||||
notifier Notifier
|
||||
}
|
||||
|
||||
// MakeNewDB ensures that a singleton instance of Database is returned per sqlite db file
|
||||
|
@ -254,6 +257,9 @@ func (db *Database) parseSaveAndSyncSetting(sf SettingField, value interface{})
|
|||
if sf.CanSync(FromInterface) {
|
||||
db.SyncQueue <- SyncSettingField{sf, value}
|
||||
}
|
||||
|
||||
db.postChangesToSubscribers(&SyncSettingField{sf, value})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -280,7 +286,8 @@ func (db *Database) DeleteMnemonic() error {
|
|||
}
|
||||
|
||||
// SaveSyncSetting stores setting data from a sync protobuf source, note it does not call SettingField.ValueHandler()
|
||||
// nor does this function attempt to write to the Database.SyncQueue
|
||||
// nor does this function attempt to write to the Database.SyncQueue,
|
||||
// yet it still writes to Database.changesSubscriptions.
|
||||
func (db *Database) SaveSyncSetting(setting SettingField, value interface{}, clock uint64) error {
|
||||
ls, err := db.GetSettingLastSynced(setting)
|
||||
if err != nil {
|
||||
|
@ -295,7 +302,13 @@ func (db *Database) SaveSyncSetting(setting SettingField, value interface{}, clo
|
|||
return err
|
||||
}
|
||||
|
||||
return db.saveSetting(setting, value)
|
||||
err = db.saveSetting(setting, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.postChangesToSubscribers(&SyncSettingField{setting, value})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) GetSettingLastSynced(setting SettingField) (result uint64, err error) {
|
||||
|
@ -712,3 +725,20 @@ func (db *Database) URLUnfurlingMode() (result int64, err error) {
|
|||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (db *Database) SubscribeToChanges() chan *SyncSettingField {
|
||||
s := make(chan *SyncSettingField, 100)
|
||||
db.changesSubscriptions = append(db.changesSubscriptions, s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (db *Database) postChangesToSubscribers(change *SyncSettingField) {
|
||||
// Publish on channels, drop if buffer is full
|
||||
for _, s := range db.changesSubscriptions {
|
||||
select {
|
||||
case s <- change:
|
||||
default:
|
||||
log.Warn("settings changes subscription channel full, dropping message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/eth-node/types"
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/multiaccounts/settings"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/identity"
|
||||
|
@ -386,6 +388,41 @@ func buildContact(publicKeyString string, publicKey *ecdsa.PublicKey) (*Contact,
|
|||
return contact, nil
|
||||
}
|
||||
|
||||
func buildSelfContact(identity *ecdsa.PrivateKey, settings *accounts.Database, multiAccounts *multiaccounts.Database, account *multiaccounts.Account) (*Contact, error) {
|
||||
myPublicKeyString := types.EncodeHex(crypto.FromECDSAPub(&identity.PublicKey))
|
||||
|
||||
c, err := buildContact(myPublicKeyString, &identity.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build contact: %w", err)
|
||||
}
|
||||
|
||||
if settings != nil {
|
||||
if s, err := settings.GetSettings(); err == nil {
|
||||
c.DisplayName = s.DisplayName
|
||||
c.Bio = s.Bio
|
||||
if s.PreferredName != nil {
|
||||
c.EnsName = *s.PreferredName
|
||||
}
|
||||
}
|
||||
if socialLinks, err := settings.GetSocialLinks(); err != nil {
|
||||
c.SocialLinks = socialLinks
|
||||
}
|
||||
}
|
||||
|
||||
if multiAccounts != nil && account != nil {
|
||||
if identityImages, err := multiAccounts.GetIdentityImages(account.KeyUID); err != nil {
|
||||
imagesMap := make(map[string]images.IdentityImage)
|
||||
for _, img := range identityImages {
|
||||
imagesMap[img.Name] = *img
|
||||
}
|
||||
|
||||
c.Images = imagesMap
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func contactIDFromPublicKey(key *ecdsa.PublicKey) string {
|
||||
return types.EncodeHex(crypto.FromECDSAPub(key))
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ type StatusUnfurler struct {
|
|||
func NewStatusUnfurler(URL string, messenger *Messenger, logger *zap.Logger) *StatusUnfurler {
|
||||
return &StatusUnfurler{
|
||||
m: messenger,
|
||||
logger: logger,
|
||||
logger: logger.With(zap.String("url", URL)),
|
||||
url: URL,
|
||||
}
|
||||
}
|
||||
|
@ -30,18 +30,20 @@ func updateThumbnail(image *images.IdentityImage, thumbnail *common.LinkPreviewT
|
|||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
thumbnail.Width, thumbnail.Height, err = images.GetImageDimensions(image.Payload)
|
||||
width, height, err := images.GetImageDimensions(image.Payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get image dimensions: %w", err)
|
||||
}
|
||||
|
||||
thumbnail.DataURI, err = image.GetDataURI()
|
||||
dataURI, err := image.GetDataURI()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get data uri: %w", err)
|
||||
}
|
||||
|
||||
thumbnail.Width = width
|
||||
thumbnail.Height = height
|
||||
thumbnail.DataURI = dataURI
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -67,7 +69,7 @@ func (u *StatusUnfurler) buildContactData(publicKey string) (*common.StatusConta
|
|||
|
||||
if image, ok := contact.Images[images.SmallDimName]; ok {
|
||||
if err = updateThumbnail(&image, &c.Icon); err != nil {
|
||||
return nil, fmt.Errorf("failed to set thumbnail: %w", err)
|
||||
u.logger.Warn("unfurling status link: failed to set contact thumbnail", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -127,6 +127,7 @@ type Messenger struct {
|
|||
shouldPublishContactCode bool
|
||||
systemMessagesTranslations *systemMessageTranslationsMap
|
||||
allChats *chatMap
|
||||
selfContact *Contact
|
||||
allContacts *contactMap
|
||||
allInstallations *installationMap
|
||||
modifiedInstallations *stringBoolMap
|
||||
|
@ -479,10 +480,9 @@ func NewMessenger(
|
|||
|
||||
savedAddressesManager := wallet.NewSavedAddressesManager(c.walletDb)
|
||||
|
||||
myPublicKeyString := types.EncodeHex(crypto.FromECDSAPub(&identity.PublicKey))
|
||||
myContact, err := buildContact(myPublicKeyString, &identity.PublicKey)
|
||||
selfContact, err := buildSelfContact(identity, settings, c.multiAccount, c.account)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build contact of ourself: " + err.Error())
|
||||
return nil, fmt.Errorf("failed to build contact of ourself: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
@ -511,9 +511,10 @@ func NewMessenger(
|
|||
featureFlags: c.featureFlags,
|
||||
systemMessagesTranslations: c.systemMessagesTranslations,
|
||||
allChats: new(chatMap),
|
||||
selfContact: selfContact,
|
||||
allContacts: &contactMap{
|
||||
logger: logger,
|
||||
me: myContact,
|
||||
me: selfContact,
|
||||
},
|
||||
allInstallations: new(installationMap),
|
||||
installationID: installationID,
|
||||
|
@ -816,6 +817,7 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
|
|||
return nil, err
|
||||
}
|
||||
m.startSyncSettingsLoop()
|
||||
m.startSettingsChangesLoop()
|
||||
m.startCommunityRekeyLoop()
|
||||
m.startCuratedCommunitiesUpdateLoop()
|
||||
|
||||
|
@ -1547,14 +1549,28 @@ func (m *Messenger) watchIdentityImageChanges() {
|
|||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-channel:
|
||||
err := m.syncProfilePictures(m.dispatchMessage)
|
||||
case change := <-channel:
|
||||
identityImages, err := m.multiAccounts.GetIdentityImages(m.account.KeyUID)
|
||||
if err != nil {
|
||||
m.logger.Error("failed to sync profile pictures to paired devices", zap.Error(err))
|
||||
m.logger.Error("failed to get profile pictures to save self contact", zap.Error(err))
|
||||
break
|
||||
}
|
||||
err = m.PublishIdentityImage()
|
||||
if err != nil {
|
||||
m.logger.Error("failed to publish identity image", zap.Error(err))
|
||||
|
||||
identityImagesMap := make(map[string]images.IdentityImage)
|
||||
for _, img := range identityImages {
|
||||
identityImagesMap[img.Name] = *img
|
||||
}
|
||||
m.selfContact.Images = identityImagesMap
|
||||
|
||||
if change.PublishExpected {
|
||||
err = m.syncProfilePictures(m.dispatchMessage, identityImages)
|
||||
if err != nil {
|
||||
m.logger.Error("failed to sync profile pictures to paired devices", zap.Error(err))
|
||||
}
|
||||
err = m.PublishIdentityImage()
|
||||
if err != nil {
|
||||
m.logger.Error("failed to publish identity image", zap.Error(err))
|
||||
}
|
||||
}
|
||||
case <-m.quit:
|
||||
return
|
||||
|
@ -2464,23 +2480,26 @@ func (m *Messenger) ShareImageMessage(request *requests.ShareImageMessage) (*Mes
|
|||
return response, nil
|
||||
}
|
||||
|
||||
func (m *Messenger) syncProfilePictures(rawMessageHandler RawMessageHandler) error {
|
||||
if !m.hasPairedDevices() {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Messenger) syncProfilePicturesFromDatabase(rawMessageHandler RawMessageHandler) error {
|
||||
keyUID := m.account.KeyUID
|
||||
images, err := m.multiAccounts.GetIdentityImages(keyUID)
|
||||
identityImages, err := m.multiAccounts.GetIdentityImages(keyUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.syncProfilePictures(rawMessageHandler, identityImages)
|
||||
}
|
||||
|
||||
func (m *Messenger) syncProfilePictures(rawMessageHandler RawMessageHandler, identityImages []*images.IdentityImage) error {
|
||||
if !m.hasPairedDevices() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pictures := make([]*protobuf.SyncProfilePicture, len(images))
|
||||
pictures := make([]*protobuf.SyncProfilePicture, len(identityImages))
|
||||
clock, chat := m.getLastClockWithRelatedChat()
|
||||
for i, image := range images {
|
||||
for i, image := range identityImages {
|
||||
p := &protobuf.SyncProfilePicture{}
|
||||
p.Name = image.Name
|
||||
p.Payload = image.Payload
|
||||
|
@ -2497,7 +2516,7 @@ func (m *Messenger) syncProfilePictures(rawMessageHandler RawMessageHandler) err
|
|||
}
|
||||
|
||||
message := &protobuf.SyncProfilePictures{}
|
||||
message.KeyUid = keyUID
|
||||
message.KeyUid = m.account.KeyUID
|
||||
message.Pictures = pictures
|
||||
|
||||
encodedMessage, err := proto.Marshal(message)
|
||||
|
@ -2606,7 +2625,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string,
|
|||
return err
|
||||
}
|
||||
|
||||
err = m.syncProfilePictures(rawMessageHandler)
|
||||
err = m.syncProfilePicturesFromDatabase(rawMessageHandler)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -756,8 +756,14 @@ func (m *Messenger) BlockedContacts() []*Contact {
|
|||
return contacts
|
||||
}
|
||||
|
||||
// GetContactByID assumes pubKey includes 0x prefix
|
||||
// GetContactByID returns a Contact for given pubKey, if it's known.
|
||||
// This function automatically checks if pubKey is self identity key and returns a Contact
|
||||
// filled with self information.
|
||||
// pubKey is assumed to include `0x` prefix
|
||||
func (m *Messenger) GetContactByID(pubKey string) *Contact {
|
||||
if pubKey == m.IdentityPublicKeyString() {
|
||||
return m.selfContact
|
||||
}
|
||||
contact, _ := m.allContacts.Load(pubKey)
|
||||
return contact
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
package protocol
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/multiaccounts/settings"
|
||||
"github.com/status-im/status-go/protocol/identity"
|
||||
)
|
||||
|
||||
func TestMessengerContacts(t *testing.T) {
|
||||
suite.Run(t, new(MessengerContactsTestSuite))
|
||||
}
|
||||
|
||||
type MessengerContactsTestSuite struct {
|
||||
MessengerBaseTestSuite
|
||||
}
|
||||
|
||||
func (s *MessengerContactsTestSuite) Test_SelfContact() {
|
||||
const timeout = 1 * time.Second
|
||||
|
||||
profileKp := accounts.GetProfileKeypairForTest(true, false, false)
|
||||
profileKp.KeyUID = s.m.account.KeyUID
|
||||
profileKp.Accounts[0].KeyUID = s.m.account.KeyUID
|
||||
|
||||
err := s.m.settings.SaveOrUpdateKeypair(profileKp)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Create values
|
||||
|
||||
displayName := "DisplayName_1"
|
||||
bio := "Bio_1"
|
||||
ensName := "EnsName_1.eth"
|
||||
socialLinks := identity.SocialLinks{{Text: identity.TelegramID, URL: "dummy.telegram"}}
|
||||
identityImages := images.SampleIdentityImages()
|
||||
|
||||
identityImagesMap := make(map[string]images.IdentityImage)
|
||||
for _, img := range identityImages {
|
||||
img.KeyUID = s.m.account.KeyUID
|
||||
identityImagesMap[img.Name] = img
|
||||
}
|
||||
|
||||
// Set values stored in settings
|
||||
|
||||
settingsList := []string{settings.DisplayName.GetReactName(), settings.PreferredName.GetReactName(), settings.Bio.GetReactName()}
|
||||
setSettingsValues := func() {
|
||||
err := s.m.SetDisplayName(displayName)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.m.SetBio(bio)
|
||||
s.Require().NoError(err)
|
||||
|
||||
err = s.m.settings.SaveSettingField(settings.PreferredName, ensName)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
SetSettingsAndWaitForChange(&s.Suite, s.m, settingsList, timeout, setSettingsValues)
|
||||
|
||||
// Set values stored in multiaccounts
|
||||
|
||||
setIdentityImages := func() {
|
||||
err := s.m.multiAccounts.StoreIdentityImages(s.m.account.KeyUID, identityImages, false)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
SetIdentityImagesAndWaitForChange(&s.Suite, s.m.multiAccounts, timeout, setIdentityImages)
|
||||
|
||||
// Set social links. They are applied immediately, no need to wait.
|
||||
|
||||
err = s.m.AddOrReplaceSocialLinks(socialLinks)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check values
|
||||
|
||||
selfContact := s.m.GetContactByID(s.m.IdentityPublicKeyString())
|
||||
s.Require().NotNil(selfContact)
|
||||
s.Require().Equal(displayName, selfContact.DisplayName)
|
||||
s.Require().Equal(bio, selfContact.Bio)
|
||||
s.Require().Equal(ensName, selfContact.EnsName)
|
||||
s.Require().Equal(socialLinks, selfContact.SocialLinks)
|
||||
s.Require().Equal(identityImagesMap, selfContact.Images)
|
||||
}
|
|
@ -180,6 +180,7 @@ func (m *Messenger) AddOrReplaceSocialLinks(socialLinks identity.SocialLinks) er
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.selfContact.SocialLinks = socialLinks
|
||||
|
||||
err = m.syncSocialLinks(context.Background(), m.dispatchMessage)
|
||||
return err
|
||||
|
|
|
@ -15,12 +15,19 @@ import (
|
|||
|
||||
"github.com/status-im/status-go/eth-node/crypto"
|
||||
"github.com/status-im/status-go/images"
|
||||
"github.com/status-im/status-go/multiaccounts/accounts"
|
||||
"github.com/status-im/status-go/multiaccounts/settings"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/protocol/requests"
|
||||
)
|
||||
|
||||
const (
|
||||
exampleIdenticonURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixA" +
|
||||
"AAAiklEQVR4nOzWwQmFQAwG4ffEXmzLIizDImzLarQBhSwSGH7mO+9hh0DI9AthCI0hNIbQGEJjCI0hNIbQxITM1YfHfl69X3m2bsu/8i5mI" +
|
||||
"obQGEJjCI0hNIbQlG+tUW83UtfNFjMRQ2gMofm8tUa3U9c2i5mIITSGqEnMRAyhMYTGEBpDaO4AAAD//5POEGncqtj1AAAAAElFTkSuQmCC"
|
||||
)
|
||||
|
||||
func TestMessengerLinkPreviews(t *testing.T) {
|
||||
suite.Run(t, new(MessengerLinkPreviewsTestSuite))
|
||||
}
|
||||
|
@ -394,9 +401,7 @@ func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusContactAdded() {
|
|||
shortKey, err := s.m.SerializePublicKey(crypto.CompressPubkey(pubkey))
|
||||
s.Require().NoError(err)
|
||||
|
||||
payload, err := images.GetPayloadFromURI("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixA" +
|
||||
"AAAiklEQVR4nOzWwQmFQAwG4ffEXmzLIizDImzLarQBhSwSGH7mO+9hh0DI9AthCI0hNIbQGEJjCI0hNIbQxITM1YfHfl69X3m2bsu/8i5mI" +
|
||||
"obQGEJjCI0hNIbQlG+tUW83UtfNFjMRQ2gMofm8tUa3U9c2i5mIITSGqEnMRAyhMYTGEBpDaO4AAAD//5POEGncqtj1AAAAAElFTkSuQmCC")
|
||||
payload, err := images.GetPayloadFromURI(exampleIdenticonURI)
|
||||
s.Require().NoError(err)
|
||||
|
||||
icon := images.IdentityImage{
|
||||
|
@ -406,7 +411,7 @@ func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusContactAdded() {
|
|||
}
|
||||
|
||||
c.Bio = "TestBio_1"
|
||||
c.DisplayName = "TestDisplayName_2"
|
||||
c.DisplayName = "TestDisplayName_1"
|
||||
c.Images = map[string]images.IdentityImage{}
|
||||
c.Images[images.SmallDimName] = icon
|
||||
s.m.allContacts.Store(c.ID, c)
|
||||
|
@ -443,6 +448,84 @@ func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusContactAdded() {
|
|||
s.Require().Equal(expectedDataURI, preview.Contact.Icon.DataURI)
|
||||
}
|
||||
|
||||
func (s *MessengerLinkPreviewsTestSuite) setProfileParameters(messenger *Messenger, displayName string, bio string, identityImages []images.IdentityImage) {
|
||||
const timeout = 1 * time.Second
|
||||
|
||||
settingsList := []string{
|
||||
settings.DisplayName.GetReactName(),
|
||||
settings.Bio.GetReactName(),
|
||||
}
|
||||
|
||||
SetSettingsAndWaitForChange(&s.Suite, messenger, settingsList, timeout, func() {
|
||||
err := messenger.SetDisplayName(displayName)
|
||||
s.Require().NoError(err)
|
||||
err = messenger.SetBio(bio)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
|
||||
SetIdentityImagesAndWaitForChange(&s.Suite, messenger.multiAccounts, timeout, func() {
|
||||
err := messenger.multiAccounts.StoreIdentityImages(messenger.account.KeyUID, identityImages, false)
|
||||
s.Require().NoError(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_SelfLink() {
|
||||
shortKey, err := s.m.SerializePublicKey(crypto.CompressPubkey(s.m.IdentityPublicKey()))
|
||||
s.Require().NoError(err)
|
||||
|
||||
profileKp := accounts.GetProfileKeypairForTest(true, false, false)
|
||||
profileKp.KeyUID = s.m.account.KeyUID
|
||||
profileKp.Accounts[0].KeyUID = s.m.account.KeyUID
|
||||
|
||||
err = s.m.settings.SaveOrUpdateKeypair(profileKp)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Set initial profile parameters
|
||||
identityImages := images.SampleIdentityImages()
|
||||
s.setProfileParameters(s.m, "TestDisplayName_3", "TestBio_3", identityImages)
|
||||
|
||||
// Generate a shared URL
|
||||
u, err := s.m.ShareUserURLWithData(s.m.IdentityPublicKeyString())
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Update contact info locally after creating the shared URL
|
||||
// This is required to test that URL-decoded data is not used in the preview.
|
||||
iconPayload, err := images.GetPayloadFromURI(exampleIdenticonURI)
|
||||
s.Require().NoError(err)
|
||||
icon := images.IdentityImage{
|
||||
Name: images.SmallDimName,
|
||||
Width: 50,
|
||||
Height: 50,
|
||||
Payload: iconPayload,
|
||||
}
|
||||
s.setProfileParameters(s.m, "TestDisplayName_4", "TestBio_4", []images.IdentityImage{icon})
|
||||
|
||||
r, err := s.m.UnfurlURLs(nil, []string{u})
|
||||
s.Require().NoError(err)
|
||||
s.Require().Len(r.StatusLinkPreviews, 1)
|
||||
s.Require().Len(r.LinkPreviews, 0)
|
||||
|
||||
userSettings, err := s.m.getSettings()
|
||||
s.Require().NoError(err)
|
||||
|
||||
preview := r.StatusLinkPreviews[0]
|
||||
s.Require().Equal(u, preview.URL)
|
||||
s.Require().Nil(preview.Community)
|
||||
s.Require().Nil(preview.Channel)
|
||||
s.Require().NotNil(preview.Contact)
|
||||
s.Require().Equal(shortKey, preview.Contact.PublicKey)
|
||||
s.Require().Equal(userSettings.DisplayName, preview.Contact.DisplayName)
|
||||
s.Require().Equal(userSettings.Bio, preview.Contact.Description)
|
||||
|
||||
s.Require().Equal(icon.Width, preview.Contact.Icon.Width)
|
||||
s.Require().Equal(icon.Height, preview.Contact.Icon.Height)
|
||||
s.Require().Equal("", preview.Contact.Icon.URL)
|
||||
|
||||
expectedDataURI, err := images.GetPayloadDataURI(icon.Payload)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(expectedDataURI, preview.Contact.Icon.DataURI)
|
||||
}
|
||||
|
||||
func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusCommunityJoined() {
|
||||
|
||||
description := &requests.CreateCommunity{
|
||||
|
|
|
@ -435,8 +435,8 @@ func (m *Messenger) parseUserURLWithChatKey(urlData string) (*URLDataResponse, e
|
|||
|
||||
contactID := common.PubkeyToHex(pubKey)
|
||||
|
||||
contact, ok := m.allContacts.Load(contactID)
|
||||
if !ok {
|
||||
contact := m.GetContactByID(contactID)
|
||||
if contact == nil {
|
||||
return nil, ErrContactNotFound
|
||||
}
|
||||
|
||||
|
@ -446,8 +446,8 @@ func (m *Messenger) parseUserURLWithChatKey(urlData string) (*URLDataResponse, e
|
|||
}
|
||||
|
||||
func (m *Messenger) ShareUserURLWithENS(contactID string) (string, error) {
|
||||
contact, ok := m.allContacts.Load(contactID)
|
||||
if !ok {
|
||||
contact := m.GetContactByID(contactID)
|
||||
if contact == nil {
|
||||
return "", ErrContactNotFound
|
||||
}
|
||||
return fmt.Sprintf("%s/u#%s", baseShareURL, contact.EnsName), nil
|
||||
|
@ -497,8 +497,8 @@ func (m *Messenger) prepareEncodedUserData(contact *Contact) (string, string, er
|
|||
}
|
||||
|
||||
func (m *Messenger) ShareUserURLWithData(contactID string) (string, error) {
|
||||
contact, ok := m.allContacts.Load(contactID)
|
||||
if !ok {
|
||||
contact := m.GetContactByID(contactID)
|
||||
if contact == nil {
|
||||
return "", ErrContactNotFound
|
||||
}
|
||||
|
||||
|
|
|
@ -159,3 +159,24 @@ func (m *Messenger) startSyncSettingsLoop() {
|
|||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (m *Messenger) startSettingsChangesLoop() {
|
||||
channel := m.settings.SubscribeToChanges()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case s := <-channel:
|
||||
switch s.GetReactName() {
|
||||
case settings.DisplayName.GetReactName():
|
||||
m.selfContact.DisplayName = s.Value.(string)
|
||||
case settings.PreferredName.GetReactName():
|
||||
m.selfContact.EnsName = s.Value.(string)
|
||||
case settings.Bio.GetReactName():
|
||||
m.selfContact.Bio = s.Value.(string)
|
||||
}
|
||||
case <-m.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -3,10 +3,12 @@ package protocol
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
"github.com/status-im/status-go/multiaccounts"
|
||||
"github.com/status-im/status-go/protocol/common"
|
||||
"github.com/status-im/status-go/protocol/protobuf"
|
||||
"github.com/status-im/status-go/protocol/tt"
|
||||
|
@ -132,3 +134,60 @@ func PairDevices(s *suite.Suite, device1, device2 *Messenger) {
|
|||
err = device2.EnableInstallation(device1.installationID)
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func SetSettingsAndWaitForChange(s *suite.Suite, messenger *Messenger, settingsReactNames []string, timeout time.Duration, actionCallback func()) {
|
||||
changedSettings := map[string]struct{}{}
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
for _, reactName := range settingsReactNames {
|
||||
wg.Add(1)
|
||||
settingReactName := reactName // Loop variables captured by 'func' literals in 'go' statements might have unexpected values
|
||||
channel := messenger.settings.SubscribeToChanges()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case setting := <-channel:
|
||||
if setting.GetReactName() == settingReactName {
|
||||
changedSettings[settingReactName] = struct{}{}
|
||||
return
|
||||
}
|
||||
case <-time.After(timeout):
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
actionCallback()
|
||||
|
||||
wg.Wait()
|
||||
s.Require().Len(changedSettings, len(settingsReactNames))
|
||||
|
||||
for _, reactName := range settingsReactNames {
|
||||
_, ok := changedSettings[reactName]
|
||||
s.Require().True(ok)
|
||||
}
|
||||
}
|
||||
|
||||
func SetIdentityImagesAndWaitForChange(s *suite.Suite, multiAccounts *multiaccounts.Database, timeout time.Duration, actionCallback func()) {
|
||||
channel := multiAccounts.SubscribeToIdentityImageChanges()
|
||||
ok := false
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
select {
|
||||
case <-channel:
|
||||
ok = true
|
||||
case <-time.After(timeout):
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
actionCallback()
|
||||
|
||||
wg.Wait()
|
||||
s.Require().True(ok)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue