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
ActivityCenterNotificationTypeCommunityBanned
ActivityCenterNotificationTypeCommunityUnbanned
ActivityCenterNotificationTypeNewInstallationReceived
ActivityCenterNotificationTypeNewInstallationCreated
)
type ActivityCenterMembershipStatus int
@ -87,6 +89,7 @@ type ActivityCenterNotification struct {
MembershipStatus ActivityCenterMembershipStatus `json:"membershipStatus"`
Name string `json:"name"`
Author string `json:"author"`
InstallationID string `json:"installationId"`
Type ActivityCenterType `json:"type"`
LastMessage *common.Message `json:"lastMessage"`
Message *common.Message `json:"message"`

View File

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

View File

@ -3658,6 +3658,32 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
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) {
var err error
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
})
if err != nil {

View File

@ -9,6 +9,7 @@ import (
"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"
@ -16,15 +17,30 @@ import (
"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 {
return err
return nil, err
}
err := m.EnableInstallation(request.InstallationID)
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) {
@ -53,7 +69,26 @@ func (m *Messenger) EnableInstallationAndPair(request *requests.EnableInstallati
i.Enabled = true
}
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

View File

@ -102,7 +102,8 @@ func (s *MessengerPairingSuite) TestMessengerPairAfterSeedPhrase() {
)
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
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"
)
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"`
}
func (j *EnableAndSyncInstallation) Validate() error {
func (j *EnableInstallationAndSync) Validate() error {
if len(j.InstallationID) == 0 {
return ErrEnableAndSyncInstallationInvalidID
return ErrEnableInstallationAndSyncInvalidID
}
return nil

View File

@ -59,13 +59,10 @@ func NewConnectionParams(netIPs []net.IP, port int, publicKey *ecdsa.PublicKey,
// - AES encryption key
// - string InstallationID of the sending device
// - string KeyUID of the sending device
//
// 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
// - 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 {
v := base58.Encode(new(big.Int).SetInt64(int64(cp.version)).Bytes())
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))
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
if cp.installationID != "" {
u, err := uuid.Parse(cp.installationID)
if err != nil {
log.Fatalf("Failed to parse UUID: %v", err)
} else {
// Convert UUID to byte slice
byteSlice := u[:]
i = base58.Encode(byteSlice)
@ -94,7 +85,6 @@ func (cp *ConnectionParams) ToString() string {
var kuid string
if 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)

View File

@ -65,7 +65,6 @@ func (s *ConnectionParamsSuite) SetupSuite() {
}
func (s *ConnectionParamsSuite) TestConnectionParams_ToString() {
keep229Compatibility = false
cp, err := s.server.MakeConnectionParams()
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)
}
func (api *PublicAPI) EnableAndSyncInstallation(request *requests.EnableAndSyncInstallation) error {
return api.service.messenger.EnableAndSyncInstallation(request)
// Deprecated: Use EnableInstallationAndSync instead
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) {