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

MSC2477: User-defined ephemeral events in rooms #2477

Open
wants to merge 14 commits into
base: old_master
Choose a base branch
from
Open
374 changes: 374 additions & 0 deletions proposals/2477-user-defined-ephemeral-events.md
@@ -0,0 +1,374 @@
# MSC2476: User-defined ephemeral events in rooms

Matrix currently handles the transfer of data in the form of Persistent- as well as Ephemeral Data
Units, both of which follow the same general design in the way they're encoded and transferred over
both the Client-Server and Server-Server API.

Currently, users are only able to provide their own event types and data in the case of
persistent data, in the form of state events as well as messages / timeline events.
The sending of ephemeral data by clients - on the other hand - is currently limited to only typing
notifications, event receipts, and presence updates. Which greatly limits the potential usefulness
of ephemeral events as a general mechanism for transferring short-lived data.

Therefore, this proposal suggest extending both the Client-Server and Server-Server APIs to allow
users to transfer arbitrary ephemeral data types and content into rooms in which they have the right
to do so.


## Proposal

The proposed change is to add support for users to provide their own data types and content, in a
similar manner to the already existing support for users to send their own types of persistent data.

Note though that this proposal does not include any support for sending user-defined ephemeral
events which are not explicitly bound to rooms, like the global `m.presence` event.

Examples of how this feature could be used are; as regular status updates to a user-requested
long-lived task, which a bot might has started for a received event. Or pehaps as a GPS live-location
ananace marked this conversation as resolved.
Show resolved Hide resolved
feature, where participating client would regularly post their current location relative to a
ananace marked this conversation as resolved.
Show resolved Hide resolved
persistent geo-URI event. Perhaps for organizing meetups, or for viewing active tracking of the
locations of vehicles in an autonomous fleet - along with peristent messages posted at a lesser
rate for a timeline generation.
ananace marked this conversation as resolved.
Show resolved Hide resolved

The example that will be used througout this proposal is an ephemeral data object that's tracking
the current status of a user-requested 3D print, with some basic printer- and print-status
information being sent every few seconds to a control room, including a reference to the event that
the status is referring to - which the client could use to render a progress bar or other graphic
with.

### Addition of an ephemeral event sending endpoint to the Client-Server API

The suggested addition to the CS API is the endpoint
`PUT /_matrix/client/r0/rooms/{roomId}/ephemeral/{eventType}/{txnId}`, which would act in an almost
Copy link
Member

Choose a reason for hiding this comment

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

worth noting that this limits custom EDUs to rooms compared to something like presence which is user based instead of room based.

Copy link
Contributor Author

@ananace ananace Aug 27, 2020

Choose a reason for hiding this comment

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

The second line in my proposal block already notes this, as I didn't think the usefulness of custom non-room EDUs merited the work of designing some whole new kind of permission system for sending them. Though perhaps a reference to user EDUs built on the Profile-as-a-Room MSC1769 could be added.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
`PUT /_matrix/client/r0/rooms/{roomId}/ephemeral/{eventType}/{txnId}`, which would act in an almost
`PUT /_matrix/client/v1/rooms/{roomId}/ephemeral/{eventType}/{txnId}`, which would act in an almost

Following the new per-endpoint versioning spec.

identical manner to the event sending endpoint that is already present.
ananace marked this conversation as resolved.
Show resolved Hide resolved
An example of how an update might be posted using the new endpoint;

```
PUT /_matrix/client/r0/rooms/%21636q39766251%3Aexample.com/ephemeral/com.example.3dprint/19914 HTTP/1.1
Content-Type: application/json

{
"print_event_id": "$E2RPcyuMUiXyDkQ02ASEbFxcJ4wFNrt5JVgov0wrqWo",
"printer_id": 10,
"status": {
"hotend_c": 181.4,
"bed_c": 62.5,
"position": [54, 275, 87.2]
},
"time": {
"elapsed": 4324,
"estimated": 7439
}
}
```

Example of a response;

Status code 200:
ananace marked this conversation as resolved.
Show resolved Hide resolved

As EDUs do not have event IDs, this is an empty JSON object

```json
{}
```

Status code 400:

The user tried to send a `m.*` EDU, instead of using the type-specific endpoint
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if we should make this off-limits to anything m.*. In fact, I wonder if we should go the opposite way and transition all room-based ephemeral messages to this endpoint - so read receipts and typing notifications.

At the moment, typing notifications are using:

PUT /_matrix/client/v3/rooms/{roomId}/typing/{userId}

{
  "timeout": 30000,
  "typing": true
}

and read markers are sent with:

POST /_matrix/client/v3/rooms/{roomId}/read_markers

{
  "m.fully_read": "$somewhere:example.org",
  "m.read": "$elsewhere:example.org"
}

The {userId} bit of the typing endpoint is especially useless, and could be cleaned up in the same move.

Presence and to-device messages would be excluded from this, as they are not bound to a room.

Copy link
Contributor

Choose a reason for hiding this comment

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

Read markers seem to be a bit of a special case, as they have an effect on the server to store something quasi-persistent, shouldn't read_markers stay out of this then? or what is the idea behind that?

Copy link
Contributor Author

@ananace ananace Nov 30, 2021

Choose a reason for hiding this comment

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

I wrote this MSC on the assumption that the custom EDUs would be a separate beast to the built-in EDUs, but I guess it could just as well be done to instead have the server handle well-defined EDUs in a certain manner. So that you - as a client dev/user - could just post m.typing or m.receipt/m.fully_read/m.read EDUs instead of calling the specific endpoints for them.

Copy link
Member

Choose a reason for hiding this comment

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

Read markers seem to be a bit of a special case, as they have an effect on the server to store something quasi-persistent, shouldn't read_markers stay out of this then? or what is the idea behind that?

Good point. m.fully_read does not fit this model, as its purpose is to store your read-up-to position, which lives in the current user's room account data. The linked endpoint just happens to allow optionally sending an m.read receipt in the same call.

Instead, we should be thinking about whether to replicate the behaviour of POST /_matrix/client/v3/rooms/{roomId}/receipt/{receiptType}/{eventId} with this then.

With that, I'm not so sure. They do fall under the banner of EDU, but a receipt is always tied to an event ID (e.g. I read up to this event ID). We'd need to include that event ID in the body of the request for m.read or for any other type of receipt. That can work, though I suppose it comes down to how much we'd like to differentiate receipts as their own thing, versus just another type of EDU.


```json
{
"errcode": "M_UNKNOWN",
"error": "Cannot send built-in ephemeral types with this endpoint"
}
```

Status code 403:

Power levels forbid the user from sending the attempted EDU

```json
{
"errcode": "M_FORBIDDEN",
"error": "You do not have permission to send the EDU"
}
```

User is not in the room

```json
{
"errcode": "M_FORBIDDEN",
"error": "You are not a member of the room"
}
```

Status code 429:

The request was rate-limited

```json
{
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
}
```

### Extension of power levels to handle user-defined ephemeral events
ananace marked this conversation as resolved.
Show resolved Hide resolved

As it would be possible for the user-defined events to be used to flood a room with invisible
traffic by malicious users - increasing the bandwidth usage for all connected servers, this proposal
also suggests extending the power levels to handle ephemeral types as well.

In any room version implementing this MSC, the auth rules to include in `m.room.power_levels` are;

- `ephemeral` (`{string: integer}`) - A mapping of EDU types to the power-level required to send them
- `ephemeral_default` (`integer`) - The default power-level required to send any EDU not listed in
the above mapping

These new keys are to function in an identical manner to the already existing `events` and
`events_default` keys, with the suggested default - and fallback - value for `ephemeral_default`
being 50, while the suggested default - and fallback - values for `ephemeral` would be;

```json
{
"m.receipt": 0,
"m.typing": 0
}
```
ananace marked this conversation as resolved.
Show resolved Hide resolved

These defaults are to ensure that all current Matrix EDUs continue to work as intended.

**NB**;
To reduce the complexity of the change, this proposal suggests to - for the time being - only limit
the user-defined types by these power levels changes. The default values for `m.*` specified here in
`ephemeral` would then only be expected to be informational in purpose.
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned earlier, any change to the auth rules requires a new room version. If we wanted to have these only apply to user-defined types initially, and then fully apply later, that would take two room versions.

Given rooms will need to be recreated anyways, it seems sensible to simply have homeservers pre-fill 0 for m.typing and m.fully_read entries when creating a room to preserve existing behaviour?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I used m.receipt as the type when I was writing, as I wasn't sure about m.read (and m.fully_read). The resulting event is m.receipt, and the endpoint for sending the information is rooms/{roomId}/receipt. But the receipt type as defined in the content is m.read.
m.fully_read is in the spec handled in a separate endpoint, but includes a link to receipts as you can include m.read in the request to combine the two into a single request. Not sure how that would be combined into things.

For now I think I'm going to keep m.receipt as the general EDU type, since it's the type that - for now - is seen in an actual ephemeral event. (after all m.fully_read only ends up in the account data, and m.read are agglomerated into m.receipt events)


### Extension of the room-specific ephemeral data received in /sync responses

Because the user-defined ephemeral events can't be aggregated and massaged by Synapse in a simple
ananace marked this conversation as resolved.
Show resolved Hide resolved
manner, this then suggests instead adding a few more fields to the room-specific ephemeral events as
they are encoded in a sync response. The suggested additions are;
ananace marked this conversation as resolved.
Show resolved Hide resolved

- `sender` (`string`) - The fully qualified ID of the user that sent the EDU
- `origin_server_ts` (`integer`) - Timestamp in milliseconds on the originating homeserver when this
event was sent

To reduce the risk of breaking existing clients, as well as reducing the scope of change required by
this proposal, the suggestion is to allow the original `m.*` events to skip these keys where no value
could be easily assigned to them. E.g. typing notices, read receipts.

```json
ananace marked this conversation as resolved.
Show resolved Hide resolved
{
"next_batch": "...",
// ...
"rooms": {
"join": {
"!636q39766251:example.com": {
// ...
"timeline": {
"events": [
{
"content": {
"gcode": "mxc://example.com/GEnfasiifADESSAF",
"printer": 10
},
"type": "com.example.3dprint_request",
"event_id": "$4CvDieFIFAzSYaykmBObZ2iUhSa5XNEUnC-GQfLl2yc",
"room_id": "!636q39766251:example.com",
"sender": "@alice:matrix.org",
"origin_server_ts": 1432735824653,
"unsigned": {
"age": 5558
}
},
{
"content": {
"body": "Print of fan_shroud_v5.gcode started on printer 10, ETA is 2h. Stream is available at https://example.com/printers/10.m3u8",
"com.example.3dprint": {
"gcode": "mxc://example.com/GEnfasiifADESSAF",
"printer": 10,
"video": "https://example.com/printers/10.m3u",
"eta": 7253
},
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$4CvDieFIFAzSYaykmBObZ2iUhSa5XNEUnC-GQfLl2yc"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1432735825887,
"sender": "@printbot:example.com",
"type": "m.room.message",
"unsigned": {
"age": 4324
},
"event_id": "$E2RPcyuMUiXyDkQ02ASEbFxcJ4wFNrt5JVgov0wrqWo",
"room_id": ""
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"type": "m.typing",
"room_id": "!636q39766251:example.com"
Copy link
Member

Choose a reason for hiding this comment

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

room_id isn't part of the spec for ephemeral event bodies down /sync.

Likewise below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The spec entry you linked includes an m.typing event in the example response for code 200, which does include room_id in the data;

# ...
    "join": {
      "!726s6s6q:example.com": {
        "account_data": {
          "events": [
            {
              "content": {
                "tags": {
                  "u.work": {
                    "order": 0.9
                  }
                }
              },
              "type": "m.tag"
            },
            {
              "content": {
                "custom_config_key": "custom_config_value"
              },
              "type": "org.example.custom.room.config"
            }
          ]
        },
        "ephemeral": {
          "events": [
            {
              "content": {
                "user_ids": [
                  "@alice:matrix.org",
                  "@bob:example.com"
                ]
              },
              "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
              "type": "m.typing"
            }
          ]
        },
# ...

I think that's where that came from, since I tried to base my examples directly on the spec examples.

Copy link
Member

Choose a reason for hiding this comment

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

I see, thanks for pointing that out! I've made a PR to fix that, as I believe that's a spec bug: #3679.

},
{
"content": {
"print_event_id": "$E2RPcyuMUiXyDkQ02ASEbFxcJ4wFNrt5JVgov0wrqWo",
"printer_id": 10,
"status": {
"hotend_c": 181.4,
"bed_c": 62.5,
"position": [54, 275, 87.2]
},
"time": {
"elapsed": 4324,
"estimated": 7440
}
},
"type": "com.example.3dprint",
"room_id": "!636q39766251:example.com",
"sender": "@printbot:example.com",
"origin_server_ts": 1432735830211
}
]
}
}
}
}
}
```

### Extension of the Server-Server spec

As the server-server protocol is currently only designed for transferring the well-defined EDUs that
exist as part of the Matrix communication protocol, this proposal suggests adding additional fields
to the EDU schema in order to let them transmit the user-specified data untouched - while still
adding source information that is important for the receiving clients.

The suggested fields to add to the EDU schema are;

- `room_id` (`string`) - The fully qualified ID of the room the event was sent in
- `sender` (`string`) - The fully qualified ID of the user that sent the event
- `origin` (`string`) - The `server_name` of the homeserver that created this event
- `origin_server_ts` (`integer`) - Timestamp in milliseconds on origin homeserver when this event
was created

A user-defined ephemeral event might then look like this when federated;

```json
{
"content": {
"print_event_id": "$E2RPcyuMUiXyDkQ02ASEbFxcJ4wFNrt5JVgov0wrqWo",
"printer_id": "10",
"status": {
"hotend_c": 181.4,
"bed_c": 62.5,
"position": [54, 275, 87.2]
},
"time": {
"elapsed": 4324,
"estimated": 7440
}
},
"room_id": "!636q39766251:example.com",
"sender": "@printbot:example.com",
"origin": "example.com",
"origin_server_ts": 1432735830211,
"edu_type": "com.example.3dprint"
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
}
```

To reduce the scope of change required by this proposal, the suggestion is to allow the original
`m.*` events to skip these keys where no value can be easily assigned to them. E.g. aggregated typing
notices, receipt lists.

## Potential issues

Because this change completely redefines how ephemeral events are used, it is likely to be expected
that some servers and clients could struggle to handle the new types of data that this proposal
would create. But as the protocol is defined with an extensible transport - JSON, it should not be
difficult - if even necessary - for clients or servers to be modified to support the changes.

Additionally, as ephemeral data is never encoded into room state there's not as many tools for
admins to handle abuse that occur through the use of the feature.
The proposals suggested changes to power levels - and limitation of what event types can be sent -
ananace marked this conversation as resolved.
Show resolved Hide resolved
should mitigate the potential of abuse from the feature though, as long as admins don't allow any
user-defined ephemeral types to be sent by regular users.

This change could impact the - currently in review - [MSC2409], necessitating some kind of filter for
ananace marked this conversation as resolved.
Show resolved Hide resolved
what event types would be transferred to appservices. [MSC2487] suggests a possible solution for this
potential problem.


## Alternatives

The ephemeral state that this change would allow to transfer could as easily be sent using
self-destructing messages with the help of [MSC1763] or [MSC2228], which would result in a similar
anoadragon453 marked this conversation as resolved.
Show resolved Hide resolved
experience to the end user.
Unfortunately using persistent events in such a manner would add a lot of unnecessary data to the
room DAG, while also increasing both the computational work and the database pressure through the
repeated and rapid insertions and redactions that it would result in.

### Client-Server protocol changes

The additions to ephemeral objects could be expanded to also apply to the normal `m.*` types as well,
which would reduce the complexity of the spec as there would be no distinction between the built-in
Matrix types as well as the user-defined types.
This could cause some clients to break though, if they expect the well-defined objects to keep to
their specced forms. Additionally, it might be hard for the server to assign a correct sender and
timestamp to events if they are aggregated from multiple sources - e.g. typing notices and read
receipt lists.
Comment on lines +385 to +388
Copy link
Member

Choose a reason for hiding this comment

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

This could cause some clients to break though, if they expect the well-defined objects to keep to
their specced forms.

If I understand correctly, this is referring to adding the sender and origin_server_ts fields to a JSON object. In the past I believe we've considered adding keys to JSON to not break backwards compatibility, as clients should just be pulling out the keys they need anyhow - and these events are not signed or otherwise wholly checked for integrity.

Additionally, it might be hard for the server to assign a correct sender and
timestamp to events if they are aggregated from multiple sources - e.g. typing notices and read
receipt lists.

This would have to be a problem that we solve anyways, regardless of backwards-compatibility periods.

I'm also not clear why it would be difficult to populate these fields for typing and read receipt lists.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As one example on the second point; m.typing events are aggregated down from the source data into a single event with a list of user_ids that are currently typing. If they are to have correct timestamps and sender data attached to match the EDU type as defined in this MSC, then they'd have to be split apart into separate objects each with their own metadata. I'm not really sure if that's desirable.


### Server-Server protocol changes

Instead of adding potentially optional keys to the EDU schema, the entire object could instead be
embedded into the content key, using an EDU type key that denotes it as an user-defined type.
This would mean a smaller change to the server-server communication, while still allowing a server
module to filter or track events based on their types or origins.

```json
{
"content": {
"room_id": "!636q39766251:example.com",
"sender": "@printbot:example.com",
"origin": "example.com",
"origin_server_ts": 1432735830211,
"type": "com.example.3dprint",
"content": {
"print_event_id": "$E2RPcyuMUiXyDkQ02ASEbFxcJ4wFNrt5JVgov0wrqWo",
"printer_id": "10",
"status": {
"hotend_c": 181.4,
"bed_c": 62.5,
"position": [54, 275, 87.2]
},
"time": {
"elapsed": 4324,
"estimated": 7440
}
}
},
"edu_type": "m.user_defined"
}
```

Possibly, the additional requirements for user-defined types could instead also be expanded to cover
the regular Matrix types as well, which would remove the need for optional fields - but could in
return impact the federation between servers, if they're built to only handle the exact requirements
of the spec.

Copy link
Member

Choose a reason for hiding this comment

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

Unstable prefixes will need to be defined in order for an implementation of the MSC to be carried out. I suggest implementation replace:

  • ephemeral and ephemeral_default fields in the m.room.power_levels state event with org.matrix.msc2477.ephemeral and org.matrix.msc2477.ephemeral_default respectively.
  • the PUT /_matrix/client/v1/rooms/{roomId}/ephemeral/{eventType}/{txnId} endpoint with PUT /_matrix/client/unstable/org.matrix.msc2477/rooms/{roomId}/ephemeral/{eventType}/{txnId}.

And an experimental room version with name org.matrix.msc2477 should be used where this feature is enabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And a whole lot later than it should've been, but I've added a section on an unstable prefix.

Wasn't sure about adding blurbs on when the m.room.power_levels keys should be allowed, but I figured that it'd a bit superfluous since the only method that will act on them is limited to the experimental room version.

[MSC1763]: https://github.com/matrix-org/matrix-doc/pull/1763
[MSC2228]: https://github.com/matrix-org/matrix-doc/pull/2228
[MSC2409]: https://github.com/matrix-org/matrix-doc/pull/2409
[MSC2487]: https://github.com/matrix-org/matrix-doc/pull/2487