status-im/status-go

View on GitHub
protocol/messenger_pairing_and_syncing.go

Summary

Maintainability
A
0 mins
Test Coverage
D
67%
package protocol

import (
    "context"
    "errors"
    "time"

    "github.com/golang/protobuf/proto"
    "go.uber.org/zap"

    "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/protocol/common"
    "github.com/status-im/status-go/protocol/encryption/multidevice"
    "github.com/status-im/status-go/protocol/protobuf"
    "github.com/status-im/status-go/protocol/requests"
)

func (m *Messenger) EnableInstallationAndSync(request *requests.EnableInstallationAndSync) (*MessengerResponse, error) {
    if err := request.Validate(); err != nil {
        return nil, err
    }
    err := m.EnableInstallation(request.InstallationID)
    if err != nil {
        return nil, err
    }
    response, err := m.SendPairInstallation(context.Background(), nil)
    if err != nil {
        return nil, err
    }
    err = m.SyncDevices(context.Background(), "", "", nil)
    if err != nil {
        return nil, err
    }

    // Delete AC notif
    err = m.deleteNotification(response, request.InstallationID)
    if err != nil {
        return nil, err
    }

    return response, nil
}

func (m *Messenger) EnableInstallationAndPair(request *requests.EnableInstallationAndPair) (*MessengerResponse, error) {
    if err := request.Validate(); err != nil {
        return nil, err
    }

    myIdentity := crypto.CompressPubkey(&m.identity.PublicKey)
    timestamp := time.Now().UnixNano()

    installation := &multidevice.Installation{
        ID:        request.InstallationID,
        Enabled:   true,
        Version:   2,
        Timestamp: timestamp,
    }

    _, err := m.encryptor.AddInstallation(myIdentity, timestamp, installation, true)
    if err != nil {
        return nil, err
    }
    i, ok := m.allInstallations.Load(request.InstallationID)
    if !ok {
        i = installation
    } else {
        i.Enabled = true
    }
    m.allInstallations.Store(request.InstallationID, i)
    response, err := m.SendPairInstallation(context.Background(), nil)
    if err != nil {
        return nil, err
    }

    notification := &ActivityCenterNotification{
        ID:             types.FromHex(request.InstallationID),
        Type:           ActivityCenterNotificationTypeNewInstallationCreated,
        InstallationID: m.installationID, // Put our own installation ID, as we're the initiator of the pairing
        Timestamp:      m.getTimesource().GetCurrentTime(),
        Read:           false,
        Deleted:        false,
        UpdatedAt:      m.GetCurrentTimeInMillis(),
    }

    err = m.addActivityCenterNotification(response, notification, nil)
    if err != nil {
        return nil, err
    }
    return response, err
}

// SendPairInstallation sends a pair installation message
func (m *Messenger) SendPairInstallation(ctx context.Context, rawMessageHandler RawMessageHandler) (*MessengerResponse, error) {
    var err error
    var response MessengerResponse

    installation, ok := m.allInstallations.Load(m.installationID)
    if !ok {
        return nil, errors.New("no installation found")
    }

    if installation.InstallationMetadata == nil {
        return nil, errors.New("no installation metadata")
    }

    clock, chat := m.getLastClockWithRelatedChat()

    pairMessage := &protobuf.SyncPairInstallation{
        Clock:          clock,
        Name:           installation.InstallationMetadata.Name,
        InstallationId: installation.ID,
        DeviceType:     installation.InstallationMetadata.DeviceType,
        Version:        installation.Version}
    encodedMessage, err := proto.Marshal(pairMessage)
    if err != nil {
        return nil, err
    }

    if rawMessageHandler == nil {
        rawMessageHandler = m.dispatchPairInstallationMessage
    }
    _, err = rawMessageHandler(ctx, common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_SYNC_PAIR_INSTALLATION,
        ResendType:  common.ResendTypeDataSync,
    })
    if err != nil {
        return nil, err
    }

    response.AddChat(chat)

    chat.LastClockValue = clock
    err = m.saveChat(chat)
    if err != nil {
        return nil, err
    }
    return &response, nil
}

// SyncDevices sends all public chats and contacts to paired devices
// TODO remove use of photoPath in contacts
func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string, rawMessageHandler RawMessageHandler) (err error) {
    if rawMessageHandler == nil {
        rawMessageHandler = m.dispatchMessage
    }

    myID := contactIDFromPublicKey(&m.identity.PublicKey)

    displayName, err := m.settings.DisplayName()
    if err != nil {
        return err
    }

    if _, err = m.sendContactUpdate(ctx, myID, displayName, ensName, photoPath, m.account.GetCustomizationColor(), rawMessageHandler); err != nil {
        return err
    }

    m.allChats.Range(func(chatID string, chat *Chat) bool {
        if !chat.shouldBeSynced() {
            return true

        }
        err = m.syncChat(ctx, chat, rawMessageHandler)
        return err == nil
    })
    if err != nil {
        return err
    }

    m.allContacts.Range(func(contactID string, contact *Contact) bool {
        if contact.ID == myID {
            return true
        }
        if contact.LocalNickname != "" || contact.added() || contact.hasAddedUs() || contact.Blocked {
            if err = m.syncContact(ctx, contact, rawMessageHandler); err != nil {
                return false
            }
        }
        return true
    })

    cs, err := m.communitiesManager.JoinedAndPendingCommunitiesWithRequests()
    if err != nil {
        return err
    }
    for _, c := range cs {
        if err = m.syncCommunity(ctx, c, rawMessageHandler); err != nil {
            return err
        }
    }

    bookmarks, err := m.browserDatabase.GetBookmarks()
    if err != nil {
        return err
    }
    for _, b := range bookmarks {
        if err = m.SyncBookmark(ctx, b, rawMessageHandler); err != nil {
            return err
        }
    }

    trustedUsers, err := m.verificationDatabase.GetAllTrustStatus()
    if err != nil {
        return err
    }
    for id, ts := range trustedUsers {
        if err = m.SyncTrustedUser(ctx, id, ts, rawMessageHandler); err != nil {
            return err
        }
    }

    verificationRequests, err := m.verificationDatabase.GetVerificationRequests()
    if err != nil {
        return err
    }
    for i := range verificationRequests {
        if err = m.SyncVerificationRequest(ctx, &verificationRequests[i], rawMessageHandler); err != nil {
            return err
        }
    }

    err = m.syncSettings(rawMessageHandler)
    if err != nil {
        return err
    }

    err = m.syncProfilePicturesFromDatabase(rawMessageHandler)
    if err != nil {
        return err
    }

    if err = m.syncLatestContactRequests(ctx, rawMessageHandler); err != nil {
        return err
    }

    // we have to sync deleted keypairs as well
    keypairs, err := m.settings.GetAllKeypairs()
    if err != nil {
        return err
    }

    for _, kp := range keypairs {
        err = m.syncKeypair(kp, rawMessageHandler)
        if err != nil {
            return err
        }
    }

    // we have to sync deleted watch only accounts as well
    woAccounts, err := m.settings.GetAllWatchOnlyAccounts()
    if err != nil {
        return err
    }

    for _, woAcc := range woAccounts {
        err = m.syncWalletAccount(woAcc, rawMessageHandler)
        if err != nil {
            return err
        }
    }

    savedAddresses, err := m.savedAddressesManager.GetRawSavedAddresses()
    if err != nil {
        return err
    }

    for i := range savedAddresses {
        sa := savedAddresses[i]

        err = m.syncSavedAddress(ctx, sa, rawMessageHandler)
        if err != nil {
            return err
        }
    }

    if err = m.syncEnsUsernameDetails(ctx, rawMessageHandler); err != nil {
        return err
    }

    if err = m.syncDeleteForMeMessage(ctx, rawMessageHandler); err != nil {
        return err
    }

    err = m.syncAccountsPositions(rawMessageHandler)
    if err != nil {
        return err
    }

    err = m.syncProfileShowcasePreferences(context.Background(), rawMessageHandler)
    if err != nil {
        return err
    }

    return nil
}

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(identityImages))
    clock, chat := m.getLastClockWithRelatedChat()
    for i, image := range identityImages {
        p := &protobuf.SyncProfilePicture{}
        p.Name = image.Name
        p.Payload = image.Payload
        p.Width = uint32(image.Width)
        p.Height = uint32(image.Height)
        p.FileSize = uint32(image.FileSize)
        p.ResizeTarget = uint32(image.ResizeTarget)
        if image.Clock == 0 {
            p.Clock = clock
        } else {
            p.Clock = image.Clock
        }
        pictures[i] = p
    }

    message := &protobuf.SyncProfilePictures{}
    message.KeyUid = m.account.KeyUID
    message.Pictures = pictures

    encodedMessage, err := proto.Marshal(message)
    if err != nil {
        return err
    }

    rawMessage := common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_SYNC_PROFILE_PICTURES,
        ResendType:  common.ResendTypeDataSync,
    }

    _, err = rawMessageHandler(ctx, rawMessage)
    if err != nil {
        return err
    }

    chat.LastClockValue = clock
    return m.saveChat(chat)
}

func (m *Messenger) syncLatestContactRequests(ctx context.Context, rawMessageHandler RawMessageHandler) error {
    latestContactRequests, err := m.persistence.LatestContactRequests()

    if err != nil {
        return err
    }

    for _, r := range latestContactRequests {
        if r.ContactRequestState == common.ContactRequestStateAccepted || r.ContactRequestState == common.ContactRequestStateDismissed {
            accepted := r.ContactRequestState == common.ContactRequestStateAccepted
            err = m.syncContactRequestDecision(ctx, r.MessageID, r.ContactID, accepted, rawMessageHandler)
            if err != nil {
                return err
            }
        }
    }
    return nil
}

func (m *Messenger) syncContactRequestDecision(ctx context.Context, requestID, contactId string, accepted bool, rawMessageHandler RawMessageHandler) error {
    m.logger.Info("syncContactRequestDecision", zap.Any("from", requestID))
    if !m.hasPairedDevices() {
        return nil
    }

    clock, chat := m.getLastClockWithRelatedChat()

    var status protobuf.SyncContactRequestDecision_DecisionStatus
    if accepted {
        status = protobuf.SyncContactRequestDecision_ACCEPTED
    } else {
        status = protobuf.SyncContactRequestDecision_DECLINED
    }

    message := &protobuf.SyncContactRequestDecision{
        RequestId:      requestID,
        ContactId:      contactId,
        Clock:          clock,
        DecisionStatus: status,
    }

    encodedMessage, err := proto.Marshal(message)
    if err != nil {
        return err
    }

    rawMessage := common.RawMessage{
        LocalChatID: chat.ID,
        Payload:     encodedMessage,
        MessageType: protobuf.ApplicationMetadataMessage_SYNC_CONTACT_REQUEST_DECISION,
        ResendType:  common.ResendTypeDataSync,
    }

    _, err = rawMessageHandler(ctx, rawMessage)
    if err != nil {
        return err
    }

    return nil
}

func (m *Messenger) getLastClockWithRelatedChat() (uint64, *Chat) {
    chatID := contactIDFromPublicKey(&m.identity.PublicKey)

    chat, ok := m.allChats.Load(chatID)
    if !ok {
        chat = OneToOneFromPublicKey(&m.identity.PublicKey, m.getTimesource())
        // We don't want to show the chat to the user
        chat.Active = false
    }

    m.allChats.Store(chat.ID, chat)
    clock, _ := chat.NextClockAndTimestamp(m.getTimesource())

    return clock, chat
}

func (m *Messenger) syncProfilePicturesFromDatabase(rawMessageHandler RawMessageHandler) error {
    keyUID := m.account.KeyUID
    identityImages, err := m.multiAccounts.GetIdentityImages(keyUID)
    if err != nil {
        return err
    }
    return m.syncProfilePictures(rawMessageHandler, identityImages)
}

func (m *Messenger) InitInstallations() error {
    installations, err := m.encryptor.GetOurInstallations(&m.identity.PublicKey)
    if err != nil {
        return err
    }

    for _, installation := range installations {
        m.allInstallations.Store(installation.ID, installation)
    }

    err = m.setInstallationHostname()
    if err != nil {
        return err
    }

    if m.telemetryClient != nil {
        installation, ok := m.allInstallations.Load(m.installationID)
        if ok {
            m.telemetryClient.SetDeviceType(installation.InstallationMetadata.DeviceType)
        }
    }

    return nil
}

func (m *Messenger) Installations() []*multidevice.Installation {
    installations := make([]*multidevice.Installation, m.allInstallations.Len())

    var i = 0
    m.allInstallations.Range(func(installationID string, installation *multidevice.Installation) (shouldContinue bool) {
        installations[i] = installation
        i++
        return true
    })
    return installations
}

func (m *Messenger) setInstallationMetadata(id string, data *multidevice.InstallationMetadata) error {
    installation, ok := m.allInstallations.Load(id)
    if !ok {
        return errors.New("no installation found")
    }

    installation.InstallationMetadata = data
    return m.encryptor.SetInstallationMetadata(m.IdentityPublicKey(), id, data)
}

func (m *Messenger) SetInstallationMetadata(id string, data *multidevice.InstallationMetadata) error {
    return m.setInstallationMetadata(id, data)
}

func (m *Messenger) SetInstallationName(id string, name string) error {
    installation, ok := m.allInstallations.Load(id)
    if !ok {
        return errors.New("no installation found")
    }

    installation.InstallationMetadata.Name = name
    return m.encryptor.SetInstallationName(m.IdentityPublicKey(), id, name)
}

func (m *Messenger) EnableInstallation(id string) error {
    installation, ok := m.allInstallations.Load(id)
    if !ok {
        return errors.New("no installation found")
    }

    err := m.encryptor.EnableInstallation(&m.identity.PublicKey, id)
    if err != nil {
        return err
    }
    installation.Enabled = true
    // TODO(samyoul) remove storing of an updated reference pointer?
    m.allInstallations.Store(id, installation)
    return nil
}

func (m *Messenger) DisableInstallation(id string) error {
    installation, ok := m.allInstallations.Load(id)
    if !ok {
        return errors.New("no installation found")
    }

    err := m.encryptor.DisableInstallation(&m.identity.PublicKey, id)
    if err != nil {
        return err
    }
    installation.Enabled = false
    // TODO(samyoul) remove storing of an updated reference pointer?
    m.allInstallations.Store(id, installation)
    return nil
}