DEV: Emit a 'change' event when PresenceChannel info changes (#17088)

e.g.

```
presenceChannel = this.presence.getChannel('/blah');
presenceChannel.subscribe();
presenceChannel.on('change', (channel) => console.log(channel.users));
```

This commit also does some refactoring to remove the use of an unnecessary EmberObject and dynamic `defineProperty` call
This commit is contained in:
David Taylor 2022-06-15 16:13:44 +01:00 committed by GitHub
parent f723b4c322
commit 275849771f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 18 deletions

View File

@ -1,6 +1,5 @@
import Service from "@ember/service";
import EmberObject, { computed, defineProperty } from "@ember/object";
import { readOnly } from "@ember/object/computed";
import EmberObject, { computed } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import {
cancel,
@ -20,6 +19,7 @@ import userPresent, {
removeOnPresenceChange,
} from "discourse/lib/user-presence";
import { bind } from "discourse-common/utils/decorators";
import Evented from "@ember/object/evented";
const PRESENCE_INTERVAL_S = 30;
const PRESENCE_DEBOUNCE_MS = isTesting() ? 0 : 500;
@ -45,17 +45,12 @@ export class PresenceChannelNotFound extends Error {}
// Instances of this class are handed out to consumers. They act as
// convenient proxies to the PresenceService and PresenceServiceState
class PresenceChannel extends EmberObject {
// The 'change' event is fired whenever the users list or the count change
class PresenceChannel extends EmberObject.extend(Evented) {
init({ name, presenceService }) {
super.init(...arguments);
this.name = name;
this.presenceService = presenceService;
defineProperty(
this,
"_presenceState",
readOnly(`presenceService._presenceChannelStates.${name}`)
);
this.set("present", false);
this.set("subscribed", false);
}
@ -91,8 +86,12 @@ class PresenceChannel extends EmberObject {
if (this.subscribed) {
return;
}
await this.presenceService._subscribe(this, initialData);
const state = await this.presenceService._subscribe(this, initialData);
this.set("subscribed", true);
this.set("_presenceState", state);
this._publishChange();
state.on("change", this._publishChange);
}
async unsubscribe() {
@ -101,6 +100,14 @@ class PresenceChannel extends EmberObject {
}
await this.presenceService._unsubscribe(this);
this.set("subscribed", false);
this._presenceState.off("change", this._publishChange);
this.set("_presenceState", null);
this._publishChange();
}
@bind
_publishChange() {
this.trigger("change", this);
}
@computed("_presenceState.users", "subscribed")
@ -128,7 +135,7 @@ class PresenceChannel extends EmberObject {
}
}
class PresenceChannelState extends EmberObject {
class PresenceChannelState extends EmberObject.extend(Evented) {
init({ name, presenceService }) {
super.init(...arguments);
this.name = name;
@ -179,6 +186,7 @@ class PresenceChannelState extends EmberObject {
);
this.set("_subscribedCallback", callback);
this.trigger("change");
}
// Stop subscribing to updates from the server about this channel
@ -191,6 +199,7 @@ class PresenceChannelState extends EmberObject {
this.set("_subscribedCallback", null);
this.set("users", null);
this.set("count", null);
this.trigger("change");
}
}
@ -221,6 +230,7 @@ class PresenceChannelState extends EmberObject {
if (this.countOnly && data.count_delta !== undefined) {
this.set("count", this.count + data.count_delta);
this.trigger("change");
} else if (
!this.countOnly &&
(data.entering_users || data.leaving_user_ids)
@ -235,6 +245,7 @@ class PresenceChannelState extends EmberObject {
this.users.removeObjects(toRemove);
}
this.set("count", this.users.length);
this.trigger("change");
} else {
// Unexpected message
await this._resubscribe();
@ -247,7 +258,7 @@ export default class PresenceService extends Service {
init() {
super.init(...arguments);
this._queuedEvents = [];
this._presenceChannelStates = EmberObject.create();
this._presenceChannelStates = new Map();
this._presentProxies = new Map();
this._subscribedProxies = new Map();
this._initialDataRequests = new Map();
@ -429,7 +440,7 @@ export default class PresenceService extends Service {
this._addSubscribed(channelProxy);
const channelName = channelProxy.name;
let state = this._presenceChannelStates[channelName];
let state = this._presenceChannelStates.get(channelName);
if (!state) {
state = PresenceChannelState.create({
name: channelName,
@ -438,14 +449,15 @@ export default class PresenceService extends Service {
this._presenceChannelStates.set(channelName, state);
await state.subscribe(initialData);
}
return state;
}
_unsubscribe(channelProxy) {
const subscribedCount = this._removeSubscribed(channelProxy);
if (subscribedCount === 0) {
const channelName = channelProxy.name;
this._presenceChannelStates[channelName].unsubscribe();
this._presenceChannelStates.set(channelName, undefined);
this._presenceChannelStates.get(channelName).unsubscribe();
this._presenceChannelStates.delete(channelName);
}
}

View File

@ -59,12 +59,17 @@ acceptance("Presence - Subscribing", function (needs) {
test("subscribing and receiving updates", async function (assert) {
let presenceService = this.container.lookup("service:presence");
let channel = presenceService.getChannel("/test/ch1");
let changes = 0;
const countChanges = () => changes++;
channel.on("change", countChanges);
assert.strictEqual(channel.name, "/test/ch1");
await channel.subscribe({
users: usersFixture(),
last_message_id: 1,
});
assert.strictEqual(changes, 1);
assert.strictEqual(channel.users.length, 3, "it starts with three users");
@ -78,6 +83,7 @@ acceptance("Presence - Subscribing", function (needs) {
);
assert.strictEqual(channel.users.length, 2, "one user is removed");
assert.strictEqual(changes, 2);
publishToMessageBus(
"/presence/test/ch1",
@ -89,6 +95,8 @@ acceptance("Presence - Subscribing", function (needs) {
);
assert.strictEqual(channel.users.length, 3, "one user is added");
assert.strictEqual(changes, 3);
channel.off("change", countChanges);
});
test("fetches data when no initial state", async function (assert) {
@ -216,14 +224,14 @@ acceptance("Presence - Subscribing", function (needs) {
assert.strictEqual(channel.subscribed, false, "channel can unsubscribe");
assert.strictEqual(
channelDup._presenceState,
channel._presenceState,
"state is maintained"
presenceService._presenceChannelStates.get(channel.name),
"state is maintained in the subscribed channel"
);
await channelDup.unsubscribe();
assert.strictEqual(channel.subscribed, false, "channelDup can unsubscribe");
assert.strictEqual(
channelDup._presenceState,
presenceService._presenceChannelStates.get(channel.name),
undefined,
"state is cleared"
);