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
|
ToMultiAccount() *Account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IdentityImageSubscriptionChange struct {
|
||||||
|
PublishExpected bool
|
||||||
|
}
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
identityImageSubscriptions []chan struct{}
|
identityImageSubscriptions []chan *IdentityImageSubscriptionChange
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitializeDB creates db file at a given path and applies migrations.
|
// 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(&IdentityImageSubscriptionChange{
|
||||||
db.publishOnIdentityImageSubscriptions()
|
PublishExpected: publish,
|
||||||
}
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) SubscribeToIdentityImageChanges() chan struct{} {
|
func (db *Database) SubscribeToIdentityImageChanges() chan *IdentityImageSubscriptionChange {
|
||||||
s := make(chan struct{}, 100)
|
s := make(chan *IdentityImageSubscriptionChange, 100)
|
||||||
db.identityImageSubscriptions = append(db.identityImageSubscriptions, s)
|
db.identityImageSubscriptions = append(db.identityImageSubscriptions, s)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) publishOnIdentityImageSubscriptions() {
|
func (db *Database) publishOnIdentityImageSubscriptions(change *IdentityImageSubscriptionChange) {
|
||||||
// Publish on channels, drop if buffer is full
|
// Publish on channels, drop if buffer is full
|
||||||
for _, s := range db.identityImageSubscriptions {
|
for _, s := range db.identityImageSubscriptions {
|
||||||
select {
|
select {
|
||||||
case s <- struct{}{}:
|
case s <- change:
|
||||||
default:
|
default:
|
||||||
log.Warn("subscription channel full, dropping message")
|
log.Warn("subscription channel full, dropping message")
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
||||||
"github.com/status-im/status-go/common/dbsetup"
|
"github.com/status-im/status-go/common/dbsetup"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/multiaccounts/errors"
|
"github.com/status-im/status-go/multiaccounts/errors"
|
||||||
|
@ -28,9 +30,10 @@ var (
|
||||||
|
|
||||||
// Database sql wrapper for operations with browser objects.
|
// Database sql wrapper for operations with browser objects.
|
||||||
type Database struct {
|
type Database struct {
|
||||||
db *sql.DB
|
db *sql.DB
|
||||||
SyncQueue chan SyncSettingField
|
SyncQueue chan SyncSettingField
|
||||||
notifier Notifier
|
changesSubscriptions []chan *SyncSettingField
|
||||||
|
notifier Notifier
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeNewDB ensures that a singleton instance of Database is returned per sqlite db file
|
// 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) {
|
if sf.CanSync(FromInterface) {
|
||||||
db.SyncQueue <- SyncSettingField{sf, value}
|
db.SyncQueue <- SyncSettingField{sf, value}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.postChangesToSubscribers(&SyncSettingField{sf, value})
|
||||||
|
|
||||||
return nil
|
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()
|
// 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 {
|
func (db *Database) SaveSyncSetting(setting SettingField, value interface{}, clock uint64) error {
|
||||||
ls, err := db.GetSettingLastSynced(setting)
|
ls, err := db.GetSettingLastSynced(setting)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -295,7 +302,13 @@ func (db *Database) SaveSyncSetting(setting SettingField, value interface{}, clo
|
||||||
return err
|
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) {
|
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
|
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/crypto"
|
||||||
"github.com/status-im/status-go/eth-node/types"
|
"github.com/status-im/status-go/eth-node/types"
|
||||||
"github.com/status-im/status-go/images"
|
"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/multiaccounts/settings"
|
||||||
"github.com/status-im/status-go/protocol/common"
|
"github.com/status-im/status-go/protocol/common"
|
||||||
"github.com/status-im/status-go/protocol/identity"
|
"github.com/status-im/status-go/protocol/identity"
|
||||||
|
@ -386,6 +388,41 @@ func buildContact(publicKeyString string, publicKey *ecdsa.PublicKey) (*Contact,
|
||||||
return contact, nil
|
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 {
|
func contactIDFromPublicKey(key *ecdsa.PublicKey) string {
|
||||||
return types.EncodeHex(crypto.FromECDSAPub(key))
|
return types.EncodeHex(crypto.FromECDSAPub(key))
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ type StatusUnfurler struct {
|
||||||
func NewStatusUnfurler(URL string, messenger *Messenger, logger *zap.Logger) *StatusUnfurler {
|
func NewStatusUnfurler(URL string, messenger *Messenger, logger *zap.Logger) *StatusUnfurler {
|
||||||
return &StatusUnfurler{
|
return &StatusUnfurler{
|
||||||
m: messenger,
|
m: messenger,
|
||||||
logger: logger,
|
logger: logger.With(zap.String("url", URL)),
|
||||||
url: URL,
|
url: URL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,18 +30,20 @@ func updateThumbnail(image *images.IdentityImage, thumbnail *common.LinkPreviewT
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
width, height, err := images.GetImageDimensions(image.Payload)
|
||||||
|
|
||||||
thumbnail.Width, thumbnail.Height, err = images.GetImageDimensions(image.Payload)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get image dimensions: %w", err)
|
return fmt.Errorf("failed to get image dimensions: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnail.DataURI, err = image.GetDataURI()
|
dataURI, err := image.GetDataURI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get data uri: %w", err)
|
return fmt.Errorf("failed to get data uri: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thumbnail.Width = width
|
||||||
|
thumbnail.Height = height
|
||||||
|
thumbnail.DataURI = dataURI
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +69,7 @@ func (u *StatusUnfurler) buildContactData(publicKey string) (*common.StatusConta
|
||||||
|
|
||||||
if image, ok := contact.Images[images.SmallDimName]; ok {
|
if image, ok := contact.Images[images.SmallDimName]; ok {
|
||||||
if err = updateThumbnail(&image, &c.Icon); err != nil {
|
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
|
shouldPublishContactCode bool
|
||||||
systemMessagesTranslations *systemMessageTranslationsMap
|
systemMessagesTranslations *systemMessageTranslationsMap
|
||||||
allChats *chatMap
|
allChats *chatMap
|
||||||
|
selfContact *Contact
|
||||||
allContacts *contactMap
|
allContacts *contactMap
|
||||||
allInstallations *installationMap
|
allInstallations *installationMap
|
||||||
modifiedInstallations *stringBoolMap
|
modifiedInstallations *stringBoolMap
|
||||||
|
@ -479,10 +480,9 @@ func NewMessenger(
|
||||||
|
|
||||||
savedAddressesManager := wallet.NewSavedAddressesManager(c.walletDb)
|
savedAddressesManager := wallet.NewSavedAddressesManager(c.walletDb)
|
||||||
|
|
||||||
myPublicKeyString := types.EncodeHex(crypto.FromECDSAPub(&identity.PublicKey))
|
selfContact, err := buildSelfContact(identity, settings, c.multiAccount, c.account)
|
||||||
myContact, err := buildContact(myPublicKeyString, &identity.PublicKey)
|
|
||||||
if err != nil {
|
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())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
@ -511,9 +511,10 @@ func NewMessenger(
|
||||||
featureFlags: c.featureFlags,
|
featureFlags: c.featureFlags,
|
||||||
systemMessagesTranslations: c.systemMessagesTranslations,
|
systemMessagesTranslations: c.systemMessagesTranslations,
|
||||||
allChats: new(chatMap),
|
allChats: new(chatMap),
|
||||||
|
selfContact: selfContact,
|
||||||
allContacts: &contactMap{
|
allContacts: &contactMap{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
me: myContact,
|
me: selfContact,
|
||||||
},
|
},
|
||||||
allInstallations: new(installationMap),
|
allInstallations: new(installationMap),
|
||||||
installationID: installationID,
|
installationID: installationID,
|
||||||
|
@ -816,6 +817,7 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m.startSyncSettingsLoop()
|
m.startSyncSettingsLoop()
|
||||||
|
m.startSettingsChangesLoop()
|
||||||
m.startCommunityRekeyLoop()
|
m.startCommunityRekeyLoop()
|
||||||
m.startCuratedCommunitiesUpdateLoop()
|
m.startCuratedCommunitiesUpdateLoop()
|
||||||
|
|
||||||
|
@ -1547,14 +1549,28 @@ func (m *Messenger) watchIdentityImageChanges() {
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-channel:
|
case change := <-channel:
|
||||||
err := m.syncProfilePictures(m.dispatchMessage)
|
identityImages, err := m.multiAccounts.GetIdentityImages(m.account.KeyUID)
|
||||||
if err != nil {
|
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 {
|
identityImagesMap := make(map[string]images.IdentityImage)
|
||||||
m.logger.Error("failed to publish identity image", zap.Error(err))
|
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:
|
case <-m.quit:
|
||||||
return
|
return
|
||||||
|
@ -2464,23 +2480,26 @@ func (m *Messenger) ShareImageMessage(request *requests.ShareImageMessage) (*Mes
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) syncProfilePictures(rawMessageHandler RawMessageHandler) error {
|
func (m *Messenger) syncProfilePicturesFromDatabase(rawMessageHandler RawMessageHandler) error {
|
||||||
if !m.hasPairedDevices() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
keyUID := m.account.KeyUID
|
keyUID := m.account.KeyUID
|
||||||
images, err := m.multiAccounts.GetIdentityImages(keyUID)
|
identityImages, err := m.multiAccounts.GetIdentityImages(keyUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
pictures := make([]*protobuf.SyncProfilePicture, len(images))
|
pictures := make([]*protobuf.SyncProfilePicture, len(identityImages))
|
||||||
clock, chat := m.getLastClockWithRelatedChat()
|
clock, chat := m.getLastClockWithRelatedChat()
|
||||||
for i, image := range images {
|
for i, image := range identityImages {
|
||||||
p := &protobuf.SyncProfilePicture{}
|
p := &protobuf.SyncProfilePicture{}
|
||||||
p.Name = image.Name
|
p.Name = image.Name
|
||||||
p.Payload = image.Payload
|
p.Payload = image.Payload
|
||||||
|
@ -2497,7 +2516,7 @@ func (m *Messenger) syncProfilePictures(rawMessageHandler RawMessageHandler) err
|
||||||
}
|
}
|
||||||
|
|
||||||
message := &protobuf.SyncProfilePictures{}
|
message := &protobuf.SyncProfilePictures{}
|
||||||
message.KeyUid = keyUID
|
message.KeyUid = m.account.KeyUID
|
||||||
message.Pictures = pictures
|
message.Pictures = pictures
|
||||||
|
|
||||||
encodedMessage, err := proto.Marshal(message)
|
encodedMessage, err := proto.Marshal(message)
|
||||||
|
@ -2606,7 +2625,7 @@ func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = m.syncProfilePictures(rawMessageHandler)
|
err = m.syncProfilePicturesFromDatabase(rawMessageHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -756,8 +756,14 @@ func (m *Messenger) BlockedContacts() []*Contact {
|
||||||
return contacts
|
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 {
|
func (m *Messenger) GetContactByID(pubKey string) *Contact {
|
||||||
|
if pubKey == m.IdentityPublicKeyString() {
|
||||||
|
return m.selfContact
|
||||||
|
}
|
||||||
contact, _ := m.allContacts.Load(pubKey)
|
contact, _ := m.allContacts.Load(pubKey)
|
||||||
return contact
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m.selfContact.SocialLinks = socialLinks
|
||||||
|
|
||||||
err = m.syncSocialLinks(context.Background(), m.dispatchMessage)
|
err = m.syncSocialLinks(context.Background(), m.dispatchMessage)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -15,12 +15,19 @@ import (
|
||||||
|
|
||||||
"github.com/status-im/status-go/eth-node/crypto"
|
"github.com/status-im/status-go/eth-node/crypto"
|
||||||
"github.com/status-im/status-go/images"
|
"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/multiaccounts/settings"
|
||||||
"github.com/status-im/status-go/protocol/common"
|
"github.com/status-im/status-go/protocol/common"
|
||||||
"github.com/status-im/status-go/protocol/protobuf"
|
"github.com/status-im/status-go/protocol/protobuf"
|
||||||
"github.com/status-im/status-go/protocol/requests"
|
"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) {
|
func TestMessengerLinkPreviews(t *testing.T) {
|
||||||
suite.Run(t, new(MessengerLinkPreviewsTestSuite))
|
suite.Run(t, new(MessengerLinkPreviewsTestSuite))
|
||||||
}
|
}
|
||||||
|
@ -394,9 +401,7 @@ func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusContactAdded() {
|
||||||
shortKey, err := s.m.SerializePublicKey(crypto.CompressPubkey(pubkey))
|
shortKey, err := s.m.SerializePublicKey(crypto.CompressPubkey(pubkey))
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
payload, err := images.GetPayloadFromURI("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixA" +
|
payload, err := images.GetPayloadFromURI(exampleIdenticonURI)
|
||||||
"AAAiklEQVR4nOzWwQmFQAwG4ffEXmzLIizDImzLarQBhSwSGH7mO+9hh0DI9AthCI0hNIbQGEJjCI0hNIbQxITM1YfHfl69X3m2bsu/8i5mI" +
|
|
||||||
"obQGEJjCI0hNIbQlG+tUW83UtfNFjMRQ2gMofm8tUa3U9c2i5mIITSGqEnMRAyhMYTGEBpDaO4AAAD//5POEGncqtj1AAAAAElFTkSuQmCC")
|
|
||||||
s.Require().NoError(err)
|
s.Require().NoError(err)
|
||||||
|
|
||||||
icon := images.IdentityImage{
|
icon := images.IdentityImage{
|
||||||
|
@ -406,7 +411,7 @@ func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusContactAdded() {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Bio = "TestBio_1"
|
c.Bio = "TestBio_1"
|
||||||
c.DisplayName = "TestDisplayName_2"
|
c.DisplayName = "TestDisplayName_1"
|
||||||
c.Images = map[string]images.IdentityImage{}
|
c.Images = map[string]images.IdentityImage{}
|
||||||
c.Images[images.SmallDimName] = icon
|
c.Images[images.SmallDimName] = icon
|
||||||
s.m.allContacts.Store(c.ID, c)
|
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)
|
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() {
|
func (s *MessengerLinkPreviewsTestSuite) Test_UnfurlURLs_StatusCommunityJoined() {
|
||||||
|
|
||||||
description := &requests.CreateCommunity{
|
description := &requests.CreateCommunity{
|
||||||
|
|
|
@ -435,8 +435,8 @@ func (m *Messenger) parseUserURLWithChatKey(urlData string) (*URLDataResponse, e
|
||||||
|
|
||||||
contactID := common.PubkeyToHex(pubKey)
|
contactID := common.PubkeyToHex(pubKey)
|
||||||
|
|
||||||
contact, ok := m.allContacts.Load(contactID)
|
contact := m.GetContactByID(contactID)
|
||||||
if !ok {
|
if contact == nil {
|
||||||
return nil, ErrContactNotFound
|
return nil, ErrContactNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,8 +446,8 @@ func (m *Messenger) parseUserURLWithChatKey(urlData string) (*URLDataResponse, e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Messenger) ShareUserURLWithENS(contactID string) (string, error) {
|
func (m *Messenger) ShareUserURLWithENS(contactID string) (string, error) {
|
||||||
contact, ok := m.allContacts.Load(contactID)
|
contact := m.GetContactByID(contactID)
|
||||||
if !ok {
|
if contact == nil {
|
||||||
return "", ErrContactNotFound
|
return "", ErrContactNotFound
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s/u#%s", baseShareURL, contact.EnsName), nil
|
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) {
|
func (m *Messenger) ShareUserURLWithData(contactID string) (string, error) {
|
||||||
contact, ok := m.allContacts.Load(contactID)
|
contact := m.GetContactByID(contactID)
|
||||||
if !ok {
|
if contact == nil {
|
||||||
return "", ErrContactNotFound
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"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/common"
|
||||||
"github.com/status-im/status-go/protocol/protobuf"
|
"github.com/status-im/status-go/protocol/protobuf"
|
||||||
"github.com/status-im/status-go/protocol/tt"
|
"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)
|
err = device2.EnableInstallation(device1.installationID)
|
||||||
s.Require().NoError(err)
|
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