mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-03 05:10:25 +03:00
fix(presence): enforce visibility and relationship invariants (#1507)
This commit is contained in:
@@ -313,9 +313,9 @@ export class GroupDmOperationsService {
|
||||
|
||||
private async syncGroupDmRecipientsForUser(userId: UserID): Promise<void> {
|
||||
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<string, Array<string>> = {};
|
||||
for (const channel of dmChannels) {
|
||||
for (const channel of groupDmChannels) {
|
||||
const otherRecipients = Array.from(channel.recipientIds)
|
||||
.filter((recId) => recId !== userId)
|
||||
.map((recId) => recId.toString());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) ->
|
||||
#{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(#{})).
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
|
||||
).
|
||||
|
||||
@@ -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]),
|
||||
|
||||
@@ -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">>},
|
||||
|
||||
@@ -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([])),
|
||||
|
||||
@@ -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}}),
|
||||
|
||||
@@ -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() ->
|
||||
|
||||
Reference in New Issue
Block a user