diff --git a/clientapi/routing/presence.go b/clientapi/routing/presence.go index a0190c38c..5aa6d8dd2 100644 --- a/clientapi/routing/presence.go +++ b/clientapi/routing/presence.go @@ -37,14 +37,6 @@ type presenceReq struct { StatusMsg *string `json:"status_msg,omitempty"` } -// set a unix timestamp of when it last saw the types -// this way it can filter based on time -var lastPresence map[int]int64 = make(map[int]int64) - -// how long before the online status expires -// should be long enough that any client will have another sync before expiring -const presenceTimeout int64 = 10 - func SetPresence( req *http.Request, cfg *config.ClientAPI, @@ -52,10 +44,6 @@ func SetPresence( producer *producers.SyncAPIProducer, userID string, ) util.JSONResponse { - - //grab time for caching - workingTime := time.Now().Unix() - if !cfg.Matrix.Presence.EnableOutbound { return util.JSONResponse{ Code: http.StatusOK, @@ -81,54 +69,12 @@ func SetPresence( JSON: spec.Unknown(fmt.Sprintf("Unknown presence '%s'.", presence.Presence)), } } - - //update time for each presence - lastPresence[int(presenceStatus)] = workingTime - - //online will always get priority - if (workingTime - lastPresence[int(types.PresenceOnline)]) < presenceTimeout { - err := producer.SendPresence(req.Context(), userID, types.PresenceOnline, presence.StatusMsg) - if err != nil { - log.WithError(err).Errorf("failed to update presence to Online") - return util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - - //idle gets secondary priority because your presence shouldnt be idle if you are on a different device - //kinda copying discord presence - } else if (workingTime - lastPresence[int(types.PresenceUnavailable)]) < presenceTimeout { - err := producer.SendPresence(req.Context(), userID, types.PresenceUnavailable, presence.StatusMsg) - if err != nil { - log.WithError(err).Errorf("failed to update presence to Unavailable (~idle)") - return util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - - //only set offline status if there is no known online devices - //clients may set offline to attempt to not alter the online status of the user - } else if (workingTime - lastPresence[int(types.PresenceOffline)]) < presenceTimeout { - err := producer.SendPresence(req.Context(), userID, types.PresenceOffline, presence.StatusMsg) - if err != nil { - log.WithError(err).Errorf("failed to update presence to Offline") - return util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } - } - - //set unknown if there is truly no devices that we know the state of - } else { - err := producer.SendPresence(req.Context(), userID, types.PresenceUnknown, presence.StatusMsg) - if err != nil { - log.WithError(err).Errorf("failed to update presence as Unknown") - return util.JSONResponse{ - Code: http.StatusInternalServerError, - JSON: spec.InternalServerError{}, - } + err := producer.SendPresence(req.Context(), userID, presenceStatus, presence.StatusMsg) + if err != nil { + log.WithError(err).Errorf("failed to update presence") + return util.JSONResponse{ + Code: http.StatusInternalServerError, + JSON: spec.InternalServerError{}, } } diff --git a/syncapi/producers/federationapi_presence.go b/syncapi/producers/federationapi_presence.go index eab1b0b25..4d49986a5 100644 --- a/syncapi/producers/federationapi_presence.go +++ b/syncapi/producers/federationapi_presence.go @@ -33,6 +33,7 @@ type FederationAPIPresenceProducer struct { func (f *FederationAPIPresenceProducer) SendPresence( userID string, presence types.Presence, statusMsg *string, ) error { + println("setting presence for", userID, " as ", presence.String()) msg := nats.NewMsg(f.Topic) msg.Header.Set(jetstream.UserID, userID) msg.Header.Set("presence", presence.String()) diff --git a/syncapi/sync/requestpool.go b/syncapi/sync/requestpool.go index 5a92c70e1..3bf234b5c 100644 --- a/syncapi/sync/requestpool.go +++ b/syncapi/sync/requestpool.go @@ -120,8 +120,20 @@ func (rp *RequestPool) cleanPresence(db storage.Presence, cleanupTime time.Durat } } +// set a unix timestamp of when it last saw the types +// this way it can filter based on time +var lastPresence map[string]map[int]int64 = make(map[string]map[int]int64) + +// how long before the online status expires +// should be long enough that any client will have another sync before expiring +const presenceTimeout int64 = 10 + // updatePresence sends presence updates to the SyncAPI and FederationAPI func (rp *RequestPool) updatePresence(db storage.Presence, presence string, userID string) { + + //grab time for caching + workingTime := time.Now().Unix() + if !rp.cfg.Matrix.Presence.EnableOutbound { return } @@ -140,6 +152,30 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user LastActiveTS: spec.AsTimestamp(time.Now()), } + //update time for each presence + lastPresence[userID][int(presenceID)] = workingTime + + var presenceToSet types.Presence + + //online will always get priority + if (workingTime - lastPresence[userID][int(types.PresenceOnline)]) < presenceTimeout { + presenceToSet = types.PresenceOnline + + //idle gets secondary priority because your presence shouldnt be idle if you are on a different device + //kinda copying discord presence + } else if (workingTime - lastPresence[userID][int(types.PresenceUnavailable)]) < presenceTimeout { + presenceToSet = types.PresenceUnavailable + + //only set offline status if there is no known online devices + //clients may set offline to attempt to not alter the online status of the user + } else if (workingTime - lastPresence[userID][int(types.PresenceOffline)]) < presenceTimeout { + presenceToSet = types.PresenceOffline + + //set unknown if there is truly no devices that we know the state of + } else { + presenceToSet = types.PresenceUnknown + } + // ensure we also send the current status_msg to federated servers and not nil dbPresence, err := db.GetPresences(context.Background(), []string{userID}) if err != nil && err != sql.ErrNoRows { @@ -148,7 +184,7 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user if len(dbPresence) > 0 && dbPresence[0] != nil { newPresence.ClientFields = dbPresence[0].ClientFields } - newPresence.ClientFields.Presence = presenceID.String() + newPresence.ClientFields.Presence = presenceToSet.String() defer rp.presence.Store(userID, newPresence) // avoid spamming presence updates when syncing @@ -160,7 +196,7 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user } } - if err := rp.producer.SendPresence(userID, presenceID, newPresence.ClientFields.StatusMsg); err != nil { + if err := rp.producer.SendPresence(userID, presenceToSet, newPresence.ClientFields.StatusMsg); err != nil { logrus.WithError(err).Error("Unable to publish presence message from sync") return } @@ -168,7 +204,7 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user // now synchronously update our view of the world. It's critical we do this before calculating // the /sync response else we may not return presence: online immediately. rp.consumer.EmitPresence( - context.Background(), userID, presenceID, newPresence.ClientFields.StatusMsg, + context.Background(), userID, presenceToSet, newPresence.ClientFields.StatusMsg, spec.AsTimestamp(time.Now()), true, ) }