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