Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 6ed4513

Browse files
committed
Implement changes to MSC2285
Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
1 parent bc2a3db commit 6ed4513

File tree

10 files changed

+139
-125
lines changed

10 files changed

+139
-125
lines changed

synapse/api/constants.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,5 @@ class GuestAccess:
255255

256256
class ReceiptTypes:
257257
READ: Final = "m.read"
258-
259-
260-
class ReadReceiptEventFields:
261-
MSC2285_HIDDEN: Final = "org.matrix.msc2285.hidden"
258+
READ_PRIVATE: Final = "org.matrix.msc2285.read.private"
259+
FULLY_READ: Final = "m.fully_read"

synapse/handlers/receipts.py

+22-29
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import logging
1515
from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple
1616

17-
from synapse.api.constants import ReadReceiptEventFields, ReceiptTypes
17+
from synapse.api.constants import ReceiptTypes
1818
from synapse.appservice import ApplicationService
1919
from synapse.streams import EventSource
2020
from synapse.types import JsonDict, ReadReceipt, UserID, get_domain_from_id
@@ -138,7 +138,7 @@ async def _handle_new_receipts(self, receipts: List[ReadReceipt]) -> bool:
138138
return True
139139

140140
async def received_client_receipt(
141-
self, room_id: str, receipt_type: str, user_id: str, event_id: str, hidden: bool
141+
self, room_id: str, receipt_type: str, user_id: str, event_id: str
142142
) -> None:
143143
"""Called when a client tells us a local user has read up to the given
144144
event_id in the room.
@@ -148,15 +148,16 @@ async def received_client_receipt(
148148
receipt_type=receipt_type,
149149
user_id=user_id,
150150
event_ids=[event_id],
151-
data={"ts": int(self.clock.time_msec()), "hidden": hidden},
151+
data={"ts": int(self.clock.time_msec())},
152152
)
153153

154154
is_new = await self._handle_new_receipts([receipt])
155155
if not is_new:
156156
return
157157

158158
if self.federation_sender and not (
159-
self.hs.config.experimental.msc2285_enabled and hidden
159+
self.hs.config.experimental.msc2285_enabled
160+
and receipt_type == ReceiptTypes.READ_PRIVATE
160161
):
161162
await self.federation_sender.send_read_receipt(receipt)
162163

@@ -178,35 +179,27 @@ def filter_out_hidden(events: List[JsonDict], user_id: str) -> List[JsonDict]:
178179

179180
for event_id in content.keys():
180181
event_content = content.get(event_id, {})
181-
m_read = event_content.get(ReceiptTypes.READ, {})
182182

183-
# If m_read is missing copy over the original event_content as there is nothing to process here
184-
if not m_read:
185-
new_event["content"][event_id] = event_content.copy()
183+
m_read = event_content.get(ReceiptTypes.READ, None)
184+
if m_read:
185+
new_event["content"][event_id] = {ReceiptTypes.READ: m_read}
186186
continue
187187

188-
new_users = {}
189-
for rr_user_id, user_rr in m_read.items():
190-
try:
191-
hidden = user_rr.get("hidden")
192-
except AttributeError:
193-
# Due to https://github.com/matrix-org/synapse/issues/10376
194-
# there are cases where user_rr is a string, in those cases
195-
# we just ignore the read receipt
196-
continue
188+
m_read_private = event_content.get(ReceiptTypes.READ_PRIVATE, None)
189+
if m_read_private:
190+
new_users = {}
191+
for rr_user_id, user_rr in m_read_private.items():
192+
if rr_user_id == user_id:
193+
new_users[rr_user_id] = user_rr.copy()
194+
195+
# Set new users unless empty
196+
if len(new_users.keys()) > 0:
197+
new_event["content"][event_id] = {
198+
ReceiptTypes.READ_PRIVATE: new_users
199+
}
200+
continue
197201

198-
if hidden is not True or rr_user_id == user_id:
199-
new_users[rr_user_id] = user_rr.copy()
200-
# If hidden has a value replace hidden with the correct prefixed key
201-
if hidden is not None:
202-
new_users[rr_user_id].pop("hidden")
203-
new_users[rr_user_id][
204-
ReadReceiptEventFields.MSC2285_HIDDEN
205-
] = hidden
206-
207-
# Set new users unless empty
208-
if len(new_users.keys()) > 0:
209-
new_event["content"][event_id] = {ReceiptTypes.READ: new_users}
202+
new_event["content"][event_id] = event_content
210203

211204
# Append new_event to visible_events unless empty
212205
if len(new_event["content"].keys()) > 0:

synapse/handlers/sync.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1065,8 +1065,16 @@ async def unread_notifs_for_room_id(
10651065
last_unread_event_id = await self.store.get_last_receipt_event_id_for_user(
10661066
user_id=sync_config.user.to_string(),
10671067
room_id=room_id,
1068-
receipt_type=ReceiptTypes.READ,
1068+
receipt_type=ReceiptTypes.READ_PRIVATE,
10691069
)
1070+
if not last_unread_event_id:
1071+
last_unread_event_id = (
1072+
await self.store.get_last_receipt_event_id_for_user(
1073+
user_id=sync_config.user.to_string(),
1074+
room_id=room_id,
1075+
receipt_type=ReceiptTypes.READ,
1076+
)
1077+
)
10701078

10711079
return await self.store.get_unread_event_push_actions_by_room_for_user(
10721080
room_id, sync_config.user.to_string(), last_unread_event_id

synapse/push/push_tools.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414
from typing import Dict
1515

16-
from synapse.api.constants import ReceiptTypes
1716
from synapse.events import EventBase
1817
from synapse.push.presentable_names import calculate_room_name, name_from_member_event
1918
from synapse.storage import Storage
@@ -24,7 +23,7 @@ async def get_badge_count(store: DataStore, user_id: str, group_by_room: bool) -
2423
invites = await store.get_invited_rooms_for_local_user(user_id)
2524
joins = await store.get_rooms_for_user(user_id)
2625

27-
my_receipts_by_room = await store.get_receipts_for_user(user_id, ReceiptTypes.READ)
26+
my_receipts_by_room = await store.get_receipts_for_user(user_id)
2827

2928
badge = len(invites)
3029

synapse/rest/client/notifications.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import logging
1616
from typing import TYPE_CHECKING, Tuple
1717

18-
from synapse.api.constants import ReceiptTypes
1918
from synapse.events.utils import (
2019
SerializeEventConfig,
2120
format_event_for_client_v2_without_room_id,
@@ -58,7 +57,7 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
5857
)
5958

6059
receipts_by_room = await self.store.get_receipts_for_user_with_orderings(
61-
user_id, ReceiptTypes.READ
60+
user_id
6261
)
6362

6463
notif_event_ids = [pa.event_id for pa in push_actions]

synapse/rest/client/read_marker.py

+18-14
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
import logging
1616
from typing import TYPE_CHECKING, Tuple
1717

18-
from synapse.api.constants import ReadReceiptEventFields, ReceiptTypes
19-
from synapse.api.errors import Codes, SynapseError
18+
from synapse.api.constants import ReceiptTypes
2019
from synapse.http.server import HttpServer
2120
from synapse.http.servlet import RestServlet, parse_json_object_from_request
2221
from synapse.http.site import SynapseRequest
@@ -48,27 +47,32 @@ async def on_POST(
4847
await self.presence_handler.bump_presence_active_time(requester.user)
4948

5049
body = parse_json_object_from_request(request)
51-
read_event_id = body.get(ReceiptTypes.READ, None)
52-
hidden = body.get(ReadReceiptEventFields.MSC2285_HIDDEN, False)
53-
54-
if not isinstance(hidden, bool):
55-
raise SynapseError(
56-
400,
57-
"Param %s must be a boolean, if given"
58-
% ReadReceiptEventFields.MSC2285_HIDDEN,
59-
Codes.BAD_JSON,
60-
)
6150

51+
read_event_id = body.get(ReceiptTypes.READ, None)
6252
if read_event_id:
6353
await self.receipts_handler.received_client_receipt(
6454
room_id,
6555
ReceiptTypes.READ,
6656
user_id=requester.user.to_string(),
6757
event_id=read_event_id,
68-
hidden=hidden,
58+
)
59+
await self.receipts_handler.received_client_receipt(
60+
room_id,
61+
ReceiptTypes.READ_PRIVATE,
62+
user_id=requester.user.to_string(),
63+
event_id=read_event_id,
64+
)
65+
66+
read_private_event_id = body.get(ReceiptTypes.READ_PRIVATE, None)
67+
if read_private_event_id:
68+
await self.receipts_handler.received_client_receipt(
69+
room_id,
70+
ReceiptTypes.READ_PRIVATE,
71+
user_id=requester.user.to_string(),
72+
event_id=read_private_event_id,
6973
)
7074

71-
read_marker_event_id = body.get("m.fully_read", None)
75+
read_marker_event_id = body.get(ReceiptTypes.FULLY_READ, None)
7276
if read_marker_event_id:
7377
await self.read_marker_handler.received_client_read_marker(
7478
room_id,

synapse/rest/client/receipts.py

+39-21
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
import re
1717
from typing import TYPE_CHECKING, Tuple
1818

19-
from synapse.api.constants import ReadReceiptEventFields, ReceiptTypes
20-
from synapse.api.errors import Codes, SynapseError
19+
from synapse.api.constants import ReceiptTypes
20+
from synapse.api.errors import SynapseError
2121
from synapse.http import get_request_user_agent
2222
from synapse.http.server import HttpServer
2323
from synapse.http.servlet import RestServlet, parse_json_object_from_request
@@ -46,42 +46,60 @@ def __init__(self, hs: "HomeServer"):
4646
self.hs = hs
4747
self.auth = hs.get_auth()
4848
self.receipts_handler = hs.get_receipts_handler()
49+
self.read_marker_handler = hs.get_read_marker_handler()
4950
self.presence_handler = hs.get_presence_handler()
5051

5152
async def on_POST(
5253
self, request: SynapseRequest, room_id: str, receipt_type: str, event_id: str
5354
) -> Tuple[int, JsonDict]:
5455
requester = await self.auth.get_user_by_req(request)
5556

56-
if receipt_type != ReceiptTypes.READ:
57-
raise SynapseError(400, "Receipt type must be 'm.read'")
57+
if receipt_type not in [
58+
ReceiptTypes.READ,
59+
ReceiptTypes.READ_PRIVATE,
60+
ReceiptTypes.FULLY_READ,
61+
]:
62+
raise SynapseError(
63+
400,
64+
"Receipt type must be 'm.read', 'org.matrix.msc2285.read.private' or 'm.fully_read'",
65+
)
5866

5967
# Do not allow older SchildiChat and Element Android clients (prior to Element/1.[012].x) to send an empty body.
6068
user_agent = get_request_user_agent(request)
6169
allow_empty_body = False
6270
if "Android" in user_agent:
6371
if pattern.match(user_agent) or "Riot" in user_agent:
6472
allow_empty_body = True
65-
body = parse_json_object_from_request(request, allow_empty_body)
66-
hidden = body.get(ReadReceiptEventFields.MSC2285_HIDDEN, False)
67-
68-
if not isinstance(hidden, bool):
69-
raise SynapseError(
70-
400,
71-
"Param %s must be a boolean, if given"
72-
% ReadReceiptEventFields.MSC2285_HIDDEN,
73-
Codes.BAD_JSON,
74-
)
73+
parse_json_object_from_request(request, allow_empty_body)
7574

7675
await self.presence_handler.bump_presence_active_time(requester.user)
7776

78-
await self.receipts_handler.received_client_receipt(
79-
room_id,
80-
receipt_type,
81-
user_id=requester.user.to_string(),
82-
event_id=event_id,
83-
hidden=hidden,
84-
)
77+
if receipt_type == ReceiptTypes.FULLY_READ:
78+
await self.read_marker_handler.received_client_read_marker(
79+
room_id,
80+
user_id=requester.user.to_string(),
81+
event_id=event_id,
82+
)
83+
elif receipt_type == ReceiptTypes.READ:
84+
await self.receipts_handler.received_client_receipt(
85+
room_id,
86+
ReceiptTypes.READ,
87+
user_id=requester.user.to_string(),
88+
event_id=event_id,
89+
)
90+
await self.receipts_handler.received_client_receipt(
91+
room_id,
92+
ReceiptTypes.READ_PRIVATE,
93+
user_id=requester.user.to_string(),
94+
event_id=event_id,
95+
)
96+
else:
97+
await self.receipts_handler.received_client_receipt(
98+
room_id,
99+
receipt_type,
100+
user_id=requester.user.to_string(),
101+
event_id=event_id,
102+
)
85103

86104
return 200, {}
87105

synapse/storage/databases/main/receipts.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -134,22 +134,18 @@ async def get_last_receipt_event_id_for_user(
134134
allow_none=True,
135135
)
136136

137-
@cached(num_args=2)
138-
async def get_receipts_for_user(
139-
self, user_id: str, receipt_type: str
140-
) -> Dict[str, str]:
137+
@cached(num_args=1)
138+
async def get_receipts_for_user(self, user_id: str) -> Dict[str, str]:
141139
rows = await self.db_pool.simple_select_list(
142140
table="receipts_linearized",
143-
keyvalues={"user_id": user_id, "receipt_type": receipt_type},
141+
keyvalues={"user_id": user_id},
144142
retcols=("room_id", "event_id"),
145143
desc="get_receipts_for_user",
146144
)
147145

148146
return {row["room_id"]: row["event_id"] for row in rows}
149147

150-
async def get_receipts_for_user_with_orderings(
151-
self, user_id: str, receipt_type: str
152-
) -> JsonDict:
148+
async def get_receipts_for_user_with_orderings(self, user_id: str) -> JsonDict:
153149
def f(txn: LoggingTransaction) -> List[Tuple[str, str, int, int]]:
154150
sql = (
155151
"SELECT rl.room_id, rl.event_id,"

0 commit comments

Comments
 (0)