@@ -2284,11 +2284,24 @@ async def get_extensions_response(
22842284 from_token = from_token ,
22852285 )
22862286
2287+ typing_response = None
2288+ if sync_config .extensions .typing is not None :
2289+ typing_response = await self .get_typing_extension_response (
2290+ sync_config = sync_config ,
2291+ actual_lists = actual_lists ,
2292+ actual_room_ids = actual_room_ids ,
2293+ actual_room_response_map = actual_room_response_map ,
2294+ typing_request = sync_config .extensions .typing ,
2295+ to_token = to_token ,
2296+ from_token = from_token ,
2297+ )
2298+
22872299 return SlidingSyncResult .Extensions (
22882300 to_device = to_device_response ,
22892301 e2ee = e2ee_response ,
22902302 account_data = account_data_response ,
22912303 receipts = receipts_response ,
2304+ typing = typing_response ,
22922305 )
22932306
22942307 def find_relevant_room_ids_for_extension (
@@ -2615,6 +2628,8 @@ async def get_receipts_extension_response(
26152628
26162629 room_id_to_receipt_map : Dict [str , JsonMapping ] = {}
26172630 if len (relevant_room_ids ) > 0 :
2631+ # TODO: Take connection tracking into account so that when a room comes back
2632+ # into range we can send the receipts that were missed.
26182633 receipt_source = self .event_sources .sources .receipt
26192634 receipts , _ = await receipt_source .get_new_events (
26202635 user = sync_config .user ,
@@ -2636,6 +2651,8 @@ async def get_receipts_extension_response(
26362651 type = receipt ["type" ]
26372652 content = receipt ["content" ]
26382653
2654+ # For `inital: True` rooms, we only want to include receipts for events
2655+ # in the timeline.
26392656 room_result = actual_room_response_map .get (room_id )
26402657 if room_result is not None :
26412658 if room_result .initial :
@@ -2659,6 +2676,70 @@ async def get_receipts_extension_response(
26592676 room_id_to_receipt_map = room_id_to_receipt_map ,
26602677 )
26612678
2679+ async def get_typing_extension_response (
2680+ self ,
2681+ sync_config : SlidingSyncConfig ,
2682+ actual_lists : Dict [str , SlidingSyncResult .SlidingWindowList ],
2683+ actual_room_ids : Set [str ],
2684+ actual_room_response_map : Dict [str , SlidingSyncResult .RoomResult ],
2685+ typing_request : SlidingSyncConfig .Extensions .TypingExtension ,
2686+ to_token : StreamToken ,
2687+ from_token : Optional [SlidingSyncStreamToken ],
2688+ ) -> Optional [SlidingSyncResult .Extensions .TypingExtension ]:
2689+ """Handle Typing Notification extension (MSC3961)
2690+
2691+ Args:
2692+ sync_config: Sync configuration
2693+ actual_lists: Sliding window API. A map of list key to list results in the
2694+ Sliding Sync response.
2695+ actual_room_ids: The actual room IDs in the the Sliding Sync response.
2696+ actual_room_response_map: A map of room ID to room results in the the
2697+ Sliding Sync response.
2698+ account_data_request: The account_data extension from the request
2699+ to_token: The point in the stream to sync up to.
2700+ from_token: The point in the stream to sync from.
2701+ """
2702+ # Skip if the extension is not enabled
2703+ if not typing_request .enabled :
2704+ return None
2705+
2706+ relevant_room_ids = self .find_relevant_room_ids_for_extension (
2707+ requested_lists = typing_request .lists ,
2708+ requested_room_ids = typing_request .rooms ,
2709+ actual_lists = actual_lists ,
2710+ actual_room_ids = actual_room_ids ,
2711+ )
2712+
2713+ room_id_to_typing_map : Dict [str , JsonMapping ] = {}
2714+ if len (relevant_room_ids ) > 0 :
2715+ # Note: We don't need to take connection tracking into account for typing
2716+ # notifications because they'll get anything still relevant and hasn't timed
2717+ # out when the room comes into range. We consider the gap where the room
2718+ # fell out of range, as long enough for any typing notifications to have
2719+ # timed out (it's not worth the 30 seconds of data we may have missed).
2720+ typing_source = self .event_sources .sources .typing
2721+ typing_notifications , _ = await typing_source .get_new_events (
2722+ user = sync_config .user ,
2723+ from_key = (from_token .stream_token .typing_key if from_token else 0 ),
2724+ to_key = to_token .typing_key ,
2725+ # This is a dummy value and isn't used in the function
2726+ limit = 0 ,
2727+ room_ids = relevant_room_ids ,
2728+ is_guest = False ,
2729+ )
2730+
2731+ for typing_notification in typing_notifications :
2732+ # These fields should exist for every typing notification
2733+ room_id = typing_notification ["room_id" ]
2734+ type = typing_notification ["type" ]
2735+ content = typing_notification ["content" ]
2736+
2737+ room_id_to_typing_map [room_id ] = {"type" : type , "content" : content }
2738+
2739+ return SlidingSyncResult .Extensions .TypingExtension (
2740+ room_id_to_typing_map = room_id_to_typing_map ,
2741+ )
2742+
26622743
26632744class HaveSentRoomFlag (Enum ):
26642745 """Flag for whether we have sent the room down a sliding sync connection.
0 commit comments