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

Commit ab18441

Browse files
Support stable identifiers for MSC2285: private read receipts. (#13273)
This adds support for the stable identifiers of MSC2285 while continuing to support the unstable identifiers behind the configuration flag. These will be removed in a future version.
1 parent e2ed1b7 commit ab18441

File tree

14 files changed

+246
-94
lines changed

14 files changed

+246
-94
lines changed

changelog.d/13273.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for stable prefixes for [MSC2285 (private read receipts)](https://github.com/matrix-org/matrix-spec-proposals/pull/2285).

synapse/api/constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,8 @@ class GuestAccess:
257257

258258
class ReceiptTypes:
259259
READ: Final = "m.read"
260-
READ_PRIVATE: Final = "org.matrix.msc2285.read.private"
260+
READ_PRIVATE: Final = "m.read.private"
261+
UNSTABLE_READ_PRIVATE: Final = "org.matrix.msc2285.read.private"
261262
FULLY_READ: Final = "m.fully_read"
262263

263264

synapse/config/experimental.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
3232
# MSC2716 (importing historical messages)
3333
self.msc2716_enabled: bool = experimental.get("msc2716_enabled", False)
3434

35-
# MSC2285 (private read receipts)
35+
# MSC2285 (unstable private read receipts)
3636
self.msc2285_enabled: bool = experimental.get("msc2285_enabled", False)
3737

3838
# MSC3244 (room version capabilities)

synapse/handlers/initial_sync.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ async def _snapshot_all_rooms(
143143
joined_rooms,
144144
to_key=int(now_token.receipt_key),
145145
)
146-
if self.hs.config.experimental.msc2285_enabled:
147-
receipt = ReceiptEventSource.filter_out_private_receipts(receipt, user_id)
146+
147+
receipt = ReceiptEventSource.filter_out_private_receipts(receipt, user_id)
148148

149149
tags_by_room = await self.store.get_tags_for_user(user_id)
150150

@@ -456,11 +456,8 @@ async def get_receipts() -> List[JsonDict]:
456456
)
457457
if not receipts:
458458
return []
459-
if self.hs.config.experimental.msc2285_enabled:
460-
receipts = ReceiptEventSource.filter_out_private_receipts(
461-
receipts, user_id
462-
)
463-
return receipts
459+
460+
return ReceiptEventSource.filter_out_private_receipts(receipts, user_id)
464461

465462
presence, receipts, (messages, token) = await make_deferred_yieldable(
466463
gather_results(

synapse/handlers/receipts.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,10 @@ async def received_client_receipt(
163163
if not is_new:
164164
return
165165

166-
if self.federation_sender and receipt_type != ReceiptTypes.READ_PRIVATE:
166+
if self.federation_sender and receipt_type not in (
167+
ReceiptTypes.READ_PRIVATE,
168+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
169+
):
167170
await self.federation_sender.send_read_receipt(receipt)
168171

169172

@@ -203,24 +206,38 @@ def filter_out_private_receipts(
203206
for event_id, orig_event_content in room.get("content", {}).items():
204207
event_content = orig_event_content
205208
# If there are private read receipts, additional logic is necessary.
206-
if ReceiptTypes.READ_PRIVATE in event_content:
209+
if (
210+
ReceiptTypes.READ_PRIVATE in event_content
211+
or ReceiptTypes.UNSTABLE_READ_PRIVATE in event_content
212+
):
207213
# Make a copy without private read receipts to avoid leaking
208214
# other user's private read receipts..
209215
event_content = {
210216
receipt_type: receipt_value
211217
for receipt_type, receipt_value in event_content.items()
212-
if receipt_type != ReceiptTypes.READ_PRIVATE
218+
if receipt_type
219+
not in (
220+
ReceiptTypes.READ_PRIVATE,
221+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
222+
)
213223
}
214224

215225
# Copy the current user's private read receipt from the
216226
# original content, if it exists.
217-
user_private_read_receipt = orig_event_content[
218-
ReceiptTypes.READ_PRIVATE
219-
].get(user_id, None)
227+
user_private_read_receipt = orig_event_content.get(
228+
ReceiptTypes.READ_PRIVATE, {}
229+
).get(user_id, None)
220230
if user_private_read_receipt:
221231
event_content[ReceiptTypes.READ_PRIVATE] = {
222232
user_id: user_private_read_receipt
223233
}
234+
user_unstable_private_read_receipt = orig_event_content.get(
235+
ReceiptTypes.UNSTABLE_READ_PRIVATE, {}
236+
).get(user_id, None)
237+
if user_unstable_private_read_receipt:
238+
event_content[ReceiptTypes.UNSTABLE_READ_PRIVATE] = {
239+
user_id: user_unstable_private_read_receipt
240+
}
224241

225242
# Include the event if there is at least one non-private read
226243
# receipt or the current user has a private read receipt.
@@ -256,10 +273,9 @@ async def get_new_events(
256273
room_ids, from_key=from_key, to_key=to_key
257274
)
258275

259-
if self.config.experimental.msc2285_enabled:
260-
events = ReceiptEventSource.filter_out_private_receipts(
261-
events, user.to_string()
262-
)
276+
events = ReceiptEventSource.filter_out_private_receipts(
277+
events, user.to_string()
278+
)
263279

264280
return events, to_key
265281

synapse/replication/tcp/client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,10 @@ async def _on_new_receipts(
416416
if not self._is_mine_id(receipt.user_id):
417417
continue
418418
# Private read receipts never get sent over federation.
419-
if receipt.receipt_type == ReceiptTypes.READ_PRIVATE:
419+
if receipt.receipt_type in (
420+
ReceiptTypes.READ_PRIVATE,
421+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
422+
):
420423
continue
421424
receipt_info = ReadReceipt(
422425
receipt.room_id,

synapse/rest/client/notifications.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,12 @@ async def on_GET(self, request: SynapseRequest) -> Tuple[int, JsonDict]:
5858
)
5959

6060
receipts_by_room = await self.store.get_receipts_for_user_with_orderings(
61-
user_id, [ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE]
61+
user_id,
62+
[
63+
ReceiptTypes.READ,
64+
ReceiptTypes.READ_PRIVATE,
65+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
66+
],
6267
)
6368

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

synapse/rest/client/read_marker.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,13 @@ def __init__(self, hs: "HomeServer"):
4040
self.read_marker_handler = hs.get_read_marker_handler()
4141
self.presence_handler = hs.get_presence_handler()
4242

43-
self._known_receipt_types = {ReceiptTypes.READ, ReceiptTypes.FULLY_READ}
43+
self._known_receipt_types = {
44+
ReceiptTypes.READ,
45+
ReceiptTypes.FULLY_READ,
46+
ReceiptTypes.READ_PRIVATE,
47+
}
4448
if hs.config.experimental.msc2285_enabled:
45-
self._known_receipt_types.add(ReceiptTypes.READ_PRIVATE)
49+
self._known_receipt_types.add(ReceiptTypes.UNSTABLE_READ_PRIVATE)
4650

4751
async def on_POST(
4852
self, request: SynapseRequest, room_id: str

synapse/rest/client/receipts.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ def __init__(self, hs: "HomeServer"):
4444
self.read_marker_handler = hs.get_read_marker_handler()
4545
self.presence_handler = hs.get_presence_handler()
4646

47-
self._known_receipt_types = {ReceiptTypes.READ}
47+
self._known_receipt_types = {
48+
ReceiptTypes.READ,
49+
ReceiptTypes.READ_PRIVATE,
50+
ReceiptTypes.FULLY_READ,
51+
}
4852
if hs.config.experimental.msc2285_enabled:
49-
self._known_receipt_types.update(
50-
(ReceiptTypes.READ_PRIVATE, ReceiptTypes.FULLY_READ)
51-
)
53+
self._known_receipt_types.add(ReceiptTypes.UNSTABLE_READ_PRIVATE)
5254

5355
async def on_POST(
5456
self, request: SynapseRequest, room_id: str, receipt_type: str, event_id: str

synapse/rest/client/versions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def on_GET(self, request: Request) -> Tuple[int, JsonDict]:
9494
# Supports the busy presence state described in MSC3026.
9595
"org.matrix.msc3026.busy_presence": self.config.experimental.msc3026_enabled,
9696
# Supports receiving private read receipts as per MSC2285
97+
"org.matrix.msc2285.stable": True, # TODO: Remove when MSC2285 becomes a part of the spec
9798
"org.matrix.msc2285": self.config.experimental.msc2285_enabled,
9899
# Supports filtering of /publicRooms by room type as per MSC3827
99100
"org.matrix.msc3827.stable": True,

synapse/storage/databases/main/event_push_actions.py

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080

8181
from synapse.api.constants import ReceiptTypes
8282
from synapse.metrics.background_process_metrics import wrap_as_background_process
83-
from synapse.storage._base import SQLBaseStore, db_to_json
83+
from synapse.storage._base import SQLBaseStore, db_to_json, make_in_list_sql_clause
8484
from synapse.storage.database import (
8585
DatabasePool,
8686
LoggingDatabaseConnection,
@@ -259,7 +259,11 @@ def _get_unread_counts_by_receipt_txn(
259259
txn,
260260
user_id,
261261
room_id,
262-
receipt_types=(ReceiptTypes.READ, ReceiptTypes.READ_PRIVATE),
262+
receipt_types=(
263+
ReceiptTypes.READ,
264+
ReceiptTypes.READ_PRIVATE,
265+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
266+
),
263267
)
264268

265269
stream_ordering = None
@@ -448,25 +452,37 @@ async def get_unread_push_actions_for_user_in_range_for_http(
448452
The list will be ordered by ascending stream_ordering.
449453
The list will have between 0~limit entries.
450454
"""
455+
451456
# find rooms that have a read receipt in them and return the next
452457
# push actions
453458
def get_after_receipt(
454459
txn: LoggingTransaction,
455460
) -> List[Tuple[str, str, int, str, bool]]:
456461
# find rooms that have a read receipt in them and return the next
457462
# push actions
458-
sql = """
463+
464+
receipt_types_clause, args = make_in_list_sql_clause(
465+
self.database_engine,
466+
"receipt_type",
467+
(
468+
ReceiptTypes.READ,
469+
ReceiptTypes.READ_PRIVATE,
470+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
471+
),
472+
)
473+
474+
sql = f"""
459475
SELECT ep.event_id, ep.room_id, ep.stream_ordering, ep.actions,
460476
ep.highlight
461477
FROM (
462478
SELECT room_id,
463479
MAX(stream_ordering) as stream_ordering
464480
FROM events
465481
INNER JOIN receipts_linearized USING (room_id, event_id)
466-
WHERE receipt_type = 'm.read' AND user_id = ?
482+
WHERE {receipt_types_clause} AND user_id = ?
467483
GROUP BY room_id
468484
) AS rl,
469-
event_push_actions AS ep
485+
event_push_actions AS ep
470486
WHERE
471487
ep.room_id = rl.room_id
472488
AND ep.stream_ordering > rl.stream_ordering
@@ -476,7 +492,9 @@ def get_after_receipt(
476492
AND ep.notif = 1
477493
ORDER BY ep.stream_ordering ASC LIMIT ?
478494
"""
479-
args = [user_id, user_id, min_stream_ordering, max_stream_ordering, limit]
495+
args.extend(
496+
(user_id, user_id, min_stream_ordering, max_stream_ordering, limit)
497+
)
480498
txn.execute(sql, args)
481499
return cast(List[Tuple[str, str, int, str, bool]], txn.fetchall())
482500

@@ -490,15 +508,25 @@ def get_after_receipt(
490508
def get_no_receipt(
491509
txn: LoggingTransaction,
492510
) -> List[Tuple[str, str, int, str, bool]]:
493-
sql = """
511+
receipt_types_clause, args = make_in_list_sql_clause(
512+
self.database_engine,
513+
"receipt_type",
514+
(
515+
ReceiptTypes.READ,
516+
ReceiptTypes.READ_PRIVATE,
517+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
518+
),
519+
)
520+
521+
sql = f"""
494522
SELECT ep.event_id, ep.room_id, ep.stream_ordering, ep.actions,
495523
ep.highlight
496524
FROM event_push_actions AS ep
497525
INNER JOIN events AS e USING (room_id, event_id)
498526
WHERE
499527
ep.room_id NOT IN (
500528
SELECT room_id FROM receipts_linearized
501-
WHERE receipt_type = 'm.read' AND user_id = ?
529+
WHERE {receipt_types_clause} AND user_id = ?
502530
GROUP BY room_id
503531
)
504532
AND ep.user_id = ?
@@ -507,7 +535,9 @@ def get_no_receipt(
507535
AND ep.notif = 1
508536
ORDER BY ep.stream_ordering ASC LIMIT ?
509537
"""
510-
args = [user_id, user_id, min_stream_ordering, max_stream_ordering, limit]
538+
args.extend(
539+
(user_id, user_id, min_stream_ordering, max_stream_ordering, limit)
540+
)
511541
txn.execute(sql, args)
512542
return cast(List[Tuple[str, str, int, str, bool]], txn.fetchall())
513543

@@ -557,20 +587,31 @@ async def get_unread_push_actions_for_user_in_range_for_email(
557587
The list will be ordered by descending received_ts.
558588
The list will have between 0~limit entries.
559589
"""
590+
560591
# find rooms that have a read receipt in them and return the most recent
561592
# push actions
562593
def get_after_receipt(
563594
txn: LoggingTransaction,
564595
) -> List[Tuple[str, str, int, str, bool, int]]:
565-
sql = """
596+
receipt_types_clause, args = make_in_list_sql_clause(
597+
self.database_engine,
598+
"receipt_type",
599+
(
600+
ReceiptTypes.READ,
601+
ReceiptTypes.READ_PRIVATE,
602+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
603+
),
604+
)
605+
606+
sql = f"""
566607
SELECT ep.event_id, ep.room_id, ep.stream_ordering, ep.actions,
567608
ep.highlight, e.received_ts
568609
FROM (
569610
SELECT room_id,
570611
MAX(stream_ordering) as stream_ordering
571612
FROM events
572613
INNER JOIN receipts_linearized USING (room_id, event_id)
573-
WHERE receipt_type = 'm.read' AND user_id = ?
614+
WHERE {receipt_types_clause} AND user_id = ?
574615
GROUP BY room_id
575616
) AS rl,
576617
event_push_actions AS ep
@@ -584,7 +625,9 @@ def get_after_receipt(
584625
AND ep.notif = 1
585626
ORDER BY ep.stream_ordering DESC LIMIT ?
586627
"""
587-
args = [user_id, user_id, min_stream_ordering, max_stream_ordering, limit]
628+
args.extend(
629+
(user_id, user_id, min_stream_ordering, max_stream_ordering, limit)
630+
)
588631
txn.execute(sql, args)
589632
return cast(List[Tuple[str, str, int, str, bool, int]], txn.fetchall())
590633

@@ -598,15 +641,25 @@ def get_after_receipt(
598641
def get_no_receipt(
599642
txn: LoggingTransaction,
600643
) -> List[Tuple[str, str, int, str, bool, int]]:
601-
sql = """
644+
receipt_types_clause, args = make_in_list_sql_clause(
645+
self.database_engine,
646+
"receipt_type",
647+
(
648+
ReceiptTypes.READ,
649+
ReceiptTypes.READ_PRIVATE,
650+
ReceiptTypes.UNSTABLE_READ_PRIVATE,
651+
),
652+
)
653+
654+
sql = f"""
602655
SELECT ep.event_id, ep.room_id, ep.stream_ordering, ep.actions,
603656
ep.highlight, e.received_ts
604657
FROM event_push_actions AS ep
605658
INNER JOIN events AS e USING (room_id, event_id)
606659
WHERE
607660
ep.room_id NOT IN (
608661
SELECT room_id FROM receipts_linearized
609-
WHERE receipt_type = 'm.read' AND user_id = ?
662+
WHERE {receipt_types_clause} AND user_id = ?
610663
GROUP BY room_id
611664
)
612665
AND ep.user_id = ?
@@ -615,7 +668,9 @@ def get_no_receipt(
615668
AND ep.notif = 1
616669
ORDER BY ep.stream_ordering DESC LIMIT ?
617670
"""
618-
args = [user_id, user_id, min_stream_ordering, max_stream_ordering, limit]
671+
args.extend(
672+
(user_id, user_id, min_stream_ordering, max_stream_ordering, limit)
673+
)
619674
txn.execute(sql, args)
620675
return cast(List[Tuple[str, str, int, str, bool, int]], txn.fetchall())
621676

0 commit comments

Comments
 (0)