feat(sync)!: leftovers work for sync fallback (#5794)

* feat(sync)!: remove compatibility with v2.29

* feat(sync)_: add AC notifications when initiating the sync fallback

Needed for https://github.com/status-im/status-desktop/issues/15750

Adds an AC notification when the syncing fails and the user is prompted to use a seed phrase instead.
There is one notification for the initiator (created) and one for the old account (received).
Once the flow is completed, ie the receiver presses Enable and sync,  the notifications are deleted

* test_: update test

* fix_: lint issue

* chore_: ignore tmp file generated by make lint-fix

* chore_: rename EnableAndSyncInstallation to EnableInstallationAndSync

* chore_: address review feedback

* chore_: revert changes to .gitignore

* fix_: simplify code

* fix_: keep old API

---------

Co-authored-by: Jonathan Rainville <rainville.jonathan@gmail.com>
This commit is contained in:
frank 2024-09-19 16:17:46 +08:00 committed by GitHub
parent 6e5a32c022
commit f04a9a8726
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 309 additions and 181 deletions

View File

@ -40,6 +40,8 @@ const (
ActivityCenterNotificationTypeFirstCommunityTokenReceived ActivityCenterNotificationTypeFirstCommunityTokenReceived
ActivityCenterNotificationTypeCommunityBanned ActivityCenterNotificationTypeCommunityBanned
ActivityCenterNotificationTypeCommunityUnbanned ActivityCenterNotificationTypeCommunityUnbanned
ActivityCenterNotificationTypeNewInstallationReceived
ActivityCenterNotificationTypeNewInstallationCreated
) )
type ActivityCenterMembershipStatus int type ActivityCenterMembershipStatus int
@ -87,6 +89,7 @@ type ActivityCenterNotification struct {
MembershipStatus ActivityCenterMembershipStatus `json:"membershipStatus"` MembershipStatus ActivityCenterMembershipStatus `json:"membershipStatus"`
Name string `json:"name"` Name string `json:"name"`
Author string `json:"author"` Author string `json:"author"`
InstallationID string `json:"installationId"`
Type ActivityCenterType `json:"type"` Type ActivityCenterType `json:"type"`
LastMessage *common.Message `json:"lastMessage"` LastMessage *common.Message `json:"lastMessage"`
Message *common.Message `json:"message"` Message *common.Message `json:"message"`

View File

@ -148,9 +148,10 @@ func (db sqlitePersistence) SaveActivityCenterNotification(notification *Activit
dismissed, dismissed,
token_data, token_data,
deleted, deleted,
updated_at updated_at,
installation_id
) )
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`, `,
notification.ID, notification.ID,
notification.Timestamp, notification.Timestamp,
@ -168,6 +169,7 @@ func (db sqlitePersistence) SaveActivityCenterNotification(notification *Activit
encodedTokenData, encodedTokenData,
notification.Deleted, notification.Deleted,
notification.UpdatedAt, notification.UpdatedAt,
notification.InstallationID,
) )
if err != nil { if err != nil {
return 0, err return 0, err
@ -269,6 +271,7 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRow(row *sql.Row)
var tokenDataBytes []byte var tokenDataBytes []byte
var name sql.NullString var name sql.NullString
var author sql.NullString var author sql.NullString
var installationID sql.NullString
notification := &ActivityCenterNotification{} notification := &ActivityCenterNotification{}
err := row.Scan( err := row.Scan(
&notification.ID, &notification.ID,
@ -288,7 +291,9 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRow(row *sql.Row)
&name, &name,
&author, &author,
&tokenDataBytes, &tokenDataBytes,
&notification.UpdatedAt) &notification.UpdatedAt,
&installationID,
)
if err != nil { if err != nil {
return nil, err return nil, err
@ -310,6 +315,10 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRow(row *sql.Row)
notification.Author = author.String notification.Author = author.String
} }
if installationID.Valid {
notification.InstallationID = installationID.String
}
if len(tokenDataBytes) > 0 { if len(tokenDataBytes) > 0 {
err = json.Unmarshal(tokenDataBytes, &notification.TokenData) err = json.Unmarshal(tokenDataBytes, &notification.TokenData)
if err != nil { if err != nil {
@ -359,6 +368,7 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRows(rows *sql.Ro
var tokenDataBytes []byte var tokenDataBytes []byte
var name sql.NullString var name sql.NullString
var author sql.NullString var author sql.NullString
var installationID sql.NullString
notification := &ActivityCenterNotification{} notification := &ActivityCenterNotification{}
err := rows.Scan( err := rows.Scan(
&notification.ID, &notification.ID,
@ -378,7 +388,9 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRows(rows *sql.Ro
&author, &author,
&tokenDataBytes, &tokenDataBytes,
&latestCursor, &latestCursor,
&notification.UpdatedAt) &notification.UpdatedAt,
&installationID,
)
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
@ -399,6 +411,10 @@ func (db sqlitePersistence) unmarshalActivityCenterNotificationRows(rows *sql.Ro
notification.Author = author.String notification.Author = author.String
} }
if installationID.Valid {
notification.InstallationID = installationID.String
}
if len(tokenDataBytes) > 0 { if len(tokenDataBytes) > 0 {
tokenData := &ActivityTokenData{} tokenData := &ActivityTokenData{}
if err = json.Unmarshal(tokenDataBytes, &tokenData); err != nil { if err = json.Unmarshal(tokenDataBytes, &tokenData); err != nil {
@ -539,7 +555,8 @@ func (db sqlitePersistence) buildActivityCenterQuery(tx *sql.Tx, params activity
a.author, a.author,
a.token_data, a.token_data,
substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor, substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor,
a.updated_at a.updated_at,
a.installation_id
FROM activity_center_notifications a FROM activity_center_notifications a
LEFT JOIN chats c LEFT JOIN chats c
ON ON
@ -660,7 +677,8 @@ func (db sqlitePersistence) GetActivityCenterNotificationsByID(ids []types.HexBy
a.author, a.author,
a.token_data, a.token_data,
substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor, substr('0000000000000000000000000000000000000000000000000000000000000000' || a.timestamp, -64, 64) || hex(a.id) as cursor,
a.updated_at a.updated_at,
a.installation_id
FROM activity_center_notifications a FROM activity_center_notifications a
LEFT JOIN chats c LEFT JOIN chats c
ON ON
@ -700,7 +718,8 @@ func (db sqlitePersistence) GetActivityCenterNotificationByID(id types.HexBytes)
c.name, c.name,
a.author, a.author,
a.token_data, a.token_data,
a.updated_at a.updated_at,
a.installation_id
FROM activity_center_notifications a FROM activity_center_notifications a
LEFT JOIN chats c LEFT JOIN chats c
ON ON
@ -1334,7 +1353,8 @@ func (db sqlitePersistence) ActiveContactRequestNotification(contactID string) (
c.name, c.name,
a.author, a.author,
a.token_data, a.token_data,
a.updated_at a.updated_at,
a.installation_id
FROM activity_center_notifications a FROM activity_center_notifications a
LEFT JOIN chats c ON c.id = a.chat_id LEFT JOIN chats c ON c.id = a.chat_id
WHERE a.author = ? WHERE a.author = ?

View File

@ -3658,6 +3658,32 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
return m.saveDataAndPrepareResponse(messageState) return m.saveDataAndPrepareResponse(messageState)
} }
func (m *Messenger) deleteNotification(response *MessengerResponse, installationID string) error {
notification, err := m.persistence.GetActivityCenterNotificationByID(types.FromHex(installationID))
if err != nil {
return err
}
if notification == nil {
return nil
}
updatedAt := m.GetCurrentTimeInMillis()
notification.UpdatedAt = updatedAt
notification.Deleted = true
// we shouldn't sync deleted notification here,
// as the same user on different devices will receive the same message(CommunityCancelRequestToJoin) ?
err = m.persistence.DeleteActivityCenterNotificationByID(types.FromHex(installationID), updatedAt)
if err != nil {
m.logger.Error("failed to delete notification from Activity Center", zap.Error(err))
return err
}
// sending signal to client to remove the activity center notification from UI
response.AddActivityCenterNotification(notification)
return nil
}
func (m *Messenger) saveDataAndPrepareResponse(messageState *ReceivedMessageState) (*MessengerResponse, error) { func (m *Messenger) saveDataAndPrepareResponse(messageState *ReceivedMessageState) (*MessengerResponse, error) {
var err error var err error
var contactsToSave []*Contact var contactsToSave []*Contact
@ -3697,6 +3723,31 @@ func (m *Messenger) saveDataAndPrepareResponse(messageState *ReceivedMessageStat
} }
} }
if installation.Enabled {
// Delete AC notif since the installation is now enabled
err = m.deleteNotification(messageState.Response, id)
if err != nil {
m.logger.Error("error deleting notification", zap.Error(err))
return false
}
} else if id != m.installationID {
// Add activity center notification when we receive a new installation
notification := &ActivityCenterNotification{
ID: types.FromHex(id),
Type: ActivityCenterNotificationTypeNewInstallationReceived,
InstallationID: id,
Timestamp: m.getTimesource().GetCurrentTime(),
Read: false,
Deleted: false,
UpdatedAt: m.GetCurrentTimeInMillis(),
}
err = m.addActivityCenterNotification(messageState.Response, notification, nil)
if err != nil {
return false
}
}
return true return true
}) })
if err != nil { if err != nil {

View File

@ -9,6 +9,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"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/images" "github.com/status-im/status-go/images"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/encryption/multidevice" "github.com/status-im/status-go/protocol/encryption/multidevice"
@ -16,15 +17,30 @@ import (
"github.com/status-im/status-go/protocol/requests" "github.com/status-im/status-go/protocol/requests"
) )
func (m *Messenger) EnableAndSyncInstallation(request *requests.EnableAndSyncInstallation) error { func (m *Messenger) EnableInstallationAndSync(request *requests.EnableInstallationAndSync) (*MessengerResponse, error) {
if err := request.Validate(); err != nil { if err := request.Validate(); err != nil {
return err return nil, err
} }
err := m.EnableInstallation(request.InstallationID) err := m.EnableInstallation(request.InstallationID)
if err != nil { if err != nil {
return err return nil, err
} }
return m.SyncDevices(context.Background(), "", "", nil) 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) { func (m *Messenger) EnableInstallationAndPair(request *requests.EnableInstallationAndPair) (*MessengerResponse, error) {
@ -53,7 +69,26 @@ func (m *Messenger) EnableInstallationAndPair(request *requests.EnableInstallati
i.Enabled = true i.Enabled = true
} }
m.allInstallations.Store(request.InstallationID, i) m.allInstallations.Store(request.InstallationID, i)
return m.SendPairInstallation(context.Background(), nil) 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 // SendPairInstallation sends a pair installation message

View File

@ -102,7 +102,8 @@ func (s *MessengerPairingSuite) TestMessengerPairAfterSeedPhrase() {
) )
s.Require().NoError(err) s.Require().NoError(err)
s.Require().NoError(alice1.EnableAndSyncInstallation(&requests.EnableAndSyncInstallation{InstallationID: installationID2})) _, err = alice1.EnableInstallationAndSync(&requests.EnableInstallationAndSync{InstallationID: installationID2})
s.Require().NoError(err)
// check if the display name is synced // check if the display name is synced
err = tt.RetryWithBackOff(func() error { err = tt.RetryWithBackOff(func() error {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
ALTER TABLE activity_center_notifications ADD COLUMN installation_id TEXT DEFAULT NULL;

View File

@ -4,15 +4,15 @@ import (
"errors" "errors"
) )
var ErrEnableAndSyncInstallationInvalidID = errors.New("enable and sync installation: invalid installation id") var ErrEnableInstallationAndSyncInvalidID = errors.New("enable installation and sync : invalid installation id")
type EnableAndSyncInstallation struct { type EnableInstallationAndSync struct {
InstallationID string `json:"installationId"` InstallationID string `json:"installationId"`
} }
func (j *EnableAndSyncInstallation) Validate() error { func (j *EnableInstallationAndSync) Validate() error {
if len(j.InstallationID) == 0 { if len(j.InstallationID) == 0 {
return ErrEnableAndSyncInstallationInvalidID return ErrEnableInstallationAndSyncInvalidID
} }
return nil return nil

View File

@ -59,13 +59,10 @@ func NewConnectionParams(netIPs []net.IP, port int, publicKey *ecdsa.PublicKey,
// - AES encryption key // - AES encryption key
// - string InstallationID of the sending device // - string InstallationID of the sending device
// - string KeyUID of the sending device // - string KeyUID of the sending device
//
// NOTE: // NOTE:
// - append(accrete) parameters instead of changing(breaking) existing parameters. Appending should **never** break, modifying existing parameters will break. Watch this before making changes: https://www.youtube.com/watch?v=oyLBGkS5ICk // - append(accrete) parameters instead of changing(breaking) existing parameters. Appending should **never** break, modifying existing parameters will break. Watch this before making changes: https://www.youtube.com/watch?v=oyLBGkS5ICk
// - never strictly check version, unless you really want to break // - never strictly check version, unless you really want to break
// This flag is used to keep compatibility with 2.29. It will output a 5 parameters connection string with version 3.
var keep229Compatibility bool = true
func (cp *ConnectionParams) ToString() string { func (cp *ConnectionParams) ToString() string {
v := base58.Encode(new(big.Int).SetInt64(int64(cp.version)).Bytes()) v := base58.Encode(new(big.Int).SetInt64(int64(cp.version)).Bytes())
ips := base58.Encode(SerializeNetIps(cp.netIPs)) ips := base58.Encode(SerializeNetIps(cp.netIPs))
@ -73,18 +70,12 @@ func (cp *ConnectionParams) ToString() string {
k := base58.Encode(elliptic.MarshalCompressed(cp.publicKey.Curve, cp.publicKey.X, cp.publicKey.Y)) k := base58.Encode(elliptic.MarshalCompressed(cp.publicKey.Curve, cp.publicKey.X, cp.publicKey.Y))
ek := base58.Encode(cp.aesKey) ek := base58.Encode(cp.aesKey)
if keep229Compatibility {
return fmt.Sprintf("%s%s:%s:%s:%s:%s", connectionStringID, v, ips, p, k, ek)
}
var i string var i string
if cp.installationID != "" { if cp.installationID != "" {
u, err := uuid.Parse(cp.installationID) u, err := uuid.Parse(cp.installationID)
if err != nil { if err != nil {
log.Fatalf("Failed to parse UUID: %v", err) log.Fatalf("Failed to parse UUID: %v", err)
} else { } else {
// Convert UUID to byte slice // Convert UUID to byte slice
byteSlice := u[:] byteSlice := u[:]
i = base58.Encode(byteSlice) i = base58.Encode(byteSlice)
@ -94,7 +85,6 @@ func (cp *ConnectionParams) ToString() string {
var kuid string var kuid string
if cp.keyUID != "" { if cp.keyUID != "" {
kuid = base58.Encode([]byte(cp.keyUID)) kuid = base58.Encode([]byte(cp.keyUID))
} }
return fmt.Sprintf("%s%s:%s:%s:%s:%s:%s:%s", connectionStringID, v, ips, p, k, ek, i, kuid) return fmt.Sprintf("%s%s:%s:%s:%s:%s:%s:%s", connectionStringID, v, ips, p, k, ek, i, kuid)

View File

@ -65,7 +65,6 @@ func (s *ConnectionParamsSuite) SetupSuite() {
} }
func (s *ConnectionParamsSuite) TestConnectionParams_ToString() { func (s *ConnectionParamsSuite) TestConnectionParams_ToString() {
keep229Compatibility = false
cp, err := s.server.MakeConnectionParams() cp, err := s.server.MakeConnectionParams()
s.Require().NoError(err) s.Require().NoError(err)

View File

@ -1063,8 +1063,13 @@ func (api *PublicAPI) SyncDevices(ctx context.Context, name, picture string) err
return api.service.messenger.SyncDevices(ctx, name, picture, nil) return api.service.messenger.SyncDevices(ctx, name, picture, nil)
} }
func (api *PublicAPI) EnableAndSyncInstallation(request *requests.EnableAndSyncInstallation) error { // Deprecated: Use EnableInstallationAndSync instead
return api.service.messenger.EnableAndSyncInstallation(request) func (api *PublicAPI) EnableAndSyncInstallation(request *requests.EnableInstallationAndSync) (*protocol.MessengerResponse, error) {
return api.service.messenger.EnableInstallationAndSync(request)
}
func (api *PublicAPI) EnableInstallationAndSync(request *requests.EnableInstallationAndSync) (*protocol.MessengerResponse, error) {
return api.service.messenger.EnableInstallationAndSync(request)
} }
func (api *PublicAPI) EnableInstallationAndPair(request *requests.EnableInstallationAndPair) (*protocol.MessengerResponse, error) { func (api *PublicAPI) EnableInstallationAndPair(request *requests.EnableInstallationAndPair) (*protocol.MessengerResponse, error) {