package protocol import ( "context" "encoding/json" v1protocol "github.com/status-im/status-go/protocol/v1" "github.com/status-im/status-go/protocol/verification" "github.com/golang/protobuf/proto" "github.com/pkg/errors" "go.uber.org/zap" "github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/protobuf" ) func toHexBytes(b [][]byte) []types.HexBytes { hb := make([]types.HexBytes, len(b)) for i, v := range b { hb[i] = types.HexBytes(v) } return hb } func fromHexBytes(hb []types.HexBytes) [][]byte { b := make([][]byte, len(hb)) for i, v := range hb { b[i] = v } return b } func (m *Messenger) ActivityCenterNotifications(request ActivityCenterNotificationsRequest) (*ActivityCenterPaginationResponse, error) { cursor, notifications, err := m.persistence.ActivityCenterNotifications(request.Cursor, request.Limit, request.ActivityTypes, request.ReadType, true) if err != nil { return nil, err } if m.httpServer != nil { for _, notification := range notifications { if notification.Message != nil { m.prepareMessage(notification.Message, m.httpServer) image := notification.Message.GetImage() if image != nil && image.AlbumId != "" { album, err := m.persistence.albumMessages(notification.Message.LocalChatID, image.AlbumId) if err != nil { return nil, err } notification.AlbumMessages = album } } if notification.AlbumMessages != nil { for _, message := range notification.AlbumMessages { m.prepareMessage(message, m.httpServer) } } } } return &ActivityCenterPaginationResponse{ Cursor: cursor, Notifications: notifications, }, nil } func (m *Messenger) ActivityCenterNotificationsCount(request ActivityCenterCountRequest) (*ActivityCenterCountResponse, error) { response := make(ActivityCenterCountResponse) for _, activityType := range request.ActivityTypes { count, err := m.persistence.ActivityCenterNotificationsCount([]ActivityCenterType{activityType}, request.ReadType, true) if err != nil { return nil, err } response[activityType] = count } return &response, nil } func (m *Messenger) HasUnseenActivityCenterNotifications() (bool, error) { seen, _, err := m.persistence.HasUnseenActivityCenterNotifications() return seen, err } func (m *Messenger) GetActivityCenterState() (*ActivityCenterState, error) { return m.persistence.GetActivityCenterState() } func (m *Messenger) syncActivityCenterNotificationState(state *ActivityCenterState) error { if state == nil { return nil } syncStateMessage := &protobuf.SyncActivityCenterNotificationState{ UpdatedAt: state.UpdatedAt, HasSeen: state.HasSeen, } encodedMessage, err := proto.Marshal(syncStateMessage) if err != nil { return err } return m.sendToPairedDevices(context.TODO(), common.RawMessage{ Payload: encodedMessage, MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACTIVITY_CENTER_NOTIFICATION_STATE, ResendAutomatically: true, }) } func (m *Messenger) syncActivityCenterNotifications(notifications []*ActivityCenterNotification) (err error) { if notifications == nil { return nil } var s []*protobuf.SyncActivityCenterNotification for _, n := range notifications { var p *protobuf.SyncActivityCenterNotification p, err = convertActivityCenterNotificationToProtobuf(*n) if err != nil { return } s = append(s, p) } var encodedMessage []byte encodedMessage, err = proto.Marshal(&protobuf.SyncActivityCenterNotifications{ ActivityCenterNotifications: s, }) if err != nil { return } return m.sendToPairedDevices(context.TODO(), common.RawMessage{ Payload: encodedMessage, MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACTIVITY_CENTER_NOTIFICATIONS, ResendAutomatically: true, }) } func (m *Messenger) MarkAsSeenActivityCenterNotifications() (*MessengerResponse, error) { response := &MessengerResponse{} s := &ActivityCenterState{ UpdatedAt: m.getCurrentTimeInMillis(), HasSeen: true, } n, err := m.persistence.UpdateActivityCenterNotificationState(s) if err != nil { return nil, err } state, err := m.persistence.GetActivityCenterState() if err != nil { return nil, err } response.SetActivityCenterState(state) if n > 0 { return response, m.syncActivityCenterNotificationState(state) } return response, nil } func (m *Messenger) MarkAllActivityCenterNotificationsRead(ctx context.Context) (*MessengerResponse, error) { response := &MessengerResponse{} updateAt := m.getCurrentTimeInMillis() if m.hasPairedDevices() { ids, err := m.persistence.GetNotReadActivityCenterNotificationIds() if err != nil { return nil, err } _, err = m.MarkActivityCenterNotificationsRead(ctx, toHexBytes(ids), updateAt, true) return nil, err } err := m.persistence.MarkAllActivityCenterNotificationsRead(updateAt) if err != nil { return nil, err } state, err := m.persistence.GetActivityCenterState() if err != nil { return nil, err } response.SetActivityCenterState(state) return response, nil } func (m *Messenger) MarkActivityCenterNotificationsRead(ctx context.Context, ids []types.HexBytes, updatedAt uint64, sync bool) (*MessengerResponse, error) { response := &MessengerResponse{} if updatedAt == 0 { updatedAt = m.getCurrentTimeInMillis() } err := m.persistence.MarkActivityCenterNotificationsRead(ids, updatedAt) if err != nil { return nil, err } if !sync { notifications, err := m.persistence.GetActivityCenterNotificationsByID(ids) if err != nil { return nil, err } return m.processActivityCenterNotifications(notifications, true) } syncMessage := &protobuf.SyncActivityCenterRead{ Clock: updatedAt, Ids: fromHexBytes(ids), } encodedMessage, err := proto.Marshal(syncMessage) if err != nil { return nil, err } err = m.sendToPairedDevices(ctx, common.RawMessage{ Payload: encodedMessage, MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACTIVITY_CENTER_READ, ResendAutomatically: true, }) if err != nil { return nil, err } state, err := m.persistence.GetActivityCenterState() if err != nil { return nil, err } response.SetActivityCenterState(state) return response, nil } func (m *Messenger) MarkActivityCenterNotificationsUnread(ids []types.HexBytes) (*MessengerResponse, error) { response := &MessengerResponse{} notifications, err := m.persistence.MarkActivityCenterNotificationsUnread(ids, m.getCurrentTimeInMillis()) if err != nil { return nil, err } err = m.syncActivityCenterNotifications(notifications) if err != nil { m.logger.Error("MarkActivityCenterNotificationsUnread, failed to sync activity center notifications", zap.Error(err)) return nil, err } state, err := m.persistence.GetActivityCenterState() if err != nil { return nil, err } response.SetActivityCenterState(state) return response, nil } func (m *Messenger) processActivityCenterNotifications(notifications []*ActivityCenterNotification, addNotifications bool) (*MessengerResponse, error) { response := &MessengerResponse{} var chats []*Chat for _, notification := range notifications { if notification.ChatID != "" { chat, ok := m.allChats.Load(notification.ChatID) if !ok { // This should not really happen, but ignore just in case it was deleted in the meantime m.logger.Warn("chat not found") continue } chat.Active = true if chat.PrivateGroupChat() { // Send Joined message for backward compatibility _, err := m.ConfirmJoiningGroup(context.Background(), chat.ID) if err != nil { m.logger.Error("failed to join group", zap.Error(err)) return nil, err } } chats = append(chats, chat) response.AddChat(chat) } if addNotifications { response.AddActivityCenterNotification(notification) } } if len(chats) != 0 { err := m.saveChats(chats) if err != nil { return nil, err } } return response, nil } func (m *Messenger) processAcceptedActivityCenterNotifications(ctx context.Context, notifications []*ActivityCenterNotification, sync bool) (*MessengerResponse, error) { ids := make([][]byte, len(notifications)) for i := range notifications { ids[i] = notifications[i].ID } if sync { syncMessage := &protobuf.SyncActivityCenterAccepted{ Clock: m.getCurrentTimeInMillis(), Ids: ids, } encodedMessage, err := proto.Marshal(syncMessage) if err != nil { return nil, err } err = m.sendToPairedDevices(ctx, common.RawMessage{ Payload: encodedMessage, MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACTIVITY_CENTER_ACCEPTED, ResendAutomatically: true, }) if err != nil { return nil, err } } return m.processActivityCenterNotifications(notifications, !sync) } func (m *Messenger) AcceptActivityCenterNotifications(ctx context.Context, ids []types.HexBytes, updatedAt uint64, sync bool) (*MessengerResponse, error) { if len(ids) == 0 { return nil, errors.New("notifications ids are not provided") } if updatedAt == 0 { updatedAt = m.getCurrentTimeInMillis() } notifications, err := m.persistence.AcceptActivityCenterNotifications(ids, updatedAt) if err != nil { return nil, err } return m.processAcceptedActivityCenterNotifications(ctx, notifications, sync) } func (m *Messenger) DismissActivityCenterNotifications(ctx context.Context, ids []types.HexBytes, updatedAt uint64, sync bool) (*MessengerResponse, error) { if updatedAt == 0 { updatedAt = m.getCurrentTimeInMillis() } err := m.persistence.DismissActivityCenterNotifications(ids, updatedAt) if err != nil { return nil, err } if !sync { notifications, err := m.persistence.GetActivityCenterNotificationsByID(ids) if err != nil { return nil, err } return m.processActivityCenterNotifications(notifications, true) } syncMessage := &protobuf.SyncActivityCenterDismissed{ Clock: updatedAt, Ids: fromHexBytes(ids), } encodedMessage, err := proto.Marshal(syncMessage) if err != nil { return nil, err } err = m.sendToPairedDevices(ctx, common.RawMessage{ Payload: encodedMessage, MessageType: protobuf.ApplicationMetadataMessage_SYNC_ACTIVITY_CENTER_DISMISSED, ResendAutomatically: true, }) return nil, err } func (m *Messenger) DeleteActivityCenterNotifications(ctx context.Context, ids []types.HexBytes, sync bool) error { notifications, err := m.persistence.DeleteActivityCenterNotifications(ids, m.getCurrentTimeInMillis()) if err != nil { return err } err = m.syncActivityCenterNotifications(notifications) if err != nil { m.logger.Error("DeleteActivityCenterNotifications, failed to sync activity center notifications", zap.Error(err)) } return err } func (m *Messenger) ActivityCenterNotification(id types.HexBytes) (*ActivityCenterNotification, error) { notification, err := m.persistence.GetActivityCenterNotificationByID(id) if err != nil { return nil, err } if notification.Message != nil { image := notification.Message.GetImage() if image != nil && image.AlbumId != "" { album, err := m.persistence.albumMessages(notification.Message.LocalChatID, image.AlbumId) if err != nil { return nil, err } notification.AlbumMessages = album } } return notification, nil } func (m *Messenger) HandleSyncActivityCenterRead(state *ReceivedMessageState, message *protobuf.SyncActivityCenterRead, statusMessage *v1protocol.StatusMessage) error { resp, err := m.MarkActivityCenterNotificationsRead(context.TODO(), toHexBytes(message.Ids), message.Clock, false) if err != nil { return err } return state.Response.Merge(resp) } func (m *Messenger) HandleSyncActivityCenterAccepted(state *ReceivedMessageState, message *protobuf.SyncActivityCenterAccepted, statusMessage *v1protocol.StatusMessage) error { resp, err := m.AcceptActivityCenterNotifications(context.TODO(), toHexBytes(message.Ids), message.Clock, false) if err != nil { return err } return state.Response.Merge(resp) } func (m *Messenger) HandleSyncActivityCenterDismissed(state *ReceivedMessageState, message *protobuf.SyncActivityCenterDismissed, statusMessage *v1protocol.StatusMessage) error { resp, err := m.DismissActivityCenterNotifications(context.TODO(), toHexBytes(message.Ids), message.Clock, false) if err != nil { return err } return state.Response.Merge(resp) } func (m *Messenger) HandleSyncActivityCenterNotificationState(state *ReceivedMessageState, a *protobuf.SyncActivityCenterNotificationState, statusMessage *v1protocol.StatusMessage) error { s := &ActivityCenterState{ HasSeen: a.HasSeen, UpdatedAt: a.UpdatedAt, } n, err := m.persistence.UpdateActivityCenterNotificationState(s) if err != nil { return err } if n > 0 { state.Response.SetActivityCenterState(s) } return nil } func (m *Messenger) HandleSyncActivityCenterNotifications(state *ReceivedMessageState, a *protobuf.SyncActivityCenterNotifications, statusMessage *v1protocol.StatusMessage) error { var notifications []*ActivityCenterNotification for _, n := range a.ActivityCenterNotifications { notification, err := convertActivityCenterNotificationFromProtobuf(n) if err != nil { return err } affectedNum, err := m.persistence.SaveActivityCenterNotification(notification, false) if err != nil { return err } if affectedNum > 0 { notifications = append(notifications, notification) } } response, err := m.processActivityCenterNotifications(notifications, true) if err != nil { return err } return state.Response.Merge(response) } func convertActivityCenterNotificationToProtobuf(n ActivityCenterNotification) (syncActivityCenterNotification *protobuf.SyncActivityCenterNotification, err error) { var ( message []byte replyMessage []byte ) if n.Message != nil { message, err = json.Marshal(n.Message) if err != nil { return } } if n.ReplyMessage != nil { replyMessage, err = json.Marshal(n.ReplyMessage) if err != nil { return } } syncActivityCenterNotification = &protobuf.SyncActivityCenterNotification{ Id: n.ID, Timestamp: n.Timestamp, NotificationType: protobuf.SyncActivityCenterNotification_NotificationType(n.Type), ChatId: n.ChatID, Read: n.Read, Dismissed: n.Dismissed, Accepted: n.Accepted, Message: message, Author: n.Author, ReplyMessage: replyMessage, CommunityId: n.CommunityID, MembershipStatus: protobuf.SyncActivityCenterNotification_MembershipStatus(n.MembershipStatus), ContactVerificationStatus: protobuf.SyncActivityCenterNotification_ContactVerificationStatus(n.ContactVerificationStatus), Deleted: n.Deleted, UpdatedAt: n.UpdatedAt, } return } func convertActivityCenterNotificationFromProtobuf(proto *protobuf.SyncActivityCenterNotification) (*ActivityCenterNotification, error) { if proto == nil { return nil, errors.New("convertActivityCenterNotificationFromProtobuf, proto is nil") } a := &ActivityCenterNotification{ ID: proto.Id, ChatID: proto.ChatId, CommunityID: proto.CommunityId, MembershipStatus: ActivityCenterMembershipStatus(proto.MembershipStatus), Author: proto.Author, Type: ActivityCenterType(proto.NotificationType), Timestamp: proto.Timestamp, Read: proto.Read, Accepted: proto.Accepted, Dismissed: proto.Dismissed, Deleted: proto.Deleted, ContactVerificationStatus: verification.RequestStatus(proto.ContactVerificationStatus), UpdatedAt: proto.UpdatedAt, } if len(proto.Message) > 0 { message := common.NewMessage() err := json.Unmarshal(proto.Message, &message) if err != nil { return nil, err } a.Message = message } if len(proto.ReplyMessage) > 0 { replyMessage := common.NewMessage() err := json.Unmarshal(proto.ReplyMessage, &replyMessage) if err != nil { return nil, err } a.ReplyMessage = replyMessage } return a, nil }