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

MSC1767: Extensible event types & fallback in Matrix (v2) #1767

Merged
merged 45 commits into from
Feb 6, 2023
Merged
Changes from 12 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
6121eee
first cut of MSC1767 for extensible events (replaces MSC1225)
ara4n Jan 1, 2019
ba2c279
tabs->spaces
ara4n Jan 1, 2019
132526c
fix markdown
ara4n Jan 1, 2019
dcedbeb
fix markdown
ara4n Jan 1, 2019
02f257a
GFM needs two spaces after ##?
ara4n Jan 1, 2019
e8d8b94
delta from msc1225
ara4n Jan 1, 2019
2929a66
Merge branch 'master' into matthew/msc1767
turt2live Mar 31, 2021
2ab20c3
2021 refresh
turt2live Mar 31, 2021
d7cb0a9
Refactor to be just text messaging + schema
turt2live Dec 7, 2021
fdd5b84
Update MSC numbers
turt2live Dec 7, 2021
80e8a42
Touchups for FCP
turt2live Jan 12, 2022
0e4631c
*ahem*
turt2live Jan 13, 2022
4afdc32
Rewrite MSC to be understandable?
turt2live Jul 2, 2022
d6046d8
Update proposals/1767-extensible-events.md
turt2live Jul 4, 2022
e05922d
Rewrite MSC, again
turt2live Nov 10, 2022
3af300b
Clarify Andy's blog state
turt2live Nov 10, 2022
5f15b9f
Relax m.markup's requirements
turt2live Nov 10, 2022
0564626
Misc clarifications
turt2live Nov 11, 2022
78d77f7
Revert "Relax m.markup's requirements"
turt2live Nov 10, 2022
16f8f08
Clarify push rules handling
turt2live Nov 11, 2022
13fd844
Fix example
turt2live Nov 11, 2022
b10cc39
Merge branch 'old_master' into matthew/msc1767
turt2live Nov 14, 2022
67d3942
Update extensible events MSCs list
turt2live Nov 15, 2022
516f8da
Update Andy's blog post reference
turt2live Nov 30, 2022
48e0588
Update per review feedback
turt2live Jan 13, 2023
e38c1ea
Cover all the bases
turt2live Jan 13, 2023
d793b56
Clarify mixins are exactly what they're assumed to be
turt2live Jan 16, 2023
4d7e4a6
Clarify fallback and number of datums per event
turt2live Jan 16, 2023
99284b3
Fix description of plain text baseline
turt2live Jan 17, 2023
ed21067
Clarify SCT's role in feature scope & cut "mixed" events (not mixins)
turt2live Jan 17, 2023
7ed4cbc
Fix description of mixins
turt2live Jan 17, 2023
67bb399
Update proposals/1767-extensible-events.md
turt2live Jan 17, 2023
c18b155
Expand on how unknown events are handled
turt2live Jan 17, 2023
d7488b8
Apply suggestions from code review
turt2live Jan 17, 2023
d11a26c
Trim line length post-suggestions
turt2live Jan 17, 2023
38b8878
Mention that extensible events might appear in other room versions early
turt2live Jan 17, 2023
435f172
Remove duplicated unknown parse order being an implementation detail
turt2live Jan 17, 2023
564499f
Note difference between optional content blocks and mixins
turt2live Jan 17, 2023
4bd286e
Apply suggestions from code review
turt2live Jan 18, 2023
3819d92
Update proposals/1767-extensible-events.md
turt2live Jan 30, 2023
6861219
Clarify that clients are still strongly encouraged to validate HTML
turt2live Jan 31, 2023
c5db1a0
Fix mention of mixing legacy event types
turt2live Jan 31, 2023
7381697
Update proposals/1767-extensible-events.md
turt2live Jan 31, 2023
98bc9ad
Link to MSC3765
turt2live Jan 31, 2023
7161196
Rename `m.markup` to `m.text`
turt2live Jan 31, 2023
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
353 changes: 353 additions & 0 deletions proposals/1767-extensible-events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
# MSC1767: Extensible events in Matrix
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved

While events are currently JSON blobs which accept additional metadata appended to them,
there is no formal structure for how to represent this information or interpret it on the
client side.

New events also typically reinvent the wheel rather than reuse existing types, such as in
cases where captions, thumbnails, etc need to be considered. This has further issues of
turt2live marked this conversation as resolved.
Show resolved Hide resolved
clients not knowing how to render these unknown events, leading to mixed compatibility
within the ecosystem.

The above is seriously hindering uptake of new event types in Matrix - whether that’s
richer data types (stickers, location, calendaring, etc) or IOT-style use cases.
It also means we are currently using an underspecified solution for rich
messaging, as there has never been a previous agreed way of a jury rig solution
for rich messaging as we never previously agreed a way of expressing alternate
turt2live marked this conversation as resolved.
Show resolved Hide resolved
formats of displaying the same event.

This proposal introduces a structure for how new events operate and covers the plain
text messaging component of Matrix. Further/future MSCs cover additional functionality
and the remaining messaging components. As of writing, those MSCs are:

* [MSC3488 - Location data](https://github.com/matrix-org/matrix-doc/pull/3488)
* [MSC3381 - Polls](https://github.com/matrix-org/matrix-doc/pull/3381)
* [MSC3245 - Voice messages](https://github.com/matrix-org/matrix-doc/pull/3245)
* [MSC3246 - Audio (non-voice)](https://github.com/matrix-org/matrix-doc/pull/3246)
* [MSC3551 - Files](https://github.com/matrix-org/matrix-doc/pull/3551)
* [MSC3552 - Images and Stickers](https://github.com/matrix-org/matrix-doc/pull/3552)
* [MSC3553 - Videos](https://github.com/matrix-org/matrix-doc/pull/3553)
* [MSC3554 - Translatable text](https://github.com/matrix-org/matrix-doc/pull/3554)

## Proposal

Events continue having a primary type when sending them in Matrix, but the contents
are grouped together into different types. This allows events to easily extend an
event with additional metadata, re-use existing structures, and have the event
renderable on clients which have not special-cased support for the primary type yet.

The names of the keys in the contents are expected to be namespaced, aligned with
event types, though some keys might not be labeled as such for backwards compatibility.

When types are represented as arrays, they are ordered in preference for rendering.
For example, an `m.thumbnail` might have multiple sizes: a client which does not
support/understand thumbnail sizing would use the first available thumbnail rather
than finding the best possible one.

The event types referenced in the event content MUST refer to the same event and
cannot be used to multiplex data into a single event. Relationships between events
turt2live marked this conversation as resolved.
Show resolved Hide resolved
are formed with `m.relates_to` and similar structures. For clarity, and as an example,
this means that an image event refers to exactly one image - an album of images can
be represented as a thread/relationship chain of singular images.

Because events are generally sent in the context of a room, all primary types introduced
turt2live marked this conversation as resolved.
Show resolved Hide resolved
to the Matrix specification are expected to drop their "room" part. For example, instead
of `m.room.event` the type would be `m.event`. Where features have multiple events, such
as polls, the expectation is that they'll declare a relevant namespace like `m.poll.*`.

### Text messaging
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Currently text content can be identified from the `msgtype` on `m.room.message` events:
`m.text`, `m.emote`, and `m.notice` being the values.

All three additionally support HTML through a combination of `format` and `formatted_body`.

When translated to Extensible Events, the primary type now maps to a slightly different
value:
* A `msgtype` of `m.text` would be a primary type of `m.message`
* A `msgtype` of `m.emote` would be a primary type of `m.emote`
* A `msgtype` of `m.notice` would be a primary type of `m.notice`

All 3 primary types have the same event content structure:

```json5
{
"type": "m.message",
turt2live marked this conversation as resolved.
Show resolved Hide resolved
"content": {
// short form

"m.text": "i am a *fish*", // doesn't have to be markdown, but useful as a "fallback" for HTML
turt2live marked this conversation as resolved.
Show resolved Hide resolved
"m.html": "i am a <b>fish</b>"
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}
}
```
```json5
{
"type": "m.message",
"content": {
// longer form

"m.message": [
turt2live marked this conversation as resolved.
Show resolved Hide resolved
{
"mimetype": "text/html", // optional, default text/plain
"body": "i am a <b>fish</b>"
},
{
"mimetype": "text/plain",
"body": "i am a *fish*" // doesn't have to be markdown, but useful as a "fallback"
}
],
}
}
```

`m.text` and `m.html` are shortform types used to represent the most common scenarios. In cases
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved
where either `m.text` and `m.html` are specified alongside `m.message`, `m.message` should be
preferred.
turt2live marked this conversation as resolved.
Show resolved Hide resolved
turt2live marked this conversation as resolved.
Show resolved Hide resolved

For completeness, here are emotes and notices:

```json5
{
"type": "m.emote",
"content": {
"m.text": "is a fish" // note the semantics of the old msgtype are preserved
}
}
```
turt2live marked this conversation as resolved.
Show resolved Hide resolved
```json5
{
"type": "m.notice",
"content": {
"m.text": "I am a bot who does not wish to use normally-styled text"
}
}
```

### Rendering (un)known events

A client's approach to rendering a known event is unchanged: if the primary event type is known, the
client will pick out the details it needs from `content` and render accordingly.

If the primary type is not known, the client should inspect `content` for types it does know and render
the best match. Determining the best match is intentionally left as an implementation detail, however
clients might wish to use a modified version of the following sequence:

1. All known primary event types not listed in this list.
* Some events, like [polls](https://github.com/matrix-org/matrix-doc/pull/3381), might not make sense to
fall back to. Clients should only use primary types which make sense for their specific application,
which might typically be just the ones listed below.
turt2live marked this conversation as resolved.
Show resolved Hide resolved
2. `m.video` (from [MSC3553](https://github.com/matrix-org/matrix-doc/pull/3553))
3. `m.audio` / `m.voice` (from [MSC3246](https://github.com/matrix-org/matrix-doc/pull/3246)
and [MSC3245](https://github.com/matrix-org/matrix-doc/pull/3245))
4. `m.image` (from [MSC3552](https://github.com/matrix-org/matrix-doc/pull/3552))
5. `m.file` (from [MSC3551](https://github.com/matrix-org/matrix-doc/pull/3551))
6. `m.location` (from [MSC3488](https://github.com/matrix-org/matrix-doc/pull/3488))
turt2live marked this conversation as resolved.
Show resolved Hide resolved
9. `m.message` (implying `m.text` and `m.html` too)
turt2live marked this conversation as resolved.
Show resolved Hide resolved
10. Fail to render due to unrepresentable event

Note that this additionally allows clients to fall back gracefully on some event types it might not want
to implement specifically, but still want to support, such as stickers which are effectively images with
largely optional additional rendering requirements.

All primary event types in the specification include their fallback representations as part of the schema.
This schema forms the minimum a client must emit for that event type, hwoever clients are welcome to
include additional types if they feel it is relevant. For example, a hypothetical IoT event added to the
spec might be as follows:

```json5
{
"type": "m.temperature",
"content": {
turt2live marked this conversation as resolved.
Show resolved Hide resolved
"m.temperature": {
"celsius": 27
},
"m.text": "it is 27 degrees"
}
// irrelevant fields excluded
}
```

In this example, the `m.temperature` event in `content` must be accompanied by an `m.message` fallback of
some kind, which can include a shortcut of `m.text` per this proposal.

Though required by the event schema, clients should not explicitly rely on other clients sending all of the
appropriate event types in `content`. If they are aware of what `m.temperature` is (for example), they will
not need the `m.message` components - clients which are unaware simply won't render the event, however.

In the case an event needs to fallback to `m.emote` or `m.notice`, the appropriate type can be included
in the event content as such:

```json5
{
"type": "org.example.custom.event",
"content": {
"m.text": "is a fish",
"m.emote": {}, // denotes that this should fall back to an emote, but worst case gets rendered as text
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}
}
```

### Transition

It's simply not feasible to change the entirety of the Matrix ecosystem to a whole new set of events
overnight, so this proposal includes a time-constrained transition period to encourage all client
implementations to update their support for this schema before the ecosystem starts to move forward with
sending the newly-proposed primary event types.

Upon being included in a **released** version of the specification, the following happens:
* `m.room.message` is deprecated **but still used**.
* Clients include the fallback support described below in their outgoing events.
* Clients prefer the new format in events which include it.
* Clients support the new primary event types, but continue to send `m.room.message` instead.
turt2live marked this conversation as resolved.
Show resolved Hide resolved
* A 1 year timer begins for clients to implement the above conditions.
* This can be shortened if the ecosystem adopts the format sooner.
* After the (potentially shortened) timer, an MSC is introduced to remove the deprecated `m.room.message`
event format. Once accepted under the relevant process, clients/implementations start sending the proper
primary event types instead of `m.room.message`.

Unfortunately, this approach does effectively mean that most clients will have eternal technical debt of
having to support the fallback approach as the vast majority of user-facing clients will want to preserve
the visibility of history before this MSC's introduction. Some clients, like bridges and bots, are likely
able to drop their support for rendering `m.room.message` events as they would have already processed the
older events (or have no reason/desire to backfill in a room).

As a fallback, a client simply smashes `m.room.message` together with the relevant event contents:

```json5
{
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "Hello World",
"format": "org.matrix.custom.html",
"formatted_body": "<b>Hello</b> World",
"m.text": "Hello World",
"m.html": "<b>Hello</b> World"
}
}
```
or
```json5
{
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "Hello World",
"format": "org.matrix.custom.html",
"formatted_body": "<b>Hello</b> World",
"m.message": [
{"mimetype": "text/plain", "body": "Hello world"},
{"mimetype": "text/html", "body": "<b>Hello world</b>"}
]
}
}
```

A client can infer the intended primary type from the `msgtype` and otherwise engage their fallback process
to try and render unknown `msgtype`s.
turt2live marked this conversation as resolved.
Show resolved Hide resolved

Note that this fallback only applies in cases where the `m.room.message` `msgtype` was converted to a dedicated
primary event: new features, or events which don't have a `msgtype`, should send their primary event type instead.
Such an example is [Polls](https://github.com/matrix-org/matrix-doc/pull/3381).

### State events

State events are typically not rendered as they usually contain metadata for the room rather than information for
the historical record, however there are a few cases where rendering a state event might be desirable. Clients are
not required to look for event types in state event contents, though are recommended to do so.

This proposal does not introduce any additional requirement for state events to include these rendering details.

A hypothetical scenario would be a bridge state event that includes `m.text` to say a bridge has been added/edited.

### Hypothetical IOT event example

with text fallback:

```json5
{
"type": "net.arasphere.temperature",
"content": {
"m.text": "The temperature is 37C",
"m.html": "The temperature is <font color='#f00'>37C</font>",
"net.arasphere.temperature": {
"temperature": 37
}
}
}
```

## Potential issues

It's a bit ugly to not know whether a given key will take a string, object or array.

It's a bit arbitrary as to which fields are allowed lists of fallbacks (eg image thumbnails).

It's a bit ugly that you have to look over the keys of contents to see what types
are present, but better than duplicating this into an explicit `types` list within the
event content (on balance).

We're skipping over defining rules for which fallback combinations to display
turt2live marked this conversation as resolved.
Show resolved Hide resolved
(i.e. "display hints") for now; these can be added in a future MSC if needed.
[MSC1225](https://github.com/matrix-org/matrix-doc/issues/1225) contains a proposal for this.

Placing event types at the top level of `content` is a bit unfortunate, particularly
when mixing in `m.room.message` compatibility, though mixes nicely thanks to namespacing.
Potentially conflicting cases in the wild would be namespaced fields, which would get
translated as unknown event types, and thus skipped by the rendering machinery.

This proposal additionally does not consider how other functionality like Replies, Edits,
Push Notifications, etc work. These are explicitly deferred to future MSCs.

turt2live marked this conversation as resolved.
Show resolved Hide resolved
## Security considerations
turt2live marked this conversation as resolved.
Show resolved Hide resolved

We can't apply ACLs serverside to the types embedded in the event contents.
turt2live marked this conversation as resolved.
Show resolved Hide resolved
However, this is inevitable given the existence of E2EE, so we have no choice but
for clients to apply ACLs clientside (e.g. refuse to render an `m.image` contents
on an event if the sender doesn't have enough PL to send an `m.image` event).

Like today, it's possible to have the different representations of an event not match,
thus introducing a potential for malicious payloads (text-only clients seeing something
different to HTML-friendly ones). Clients could try to do similarity comparisons, though
this is complicated with features like HTML and arbitrary custom markup (markdown, etc)
showing up in the plaintext or in tertiary formats on the events. Historically, room
moderators have been pretty good about removing these malicious senders from their rooms
when other users point out (quite quickly) that the event is appearing funky to them.

Copy link
Contributor

@Gnuxie Gnuxie May 30, 2024

Choose a reason for hiding this comment

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

m.room.power_levels provide no way of restricting what media (via mixins) you can embed into events.

Copy link
Member

@anoadragon453 anoadragon453 Jun 25, 2024

Choose a reason for hiding this comment

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

@Gnuxie could you write this up as a new matrix-spec issue to make sure it doesn't get lost?

Copy link
Contributor

Choose a reason for hiding this comment

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

## Unstable prefix

While this MSC is not considered stable by the specification, implementations *must* use
`org.matrix.msc1767` as a prefix to denote the unstable functionality. For example, sending
an `m.message` event would mean sending an `org.matrix.msc1767.message` event instead.

Implementations wishing to trial the migration ahead of this MSC's inclusion in the spec would
use the unstable prefix mentioned above. A fallen back `m.room.message` example event becomes:
turt2live marked this conversation as resolved.
Show resolved Hide resolved

```json5
{
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "Hello World",
"format": "org.matrix.custom.html",
"formatted_body": "<b>Hello</b> World",
"org.matrix.msc1767.text": "Hello World",
"org.matrix.msc1767.html": "<b>Hello</b> World"
turt2live marked this conversation as resolved.
Show resolved Hide resolved
}
}
```

## Changes from MSC1225

* converted from googledoc to MD, and to be a single PR rather than split PR/Issue.
* simplifies it by removing displayhints (for now - deferred to a future MSC).
* removes all references to mixins, as the term was scaring people and making it feel far too type-theoretic.
turt2live marked this conversation as resolved.
Show resolved Hide resolved
* replaces the clunky m.text.1 idea with lists for types which support fallbacks.
* removes the concept of optional compact form for m.text by instead having m.text always in compact form.
* tries to accomodate most of the feedback on GH and Google Docs from MSC1225.

## Historical changes
turt2live marked this conversation as resolved.
Show resolved Hide resolved

* Anything that wasn't simple text rendering was broken out to dedicated MSCs in an effort to get the
structure approved and adopted while the more complex types get implemented independently.