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

MSC4140: Cancellable delayed events #4140

Open
wants to merge 73 commits into
base: main
Choose a base branch
from
Open
Changes from 12 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
480b00a
draft for expiring event PR
toger5 May 7, 2024
8839b8d
Add msc number
toger5 May 7, 2024
8bf6db7
add security consideration and alternatives
toger5 May 7, 2024
8ec6374
alternative name and alternative content
toger5 May 8, 2024
9f45cfa
review andrewF
toger5 May 8, 2024
54fff99
draft of iteration two (after meeting with the backend team)
toger5 May 10, 2024
abdfe1c
timeout_refresh_token is not a well description since the same token …
toger5 May 13, 2024
53f6186
rename msc, rephrase introduction
toger5 May 14, 2024
087c74e
Add usecase specific section.
toger5 May 20, 2024
538b853
add GET futures endpoint
toger5 May 20, 2024
f7a1aad
shorten introduction
toger5 May 21, 2024
f5f4b38
add alternative section to not include the `m.send_now` field
toger5 May 22, 2024
c16afbc
Update proposals/4140-delayed-events-futures.md
toger5 May 31, 2024
c52c80d
batch sending considerations
toger5 May 31, 2024
bf22260
Update proposals/4140-delayed-events-futures.md
toger5 Jun 3, 2024
7f0d80f
Update proposals/4140-delayed-events-futures.md
toger5 Jun 3, 2024
7b192ac
Update proposals/4140-delayed-events-futures.md
toger5 Jun 3, 2024
f3bf66d
review
toger5 Jun 3, 2024
2d7b27d
add background to usecase specific considerations
toger5 Jun 4, 2024
1140ce9
Simplify main proposal for widget api usage
toger5 Jun 5, 2024
0a7896e
make `future_group_id` server generated and small adjustments
toger5 Jun 6, 2024
8fa33d6
review
toger5 Jun 6, 2024
49d5294
user scoping details
toger5 Jun 13, 2024
7550d9b
Update proposals/4140-delayed-events-futures.md
toger5 Jun 13, 2024
a663bb4
add rate limiting section
toger5 Jun 14, 2024
99b3a20
rename `/futures` to `/future`
toger5 Jun 19, 2024
eb50a19
Update everything to v1
toger5 Jun 20, 2024
9ff051e
Update proposals/4140-delayed-events-futures.md
toger5 Jun 22, 2024
425b9bf
review
toger5 Jun 24, 2024
2e7be46
Swap the alternative of reusing the send and state request with the m…
toger5 Jun 24, 2024
5653fe1
add reference to MSC4143 (MatrixRTC)
toger5 Jul 2, 2024
af060cf
Meeting feedback:
toger5 Jul 15, 2024
bff704c
remove parent concept.
toger5 Jul 22, 2024
fa461e2
Refactor + unstable prefixes
hughns Jul 23, 2024
d195218
Update to GET /futures
hughns Jul 23, 2024
b5ac9b2
Standardise endpoint for interacting with futures
hughns Jul 23, 2024
c07dd9b
Make it clear that events can be cancelled
hughns Jul 24, 2024
b79e4e2
Make wording more authoritative
hughns Jul 24, 2024
e125901
Remove erroneous old example
hughns Jul 24, 2024
0443cd9
Take view that clients don't act on behalf of a user
hughns Jul 24, 2024
7df0919
Use name delay_id and delayed_events
hughns Jul 24, 2024
ff95144
Clean up more references to future
hughns Jul 24, 2024
93c932a
event_type => type
hughns Jul 24, 2024
6cce3bd
Tidying
hughns Jul 24, 2024
ac2d2c5
Pagination on GET endpoint
hughns Jul 24, 2024
84e20fd
Add alternative of M_INVALID_PARAM
hughns Jul 24, 2024
ac3bd9d
change order of manage <-> get sections
toger5 Jul 25, 2024
de77a90
Obey lint rules and more renames
toger5 Jul 25, 2024
677d6f3
TOC update
hughns Jul 25, 2024
4caecd1
Additional notes on security considerations
hughns Jul 25, 2024
c482d58
Add rate limiting requirements
hughns Jul 25, 2024
9098fea
Describe MatrixRTC use case in terms of heartbeats
hughns Jul 25, 2024
7e06e85
"leave" => "hangup" for consistency
hughns Jul 25, 2024
a1b8121
Wording clarification
hughns Jul 25, 2024
3a3a5b5
Additional reference to heartbeat
hughns Jul 25, 2024
97d4141
TOC update (use monospace and dont list msc title
toger5 Jul 25, 2024
99c9467
be consistent with "restart" wording (we used reset, refresh and rest…
toger5 Jul 25, 2024
6f2aa5e
Add compatibility with Cryptographic Identities to potenail issues
hughns Jul 25, 2024
5e43db0
Add placeholder for MSC3277
hughns Jul 25, 2024
84b8dc0
Add note about MSC2716 batch sending
hughns Jul 25, 2024
e7d0986
Add alternative of not providing a send action
hughns Jul 25, 2024
850bf9e
Add alternative of using DELETE HTTP method
hughns Jul 25, 2024
e1b460a
Add clarification about regular/non-state events not getting cancelled
hughns Jul 25, 2024
114da1e
Clarify contents of content field on GET response
hughns Jul 25, 2024
772590f
Expose transaction ID in GET response
hughns Jul 25, 2024
4a2ca48
Add context on MSC3277 alternative
hughns Jul 25, 2024
37979cd
Add more context to MQTT style alternative
hughns Jul 25, 2024
195ab6a
Add alternative of typing notifications
hughns Jul 25, 2024
0f8a2d1
Update TOC
hughns Jul 26, 2024
886f378
Fix TOC
hughns Aug 2, 2024
8523ed4
Note on alternative names for `running_since`
hughns Aug 2, 2024
883e6b5
Revert "Expose transaction ID in GET response"
AndrewFerr Sep 11, 2024
caece4d
Scope GET to only the requesting user's events
AndrewFerr Sep 11, 2024
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
300 changes: 300 additions & 0 deletions proposals/4140-delayed-events-futures.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
# MSC4140: Delayed events (Futures)

Allowing to schdule/delay events would solve numerous issues in
toger5 marked this conversation as resolved.
Show resolved Hide resolved
toger5 marked this conversation as resolved.
Show resolved Hide resolved
matrix.
toger5 marked this conversation as resolved.
Show resolved Hide resolved

- Updating call member events after the user disconnected.
- Sending scheduled messages (send at a specific time)
toger5 marked this conversation as resolved.
Show resolved Hide resolved
- Creating self destructing events (By sending a delayed redact)
toger5 marked this conversation as resolved.
Show resolved Hide resolved

Currently there is no mechanism for a client to reliably that an event is still valid.
toger5 marked this conversation as resolved.
Show resolved Hide resolved
The only way to update an event is to post a new one.
In some situations the client just looses connection or fails to sent the expired
toger5 marked this conversation as resolved.
Show resolved Hide resolved
version of the event. This proposal also includes a expiration/timeout
toger5 marked this conversation as resolved.
Show resolved Hide resolved
system so that those scenarios are also covered.

We want to send an event in advance
toger5 marked this conversation as resolved.
Show resolved Hide resolved
to the homeserver but let the homeserver decide the time when its actually added to the
toger5 marked this conversation as resolved.
Show resolved Hide resolved
DAG.
The condition for actually sending the delayed event would could be a timeout or a external trigger via a synapse endpoint.
toger5 marked this conversation as resolved.
Show resolved Hide resolved

## Proposal

To make this as generic as possible, the proposed solution is to allow sending
multiple presigned events and delegate the control of when to actually send these
events to an external services. This allows to a very flexible way to mark events as expired,
toger5 marked this conversation as resolved.
Show resolved Hide resolved
since the sender can choose what event will be sent once expired.
hughns marked this conversation as resolved.
Show resolved Hide resolved

We call those delayed events `Futures`.

A new endpoint is introduced:
`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}`
toger5 marked this conversation as resolved.
Show resolved Hide resolved
It behaves exactly like the normal send endpoint except that that it allows
to send a list of event contents. The body looks as following:
toger5 marked this conversation as resolved.
Show resolved Hide resolved

```json
{
"m.timeout": 10,
"m.send_on_timeout": {
"content": sendEventBody0,
"type": "m.room.message",
},

"m.send_on_action:${actionName}": {
"content": sendEventBody1,
"type": "m.room.message"
},

// optional
"m.send_now": {
"content": sendEventBody2,
"type": "m.room.message"
},
}
```
toger5 marked this conversation as resolved.
Show resolved Hide resolved

Each of the `sendEventBody` objects are exactly the same as sending a normal
event.

There can be an arbitrary amount of `actionName`s.
toger5 marked this conversation as resolved.
Show resolved Hide resolved
toger5 marked this conversation as resolved.
Show resolved Hide resolved

All of the fields are optional except the `timeout` and the `send_on_timeout`.
This guarantees that all tokens will expire eventually.
toger5 marked this conversation as resolved.
Show resolved Hide resolved

The homeserver can set a limit to the timeout and return an error if the limit
is exceeded.

### Response

The response will mimic the request:

```json
toger5 marked this conversation as resolved.
Show resolved Hide resolved
{
"m.send_on_timeout": { "eventId": "id_hash" },
"m.send_on_action:${actionName}": { "eventId": "id_hash" },

"future_token": "token",

// optional
"m.send_now": { "eventId": "id_hash" }
}
```

### Delegating futures

The `token` can be used to call another future related endpoint:
`PUT /_matrix/client/v3/futures/refresh` and `PUT /_matrix/client/v3/futures/action/${actionName}`.
toger5 marked this conversation as resolved.
Show resolved Hide resolved
toger5 marked this conversation as resolved.
Show resolved Hide resolved
where the body is:

```json
toger5 marked this conversation as resolved.
Show resolved Hide resolved
{
"future_token": "token"
}
```

The information required to call this endpoint is very limited so that almost
no metadata is leaked. This allows to share a refresh link to a different
service. This allows to delegate the send time. An SFU for instance, that tracks the current client connection state,
and pings the HS to refresh and call a dedicated action to communicate
that the user has intentionally left the conference.

The homeserver does the following when receiving a Future.

- It **sends** the optional `m.send_now` event.
- It **generates** a `future_token` and stores it alongside with the time
of retrieval, the event list and the timeout duration.
- **Starts a timer** for the stored `future_token`.

- If a `PUT /_matrix/client/v3/futures/refresh` is received, it
**restarts the timer** with the stored timeout duration.
- If a `PUT /_matrix/client/v3/futures/action/${actionName}` is received, it **sends the associated action event**
`m.action:${actionName}`.
toger5 marked this conversation as resolved.
Show resolved Hide resolved
- If the timer times out, **it sends the timeout event** `m.send_timeout`.
- If the future is a state event and includes a `m.send_now` event
the future is only valid while the `m.send_now`
is still the current state:
AndrewFerr marked this conversation as resolved.
Show resolved Hide resolved

- This means, if the homeserver receives
a new state event for the same state key, the **`future_token`**
**gets invalidated and the associated timer is stopped**.

- There is no race condition here since a possible race between timeout and
new event will always converge to the new event:
- Timeout -> new event: the room state will be updated twice. once by
the content of the `m.send_on_timeout` event but later with the new event.
- new event -> timeout: the new event will invalidate the future. No
toger5 marked this conversation as resolved.
Show resolved Hide resolved

- After the homeservers sends a timeout or action future event, the associated
timer and `future_token` is canceled/invalidated.

So for each Future the client sends, the homeserver will send one event
conditionally at an unknown time that can trigger logic on the client.
This allows for any generic timeout logic.

Timed messages/reminders or ephemeral events could be implemented using this where
clients send a redact as a future or a room event with intentional mentions.

In some scenarios it is important to allow to send an event with an associated
future at the same time.

- One example would be redacting an event. It only makes sense to redact the event
if it exists.
It might be important to have the guarantee, that the redact is received
by the server at the time where the original message is sent.
- In the case of a state event we might want to set the state to `A` and after a
timeout reset it to `{}`. If we have two separate request sending `A` could work
but the event with content `{}` could fail. The state would not automatically
reset to `{}`.

For this usecase an optional `m.send_now` field can be added to the body.
hughns marked this conversation as resolved.
Show resolved Hide resolved

### Getting running futures

Using `GET /_matrix/client/v3/futures` it is possible to get the list of all running futures.
This is an authenticated endpoint. It sends back the json
of the final events how they will end up in the DAG with the associated `future_token`.

```json
toger5 marked this conversation as resolved.
Show resolved Hide resolved
[
{
"m.send_now": finalEvent_0,
AndrewFerr marked this conversation as resolved.
Show resolved Hide resolved
"m.send_on_timeout": finalEvent_1,
...,

"future_token":"token"
},
]
```

This can be used so clients can optionally display events
that will be send in the future.
For self destructing messages it is recommanded to include

Check warning on line 171 in proposals/4140-delayed-events-futures.md

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"recommanded" should be "recommended".
toger5 marked this conversation as resolved.
Show resolved Hide resolved
this information in the event itself so that the usage of
this endpoint can be minimized.

## Usecase specific considerations

### MatrixRTC

We want can use the actions and the timeout for matrix rtc for the following situations

- If the client takes care of its membership, we use a short timeout value (around 5-20 seconds)
The client will have to ping the refresh endpoint approx every 2-19 seconds.
- When the SFU is capable of taking care of managing our connection state and we trust the SFU to
not disconnect a really long value can be chosen (approx. 2-10hours). The SFU will then only send
an action once the user disconnects or looses connection (it could even be a different action for both cases
handling them differently on the client)
This significantly reduces the amount of calls for the `/future` endpoint since the sfu only needs to ping
once per session (per user) and every 2-5hours (instead of every `X` seconds.)

### Self destructing messages
toger5 marked this conversation as resolved.
Show resolved Hide resolved

This MSC also allows to implement self destructing messages:
toger5 marked this conversation as resolved.
Show resolved Hide resolved
toger5 marked this conversation as resolved.
Show resolved Hide resolved

`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`

```json
{
"m.text": "my msg"
}
```

`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}`

```json
{
"m.timeout": 10*60,
"m.send_on_timeout": {
"type":"m.room.readact",
"content":{
"redacts": "EvId"
}
}
}
```

## EventId template variable
toger5 marked this conversation as resolved.
Show resolved Hide resolved

It would be useful to be able to send redactions and edits as one http request.
toger5 marked this conversation as resolved.
Show resolved Hide resolved
This would make sure that the client cannot loose connection after sending the first event.
toger5 marked this conversation as resolved.
Show resolved Hide resolved
For instance sending a self destructing message without the redaction.
toger5 marked this conversation as resolved.
Show resolved Hide resolved

The optional proposal is to introduce template variables that are only valid in `Future` events.
`$m.send_now.event_id` in the content of one of the `m.send_on_action:${actionName}` and
`m.send_on_timeout` contents this template variable can be used.
The **Self destructing messages** example would simplify to:
toger5 marked this conversation as resolved.
Show resolved Hide resolved

`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}`

```json
{
"m.send_now":{
"type":"m.room.message",
"content":{
"m.text": "my msg"
}
},
"m.timeout": 10*60,
"m.send_on_timeout": {
"type":"m.room.readact",
"content":{
"redacts": "$m.send_now.event_id"
}
}
}
```

## Potential issues

## Alternatives

[MSC4018](https://github.com/matrix-org/matrix-spec-proposals/pull/4018) also
proposes a way to make call memberships reliable. It uses the client sync loop as
an indicator to determine if the event is expired. Instead of letting the SFU
inform about the call termination or using the call app ping loop like we propose
here.

---

The following names for the endpoint are considered

- Future
- DelayedEvents
- RetardedEvents
toger5 marked this conversation as resolved.
Show resolved Hide resolved
toger5 marked this conversation as resolved.
Show resolved Hide resolved

---

The `m.send_now` field could not be part of the future. This would also
mitigate the need for the `$m.send_now.event_id` template variable.

It would come with the cost that there is no way to guarantee, taht the current state and the future are recieved by the homeserver.

Check warning on line 270 in proposals/4140-delayed-events-futures.md

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"taht" should be "that".

Check warning on line 270 in proposals/4140-delayed-events-futures.md

View workflow job for this annotation

GitHub Actions / Spell Check with Typos

"recieved" should be "received".
The client would need to send the events in sequence, so the
connection could be lost between the now event and the future.
It is expected that this is a very rare case.

Sequence wise it might make sense to not include the `m.send_now` in this
msc and solve the topic by a good and flexible batch sending solution
independent of this PR. (then the future and the event could be sent in one batch giving the same result as the `m.send_now` field)

## Security considerations

We are using an unauthenticated endpoint to refresh the expirations. Since we use
the token it is hard to guess a correct request and force one of the actions
events of the Future.

It is an intentional decision to not provide an endpoint like
`PUT /_matrix/client/v3/futures/room/{roomId}/event/{eventId}`
where any client with access to the room could also `end` or `refresh`
the expiration. With the token the client sending the event has ownership
over the expiration and only intentional delegation of that ownership
(sharing the token) is possible.

On the other hand the token makes sure that the instance gets as little
information about the matrix metadata of the associated `future` event. It cannot
toger5 marked this conversation as resolved.
Show resolved Hide resolved
even tell with which room or user it is interacting.

## Unstable prefix

use `io.element.` instead of `m.` as long as the msc is not stable.
toger5 marked this conversation as resolved.
Show resolved Hide resolved

## Dependencies
Loading