Skip to content

Commit

Permalink
feat(onboarding)_: add a notification when importing old account
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jrainville committed Jan 23, 2025
1 parent e4d5622 commit b8a3d32
Show file tree
Hide file tree
Showing 7 changed files with 297 additions and 2 deletions.
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
66 changes: 66 additions & 0 deletions protocol/messenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import (
"github.com/status-im/status-go/protocol/transport"
v1protocol "github.com/status-im/status-go/protocol/v1"
"github.com/status-im/status-go/protocol/verification"
"github.com/status-im/status-go/protocol/wakusync"
"github.com/status-im/status-go/server"
"github.com/status-im/status-go/services/browsers"
ensservice "github.com/status-im/status-go/services/ens"
Expand All @@ -84,6 +85,8 @@ const (
privateChat ChatContext = "private-chat"
)

const backupSyncingNotificationID = "BACKUP_SYNCING"

// errors
var (
ErrChatNotFoundError = errors.New("Chat not found")
Expand Down Expand Up @@ -195,6 +198,10 @@ type Messenger struct {
peersyncingRequests map[string]uint64

mvdsStatusChangeEvent chan datasyncnode.PeerStatusChangeEvent

fetchingBackedUpDataProgress map[string]wakusync.FetchingBackedUpDataTracking
lastKnownBackedUpMsgClock uint64
fetchingBackedUpDataCompleted bool
}

type EnvelopeEventsInterceptor struct {
Expand Down Expand Up @@ -917,9 +924,68 @@ func (m *Messenger) Start() (*MessengerResponse, error) {
}
}

if m.processBackedupMessages {
err = m.startBackupFetchingTracking(response)
if err != nil {
return nil, err
}
}

return response, 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), // TODO put this in a constant
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
time.AfterFunc(1*time.Minute+30*time.Second, func() {
if m.fetchingBackedUpDataCompleted {
// Nothing to do, the fetching has completed successfully
return
}
// Update the AC notification to the failure state
hexBytesIds := []types.HexBytes{}
hexBytesIds = append(hexBytesIds, types.FromHex(backupSyncingNotificationID))
notifications, err := m.persistence.GetActivityCenterNotificationsByID(hexBytesIds)
if err != nil {
m.logger.Error("failed to get activity center notification", zap.Error(err))
} else if len(notifications) == 1 {
notification := notifications[0]
notification.UpdatedAt = m.GetCurrentTimeInMillis()
if m.fetchingBackedUpDataProgress == nil || len(m.fetchingBackedUpDataProgress) == 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))
} else {
if m.config.messengerSignalsHandler != nil {
resp := &MessengerResponse{}
resp.AddActivityCenterNotification(notification)
m.config.messengerSignalsHandler.MessengerResponse(resp)
}
}
}
})

return nil
}

func (m *Messenger) startHistoryArchivesImportLoop() {
defer gocommon.LogOnPanic()
joinedCommunities, err := m.communitiesManager.Joined()
Expand Down
59 changes: 59 additions & 0 deletions protocol/messenger_backup_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"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 Down Expand Up @@ -96,6 +97,64 @@ func (m *Messenger) handleBackup(state *ReceivedMessageState, message *protobuf.
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyKeypairs, message.KeypairDetails)
response.AddFetchingBackedUpDataDetails(SyncWakuSectionKeyWatchOnlyAccounts, message.WatchOnlyAccountDetails)

if !m.fetchingBackedUpDataCompleted {
evaluate := true
if m.lastKnownBackedUpMsgClock > message.Clock {
evaluate = false
} else if m.lastKnownBackedUpMsgClock < message.Clock {
// Reset the progress tracker because we have access to a more recent copy of the backup
m.lastKnownBackedUpMsgClock = message.Clock
m.fetchingBackedUpDataProgress = make(map[string]wakusync.FetchingBackedUpDataTracking)
for backupName, details := range response.FetchingBackedUpDataDetails() {
m.fetchingBackedUpDataProgress[backupName] = wakusync.FetchingBackedUpDataTracking{
LoadedItems: make(map[uint32]bool),
TotalNumber: details.TotalNumber,
}
}
if len(m.fetchingBackedUpDataProgress) == 0 {
evaluate = false
}
}

// Evaluate the progress of the backup
if evaluate {
// Set the new items before evaluating
for backupName, details := range response.FetchingBackedUpDataDetails() {
m.fetchingBackedUpDataProgress[backupName].LoadedItems[details.DataNumber] = true
}

receivedEverything := true
for _, tracker := range m.fetchingBackedUpDataProgress {
if len(tracker.LoadedItems)-1 < int(tracker.TotalNumber) {
receivedEverything = false
break
}
}

if receivedEverything {
m.fetchingBackedUpDataCompleted = true

// Update the AC notification and add it to the response
hexBytesIds := []types.HexBytes{}
hexBytesIds = append(hexBytesIds, types.FromHex(backupSyncingNotificationID))
notifications, err := m.persistence.GetActivityCenterNotificationsByID(hexBytesIds)
if err != nil {
errors = append(errors, err)
} else if len(notifications) == 1 {
notification := notifications[0]
notification.UpdatedAt = m.GetCurrentTimeInMillis()
notification.Type = ActivityCenterNotificationTypeBackupSyncingSuccess
_, err = m.persistence.SaveActivityCenterNotification(notification, true)
if err != nil {
errors = append(errors, err)
} else {
state.Response.AddActivityCenterNotification(notification)
}
}
}
}
}

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

Expand Down
153 changes: 153 additions & 0 deletions protocol/messenger_backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,159 @@ func (s *MessengerBackupSuite) TestBackupProfileWithInvalidDisplayName() {
s.Require().Equal("", storedBob1DisplayName)
}

func (s *MessengerBackupSuite) TestFetchingDuringBackup() {
bob1 := s.m
bob1.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{}

state := ReceivedMessageState{
Response: &MessengerResponse{},
}

backup := &protobuf.Backup{
Clock: 1,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
ProfileDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
}

err := bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)
s.Require().Len(bob1.fetchingBackedUpDataProgress, 3)
s.Require().Equal(uint32(1), bob1.fetchingBackedUpDataProgress[SyncWakuSectionKeyContacts].TotalNumber)

// Parse a backup with a higher clock so reset the fetching
backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(2),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
ProfileDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
SettingsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
KeypairDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
WatchOnlyAccountDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)
s.Require().Len(bob1.fetchingBackedUpDataProgress, 6)
s.Require().Equal(uint32(2), bob1.fetchingBackedUpDataProgress[SyncWakuSectionKeyContacts].TotalNumber)

// Backup with a smaller clock is ignored
backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(5),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(0),
TotalNumber: uint32(1),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)
// The values are gonna be the same as before as the backup was ignored
s.Require().Len(bob1.fetchingBackedUpDataProgress, 6)
s.Require().Equal(uint32(2), bob1.fetchingBackedUpDataProgress[SyncWakuSectionKeyContacts].TotalNumber)

// Parse the backup with almost all the correct data numbers
backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(2),
},
CommunitiesDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
ProfileDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
SettingsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
KeypairDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
WatchOnlyAccountDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(1),
TotalNumber: uint32(1),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is not done, so no signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 0)

// Parse the remaining backup so the notification should be sent now
backup = &protobuf.Backup{
Clock: 2,
ContactsDetails: &protobuf.FetchingBackedUpDataDetails{
DataNumber: uint32(2),
TotalNumber: uint32(2),
},
}
err = bob1.HandleBackup(
&state,
backup,
&v1protocol.StatusMessage{},
)
s.Require().NoError(err)
// The backup is done, so the signal should be sent
s.Require().Len(state.Response.ActivityCenterNotifications(), 1)
}

func (s *MessengerBackupSuite) TestBackupSettings() {
s.T().Skip("flaky test")
const (
Expand Down
7 changes: 6 additions & 1 deletion protocol/messenger_mailserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,13 @@ func (m *Messenger) RequestAllHistoricMessages(forceFetchingBackup, withRetries
return nil, nil
}

allResponses := &MessengerResponse{}
if forceFetchingBackup || !backupFetched {
err = m.startBackupFetchingTracking(allResponses)
if err != nil {
return nil, err
}

m.logger.Info("fetching backup")
err := m.syncBackup()
if err != nil {
Expand All @@ -336,7 +342,6 @@ func (m *Messenger) RequestAllHistoricMessages(forceFetchingBackup, withRetries
defer m.resetFiltersPriority(filters)

filtersByMs := m.SplitFiltersByStoreNode(filters)
allResponses := &MessengerResponse{}
for communityID, filtersForMs := range filtersByMs {
peerID := m.getCommunityStorenode(communityID)
if withRetries {
Expand Down
5 changes: 4 additions & 1 deletion protocol/messenger_testing_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ type MessengerSignalsHandlerMock struct {
}

func (m *MessengerSignalsHandlerMock) SendWakuFetchingBackupProgress(response *wakusync.WakuBackedUpDataResponse) {
m.wakuBackedUpDataResponseChan <- response
select {
case m.wakuBackedUpDataResponseChan <- response:
default:
}
}
func (m *MessengerSignalsHandlerMock) SendWakuBackedUpProfile(*wakusync.WakuBackedUpDataResponse) {}
func (m *MessengerSignalsHandlerMock) SendWakuBackedUpSettings(*wakusync.WakuBackedUpDataResponse) {}
Expand Down
5 changes: 5 additions & 0 deletions protocol/wakusync/progress_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ type FetchingBackupedDataDetails struct {
TotalNumber uint32 `json:"totalNumber,omitempty"`
}

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

func (sfwr *WakuBackedUpDataResponse) AddFetchingBackedUpDataDetails(section string, details *protobuf.FetchingBackedUpDataDetails) {
if details == nil {
return
Expand Down

0 comments on commit b8a3d32

Please sign in to comment.