DEV: Allow PresenceChannel to specify custom activity thresholds (#15217)

This allows consumers to vary the parameters on a per-channel basis. e.g. if you wanted a channel to consider someone 'away' after 10 minutes, and another channel to consider someone 'away' after 1 minute, that is now possible.
This commit is contained in:
David Taylor 2021-12-07 20:57:57 +00:00 committed by GitHub
parent 6cae6aadf4
commit f3d480dacb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 27 additions and 16 deletions

View File

@ -19,6 +19,7 @@ import userPresent, {
onPresenceChange,
removeOnPresenceChange,
} from "discourse/lib/user-presence";
import { bind } from "discourse-common/utils/decorators";
const PRESENCE_INTERVAL_S = 30;
const PRESENCE_DEBOUNCE_MS = isTesting() ? 0 : 500;
@ -26,7 +27,7 @@ const PRESENCE_THROTTLE_MS = isTesting() ? 0 : 1000;
const PRESENCE_GET_RETRY_MS = 5000;
const USER_PRESENCE_ARGS = {
const DEFAULT_ACTIVE_OPTIONS = {
userUnseenTime: 60000,
browserHiddenTime: 10000,
};
@ -63,8 +64,19 @@ class PresenceChannel extends EmberObject {
// By default, the user will temporarily 'leave' the channel when
// the current tab is in the background, or has no interaction for more than 60 seconds.
// To override this behaviour, set onlyWhileActive: false
async enter({ onlyWhileActive = true } = {}) {
this.setProperties({ onlyWhileActive });
// To specify custom thresholds, set `activeOptions`. See `lib/user-presence.js` for options.
async enter({ onlyWhileActive = true, activeOptions = null } = {}) {
if (onlyWhileActive && activeOptions) {
for (const key in DEFAULT_ACTIVE_OPTIONS) {
if (activeOptions[key] < DEFAULT_ACTIVE_OPTIONS[key]) {
throw `${key} cannot be less than ${DEFAULT_ACTIVE_OPTIONS[key]} (given ${activeOptions[key]})`;
}
}
} else if (onlyWhileActive && !activeOptions) {
activeOptions = DEFAULT_ACTIVE_OPTIONS;
}
this.setProperties({ activeOptions });
await this.presenceService._enter(this);
this.set("present", true);
}
@ -241,13 +253,10 @@ export default class PresenceService extends Service {
this._initialDataRequests = new Map();
if (this.currentUser) {
this._beforeUnloadCallback = () => this._beaconLeaveAll();
window.addEventListener("beforeunload", this._beforeUnloadCallback);
this._presenceChangeCallback = () => this._throttledUpdateServer();
window.addEventListener("beforeunload", this._beaconLeaveAll);
onPresenceChange({
...USER_PRESENCE_ARGS,
callback: this._presenceChangeCallback,
...DEFAULT_ACTIVE_OPTIONS,
callback: this._throttledUpdateServer,
});
}
}
@ -258,8 +267,8 @@ export default class PresenceService extends Service {
willDestroy() {
super.willDestroy(...arguments);
window.removeEventListener("beforeunload", this._beforeUnloadCallback);
removeOnPresenceChange(this._presenceChangeCallback);
window.removeEventListener("beforeunload", this._beaconLeaveAll);
removeOnPresenceChange(this._throttledUpdateServer);
}
// Get a PresenceChannel object representing a single channel
@ -440,6 +449,7 @@ export default class PresenceService extends Service {
}
}
@bind
_beaconLeaveAll() {
if (isTesting()) {
return;
@ -490,15 +500,15 @@ export default class PresenceService extends Service {
.filter((e) => e.type === "leave")
.map((e) => e.channel);
const userIsPresent = userPresent(USER_PRESENCE_ARGS);
for (const [channelName, proxies] of this._presentProxies) {
if (
!userIsPresent &&
Array.from(proxies).every((p) => p.onlyWhileActive)
Array.from(proxies).some((p) => {
return !p.activeOptions || userPresent(p.activeOptions);
})
) {
channelsToLeave.push(channelName);
} else {
presentChannels.push(channelName);
} else {
channelsToLeave.push(channelName);
}
}
@ -551,6 +561,7 @@ export default class PresenceService extends Service {
// in a sequence of calls. We want both. We want the first event, to make
// things very responsive. Then if things are happening too frequently, we
// drop back to the last event via the regular throttle function.
@bind
_throttledUpdateServer() {
if (
!this._lastUpdate ||