aboutsummaryrefslogtreecommitdiff
path: root/syncapi/sync/requestpool.go
diff options
context:
space:
mode:
Diffstat (limited to 'syncapi/sync/requestpool.go')
-rw-r--r--syncapi/sync/requestpool.go65
1 files changed, 62 insertions, 3 deletions
diff --git a/syncapi/sync/requestpool.go b/syncapi/sync/requestpool.go
index 5a92c70e..494be05f 100644
--- a/syncapi/sync/requestpool.go
+++ b/syncapi/sync/requestpool.go
@@ -120,11 +120,34 @@ 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
+type PresenceMap struct {
+ mu sync.Mutex
+ seen map[string]map[types.Presence]time.Time
+}
+
+var lastPresence PresenceMap
+
+// how long before the online status expires
+// should be long enough that any client will have another sync before expiring
+const presenceTimeout = time.Second * 10
+
// updatePresence sends presence updates to the SyncAPI and FederationAPI
func (rp *RequestPool) updatePresence(db storage.Presence, presence string, userID string) {
+ // allow checking back on presence to set offline if needed
+ rp.updatePresenceInternal(db, presence, userID, true)
+}
+
+func (rp *RequestPool) updatePresenceInternal(db storage.Presence, presence string, userID string, checkAgain bool) {
if !rp.cfg.Matrix.Presence.EnableOutbound {
return
}
+
+ // lock the map to this thread
+ lastPresence.mu.Lock()
+ defer lastPresence.mu.Unlock()
+
if presence == "" {
presence = types.PresenceOnline.String()
}
@@ -140,6 +163,41 @@ func (rp *RequestPool) updatePresence(db storage.Presence, presence string, user
LastActiveTS: spec.AsTimestamp(time.Now()),
}
+ // make sure that the map is defined correctly as needed
+ if lastPresence.seen == nil {
+ lastPresence.seen = make(map[string]map[types.Presence]time.Time)
+ }
+ if lastPresence.seen[userID] == nil {
+ lastPresence.seen[userID] = make(map[types.Presence]time.Time)
+ }
+
+ now := time.Now()
+ // update time for each presence
+ lastPresence.seen[userID][presenceID] = now
+
+ // Default to unknown presence
+ presenceToSet := types.PresenceUnknown
+ switch {
+ case now.Sub(lastPresence.seen[userID][types.PresenceOnline]) < presenceTimeout:
+ // online will always get priority
+ presenceToSet = types.PresenceOnline
+ case now.Sub(lastPresence.seen[userID][types.PresenceUnavailable]) < presenceTimeout:
+ // idle gets secondary priority because your presence shouldnt be idle if you are on a different device
+ // kinda copying discord presence
+ presenceToSet = types.PresenceUnavailable
+ case now.Sub(lastPresence.seen[userID][types.PresenceOffline]) < presenceTimeout:
+ // 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
+ presenceToSet = types.PresenceOffline
+
+ if checkAgain {
+ // after a timeout, check presence again to make sure it gets set as offline sooner or later
+ time.AfterFunc(presenceTimeout, func() {
+ rp.updatePresenceInternal(db, types.PresenceOffline.String(), userID, false)
+ })
+ }
+ }
+
// 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 +206,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 +218,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,9 +226,10 @@ 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,
)
+
}
func (rp *RequestPool) updateLastSeen(req *http.Request, device *userapi.Device) {