Skip to content

Commit

Permalink
feat(onboarding)_: add a notification when importing old account (#6255)
Browse files Browse the repository at this point in the history
* feat(onboarding)_: add a notification when importing old account

Needed for status-im/status-desktop#17028

When a user imports an old account, we start fetching the backups. Now, we show an activity center notification to let the user know, because the onboarding doesn't block during the fetching anymore, so it happens in the background.

After a timeout, the notification turns into a failure state (full or partial). The user can then restart the fetching.

I added a test to validate the logic of calculating if the fetching was a success. That logic used to be in Nim.

* fix_: address pr comments

* fix: proper cancel backupFetchingTimeout

---------

Co-authored-by: Igor Sirotin <sirotin@status.im>
  • Loading branch information
jrainville and igor-sirotin authored Feb 5, 2025
1 parent 712fc66 commit 0526d18
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 1 deletion.
4 changes: 4 additions & 0 deletions protocol/activity_center.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const (
ActivityCenterNotificationTypeCommunityUnbanned
ActivityCenterNotificationTypeNewInstallationReceived
ActivityCenterNotificationTypeNewInstallationCreated
ActivityCenterNotificationTypeBackupSyncingFetching
ActivityCenterNotificationTypeBackupSyncingSuccess
ActivityCenterNotificationTypeBackupSyncingPartialFailure
ActivityCenterNotificationTypeBackupSyncingFailure
)

type ActivityCenterMembershipStatus int
Expand Down
14 changes: 14 additions & 0 deletions protocol/messenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ type Messenger struct {
peersyncingRequests map[string]uint64

mvdsStatusChangeEvent chan datasyncnode.PeerStatusChangeEvent

backedUpFetchingStatus *BackupFetchingStatus
}

type EnvelopeEventsInterceptor struct {
Expand Down Expand Up @@ -917,6 +919,18 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
}
}

if m.processBackedupMessages {
m.backedUpFetchingStatus = &BackupFetchingStatus{
dataProgress: make(map[string]FetchingBackedUpDataTracking),
lastKnownMsgClock: 0,
fetchingCompleted: false,
}
err = m.startBackupFetchingTracking(response)
if err != nil {
return nil, err
}
}

return response, nil
}

Expand Down
166 changes: 166 additions & 0 deletions protocol/messenger_backup_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package protocol

import (
"database/sql"
"sync"
"time"

"go.uber.org/zap"

"github.com/golang/protobuf/proto"

utils "github.com/status-im/status-go/common"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts/errors"
"github.com/status-im/status-go/multiaccounts/settings"
Expand All @@ -28,6 +31,20 @@ const (
SyncWakuSectionKeyWatchOnlyAccounts = "watchOnlyAccounts"
)

const backupSyncingNotificationID = "BACKUP_SYNCING"

type FetchingBackedUpDataTracking struct {
LoadedItems map[uint32]bool
TotalNumber uint32
}

type BackupFetchingStatus struct {
dataProgress map[string]FetchingBackedUpDataTracking
lastKnownMsgClock uint64
fetchingCompleted bool
fetchingCompletedMutex sync.Mutex
}

func (m *Messenger) HandleBackup(state *ReceivedMessageState, message *protobuf.Backup, statusMessage *v1protocol.StatusMessage) error {
if !m.processBackedupMessages {
return nil
Expand Down Expand Up @@ -96,6 +113,11 @@ func (m *Messenger) handleBackup(state *ReceivedMessageState, message *protobuf.
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyKeypairs, message.KeypairDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyWatchOnlyAccounts, message.WatchOnlyAccountDetails)

err = m.updateBackupFetchProgress(message, &response, state)
if err != nil {
errors = append(errors, err)
}

m.config.messengerSignalsHandler.SendWakuFetchingBackupProgress(&response)
}

Expand All @@ -104,6 +126,150 @@ func (m *Messenger) handleBackup(state *ReceivedMessageState, message *protobuf.
return errors
}

func (m *Messenger) updateBackupFetchProgress(message *protobuf.Backup, response *wakusync.WakuBackedUpDataResponse, state *ReceivedMessageState) error {
m.backedUpFetchingStatus.fetchingCompletedMutex.Lock()
defer m.backedUpFetchingStatus.fetchingCompletedMutex.Unlock()

if m.backedUpFetchingStatus.fetchingCompleted {
return nil
}

if m.backedUpFetchingStatus.lastKnownMsgClock > message.Clock {
return nil
}

if m.backedUpFetchingStatus.lastKnownMsgClock < message.Clock {
// Reset the progress tracker because we have access to a more recent copy of the backup
m.backedUpFetchingStatus.lastKnownMsgClock = message.Clock
m.backedUpFetchingStatus.dataProgress = make(map[string]FetchingBackedUpDataTracking)
for backupName, details := range response.FetchingBackedUpDataDetails() {
m.backedUpFetchingStatus.dataProgress[backupName] = FetchingBackedUpDataTracking{
LoadedItems: make(map[uint32]bool),
TotalNumber: details.TotalNumber,
}
}

if len(m.backedUpFetchingStatus.dataProgress) == 0 {
return nil
}
}

// Evaluate the progress of the backup

// Set the new items before evaluating
for backupName, details := range response.FetchingBackedUpDataDetails() {
m.backedUpFetchingStatus.dataProgress[backupName].LoadedItems[details.DataNumber] = true
}

for _, tracker := range m.backedUpFetchingStatus.dataProgress {
if len(tracker.LoadedItems)-1 < int(tracker.TotalNumber) {
// have not received everything yet
return nil
}
}

m.backedUpFetchingStatus.fetchingCompleted = true

// Update the AC notification and add it to the response
notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(backupSyncingNotificationID))
if err != nil {
return err
}

if notification == nil {
return nil
}

notification.UpdatedAt = m.GetCurrentTimeInMillis()
notification.Type = ActivityCenterNotificationTypeBackupSyncingSuccess
_, err = m.persistence.SaveActivityCenterNotification(notification, true)
if err != nil {
return err
}

state.Response.AddActivityCenterNotification(notification)
return nil
}

func (m *Messenger) startBackupFetchingTracking(response *MessengerResponse) error {
// Add an acivity center notification to show that we are fetching back up messages
notification := &ActivityCenterNotification{
ID: types.FromHex(backupSyncingNotificationID),
Type: ActivityCenterNotificationTypeBackupSyncingFetching,
Timestamp: m.getTimesource().GetCurrentTime(),
Read: false,
Deleted: false,
UpdatedAt: m.GetCurrentTimeInMillis(),
}
err := m.addActivityCenterNotification(response, notification, nil)

if err != nil {
return err
}

// Add a timeout to mark the backup syncing as failed after 1 minute and 30 seconds
m.shutdownWaitGroup.Add(1)
go func() {
defer utils.LogOnPanic()
defer m.shutdownWaitGroup.Done()
m.watchBackupFetching()
}()

return nil
}

func (m *Messenger) watchBackupFetching() {
select {
case <-m.ctx.Done():
return
case <-time.After(90 * time.Second):
m.backupFetchingTimeout()
}
}

func (m *Messenger) backupFetchingTimeout() {
if m.backedUpFetchingStatus == nil {
return
}

m.backedUpFetchingStatus.fetchingCompletedMutex.Lock()
defer m.backedUpFetchingStatus.fetchingCompletedMutex.Unlock()

if m.backedUpFetchingStatus.fetchingCompleted {
// Nothing to do, the fetching has completed successfully
return
}
// Update the AC notification to the failure state
notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(backupSyncingNotificationID))
if err != nil {
m.logger.Error("failed to get activity center notification", zap.Error(err))
return
}
if notification == nil {
return
}

notification.UpdatedAt = m.GetCurrentTimeInMillis()
if m.backedUpFetchingStatus.dataProgress == nil || len(m.backedUpFetchingStatus.dataProgress) == 0 {
notification.Type = ActivityCenterNotificationTypeBackupSyncingFailure
} else {
notification.Type = ActivityCenterNotificationTypeBackupSyncingPartialFailure
}
_, err = m.persistence.SaveActivityCenterNotification(notification, true)
if err != nil {
m.logger.Error("failed to save activity center notification", zap.Error(err))
return
}

if m.config.messengerSignalsHandler == nil {
return
}

resp := &MessengerResponse{}
resp.AddActivityCenterNotification(notification)
m.config.messengerSignalsHandler.MessengerResponse(resp)
}

func (m *Messenger) handleBackedUpProfile(message *protobuf.BackedUpProfile, backupTime uint64) error {
if message == nil {
return nil
Expand Down
Loading

0 comments on commit 0526d18

Please sign in to comment.