fix: allow disabling member lists
This commit is contained in:
parent
67267d509d
commit
f1bfd080e2
@ -20,6 +20,7 @@
|
|||||||
import styles from '@app/components/channel/ChannelMembers.module.css';
|
import styles from '@app/components/channel/ChannelMembers.module.css';
|
||||||
import {MemberListContainer} from '@app/components/channel/MemberListContainer';
|
import {MemberListContainer} from '@app/components/channel/MemberListContainer';
|
||||||
import {MemberListItem} from '@app/components/channel/MemberListItem';
|
import {MemberListItem} from '@app/components/channel/MemberListItem';
|
||||||
|
import {MemberListUnavailableFallback} from '@app/components/channel/shared/MemberListUnavailableFallback';
|
||||||
import {OutlineFrame} from '@app/components/layout/OutlineFrame';
|
import {OutlineFrame} from '@app/components/layout/OutlineFrame';
|
||||||
import {resolveMemberListPresence} from '@app/hooks/useMemberListPresence';
|
import {resolveMemberListPresence} from '@app/hooks/useMemberListPresence';
|
||||||
import {useMemberListSubscription} from '@app/hooks/useMemberListSubscription';
|
import {useMemberListSubscription} from '@app/hooks/useMemberListSubscription';
|
||||||
@ -34,6 +35,7 @@ import type {GroupDMMemberGroup} from '@app/utils/MemberListUtils';
|
|||||||
import * as MemberListUtils from '@app/utils/MemberListUtils';
|
import * as MemberListUtils from '@app/utils/MemberListUtils';
|
||||||
import * as NicknameUtils from '@app/utils/NicknameUtils';
|
import * as NicknameUtils from '@app/utils/NicknameUtils';
|
||||||
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
import {ChannelTypes} from '@fluxer/constants/src/ChannelConstants';
|
||||||
|
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
|
||||||
import {isOfflineStatus} from '@fluxer/constants/src/StatusConstants';
|
import {isOfflineStatus} from '@fluxer/constants/src/StatusConstants';
|
||||||
import {useLingui} from '@lingui/react/macro';
|
import {useLingui} from '@lingui/react/macro';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
@ -152,11 +154,12 @@ interface LazyMemberListProps {
|
|||||||
|
|
||||||
const LazyMemberList = observer(function LazyMemberList({guild, channel}: LazyMemberListProps) {
|
const LazyMemberList = observer(function LazyMemberList({guild, channel}: LazyMemberListProps) {
|
||||||
const [subscribedRange, setSubscribedRange] = useState<[number, number]>(INITIAL_MEMBER_RANGE);
|
const [subscribedRange, setSubscribedRange] = useState<[number, number]>(INITIAL_MEMBER_RANGE);
|
||||||
|
const memberListUpdatesDisabled = (guild.disabledOperations & GuildOperations.MEMBER_LIST_UPDATES) !== 0;
|
||||||
|
|
||||||
const {subscribe} = useMemberListSubscription({
|
const {subscribe} = useMemberListSubscription({
|
||||||
guildId: guild.id,
|
guildId: guild.id,
|
||||||
channelId: channel.id,
|
channelId: channel.id,
|
||||||
enabled: true,
|
enabled: !memberListUpdatesDisabled,
|
||||||
allowInitialUnfocusedLoad: true,
|
allowInitialUnfocusedLoad: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -181,6 +184,14 @@ const LazyMemberList = observer(function LazyMemberList({guild, channel}: LazyMe
|
|||||||
[subscribedRange, subscribe],
|
[subscribedRange, subscribe],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (memberListUpdatesDisabled) {
|
||||||
|
return (
|
||||||
|
<MemberListContainer channelId={channel.id}>
|
||||||
|
<MemberListUnavailableFallback />
|
||||||
|
</MemberListContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<MemberListContainer channelId={channel.id}>
|
<MemberListContainer channelId={channel.id}>
|
||||||
|
|||||||
@ -17,10 +17,14 @@
|
|||||||
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {GuildRecord} from '@app/records/GuildRecord';
|
||||||
import GuildMemberStore from '@app/stores/GuildMemberStore';
|
import GuildMemberStore from '@app/stores/GuildMemberStore';
|
||||||
|
import GuildStore from '@app/stores/GuildStore';
|
||||||
import MemberSidebarStore from '@app/stores/MemberSidebarStore';
|
import MemberSidebarStore from '@app/stores/MemberSidebarStore';
|
||||||
import {buildMemberListLayout} from '@app/utils/MemberListLayout';
|
import {buildMemberListLayout} from '@app/utils/MemberListLayout';
|
||||||
|
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
|
||||||
import type {GuildMemberData} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
|
import type {GuildMemberData} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
|
||||||
|
import type {Guild} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
||||||
import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
|
import type {UserPartialResponse} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
|
||||||
import {beforeEach, describe, expect, test} from 'vitest';
|
import {beforeEach, describe, expect, test} from 'vitest';
|
||||||
|
|
||||||
@ -60,10 +64,25 @@ function seedMembers(guildId: string, members: Array<{id: string; name: string}>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createGuild(guildId: string, disabledOperations = 0): GuildRecord {
|
||||||
|
const guild: Guild = {
|
||||||
|
id: guildId,
|
||||||
|
name: `Guild ${guildId}`,
|
||||||
|
icon: null,
|
||||||
|
vanity_url_code: null,
|
||||||
|
owner_id: 'owner-1',
|
||||||
|
system_channel_id: null,
|
||||||
|
features: [],
|
||||||
|
disabled_operations: disabledOperations,
|
||||||
|
};
|
||||||
|
return new GuildRecord(guild);
|
||||||
|
}
|
||||||
|
|
||||||
describe('MemberSidebarStore', () => {
|
describe('MemberSidebarStore', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
MemberSidebarStore.handleSessionInvalidated();
|
MemberSidebarStore.handleSessionInvalidated();
|
||||||
GuildMemberStore.handleConnectionOpen([]);
|
GuildMemberStore.handleConnectionOpen([]);
|
||||||
|
GuildStore.guilds = {};
|
||||||
});
|
});
|
||||||
|
|
||||||
test('stores members by member index when sync includes group entries', () => {
|
test('stores members by member index when sync includes group entries', () => {
|
||||||
@ -357,4 +376,39 @@ describe('MemberSidebarStore', () => {
|
|||||||
expect(Array.from(listState?.rows.keys() ?? [])).toEqual([0, 1, 2]);
|
expect(Array.from(listState?.rows.keys() ?? [])).toEqual([0, 1, 2]);
|
||||||
expect(Array.from(listState?.items.values() ?? []).map((item) => item.data.user.id)).toEqual(['u-1', 'u-2']);
|
expect(Array.from(listState?.items.values() ?? []).map((item) => item.data.user.id)).toEqual(['u-1', 'u-2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ignores list updates when member list updates are disabled for the guild', () => {
|
||||||
|
const guildId = 'guild-disabled-updates';
|
||||||
|
const listId = 'list-disabled-updates';
|
||||||
|
GuildStore.guilds[guildId] = createGuild(guildId, GuildOperations.MEMBER_LIST_UPDATES);
|
||||||
|
seedMembers(guildId, [{id: 'u-1', name: 'Alpha'}]);
|
||||||
|
|
||||||
|
MemberSidebarStore.handleListUpdate({
|
||||||
|
guildId,
|
||||||
|
listId,
|
||||||
|
memberCount: 1,
|
||||||
|
onlineCount: 1,
|
||||||
|
groups: [{id: 'online', count: 1}],
|
||||||
|
ops: [
|
||||||
|
{
|
||||||
|
op: 'SYNC',
|
||||||
|
range: [0, 1],
|
||||||
|
items: [{group: {id: 'online', count: 1}}, {member: {user: {id: 'u-1'}}}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(MemberSidebarStore.getList(guildId, listId)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats subscribe attempts as no-op when member list updates are disabled for the guild', () => {
|
||||||
|
const guildId = 'guild-disabled-subscribe';
|
||||||
|
const channelId = 'channel-disabled-subscribe';
|
||||||
|
GuildStore.guilds[guildId] = createGuild(guildId, GuildOperations.MEMBER_LIST_UPDATES);
|
||||||
|
|
||||||
|
MemberSidebarStore.subscribeToChannel(guildId, channelId, [[0, 99]]);
|
||||||
|
|
||||||
|
expect(MemberSidebarStore.getList(guildId, channelId)).toBeUndefined();
|
||||||
|
expect(MemberSidebarStore.getSubscribedRanges(guildId, channelId)).toEqual([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import {CustomStatusEmitter} from '@app/lib/CustomStatusEmitter';
|
|||||||
import {Logger} from '@app/lib/Logger';
|
import {Logger} from '@app/lib/Logger';
|
||||||
import type {GuildMemberRecord} from '@app/records/GuildMemberRecord';
|
import type {GuildMemberRecord} from '@app/records/GuildMemberRecord';
|
||||||
import GuildMemberStore from '@app/stores/GuildMemberStore';
|
import GuildMemberStore from '@app/stores/GuildMemberStore';
|
||||||
|
import GuildStore from '@app/stores/GuildStore';
|
||||||
import GatewayConnectionStore from '@app/stores/gateway/GatewayConnectionStore';
|
import GatewayConnectionStore from '@app/stores/gateway/GatewayConnectionStore';
|
||||||
import WindowStore from '@app/stores/WindowStore';
|
import WindowStore from '@app/stores/WindowStore';
|
||||||
import {
|
import {
|
||||||
@ -31,6 +32,7 @@ import {
|
|||||||
getTotalMemberCount,
|
getTotalMemberCount,
|
||||||
getTotalRowsFromLayout,
|
getTotalRowsFromLayout,
|
||||||
} from '@app/utils/MemberListLayout';
|
} from '@app/utils/MemberListLayout';
|
||||||
|
import {GuildOperations} from '@fluxer/constants/src/GuildConstants';
|
||||||
import type {StatusType} from '@fluxer/constants/src/StatusConstants';
|
import type {StatusType} from '@fluxer/constants/src/StatusConstants';
|
||||||
import {StatusTypes} from '@fluxer/constants/src/StatusConstants';
|
import {StatusTypes} from '@fluxer/constants/src/StatusConstants';
|
||||||
import {makeAutoObservable} from 'mobx';
|
import {makeAutoObservable} from 'mobx';
|
||||||
@ -165,6 +167,10 @@ class MemberSidebarStore {
|
|||||||
ops: Array<MemberListOperation>;
|
ops: Array<MemberListOperation>;
|
||||||
}): void {
|
}): void {
|
||||||
const {guildId, listId, channelId, memberCount, onlineCount, groups, ops} = params;
|
const {guildId, listId, channelId, memberCount, onlineCount, groups, ops} = params;
|
||||||
|
if (this.isMemberListUpdatesDisabled(guildId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const storageKey = listId;
|
const storageKey = listId;
|
||||||
const existingGuildLists = this.lists[guildId] ?? {};
|
const existingGuildLists = this.lists[guildId] ?? {};
|
||||||
const guildLists: Record<string, MemberListState> = {...existingGuildLists};
|
const guildLists: Record<string, MemberListState> = {...existingGuildLists};
|
||||||
@ -445,6 +451,10 @@ class MemberSidebarStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subscribeToChannel(guildId: string, channelId: string, ranges: Array<[number, number]>): void {
|
subscribeToChannel(guildId: string, channelId: string, ranges: Array<[number, number]>): void {
|
||||||
|
if (this.isMemberListUpdatesDisabled(guildId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const storageKey = this.resolveListKey(guildId, channelId);
|
const storageKey = this.resolveListKey(guildId, channelId);
|
||||||
const socket = GatewayConnectionStore.socket;
|
const socket = GatewayConnectionStore.socket;
|
||||||
|
|
||||||
@ -564,6 +574,14 @@ class MemberSidebarStore {
|
|||||||
return listState.customStatuses.get(userId) ?? null;
|
return listState.customStatuses.get(userId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isMemberListUpdatesDisabled(guildId: string): boolean {
|
||||||
|
const guild = GuildStore.getGuild(guildId);
|
||||||
|
if (!guild) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (guild.disabledOperations & GuildOperations.MEMBER_LIST_UPDATES) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
private touchList(guildId: string, listId: string): void {
|
private touchList(guildId: string, listId: string): void {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (!this.lastAccess[guildId]) {
|
if (!this.lastAccess[guildId]) {
|
||||||
|
|||||||
@ -23,7 +23,8 @@
|
|||||||
decorate_member_data/3,
|
decorate_member_data/3,
|
||||||
extract_member_for_event/3,
|
extract_member_for_event/3,
|
||||||
collect_and_send_push_notifications/3,
|
collect_and_send_push_notifications/3,
|
||||||
normalize_event/1
|
normalize_event/1,
|
||||||
|
is_member_list_updates_enabled/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-type guild_state() :: map().
|
-type guild_state() :: map().
|
||||||
|
|||||||
@ -22,6 +22,10 @@
|
|||||||
handle_cast/2
|
handle_cast/2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-endif.
|
||||||
|
|
||||||
-type guild_state() :: map().
|
-type guild_state() :: map().
|
||||||
-type user_id() :: integer().
|
-type user_id() :: integer().
|
||||||
-type session_id() :: binary().
|
-type session_id() :: binary().
|
||||||
@ -43,30 +47,36 @@ handle_cast({very_large_guild_member_list_deliver, Deliveries}, State) when is_l
|
|||||||
|
|
||||||
-spec handle_lazy_subscribe(map(), guild_state()) -> {reply, ok, guild_state()}.
|
-spec handle_lazy_subscribe(map(), guild_state()) -> {reply, ok, guild_state()}.
|
||||||
handle_lazy_subscribe(Request, State) ->
|
handle_lazy_subscribe(Request, State) ->
|
||||||
case maps:get(disable_member_list_updates, State, false) of
|
#{session_id := SessionId, channel_id := ChannelId, ranges := Ranges} = Request,
|
||||||
|
case should_ignore_member_list_subscribe(Ranges, State) of
|
||||||
true ->
|
true ->
|
||||||
{reply, ok, State};
|
{reply, ok, State};
|
||||||
false ->
|
false ->
|
||||||
#{session_id := SessionId, channel_id := ChannelId, ranges := Ranges} = Request,
|
Sessions0 = maps:get(sessions, State, #{}),
|
||||||
Sessions0 = maps:get(sessions, State, #{}),
|
SessionUserId = get_session_user_id(SessionId, Sessions0),
|
||||||
SessionUserId = get_session_user_id(SessionId, Sessions0),
|
case
|
||||||
case
|
is_integer(SessionUserId) andalso
|
||||||
is_integer(SessionUserId) andalso
|
guild_permissions:can_view_channel(SessionUserId, ChannelId, undefined, State)
|
||||||
guild_permissions:can_view_channel(SessionUserId, ChannelId, undefined, State)
|
of
|
||||||
of
|
true ->
|
||||||
true ->
|
GuildId = maps:get(id, State),
|
||||||
GuildId = maps:get(id, State),
|
ListId = guild_member_list:calculate_list_id(ChannelId, State),
|
||||||
ListId = guild_member_list:calculate_list_id(ChannelId, State),
|
{NewState, ShouldSendSync, NormalizedRanges} =
|
||||||
{NewState, ShouldSendSync, NormalizedRanges} =
|
guild_member_list:subscribe_ranges(SessionId, ListId, Ranges, State),
|
||||||
guild_member_list:subscribe_ranges(SessionId, ListId, Ranges, State),
|
handle_lazy_subscribe_sync(
|
||||||
handle_lazy_subscribe_sync(
|
ShouldSendSync, NormalizedRanges, GuildId, ListId, ChannelId, SessionId, NewState
|
||||||
ShouldSendSync, NormalizedRanges, GuildId, ListId, ChannelId, SessionId, NewState
|
);
|
||||||
);
|
false ->
|
||||||
false ->
|
{reply, ok, State}
|
||||||
{reply, ok, State}
|
end
|
||||||
end
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec should_ignore_member_list_subscribe(list(), guild_state()) -> boolean().
|
||||||
|
should_ignore_member_list_subscribe([], _State) ->
|
||||||
|
false;
|
||||||
|
should_ignore_member_list_subscribe(_Ranges, State) ->
|
||||||
|
not guild_dispatch:is_member_list_updates_enabled(State).
|
||||||
|
|
||||||
-spec handle_lazy_subscribe_sync(
|
-spec handle_lazy_subscribe_sync(
|
||||||
boolean(), list(), integer(), term(), channel_id(), session_id(), guild_state()
|
boolean(), list(), integer(), term(), channel_id(), session_id(), guild_state()
|
||||||
) ->
|
) ->
|
||||||
@ -325,3 +335,29 @@ can_session_view_channel(SessionData, ChannelId, State) ->
|
|||||||
_ ->
|
_ ->
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
|
||||||
|
-spec disabled_operations_state(integer() | binary()) -> guild_state().
|
||||||
|
disabled_operations_state(Value) ->
|
||||||
|
#{data => #{<<"guild">> => #{<<"disabled_operations">> => Value}}}.
|
||||||
|
|
||||||
|
should_ignore_member_list_subscribe_ignores_non_empty_ranges_when_disabled_test() ->
|
||||||
|
?assertEqual(
|
||||||
|
true,
|
||||||
|
should_ignore_member_list_subscribe(
|
||||||
|
[{0, 99}],
|
||||||
|
disabled_operations_state(1 bsl 6)
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
should_ignore_member_list_subscribe_allows_empty_ranges_when_disabled_test() ->
|
||||||
|
?assertEqual(
|
||||||
|
false,
|
||||||
|
should_ignore_member_list_subscribe(
|
||||||
|
[],
|
||||||
|
disabled_operations_state(1 bsl 6)
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
-endif.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user