github.com/status-im/status-go@v1.1.0/protocol/messenger_status_updates.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/golang/protobuf/proto"
     9  	"go.uber.org/zap"
    10  
    11  	datasyncnode "github.com/status-im/mvds/node"
    12  
    13  	datasyncpeer "github.com/status-im/status-go/protocol/datasync/peer"
    14  
    15  	"github.com/status-im/status-go/multiaccounts/settings"
    16  	"github.com/status-im/status-go/protocol/common"
    17  	"github.com/status-im/status-go/protocol/communities"
    18  	"github.com/status-im/status-go/protocol/protobuf"
    19  	"github.com/status-im/status-go/protocol/transport"
    20  	v1protocol "github.com/status-im/status-go/protocol/v1"
    21  )
    22  
    23  func (m *Messenger) GetCurrentUserStatus() (*UserStatus, error) {
    24  
    25  	status := &UserStatus{
    26  		StatusType: int(protobuf.StatusUpdate_AUTOMATIC),
    27  		Clock:      0,
    28  		CustomText: "",
    29  	}
    30  
    31  	err := m.settings.GetCurrentStatus(status)
    32  	if err != nil {
    33  		m.logger.Debug("Error obtaining latest status", zap.Error(err))
    34  		return nil, err
    35  	}
    36  
    37  	return status, nil
    38  }
    39  
    40  func (m *Messenger) sendUserStatus(ctx context.Context, status UserStatus) error {
    41  	shouldBroadcastUserStatus, err := m.settings.ShouldBroadcastUserStatus()
    42  	if err != nil {
    43  		return err
    44  	}
    45  
    46  	if !shouldBroadcastUserStatus {
    47  		m.logger.Debug("user status should not be broadcasted")
    48  		return nil
    49  	}
    50  
    51  	status.Clock = uint64(time.Now().Unix())
    52  
    53  	err = m.settings.SaveSettingField(settings.CurrentUserStatus, status)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	statusUpdate := &protobuf.StatusUpdate{
    59  		Clock:      status.Clock,
    60  		StatusType: protobuf.StatusUpdate_StatusType(status.StatusType),
    61  		CustomText: status.CustomText,
    62  	}
    63  
    64  	encodedMessage, err := proto.Marshal(statusUpdate)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	contactCodeTopic := transport.ContactCodeTopic(&m.identity.PublicKey)
    70  
    71  	rawMessage := common.RawMessage{
    72  		LocalChatID: contactCodeTopic,
    73  		Payload:     encodedMessage,
    74  		MessageType: protobuf.ApplicationMetadataMessage_STATUS_UPDATE,
    75  		ResendType:  common.ResendTypeNone, // does this need to be resent?
    76  		Ephemeral:   statusUpdate.StatusType == protobuf.StatusUpdate_AUTOMATIC,
    77  		Priority:    &common.LowPriority,
    78  	}
    79  
    80  	_, err = m.sender.SendPublic(ctx, contactCodeTopic, rawMessage)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	joinedCommunities, err := m.communitiesManager.Joined()
    86  	if err != nil {
    87  		return err
    88  	}
    89  	for _, community := range joinedCommunities {
    90  		rawMessage.LocalChatID = community.StatusUpdatesChannelID()
    91  		rawMessage.PubsubTopic = community.PubsubTopic()
    92  		_, err = m.sender.SendPublic(ctx, rawMessage.LocalChatID, rawMessage)
    93  		if err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  func (m *Messenger) sendCurrentUserStatus(ctx context.Context) {
   102  	err := m.persistence.CleanOlderStatusUpdates()
   103  	if err != nil {
   104  		m.logger.Debug("Error cleaning status updates", zap.Error(err))
   105  		return
   106  	}
   107  
   108  	shouldBroadcastUserStatus, err := m.settings.ShouldBroadcastUserStatus()
   109  	if err != nil {
   110  		m.logger.Debug("Error while getting status broadcast setting", zap.Error(err))
   111  		return
   112  	}
   113  
   114  	if !shouldBroadcastUserStatus {
   115  		m.logger.Debug("user status should not be broadcasted")
   116  		return
   117  	}
   118  
   119  	currStatus, err := m.GetCurrentUserStatus()
   120  	if err != nil {
   121  		m.logger.Debug("Error obtaining latest status", zap.Error(err))
   122  		return
   123  	}
   124  
   125  	if err := m.sendUserStatus(ctx, *currStatus); err != nil {
   126  		m.logger.Debug("Error when sending the latest user status", zap.Error(err))
   127  	}
   128  }
   129  
   130  func (m *Messenger) sendCurrentUserStatusToCommunity(ctx context.Context, community *communities.Community) error {
   131  	logger := m.logger.Named("sendCurrentUserStatusToCommunity")
   132  
   133  	shouldBroadcastUserStatus, err := m.settings.ShouldBroadcastUserStatus()
   134  	if err != nil {
   135  		logger.Debug("m.settings.ShouldBroadcastUserStatus error", zap.Error(err))
   136  		return err
   137  	}
   138  
   139  	if !shouldBroadcastUserStatus {
   140  		logger.Debug("user status should not be broadcasted")
   141  		return nil
   142  	}
   143  
   144  	status, err := m.GetCurrentUserStatus()
   145  	if err != nil {
   146  		logger.Debug("Error obtaining latest status", zap.Error(err))
   147  		return err
   148  	}
   149  
   150  	status.Clock = uint64(time.Now().Unix())
   151  
   152  	err = m.settings.SaveSettingField(settings.CurrentUserStatus, status)
   153  	if err != nil {
   154  		logger.Debug("m.settings.SaveSetting error",
   155  			zap.Any("current-user-status", status),
   156  			zap.Error(err))
   157  		return err
   158  	}
   159  
   160  	statusUpdate := &protobuf.StatusUpdate{
   161  		Clock:      status.Clock,
   162  		StatusType: protobuf.StatusUpdate_StatusType(status.StatusType),
   163  		CustomText: status.CustomText,
   164  	}
   165  
   166  	encodedMessage, err := proto.Marshal(statusUpdate)
   167  	if err != nil {
   168  		logger.Debug("proto.Marshal error",
   169  			zap.Any("protobuf.StatusUpdate", statusUpdate),
   170  			zap.Error(err))
   171  		return err
   172  	}
   173  
   174  	rawMessage := common.RawMessage{
   175  		LocalChatID: community.StatusUpdatesChannelID(),
   176  		Payload:     encodedMessage,
   177  		MessageType: protobuf.ApplicationMetadataMessage_STATUS_UPDATE,
   178  		ResendType:  common.ResendTypeNone, // does this need to be resent?
   179  		Ephemeral:   statusUpdate.StatusType == protobuf.StatusUpdate_AUTOMATIC,
   180  		PubsubTopic: community.PubsubTopic(),
   181  		Priority:    &common.LowPriority,
   182  	}
   183  
   184  	_, err = m.sender.SendPublic(ctx, rawMessage.LocalChatID, rawMessage)
   185  	if err != nil {
   186  		logger.Debug("m.sender.SendPublic error", zap.Error(err))
   187  		return err
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  func (m *Messenger) broadcastLatestUserStatus() {
   194  	m.logger.Debug("broadcasting user status")
   195  	ctx := context.Background()
   196  	go func() {
   197  		// Ensure that we are connected before sending a message
   198  		time.Sleep(5 * time.Second)
   199  		m.sendCurrentUserStatus(ctx)
   200  	}()
   201  
   202  	go func() {
   203  		for {
   204  			select {
   205  			case <-time.After(5 * time.Minute):
   206  				m.sendCurrentUserStatus(ctx)
   207  			case <-m.quit:
   208  				return
   209  			}
   210  		}
   211  	}()
   212  }
   213  
   214  func (m *Messenger) SetUserStatus(ctx context.Context, newStatus int, newCustomText string) error {
   215  	if len([]rune(newCustomText)) > maxStatusMessageText {
   216  		return fmt.Errorf("custom text shouldn't be longer than %d", maxStatusMessageText)
   217  	}
   218  
   219  	if newStatus != int(protobuf.StatusUpdate_AUTOMATIC) &&
   220  		newStatus != int(protobuf.StatusUpdate_DO_NOT_DISTURB) &&
   221  		newStatus != int(protobuf.StatusUpdate_ALWAYS_ONLINE) &&
   222  		newStatus != int(protobuf.StatusUpdate_INACTIVE) {
   223  		return fmt.Errorf("unknown status type")
   224  	}
   225  
   226  	currStatus, err := m.GetCurrentUserStatus()
   227  	if err != nil {
   228  		m.logger.Debug("Error obtaining latest status", zap.Error(err))
   229  		return err
   230  	}
   231  
   232  	if newStatus == currStatus.StatusType && newCustomText == currStatus.CustomText {
   233  		m.logger.Debug("Status type did not change")
   234  		return nil
   235  	}
   236  
   237  	currStatus.StatusType = newStatus
   238  	currStatus.CustomText = newCustomText
   239  
   240  	return m.sendUserStatus(ctx, *currStatus)
   241  }
   242  
   243  func (m *Messenger) HandleStatusUpdate(state *ReceivedMessageState, message *protobuf.StatusUpdate, statusMessage *v1protocol.StatusMessage) error {
   244  	if err := ValidateStatusUpdate(message); err != nil {
   245  		return err
   246  	}
   247  
   248  	if common.IsPubKeyEqual(state.CurrentMessageState.PublicKey, &m.identity.PublicKey) { // Status message is ours
   249  		currentStatus, err := m.GetCurrentUserStatus()
   250  		if err != nil {
   251  			m.logger.Debug("Error obtaining latest status", zap.Error(err))
   252  			return err
   253  		}
   254  
   255  		if currentStatus.Clock >= message.Clock {
   256  			return nil // older status message, or status does not change ignoring it
   257  		}
   258  		newStatus := ToUserStatus(message)
   259  		err = m.settings.SaveSettingField(settings.CurrentUserStatus, newStatus)
   260  		if err != nil {
   261  			return err
   262  		}
   263  		state.Response.SetCurrentStatus(newStatus)
   264  	} else {
   265  		statusUpdate := ToUserStatus(message)
   266  		statusUpdate.PublicKey = state.CurrentMessageState.Contact.ID
   267  
   268  		err := m.persistence.InsertStatusUpdate(statusUpdate)
   269  		if err != nil {
   270  			return err
   271  		}
   272  		state.Response.AddStatusUpdate(statusUpdate)
   273  		if statusUpdate.StatusType == int(protobuf.StatusUpdate_AUTOMATIC) ||
   274  			statusUpdate.StatusType == int(protobuf.StatusUpdate_ALWAYS_ONLINE) ||
   275  			statusUpdate.StatusType == int(protobuf.StatusUpdate_INACTIVE) {
   276  			m.logger.Debug("reset data sync for peer", zap.String("public_key", statusUpdate.PublicKey), zap.Uint64("clock", statusUpdate.Clock))
   277  			select {
   278  			case m.mvdsStatusChangeEvent <- datasyncnode.PeerStatusChangeEvent{
   279  				PeerID:    datasyncpeer.PublicKeyToPeerID(*state.CurrentMessageState.PublicKey),
   280  				Status:    datasyncnode.OnlineStatus,
   281  				EventTime: statusUpdate.Clock,
   282  			}:
   283  			default:
   284  				m.logger.Debug("mvdsStatusChangeEvent channel is full")
   285  			}
   286  
   287  		}
   288  	}
   289  
   290  	return nil
   291  }
   292  
   293  func (m *Messenger) StatusUpdates() ([]UserStatus, error) {
   294  	return m.persistence.StatusUpdates()
   295  }
   296  
   297  func (m *Messenger) timeoutStatusUpdates(fromClock uint64, tillClock uint64) {
   298  	// Most of the time we only need to time out just one status update,
   299  	// but the range covers special cases like, other status updates had the same clock value
   300  	// or the received another status update with higher clock value than the reference clock but
   301  	// lower clock value than the nextClock
   302  	deactivatedStatusUpdates, err := m.persistence.DeactivatedAutomaticStatusUpdates(fromClock, tillClock)
   303  
   304  	// Send deactivatedStatusUpdates to Client
   305  	if err == nil {
   306  		if m.config.messengerSignalsHandler != nil {
   307  			m.config.messengerSignalsHandler.StatusUpdatesTimedOut(&deactivatedStatusUpdates)
   308  		}
   309  	} else {
   310  		m.logger.Debug("Unable to get deactivated automatic status updates from db", zap.Error(err))
   311  	}
   312  }
   313  
   314  func (m *Messenger) timeoutAutomaticStatusUpdates() {
   315  
   316  	nextClock := uint64(0)
   317  	waitDuration := uint64(10) // Initial 10 sec wait, to make sure new status updates are fetched before starting timing out loop
   318  	fiveMinutes := uint64(5 * 60)
   319  	referenceClock := uint64(time.Now().Unix()) - fiveMinutes
   320  
   321  	go func() {
   322  		for {
   323  			select {
   324  			case <-time.After(time.Duration(waitDuration) * time.Second):
   325  				tempNextClock, err := m.persistence.NextHigherClockValueOfAutomaticStatusUpdates(referenceClock)
   326  
   327  				if err == nil {
   328  					if nextClock == 0 || tempNextClock > nextClock {
   329  						nextClock = tempNextClock
   330  						// Extra 5 sec wait (broadcast receiving delay)
   331  						waitDuration = tempNextClock + fiveMinutes + 5 - uint64(time.Now().Unix())
   332  					} else {
   333  						m.timeoutStatusUpdates(referenceClock, tempNextClock)
   334  						waitDuration = 0
   335  						referenceClock = tempNextClock
   336  					}
   337  				} else if err == common.ErrRecordNotFound {
   338  					// No More status updates to timeout, keep loop running at five minutes interval
   339  					waitDuration = fiveMinutes
   340  				} else {
   341  					m.logger.Debug("Unable to timeout automatic status updates", zap.Error(err))
   342  					return
   343  				}
   344  			case <-m.quit:
   345  				return
   346  			}
   347  		}
   348  	}()
   349  }