diff --git a/synapse/events/utils.py b/synapse/events/utils.py
index 9386fa29dd..7120062127 100644
--- a/synapse/events/utils.py
+++ b/synapse/events/utils.py
@@ -26,6 +26,7 @@ from typing import (
Union,
)
+import attr
from frozendict import frozendict
from synapse.api.constants import EventContentFields, EventTypes, RelationTypes
@@ -37,7 +38,8 @@ from synapse.util.frozenutils import unfreeze
from . import EventBase
if TYPE_CHECKING:
- from synapse.storage.databases.main.relations import BundledAggregations
+ from synapse.handlers.relations import BundledAggregations
+ from synapse.server import HomeServer
# Split strings on "." but not "\." This uses a negative lookbehind assertion for '\'
@@ -303,29 +305,37 @@ def format_event_for_client_v2_without_room_id(d: JsonDict) -> JsonDict:
return d
+@attr.s(slots=True, frozen=True, auto_attribs=True)
+class SerializeEventConfig:
+ as_client_event: bool = True
+ # Function to convert from federation format to client format
+ event_format: Callable[[JsonDict], JsonDict] = format_event_for_client_v1
+ # ID of the user's auth token - used for namespacing of transaction IDs
+ token_id: Optional[int] = None
+ # List of event fields to include. If empty, all fields will be returned.
+ only_event_fields: Optional[List[str]] = None
+ # Some events can have stripped room state stored in the `unsigned` field.
+ # This is required for invite and knock functionality. If this option is
+ # False, that state will be removed from the event before it is returned.
+ # Otherwise, it will be kept.
+ include_stripped_room_state: bool = False
+
+
+_DEFAULT_SERIALIZE_EVENT_CONFIG = SerializeEventConfig()
+
+
def serialize_event(
e: Union[JsonDict, EventBase],
time_now_ms: int,
*,
- as_client_event: bool = True,
- event_format: Callable[[JsonDict], JsonDict] = format_event_for_client_v1,
- token_id: Optional[str] = None,
- only_event_fields: Optional[List[str]] = None,
- include_stripped_room_state: bool = False,
+ config: SerializeEventConfig = _DEFAULT_SERIALIZE_EVENT_CONFIG,
) -> JsonDict:
"""Serialize event for clients
Args:
e
time_now_ms
- as_client_event
- event_format
- token_id
- only_event_fields
- include_stripped_room_state: Some events can have stripped room state
- stored in the `unsigned` field. This is required for invite and knock
- functionality. If this option is False, that state will be removed from the
- event before it is returned. Otherwise, it will be kept.
+ config: Event serialization config
Returns:
The serialized event dictionary.
@@ -348,11 +358,11 @@ def serialize_event(
if "redacted_because" in e.unsigned:
d["unsigned"]["redacted_because"] = serialize_event(
- e.unsigned["redacted_because"], time_now_ms, event_format=event_format
+ e.unsigned["redacted_because"], time_now_ms, config=config
)
- if token_id is not None:
- if token_id == getattr(e.internal_metadata, "token_id", None):
+ if config.token_id is not None:
+ if config.token_id == getattr(e.internal_metadata, "token_id", None):
txn_id = getattr(e.internal_metadata, "txn_id", None)
if txn_id is not None:
d["unsigned"]["transaction_id"] = txn_id
@@ -361,13 +371,14 @@ def serialize_event(
# that are meant to provide metadata about a room to an invitee/knocker. They are
# intended to only be included in specific circumstances, such as down sync, and
# should not be included in any other case.
- if not include_stripped_room_state:
+ if not config.include_stripped_room_state:
d["unsigned"].pop("invite_room_state", None)
d["unsigned"].pop("knock_room_state", None)
- if as_client_event:
- d = event_format(d)
+ if config.as_client_event:
+ d = config.event_format(d)
+ only_event_fields = config.only_event_fields
if only_event_fields:
if not isinstance(only_event_fields, list) or not all(
isinstance(f, str) for f in only_event_fields
@@ -385,23 +396,26 @@ class EventClientSerializer:
clients.
"""
+ def __init__(self, hs: "HomeServer"):
+ self._msc3440_enabled = hs.config.experimental.msc3440_enabled
+
def serialize_event(
self,
event: Union[JsonDict, EventBase],
time_now: int,
*,
+ config: SerializeEventConfig = _DEFAULT_SERIALIZE_EVENT_CONFIG,
bundle_aggregations: Optional[Dict[str, "BundledAggregations"]] = None,
- **kwargs: Any,
) -> JsonDict:
"""Serializes a single event.
Args:
event: The event being serialized.
time_now: The current time in milliseconds
+ config: Event serialization config
bundle_aggregations: Whether to include the bundled aggregations for this
event. Only applies to non-state events. (State events never include
bundled aggregations.)
- **kwargs: Arguments to pass to `serialize_event`
Returns:
The serialized event
@@ -410,7 +424,7 @@ class EventClientSerializer:
if not isinstance(event, EventBase):
return event
- serialized_event = serialize_event(event, time_now, **kwargs)
+ serialized_event = serialize_event(event, time_now, config=config)
# Check if there are any bundled aggregations to include with the event.
if bundle_aggregations:
@@ -419,6 +433,7 @@ class EventClientSerializer:
self._inject_bundled_aggregations(
event,
time_now,
+ config,
bundle_aggregations[event.event_id],
serialized_event,
)
@@ -456,6 +471,7 @@ class EventClientSerializer:
self,
event: EventBase,
time_now: int,
+ config: SerializeEventConfig,
aggregations: "BundledAggregations",
serialized_event: JsonDict,
) -> None:
@@ -466,6 +482,7 @@ class EventClientSerializer:
time_now: The current time in milliseconds
aggregations: The bundled aggregation to serialize.
serialized_event: The serialized event which may be modified.
+ config: Event serialization config
"""
serialized_aggregations = {}
@@ -493,8 +510,8 @@ class EventClientSerializer:
thread = aggregations.thread
# Don't bundle aggregations as this could recurse forever.
- serialized_latest_event = self.serialize_event(
- thread.latest_event, time_now, bundle_aggregations=None
+ serialized_latest_event = serialize_event(
+ thread.latest_event, time_now, config=config
)
# Manually apply an edit, if one exists.
if thread.latest_edit:
@@ -502,33 +519,53 @@ class EventClientSerializer:
thread.latest_event, serialized_latest_event, thread.latest_edit
)
- serialized_aggregations[RelationTypes.THREAD] = {
+ thread_summary = {
"latest_event": serialized_latest_event,
"count": thread.count,
"current_user_participated": thread.current_user_participated,
}
+ serialized_aggregations[RelationTypes.THREAD] = thread_summary
+ if self._msc3440_enabled:
+ serialized_aggregations[RelationTypes.UNSTABLE_THREAD] = thread_summary
# Include the bundled aggregations in the event.
if serialized_aggregations:
- serialized_event["unsigned"].setdefault("m.relations", {}).update(
- serialized_aggregations
- )
+ # There is likely already an "unsigned" field, but a filter might
+ # have stripped it off (via the event_fields option). The server is
+ # allowed to return additional fields, so add it back.
+ serialized_event.setdefault("unsigned", {}).setdefault(
+ "m.relations", {}
+ ).update(serialized_aggregations)
def serialize_events(
- self, events: Iterable[Union[JsonDict, EventBase]], time_now: int, **kwargs: Any
+ self,
+ events: Iterable[Union[JsonDict, EventBase]],
+ time_now: int,
+ *,
+ config: SerializeEventConfig = _DEFAULT_SERIALIZE_EVENT_CONFIG,
+ bundle_aggregations: Optional[Dict[str, "BundledAggregations"]] = None,
) -> List[JsonDict]:
"""Serializes multiple events.
Args:
event
time_now: The current time in milliseconds
- **kwargs: Arguments to pass to `serialize_event`
+ config: Event serialization config
+ bundle_aggregations: Whether to include the bundled aggregations for this
+ event. Only applies to non-state events. (State events never include
+ bundled aggregations.)
Returns:
The list of serialized events
"""
return [
- self.serialize_event(event, time_now=time_now, **kwargs) for event in events
+ self.serialize_event(
+ event,
+ time_now,
+ config=config,
+ bundle_aggregations=bundle_aggregations,
+ )
+ for event in events
]
|