diff --git a/fluxer_api/src/api/channel/services/group_dm/GroupDmOperationsService.ts b/fluxer_api/src/api/channel/services/group_dm/GroupDmOperationsService.ts index 62eddf753..ba7b9f25e 100644 --- a/fluxer_api/src/api/channel/services/group_dm/GroupDmOperationsService.ts +++ b/fluxer_api/src/api/channel/services/group_dm/GroupDmOperationsService.ts @@ -313,9 +313,9 @@ export class GroupDmOperationsService { private async syncGroupDmRecipientsForUser(userId: UserID): Promise { const channels = await this.userRepository.listPrivateChannels(userId); - const dmChannels = channels.filter((ch) => ch.type === ChannelTypes.DM || ch.type === ChannelTypes.GROUP_DM); + const groupDmChannels = channels.filter((ch) => ch.type === ChannelTypes.GROUP_DM); const recipientsByChannel: Record> = {}; - for (const channel of dmChannels) { + for (const channel of groupDmChannels) { const otherRecipients = Array.from(channel.recipientIds) .filter((recId) => recId !== userId) .map((recId) => recId.toString()); diff --git a/fluxer_gateway/src/gateway/gateway_rpc_presence.erl b/fluxer_gateway/src/gateway/gateway_rpc_presence.erl index fb70f09f8..d44bca7c0 100644 --- a/fluxer_gateway/src/gateway/gateway_rpc_presence.erl +++ b/fluxer_gateway/src/gateway/gateway_rpc_presence.erl @@ -109,10 +109,10 @@ handle_sync_dm_recipients(#{ <<"recipients_by_channel">> := RecipientsByChannel }) -> UserId = validation:snowflake_or_throw(<<"user_id">>, UserIdBin), - NormalizedRecipients = normalize_recipients(RecipientsByChannel), + _ = normalize_recipients(RecipientsByChannel), case lookup_owner_presence(UserId) of {ok, Pid} -> - gen_server:cast(Pid, {sync_group_dm_recipients, NormalizedRecipients}), + gen_server:cast(Pid, presence_rejoin), true; {error, not_found} -> true; diff --git a/fluxer_gateway/src/presence/presence.erl b/fluxer_gateway/src/presence/presence.erl index 3b9fc8f81..736f86b2f 100644 --- a/fluxer_gateway/src/presence/presence.erl +++ b/fluxer_gateway/src/presence/presence.erl @@ -115,6 +115,11 @@ handle_cast({dispatch, Event, Data}, State) when is_atom(Event), is_map(Data) -> handle_dispatch_cast(Event, Data, State); handle_cast(presence_rejoin, State) -> handle_presence_rejoin(State); +handle_cast(reconcile_flattened_presence, State) -> + {noreply, + presence_broadcast:publish_global_presence( + maps:get(sessions, State), State + )}; handle_cast({presence_update, Request}, State) when is_map(Request) -> handle_presence_update_cast(Request, State); handle_cast({terminate_session, SessionIdHashes}, State) when is_list(SessionIdHashes) -> @@ -291,6 +296,28 @@ presence_rejoin_with_no_sessions_is_noop_test() -> State = test_state(#{}), ?assertEqual({noreply, State}, handle_cast(presence_rejoin, State)). +reconcile_flattened_presence_uses_current_session_state_test() -> + State = test_state(#{}), + PublishedState = State#{last_published_presence => #{status => <<"offline">>}}, + meck:new(presence_broadcast, [passthrough]), + meck:expect( + presence_broadcast, + publish_global_presence, + fun(Sessions, ReceivedState) -> + ?assertEqual(#{}, Sessions), + ?assertEqual(State, ReceivedState), + PublishedState + end + ), + try + ?assertEqual( + {noreply, PublishedState}, + handle_cast(reconcile_flattened_presence, State) + ) + after + meck:unload(presence_broadcast) + end. + -spec test_state(sessions()) -> state(). test_state(Sessions) -> #{ diff --git a/fluxer_gateway/src/presence/presence_session.erl b/fluxer_gateway/src/presence/presence_session.erl index 7363ba2db..852cbbc99 100644 --- a/fluxer_gateway/src/presence/presence_session.erl +++ b/fluxer_gateway/src/presence/presence_session.erl @@ -319,7 +319,7 @@ handle_session_connect_existing_session_refreshes_status_test() -> ?assertEqual(Ref, maps:get(mref, UpdatedSession)), [AllSession | _] = SessionsData, ?assertEqual(<<"dnd">>, maps:get(<<"status">>, AllSession)), - ?assertEqual(true, maps:get(<<"mobile">>, AllSession)). + ?assertEqual(false, maps:get(<<"mobile">>, AllSession)). flush_test_messages() -> receive diff --git a/fluxer_gateway/src/presence/presence_status.erl b/fluxer_gateway/src/presence/presence_status.erl index e2e835632..9b9f7557a 100644 --- a/fluxer_gateway/src/presence/presence_status.erl +++ b/fluxer_gateway/src/presence/presence_status.erl @@ -20,16 +20,11 @@ -spec get_current_status(sessions()) -> status(). get_current_status(Sessions) -> AllStatuses = [maps:get(status, S) || S <- maps:values(Sessions)], - case lists:member(invisible, AllStatuses) of - true -> - invisible; - false -> - resolve_status_precedence(AllStatuses) - end. + resolve_status_precedence(AllStatuses). -spec resolve_status_precedence([status()]) -> status(). resolve_status_precedence(AllStatuses) -> - StatusPrecedence = [dnd, online, idle], + StatusPrecedence = [dnd, online, idle, invisible], lists:foldl( fun(Status, Acc) -> promote_status(Status, Acc, AllStatuses) @@ -49,12 +44,13 @@ promote_status(_Status, Acc, _AllStatuses) -> -spec get_flattened_mobile(sessions()) -> boolean(). get_flattened_mobile(Sessions) -> - lists:any( - fun(Session) -> - maps:get(mobile, Session, false) - end, - maps:values(Sessions) - ). + get_current_status(Sessions) =:= online andalso + lists:any(fun is_online_mobile_session/1, maps:values(Sessions)). + +-spec is_online_mobile_session(map()) -> boolean(). +is_online_mobile_session(Session) -> + maps:get(status, Session, offline) =:= online andalso + maps:get(mobile, Session, false). -spec get_flattened_afk(sessions()) -> boolean(). get_flattened_afk(Sessions) -> @@ -150,11 +146,18 @@ get_current_status_dnd_over_idle_test() -> }, ?assertEqual(dnd, get_current_status(Sessions)). -get_current_status_invisible_test() -> +get_current_status_visible_over_invisible_test() -> Sessions = #{ <<"s1">> => #{status => invisible, afk => false, mobile => false}, <<"s2">> => #{status => online, afk => false, mobile => false} }, + ?assertEqual(online, get_current_status(Sessions)). + +get_current_status_invisible_without_visible_session_test() -> + Sessions = #{ + <<"s1">> => #{status => invisible, afk => false, mobile => false}, + <<"s2">> => #{status => offline, afk => false, mobile => false} + }, ?assertEqual(invisible, get_current_status(Sessions)). get_flattened_mobile_true_test() -> @@ -168,7 +171,46 @@ get_flattened_mobile_false_test() -> Sessions = #{ <<"s1">> => #{status => online, afk => false, mobile => false} }, - ?assertEqual(false, get_flattened_mobile(Sessions)). + ?assertEqual(false, get_flattened_mobile(Sessions)), + ?assertEqual( + false, + get_flattened_mobile(#{ + <<"s1">> => #{status => idle, afk => false, mobile => true} + }) + ), + ?assertEqual( + false, + get_flattened_mobile(#{ + <<"s1">> => #{status => dnd, afk => false, mobile => true} + }) + ), + ?assertEqual( + false, + get_flattened_mobile(#{ + <<"s1">> => #{status => invisible, afk => false, mobile => true} + }) + ), + ?assertEqual( + false, + get_flattened_mobile(#{ + <<"s1">> => #{status => dnd, afk => false, mobile => false}, + <<"s2">> => #{status => online, afk => false, mobile => true} + }) + ), + ?assertEqual( + false, + get_flattened_mobile(#{ + <<"s1">> => #{status => online, afk => false, mobile => false}, + <<"s2">> => #{status => idle, afk => false, mobile => true} + }) + ), + ?assertEqual( + true, + get_flattened_mobile(#{ + <<"s1">> => #{status => online, afk => false, mobile => true}, + <<"s2">> => #{status => invisible, afk => false, mobile => false} + }) + ). get_flattened_mobile_empty_test() -> ?assertEqual(false, get_flattened_mobile(#{})). diff --git a/fluxer_gateway/src/presence/presence_targets.erl b/fluxer_gateway/src/presence/presence_targets.erl index f874dc556..aa718ec79 100644 --- a/fluxer_gateway/src/presence/presence_targets.erl +++ b/fluxer_gateway/src/presence/presence_targets.erl @@ -5,6 +5,8 @@ -export([ friend_ids_from_state/1, + group_dm_recipients_from_state/1, + group_dm_channel_recipient_ids/2, dm_recipients_from_state/1, dm_channel_recipient_ids/2, map_from_ids/1 @@ -24,13 +26,11 @@ friend_ids_from_state(State) -> -spec accumulate_friend_id(term(), term(), [user_id()]) -> [user_id()]. accumulate_friend_id(UserId, 1, Acc) when is_integer(UserId) -> [UserId | Acc]; -accumulate_friend_id(UserId, 3, Acc) when is_integer(UserId) -> - [UserId | Acc]; accumulate_friend_id(_UserId, _Type, Acc) -> Acc. --spec dm_recipients_from_state(state()) -> #{channel_id() => #{user_id() => true}}. -dm_recipients_from_state(State) -> +-spec group_dm_recipients_from_state(state()) -> #{channel_id() => #{user_id() => true}}. +group_dm_recipients_from_state(State) -> UserId = maps:get(user_id, State, undefined), Channels = maps:get(channels, State, #{}), maps:fold( @@ -41,11 +41,15 @@ dm_recipients_from_state(State) -> Channels ). +-spec dm_recipients_from_state(state()) -> #{channel_id() => #{user_id() => true}}. +dm_recipients_from_state(State) -> + group_dm_recipients_from_state(State). + -spec accumulate_dm_channel(term(), term(), user_id() | undefined, map()) -> map(). accumulate_dm_channel(ChannelId, Channel, UserId, Acc) when is_integer(ChannelId), is_map(Channel) -> - case is_dm_channel_type(maps:get(<<"type">>, Channel, 0)) of + case is_group_dm_channel_type(maps:get(<<"type">>, Channel, 0)) of true -> RecipientIds = extract_recipient_ids(Channel), Acc#{ChannelId => map_from_ids([Rid || Rid <- RecipientIds, Rid =/= UserId])}; @@ -55,22 +59,25 @@ accumulate_dm_channel(ChannelId, Channel, UserId, Acc) when accumulate_dm_channel(_ChannelId, _Channel, _UserId, Acc) -> Acc. --spec is_dm_channel_type(term()) -> boolean(). -is_dm_channel_type(1) -> true; -is_dm_channel_type(3) -> true; -is_dm_channel_type(_) -> false. +-spec is_group_dm_channel_type(term()) -> boolean(). +is_group_dm_channel_type(3) -> true; +is_group_dm_channel_type(_) -> false. --spec dm_channel_recipient_ids(term(), user_id() | undefined) -> [user_id()]. -dm_channel_recipient_ids(Channel, SelfUserId) when is_map(Channel) -> - case is_dm_channel_type(maps:get(<<"type">>, Channel, 0)) of +-spec group_dm_channel_recipient_ids(term(), user_id() | undefined) -> [user_id()]. +group_dm_channel_recipient_ids(Channel, SelfUserId) when is_map(Channel) -> + case is_group_dm_channel_type(maps:get(<<"type">>, Channel, 0)) of true -> [Rid || Rid <- extract_recipient_ids(Channel), Rid =/= SelfUserId]; false -> [] end; -dm_channel_recipient_ids(_Channel, _SelfUserId) -> +group_dm_channel_recipient_ids(_Channel, _SelfUserId) -> []. +-spec dm_channel_recipient_ids(term(), user_id() | undefined) -> [user_id()]. +dm_channel_recipient_ids(Channel, SelfUserId) -> + group_dm_channel_recipient_ids(Channel, SelfUserId). + -spec extract_recipient_ids(map()) -> [user_id()]. extract_recipient_ids(Channel) -> Recipients = maps:get( @@ -130,7 +137,7 @@ friend_ids_from_state_filters_relationship_types_test() -> } }, Ids = lists:sort(friend_ids_from_state(State)), - ?assertEqual([10, 11], Ids). + ?assertEqual([10], Ids). friend_ids_from_state_empty_test() -> State = #{relationships => #{}}, @@ -140,7 +147,7 @@ friend_ids_from_state_missing_key_test() -> State = #{}, ?assertEqual([], friend_ids_from_state(State)). -dm_recipients_from_state_test() -> +group_dm_recipients_from_state_test() -> State = #{ user_id => 1, channels => #{ @@ -148,10 +155,10 @@ dm_recipients_from_state_test() -> 200 => api_channel(<<"200">>, 0, [<<"4">>]) } }, - Result = dm_recipients_from_state(State), + Result = group_dm_recipients_from_state(State), ?assertEqual(#{100 => #{2 => true, 3 => true}}, Result). -dm_recipients_from_state_includes_one_to_one_dms_test() -> +group_dm_recipients_from_state_excludes_one_to_one_dms_test() -> State = #{ user_id => 1, channels => #{ @@ -160,16 +167,10 @@ dm_recipients_from_state_includes_one_to_one_dms_test() -> 300 => api_channel(<<"300">>, 0, [<<"5">>]) } }, - Result = dm_recipients_from_state(State), - ?assertEqual( - #{ - 100 => #{2 => true}, - 200 => #{3 => true, 4 => true} - }, - Result - ). + Result = group_dm_recipients_from_state(State), + ?assertEqual(#{200 => #{3 => true, 4 => true}}, Result). -dm_recipients_excludes_self_test() -> +group_dm_recipients_excludes_self_test() -> State = #{ user_id => 2, channels => #{ @@ -177,17 +178,17 @@ dm_recipients_excludes_self_test() -> 200 => api_channel(<<"200">>, 1, [<<"2">>, <<"7">>]) } }, - Result = dm_recipients_from_state(State), - ?assertEqual(#{100 => #{3 => true}, 200 => #{7 => true}}, Result). + Result = group_dm_recipients_from_state(State), + ?assertEqual(#{100 => #{3 => true}}, Result). -dm_recipients_supports_recipient_ids_field_test() -> +group_dm_recipients_supports_recipient_ids_field_test() -> State = #{ user_id => 1, channels => #{ - 100 => #{<<"type">> => 1, <<"recipient_ids">> => [<<"2">>]} + 100 => #{<<"type">> => 3, <<"recipient_ids">> => [<<"2">>]} } }, - Result = dm_recipients_from_state(State), + Result = group_dm_recipients_from_state(State), ?assertEqual(#{100 => #{2 => true}}, Result). api_channel(IdBin, Type, RecipientIdBins) -> diff --git a/fluxer_gateway/src/session/session.erl b/fluxer_gateway/src/session/session.erl index b87fa1e6b..0b813608b 100644 --- a/fluxer_gateway/src/session/session.erl +++ b/fluxer_gateway/src/session/session.erl @@ -309,7 +309,8 @@ do_handle_info_misc(_Info, State) -> -spec handle_presence_rejoin_check(session_state()) -> {noreply, session_state()}. handle_presence_rejoin_check(State) -> - {noreply, session_connection:repair_presence_connection(State)}. + RepairedState = session_connection:repair_presence_connection(State), + {noreply, session_dispatch_presence:sync_presence_targets(RepairedState)}. -spec terminate(term(), session_state()) -> ok. terminate(Reason, State) -> diff --git a/fluxer_gateway/src/session/session_connection_presence.erl b/fluxer_gateway/src/session/session_connection_presence.erl index 91bea3b6a..c3ab183a3 100644 --- a/fluxer_gateway/src/session/session_connection_presence.erl +++ b/fluxer_gateway/src/session/session_connection_presence.erl @@ -50,14 +50,14 @@ safe_presence_lookup(Request) -> -spec build_presence_request(session_state()) -> map(). build_presence_request(State) when is_map(State) -> FriendIds = presence_targets:friend_ids_from_state(State), - DmRecipients = presence_targets:dm_recipients_from_state(State), + GroupDmRecipients = presence_targets:group_dm_recipients_from_state(State), #{ user_id => maps:get(user_id, State), user_data => maps:get(user_data, State), guild_ids => maps:keys(maps:get(guilds, State)), status => maps:get(status, State), friend_ids => FriendIds, - group_dm_recipients => DmRecipients, + group_dm_recipients => GroupDmRecipients, custom_status => maps:get(custom_status, State, null) }. @@ -70,7 +70,7 @@ do_session_connect(Pid, Attempt, State) when is_map(State) -> Mobile = maps:get(mobile, State), SocketPid = maps:get(socket_pid, State, undefined), FriendIds = presence_targets:friend_ids_from_state(State), - DmRecipients = presence_targets:dm_recipients_from_state(State), + GroupDmRecipients = presence_targets:group_dm_recipients_from_state(State), try_session_connect( Pid, SessionId, @@ -79,7 +79,7 @@ do_session_connect(Pid, Attempt, State) when is_map(State) -> Mobile, SocketPid, FriendIds, - DmRecipients, + GroupDmRecipients, Attempt, State ). diff --git a/fluxer_gateway/src/session/session_dispatch_presence.erl b/fluxer_gateway/src/session/session_dispatch_presence.erl index be9f59c54..1d1045f0f 100644 --- a/fluxer_gateway/src/session/session_dispatch_presence.erl +++ b/fluxer_gateway/src/session/session_dispatch_presence.erl @@ -10,6 +10,7 @@ flush_all_pending_presences/1, dispatch_presence_now/2, maybe_sync_presence_targets/3, + sync_presence_targets/1, event_changes_presence_targets/1, presence_user_id/1, relationship_target_id/1 @@ -32,21 +33,28 @@ should_buffer_presence(presence_update, Data, State) -> true -> true; false -> - check_non_guild_presence(Data, State) + check_presence_scope(Data, State) end; should_buffer_presence(_, _, _) -> false. --spec check_non_guild_presence(map(), session_state()) -> boolean(). -check_non_guild_presence(Data, State) -> - HasGuildId = - is_map(Data) andalso (maps:get(<<"guild_id">>, Data, undefined) =/= undefined), - case HasGuildId of - true -> - false; - false -> +-spec check_presence_scope(map(), session_state()) -> boolean(). +check_presence_scope(Data, State) -> + case maps:find(<<"guild_id">>, Data) of + error -> UserId = presence_user_id(Data), - check_user_presence_buffering(UserId, State) + check_user_presence_buffering(UserId, State); + {ok, GuildIdValue} -> + should_buffer_guild_presence(GuildIdValue, State) + end. + +-spec should_buffer_guild_presence(term(), session_state()) -> boolean(). +should_buffer_guild_presence(GuildIdValue, State) -> + case snowflake_id:parse_maybe(GuildIdValue) of + GuildId when is_integer(GuildId) -> + not maps:is_key(GuildId, maps:get(guilds, State, #{})); + undefined -> + true end. -spec check_user_presence_buffering(user_id() | undefined, session_state()) -> boolean(). @@ -55,8 +63,8 @@ check_user_presence_buffering(undefined, _State) -> check_user_presence_buffering(UserId, State) -> Relationships = maps:get(relationships, State, #{}), IsRelationship = relationship_allows_presence(UserId, Relationships), - IsDmRecipient = is_dm_recipient(UserId, State), - not (IsRelationship orelse IsDmRecipient). + IsGroupDmRecipient = is_group_dm_recipient(UserId, State), + not (IsRelationship orelse IsGroupDmRecipient). -spec relationship_allows_presence(user_id(), #{user_id() => integer()}) -> boolean(). relationship_allows_presence(UserId, Relationships) when @@ -64,31 +72,30 @@ relationship_allows_presence(UserId, Relationships) when -> case maps:get(UserId, Relationships, undefined) of 1 -> true; - 3 -> true; _ -> false end; relationship_allows_presence(_, _) -> false. --spec is_dm_recipient(user_id(), session_state()) -> boolean(). -is_dm_recipient(UserId, State) when is_map(State) -> - DmRecipients = presence_targets:dm_recipients_from_state(State), +-spec is_group_dm_recipient(user_id(), session_state()) -> boolean(). +is_group_dm_recipient(UserId, State) when is_map(State) -> + GroupDmRecipients = presence_targets:group_dm_recipients_from_state(State), maps:fold( fun (_, Recipients, false) -> maps:is_key(UserId, Recipients); (_, _, true) -> true end, false, - DmRecipients + GroupDmRecipients ). -spec buffer_presence(event(), map(), session_state()) -> session_state(). buffer_presence(Event, Data, State) -> - Pending = maps:get(pending_presences, State, []), + Pending = ensure_queue(maps:get(pending_presences, State, [])), UserId = presence_user_id(Data), Entry = #{event => Event, data => Data, user_id => UserId}, - Trimmed = trim_rev_list(Pending, ?MAX_PENDING_PRESENCE_BUFFER_SIZE - 1), - NewPending = [Entry | Trimmed], + Trimmed = trim_queue_from_tail(Pending, ?MAX_PENDING_PRESENCE_BUFFER_SIZE - 1), + NewPending = queue:in_r(Entry, Trimmed), State#{pending_presences => NewPending}. -spec maybe_flush_pending_presences(event(), map(), session_state()) -> @@ -110,7 +117,7 @@ maybe_flush_pending_presences(_, _, State) -> {session_state(), [user_id()]}. flush_dm_channel_pending_presences(Data, State) -> SelfUserId = maps:get(user_id, State, undefined), - RecipientIds = presence_targets:dm_channel_recipient_ids(Data, SelfUserId), + RecipientIds = presence_targets:group_dm_channel_recipient_ids(Data, SelfUserId), flush_pending_presences_for_ids(RecipientIds, State). -spec flush_added_recipient_pending_presences(map(), session_state()) -> @@ -139,9 +146,6 @@ maybe_flush_relationship_pending_presences(Data, State) -> 1 -> TargetId = relationship_target_id(Data), {flush_pending_presences(TargetId, State), flushed_id_list(TargetId)}; - 3 -> - TargetId = relationship_target_id(Data), - {flush_pending_presences(TargetId, State), flushed_id_list(TargetId)}; _ -> {State, []} end. @@ -207,6 +211,10 @@ maybe_sync_presence_targets(Event, FlushedIds, State) -> false -> State end. +-spec sync_presence_targets(session_state()) -> session_state(). +sync_presence_targets(State) -> + sync_presence_targets([], State). + -spec event_changes_presence_targets(event()) -> boolean(). event_changes_presence_targets(relationship_add) -> true; event_changes_presence_targets(relationship_update) -> true; @@ -226,9 +234,9 @@ sync_presence_targets(FlushedIds, State) when is_map(State) -> State; Pid when is_pid(Pid) -> FriendIds = presence_targets:friend_ids_from_state(State), - DmRecipients = presence_targets:dm_recipients_from_state(State), + GroupDmRecipients = presence_targets:group_dm_recipients_from_state(State), gen_server:cast(Pid, {sync_friends, FriendIds, FlushedIds}), - gen_server:cast(Pid, {sync_group_dm_recipients, DmRecipients}), + gen_server:cast(Pid, {sync_group_dm_recipients, GroupDmRecipients}), State end. @@ -253,9 +261,12 @@ relationship_target_id(Data) when is_map(Data) -> ensure_queue(List) when is_list(List) -> queue:from_list(List); ensure_queue(Q) -> Q. --spec trim_rev_list([T], non_neg_integer()) -> [T]. -trim_rev_list(List, MaxLen) when length(List) =< MaxLen -> List; -trim_rev_list(List, MaxLen) -> lists:sublist(List, MaxLen). +-spec trim_queue_from_tail(queue:queue(T), non_neg_integer()) -> queue:queue(T). +trim_queue_from_tail(Queue, MaxLen) -> + case queue:len(Queue) > MaxLen of + true -> trim_queue_from_tail(queue:drop_r(Queue), MaxLen); + false -> Queue + end. -spec send_to_socket(pid() | undefined, event(), map(), non_neg_integer()) -> ok. send_to_socket(undefined, _Event, _Data, _Seq) -> @@ -269,7 +280,7 @@ send_to_socket(Pid, Event, Data, Seq) when is_pid(Pid) -> relationship_allows_presence_test() -> ?assertEqual(true, relationship_allows_presence(1, #{1 => 1})), - ?assertEqual(true, relationship_allows_presence(1, #{1 => 3})), + ?assertEqual(false, relationship_allows_presence(1, #{1 => 3})), ?assertEqual(false, relationship_allows_presence(1, #{1 => 0})), ?assertEqual(false, relationship_allows_presence(1, #{1 => 2})), ?assertEqual(false, relationship_allows_presence(1, #{1 => 4})), @@ -299,10 +310,10 @@ buffering_test_state() -> presence_data(UserIdBin) -> #{<<"user">> => #{<<"id">> => UserIdBin}, <<"status">> => <<"online">>}. -should_buffer_presence_passes_one_to_one_dm_recipient_test() -> +should_buffer_presence_buffers_one_to_one_dm_recipient_test() -> State = buffering_test_state(), ?assertEqual( - false, should_buffer_presence(presence_update, presence_data(<<"2">>), State) + true, should_buffer_presence(presence_update, presence_data(<<"2">>), State) ). should_buffer_presence_passes_group_dm_recipient_test() -> @@ -323,11 +334,16 @@ should_buffer_presence_passes_friend_test() -> false, should_buffer_presence(presence_update, presence_data(<<"4">>), State) ). -should_buffer_presence_passes_guild_presence_test() -> - State = buffering_test_state(), - Data = (presence_data(<<"99">>))#{<<"guild_id">> => <<"42">>}, +one_to_one_dm_presence_passes_attached_guild_scope_test() -> + State = (buffering_test_state())#{guilds => #{42 => undefined}}, + Data = (presence_data(<<"2">>))#{<<"guild_id">> => <<"42">>}, ?assertEqual(false, should_buffer_presence(presence_update, Data, State)). +one_to_one_dm_presence_buffers_unattached_guild_scope_test() -> + State = (buffering_test_state())#{guilds => #{42 => undefined}}, + Data = (presence_data(<<"2">>))#{<<"guild_id">> => <<"99">>}, + ?assertEqual(true, should_buffer_presence(presence_update, Data, State)). + flush_test_state(PendingUserIds) -> #{ user_id => 1, @@ -344,7 +360,7 @@ flush_test_state(PendingUserIds) -> ] }. -channel_create_flushes_pending_dm_recipient_presence_test() -> +channel_create_does_not_flush_one_to_one_dm_recipient_presence_test() -> State = flush_test_state([2, 99]), ChannelData = #{ <<"id">> => <<"100">>, @@ -354,10 +370,10 @@ channel_create_flushes_pending_dm_recipient_presence_test() -> {NewState, FlushedIds} = maybe_flush_pending_presences( channel_create, ChannelData, State ), - ?assertEqual([2], FlushedIds), - ?assertEqual(1, maps:get(seq, NewState)), + ?assertEqual([], FlushedIds), + ?assertEqual(0, maps:get(seq, NewState)), Remaining = queue:to_list(ensure_queue(maps:get(pending_presences, NewState))), - ?assertEqual([99], [maps:get(user_id, P) || P <- Remaining]). + ?assertEqual([2, 99], [maps:get(user_id, P) || P <- Remaining]). channel_create_ignores_guild_channels_test() -> State = flush_test_state([2]), diff --git a/fluxer_gateway/src/session/session_ready_collect.erl b/fluxer_gateway/src/session/session_ready_collect.erl index 8b011c841..77a546e7c 100644 --- a/fluxer_gateway/src/session/session_ready_collect.erl +++ b/fluxer_gateway/src/session/session_ready_collect.erl @@ -52,14 +52,14 @@ collect_ready_presences(State, _CollectedGuilds) -> -spec collect_presence_targets(session_state(), user_id()) -> [user_id()]. collect_presence_targets(State, CurrentUserId) when is_map(State) -> FIds = presence_targets:friend_ids_from_state(State), - DmMap = presence_targets:dm_recipients_from_state(State), + GroupDmMap = presence_targets:group_dm_recipients_from_state(State), TargetMap0 = add_presence_target_ids(FIds, CurrentUserId, #{}), TargetMap = maps:fold( fun(_Cid, Recipients, Acc) -> add_presence_target_map(Recipients, CurrentUserId, Acc) end, TargetMap0, - DmMap + GroupDmMap ), maps:keys(TargetMap). @@ -350,7 +350,7 @@ collect_presence_targets_deduplicates_before_fetch_test() -> } } }, - ?assertEqual([2, 3, 5], lists:sort(collect_presence_targets(State, 1))). + ?assertEqual([2, 5], lists:sort(collect_presence_targets(State, 1))). collect_ready_users_collects_directly_into_dedup_map_test() -> UserA = #{<<"id">> => <<"10">>, <<"username">> => <<"a">>}, diff --git a/fluxer_gateway/test/presence_broadcast_subscriptions_tests.erl b/fluxer_gateway/test/presence_broadcast_subscriptions_tests.erl index 46c9e2652..f5ffa34d8 100644 --- a/fluxer_gateway/test/presence_broadcast_subscriptions_tests.erl +++ b/fluxer_gateway/test/presence_broadcast_subscriptions_tests.erl @@ -26,7 +26,7 @@ gdm_subscription_add_remove_test() -> State2 = presence_broadcast_subscriptions:sync_group_dm_subscriptions(#{}, State1), ?assertEqual(false, maps:is_key(10, maps:get(subscriptions, State2, #{}))). -one_to_one_dm_recipients_get_subscriptions_test() -> +only_group_dm_recipients_get_global_subscriptions_test() -> maybe_start_presence_bus(), maybe_start_presence_cache(), SessionState = #{ @@ -47,7 +47,7 @@ one_to_one_dm_recipients_get_subscriptions_test() -> } } }, - DmRecipients = presence_targets:dm_recipients_from_state(SessionState), + GroupDmRecipients = presence_targets:group_dm_recipients_from_state(SessionState), BaseState = #{ user_id => 1, is_bot => false, @@ -58,20 +58,21 @@ one_to_one_dm_recipients_get_subscriptions_test() -> group_dm_recipients => #{} }, State1 = presence_broadcast_subscriptions:sync_group_dm_subscriptions( - DmRecipients, BaseState + GroupDmRecipients, BaseState ), Subscriptions = maps:get(subscriptions, State1), - ?assertEqual(true, maps:get(100, maps:get(gdm_channels, maps:get(2, Subscriptions)))), + ?assertEqual(false, maps:is_key(2, Subscriptions)), ?assertEqual(true, maps:get(200, maps:get(gdm_channels, maps:get(3, Subscriptions)))), ?assertEqual(true, maps:get(200, maps:get(gdm_channels, maps:get(4, Subscriptions)))), - StateAfterDmClose = presence_broadcast_subscriptions:sync_group_dm_subscriptions( - maps:remove(100, DmRecipients), State1 + StateAfterGroupDmClose = presence_broadcast_subscriptions:sync_group_dm_subscriptions( + maps:remove(200, GroupDmRecipients), State1 ), - SubsAfterClose = maps:get(subscriptions, StateAfterDmClose), + SubsAfterClose = maps:get(subscriptions, StateAfterGroupDmClose), ?assertEqual(false, maps:is_key(2, SubsAfterClose)), - ?assertEqual(true, maps:is_key(3, SubsAfterClose)). + ?assertEqual(false, maps:is_key(3, SubsAfterClose)), + ?assertEqual(false, maps:is_key(4, SubsAfterClose)). -initial_subscriptions_include_one_to_one_dm_recipients_test() -> +one_to_one_dm_does_not_create_global_subscription_test() -> maybe_start_presence_bus(), SessionState = #{ user_id => 1, @@ -90,11 +91,11 @@ initial_subscriptions_include_one_to_one_dm_recipients_test() -> user_data => #{}, subscriptions => #{}, friends => #{}, - group_dm_recipients => presence_targets:dm_recipients_from_state(SessionState) + group_dm_recipients => presence_targets:group_dm_recipients_from_state(SessionState) }, State1 = presence_broadcast_subscriptions:ensure_initial_global_subscriptions(State), Subscriptions = maps:get(subscriptions, State1), - ?assertEqual(true, maps:get(100, maps:get(gdm_channels, maps:get(2, Subscriptions)))). + ?assertEqual(#{}, Subscriptions). map_from_ids_test() -> ?assertEqual(#{}, presence_broadcast_subscriptions:map_from_ids([])), diff --git a/fluxer_gateway/test/session_dispatch_integration_tests.erl b/fluxer_gateway/test/session_dispatch_integration_tests.erl index aebd9e081..3624bd448 100644 --- a/fluxer_gateway/test/session_dispatch_integration_tests.erl +++ b/fluxer_gateway/test/session_dispatch_integration_tests.erl @@ -27,8 +27,17 @@ base_state(Opts) -> Opts ). -presence_update_with_guild_id_not_buffered_test() -> - State0 = base_state(#{}), +one_to_one_dm_presence_with_attached_guild_id_not_buffered_test() -> + State0 = base_state(#{ + guilds => #{123 => connected}, + channels => #{ + 100 => #{ + <<"id">> => <<"100">>, + <<"type">> => 1, + <<"recipients">> => [#{<<"id">> => <<"2">>}] + } + } + }), Presence = #{ <<"guild_id">> => <<"123">>, <<"user">> => #{<<"id">> => <<"2">>}, @@ -38,11 +47,31 @@ presence_update_with_guild_id_not_buffered_test() -> ?assertEqual([], maps:get(pending_presences, State1, [])), ?assertEqual(1, limited_deque:size(maps:get(buffer, State1))). +one_to_one_dm_presence_with_unattached_guild_id_buffered_test() -> + State0 = base_state(#{ + guilds => #{456 => connected}, + channels => #{ + 100 => #{ + <<"id">> => <<"100">>, + <<"type">> => 1, + <<"recipients">> => [#{<<"id">> => <<"2">>}] + } + } + }), + Presence = #{ + <<"guild_id">> => <<"123">>, + <<"user">> => #{<<"id">> => <<"2">>}, + <<"status">> => <<"idle">> + }, + {noreply, State1} = session_dispatch:handle_dispatch(presence_update, Presence, State0), + ?assertEqual(1, pending_presence_count(State1)), + ?assertEqual(0, limited_deque:size(maps:get(buffer, State1))). + presence_update_without_guild_id_buffered_for_non_relationship_test() -> State0 = base_state(#{}), Presence = #{<<"user">> => #{<<"id">> => <<"2">>}, <<"status">> => <<"online">>}, {noreply, State1} = session_dispatch:handle_dispatch(presence_update, Presence, State0), - ?assertEqual(1, length(maps:get(pending_presences, State1, []))), + ?assertEqual(1, pending_presence_count(State1)), ?assertEqual(0, limited_deque:size(maps:get(buffer, State1))). presence_update_without_guild_id_not_buffered_for_relationship_test() -> @@ -56,28 +85,43 @@ presence_update_buffered_for_outgoing_request_relationship_test() -> State0 = base_state(#{relationships => #{2 => 4}}), Presence = #{<<"user">> => #{<<"id">> => <<"2">>}, <<"status">> => <<"online">>}, {noreply, State1} = session_dispatch:handle_dispatch(presence_update, Presence, State0), - ?assertEqual(1, length(maps:get(pending_presences, State1, []))), + ?assertEqual(1, pending_presence_count(State1)), ?assertEqual(0, limited_deque:size(maps:get(buffer, State1))). -presence_update_not_buffered_for_incoming_request_test() -> +presence_update_buffered_for_incoming_request_test() -> State0 = base_state(#{relationships => #{2 => 3}}), Presence = #{<<"user">> => #{<<"id">> => <<"2">>}, <<"status">> => <<"online">>}, {noreply, State1} = session_dispatch:handle_dispatch(presence_update, Presence, State0), - ?assertEqual([], maps:get(pending_presences, State1, [])), - ?assertEqual(1, limited_deque:size(maps:get(buffer, State1))). + ?assertEqual(1, pending_presence_count(State1)), + ?assertEqual(0, limited_deque:size(maps:get(buffer, State1))). -presence_update_not_buffered_after_relationship_add_test() -> +relationship_mutations_keep_presence_buffer_valid_test() -> State0 = base_state(#{}), - Relationship = #{ + Friend = #{ <<"id">> => <<"2">>, <<"type">> => 1, <<"user">> => #{<<"id">> => <<"2">>} }, {noreply, State1} = session_dispatch:handle_dispatch( - relationship_add, Relationship, State0 + relationship_add, Friend, State0 ), - Presence = #{<<"user">> => #{<<"id">> => <<"2">>}, <<"status">> => <<"online">>}, - {noreply, State2} = session_dispatch:handle_dispatch(presence_update, Presence, State1), + Online = #{<<"user">> => #{<<"id">> => <<"2">>}, <<"status">> => <<"online">>}, + {noreply, State2} = session_dispatch:handle_dispatch(presence_update, Online, State1), ?assertEqual(0, pending_presence_count(State2)), - ?assertEqual(2, limited_deque:size(maps:get(buffer, State2))). + Blocked = Friend#{<<"type">> => 2}, + {noreply, State3} = session_dispatch:handle_dispatch( + relationship_update, Blocked, State2 + ), + Offline = Online#{<<"status">> => <<"offline">>}, + {noreply, State4} = session_dispatch:handle_dispatch(presence_update, Offline, State3), + ?assertEqual(1, pending_presence_count(State4)), + {noreply, State5} = session_dispatch:handle_dispatch( + relationship_add, Friend, State4 + ), + ?assertEqual(0, pending_presence_count(State5)), + {noreply, State6} = session_dispatch:handle_dispatch( + relationship_remove, #{<<"id">> => <<"2">>}, State5 + ), + {noreply, State7} = session_dispatch:handle_dispatch(presence_update, Offline, State6), + ?assertEqual(1, pending_presence_count(State7)). presence_update_skips_sync_presence_targets_test() -> State0 = base_state(#{relationships => #{2 => 1}}), diff --git a/fluxer_gateway/test/session_ready_tests.erl b/fluxer_gateway/test/session_ready_tests.erl index 073b18e23..0f3f76b40 100644 --- a/fluxer_gateway/test/session_ready_tests.erl +++ b/fluxer_gateway/test/session_ready_tests.erl @@ -367,7 +367,7 @@ base_state_for_guild_dispatch_test() -> collected_guild_states => [] }. -collect_ready_presences_includes_one_to_one_dm_recipients_test() -> +collect_ready_presences_does_not_add_one_to_one_dm_as_global_target_test() -> {ok, CachePid} = maybe_start_presence_cache(), OnlineDmUser = #{ <<"status">> => <<"online">>, @@ -398,7 +398,7 @@ collect_ready_presences_includes_one_to_one_dm_recipients_test() -> maps:get(<<"id">>, maps:get(<<"user">>, P, #{}), undefined) || P <- Presences ], - ?assertEqual([<<"2">>], PresenceIds), + ?assertEqual([], PresenceIds), ?assertEqual(ok, gen_server:stop(CachePid)). collect_ready_presences_includes_friend_without_dm_recipients_test() ->