|
| 1 | +# MSC2677: Annotations and Reactions |
| 2 | + |
| 3 | +Users sometimes wish to respond to a message using emojis. When such responses |
| 4 | +are grouped visually below the message being reacted to, this provides a |
| 5 | +(visually) light-weight way for users to react to messages. |
| 6 | + |
| 7 | +This proposal was originally part of [MSC1849](https://github.com/matrix-org/matrix-doc/pull/1849). |
| 8 | + |
| 9 | +## Background |
| 10 | + |
| 11 | +As with [message |
| 12 | +edits](https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2676-message-editing.md#background), |
| 13 | +support for reactions were landed in the Element clients and Synapse in May |
| 14 | +2019, following the proposals of |
| 15 | +[MSC1849](https://github.com/matrix-org/matrix-doc/pull/1849) and then |
| 16 | +presented as being "production-ready", despite them not yet having been adopted |
| 17 | +into the Matrix specification. |
| 18 | + |
| 19 | +Again as with edits, the current situation is therefore that client or server |
| 20 | +implementations hoping to interact with Element users must simply follow the |
| 21 | +examples of that implementation. |
| 22 | + |
| 23 | +To rectify the situation, this MSC therefore seeks primarily to formalise the |
| 24 | +status quo. Although there is plenty of scope for improvement, we consider |
| 25 | +that better done in *future* MSCs, based on a shared understanding of the |
| 26 | +*current* implementation. |
| 27 | + |
| 28 | +In short, this MSC prefers fidelity to the current implementations over |
| 29 | +elegance of design. |
| 30 | + |
| 31 | +On the positive side: this MSC is the last part of the former MSC1849 to be |
| 32 | +formalised, and is by far the most significant feature implemented by the |
| 33 | +Element clients which has yet to be specified. |
| 34 | + |
| 35 | +## Proposal |
| 36 | + |
| 37 | +### `m.annotation` event relationship type |
| 38 | + |
| 39 | +A new [event relationship type](https://spec.matrix.org/v1.6/client-server-api/#relationship-types) |
| 40 | +with a `rel_type` of `m.annotation`. |
| 41 | + |
| 42 | +This relationship type is intended primarily for handling emoji reactions, allowing clients to |
| 43 | +send an event which annotates an existing event. |
| 44 | + |
| 45 | +Another potential usage of annotations is for bots, which could use them to |
| 46 | +report the success/failure or progress of a command. |
| 47 | + |
| 48 | +Along with the normal properties `event_id` and `rel_type`, the |
| 49 | +[`m.relates_to`](https://spec.matrix.org/v1.6/client-server-api/#definition-mrelates_to) |
| 50 | +property should contains a `key` that indicates the annotation being |
| 51 | +applied. For example, when reacting with emojis, the `key` contains the emoji |
| 52 | +being used. |
| 53 | + |
| 54 | +An event annotating another with the thumbs-up emoji would therefore have the following `m.relates_to` propperty: |
| 55 | + |
| 56 | +```json |
| 57 | +"m.relates_to": { |
| 58 | + "rel_type": "m.annotation", |
| 59 | + "event_id": "$some_event_id", |
| 60 | + "key": "👍" |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +When sending emoji reactions, the `key` property should include the unicode |
| 65 | +[emoji presentation |
| 66 | +selector](https://www.unicode.org/reports/tr51/#def_emoji_presentation_selector) |
| 67 | +(`\uFE0F`) for codepoints which allow it (see the [emoji variation sequences |
| 68 | +list](https://www.unicode.org/Public/UCD/latest/ucd/emoji/emoji-variation-sequences.txt)). |
| 69 | + |
| 70 | +Any `type` of event is eligible for an annotation, including state events. |
| 71 | + |
| 72 | +### `m.reaction` event type |
| 73 | + |
| 74 | +A new message type `m.reaction` is proposed to indicate that a user is reacting |
| 75 | +to a message. No `content` properties are defined for this event type: it serves |
| 76 | +only to hold a relationship to another event. |
| 77 | + |
| 78 | +For example, an `m.reaction` event which annotates an existing event with a 👍 |
| 79 | +looks like: |
| 80 | + |
| 81 | +```json |
| 82 | +{ |
| 83 | + "type": "m.reaction", |
| 84 | + "content": { |
| 85 | + "m.relates_to": { |
| 86 | + "rel_type": "m.annotation", |
| 87 | + "event_id": "$some_event_id", |
| 88 | + "key": "👍" |
| 89 | + } |
| 90 | + } |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +Since they contain no `content` other than `m.relates_to`, `m.reaction` events |
| 95 | +are normally not encrypted, as there would be no benefit in doing so. (However, |
| 96 | +see [Encrypted reactions](#encrypted-reactions) below.) |
| 97 | + |
| 98 | +### Interation with edited events |
| 99 | + |
| 100 | +It is not considered valid to send an annotation for a [replacement |
| 101 | +event](https://spec.matrix.org/v1.6/client-server-api/#event-replacements) |
| 102 | +(i.e., a message edit event): any reactions should refer to the original |
| 103 | +event. Annotations of replacement events will be ignored according to the rules |
| 104 | +for [counting annotations](#counting-annotations). |
| 105 | + |
| 106 | +As an aside, note that it is not possible to edit a reaction, since replacement |
| 107 | +events do not change `m.relates_to` (see [Applying |
| 108 | +`m.new_content`](https://spec.matrix.org/v1.6/client-server-api/#applying-mnew_content)), |
| 109 | +and there is no other meaningful content within `m.reaction`. If a user wishes |
| 110 | +to change their reaction, the original reaction should be redacted and a new |
| 111 | +one sent in its place. |
| 112 | + |
| 113 | +### Counting annotations |
| 114 | + |
| 115 | +The intention of annotations is that they are counted up, rather than being displayed individually. |
| 116 | + |
| 117 | +Clients must keep count of the number of annotations with a given event `type` |
| 118 | +and annotation `key` they observe for each event; these counts are typically |
| 119 | +presented alongside the event in the timeline. |
| 120 | + |
| 121 | +When performing this count: |
| 122 | + |
| 123 | + * Each event `type` and annotation `key` should normally be counted separately, |
| 124 | + though whether to actually do so is an implementation decision. |
| 125 | + |
| 126 | + * Annotation events sent by [ignored users](https://spec.matrix.org/v1.6/client-server-api/#ignoring-users) |
| 127 | + should be excluded from the count. |
| 128 | + |
| 129 | + * Multiple identical annotations (i.e., with the same event `type` and |
| 130 | + annotation `key`) from the same user (i.e., events with the same `sender`) should |
| 131 | + be treated as a single annotation. |
| 132 | + |
| 133 | + * It is not considered valid to annotate an event which itself has an |
| 134 | + `m.relates_to` with `rel_type: m.annotation` or `rel_type: |
| 135 | + m.replace`. Implementations should ignore any such annotation events. |
| 136 | + |
| 137 | + * When an annotation is redacted, it is removed from the count. |
| 138 | + |
| 139 | +### Push rules |
| 140 | + |
| 141 | +Since reactions are considered "metadata" that annotate an existing event, they |
| 142 | +should not by default trigger notifications. Thus a new [default override |
| 143 | +rule](https://spec.matrix.org/v1.6/client-server-api/#default-override-rules) |
| 144 | +is to be added that ignores reaction events: |
| 145 | + |
| 146 | +```json |
| 147 | +{ |
| 148 | + "rule_id": ".m.rule.reaction", |
| 149 | + "default": true, |
| 150 | + "enabled": true, |
| 151 | + "conditions": [ |
| 152 | + { |
| 153 | + "kind": "event_match", |
| 154 | + "key": "type", |
| 155 | + "pattern": "m.reaction" |
| 156 | + } |
| 157 | + ], |
| 158 | + "actions": [] |
| 159 | +} |
| 160 | +``` |
| 161 | + |
| 162 | +The rule is added between `.m.rule.tombstone` and `.m.rule.room.server_acl`. |
| 163 | + |
| 164 | +(Synapse implementation: [base_rules.rs](https://github.com/matrix-org/synapse/blob/157c571f3e9d3d09cd763405b6a9eb967f2807e7/rust/src/push/base_rules.rs#L216-L229)) |
| 165 | + |
| 166 | +### Server support |
| 167 | + |
| 168 | +#### Avoiding duplicate annotations |
| 169 | + |
| 170 | +Homeservers should prevent users from sending a second annotation for a given |
| 171 | +event with identical event `type` and annotation `key` (unless the first event |
| 172 | +has been redacted). |
| 173 | + |
| 174 | +Attempts to send such an annotation should be rejected with a 400 error and an |
| 175 | +error code of `M_DUPLICATE_ANNOTATION`. |
| 176 | + |
| 177 | +Note that this does not guarantee that duplicate annotations will not arrive |
| 178 | +over federation. Clients and servers are responsible for deduplicating received |
| 179 | +annotations when [counting annotations](#counting-annotations). |
| 180 | + |
| 181 | +#### Server-side aggregation of `m.annotation` relationships |
| 182 | + |
| 183 | +`m.annotation` relationships are *not* [aggregated](https://spec.matrix.org/v1.6/client-server-api/#aggregations) |
| 184 | +by the server. In other words, `m.annotation` is not included in the `m.relations` property. |
| 185 | + |
| 186 | +## Alternatives |
| 187 | + |
| 188 | +### Encrypted reactions |
| 189 | + |
| 190 | +[matrix-spec#660](https://github.com/matrix-org/matrix-spec/issues/660) |
| 191 | +discusses the possibility of encrypting message relationships in general. |
| 192 | + |
| 193 | +Given that reactions do not rely on server-side aggregation support, an easier |
| 194 | +solution to encrypting reactions might be not to use the relationships |
| 195 | +framework at all and instead just use a keys within `m.reaction` events, which |
| 196 | +could then be encrypted. For example, a reaction could instead be formatted as: |
| 197 | + |
| 198 | +```json5 |
| 199 | +{ |
| 200 | + "type": "m.reaction", |
| 201 | + "content": { |
| 202 | + "event_id": "$some_event_id", |
| 203 | + "key": "👍" |
| 204 | + } |
| 205 | +} |
| 206 | +``` |
| 207 | + |
| 208 | +### Extended annotation use case |
| 209 | + |
| 210 | +In future it might be useful to be able to annotate events with more |
| 211 | +information, some examples include: |
| 212 | + |
| 213 | + * Annotate commit/PR notification messages with their associated CI state, e.g. |
| 214 | + pending/passed/failed. |
| 215 | + * If a user issues a command to a bot, e.g. `!deploy-site` the bot could |
| 216 | + annotate that event with current state, like "acknowledged", |
| 217 | + "redeploying...", "success", "failed", etc. |
| 218 | + * Other use cases...? |
| 219 | + |
| 220 | +However, this doesn't really work with the proposed grouping, as the aggregation |
| 221 | +key wouldn't contain the right information needed to display it (unlike for |
| 222 | +reactions). |
| 223 | + |
| 224 | +One way to potentially support this is to include the events (or a subset of the |
| 225 | +event) when grouping, so that clients have enough information to render them. |
| 226 | +However this dramatically inceases the size of the parent event if we bundle the |
| 227 | +full events inside, even if limit the number we bundle in. To reduce the |
| 228 | +overhead the annotation event could include a `m.result` field which gets |
| 229 | +included. |
| 230 | + |
| 231 | +This would look something like the following, where the annotation is: |
| 232 | + |
| 233 | +```json |
| 234 | +{ |
| 235 | + "type": "m.bot_command_response", |
| 236 | + "content": { |
| 237 | + "m.result": { |
| 238 | + "state": "success", |
| 239 | + }, |
| 240 | + "m.relates_to": { |
| 241 | + "type": "m.annotation", |
| 242 | + "key": "" |
| 243 | + } |
| 244 | + } |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +and gets bundled into an event like: |
| 249 | + |
| 250 | +```json |
| 251 | +{ |
| 252 | + "unsigned": { |
| 253 | + "m.relations": { |
| 254 | + "m.annotation": [ |
| 255 | + { |
| 256 | + "type": "m.bot_command_response", |
| 257 | + "key": "", |
| 258 | + "count": 1, |
| 259 | + "chunk": [ |
| 260 | + { |
| 261 | + "m.result": { |
| 262 | + "state": "success", |
| 263 | + }, |
| 264 | + } |
| 265 | + ], |
| 266 | + "limited": false, |
| 267 | + } |
| 268 | + ] |
| 269 | + } |
| 270 | + } |
| 271 | +} |
| 272 | +``` |
| 273 | + |
| 274 | +This is something that could be added later on. A few issues with this are: |
| 275 | + |
| 276 | + * How does this work with E2EE? How do we encrypt the `m.result`? |
| 277 | + * We would end up including old annotations that had been superceded, should |
| 278 | + these be done via edits instead? |
| 279 | + |
| 280 | +## Security considerations |
| 281 | + |
| 282 | +Clients should render reactions that have a long `key` field in a sensible |
| 283 | +manner. For example, clients can elide overly-long reactions. |
| 284 | + |
| 285 | +If using reactions for upvoting/downvoting purposes we would almost certainly want to anonymise the |
| 286 | +reactor, at least from other users if not server admins, to avoid retribution problems. |
| 287 | +This gives an unfair advantage to people who run their own servers however and |
| 288 | +can cheat and deanonymise (and publish) reactor details. In practice, reactions may |
| 289 | +not be best used for upvote/downvote as at the unbundled level they are intrinsically |
| 290 | +private data. |
| 291 | + |
| 292 | +Or in a MSC1228 world... we could let users join the room under an anonymous |
| 293 | +persona from a big public server in order to vote? However, such anonymous personae |
| 294 | +would lack any reputation data. |
0 commit comments