discourse/plugins/chat/assets/javascripts/discourse/services/chat-subscriptions-manager.js
Joffrey JAFFEUX c0808b2537
FIX: correctly makes dm creator to follow channel (#22470)
In previous changes we prevented creating a channel to also make users follow the channel. We were forcing recipients to follow the channel on message sent but this was not including the creator of the message itself.

This commit fixes it and also write an end-to-end system spec to cover these cases. The message creator service is currently being rewritten and should correctly test and ensure this logic is present.

This commit also makes changes on the frontend to instantly follow a DM when you open it, this change prevents a green dot to appear for a split second when you send a message in a channel you were previously not following. Only recipients will see the green dot.
2023-07-06 21:42:19 +02:00

447 lines
13 KiB
JavaScript

import Service, { inject as service } from "@ember/service";
import I18n from "I18n";
import { bind } from "discourse-common/utils/decorators";
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
import ChatChannelArchive from "../models/chat-channel-archive";
export default class ChatSubscriptionsManager extends Service {
@service store;
@service chatChannelsManager;
@service chatTrackingStateManager;
@service currentUser;
@service appEvents;
@service chat;
@service dialog;
@service router;
_channelSubscriptions = new Set();
startChannelSubscription(channel) {
if (
channel.currentUserMembership.muted ||
this._channelSubscriptions.has(channel.id)
) {
return;
}
this._channelSubscriptions.add(channel.id);
if (!channel.isDirectMessageChannel) {
this._startChannelMentionsSubscription(channel);
this._startKickFromChannelSubscription(channel);
}
this._startChannelNewMessagesSubscription(channel);
}
stopChannelSubscription(channel) {
this.messageBus.unsubscribe(
`/chat/${channel.id}/new-messages`,
this._onNewMessages
);
if (!channel.isDirectMessageChannel) {
this.messageBus.unsubscribe(
`/chat/${channel.id}/new-mentions`,
this._onNewMentions
);
this.messageBus.unsubscribe(
`/chat/${channel.id}/kick`,
this._onKickFromChannel
);
}
this._channelSubscriptions.delete(channel.id);
}
startChannelsSubscriptions(messageBusIds) {
this._startNewChannelSubscription(messageBusIds.new_channel);
this._startChannelArchiveStatusSubscription(messageBusIds.archive_status);
this._startUserTrackingStateSubscription(messageBusIds.user_tracking_state);
this._startChannelsEditsSubscription(messageBusIds.channel_edits);
this._startChannelsStatusChangesSubscription(messageBusIds.channel_status);
this._startChannelsMetadataChangesSubscription(
messageBusIds.channel_metadata
);
}
stopChannelsSubscriptions() {
this._stopNewChannelSubscription();
this._stopChannelArchiveStatusSubscription();
this._stopUserTrackingStateSubscription();
this._stopChannelsEditsSubscription();
this._stopChannelsStatusChangesSubscription();
this._stopChannelsMetadataChangesSubscription();
(this.chatChannelsManager.channels || []).forEach((channel) => {
this.stopChannelSubscription(channel);
});
}
_startChannelArchiveStatusSubscription(lastId) {
if (this.currentUser.admin) {
this.messageBus.subscribe(
"/chat/channel-archive-status",
this._onChannelArchiveStatusUpdate,
lastId
);
}
}
_stopChannelArchiveStatusSubscription() {
if (this.currentUser.admin) {
this.messageBus.unsubscribe(
"/chat/channel-archive-status",
this._onChannelArchiveStatusUpdate
);
}
}
_startChannelMentionsSubscription(channel) {
this.messageBus.subscribe(
`/chat/${channel.id}/new-mentions`,
this._onNewMentions,
channel.meta.message_bus_last_ids.new_mentions
);
}
_startKickFromChannelSubscription(channel) {
this.messageBus.subscribe(
`/chat/${channel.id}/kick`,
this._onKickFromChannel,
channel.meta.message_bus_last_ids.kick
);
}
@bind
_onChannelArchiveStatusUpdate(busData) {
// we don't want to fetch a channel we don't have locally because archive status changed
this.chatChannelsManager
.find(busData.chat_channel_id, { fetchIfNotFound: false })
.then((channel) => {
if (!channel) {
return;
}
channel.archive = ChatChannelArchive.create(busData);
});
}
@bind
_onNewMentions(busData) {
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
const membership = channel.currentUserMembership;
if (busData.message_id > membership?.lastReadMessageId) {
channel.tracking.mentionCount++;
}
});
}
@bind
_onKickFromChannel(busData) {
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
if (this.chat.activeChannel.id === channel.id) {
this.dialog.alert({
message: I18n.t("chat.kicked_from_channel"),
didConfirm: () => {
this.chatChannelsManager.remove(channel);
const firstChannel =
this.chatChannelsManager.publicMessageChannels[0];
if (firstChannel) {
this.router.transitionTo(
"chat.channel",
...firstChannel.routeModels
);
} else {
this.router.transitionTo("chat.browse");
}
},
});
} else {
this.chatChannelsManager.remove(channel);
}
});
}
_startChannelNewMessagesSubscription(channel) {
this.messageBus.subscribe(
`/chat/${channel.id}/new-messages`,
this._onNewMessages,
channel.meta.message_bus_last_ids.new_messages
);
}
@bind
_onNewMessages(busData) {
switch (busData.type) {
case "channel":
this._onNewChannelMessage(busData);
break;
case "thread":
this._onNewThreadMessage(busData);
break;
}
}
_onNewChannelMessage(busData) {
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
if (busData.user_id === this.currentUser.id) {
// User sent message, update tracking state to no unread
channel.currentUserMembership.lastReadMessageId = busData.message_id;
} else {
// Ignored user sent message, update tracking state to no unread
if (this.currentUser.ignored_users.includes(busData.username)) {
channel.currentUserMembership.lastReadMessageId = busData.message_id;
} else {
if (
busData.message_id >
(channel.currentUserMembership.lastReadMessageId || 0)
) {
channel.tracking.unreadCount++;
}
// Thread should be considered unread if not already.
if (busData.thread_id) {
channel.threadsManager
.find(busData.channel_id, busData.thread_id)
.then((thread) => {
if (thread.currentUserMembership) {
channel.unreadThreadIds.add(busData.thread_id);
}
});
}
}
}
channel.lastMessageSentAt = new Date();
});
}
_onNewThreadMessage(busData) {
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
channel.threadsManager
.find(busData.channel_id, busData.thread_id)
.then((thread) => {
if (busData.user_id === this.currentUser.id) {
// Thread should no longer be considered unread.
if (thread.currentUserMembership) {
channel.unreadThreadIds.delete(busData.thread_id);
thread.currentUserMembership.lastReadMessageId =
busData.message_id;
}
} else {
// Ignored user sent message, update tracking state to no unread
if (this.currentUser.ignored_users.includes(busData.username)) {
if (thread.currentUserMembership) {
thread.currentUserMembership.lastReadMessageId =
busData.message_id;
}
} else {
// Message from other user. Increment unread for thread tracking state.
if (
thread.currentUserMembership &&
busData.message_id >
(thread.currentUserMembership.lastReadMessageId || 0) &&
!thread.currentUserMembership.isQuiet
) {
channel.unreadThreadIds.add(busData.thread_id);
thread.tracking.unreadCount++;
}
}
}
});
});
}
_startUserTrackingStateSubscription(lastId) {
if (!this.currentUser) {
return;
}
this.messageBus.subscribe(
`/chat/user-tracking-state/${this.currentUser.id}`,
this._onUserTrackingStateUpdate,
lastId
);
this.messageBus.subscribe(
`/chat/bulk-user-tracking-state/${this.currentUser.id}`,
this._onBulkUserTrackingStateUpdate,
lastId
);
}
_stopUserTrackingStateSubscription() {
if (!this.currentUser) {
return;
}
this.messageBus.unsubscribe(
`/chat/user-tracking-state/${this.currentUser.id}`,
this._onUserTrackingStateUpdate
);
this.messageBus.unsubscribe(
`/chat/bulk-user-tracking-state/${this.currentUser.id}`,
this._onBulkUserTrackingStateUpdate
);
}
@bind
_onBulkUserTrackingStateUpdate(busData) {
Object.keys(busData).forEach((channelId) => {
this._updateChannelTrackingData(channelId, busData[channelId]);
});
}
@bind
_onUserTrackingStateUpdate(busData) {
this._updateChannelTrackingData(busData.channel_id, busData);
}
@bind
_updateChannelTrackingData(channelId, busData) {
this.chatChannelsManager.find(channelId).then((channel) => {
if (!busData.thread_id) {
channel.currentUserMembership.lastReadMessageId =
busData.last_read_message_id;
}
channel.tracking.unreadCount = busData.unread_count;
channel.tracking.mentionCount = busData.mention_count;
if (busData.hasOwnProperty("unread_thread_ids")) {
channel.unreadThreadIds = busData.unread_thread_ids;
}
if (busData.thread_id && busData.hasOwnProperty("thread_tracking")) {
channel.threadsManager
.find(channelId, busData.thread_id)
.then((thread) => {
if (
thread.currentUserMembership &&
!thread.currentUserMembership.isQuiet
) {
thread.currentUserMembership.lastReadMessageId =
busData.last_read_message_id;
thread.tracking.unreadCount =
busData.thread_tracking.unread_count;
thread.tracking.mentionCount =
busData.thread_tracking.mention_count;
}
});
}
});
}
_startNewChannelSubscription(lastId) {
this.messageBus.subscribe(
"/chat/new-channel",
this._onNewChannelSubscription,
lastId
);
}
_stopNewChannelSubscription() {
this.messageBus.unsubscribe(
"/chat/new-channel",
this._onNewChannelSubscription
);
}
@bind
_onNewChannelSubscription(data) {
this.chatChannelsManager.find(data.channel.id).then((channel) => {
// we need to refresh here to have correct last message ids
channel.meta = data.channel.meta;
channel.currentUserMembership = data.channel.current_user_membership;
if (
channel.isDirectMessageChannel &&
!channel.currentUserMembership.following
) {
channel.tracking.unreadCount = 1;
}
this.chatChannelsManager.follow(channel);
});
}
_startChannelsMetadataChangesSubscription(lastId) {
this.messageBus.subscribe(
"/chat/channel-metadata",
this._onChannelMetadata,
lastId
);
}
_startChannelsEditsSubscription(lastId) {
this.messageBus.subscribe(
"/chat/channel-edits",
this._onChannelEdits,
lastId
);
}
_startChannelsStatusChangesSubscription(lastId) {
this.messageBus.subscribe(
"/chat/channel-status",
this._onChannelStatus,
lastId
);
}
_stopChannelsStatusChangesSubscription() {
this.messageBus.unsubscribe("/chat/channel-status", this._onChannelStatus);
}
_stopChannelsEditsSubscription() {
this.messageBus.unsubscribe("/chat/channel-edits", this._onChannelEdits);
}
_stopChannelsMetadataChangesSubscription() {
this.messageBus.unsubscribe(
"/chat/channel-metadata",
this._onChannelMetadata
);
}
@bind
_onChannelMetadata(busData) {
this.chatChannelsManager
.find(busData.chat_channel_id, { fetchIfNotFound: false })
.then((channel) => {
if (channel) {
channel.membershipsCount = busData.memberships_count;
this.appEvents.trigger("chat:refresh-channel-members");
}
});
}
@bind
_onChannelEdits(busData) {
this.chatChannelsManager.find(busData.chat_channel_id).then((channel) => {
if (channel) {
channel.title = busData.name;
channel.description = busData.description;
channel.slug = busData.slug;
}
});
}
@bind
_onChannelStatus(busData) {
this.chatChannelsManager.find(busData.chat_channel_id).then((channel) => {
channel.status = busData.status;
// it is not possible for the user to set their last read message id
// if the channel has been archived, because all the messages have
// been deleted. we don't want them seeing the blue dot anymore so
// just completely reset the unreads
if (busData.status === CHANNEL_STATUSES.archived) {
channel.tracking.reset();
}
});
}
}