-
Notifications
You must be signed in to change notification settings - Fork 399
MSC2677: Annotations and reactions #2677
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
Merged
turt2live
merged 14 commits into
matrix-org:old_master
from
uhoreg:aggregations-reactions
Mar 26, 2023
Merged
Changes from 3 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
4d6ce1b
initial version of reactions proposal
uhoreg 8762524
fix MSC numbers
uhoreg 2e75639
add security consideration
uhoreg 5852b28
remove event type from aggregation grouping criteria because of e2ee
bwindels 121ca14
Apply suggestions from code review
richvdh a0ed8e7
Update intro and add background
richvdh 657a746
Corrections and clarifications to the main text
richvdh dad2b07
Clarify counting rules and interactions with edits
richvdh fba3948
Error code for deduplicating annotations
richvdh 4031ad4
Clarify eligible target events
richvdh 0d146ca
Notes on encryption
richvdh c28c385
Clarify variation-16
richvdh fdbb745
Update 2677-reactions.md
richvdh 749198f
No server-side aggregation for reactions
richvdh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
# MSC2677: Annotations and Reactions | ||
|
||
Users sometimes wish to respond to a message using emojis. When such responses | ||
are grouped visually below the message being reacted to, this provides a | ||
(visually) light-weight way for users to react to messages. | ||
|
||
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. | ||
* [MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675) 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. | ||
* This proposal defines how users can annotate events, such as reacting to | ||
events with emoji, using this mechanism. | ||
|
||
## Proposal | ||
|
||
A new `rel_type` of `m.annotation` is defined for use with the `m.relates_to` | ||
field as defined in | ||
[MSC2674](https://github.com/matrix-org/matrix-doc/pull/2674). This `rel_type` | ||
is intended primarily for handling emoji reactions, these lets you define an | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
event which annotates an existing event. The annotations are typically | ||
presented alongside the event in the timeline. When used, the `m.relates_to` | ||
field also contains a `key` that indicates the annotation being applied. For | ||
example, when reacting with emojis, the `key` contains the emoji being used. | ||
When aggregated (as in | ||
[MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675)), it groups | ||
events together based on their `key` and `type` and returns a `count`. Another | ||
bwindels marked this conversation as resolved.
Show resolved
Hide resolved
|
||
usage of an annotation is e.g. for bots, who could use annotations to report | ||
the success/failure or progress of a command. | ||
|
||
A new message type `m.reaction` is defined to indicate that a user is reacting | ||
to a message. | ||
|
||
For example, an `m.reaction` event which annotates an existing event with a 👍 | ||
looks like: | ||
|
||
```json | ||
{ | ||
"type": "m.reaction", | ||
"content": { | ||
"m.relates_to": { | ||
"rel_type": "m.annotation", | ||
"event_id": "$some_event_id", | ||
"key": "👍" | ||
} | ||
} | ||
} | ||
``` | ||
|
||
When sending emoji reactions, the `key` field should include the colourful | ||
variation-16 when applicable. | ||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Push rules | ||
|
||
Since reactions are considered "metadata" that annotate an existing event, they | ||
should not by default trigger notifications. Thus a new default override rule | ||
is to be added that ignores reaction events: | ||
|
||
```json | ||
{ | ||
"rule_id": ".m.rule.reaction", | ||
"default": true, | ||
"enabled": true, | ||
"conditions": [ | ||
{ | ||
"kind": "event_match", | ||
"key": "type", | ||
"pattern": "m.reaction" | ||
} | ||
], | ||
"actions": [ | ||
"dont_notify" | ||
] | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
``` | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Server support | ||
|
||
When an annotation event is sent to clients via `/sync`, a new field | ||
`annotation_count` is provided in the `unsigned` field of the event, calculated | ||
by the server to provide the current absolute count of the given annotation key | ||
as of that point of the event, to avoid the client having to accurately track | ||
the absolute value itself. | ||
|
||
XXX: is this implemented in Synapse yet? | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
For instance, an incremental sync might include the following: | ||
|
||
```json | ||
{ | ||
"type": "m.reaction", | ||
"sender": "@matthew:matrix.org", | ||
"content": { | ||
"m.relates_to": { | ||
"rel_type": "m.annotation", | ||
"event_id": "$some_event_id", | ||
"key": "👍" | ||
} | ||
}, | ||
"unsigned": { | ||
"annotation_count": 1234, | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
``` | ||
|
||
...to indicate that Matthew just thumbsupped a given event, bringing the current | ||
total to 1234 thumbsups. | ||
|
||
#### Bundled relations | ||
|
||
When annotations are bundled according to the [Bundled relations section of | ||
MSC2675](https://github.com/uhoreg/matrix-doc/blob/aggregations-helpers/proposals/2675-aggregations-server.md#bundled-relations), | ||
the aggregated value in the bundle provides the `type` of the relation event, | ||
the aggregation `key`, the `origin_server_ts` of the first reaction to that | ||
event, and the `count` of the number of annotations of that `type` and `key` | ||
which reference that event. | ||
|
||
For instance, the below example shows an event with five bundled relations: | ||
three thumbsup reaction annotations, and two thumbsdown reaction annotations. | ||
|
||
```json | ||
{ | ||
..., | ||
"unsigned": { | ||
"m.relations": { | ||
"m.annotation": { | ||
"chunk": [ | ||
{ | ||
"type": "m.reaction", | ||
"key": "👍", | ||
"origin_server_ts": 1562763768320, | ||
"count": 3 | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
{ | ||
"type": "m.reaction", | ||
"key": "👎", | ||
"origin_server_ts": 1562763768320, | ||
"count": 2 | ||
} | ||
], | ||
"limited": false, | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"count": 2 | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
XXX: is the example correct? | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
### Redactions | ||
|
||
When a message using a `rel_type` of `m.annotation` is redacted, this removes | ||
the annotation from the message. | ||
|
||
## Edge Cases | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
How do you stop people reacting more than once with the same key? | ||
1. You error with 400 (M_INVALID_REL_TYPE) if they try to react twice with the same key, locally | ||
2. You flatten duplicate reactions received over federation from the same user | ||
when calculating your local aggregations | ||
3. You don't pass duplicate reactions received over federation to your local user. | ||
4. XXX: does synapse do 2 & 3 yet? | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Can you [edit](https://github.com/matrix-org-matrix-doc/pull/2676) a reaction? | ||
* It feels reasonable to say "if you want to edit a reaction, redact it and resend". | ||
`rel_type` is immutable, much like `type`. | ||
|
||
Can you react to a reaction? | ||
* Yes, at the protocol level. But you shouldn't expect clients to do anything | ||
useful with it. | ||
uhoreg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
What happens when you react to an edit? | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* You should be able to, but the reaction should be attributed to the edit (or | ||
its contents) rather than the message as a whole. | ||
* Edits gather their own reactions, and the clients should display | ||
the reactions on the most recent edit. | ||
* This provides a social pressure to get your edits in quickly before there | ||
are many reactions, otherwise the reactions will get lost. | ||
* And it avoids us randomly aggregating reactions to potentially very | ||
different contents of messages. | ||
|
||
Which message types are reactable? | ||
* Any. But perhaps we should provide some UI best practice guidelines: | ||
* `m.room.message` must be reactable | ||
* `m.sticker` too | ||
* ...but anything else may not be rendered. | ||
richvdh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Alternatives | ||
|
||
### Extended annotation use case | ||
|
||
In future it might be useful to be able to annotate events with more | ||
information, some examples include: | ||
|
||
* Annotate commit/PR notification messages with their associated CI state, e.g. | ||
pending/passed/failed. | ||
* If a user issues a command to a bot, e.g. `!deploy-site` the bot could | ||
annotate that event with current state, like "acknowledged", | ||
"redeploying...", "success", "failed", etc. | ||
* Other use cases...? | ||
|
||
However, this doesn't really work with the proposed grouping, as the aggregation | ||
key wouldn't contain the right information needed to display it (unlike for | ||
reactions). | ||
|
||
One way to potentially support this is to include the events (or a subset of the | ||
event) when grouping, so that clients have enough information to render them. | ||
However this dramatically inceases the size of the parent event if we bundle the | ||
full events inside, even if limit the number we bundle in. To reduce the | ||
overhead the annotation event could include a `m.result` field which gets | ||
included. | ||
|
||
This would look something like the following, where the annotation is: | ||
|
||
```json | ||
{ | ||
"type": "m.bot_command_response", | ||
"content": { | ||
"m.result": { | ||
"state": "success", | ||
}, | ||
"m.relates_to": { | ||
"type": "m.annotation", | ||
"key": "" | ||
} | ||
} | ||
} | ||
``` | ||
|
||
and gets bundled into an event like: | ||
|
||
```json | ||
{ | ||
"unsigned": { | ||
"m.relations": { | ||
"m.annotation": [ | ||
{ | ||
"type": "m.bot_command_response", | ||
"key": "", | ||
"count": 1, | ||
"chunk": [ | ||
{ | ||
"m.result": { | ||
"state": "success", | ||
}, | ||
} | ||
], | ||
"limited": false, | ||
} | ||
] | ||
} | ||
} | ||
} | ||
``` | ||
|
||
This is something that could be added later on. A few issues with this are: | ||
|
||
* How does this work with E2EE? How do we encrypt the `m.result`? | ||
* We would end up including old annotations that had been superceded, should | ||
these be done via edits instead? | ||
|
||
## Security considerations | ||
|
||
Clients should render reactions that have a long `key` field in a sensible | ||
manner. For example, clients can elide overly-long reactions. | ||
bwindels marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
If using reactions for upvoting/downvoting purposes we would almost certainly want to anonymise the | ||
reactor, at least from other users if not server admins, to avoid retribution problems. | ||
This gives an unfair advantage to people who run their own servers however and | ||
can cheat and deanonymise (and publish) reactor details. In practice, reactions may | ||
not be best used for upvote/downvote as at the unbundled level they are intrinsically | ||
private data. | ||
|
||
Or in a MSC1228 world... we could let users join the room under an anonymous | ||
persona from a big public server in order to vote? However, such anonymous personae | ||
would lack any reputation data. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.