Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSC2675: Serverside aggregations of message relationships #2675

Merged
merged 109 commits into from
Jan 15, 2022
Merged
Changes from 3 commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
c9794d4
initial version of serverside aggregations proposal
uhoreg Jul 7, 2020
03ce398
fix MSC numbers
uhoreg Jul 7, 2020
e5d133b
clarification
uhoreg Jun 2, 2021
2d36c36
add e2ee section from 2674 here, as it is only needed for server-side…
bwindels Jul 1, 2021
8eec329
move edge case wrt to calling /context on a relation here from 2674
bwindels Jul 5, 2021
343afff
fix typo
bwindels Nov 23, 2021
8e6c0a3
clarify which APIs should bundle relations
bwindels Nov 23, 2021
d5bbc72
move stale_events over to future extensions section
bwindels Nov 23, 2021
2091034
summarize stale_events and make tone conditional to mark that is not …
bwindels Nov 23, 2021
68c95a4
casing and wording
bwindels Nov 23, 2021
84a4a75
clarify in summary an API for requesting relations is also proposed
bwindels Nov 23, 2021
ca4ceff
remove proposal for batch get event api as is unused and unimplemented
bwindels Nov 24, 2021
7de182c
attempt to clarify relations vs aggregations
bwindels Nov 24, 2021
143a70e
clarify pagination and align it with synapse impl already in the wild
bwindels Nov 25, 2021
ad159f4
conciseness
bwindels Nov 25, 2021
948f7cd
better headers
bwindels Nov 25, 2021
5d7b404
clarify that relations are always returned, contrary to aggregations
bwindels Nov 25, 2021
e8aca3e
document the limitation of the event type not being known in e2ee rooms
bwindels Nov 25, 2021
657617f
specify that redacted relations are not aggregated
bwindels Nov 25, 2021
ffa3995
remove type in (non-binding) example as synapse doesn't do this
bwindels Nov 25, 2021
561cfc3
mention that these are just examples
bwindels Nov 25, 2021
42221ab
clarify that this is a non-normative example
bwindels Nov 25, 2021
037cab3
Update proposals/2675-aggregations-server.md
bwindels Nov 25, 2021
d7aa3ed
add http method for endpoint list
bwindels Nov 25, 2021
30cf5f8
line break
bwindels Nov 26, 2021
ee152e0
remove "unbundled relations" term, it's just confusing
bwindels Nov 26, 2021
483224a
some more restructuring of text after changing doc structure
bwindels Nov 26, 2021
16723da
mention original_event for m.replace relations
bwindels Nov 26, 2021
8e532ac
remove dir param as it is unused and unimplemented
bwindels Nov 29, 2021
6cf4d58
clarify that relating pending events should happen by transaction_id
bwindels Nov 29, 2021
b0fbee1
remove unimplemented /aggregations/{eventID}//{eventType}/{key}
bwindels Nov 29, 2021
53a30fc
Update proposals/2675-aggregations-server.md
bwindels Nov 30, 2021
f959119
mention that the server might not be aware of all the relations
bwindels Nov 30, 2021
dead293
clarify that redacted events should still return their relations and …
bwindels Nov 30, 2021
7299a8c
remove /context edge case, it should not be special-cased
bwindels Nov 30, 2021
37db984
Update proposals/2675-aggregations-server.md
bwindels Nov 30, 2021
36fb70b
Update proposals/2675-aggregations-server.md
bwindels Nov 30, 2021
061a104
Update proposals/2675-aggregations-server.md
bwindels Nov 30, 2021
bf9340e
bad example, replies doesn't use relations
bwindels Nov 30, 2021
c8533ec
clarify that we dont bundle discrete events
bwindels Nov 30, 2021
51bc1da
clarify that we dont bundle discrete events, again
bwindels Nov 30, 2021
7559275
improve example
bwindels Nov 30, 2021
c475bb9
clarify this MSC does not use a prefix
bwindels Nov 30, 2021
872d3f5
better english
bwindels Nov 30, 2021
7119947
clarify pagination in example
bwindels Nov 30, 2021
0d7b525
better english
bwindels Nov 30, 2021
764d785
remove contradication: m.reference doesn't support pagination but exa…
bwindels Nov 30, 2021
26376de
double punctuation
bwindels Nov 30, 2021
382bb1d
clarify that only the bundled aggregation limit for truncation can't …
bwindels Nov 30, 2021
a0af573
move e2ee limitation to limitations section
bwindels Nov 30, 2021
2557d23
clarify prefixes
bwindels Nov 30, 2021
c62165a
mention that state events never bundle aggregations
bwindels Dec 2, 2021
e26e465
Update proposals/2675-aggregations-server.md
bwindels Dec 3, 2021
503a569
add that the visibility of relations can derive from that of the target
bwindels Dec 14, 2021
049863b
typsos
bwindels Dec 14, 2021
44d04f4
be more explicit
bwindels Dec 14, 2021
1160b4a
moar rewording
bwindels Dec 14, 2021
0abf2ce
keep related parts together
bwindels Dec 14, 2021
e460a27
don't make a relation invisible because the target event isn't
bwindels Dec 14, 2021
58cb3ef
Update proposals/2675-aggregations-server.md
bwindels Dec 14, 2021
de00362
better words
bwindels Dec 14, 2021
3dcf0ce
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
9d96311
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
541632f
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
a9943c0
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
f97df03
be more precise when clients should ensure the key is shared
bwindels Dec 15, 2021
798835a
mention that ignored users can cause different aggregations for users
bwindels Dec 15, 2021
fe78152
move visibility rule changes to MSC3570
bwindels Dec 15, 2021
17122bf
don't overspecify visibility limitation, allow for unspecified behaviour
bwindels Dec 15, 2021
ba59c43
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
68b302e
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
d182b9a
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
67d60b1
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
71757a4
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
48c3063
move non-normative note to below example
bwindels Dec 15, 2021
f93143b
make rel_type mandatory as the response structure doesn't allow for m…
bwindels Dec 15, 2021
29fcd87
fix typo/thinko
bwindels Dec 15, 2021
6a76e5c
make pagination forward only as there is no use case for backwards
bwindels Dec 15, 2021
f61d8dc
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
c65b467
add non-normative aggregation examples
bwindels Dec 15, 2021
0717a51
Update proposals/2675-aggregations-server.md
bwindels Dec 15, 2021
71e4e5c
use relation type rather than rel_type
bwindels Dec 15, 2021
942cfcb
change trailing slashes remark to event_type, rel_type is mandatory now
bwindels Dec 16, 2021
b680406
reword and split out client-side aggregation section
bwindels Dec 16, 2021
ef57449
rename parent event to target event, the term used elsewhere
bwindels Dec 16, 2021
33471e6
apply suggestion
bwindels Dec 16, 2021
9399b0f
apply suggestion
bwindels Dec 16, 2021
6e8ff69
remove pagination
bwindels Dec 16, 2021
8033733
remove mentions of /aggregations endpoint after removing pagination
bwindels Dec 16, 2021
3b5f05c
add note about not bundling into state events
bwindels Dec 16, 2021
513cd91
restructure headers so more of the aggregations stuff is under section
bwindels Dec 16, 2021
178976a
make rel_type mandatory for /relations and better wording
bwindels Dec 16, 2021
06fcc15
remove confusion that aggregations contain more info than relations
bwindels Dec 16, 2021
ea8ada3
Update proposals/2675-aggregations-server.md
bwindels Dec 16, 2021
6cdf9e7
Update proposals/2675-aggregations-server.md
bwindels Dec 16, 2021
bcf7d15
mention that tokens from /sync, /messages can be used on /relations
bwindels Dec 16, 2021
0114090
try not to be overly prescriptive
bwindels Dec 16, 2021
3c77504
remove edge case of ignoring events without target event, as ignoring…
bwindels Dec 17, 2021
8a3d9b1
clarify limitation for encrypted rooms
bwindels Dec 17, 2021
8460f64
make rel_type optional again for /relations
bwindels Dec 17, 2021
e1e2593
Update proposals/2675-aggregations-server.md
bwindels Jan 7, 2022
e72837a
Update proposals/2675-aggregations-server.md
bwindels Jan 7, 2022
ae20bcd
Update proposals/2675-aggregations-server.md
bwindels Jan 7, 2022
1dbe2f0
Update proposals/2675-aggregations-server.md
bwindels Jan 7, 2022
88892a5
Update proposals/2675-aggregations-server.md
bwindels Jan 7, 2022
67fa56e
mention requires auth and rate-limited on /relations
bwindels Jan 7, 2022
be6b1c7
replace hypothetical examples for bundled aggregations with non-norma…
bwindels Jan 10, 2022
8f3a28a
move to MSC 2676 as it's specific to edits
bwindels Jan 10, 2022
ae7dc26
dont repeat how local echo using transaction_id works
bwindels Jan 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
339 changes: 339 additions & 0 deletions proposals/2675-aggregations-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
# MSC2675: Serverside aggregations of message relationships
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@revidee says:

So regarding Handling limited (gappy) syncs: I couldn't find any MSC Proposal handling this, is there any?

IMHO, this is the most important part, since for example:
If we received and event via sync, which may has annotations bundled into it and keep it up-to-date via local aggregation using all future m.relates_to events, but then go offline for a day and come back, without syncing all events in-between, we end up in an incorrect state for the local aggregations of the already received event.

If we would simply use a locally cached copy of the locally aggregated event, it would not match a refreshed version from the server. Thus, in this situation, we can't really trust any local copies of events, except we query all relational events, that happened in-between. This would make "jumping back" to the locally saved event somewhat incorrect (mismatches a fresh copy from the server). And refreshing all locally saved events would diminish any possible traffic savings gained by local caching.

Only case I see in which we could realistically save traffic is an initial sync, with filters enabled to not fetch all m.annotation / ... / events, but include then in consecutive /sync calls. Other than that, this makes locally caching/saving events, in order to not re-query them, a step harder, since we have to be really careful to keep all the local events in sync & up-to-date.

This problem is (imho) rather drastic in bigger rooms with large amounts of daily traffic. When loading older messages we may end up loading batches upon batches of events filled with relational events only until we load a real m.text message that the client could display. (e.g. if many reactions have happened) But we cannot exclude them, if we choose to locally aggregate already received events, since we might miss some otherwise.

In that light, it almost seems more logical and traffic-saving to discard local copies all together in order to take advantage of aggregations and bundling. In that case, we can always apply a filter for the events received in /sync and can be sure that the data received is up-to-date. (after loading/paginating through the /aggregations endpoint if there is a next_batch present)

Am I missing something obvious here, or am I correct that making local copies in combination with this MSC seems like a pain?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So regarding Handling limited (gappy) syncs: I couldn't find any MSC Proposal handling this, is there any?

Correct, there is currently not MSC which proposes this behavior.

IMHO, this is the most important part, since for example:
If we received and event via sync, which may has annotations bundled into it and keep it up-to-date via local aggregation using all future m.relates_to events, but then go offline for a day and come back, without syncing all events in-between, we end up in an incorrect state for the local aggregations of the already received event.

Correct, the local client might not know some of the related messages without filling the entire gap. (Note that I don't think this is any different than if only local aggregation was done -- so I'm unsure if this MSC really changes that behavior.)

If we would simply use a locally cached copy of the locally aggregated event, it would not match a refreshed version from the server. Thus, in this situation, we can't really trust any local copies of events, except we query all relational events, that happened in-between. This would make "jumping back" to the locally saved event somewhat incorrect (mismatches a fresh copy from the server). And refreshing all locally saved events would diminish any possible traffic savings gained by local caching.

I think this is pretty much what the gappy syncs issue in the MSC describes. I'm unsure what current clients do -- @gsouquet do you know if Element Web does anything special in this case?

Only case I see in which we could realistically save traffic is an initial sync, with filters enabled to not fetch all m.annotation / ... / events, but include then in consecutive /sync calls. Other than that, this makes locally caching/saving events, in order to not re-query them, a step harder, since we have to be really careful to keep all the local events in sync & up-to-date.

This might work, but I doubt the savings in traffic would be the tremendous, unless the majority of your events are annotations, specifically. (I think you would still want to fetch references / edits / threads since the aggregation of those somewhat assumes you're going to fetch the full event anyway).

In that light, it almost seems more logical and traffic-saving to discard local copies all together in order to take advantage of aggregations and bundling. In that case, we can always apply a filter for the events received in /sync and can be sure that the data received is up-to-date. (after loading/paginating through the /aggregations endpoint if there is a next_batch present)

Note that the /aggregations endpoint was removed from this MSC (and from Synapse). MSC3571 includes the bits split out of MSC2675.

Am I missing something obvious here, or am I correct that making local copies in combination with this MSC seems like a pain?

I don't think you're missing anything obvious, but maybe @gsouquet has some ideas of how the clients deal with this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for raising this @revidee

Just like @clokep said, I believe you're pretty much on point and your assessment of the situation is pretty correct.

Besides threads, Element Web does not use bundled aggregation.

If we take the example of reactions, the bundled aggregations does not give you enough information to generate the user interface that Element wants to provide. To be able to do that you will need a complete list of the annotations, which is not practical and comes with quite a big network overhead

The way that threads went around this problem is by providing a set of information to render the initial event tile without having to fetch all events that belong to a thread (we're currently giving the number of replies to a thread, whether the logged in user has participated to it, and the last event of that thread). That means that client only have to fetch the root event of that thread to refresh the bundled relationship on app load.
This is more practical but comes at the cost of slightly coupling your server implementation with your UI

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alphapapa says:

@ara4n @turt2live I guess it's pointless to comment here since the proposal has been merged, but I feel like chiming in, as a client developer:

I've been reluctant to implement anything not yet in a released version of the spec, but in my client I did implement support for reaction annotations, since they're so widely used in Element and other clients already. But now I'm seeing my client not render annotations in some cases, which I've found is because the server is now aggregating some of them.

I understand the motivations for this proposal, but at the same time, it increases the complexity of developing clients: rather than simply handling each reaction annotation event, a client must handle both aggregated reaction annotations and individual reaction annotation events. This means that, when an individual event comes in, I have to emulate the server-side aggregation in my client, adding it to the annotated event's unsigned data myself, so that I can still have a single path for rendering an event.

Fundamentally, this means that a single logical action (reacting to a message) may now be represented in a room's timeline in two different ways: as individual events, or as aggregations of metadata on the annotated event. So this does not seem to make it easier for clients, but rather, more complicated.

I feel like Matrix would do well to emulate the Zen of Python: there should be one, and preferably only one, way to represent a logical event in a room's timeline. This would greatly simplify implementations on both clients and servers, which would do much to help ensure Matrix's adoption and longevity.

My two cents. Thanks.


It's common to want to send events in Matrix which relate to existing events -
for instance, reactions, edits and even replies/threads.

Clients typically need to track the related events alongside the original
event they relate to, in order to correctly display them. For instance,
reaction events need to be aggregated together by summing and be shown next to
the event they react to; edits need to be aggregated together by replacing the
original event and subsequent edits; replies need to be indented after the
message they respond to, etc.

It is possible to treat relations as normal events and aggregate them
clientside, but to do so comprehensively could be very resource intensive, as
the client would need to spider all possible events in a room to find
relationships and maintain an correct view.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

Instead, this proposal seeks to solve this problem by defining APIs to let the
server calculate the aggregations on behalf of the client, and so bundle the
related events with the original event where appropriate.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

This proposal is one in a series of proposals that defines a mechanism for
events to relate to each other. Together, these proposals replace
[MSC1849](https://github.com/matrix-org/matrix-doc/pull/1849).

* [MSC2674](https://github.com/matrix-org/matrix-doc/pull/2674) defines a
standard shape for indicating events which relate to other events.
* This proposal defines APIs to let the server calculate the aggregations on
behalf of the client, and so bundle the related events with the original
event where appropriate.
* [MSC2676](https://github.com/matrix-org/matrix-doc/pull/2676) defines how
users can edit messages using this mechanism.
* [MSC2677](https://github.com/matrix-org/matrix-doc/pull/2677) defines how
users can annotate events, such as reacting to events with emoji, using this
mechanism.

## Proposal

### Receiving relations

#### Unbundled relation events

Relations are received during non-gappy incremental syncs (that is, syncs
bwindels marked this conversation as resolved.
Show resolved Hide resolved
called with a `since` token, and that have `limited: false` in the portion of
response for the given room) as normal discrete Matrix events. These are
called "unbundled relation events".

#### Bundled relations

Other than during non-gappy incremental syncs, an aggregate view of relation
events should be bundled into the unsigned data of the event they relate to,
rather than sending un-bundled individual relation events. This is called a
bundled relation (or bundled aggregation), and by sending a summary of the
aggregations, avoids us having to always send lots of individual unbundled
clokep marked this conversation as resolved.
Show resolved Hide resolved
relation events individually to the client.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

Any API which receives events should bundle relations (apart from non-gappy
bwindels marked this conversation as resolved.
Show resolved Hide resolved
incremental syncs), for instance: initial sync, gappy incremental sync,
bwindels marked this conversation as resolved.
Show resolved Hide resolved
/messages and /context.

The bundled relations are grouped according to their `rel_type`, and then
paginated within each group using Matrix's defined pagination idiom of `count`,
`limited` and `chunk` fields - respectively giving the total number of
elements in the list, whether that list has been truncated, and an array of
elements in the list.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

The format of the aggregated value in the bundle depends on the relation type.
clokep marked this conversation as resolved.
Show resolved Hide resolved

`m.reference` list the `event_id` and event `type` of the events which
reference that event.

The formats for other relation types may be defined in the proposals that
define the relation types.

For instance, the below example shows an event with five bundled relations:
three thumbsup reaction annotations, one replace, and one reference.

```json
{
...,
"unsigned": {
"m.relations": {
"m.annotation": {
"chunk": [
{
"type": "m.reaction",
"key": "👍",
"origin_server_ts": 1562763768320,
bwindels marked this conversation as resolved.
Show resolved Hide resolved
"count": 3
bwindels marked this conversation as resolved.
Show resolved Hide resolved
}
],
"limited": false,
"count": 1
bwindels marked this conversation as resolved.
Show resolved Hide resolved
},
"m.reference": {
"chunk": [
{
"type": "m.room.message",
bwindels marked this conversation as resolved.
Show resolved Hide resolved
"event_id": "$some_event_id"
}
],
"limited": false,
"count": 1
},
"m.replace": {
"event_id": "$edit_event_id",
"origin_server_ts": 1562763768320,
"sender": "@bruno1:localhost"
}
}
}
}
```

#### Handling limited (gappy) syncs

For the special case of a gappy incremental sync, many relations (particularly
reactions) may have occurred during the gap. It would be inefficient to send
each one individually to the client, but it would also be inefficient to send
all possible bundled aggregations to the client.

The simplest thing a client can do is to just throw away its history for a
room on seeing a gappy incremental sync, and then re-paginate the history of
the room using /messages in order to get a consistent view of the relations
which may have changed during the gap. However, this is quite inefficient,
and prohibits the client from persisting multiple sections of timeline for a
given room.

Alternatively, the server tells the client the event IDs of events which
predate the gap which received relations during the gap. This means that the
client can invalidate its copy of those events (if any) and then requery them
(including their bundled relations) from the server if/when needed.

The server does this with the new `stale_events` field of each room object
in the sync response. The `stale_events` field lists all the event IDs
prior to the gap which had updated relations during the gap. The event IDs
are grouped by relation type, and limited to N entries for efficiency. N
should be 100. If the number of events with stale relations exceeds N, the
list is marked as `limited` as per the normal Matrix pagination model. We do
not include events referenced by `m.reference` as stale, in favour of more
sophisticated pagination techniques in future. For instance:
bwindels marked this conversation as resolved.
Show resolved Hide resolved

```json
"!roomid:matrix.org": {
"account_data": {},
"ephemeral": {},
"state": {},
"summary": {},
"timeline": {},
"unread_notifications": {},
"stale_events": {
"m.annotations": {
"chunk": [
"$12345676321:matrix.org",
"$12345321432:matrix.org"
],
"limited": false
}
}
}
```

This shows that in the gappy sync response, a given room has two events prior
to the gap which received new annotations during the gap. Therefore if the
client has cached a local copy of those events, it should invalidate them, and
subsequently refresh them as needed.

To refresh events, we need an API to load arbitrary events from the room in
bwindels marked this conversation as resolved.
Show resolved Hide resolved
bulk, which the CS API doesn't currently provide. We propose extending GET
`{roomId}/event/{eventId}` to accept a list of event IDs on the URL, e.g:
bwindels marked this conversation as resolved.
Show resolved Hide resolved

`POST /_matrix/client/r0/rooms/{roomId}/event`
```json
{
"event_ids": [
"$12345676321:matrix.org",
"$12345321432:matrix.org"
]
}
```
bwindels marked this conversation as resolved.
Show resolved Hide resolved

...which returns an array of events with the given IDs.

XXX: Is this implemented in Synapse yet?
bwindels marked this conversation as resolved.
Show resolved Hide resolved

#### Paginating relations and aggregations

A single event can have lots of associated relations, and we do not want to
overload the client by including them all in a bundle. Instead, we provide two
new APIs in order to paginate over the relations, which behave in a similar
way to `/messages`, except using `next_batch` and `prev_batch` names (in line
with `/sync` API). Clients can start paginating either from the earliest or
latest events using the `dir` param.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

The `/relations` API lets you iterate over all the **unbundled** relations
clokep marked this conversation as resolved.
Show resolved Hide resolved
associated with an event in standard topological order. You can optionally
bwindels marked this conversation as resolved.
Show resolved Hide resolved
filter by a given type of relation and event type:

```
GET /_matrix/client/r0/rooms/{roomID}/relations/{eventID}[/{relationType}[/{eventType}]]
bwindels marked this conversation as resolved.
Show resolved Hide resolved
```

```json
{
"chunk": [
{
"type": "m.reaction",
"sender": "...",
"content": { }
}
],
"next_batch": "some_token",
"prev_batch": "some_token"
bwindels marked this conversation as resolved.
Show resolved Hide resolved
}
```

The endpoint does not have any trailing slashes.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

FIXME: we need to spell out that this API should return the original message
when paginating over `m.replace` relations for a given message. Synapse
currently looks to include this as an `original_event` field alongside
`chunk` on all relations, which feels very redundant when we only need it for
edits. Either we specialcase it for edits, or we just have the client go
call /event to grab the contents of the original?
bwindels marked this conversation as resolved.
Show resolved Hide resolved
bwindels marked this conversation as resolved.
Show resolved Hide resolved

The `/aggregations` API lets you iterate over **bundled** relations, and within them.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

To iterate over the bundled relations for an event (optionally filtering by
relation type and target event type):
richvdh marked this conversation as resolved.
Show resolved Hide resolved

```
GET /_matrix/client/r0/rooms/{roomID}/aggregations/{eventID}[/{relationType}][/{eventType}][?filter=id]
bwindels marked this conversation as resolved.
Show resolved Hide resolved
```

```json
{
"chunk": [
{
"type": "m.reaction",
"key": "👍",
"origin_server_ts": 1562763768320,
"count": 5,
}
],
"next_batch": "some_token",
"prev_batch": "some_token"
}
```

The endpoint does not have any trailing slashes.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

Trying to iterate over an relation type which does not use an aggregation key
bwindels marked this conversation as resolved.
Show resolved Hide resolved
(i.e. `m.replace` and `m.reference`) should fail with 400 and error
M_INVALID_REL_TYPE.
clokep marked this conversation as resolved.
Show resolved Hide resolved

To iterate over the unbundled relations within a specific bundled relation, you
bwindels marked this conversation as resolved.
Show resolved Hide resolved
use the following API form, identifying the bundle based on its `key`
(therefore this only applies to `m.annotation`, as it is the only current
`rel_type` which groups relations via `key`).

```
GET /_matrix/client/r0/rooms/{roomID}/aggregations/{eventID}/${relationType}/{eventType}/{key}
```

e.g.

```
GET /_matrix/client/r0/rooms/!asd:matrix.org/aggregations/$1cd23476/m.annotation/m.reaction/👍
```

```json
{
"chunk": [
{
"type": "m.reaction",
"sender": "...",
"content": { }
},
],
"next_batch": "some_token",
"prev_batch": "some_token"
}
```

### Redactions
bwindels marked this conversation as resolved.
Show resolved Hide resolved

Trying to call `/relations` or `/aggregations` on a redacted message must return
a 404.
bwindels marked this conversation as resolved.
Show resolved Hide resolved

### Local echo

As clients only receive unbundled events through /sync, they need to locally
aggregate these unbundled events for their parent event, on top of any
server-side aggregation that might have already happened, to get a complete
picture of the aggregated relations for a given parent event, as a client
might not be aware of all relations for an event. Local aggregation should
thus also take the `m.relation` data in the `unsigned` of the parent event
into account if it has been sent already. The aggregation algorithm is the
same as the one described here for the server.

For the best possible user experience, clients should also include unsent
relations into the local aggregation. When adding a relation to the send
queue, clients should locally aggregate it into the relations of the parent
event, ideally regardless of the parent event having an `event_id` already or
bwindels marked this conversation as resolved.
Show resolved Hide resolved
still being pending. If the client gives up on sending the relation for some
reason, the relation should be de-aggregated from the relations of the parent
event. If the client offers the user a possibility of manually retrying to
send the relation, it should be re-aggregated when the user does so.

De-aggregating a relation refers to rerunning the aggregation for a given
parent event while not considering the de-aggregated event any more.

Upon receiving the remote echo for any relations, a client is likely to remove
the pending event from the send queue. Here, it should also de-aggregate the
pending event from the parent event's relations, and re-aggregate the received
remote event from `/sync` to make sure the local aggregation happens with the
same event data as on the server.

When adding a redaction for a relation to the send queue, the relation
referred to should be de-aggregated from the relations of the target of the
relation. Similar to a relation, when the sending of the redaction fails or
is cancelled, the relation should be aggregated again.

To support creating relations for pending events, clients will need a way for
bwindels marked this conversation as resolved.
Show resolved Hide resolved
events to relate to one another before the `event_id` of the parent event is
known. When the parent event receives its remote echo, the target event id
(`m.relates_to`.`event_id`) of any relations in the send queue will need to be
set the newly received `event_id`.

Particularly, please remember to let users edit unsent messages (as this is a
common case for rapidly fixing a typo in a msg which is still in flight!)
bwindels marked this conversation as resolved.
Show resolved Hide resolved

## Edge cases
bwindels marked this conversation as resolved.
Show resolved Hide resolved

How do you handle ignored users?
bwindels marked this conversation as resolved.
Show resolved Hide resolved
* Information about relations sent from ignored users must never be sent to
the client, either in bundled or unbundled form. This is to let you block
someone from harassing you with emoji reactions (or using edits as a
side-channel to harass you).