Handle room join/leave for profiles extension

This commit is contained in:
Jason Robinson
2026-07-22 12:15:58 +03:00
parent e393b282df
commit 2f8683f42f
2 changed files with 155 additions and 43 deletions
+59 -43
View File
@@ -1179,6 +1179,19 @@ class SlidingSyncExtensionHandler:
field_names_empty_means_all_fields=False if fields else True,
)
profile_user_ids = set()
left_room_user_ids = {
update.user_id
for update in updates
if update.action == ProfileUpdateAction.LEFT_ROOM.value
}
joined_room_user_ids = {
update.user_id
for update in updates
if update.action == ProfileUpdateAction.JOINED_ROOM.value
}
# Add any newly joined users
profile_user_ids.update(joined_room_user_ids)
updated_users = {
update.user_id
for update in updates
@@ -1201,54 +1214,57 @@ class SlidingSyncExtensionHandler:
continue
updated_user_fields.setdefault(update.user_id, set()).add(update.field_name)
profile_data_by_user = await self.store.get_profile_data_for_users(
profile_user_ids
)
profile_data_by_user = await self.store.get_profile_data_for_users(
profile_user_ids
)
# TODO lazy loading
is_lazy = False
# TODO lazy loading
is_lazy = False
# Serialise the profile updates into the sync response format.
for profile_user_id in profile_user_ids:
profile_data = profile_data_by_user.get(profile_user_id)
if profile_data is None:
# No profile data for this user, just return a blank dictionary
# in incremental sync, telling the clients to remove all profile
# information for this user.
response[profile_user_id] = None
continue
# Serialise the profile updates into the sync response format.
for profile_user_id in profile_user_ids:
profile_data = profile_data_by_user.get(profile_user_id)
if profile_data is None:
# No profile data for this user, just return a blank dictionary
# in incremental sync, telling the clients to remove all profile
# information for this user.
response[profile_user_id] = None
continue
per_user_updates: dict[str, JsonValue | dict[str, JsonValue]] = {}
if is_lazy:
# TODO lazy cache
per_user_updates: dict[str, JsonValue | dict[str, JsonValue]] = {}
# Include the fields the client asked for, or all, if not specified
if fields:
user_fields = set(profile_data.keys()).intersection(fields)
else:
user_fields = set(profile_data.keys())
if is_lazy:
# TODO lazy cache
for field_name in user_fields:
per_user_updates[field_name] = profile_data.get(field_name)
else:
# Include only the diff, unless the user recently joined,
# then send all the fields the client asked for.
# We don't use a cache here as for non-lazy sync we always
# send changes and/or fields the client asked for, if relevant
# as above joined condition.
user_fields = (
user_fields
if profile_user_id in joined_room_user_ids
else set(updated_user_fields.get(profile_user_id, []))
)
for field_name in user_fields:
per_user_updates[field_name] = profile_data[field_name]
# Include the fields the client asked for, or all, if not specified
if fields:
user_fields = set(profile_data.keys()).intersection(fields)
else:
user_fields = set(profile_data.keys())
for field_name in user_fields:
per_user_updates[field_name] = profile_data.get(field_name)
else:
# Include only the diff, unless the user recently joined,
# then send all the fields the client asked for.
# We don't use a cache here as for non-lazy sync we always
# send changes and/or fields the client asked for, if relevant
# as above joined condition.
user_fields = (
fields
# TODO joined_room_user_ids
if profile_user_id in []
else set(updated_user_fields.get(profile_user_id, []))
)
user_fields = set(profile_data.keys()).intersection(user_fields)
for field_name in user_fields:
per_user_updates[field_name] = profile_data[field_name]
if per_user_updates:
response[profile_user_id] = {
"updated": per_user_updates,
}
if per_user_updates:
response[profile_user_id] = {
"updated": per_user_updates,
}
# Process left rooms
if left_room_user_ids:
for other_user_id in left_room_user_ids:
# Return a null response to the client
response[other_user_id] = None
return SlidingSyncResult.Extensions.ProfilesExtension(
users=response,
@@ -434,3 +434,99 @@ class SlidingSyncProfilesTestCase(SlidingSyncBase):
}
},
)
@parameterized.expand(
[
[True, False],
[True, True],
[False, False],
[False, True],
]
)
@override_config({"include_profile_updates_in_sync": True})
def test_null_profile_returned_if_user_left_all_rooms(
self, request_fields: bool, is_lazy: bool
) -> None:
"""
Test that profile extension response returns a null for the user in
incremental sync.
"""
# TODO handle is_lazy
# Make an initial Sliding Sync request with the profiles extension enabled
profiles_config: dict = {
"enabled": True,
}
if request_fields:
profiles_config["fields"] = ["field"]
sync_body = {
"lists": {},
"extensions": {
"org.matrix.msc4262.profiles": profiles_config,
},
}
response_body, from_token = self.do_sync(sync_body, tok=self.tok)
self.helper.leave(self.joined_room, self.other_user, tok=self.other_tok)
# Make an incremental Sliding Sync request
response_body, _ = self.do_sync(sync_body, since=from_token, tok=self.tok)
# We should see a null profile
self.assertIsNone(
response_body["extensions"]["org.matrix.msc4262.profiles"]["users"][
"@other_user:test"
],
)
@parameterized.expand(
[
True,
False,
]
)
@override_config({"include_profile_updates_in_sync": True})
def test_all_fields_returned_in_incremental_non_lazy_sync_if_someone_joined(
self, request_fields: bool
) -> None:
"""
Test that profile extension response returns all profile fields in
incremental non-lazy sync, if someone joined the room..
"""
# Make an initial Sliding Sync request with the profiles extension enabled
profiles_config: dict = {
"enabled": True,
}
if request_fields:
profiles_config["fields"] = ["displayname"]
sync_body = {
"lists": {},
"extensions": {
"org.matrix.msc4262.profiles": profiles_config,
},
}
response_body, from_token = self.do_sync(sync_body, tok=self.tok)
third_user = self.register_user("third_user", "third_user")
third_tok = self.login(third_user, "third_user")
self.helper.join(self.joined_room, third_user, tok=third_tok)
# Make an incremental Sliding Sync request
response_body, _ = self.do_sync(sync_body, since=from_token, tok=self.tok)
expectation = {
"updated": {
"avatar_url": None,
"displayname": "third_user",
}
}
if request_fields:
expectation = {
"updated": {
"displayname": "third_user",
},
}
self.assertEqual(
response_body["extensions"]["org.matrix.msc4262.profiles"]["users"][
"@third_user:test"
],
expectation,
)