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 }