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

Commit d8d0271

Browse files
Send device list updates to application services (MSC3202) - part 1 (#11881)
Co-authored-by: Patrick Cloke <[email protected]>
1 parent 2fc15ac commit d8d0271

File tree

15 files changed

+490
-82
lines changed

15 files changed

+490
-82
lines changed

changelog.d/11881.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Send device list changes to application services as specified by [MSC3202](https://github.com/matrix-org/matrix-spec-proposals/pull/3202), using unstable prefixes. The `msc3202_transaction_extensions` experimental homeserver config option must be enabled and `org.matrix.msc3202: true` must be present in the application service registration file for device list changes to be sent. The "left" field is currently always empty.

synapse/appservice/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2015, 2016 OpenMarket Ltd
2+
# Copyright 2022 The Matrix.org Foundation C.I.C.
23
#
34
# Licensed under the Apache License, Version 2.0 (the "License");
45
# you may not use this file except in compliance with the License.
@@ -22,7 +23,13 @@
2223

2324
from synapse.api.constants import EventTypes
2425
from synapse.events import EventBase
25-
from synapse.types import GroupID, JsonDict, UserID, get_domain_from_id
26+
from synapse.types import (
27+
DeviceListUpdates,
28+
GroupID,
29+
JsonDict,
30+
UserID,
31+
get_domain_from_id,
32+
)
2633
from synapse.util.caches.descriptors import _CacheContext, cached
2734

2835
if TYPE_CHECKING:
@@ -400,6 +407,7 @@ def __init__(
400407
to_device_messages: List[JsonDict],
401408
one_time_key_counts: TransactionOneTimeKeyCounts,
402409
unused_fallback_keys: TransactionUnusedFallbackKeys,
410+
device_list_summary: DeviceListUpdates,
403411
):
404412
self.service = service
405413
self.id = id
@@ -408,6 +416,7 @@ def __init__(
408416
self.to_device_messages = to_device_messages
409417
self.one_time_key_counts = one_time_key_counts
410418
self.unused_fallback_keys = unused_fallback_keys
419+
self.device_list_summary = device_list_summary
411420

412421
async def send(self, as_api: "ApplicationServiceApi") -> bool:
413422
"""Sends this transaction using the provided AS API interface.
@@ -424,6 +433,7 @@ async def send(self, as_api: "ApplicationServiceApi") -> bool:
424433
to_device_messages=self.to_device_messages,
425434
one_time_key_counts=self.one_time_key_counts,
426435
unused_fallback_keys=self.unused_fallback_keys,
436+
device_list_summary=self.device_list_summary,
427437
txn_id=self.id,
428438
)
429439

synapse/appservice/api.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Copyright 2015, 2016 OpenMarket Ltd
2+
# Copyright 2022 The Matrix.org Foundation C.I.C.
23
#
34
# Licensed under the Apache License, Version 2.0 (the "License");
45
# you may not use this file except in compliance with the License.
@@ -27,7 +28,7 @@
2728
from synapse.events import EventBase
2829
from synapse.events.utils import SerializeEventConfig, serialize_event
2930
from synapse.http.client import SimpleHttpClient
30-
from synapse.types import JsonDict, ThirdPartyInstanceID
31+
from synapse.types import DeviceListUpdates, JsonDict, ThirdPartyInstanceID
3132
from synapse.util.caches.response_cache import ResponseCache
3233

3334
if TYPE_CHECKING:
@@ -225,6 +226,7 @@ async def push_bulk(
225226
to_device_messages: List[JsonDict],
226227
one_time_key_counts: TransactionOneTimeKeyCounts,
227228
unused_fallback_keys: TransactionUnusedFallbackKeys,
229+
device_list_summary: DeviceListUpdates,
228230
txn_id: Optional[int] = None,
229231
) -> bool:
230232
"""
@@ -268,6 +270,7 @@ async def push_bulk(
268270
}
269271
)
270272

273+
# TODO: Update to stable prefixes once MSC3202 completes FCP merge
271274
if service.msc3202_transaction_extensions:
272275
if one_time_key_counts:
273276
body[
@@ -277,6 +280,11 @@ async def push_bulk(
277280
body[
278281
"org.matrix.msc3202.device_unused_fallback_keys"
279282
] = unused_fallback_keys
283+
if device_list_summary:
284+
body["org.matrix.msc3202.device_lists"] = {
285+
"changed": list(device_list_summary.changed),
286+
"left": list(device_list_summary.left),
287+
}
280288

281289
try:
282290
await self.put_json(

synapse/appservice/scheduler.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
from synapse.logging.context import run_in_background
7373
from synapse.metrics.background_process_metrics import run_as_background_process
7474
from synapse.storage.databases.main import DataStore
75-
from synapse.types import JsonDict
75+
from synapse.types import DeviceListUpdates, JsonDict
7676
from synapse.util import Clock
7777

7878
if TYPE_CHECKING:
@@ -122,6 +122,7 @@ def enqueue_for_appservice(
122122
events: Optional[Collection[EventBase]] = None,
123123
ephemeral: Optional[Collection[JsonDict]] = None,
124124
to_device_messages: Optional[Collection[JsonDict]] = None,
125+
device_list_summary: Optional[DeviceListUpdates] = None,
125126
) -> None:
126127
"""
127128
Enqueue some data to be sent off to an application service.
@@ -133,10 +134,18 @@ def enqueue_for_appservice(
133134
to_device_messages: The to-device messages to send. These differ from normal
134135
to-device messages sent to clients, as they have 'to_device_id' and
135136
'to_user_id' fields.
137+
device_list_summary: A summary of users that the application service either needs
138+
to refresh the device lists of, or those that the application service need no
139+
longer track the device lists of.
136140
"""
137141
# We purposefully allow this method to run with empty events/ephemeral
138142
# collections, so that callers do not need to check iterable size themselves.
139-
if not events and not ephemeral and not to_device_messages:
143+
if (
144+
not events
145+
and not ephemeral
146+
and not to_device_messages
147+
and not device_list_summary
148+
):
140149
return
141150

142151
if events:
@@ -147,6 +156,10 @@ def enqueue_for_appservice(
147156
self.queuer.queued_to_device_messages.setdefault(appservice.id, []).extend(
148157
to_device_messages
149158
)
159+
if device_list_summary:
160+
self.queuer.queued_device_list_summaries.setdefault(
161+
appservice.id, []
162+
).append(device_list_summary)
150163

151164
# Kick off a new application service transaction
152165
self.queuer.start_background_request(appservice)
@@ -169,6 +182,8 @@ def __init__(
169182
self.queued_ephemeral: Dict[str, List[JsonDict]] = {}
170183
# dict of {service_id: [to_device_message_json]}
171184
self.queued_to_device_messages: Dict[str, List[JsonDict]] = {}
185+
# dict of {service_id: [device_list_summary]}
186+
self.queued_device_list_summaries: Dict[str, List[DeviceListUpdates]] = {}
172187

173188
# the appservices which currently have a transaction in flight
174189
self.requests_in_flight: Set[str] = set()
@@ -212,7 +227,35 @@ async def _send_request(self, service: ApplicationService) -> None:
212227
]
213228
del all_to_device_messages[:MAX_TO_DEVICE_MESSAGES_PER_TRANSACTION]
214229

215-
if not events and not ephemeral and not to_device_messages_to_send:
230+
# Consolidate any pending device list summaries into a single, up-to-date
231+
# summary.
232+
# Note: this code assumes that in a single DeviceListUpdates, a user will
233+
# never be in both "changed" and "left" sets.
234+
device_list_summary = DeviceListUpdates()
235+
for summary in self.queued_device_list_summaries.get(service.id, []):
236+
# For every user in the incoming "changed" set:
237+
# * Remove them from the existing "left" set if necessary
238+
# (as we need to start tracking them again)
239+
# * Add them to the existing "changed" set if necessary.
240+
device_list_summary.left.difference_update(summary.changed)
241+
device_list_summary.changed.update(summary.changed)
242+
243+
# For every user in the incoming "left" set:
244+
# * Remove them from the existing "changed" set if necessary
245+
# (we no longer need to track them)
246+
# * Add them to the existing "left" set if necessary.
247+
device_list_summary.changed.difference_update(summary.left)
248+
device_list_summary.left.update(summary.left)
249+
self.queued_device_list_summaries.clear()
250+
251+
if (
252+
not events
253+
and not ephemeral
254+
and not to_device_messages_to_send
255+
# DeviceListUpdates is True if either the 'changed' or 'left' sets have
256+
# at least one entry, otherwise False
257+
and not device_list_summary
258+
):
216259
return
217260

218261
one_time_key_counts: Optional[TransactionOneTimeKeyCounts] = None
@@ -240,6 +283,7 @@ async def _send_request(self, service: ApplicationService) -> None:
240283
to_device_messages_to_send,
241284
one_time_key_counts,
242285
unused_fallback_keys,
286+
device_list_summary,
243287
)
244288
except Exception:
245289
logger.exception("AS request failed")
@@ -322,6 +366,7 @@ async def send(
322366
to_device_messages: Optional[List[JsonDict]] = None,
323367
one_time_key_counts: Optional[TransactionOneTimeKeyCounts] = None,
324368
unused_fallback_keys: Optional[TransactionUnusedFallbackKeys] = None,
369+
device_list_summary: Optional[DeviceListUpdates] = None,
325370
) -> None:
326371
"""
327372
Create a transaction with the given data and send to the provided
@@ -336,6 +381,7 @@ async def send(
336381
appservice devices in the transaction.
337382
unused_fallback_keys: Lists of unused fallback keys for relevant
338383
appservice devices in the transaction.
384+
device_list_summary: The device list summary to include in the transaction.
339385
"""
340386
try:
341387
txn = await self.store.create_appservice_txn(
@@ -345,6 +391,7 @@ async def send(
345391
to_device_messages=to_device_messages or [],
346392
one_time_key_counts=one_time_key_counts or {},
347393
unused_fallback_keys=unused_fallback_keys or {},
394+
device_list_summary=device_list_summary or DeviceListUpdates(),
348395
)
349396
service_is_up = await self._is_service_up(service)
350397
if service_is_up:

synapse/config/appservice.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ def _load_appservice(
170170
# When enabled, appservice transactions contain the following information:
171171
# - device One-Time Key counts
172172
# - device unused fallback key usage states
173+
# - device list changes
173174
msc3202_transaction_extensions = as_info.get("org.matrix.msc3202", False)
174175
if not isinstance(msc3202_transaction_extensions, bool):
175176
raise ValueError(

synapse/config/experimental.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ def read_config(self, config: JsonDict, **kwargs):
5959
"msc3202_device_masquerading", False
6060
)
6161

62-
# Portion of MSC3202 related to transaction extensions:
63-
# sending one-time key counts and fallback key usage to application services.
62+
# The portion of MSC3202 related to transaction extensions:
63+
# sending device list changes, one-time key counts and fallback key
64+
# usage to application services.
6465
self.msc3202_transaction_extensions: bool = experimental.get(
6566
"msc3202_transaction_extensions", False
6667
)

0 commit comments

Comments
 (0)