From 480b00ab9678ab33958f19cb092b840c6afb2697 Mon Sep 17 00:00:00 2001 From: Timo K Date: Tue, 7 May 2024 18:52:11 +0200 Subject: [PATCH 01/85] draft for expiring event PR Signed-off-by: Timo K --- proposals/0000-keep-alive-state-events.md | 128 ++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 proposals/0000-keep-alive-state-events.md diff --git a/proposals/0000-keep-alive-state-events.md b/proposals/0000-keep-alive-state-events.md new file mode 100644 index 00000000000..6eeb05865b5 --- /dev/null +++ b/proposals/0000-keep-alive-state-events.md @@ -0,0 +1,128 @@ +# MSC0000: Expiring events with keep alive functionality + +Currently there is not mechanism for a client to provide a reliable way of +communicating that an event is still valid. The best expiration method is to post +another event that is stores that it is expired. +In some situations the client just looses connection or fails to sent the expired +version of the event. +A generic way is desired in which the event gets marked as expired by the homeserver. + +Clients can then perform custom logic based on if the event is in valid or +expired state. + +This is particularly interesting in the context of matrixRTC where we want +to ignore expired state events of users who left the call without sending a new +state empty `m.call.member` event. + +We would like the homeserver to mark this event as expired in a reasonable +time window after a user disconnected. + +## Proposal + +Events can contain a `m.will_expire: "running" | "expired" | "ended"` field. +This is an enum marking the event as +expired `m.will_expire: "expired" | "ended"` or still alive `m.will_expire: "running"`. +This field lives outside the ciphertext content (hence it also works for encrypted +events) and is set via the usual `PUT` request if the content contains the additional +`m.will_expire: 10` field (similar how it is done with relations), with the desired +timeout duration in seconds. + +Request + +```json +{ + "content": { + "m.will_expire": 10, + "other_content": "hello" + } +} +``` + +If the homeserver detects a `m.expired` field it will store and distribute the +event as: + +```json +{ + "content": { + "m.will_expire": "running", + "other_content": "hello" + } +} +``` + +The response to the client will be: + +```json +{ + "eventId": "hash_id", + "expire_refresh_token": "hash_refresh", +} +``` + +The default response is extended with the `expire_refresh_token` which +can be used to reset the expiration timeout (in this example 10 seconds). +A new unauthenticated endpoint is introduced: +`PUT /_matrix/client/v3/expiration/{refresh_method}` +where the `refresh_method` is either: `refresh`, `end` +The body contains the refresh token so the homeserver knows what to refresh. + +```json +{ + "expire_refresh_token": "hash_refresh", +} +``` + +The information of this endpoint is very limited so that almost no metadata is +leaked when using this endpoint. This allows to share a refresh link to a different +service (an SFU for instance) that can track the current client connection state, +and pings the HS to refresh and informs the HS about a disconnect. + +The homeserver does the following when receiving an event with `m.expired` + +- It generates a token and stores it alongside with the time of retrieval, +the eventId and the expire duration. +- Starts a counter for the stored expiation token. + - If a `PUT /_matrix/client/v3/expiration/refresh` is received, the + timer is restarted with the stored expire duration. + - If a `PUT /_matrix/client/v3/expiration/end` is received, the + event _gets ended_. + - If the timer times out, the event _gets expired_. + - If the event is a state event only the latest/current state is considered. If + the homeserver receives a new state event without `m.expires` but with the same + state key, the expire_refresh_token gets invalidated and the associated timer is + stopped. + +The event _gets expired_/_gets ended_ means: + +- The homeserver **sends a new event** that is a copy of the previous event but: + - If it gets _expired_ the event will include: `"m.will_expire": "expired"` + - If it gets _ended_ the event will include: `"m.will_expire": "ended"`. + - Additionally it includes a relation to the original event with `rel_type: "m.expire.relationship"` + + ```json + "m.relates_to": { + "event_id": "$original_event", + "rel_type": "m.expire.relationship" + }, + "m.will_expire": "ended" | "expired", + ``` + +- The homeserver stops the associated timer and invalidates (deletes) the `expire_refresh_token` + +So for each event that is sent with `m.will_expire: X` where X is duration in +seconds > 0. The homeserver will sent another event which can be used to trigger +logic on the client. This allows for any generic timeout logic. + +Timed messages/reminders could also be implemented using this where clients ignore +the `"will_expire":"running"` events for a specific event type but render the +`"will_expire":"expired"` events. + +## Potential issues + +## Alternatives + +## Security considerations + +## Unstable prefix + +## Dependencies From 8839b8d97d17ac5dcc00fd98b62160fb78eb8395 Mon Sep 17 00:00:00 2001 From: Timo K Date: Tue, 7 May 2024 18:53:38 +0200 Subject: [PATCH 02/85] Add msc number Signed-off-by: Timo K --- ...ents.md => 4140-expiring-events-with-keep-alive-endpoint.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename proposals/{0000-keep-alive-state-events.md => 4140-expiring-events-with-keep-alive-endpoint.md} (98%) diff --git a/proposals/0000-keep-alive-state-events.md b/proposals/4140-expiring-events-with-keep-alive-endpoint.md similarity index 98% rename from proposals/0000-keep-alive-state-events.md rename to proposals/4140-expiring-events-with-keep-alive-endpoint.md index 6eeb05865b5..b49671a353d 100644 --- a/proposals/0000-keep-alive-state-events.md +++ b/proposals/4140-expiring-events-with-keep-alive-endpoint.md @@ -1,4 +1,4 @@ -# MSC0000: Expiring events with keep alive functionality +# MSC4140: Expiring events with keep alive endpoint Currently there is not mechanism for a client to provide a reliable way of communicating that an event is still valid. The best expiration method is to post From 8bf6db72551d77094c5dab91d4bd0fdf08b52b46 Mon Sep 17 00:00:00 2001 From: Timo K Date: Tue, 7 May 2024 19:03:04 +0200 Subject: [PATCH 03/85] add security consideration and alternatives Signed-off-by: Timo K --- ...xpiring-events-with-keep-alive-endpoint.md | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/proposals/4140-expiring-events-with-keep-alive-endpoint.md b/proposals/4140-expiring-events-with-keep-alive-endpoint.md index b49671a353d..ad16c189bf3 100644 --- a/proposals/4140-expiring-events-with-keep-alive-endpoint.md +++ b/proposals/4140-expiring-events-with-keep-alive-endpoint.md @@ -1,6 +1,6 @@ # MSC4140: Expiring events with keep alive endpoint -Currently there is not mechanism for a client to provide a reliable way of +Currently there is no mechanism for a client to provide a reliable way of communicating that an event is still valid. The best expiration method is to post another event that is stores that it is expired. In some situations the client just looses connection or fails to sent the expired @@ -121,8 +121,29 @@ the `"will_expire":"running"` events for a specific event type but render the ## 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. + ## Security considerations +We are using unauthenticated endpoint to refresh the expirations. Since we use +the token it is hard to guess a correct endpoint and randomly end `will_expire` +events. + +It is an intentional decision to not provide an endpoint like +`PUT /_matrix/client/v3/expiration/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 `will_expire` event. + ## Unstable prefix ## Dependencies + From 8ec637418a2f16ff35a8a902fe401a229692412b Mon Sep 17 00:00:00 2001 From: Timo K Date: Wed, 8 May 2024 18:09:26 +0200 Subject: [PATCH 04/85] alternative name and alternative content Signed-off-by: Timo K --- ...xpiring-events-with-keep-alive-endpoint.md | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/proposals/4140-expiring-events-with-keep-alive-endpoint.md b/proposals/4140-expiring-events-with-keep-alive-endpoint.md index ad16c189bf3..917bff14995 100644 --- a/proposals/4140-expiring-events-with-keep-alive-endpoint.md +++ b/proposals/4140-expiring-events-with-keep-alive-endpoint.md @@ -31,22 +31,21 @@ Request ```json { - "content": { "m.will_expire": 10, - "other_content": "hello" - } + "body": "hello" } ``` If the homeserver detects a `m.expired` field it will store and distribute the -event as: +event as hiding the timeout duration: ```json { - "content": { + "content":{ "m.will_expire": "running", - "other_content": "hello" - } + "body": "hello", + }, + "other_fields":"sender, origin_server_ts ..." } ``` @@ -72,8 +71,8 @@ The body contains the refresh token so the homeserver knows what to refresh. } ``` -The information of this endpoint is very limited so that almost no metadata is -leaked when using this endpoint. This allows to share a refresh link to a different +The information required to call this endpoint is very limited so that almost +no metadata is leaked when. This allows to share a refresh link to a different service (an SFU for instance) that can track the current client connection state, and pings the HS to refresh and informs the HS about a disconnect. @@ -127,6 +126,28 @@ 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. +--- +It might not be necessary to change the value of `"m.will_expire" = 10` to +`"m.will_expire" = "running"` it makes it easier to understand and also +hides more potential metadata but it is questionable if that bring any benefit. + +--- +The name `m.will_expire` has been chosen since it communicates that it becomes +invalid. And that it is an event that automatically changes state +(`will_expire` vs `expired`). But it does not imply what expired vs non expired +means, it is flexible in how can be used. +Alternatives could by: + +- `m.alive` + - pro: communicates it might change (alive is always temporal) + - con: ver strong bias on how to use it `valid/invalid` +- `m.timeout` + - pro: very unbiased in how its used - timeout over can also mean the client + will show a reminder. + - pro: clear that it has something to do with time. + - con: not so clear the homeserver will automatically do sth. + - con: not so clear that this timeout can be refreshed? + ## Security considerations We are using unauthenticated endpoint to refresh the expirations. Since we use From 9f45cfa62b9c96a355758d4452dcd7430faad6c8 Mon Sep 17 00:00:00 2001 From: Timo K Date: Wed, 8 May 2024 20:48:06 +0200 Subject: [PATCH 05/85] review andrewF Signed-off-by: Timo K --- ...xpiring-events-with-keep-alive-endpoint.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/proposals/4140-expiring-events-with-keep-alive-endpoint.md b/proposals/4140-expiring-events-with-keep-alive-endpoint.md index 917bff14995..392a86c310c 100644 --- a/proposals/4140-expiring-events-with-keep-alive-endpoint.md +++ b/proposals/4140-expiring-events-with-keep-alive-endpoint.md @@ -19,12 +19,12 @@ time window after a user disconnected. ## Proposal -Events can contain a `m.will_expire: "running" | "expired" | "ended"` field. -This is an enum marking the event as -expired `m.will_expire: "expired" | "ended"` or still alive `m.will_expire: "running"`. +Events can contain a `"m.will_expire": "running" | "expired" | "ended"` field. +This is marking the event as expired `"m.will_expire": "expired" | "ended"` or as +still alive `"m.will_expire": "running"`. This field lives outside the ciphertext content (hence it also works for encrypted events) and is set via the usual `PUT` request if the content contains the additional -`m.will_expire: 10` field (similar how it is done with relations), with the desired +`"m.will_expire": 10` field (similar how it is done with relations), with the desired timeout duration in seconds. Request @@ -36,7 +36,7 @@ Request } ``` -If the homeserver detects a `m.expired` field it will store and distribute the +If the homeserver detects a `m.will_expire` field it will store and distribute the event as hiding the timeout duration: ```json @@ -53,8 +53,8 @@ The response to the client will be: ```json { - "eventId": "hash_id", - "expire_refresh_token": "hash_refresh", + "eventId": "id_hash", + "expire_refresh_token": "refresh_hash", } ``` @@ -62,34 +62,34 @@ The default response is extended with the `expire_refresh_token` which can be used to reset the expiration timeout (in this example 10 seconds). A new unauthenticated endpoint is introduced: `PUT /_matrix/client/v3/expiration/{refresh_method}` -where the `refresh_method` is either: `refresh`, `end` +where the `refresh_method` is one of: `[refresh, end]` The body contains the refresh token so the homeserver knows what to refresh. ```json { - "expire_refresh_token": "hash_refresh", + "expire_refresh_token": "refresh_hash", } ``` The information required to call this endpoint is very limited so that almost -no metadata is leaked when. This allows to share a refresh link to a different +no metadata is leaked. This allows to share a refresh link to a different service (an SFU for instance) that can track the current client connection state, and pings the HS to refresh and informs the HS about a disconnect. -The homeserver does the following when receiving an event with `m.expired` +The homeserver does the following when receiving an event with `m.will_expire` - It generates a token and stores it alongside with the time of retrieval, the eventId and the expire duration. -- Starts a counter for the stored expiation token. +- Starts a timer for the stored expiration token. - If a `PUT /_matrix/client/v3/expiration/refresh` is received, the timer is restarted with the stored expire duration. - If a `PUT /_matrix/client/v3/expiration/end` is received, the event _gets ended_. - If the timer times out, the event _gets expired_. - If the event is a state event only the latest/current state is considered. If - the homeserver receives a new state event without `m.expires` but with the same - state key, the expire_refresh_token gets invalidated and the associated timer is - stopped. + the homeserver receives a new state event without `m.will_expire` but with the + same state key, the expire_refresh_token gets invalidated and the associated timer + is stopped. The event _gets expired_/_gets ended_ means: From 54fff993729111028b67cf147ad5edc97bdd6746 Mon Sep 17 00:00:00 2001 From: Timo K Date: Fri, 10 May 2024 19:51:27 +0200 Subject: [PATCH 06/85] draft of iteration two (after meeting with the backend team) Signed-off-by: Timo K --- ...xpiring-events-with-keep-alive-endpoint.md | 199 +++++++++--------- 1 file changed, 100 insertions(+), 99 deletions(-) diff --git a/proposals/4140-expiring-events-with-keep-alive-endpoint.md b/proposals/4140-expiring-events-with-keep-alive-endpoint.md index 392a86c310c..b784c250270 100644 --- a/proposals/4140-expiring-events-with-keep-alive-endpoint.md +++ b/proposals/4140-expiring-events-with-keep-alive-endpoint.md @@ -19,102 +19,124 @@ time window after a user disconnected. ## Proposal -Events can contain a `"m.will_expire": "running" | "expired" | "ended"` field. -This is marking the event as expired `"m.will_expire": "expired" | "ended"` or as -still alive `"m.will_expire": "running"`. -This field lives outside the ciphertext content (hence it also works for encrypted -events) and is set via the usual `PUT` request if the content contains the additional -`"m.will_expire": 10` field (similar how it is done with relations), with the desired -timeout duration in seconds. +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. -Request +We call those events `Futures`. + +A new endpoint is introduced: +`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}/future` +and +`PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}/future` +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: ```json { - "m.will_expire": 10, - "body": "hello" + "m.timeout": 10, + "m.send_on_timeout": {...sendEventBody}, + + "m.send_on_action:${actionName}": {...sendEventBody}, + + // optional + "m.send_now": {...sendEventBody}, } ``` -If the homeserver detects a `m.will_expire` field it will store and distribute the -event as hiding the timeout duration: +Each of the `sendEventBody` objects are exactly the same as sending a normal +event. -```json -{ - "content":{ - "m.will_expire": "running", - "body": "hello", - }, - "other_fields":"sender, origin_server_ts ..." -} -``` +There can be an arbitrary amount of `actionName`s. + +All of the fields are optional except the `timeout` and the `send_on_timeout`. +This guarantees that all tokens will expire eventually. + +The homeserver can set a limit to the timeout and return an error if the limit +is exceeded. -The response to the client will be: +The response will mimic the request: ```json { - "eventId": "id_hash", - "expire_refresh_token": "refresh_hash", + + "m.send_on_timeout": { + "eventId": "id_hash" + }, + "m.send_on_action:${actionName}": { + "eventId": "id_hash" + }, + + "timeout_refresh_token": "refresh_token", + + // optional + "m.send_now": { "eventId": "id_hash"}, } ``` -The default response is extended with the `expire_refresh_token` which -can be used to reset the expiration timeout (in this example 10 seconds). -A new unauthenticated endpoint is introduced: -`PUT /_matrix/client/v3/expiration/{refresh_method}` -where the `refresh_method` is one of: `[refresh, end]` -The body contains the refresh token so the homeserver knows what to refresh. +The `refresh_token` can be used to call another future related endpoint: +`PUT /_matrix/client/v3/futures/refresh` and `PUT /_matrix/client/v3/futures/action/${actionName}`. +where the body is: ```json { - "expire_refresh_token": "refresh_hash", + "timeout_refresh_token":"refresh_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 (an SFU for instance) that can track the current client connection state, -and pings the HS to refresh and informs the HS about a disconnect. - -The homeserver does the following when receiving an event with `m.will_expire` - -- It generates a token and stores it alongside with the time of retrieval, -the eventId and the expire duration. -- Starts a timer for the stored expiration token. - - If a `PUT /_matrix/client/v3/expiration/refresh` is received, the - timer is restarted with the stored expire duration. - - If a `PUT /_matrix/client/v3/expiration/end` is received, the - event _gets ended_. - - If the timer times out, the event _gets expired_. - - If the event is a state event only the latest/current state is considered. If - the homeserver receives a new state event without `m.will_expire` but with the - same state key, the expire_refresh_token gets invalidated and the associated timer - is stopped. - -The event _gets expired_/_gets ended_ means: - -- The homeserver **sends a new event** that is a copy of the previous event but: - - If it gets _expired_ the event will include: `"m.will_expire": "expired"` - - If it gets _ended_ the event will include: `"m.will_expire": "ended"`. - - Additionally it includes a relation to the original event with `rel_type: "m.expire.relationship"` - - ```json - "m.relates_to": { - "event_id": "$original_event", - "rel_type": "m.expire.relationship" - }, - "m.will_expire": "ended" | "expired", - ``` - -- The homeserver stops the associated timer and invalidates (deletes) the `expire_refresh_token` - -So for each event that is sent with `m.will_expire: X` where X is duration in -seconds > 0. The homeserver will sent another event which can be used to trigger -logic on the client. This allows for any generic timeout logic. - -Timed messages/reminders could also be implemented using this where clients ignore -the `"will_expire":"running"` events for a specific event type but render the -`"will_expire":"expired"` events. +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 `timeout_refresh_token` and stores it alongside with the time +of retrieval, the event list and the timeout duration. +- Starts a timer for the stored `timeout_refresh_token`. + - If a `PUT /_matrix/client/v3/futures/refresh` is received, the + timer is restarted with the stored timeout duration. + - If a `PUT /_matrix/client/v3/futures/action/${actionName}` is received, one of + the associated `m.action:${actionName}` + event will be send. + - If the timer times out, the one of the `m.send_timeout` event will be sent. + - If the future + - is a state event (`PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}/future`) + - and includes a `m.send_now` event + + the future is only valid while the `m.send_now` + is still the current state. This means, if the homeserver receives + a new state event for the same state key, the `timeout_refresh_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 +- When a timeout or action future is sent, the homeserver stops the associated +timer and invalidates (deletes) the `timeout_refresh_token`. + +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. ## Potential issues @@ -126,43 +148,22 @@ 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. ---- -It might not be necessary to change the value of `"m.will_expire" = 10` to -`"m.will_expire" = "running"` it makes it easier to understand and also -hides more potential metadata but it is questionable if that bring any benefit. - ---- -The name `m.will_expire` has been chosen since it communicates that it becomes -invalid. And that it is an event that automatically changes state -(`will_expire` vs `expired`). But it does not imply what expired vs non expired -means, it is flexible in how can be used. -Alternatives could by: - -- `m.alive` - - pro: communicates it might change (alive is always temporal) - - con: ver strong bias on how to use it `valid/invalid` -- `m.timeout` - - pro: very unbiased in how its used - timeout over can also mean the client - will show a reminder. - - pro: clear that it has something to do with time. - - con: not so clear the homeserver will automatically do sth. - - con: not so clear that this timeout can be refreshed? - ## Security considerations -We are using unauthenticated endpoint to refresh the expirations. Since we use -the token it is hard to guess a correct endpoint and randomly end `will_expire` -events. +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/expiration/room/{roomId}/event/{eventId}` +`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 `will_expire` event. +information about the matrix metadata of the associated `future` event. It cannot +even tell with which room or user it is interacting. ## Unstable prefix From abdfe1c3399882e86ca376e626e5249005eeb5f7 Mon Sep 17 00:00:00 2001 From: Timo K Date: Mon, 13 May 2024 16:56:09 +0200 Subject: [PATCH 07/85] timeout_refresh_token is not a well description since the same token is used to trigger on of the actions Signed-off-by: Timo K --- ...140-expiring-events-with-keep-alive-endpoint.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/proposals/4140-expiring-events-with-keep-alive-endpoint.md b/proposals/4140-expiring-events-with-keep-alive-endpoint.md index b784c250270..c7ba302cd21 100644 --- a/proposals/4140-expiring-events-with-keep-alive-endpoint.md +++ b/proposals/4140-expiring-events-with-keep-alive-endpoint.md @@ -66,20 +66,20 @@ The response will mimic the request: "eventId": "id_hash" }, - "timeout_refresh_token": "refresh_token", + "future_token": "token", // optional "m.send_now": { "eventId": "id_hash"}, } ``` -The `refresh_token` can be used to call another future related endpoint: +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}`. where the body is: ```json { - "timeout_refresh_token":"refresh_token" + "future_token":"token" } ``` @@ -92,9 +92,9 @@ 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 `timeout_refresh_token` and stores it alongside with the time +- 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 `timeout_refresh_token`. +- Starts a timer for the stored `future_token`. - If a `PUT /_matrix/client/v3/futures/refresh` is received, the timer is restarted with the stored timeout duration. - If a `PUT /_matrix/client/v3/futures/action/${actionName}` is received, one of @@ -107,7 +107,7 @@ of retrieval, the event list and the timeout duration. the future is only valid while the `m.send_now` is still the current state. This means, if the homeserver receives - a new state event for the same state key, the `timeout_refresh_token` + 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: @@ -115,7 +115,7 @@ of retrieval, the event list and the timeout duration. 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 - When a timeout or action future is sent, the homeserver stops the associated -timer and invalidates (deletes) the `timeout_refresh_token`. +timer and invalidates (deletes) the `future_token`. 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. From 53f618648df827b2703897e122e9ca21221e2f0c Mon Sep 17 00:00:00 2001 From: Timo K Date: Tue, 14 May 2024 11:23:56 +0200 Subject: [PATCH 08/85] rename msc, rephrase introduction Signed-off-by: Timo K --- ...oint.md => 4140-delayed-events-futures.md} | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) rename proposals/{4140-expiring-events-with-keep-alive-endpoint.md => 4140-delayed-events-futures.md} (85%) diff --git a/proposals/4140-expiring-events-with-keep-alive-endpoint.md b/proposals/4140-delayed-events-futures.md similarity index 85% rename from proposals/4140-expiring-events-with-keep-alive-endpoint.md rename to proposals/4140-delayed-events-futures.md index c7ba302cd21..630771be737 100644 --- a/proposals/4140-expiring-events-with-keep-alive-endpoint.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,26 +1,31 @@ -# MSC4140: Expiring events with keep alive endpoint +# MSC4140: Delayed events (Futures) + +In the context of matrixRTC where we want +to ignore expired state events of users who left the call without sending a new +state empty `m.call.member` event. + +We would like the homeserver to mark this event as expired/send an expired version +in a reasonable time window after a user disconnected. Currently there is no mechanism for a client to provide a reliable way of -communicating that an event is still valid. The best expiration method is to post -another event that is stores that it is expired. +communicating that an event is still valid. +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 version of the event. -A generic way is desired in which the event gets marked as expired by the homeserver. - -Clients can then perform custom logic based on if the event is in valid or -expired state. -This is particularly interesting in the context of matrixRTC where we want -to ignore expired state events of users who left the call without sending a new -state empty `m.call.member` event. +A generic way in which one can automate expirations is desired. -We would like the homeserver to mark this event as expired in a reasonable -time window after a user disconnected. +The described usecase is solved if we allow to send an event in advance +to the homeserver but let the homeserver compute when its actually added to the +dag. +The condition for actually sending the delayed event would could be a timeout. ## Proposal -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. +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 exactly define what expiration means, +since any event that will be sent once expired can be defined. We call those events `Futures`. @@ -148,6 +153,14 @@ 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 + ## Security considerations We are using an unauthenticated endpoint to refresh the expirations. Since we use From 087c74ee88115d3b0bc8fea05d3d6538044efd4f Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 20 May 2024 12:55:21 +0200 Subject: [PATCH 09/85] Add usecase specific section. Add event type to the body Add event id template variable --- proposals/4140-delayed-events-futures.md | 158 +++++++++++++++++------ 1 file changed, 120 insertions(+), 38 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 630771be737..c157b077d5e 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -17,7 +17,7 @@ A generic way in which one can automate expirations is desired. The described usecase is solved if we allow to send an event in advance to the homeserver but let the homeserver compute when its actually added to the -dag. +DAG. The condition for actually sending the delayed event would could be a timeout. ## Proposal @@ -30,21 +30,28 @@ since any event that will be sent once expired can be defined. We call those events `Futures`. A new endpoint is introduced: -`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}/future` -and -`PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}/future` +`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` 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: ```json { "m.timeout": 10, - "m.send_on_timeout": {...sendEventBody}, - - "m.send_on_action:${actionName}": {...sendEventBody}, + "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": {...sendEventBody}, + "m.send_now": { + "content": sendEventBody2, + "type": "m.room.message" + }, } ``` @@ -59,75 +66,79 @@ This guarantees that all tokens will expire eventually. 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 { - "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"}, + "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}`. where the body is: ```json { - "future_token":"token" + "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 (an SFU for instance) that can track the current client connection state, +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, the - timer is restarted with the stored timeout duration. - - If a `PUT /_matrix/client/v3/futures/action/${actionName}` is received, one of - the associated `m.action:${actionName}` - event will be send. - - If the timer times out, the one of the `m.send_timeout` event will be sent. - - If the future - - is a state event (`PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}/future`) - - and includes a `m.send_now` event - +- 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}`. + - 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. 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. + is still the current state: + + - 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: + 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. + 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 -- When a timeout or action future is sent, the homeserver stops the associated -timer and invalidates (deletes) the `future_token`. + +- 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. +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. @@ -143,6 +154,78 @@ future at the same time. For this usecase an optional `m.send_now` field can be added to the body. +## 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 + +This MSC also allows to implement self destructing messages: + +`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 + +It would be useful to be able to send redactions and edits as one http request. +This would make sure that the client cannot loose connection after sending the first event. +For instance sending a self destructing message without the redaction. + +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: + +`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 @@ -181,4 +264,3 @@ even tell with which room or user it is interacting. ## Unstable prefix ## Dependencies - From 538b85349ea75580cdb4cd6ff82c858289b2c6b4 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 20 May 2024 14:49:52 +0200 Subject: [PATCH 10/85] add GET futures endpoint --- proposals/4140-delayed-events-futures.md | 32 +++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index c157b077d5e..3ba4a39652d 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -72,12 +72,8 @@ The response will mimic the request: ```json { - "m.send_on_timeout": { - "eventId": "id_hash" - }, - "m.send_on_action:${actionName}": { - "eventId": "id_hash" - }, + "m.send_on_timeout": { "eventId": "id_hash" }, + "m.send_on_action:${actionName}": { "eventId": "id_hash" }, "future_token": "token", @@ -154,6 +150,30 @@ future at the same time. For this usecase an optional `m.send_now` field can be added to the body. +### 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 +[ + { + "m.send_now": finalEvent_0, + "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 +this information in the event itself so that the usage of +this endpoint can be minimized. + ## Usecase specific considerations ### MatrixRTC From f7a1aad0a372aeeec19439b7a77f1452d0cc95a2 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 21 May 2024 15:13:56 +0200 Subject: [PATCH 11/85] shorten introduction --- proposals/4140-delayed-events-futures.md | 30 +++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 3ba4a39652d..c1401c2518f 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,33 +1,31 @@ # MSC4140: Delayed events (Futures) -In the context of matrixRTC where we want -to ignore expired state events of users who left the call without sending a new -state empty `m.call.member` event. +Allowing to schdule/delay events would solve numerous issues in +matrix. -We would like the homeserver to mark this event as expired/send an expired version -in a reasonable time window after a user disconnected. +- Updating call member events after the user disconnected. +- Sending scheduled messages (send at a specific time) +- Creating self destructing events (By sending a delayed redact) -Currently there is no mechanism for a client to provide a reliable way of -communicating that an event is still valid. +Currently there is no mechanism for a client to reliably that an event is still valid. 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 -version of the event. +version of the event. This proposal also includes a expiration/timeout +system so that those scenarios are also covered. -A generic way in which one can automate expirations is desired. - -The described usecase is solved if we allow to send an event in advance -to the homeserver but let the homeserver compute when its actually added to the +We want to send an event in advance +to the homeserver but let the homeserver decide the time when its actually added to the DAG. -The condition for actually sending the delayed event would could be a timeout. +The condition for actually sending the delayed event would could be a timeout or a external trigger via a synapse endpoint. ## 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 exactly define what expiration means, -since any event that will be sent once expired can be defined. +events to an external services. This allows to a very flexible way to mark events as expired, +since the sender can choose what event will be sent once expired. -We call those events `Futures`. +We call those delayed events `Futures`. A new endpoint is introduced: `PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` From f5f4b380495ff957b362469a187f15c88eed2638 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 22 May 2024 18:13:58 +0200 Subject: [PATCH 12/85] add alternative section to not include the `m.send_now` field --- proposals/4140-delayed-events-futures.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index c1401c2518f..83cef996a36 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -262,6 +262,20 @@ The following names for the endpoint are considered - DelayedEvents - RetardedEvents +--- + +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. +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 @@ -281,4 +295,6 @@ 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. + ## Dependencies From c16afbc549b6407ce4d4d46c006dd4ea8ea977a7 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Fri, 31 May 2024 09:20:45 +0200 Subject: [PATCH 13/85] Update proposals/4140-delayed-events-futures.md Co-authored-by: Andrew Ferrazzutti --- proposals/4140-delayed-events-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 83cef996a36..45116eacd65 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -83,7 +83,7 @@ The response will mimic the request: ### 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}`. +`POST /_matrix/client/v3/futures/refresh` and `POST /_matrix/client/v3/futures/action/${actionName}`. where the body is: ```json From c52c80d2566bf17567af8a941f703ab78d460b34 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 31 May 2024 09:22:32 +0200 Subject: [PATCH 14/85] batch sending considerations --- proposals/4140-delayed-events-futures.md | 44 +++++++++++++++--------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 45116eacd65..8443a138e3a 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -9,7 +9,7 @@ matrix. Currently there is no mechanism for a client to reliably that an event is still valid. 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 +In some situations the client just loses connection or fails to sent the expired version of the event. This proposal also includes a expiration/timeout system so that those scenarios are also covered. @@ -34,28 +34,28 @@ to send a list of event contents. The body looks as following: ```json { - "m.timeout": 10, - "m.send_on_timeout": { - "content": sendEventBody0, - "type": "m.room.message", - }, + "timeout": 10, + "send_on_timeout": {...fullSignedEvent}, - "m.send_on_action:${actionName}": { - "content": sendEventBody1, - "type": "m.room.message" - }, + "send_on_action":{ + "${action1}": {...fullSignedEvent}, + "${action2}": {...fullSignedEvent}, + ... + } // optional - "m.send_now": { - "content": sendEventBody2, - "type": "m.room.message" - }, + "send_now": {...fullSignedEvent}, } ``` Each of the `sendEventBody` objects are exactly the same as sending a normal event. +Power levels are evaluated once one of the events will be distributed/inserted into the DAG. +This implies a future can fail if it violates power levels at the time it resolves. +(Its also possible to successfylly send a future the user has no permission to at the time of sending +if the power level situation has changed at the time the future resolves.) + There can be an arbitrary amount of `actionName`s. All of the fields are optional except the `timeout` and the `send_on_timeout`. @@ -215,6 +215,10 @@ This MSC also allows to implement self destructing messages: ## EventId template variable +> [!IMPORTANT] +> This proposes a stop gap solution. It is highly preferred to have a general batch sending solution. +> Also see the **Alternatives** section + It would be useful to be able to send redactions and edits as one http request. This would make sure that the client cannot loose connection after sending the first event. For instance sending a self destructing message without the redaction. @@ -244,6 +248,12 @@ The **Self destructing messages** example would simplify to: } ``` +With cryptographic identities events would be presigned. +The server will first send the finilized event to the client. +At this point the client has the id but the event is not in the DAG. +So it would be trivial to sign both the event and the redaction/related event +and then send them. + ## Potential issues ## Alternatives @@ -272,10 +282,12 @@ 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 +Sequence wise it makes 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) +Especially when we use pre-signed events not having `$m.send_now.event_id` seems to be the sane solution. + ## Security considerations We are using an unauthenticated endpoint to refresh the expirations. Since we use From bf22260c1fa4ad3877f63713aa3b4b51dabc8b58 Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:16:03 +0200 Subject: [PATCH 15/85] Update proposals/4140-delayed-events-futures.md Co-authored-by: Hugh Nimmo-Smith --- proposals/4140-delayed-events-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 8443a138e3a..0d945512ee9 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,6 +1,6 @@ # MSC4140: Delayed events (Futures) -Allowing to schdule/delay events would solve numerous issues in +Allowing to schdeule/delay events would solve numerous issues in matrix. - Updating call member events after the user disconnected. From 7f0d80fd3daec7ff1402b66720564fed07b8bc9b Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:16:26 +0200 Subject: [PATCH 16/85] Update proposals/4140-delayed-events-futures.md Co-authored-by: Hugh Nimmo-Smith --- proposals/4140-delayed-events-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 0d945512ee9..35b5a7035fd 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -302,7 +302,7 @@ 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 +information about the Matrix metadata of the associated `future` event. It cannot even tell with which room or user it is interacting. ## Unstable prefix From 7b192aced29472825e1b855b58e4e2f4fe3e129c Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Mon, 3 Jun 2024 20:16:41 +0200 Subject: [PATCH 17/85] Update proposals/4140-delayed-events-futures.md Co-authored-by: Hugh Nimmo-Smith --- proposals/4140-delayed-events-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 35b5a7035fd..5448fcdaaff 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,7 +1,7 @@ # MSC4140: Delayed events (Futures) Allowing to schdeule/delay events would solve numerous issues in -matrix. +Matrix. - Updating call member events after the user disconnected. - Sending scheduled messages (send at a specific time) From f3bf66d7f7f7d96dcf738cc67adfe2cff93399fa Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 3 Jun 2024 20:25:23 +0200 Subject: [PATCH 18/85] review --- proposals/4140-delayed-events-futures.md | 48 ++++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 5448fcdaaff..3ff19aec30a 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -4,25 +4,25 @@ Allowing to schdeule/delay events would solve numerous issues in Matrix. - Updating call member events after the user disconnected. -- Sending scheduled messages (send at a specific time) -- Creating self destructing events (By sending a delayed redact) +- Sending scheduled messages (send at a specific time). +- Creating self-destructing events (by sending a delayed redact). -Currently there is no mechanism for a client to reliably that an event is still valid. +Currently there is no mechanism for a client to reliably determine that an event is still valid. The only way to update an event is to post a new one. -In some situations the client just loses connection or fails to sent the expired -version of the event. This proposal also includes a expiration/timeout +In some situations the client just loses connection or fails to send the expired +version of the event. This proposal also includes an expiration/timeout system so that those scenarios are also covered. -We want to send an event in advance -to the homeserver but let the homeserver decide the time when its actually added to the +We want to send an event to the homeserver in advance, +but let the homeserver decide the time when its actually added to the DAG. -The condition for actually sending the delayed event would could be a timeout or a external trigger via a synapse endpoint. +The condition for actually sending the delayed event could be a timeout or a external trigger via an unauthenticated Synapse endpoint. ## 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, +events to an external service. This allows a very flexible way to mark events as expired, since the sender can choose what event will be sent once expired. We call those delayed events `Futures`. @@ -51,15 +51,15 @@ to send a list of event contents. The body looks as following: Each of the `sendEventBody` objects are exactly the same as sending a normal event. -Power levels are evaluated once one of the events will be distributed/inserted into the DAG. +Power levels are evaluated for each event only once the trigger has occurred and it will be distributed/inserted into the DAG. This implies a future can fail if it violates power levels at the time it resolves. -(Its also possible to successfylly send a future the user has no permission to at the time of sending +(Its also possible to successfully send a future the user has no permission to at the time of sending if the power level situation has changed at the time the future resolves.) -There can be an arbitrary amount of `actionName`s. +There can be an arbitrary number of `actionName`s. All of the fields are optional except the `timeout` and the `send_on_timeout`. -This guarantees that all tokens will expire eventually. +This guarantees that all tokens will eventually expire. The homeserver can set a limit to the timeout and return an error if the limit is exceeded. @@ -108,7 +108,7 @@ The homeserver does the following when receiving a Future. - 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}`. + `m.send_on_action:${actionName}`. - 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` @@ -122,7 +122,7 @@ The homeserver does the following when receiving a Future. 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 + - new event -> timeout: the new event will invalidate the future. - After the homeservers sends a timeout or action future event, the associated timer and `future_token` is canceled/invalidated. @@ -168,7 +168,7 @@ of the final events how they will end up in the DAG with the associated `future_ 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 +For self-destructing messages it is recommended to include this information in the event itself so that the usage of this endpoint can be minimized. @@ -187,9 +187,9 @@ We want can use the actions and the timeout for matrix rtc for the following sit 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 +### Self-destructing messages -This MSC also allows to implement self destructing messages: +This MSC also allows to implement self-destructing messages: `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}` @@ -219,14 +219,14 @@ This MSC also allows to implement self destructing messages: > This proposes a stop gap solution. It is highly preferred to have a general batch sending solution. > Also see the **Alternatives** section -It would be useful to be able to send redactions and edits as one http request. -This would make sure that the client cannot loose connection after sending the first event. -For instance sending a self destructing message without the redaction. +It would be useful to be able to send redactions and edits as one HTTP request. +This would handle the situation where otherwise the client might lose it's connectionafter sending the first event. +For instance, sending a self-destructing message without the redaction. 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: +The **Self-destructing messages** example would simplify to: `PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` @@ -277,7 +277,7 @@ The following names for the endpoint are considered 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. +It would come with the cost that there is no way to guarantee, that the current state and the future are received by the homeserver. 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. @@ -307,6 +307,6 @@ 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. +Use `io.element.` instead of `m.` as long as the MSC is not stable. ## Dependencies From 2d7b27d56243224083a417e42cd6253e096347e1 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 4 Jun 2024 12:26:08 +0200 Subject: [PATCH 19/85] add background to usecase specific considerations --- proposals/4140-delayed-events-futures.md | 35 +++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 3ff19aec30a..a47ba4e0198 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -176,7 +176,40 @@ this endpoint can be minimized. ### MatrixRTC -We want can use the actions and the timeout for matrix rtc for the following situations +#### Background + +MatrixRTC makes it necessary to have real time information about the current matrixRTC session. +To properly display room tiles and header in the room list (or compute a list of ongoing calls) need to know: + +- If there is a running session. +- What type that session has. +- Who and how many perople are currently participating. + +A particular delicat situation is that clients are not able to inform others if they loose connection. +There are numerous approaches to solve such a situation. They split into two categories: + +- Polling based + - Ask the users if they are still connected. + - Ask an RTC backend (SFU) who is connected. +- Timeout based + - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if its expired. + - Use Future events with a 10s timeout to send the disconnected from call in less then 10s after the user is not anymore pingin the `/refresh` endpoint. (or delegate the disconnect action to a service attached to the SFU) + +Polling based solution have a big overhead in complexity and network requests on the clients. +Example: + +> A room list with 100 rooms where there has been a call before in every room (or there is an ongoing call) would require the client to send a to-device message (or a request to the SFU) to every user that has an active state event to check if they are still online. Just to display the room tile properly. + +For displaying the room list timeout based approaches are much more reasonable because this allows computing matrixRTC metadata for a room to be synchronous. + +The current solution updates the room state every X minutes. This is not elegant since we basically resend room state with the same content. In large calls this could result in huge traffic/large DAGs (100 call members implies 100 state events every X minutes.) X cannot be a long duration because it is the duration after which we can consider the event as expired. Improper disconnects would result in the user being displayed as "still in the call" for X minutes (we want this to be as short as possible!) + +Additionally this approach requires perfect server client time synchronization to compute the expiration. +This is currently not possible over federation since `unsigned.age` is not available over federation. + +#### Possible solution + +With this proposal we can provide an elegant solution using actions and timeouts to only send one event for joining and one for leaving (reliably) - 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. From 1140ce9fe5b3addb9dced923afbd62c4d2915bda Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 6 Jun 2024 00:04:43 +0200 Subject: [PATCH 20/85] Simplify main proposal for widget api usage --- proposals/4140-delayed-events-futures.md | 458 +++++++++++++++-------- 1 file changed, 308 insertions(+), 150 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index a47ba4e0198..ba58b8747a1 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,134 +1,163 @@ # MSC4140: Delayed events (Futures) -Allowing to schdeule/delay events would solve numerous issues in -Matrix. + + +- [MSC4140: Delayed events Futures](#msc4140-delayed-events-futures) + - [Proposal](#proposal) + - [Response](#response) + - [Delegating futures](#delegating-futures) + - [Getting running futures](#getting-running-futures) + - [Usecase specific considerations](#usecase-specific-considerations) + - [MatrixRTC](#matrixrtc) + - [Background](#background) + - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) + - [Self-destructing messages](#self-destructing-messages) + - [Potential issues](#potential-issues) + - [Alternatives](#alternatives) + - [Reusing the send/state endpoint](#reusing-the-sendstate-endpoint) + - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) + - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) + - [Naming](#naming) + - [Security considerations](#security-considerations) + - [Unstable prefix](#unstable-prefix) + - [Dependencies](#dependencies) + + + +The motivation for this MSC is: Updating call member events after the user disconnected by allowing to schedule/delay/timeout/expire events in a generic way. +It turnes out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: - Updating call member events after the user disconnected. - Sending scheduled messages (send at a specific time). - Creating self-destructing events (by sending a delayed redact). -Currently there is no mechanism for a client to reliably determine that an event is still valid. -The only way to update an event is to post a new one. -In some situations the client just loses connection or fails to send the expired -version of the event. This proposal also includes an expiration/timeout -system so that those scenarios are also covered. +Currently there is no mechanism for a client to reliably share that they are not part of a call anymore. +A network issue can disconnect the client. The call is not working anymore and for that user the call ended. +Since the only way to update an event is to post a new one the room state will not represent the correct call state +until the user rejoins and disconnects intentionally so the client is still online sending/updating the room state +without this devices call membership. -We want to send an event to the homeserver in advance, -but let the homeserver decide the time when its actually added to the -DAG. -The condition for actually sending the delayed event could be a timeout or a external trigger via an unauthenticated Synapse endpoint. +The same happens if the user force quits the client without pressing the hangup button. (For example closing a browser tab.) + +There are numerous possible solution to solve the call member event expiration. They are covered in detail +in the [Usecase specific considerations/MatrixRTC](#usecase-specific-considerations) section, because they are not part of the actual proposal. +In the introduction only an overview of considered options is given: + +- expiration using timestamp logic. +- expiration using bots/appservices. +- expiration with to-device messages/sfu polling. +- expiration with custom synapse logic based on the client sync loop. + +The preferred solution requires us to send an event to the homeserver in advance, +but let the homeserver decide the time/condition when its actually added to the DAG. +The condition for actually sending the delayed event could be a timeout or an external trigger +via an unauthenticated Synapse endpoint. +The Proposal section of this MSC will focus on how to achieve this. ## 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 service. This allows a very flexible way to mark events as expired, -since the sender can choose what event will be sent once expired. +To make this as generic as possible, the proposed solution is to allow sending events and delegate +the control of when to actually send these events to an external service or a timeout condition. +This allows a very flexible way to mark events as expired. +The sender can define what event will be sent once the timeout condition is met. For state +events the timed out version of the event would be an event where the content communicates, that +the users has left the call. + +This proposal also includes a way to refresh the timeout. Allowing to delay the event multiple times. +A periodic ping of the refreshing can be used as a heardbeat mechanism. Once the refresh ping is not send +anymore the timeout conditino is met and the homerver sends the event with the expired content information. +This translate to: _"only send the event when the client is not running the its program anymore (not sending the heartbeat anymore)"_ We call those delayed events `Futures`. -A new endpoint is introduced: -`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` -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: +New endpoints are introduced: -```json -{ - "timeout": 10, - "send_on_timeout": {...fullSignedEvent}, +`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{txnId}?timeout={timeout_duration}&future_group_id={group_id}` - "send_on_action":{ - "${action1}": {...fullSignedEvent}, - "${action2}": {...fullSignedEvent}, - ... - } +`PUT /_matrix/client/v3/rooms/{roomId}/state_future/{eventType}/{stateKey}?timeout={timeout_duration}&future_group_id={group_id}` - // optional - "send_now": {...fullSignedEvent}, -} -``` +Those behave like the normal `send`/`state` endpoints except that that they allow +to define a `timeout` and a `future_group_id` in their query parameters. -Each of the `sendEventBody` objects are exactly the same as sending a normal -event. +The **`future_group_id`** is an identifier defined by the client. +The purpose of this identifier is to group multiple futures in one mutually exclusive group. +Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. +All other events will be discarded. +One group can only contain one event with a `timeout` (timeout futures). The other events do not have a timeout (action futures) and are send +as an mutually exclusive alternative to the event send with `timeout`. +We call these timeout futures and action futures. +The server will respond with a [`409`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) +(`Conflict` This response is sent when a request conflicts with the current state of the server.) if +the client tries to send an action future without there being a timeout future with the same `group_id` + +The body is the same as sending a normal event. Power levels are evaluated for each event only once the trigger has occurred and it will be distributed/inserted into the DAG. This implies a future can fail if it violates power levels at the time it resolves. -(Its also possible to successfully send a future the user has no permission to at the time of sending +(It's also possible to successfully send a future the user has no permission to at the time of sending if the power level situation has changed at the time the future resolves.) -There can be an arbitrary number of `actionName`s. - -All of the fields are optional except the `timeout` and the `send_on_timeout`. -This guarantees that all tokens will eventually expire. - -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: +The response will include a `send_token` and a optional `refresh_token` but no `event_id` since the `event_id` depends on the `origin_server_ts` which is not yet determined. A timeout future will contain both, `send_token` and `refresh_token` but an action future will only have a `action_token` in its body. ```json { - "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" } + // always present + "send_token": "token", + // optional if there is a timeout + "refresh_token": "token", + "cancel_token": "token" } ``` ### Delegating futures -The `token` can be used to call another future related endpoint: -`POST /_matrix/client/v3/futures/refresh` and `POST /_matrix/client/v3/futures/action/${actionName}`. -where the body is: - -```json -{ - "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.send_on_action:${actionName}`. - - 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: - - - 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**. +This MSC also proposes a `futures` endpoint. +The `token` can be used to call this public `futures` endpoint: +`POST /_matrix/client/v3/futures/{token}` + +The information required to call this endpoint is minimal so that +no metadata is leaked when sharing the refresh/send url with a third party. +Since the refresh and send tokens are of the same format it is not even possible to evaluate +what that token is for when reading the https request log. +This unauthenticated endpoint allows to delegate resolving the future. +An SFU for instance, that tracks the current client connection state, could get a url that it +needs to call every X hours while a user is connected and a url it has to call once the user disconnects. +This way the SFU can be used as the source of truth for the call member room state even if the client +gets closed or looses connection and without knowing anything about the matrix call. + +The homeserver does the following when receiving a Future: + +- It checks for the validity of the request (based on the `timeout` and the `group_id` query parameters) + and returns a `409` if necessary. +- It **generates** a `send_token` and optionally a `refresh_token`, `cancel_token` and stores them alongside the time + of retrieval, the `group_id` and the `timeout_duration`. +- If `timeout` was present, it **Starts a timer** for the `refresh_token`. + + - If a `PUT /_matrix/client/v3/futures/{refresh_token}` is received, it + **restarts the timer** with the stored timeout duration for the associated timeout future. + - If a `PUT /_matrix/client/v3/futures/{send_token}` is received, it **sends the associated action or timeout future** + and deletes any stored futures with the `group_id` associated with that token. + - If a `PUT /_matrix/client/v3/futures/{cancel_token}` is received, it **does NOT send any future** + and deletes any stored futures with the `group_id` associated with that token. + - If a timer times out, **it sends the timeout future**. + - If the homeserver receives a _new state event_ with the same state key as existing futures the + **futures get invalidated and the associated timers are 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. + the _new state event_ will always converge to the _new state event_: + - Timeout -> _new state event_: the room state will be updated twice. once by + the content of the future but later with the content of _new state event_. + - _new state event_ -> timeout: the _new state event_ will invalidate the future. -- After the homeservers sends a timeout or action future event, the associated - timer and `future_token` is canceled/invalidated. +- After the homeservers sends a timeout future or action future, the associated + timer and tokens is canceled/deleted. + +So for each `group_id` the client sends, the homeserver will send one event +conditionally at an unknown time that can trigger logic on the client or +no event if the `/_matrix/client/v3/futures/{cancel_token}` was called. -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 @@ -146,36 +175,50 @@ future at the same time. 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. +For this usecase batch sending of multiple futures would be desired. + +We do not include batch sending in this MSC however since batch sending should +become a generic matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) ### Getting running futures -Using `GET /_matrix/client/v3/futures` it is possible to get the list of all running futures. +Using `GET /_matrix/client/v3/futures` it is possible to get the list of all running futures issues by the authenticated user. 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`. +of the final event content with the associated tokens. ```json [ { - "m.send_now": finalEvent_0, - "m.send_on_timeout": finalEvent_1, - ..., - - "future_token":"token" + "url":"https://domain/_matrix/client/v3/futures/{refresh_token}", + "body":{ + ...event_body + }, + "response":{ + // always present + "send_token": "token", + // optional if there is a timeout + "refresh_token": "token", + "cancel_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 recommended to include -this information in the event itself so that the usage of -this endpoint can be minimized. +And to optionally cancel tokens for them. + +For all usecases where the existence of a running future is also of interest for other room members, +(like self-destructing messages) it is recommended to include +this information in the effected event itself. ## Usecase specific considerations ### MatrixRTC +In this section an overview is given how this MSC is used in [MSC4143: MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) +and alternative expiration systems are evaluated. + #### Background MatrixRTC makes it necessary to have real time information about the current matrixRTC session. @@ -192,8 +235,10 @@ There are numerous approaches to solve such a situation. They split into two cat - Ask the users if they are still connected. - Ask an RTC backend (SFU) who is connected. - Timeout based + - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if its expired. - Use Future events with a 10s timeout to send the disconnected from call in less then 10s after the user is not anymore pingin the `/refresh` endpoint. (or delegate the disconnect action to a service attached to the SFU) + - Use the client sync loop as a special case timeout for call member events. (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) Polling based solution have a big overhead in complexity and network requests on the clients. Example: @@ -207,7 +252,7 @@ The current solution updates the room state every X minutes. This is not elegant Additionally this approach requires perfect server client time synchronization to compute the expiration. This is currently not possible over federation since `unsigned.age` is not available over federation. -#### Possible solution +#### How this MSC would be used for MatrixRTC With this proposal we can provide an elegant solution using actions and timeouts to only send one event for joining and one for leaving (reliably) @@ -224,6 +269,7 @@ With this proposal we can provide an elegant solution using actions and timeouts This MSC also allows to implement self-destructing messages: +First send (or generate the pdu when [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) is available): `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}` ```json @@ -232,34 +278,162 @@ This MSC also allows to implement self-destructing messages: } ``` +then send: +`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{txnId}?timeout={10*60}&future_group_id={XYZ}` + +```json +{ + "redacts": "{event_id}" +} +``` + +This would redact the message with content: `"m.text": "my msg"` after 10minutes. + +## Potential issues + +## Alternatives + +### Reusing the `send`/`state` endpoint + +Since the `send_future` and `state_future` endpoints are almost identical to the +normal `send` and `state` endpoints it comes to mind, that one could reuse them and allow adding the +query parameters `?timeout={timeout_duration}&future_group_id={group_id}` directly to the +`send` and `state` endpoint. + +This would be elegant but since those two endpoint are core to matrix changes to them might +be controversion if their return value is altered. + +Currently they always return + +```json +{ + "event_id":string +} +``` + +as a non optional field. + +When sending a future the `event_id` would not be available: + +- The `event_id` is using the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) which is + [calculated via the essential fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) + of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) +- Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) + we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. + +As a result the return type would change to: + +```json +{ + "event_id": string | undefined, + "send_token": string | undefined, + "refresh_token": string | undefined, + "cancel_token": "string | undefined +} +``` + +dependent on the query parameters. + +### Batch sending futures with custom endpoint + +The proposed solution does not allow to send multiple events/futures that belong to each other with one +HTTPS request. This is desired for self-destructing events and for matrixRTC room state events, where +we want the guarantee, that the event itself and the future removing the event both reach the homeserver +with one request. Otherwise there is a rist for the client to loose connecting or crash between sending the +event and the future which results in never expiring call memberhsip or never destructing self-destructing messages. +This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. +(Then the `timeout` and `future_group_id` could be added +to the `PDUInfo` instead of the query parameters and everything could be send at once.) + +This would be the preferred solution since we currently don't have any other batch sending mechanism. +before +It would however require lots of changes since a new widget action for futures would be needed. +With the current main proposal it is enough to add a timeout parameter to the send message widget action. + +An alternative to the proposed solution that allows this kind of batch sending would be to +introduce this endpoint: `PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` +It allows to send a list of event contents. The body looks as following: ```json { - "m.timeout": 10*60, - "m.send_on_timeout": { - "type":"m.room.readact", - "content":{ - "redacts": "EvId" - } - } + "timeout": 10, + "group_id": group_id, + + "send_on_timeout": { + "type":type, + "content":timeout_future_content + }, + + // optional + "send_on_action":{ + "${action1}": { + "type":type, + "content":action_future_content + }, + "${action2}": { + "type":type, + "content":action_future_content + }, + ... + }, + + // optional + "send_now": content, +} +``` + +We are sending the timeout and the group id inside the body and combine the timeout future +and the action future into one event. +Each of the `sendEventBody` objects are exactly the same as sending a normal +event. + +This is a batch endpoint that sends timeout and action futures at the same time. + +**Response** + +The response will be a collection of all the futures with the same fields as in the initial proposal: + +```json +{ + "send_on_timeout": { + "send_token": "token", + "refresh_token": "token", + "cancel_token": "token" + }, + // optional + "send_on_action": { + "${action1}": { "send_token": "token" }, + "${action2}": { "send_token": "token" } + }, + + // optional + "send_now": { "eventId": "id_hash" } } ``` -## EventId template variable +Working with futures is the same with this alternative. +This means, -> [!IMPORTANT] -> This proposes a stop gap solution. It is highly preferred to have a general batch sending solution. -> Also see the **Alternatives** section +- `GET /_matrix/client/v3/futures` getting running futures +- `POST /_matrix/client/v3/futures/{token}` cancel, refreshing and sending futures + +uses the exact same endpoints. +Also the behaviour of the homeserver on when to invalidate the furures is identical except, that +we don't need the error code `409` anymore since the events are sent as a batch and there cannot be +an action future without a timeout future. + +**EventId template variable** It would be useful to be able to send redactions and edits as one HTTP request. -This would handle the situation where otherwise the client might lose it's connectionafter sending the first event. -For instance, sending a self-destructing message without the redaction. +This would handle the cases where the futures need to reference the `send_now` event. +For instance, sending a self-destructing message where the redaction timeout future needs +to reference the event to redact. -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: +For this reason, template variables are introduced that are only valid in `Future` events. +`$m.send_now.event_id` in the content of one of the `send_on_action` and +`send_on_timeout` this template variable can be used. +The **Self-destructing messages** example be a single request: `PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` @@ -282,14 +456,12 @@ The **Self-destructing messages** example would simplify to: ``` With cryptographic identities events would be presigned. -The server will first send the finilized event to the client. +The server will first send the finalized event to the client. At this point the client has the id but the event is not in the DAG. So it would be trivial to sign both the event and the redaction/related event -and then send them. - -## Potential issues +and then send them via `/send_pdus` (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)). -## Alternatives +### MSC4018 (use client sync loop) [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 @@ -297,46 +469,32 @@ 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. ---- +### Naming -The following names for the endpoint are considered +The following alternative names for this concept are considered - Future - DelayedEvents - RetardedEvents - ---- - -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, that the current state and the future are received by the homeserver. -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 makes 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) - -Especially when we use pre-signed events not having `$m.send_now.event_id` seems to be the sane solution. +- PostponedEvents ## 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. +generated tokens it is hard to guess a correct request and force sending one +of the Futures. (The homeserver has them but they can always send events in your name +as long as we do not have [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) 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 +the expiration. With the token the client creating the future 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 -even tell with which room or user it is interacting. +even tell with which room or user it is interacting or what the token does (refresh vs send). ## Unstable prefix From 0a7896e92c9e1dc286ff4639fa7d0ef21546b9c2 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 6 Jun 2024 12:21:44 +0200 Subject: [PATCH 21/85] make `future_group_id` server generated and small adjustments --- proposals/4140-delayed-events-futures.md | 106 ++++++++++++++--------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index ba58b8747a1..c0338bc22cb 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -65,30 +65,43 @@ the users has left the call. This proposal also includes a way to refresh the timeout. Allowing to delay the event multiple times. A periodic ping of the refreshing can be used as a heardbeat mechanism. Once the refresh ping is not send -anymore the timeout conditino is met and the homerver sends the event with the expired content information. +anymore the timeout condition is met and the homerver sends the event with the expired content information. This translate to: _"only send the event when the client is not running the its program anymore (not sending the heartbeat anymore)"_ We call those delayed events `Futures`. New endpoints are introduced: -`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{txnId}?timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` -`PUT /_matrix/client/v3/rooms/{roomId}/state_future/{eventType}/{stateKey}?timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v3/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` Those behave like the normal `send`/`state` endpoints except that that they allow -to define a `timeout` and a `future_group_id` in their query parameters. - -The **`future_group_id`** is an identifier defined by the client. -The purpose of this identifier is to group multiple futures in one mutually exclusive group. -Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. -All other events will be discarded. -One group can only contain one event with a `timeout` (timeout futures). The other events do not have a timeout (action futures) and are send -as an mutually exclusive alternative to the event send with `timeout`. -We call these timeout futures and action futures. -The server will respond with a [`409`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) -(`Conflict` This response is sent when a request conflicts with the current state of the server.) if -the client tries to send an action future without there being a timeout future with the same `group_id` +to define `future_timeout` and `future_group_id` in their query parameters. + +- `future_timeout: number | "none"` is a required parameter that defines how long the homeserver will wait before sending + the event into the room. Since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. + - If set to `"none"` the future will never expire and can only be send by the [external delegation endpoint](#delegating-futures). + We call such a future **action future**. + - If set to a `number` we call the future **timeout future** +- `future_group_id: string` is optional if a `future_timeout` is a `number`. The purpose of this identifier is to group + **multiple futures in one mutually exclusive group**. + - Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. + All other futures will be discarded. + - Every future group needs at least one timeout future to guarantee that all future expire eventually. + - If a timeout future is send without a `future_group_id` a unique identifier will be generated by the + homeserver and is part of the `send_future`response. + +Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly and: + +- The server will respond with a [`409`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) + (`Conflict` This response is sent when a request conflicts with the current state of the server.) if + the client tries to send an action future without there being a timeout future with the same `future_group_id` +- The server can optionally configure a maximum `timeout_duration` + (In the order of one week dependent on how long they want to track futures) + The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, with a message + containing the maximum allowed `timeout_duration`) if the + client tries to send a timeout future with a larger `timeout_duration`. The body is the same as sending a normal event. @@ -106,6 +119,7 @@ The response will include a `send_token` and a optional `refresh_token` but no ` // always present "send_token": "token", // optional if there is a timeout + "future_group_id": "group_id", "refresh_token": "token", "cancel_token": "token" } @@ -125,18 +139,18 @@ This unauthenticated endpoint allows to delegate resolving the future. An SFU for instance, that tracks the current client connection state, could get a url that it needs to call every X hours while a user is connected and a url it has to call once the user disconnects. This way the SFU can be used as the source of truth for the call member room state even if the client -gets closed or looses connection and without knowing anything about the matrix call. +gets closed or looses connection and without knowing anything about the Matrix call. The homeserver does the following when receiving a Future: -- It checks for the validity of the request (based on the `timeout` and the `group_id` query parameters) - and returns a `409` if necessary. -- It **generates** a `send_token` and optionally a `refresh_token`, `cancel_token` and stores them alongside the time - of retrieval, the `group_id` and the `timeout_duration`. -- If `timeout` was present, it **Starts a timer** for the `refresh_token`. +- It checks for the validity of the request (based on the `future_timeout` and the `future_group_id` query parameters) + and returns a `409` or `400` if necessary. +- It **generates** a `send_token` and optionally a `future_group_id`, a `refresh_token` and a `cancel_token` and stores them alongside the time + of retrieval and the `timeout_duration`. +- If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. - If a `PUT /_matrix/client/v3/futures/{refresh_token}` is received, it - **restarts the timer** with the stored timeout duration for the associated timeout future. + **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - If a `PUT /_matrix/client/v3/futures/{send_token}` is received, it **sends the associated action or timeout future** and deletes any stored futures with the `group_id` associated with that token. - If a `PUT /_matrix/client/v3/futures/{cancel_token}` is received, it **does NOT send any future** @@ -154,14 +168,14 @@ The homeserver does the following when receiving a Future: - After the homeservers sends a timeout future or action future, the associated timer and tokens is canceled/deleted. -So for each `group_id` the client sends, the homeserver will send one event -conditionally at an unknown time that can trigger logic on the client or -no event if the `/_matrix/client/v3/futures/{cancel_token}` was called. +So for each `future_group_id`, the homeserver will at most send one timeline event. -This allows for any generic timeout logic. +- No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v3/futures/{cancel_token}`. +- Otherwise one of the timeout or action future will be emitted. -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. +Timed messages, tea timers, reminders or ephemeral events could be implemented +using this where clients send room events with +intentional mentions or a redaction as a future. In some scenarios it is important to allow to send an event with an associated future at the same time. @@ -177,8 +191,11 @@ future at the same time. For this usecase batch sending of multiple futures would be desired. -We do not include batch sending in this MSC however since batch sending should -become a generic matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) +We do not include batch sending in the proposal of this MSC however since batch sending should +become a generic Matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) + +There is a [batch sending version](#batch-sending-futures-with-custom-endpoint) in the Alternatives section +that proposes a future specific group sending endpoint in case this is required sooner then its realistic to implement [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). ### Getting running futures @@ -189,7 +206,7 @@ of the final event content with the associated tokens. ```json [ { - "url":"https://domain/_matrix/client/v3/futures/{refresh_token}", + "url":"/_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={timeout_duration}&future_group_id={group_id}", "body":{ ...event_body }, @@ -197,8 +214,9 @@ of the final event content with the associated tokens. // always present "send_token": "token", // optional if there is a timeout + "future_group_id": "group_id", "refresh_token": "token", - "cancel_token": "token" + "cancel_token": "token", } }, ] @@ -206,7 +224,7 @@ of the final event content with the associated tokens. This can be used so clients can optionally display events that will be send in the future. -And to optionally cancel tokens for them. +And to acquire cancel_tokens for then. For all usecases where the existence of a running future is also of interest for other room members, (like self-destructing messages) it is recommended to include @@ -221,7 +239,7 @@ and alternative expiration systems are evaluated. #### Background -MatrixRTC makes it necessary to have real time information about the current matrixRTC session. +MatrixRTC makes it necessary to have real time information about the current MatrixRTC session. To properly display room tiles and header in the room list (or compute a list of ongoing calls) need to know: - If there is a running session. @@ -245,7 +263,7 @@ Example: > A room list with 100 rooms where there has been a call before in every room (or there is an ongoing call) would require the client to send a to-device message (or a request to the SFU) to every user that has an active state event to check if they are still online. Just to display the room tile properly. -For displaying the room list timeout based approaches are much more reasonable because this allows computing matrixRTC metadata for a room to be synchronous. +For displaying the room list timeout based approaches are much more reasonable because this allows computing MatrixRTC metadata for a room to be synchronous. The current solution updates the room state every X minutes. This is not elegant since we basically resend room state with the same content. In large calls this could result in huge traffic/large DAGs (100 call members implies 100 state events every X minutes.) X cannot be a long duration because it is the duration after which we can consider the event as expired. Improper disconnects would result in the user being displayed as "still in the call" for X minutes (we want this to be as short as possible!) @@ -270,7 +288,7 @@ With this proposal we can provide an elegant solution using actions and timeouts This MSC also allows to implement self-destructing messages: First send (or generate the pdu when [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) is available): -`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}` +`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{eventType}/{txnId}` ```json { @@ -279,7 +297,7 @@ First send (or generate the pdu when [MSC4080: Cryptographic Identities](https:/ ``` then send: -`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{txnId}?timeout={10*60}&future_group_id={XYZ}` +`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={10*60}&future_group_id={XYZ}` ```json { @@ -297,10 +315,10 @@ This would redact the message with content: `"m.text": "my msg"` after 10minutes Since the `send_future` and `state_future` endpoints are almost identical to the normal `send` and `state` endpoints it comes to mind, that one could reuse them and allow adding the -query parameters `?timeout={timeout_duration}&future_group_id={group_id}` directly to the +query parameters `?future_timeout={timeout_duration}&future_group_id={group_id}` directly to the `send` and `state` endpoint. -This would be elegant but since those two endpoint are core to matrix changes to them might +This would be elegant but since those two endpoint are core to Matrix changes to them might be controversion if their return value is altered. Currently they always return @@ -326,6 +344,7 @@ As a result the return type would change to: ```json { "event_id": string | undefined, + "future_group_id": string | undefined, "send_token": string | undefined, "refresh_token": string | undefined, "cancel_token": "string | undefined @@ -337,18 +356,19 @@ dependent on the query parameters. ### Batch sending futures with custom endpoint The proposed solution does not allow to send multiple events/futures that belong to each other with one -HTTPS request. This is desired for self-destructing events and for matrixRTC room state events, where +HTTPS request. This is desired for self-destructing events and for MatrixRTC room state events, where we want the guarantee, that the event itself and the future removing the event both reach the homeserver with one request. Otherwise there is a rist for the client to loose connecting or crash between sending the event and the future which results in never expiring call memberhsip or never destructing self-destructing messages. This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. -(Then the `timeout` and `future_group_id` could be added +(Then the `future_timeout` and `future_group_id` could be added to the `PDUInfo` instead of the query parameters and everything could be send at once.) This would be the preferred solution since we currently don't have any other batch sending mechanism. before It would however require lots of changes since a new widget action for futures would be needed. -With the current main proposal it is enough to add a timeout parameter to the send message widget action. +With the current main proposal it is enough to add a `future_timeout` and `future_group_id` parameter to the send message widget action. +The widget driver would then take care of calling `send` or `send_future` based on the presence of those fields. An alternative to the proposed solution that allows this kind of batch sending would be to introduce this endpoint: @@ -412,6 +432,8 @@ The response will be a collection of all the futures with the same fields as in } ``` +We do not need a `future_group_id` since we will send one group in one request. + Working with futures is the same with this alternative. This means, From 8fa33d60bc0b2b108b3945e0fa34a03686328d91 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 6 Jun 2024 19:02:04 +0200 Subject: [PATCH 22/85] review --- proposals/4140-delayed-events-futures.md | 107 ++++++++++++++--------- 1 file changed, 67 insertions(+), 40 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index c0338bc22cb..1e8bcc1f6b8 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -25,7 +25,7 @@ The motivation for this MSC is: Updating call member events after the user disconnected by allowing to schedule/delay/timeout/expire events in a generic way. -It turnes out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: +It turns out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: - Updating call member events after the user disconnected. - Sending scheduled messages (send at a specific time). @@ -64,10 +64,10 @@ events the timed out version of the event would be an event where the content co the users has left the call. This proposal also includes a way to refresh the timeout. Allowing to delay the event multiple times. -A periodic ping of the refreshing can be used as a heardbeat mechanism. Once the refresh ping is not send -anymore the timeout condition is met and the homerver sends the event with the expired content information. +A periodic ping of the refreshing can be used as a heartbeat mechanism. Once the refresh ping is not send +anymore the timeout condition is met and the homeserver sends the event with the expired content information. -This translate to: _"only send the event when the client is not running the its program anymore (not sending the heartbeat anymore)"_ +This translate to: _"only send the event when the client is not running its program anymore (not sending the heartbeat anymore)"_ We call those delayed events `Futures`. New endpoints are introduced: @@ -79,18 +79,27 @@ New endpoints are introduced: Those behave like the normal `send`/`state` endpoints except that that they allow to define `future_timeout` and `future_group_id` in their query parameters. -- `future_timeout: number | "none"` is a required parameter that defines how long the homeserver will wait before sending - the event into the room. Since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. - - If set to `"none"` the future will never expire and can only be send by the [external delegation endpoint](#delegating-futures). +- `future_timeout: number` defines how long (in milliseconds) the homeserver will wait before sending + the event into the room. **Note**, since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. + - If this query parameter is not added the future will never expire and can only be send by the [external delegation endpoint](#delegating-futures). We call such a future **action future**. - - If set to a `number` we call the future **timeout future** + - If set to a `number` (ms) we call the future **timeout future** - `future_group_id: string` is optional if a `future_timeout` is a `number`. The purpose of this identifier is to group **multiple futures in one mutually exclusive group**. - Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. All other futures will be discarded. - Every future group needs at least one timeout future to guarantee that all future expire eventually. - - If a timeout future is send without a `future_group_id` a unique identifier will be generated by the - homeserver and is part of the `send_future`response. + - If a timeout future is sent without a `future_group_id` a unique identifier will be generated by the + homeserver and is part of the `send_future` response. + +Both of the query parameters are optional but one of them has to be present. +This gives us the following options: + +``` +?future_timeout=10 - a timeout future in a new future group +?future_timeout=10&future_group_id="groupA" - a timeout future added to groupA +?future_group_id="groupA" - an action future added to groupA +``` Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly and: @@ -112,16 +121,16 @@ if the power level situation has changed at the time the future resolves.) ### Response -The response will include a `send_token` and a optional `refresh_token` but no `event_id` since the `event_id` depends on the `origin_server_ts` which is not yet determined. A timeout future will contain both, `send_token` and `refresh_token` but an action future will only have a `action_token` in its body. +The response will include a `send_token`, `cancel_token`, the associated `future_group_id` and an optional `refresh_token` but no `event_id` since the `event_id` depends on the `origin_server_ts` which is not yet determined. A timeout future will contain `refresh_token` but an action future will not. ```json { // always present - "send_token": "token", - // optional if there is a timeout + "send_token": "send_token", + "cancel_token": "cancel_token", "future_group_id": "group_id", - "refresh_token": "token", - "cancel_token": "token" + // optional, only present if its a a timeout future response + "refresh_token": "refresh_token" } ``` @@ -145,7 +154,7 @@ The homeserver does the following when receiving a Future: - It checks for the validity of the request (based on the `future_timeout` and the `future_group_id` query parameters) and returns a `409` or `400` if necessary. -- It **generates** a `send_token` and optionally a `future_group_id`, a `refresh_token` and a `cancel_token` and stores them alongside the time +- It **generates** a `send_token`, a `cancel_token` and if not provided in the request a `future_group_id` and a optionally `refresh_token` and stores them alongside the time of retrieval and the `timeout_duration`. - If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. @@ -154,7 +163,10 @@ The homeserver does the following when receiving a Future: - If a `PUT /_matrix/client/v3/futures/{send_token}` is received, it **sends the associated action or timeout future** and deletes any stored futures with the `group_id` associated with that token. - If a `PUT /_matrix/client/v3/futures/{cancel_token}` is received, it **does NOT send any future** - and deletes any stored futures with the `group_id` associated with that token. + and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). + - If a `PUT /_matrix/client/v3/futures/{unknown_token}` is received the server responds with a `410` (Gone). + An `unknown_token` either means that the service is making something up or that the service is using a + token that is invalidated by now. - If a timer times out, **it sends the timeout future**. - If the homeserver receives a _new state event_ with the same state key as existing futures the **futures get invalidated and the associated timers are stopped**. @@ -171,7 +183,7 @@ The homeserver does the following when receiving a Future: So for each `future_group_id`, the homeserver will at most send one timeline event. - No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v3/futures/{cancel_token}`. -- Otherwise one of the timeout or action future will be emitted. +- Otherwise one of the timeout or action futures will be send. Timed messages, tea timers, reminders or ephemeral events could be implemented using this where clients send room events with @@ -206,17 +218,17 @@ of the final event content with the associated tokens. ```json [ { - "url":"/_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={timeout_duration}&future_group_id={group_id}", + "url":"/_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}", "body":{ ...event_body }, "response":{ // always present - "send_token": "token", - // optional if there is a timeout + "send_token": "send_token", + "cancel_token": "cancel_token", "future_group_id": "group_id", + // optional if there is a timeout "refresh_token": "token", - "cancel_token": "token", } }, ] @@ -244,9 +256,9 @@ To properly display room tiles and header in the room list (or compute a list of - If there is a running session. - What type that session has. -- Who and how many perople are currently participating. +- Who and how many people are currently participating. -A particular delicat situation is that clients are not able to inform others if they loose connection. +A particular delicate situation is that clients are not able to inform others if they lose connection. There are numerous approaches to solve such a situation. They split into two categories: - Polling based @@ -254,25 +266,39 @@ There are numerous approaches to solve such a situation. They split into two cat - Ask an RTC backend (SFU) who is connected. - Timeout based - - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if its expired. - - Use Future events with a 10s timeout to send the disconnected from call in less then 10s after the user is not anymore pingin the `/refresh` endpoint. (or delegate the disconnect action to a service attached to the SFU) - - Use the client sync loop as a special case timeout for call member events. (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) + - Update the room state every x seconds. + This allows clients to check how long an event has not been updated and ignore it if it's expired. + - Use Future events with a 10s timeout to send the disconnected from call + in less then 10s after the user is not anymore pinging the `/refresh` endpoint. + (or delegate the disconnect action to a service attached to the SFU) + - Use the client sync loop as a special case timeout for call member events. + (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) Polling based solution have a big overhead in complexity and network requests on the clients. Example: -> A room list with 100 rooms where there has been a call before in every room (or there is an ongoing call) would require the client to send a to-device message (or a request to the SFU) to every user that has an active state event to check if they are still online. Just to display the room tile properly. +> A room list with 100 rooms where there has been a call before in every room +> (or there is an ongoing call) would require the client to send a to-device message +> (or a request to the SFU) to every user that has an active state event to check if +> they are still online. Just to display the room tile properly. For displaying the room list timeout based approaches are much more reasonable because this allows computing MatrixRTC metadata for a room to be synchronous. -The current solution updates the room state every X minutes. This is not elegant since we basically resend room state with the same content. In large calls this could result in huge traffic/large DAGs (100 call members implies 100 state events every X minutes.) X cannot be a long duration because it is the duration after which we can consider the event as expired. Improper disconnects would result in the user being displayed as "still in the call" for X minutes (we want this to be as short as possible!) +The current solution updates the room state every X minutes. +This is not elegant since we basically resend room state with the same content. +In large calls this could result in huge traffic/large DAGs (100 call members +implies 100 state events every X minutes.) X cannot be a long duration because +it is the duration after which we can consider the event as expired. Improper +disconnects would result in the user being displayed as "still in the call" for +X minutes (we want this to be as short as possible!) Additionally this approach requires perfect server client time synchronization to compute the expiration. This is currently not possible over federation since `unsigned.age` is not available over federation. #### How this MSC would be used for MatrixRTC -With this proposal we can provide an elegant solution using actions and timeouts to only send one event for joining and one for leaving (reliably) +With this proposal we can provide an elegant solution using actions and timeouts +to only send one event for joining and one for leaving (reliably) - 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. @@ -287,7 +313,9 @@ With this proposal we can provide an elegant solution using actions and timeouts This MSC also allows to implement self-destructing messages: -First send (or generate the pdu when [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) is available): +First send (or generate the pdu when +[MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) +is available): `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{eventType}/{txnId}` ```json @@ -318,8 +346,8 @@ normal `send` and `state` endpoints it comes to mind, that one could reuse them query parameters `?future_timeout={timeout_duration}&future_group_id={group_id}` directly to the `send` and `state` endpoint. -This would be elegant but since those two endpoint are core to Matrix changes to them might -be controversion if their return value is altered. +This would be elegant but since those two endpoint are core to Matrix, changes to them might +be controversial if their return value is altered. Currently they always return @@ -355,17 +383,16 @@ dependent on the query parameters. ### Batch sending futures with custom endpoint -The proposed solution does not allow to send multiple events/futures that belong to each other with one +The proposed solution does not allow to send events together with futures that reference them with one HTTPS request. This is desired for self-destructing events and for MatrixRTC room state events, where we want the guarantee, that the event itself and the future removing the event both reach the homeserver -with one request. Otherwise there is a rist for the client to loose connecting or crash between sending the -event and the future which results in never expiring call memberhsip or never destructing self-destructing messages. +with one request. Otherwise there is a risk for the client to lose connection or crash between sending the +event and the future which results in never expiring call membership or never destructing self-destructing messages. This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. (Then the `future_timeout` and `future_group_id` could be added to the `PDUInfo` instead of the query parameters and everything could be send at once.) -This would be the preferred solution since we currently don't have any other batch sending mechanism. -before +This would be the preferred solution since we currently don't have any other batch sending mechanism. It would however require lots of changes since a new widget action for futures would be needed. With the current main proposal it is enough to add a `future_timeout` and `future_group_id` parameter to the send message widget action. The widget driver would then take care of calling `send` or `send_future` based on the presence of those fields. @@ -378,7 +405,6 @@ It allows to send a list of event contents. The body looks as following: ```json { "timeout": 10, - "group_id": group_id, "send_on_timeout": { "type":type, @@ -520,6 +546,7 @@ even tell with which room or user it is interacting or what the token does (refr ## Unstable prefix -Use `io.element.` instead of `m.` as long as the MSC is not stable. +Use `io.element.msc3140.` instead of `m.` as long as the MSC is not stable. +For the endpoints introduced in this MSC use the prefix `/io.element.msc3140/` and set the paths version string to unstable, instead of v#. ## Dependencies From 49d5294303f76f11fb6b6ac8897958545840b6e2 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 13 Jun 2024 16:56:47 +0200 Subject: [PATCH 23/85] user scoping details --- proposals/4140-delayed-events-futures.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 1e8bcc1f6b8..ad8b58f992f 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -91,6 +91,7 @@ to define `future_timeout` and `future_group_id` in their query parameters. - Every future group needs at least one timeout future to guarantee that all future expire eventually. - If a timeout future is sent without a `future_group_id` a unique identifier will be generated by the homeserver and is part of the `send_future` response. + - Group id's can only be used by one user. Reasons for this are, that this would basically allow full control over a future group once another matrix user knows the group id. It would also require to federate futures if the users are not on the same homeserver. Both of the query parameters are optional but one of them has to be present. This gives us the following options: @@ -111,6 +112,7 @@ Possible error responses are all error responses that can occur when using the ` The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, with a message containing the maximum allowed `timeout_duration`) if the client tries to send a timeout future with a larger `timeout_duration`. +- The future is using a group_id that belongs to a future group from another user. In this case the homeserver sends a [`405`] (`Not Allowed`). The body is the same as sending a normal event. From 7550d9b413565eddeff1057e8444fc21a66d275a Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Thu, 13 Jun 2024 23:55:09 +0200 Subject: [PATCH 24/85] Update proposals/4140-delayed-events-futures.md Co-authored-by: Andrew Ferrazzutti --- proposals/4140-delayed-events-futures.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index ad8b58f992f..f7467816cd3 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -160,13 +160,13 @@ The homeserver does the following when receiving a Future: of retrieval and the `timeout_duration`. - If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. - - If a `PUT /_matrix/client/v3/futures/{refresh_token}` is received, it + - If a `POST /_matrix/client/v3/futures/{refresh_token}` is received, it **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - - If a `PUT /_matrix/client/v3/futures/{send_token}` is received, it **sends the associated action or timeout future** + - If a `POST /_matrix/client/v3/futures/{send_token}` is received, it **sends the associated action or timeout future** and deletes any stored futures with the `group_id` associated with that token. - - If a `PUT /_matrix/client/v3/futures/{cancel_token}` is received, it **does NOT send any future** + - If a `POST /_matrix/client/v3/futures/{cancel_token}` is received, it **does NOT send any future** and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). - - If a `PUT /_matrix/client/v3/futures/{unknown_token}` is received the server responds with a `410` (Gone). + - If a `POST /_matrix/client/v3/futures/{unknown_token}` is received the server responds with a `410` (Gone). An `unknown_token` either means that the service is making something up or that the service is using a token that is invalidated by now. - If a timer times out, **it sends the timeout future**. From a663bb4299f55a6d1449a045e5fd5aa26a2bb236 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 14 Jun 2024 13:46:46 +0200 Subject: [PATCH 25/85] add rate limiting section --- proposals/4140-delayed-events-futures.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index f7467816cd3..a53bd8e7e08 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -155,7 +155,7 @@ gets closed or looses connection and without knowing anything about the Matrix c The homeserver does the following when receiving a Future: - It checks for the validity of the request (based on the `future_timeout` and the `future_group_id` query parameters) - and returns a `409` or `400` if necessary. + and returns a `409`, `400` or `405` if necessary. - It **generates** a `send_token`, a `cancel_token` and if not provided in the request a `future_group_id` and a optionally `refresh_token` and stores them alongside the time of retrieval and the `timeout_duration`. - If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. @@ -187,6 +187,15 @@ So for each `future_group_id`, the homeserver will at most send one timeline eve - No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v3/futures/{cancel_token}`. - Otherwise one of the timeout or action futures will be send. +**Rate limiting** the `POST /_matrix/client/v3/futures/{token}`endpoint: + +- A malicious party can try to find a correct token by randomly sending requests to this endpoint. +- Homeservers should rate limit this endpoint so that one can at + most send `N` invalid tokens in a row and then will get a `429` (Too Many Requests) + response for `T` seconds. We allow `N` invalid tokens because it is not + easy to compute if a token is still valid or not so it is useful to ask for this. +- The recommended values for `T` are 10 seconds and for `N` 5. (It is spec conform to use other values.) + Timed messages, tea timers, reminders or ephemeral events could be implemented using this where clients send room events with intentional mentions or a redaction as a future. From 99b3a20c322e72375d7fdb262ee1b62aac371f32 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 19 Jun 2024 16:33:31 +0200 Subject: [PATCH 26/85] rename `/futures` to `/future` --- proposals/4140-delayed-events-futures.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index a53bd8e7e08..72b9791d877 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -140,7 +140,7 @@ The response will include a `send_token`, `cancel_token`, the associated `future This MSC also proposes a `futures` endpoint. The `token` can be used to call this public `futures` endpoint: -`POST /_matrix/client/v3/futures/{token}` +`POST /_matrix/client/v3/future/{token}` The information required to call this endpoint is minimal so that no metadata is leaked when sharing the refresh/send url with a third party. @@ -160,13 +160,13 @@ The homeserver does the following when receiving a Future: of retrieval and the `timeout_duration`. - If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. - - If a `POST /_matrix/client/v3/futures/{refresh_token}` is received, it + - If a `POST /_matrix/client/v3/future/{refresh_token}` is received, it **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - - If a `POST /_matrix/client/v3/futures/{send_token}` is received, it **sends the associated action or timeout future** + - If a `POST /_matrix/client/v3/future/{send_token}` is received, it **sends the associated action or timeout future** and deletes any stored futures with the `group_id` associated with that token. - - If a `POST /_matrix/client/v3/futures/{cancel_token}` is received, it **does NOT send any future** + - If a `POST /_matrix/client/v3/future/{cancel_token}` is received, it **does NOT send any future** and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). - - If a `POST /_matrix/client/v3/futures/{unknown_token}` is received the server responds with a `410` (Gone). + - If a `POST /_matrix/client/v3/future/{unknown_token}` is received the server responds with a `410` (Gone). An `unknown_token` either means that the service is making something up or that the service is using a token that is invalidated by now. - If a timer times out, **it sends the timeout future**. @@ -184,10 +184,10 @@ The homeserver does the following when receiving a Future: So for each `future_group_id`, the homeserver will at most send one timeline event. -- No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v3/futures/{cancel_token}`. +- No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v3/future/{cancel_token}`. - Otherwise one of the timeout or action futures will be send. -**Rate limiting** the `POST /_matrix/client/v3/futures/{token}`endpoint: +**Rate limiting** the `POST /_matrix/client/v3/future/{token}`endpoint: - A malicious party can try to find a correct token by randomly sending requests to this endpoint. - Homeservers should rate limit this endpoint so that one can at @@ -222,7 +222,7 @@ that proposes a future specific group sending endpoint in case this is required ### Getting running futures -Using `GET /_matrix/client/v3/futures` it is possible to get the list of all running futures issues by the authenticated user. +Using `GET /_matrix/client/v3/future` it is possible to get the list of all running futures issues by the authenticated user. This is an authenticated endpoint. It sends back the json of the final event content with the associated tokens. @@ -474,8 +474,8 @@ We do not need a `future_group_id` since we will send one group in one request. Working with futures is the same with this alternative. This means, -- `GET /_matrix/client/v3/futures` getting running futures -- `POST /_matrix/client/v3/futures/{token}` cancel, refreshing and sending futures +- `GET /_matrix/client/v3/future` getting running futures +- `POST /_matrix/client/v3/future/{token}` cancel, refreshing and sending futures uses the exact same endpoints. Also the behaviour of the homeserver on when to invalidate the furures is identical except, that @@ -545,7 +545,7 @@ of the Futures. (The homeserver has them but they can always send events in your as long as we do not have [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) It is an intentional decision to not provide an endpoint like -`PUT /_matrix/client/v3/futures/room/{roomId}/event/{eventId}` +`PUT /_matrix/client/v3/future/room/{roomId}/event/{eventId}` where any client with access to the room could also `end` or `refresh` the expiration. With the token the client creating the future has ownership over the expiration and only intentional delegation of that ownership From eb50a1947a3a0712bf1f20dce95832ef2b36ff16 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 20 Jun 2024 10:52:24 +0200 Subject: [PATCH 27/85] Update everything to v1 and use proper msc number in unstable prefix section. --- proposals/4140-delayed-events-futures.md | 40 ++++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 72b9791d877..af8e85acad8 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -72,9 +72,9 @@ We call those delayed events `Futures`. New endpoints are introduced: -`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` -`PUT /_matrix/client/v3/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` Those behave like the normal `send`/`state` endpoints except that that they allow to define `future_timeout` and `future_group_id` in their query parameters. @@ -140,7 +140,7 @@ The response will include a `send_token`, `cancel_token`, the associated `future This MSC also proposes a `futures` endpoint. The `token` can be used to call this public `futures` endpoint: -`POST /_matrix/client/v3/future/{token}` +`POST /_matrix/client/v1/future/{token}` The information required to call this endpoint is minimal so that no metadata is leaked when sharing the refresh/send url with a third party. @@ -160,13 +160,13 @@ The homeserver does the following when receiving a Future: of retrieval and the `timeout_duration`. - If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. - - If a `POST /_matrix/client/v3/future/{refresh_token}` is received, it + - If a `POST /_matrix/client/v1/future/{refresh_token}` is received, it **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - - If a `POST /_matrix/client/v3/future/{send_token}` is received, it **sends the associated action or timeout future** + - If a `POST /_matrix/client/v1/future/{send_token}` is received, it **sends the associated action or timeout future** and deletes any stored futures with the `group_id` associated with that token. - - If a `POST /_matrix/client/v3/future/{cancel_token}` is received, it **does NOT send any future** + - If a `POST /_matrix/client/v1/future/{cancel_token}` is received, it **does NOT send any future** and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). - - If a `POST /_matrix/client/v3/future/{unknown_token}` is received the server responds with a `410` (Gone). + - If a `POST /_matrix/client/v1/future/{unknown_token}` is received the server responds with a `410` (Gone). An `unknown_token` either means that the service is making something up or that the service is using a token that is invalidated by now. - If a timer times out, **it sends the timeout future**. @@ -184,10 +184,10 @@ The homeserver does the following when receiving a Future: So for each `future_group_id`, the homeserver will at most send one timeline event. -- No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v3/future/{cancel_token}`. +- No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v1/future/{cancel_token}`. - Otherwise one of the timeout or action futures will be send. -**Rate limiting** the `POST /_matrix/client/v3/future/{token}`endpoint: +**Rate limiting** the `POST /_matrix/client/v1/future/{token}`endpoint: - A malicious party can try to find a correct token by randomly sending requests to this endpoint. - Homeservers should rate limit this endpoint so that one can at @@ -222,14 +222,14 @@ that proposes a future specific group sending endpoint in case this is required ### Getting running futures -Using `GET /_matrix/client/v3/future` it is possible to get the list of all running futures issues by the authenticated user. +Using `GET /_matrix/client/v1/future` it is possible to get the list of all running futures issues by the authenticated user. This is an authenticated endpoint. It sends back the json of the final event content with the associated tokens. ```json [ { - "url":"/_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}", + "url":"/_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}", "body":{ ...event_body }, @@ -327,7 +327,7 @@ This MSC also allows to implement self-destructing messages: First send (or generate the pdu when [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) is available): -`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{eventType}/{txnId}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{eventType}/{txnId}` ```json { @@ -336,7 +336,7 @@ is available): ``` then send: -`PUT /_matrix/client/v3/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={10*60}&future_group_id={XYZ}` +`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={10*60}&future_group_id={XYZ}` ```json { @@ -410,7 +410,7 @@ The widget driver would then take care of calling `send` or `send_future` based An alternative to the proposed solution that allows this kind of batch sending would be to introduce this endpoint: -`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/future/{txnId}` It allows to send a list of event contents. The body looks as following: ```json @@ -474,8 +474,8 @@ We do not need a `future_group_id` since we will send one group in one request. Working with futures is the same with this alternative. This means, -- `GET /_matrix/client/v3/future` getting running futures -- `POST /_matrix/client/v3/future/{token}` cancel, refreshing and sending futures +- `GET /_matrix/client/v1/future` getting running futures +- `POST /_matrix/client/v1/future/{token}` cancel, refreshing and sending futures uses the exact same endpoints. Also the behaviour of the homeserver on when to invalidate the furures is identical except, that @@ -494,7 +494,7 @@ For this reason, template variables are introduced that are only valid in `Futur `send_on_timeout` this template variable can be used. The **Self-destructing messages** example be a single request: -`PUT /_matrix/client/v3/rooms/{roomId}/send/future/{txnId}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/future/{txnId}` ```json { @@ -545,7 +545,7 @@ of the Futures. (The homeserver has them but they can always send events in your as long as we do not have [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) It is an intentional decision to not provide an endpoint like -`PUT /_matrix/client/v3/future/room/{roomId}/event/{eventId}` +`PUT /_matrix/client/v1/future/room/{roomId}/event/{eventId}` where any client with access to the room could also `end` or `refresh` the expiration. With the token the client creating the future has ownership over the expiration and only intentional delegation of that ownership @@ -557,7 +557,7 @@ even tell with which room or user it is interacting or what the token does (refr ## Unstable prefix -Use `io.element.msc3140.` instead of `m.` as long as the MSC is not stable. -For the endpoints introduced in this MSC use the prefix `/io.element.msc3140/` and set the paths version string to unstable, instead of v#. +Use `org.matrix.msc4140.` instead of `m.` as long as the MSC is not stable. +For the endpoints introduced in this MSC use the prefix `/org.matrix.msc4140/` and set the paths version string to unstable, instead of v#. ## Dependencies From 9ff051edb3081b008a4468f67ca3f7faf9989dfd Mon Sep 17 00:00:00 2001 From: Timo <16718859+toger5@users.noreply.github.com> Date: Sat, 22 Jun 2024 08:49:51 +0200 Subject: [PATCH 28/85] Update proposals/4140-delayed-events-futures.md Co-authored-by: Travis Ralston --- proposals/4140-delayed-events-futures.md | 1 - 1 file changed, 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index af8e85acad8..a8c96ee9113 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -534,7 +534,6 @@ The following alternative names for this concept are considered - Future - DelayedEvents -- RetardedEvents - PostponedEvents ## Security considerations From 425b9bfca47bd0fdc373c3ac4b756664fefabad4 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 24 Jun 2024 14:34:26 +0200 Subject: [PATCH 29/85] review --- proposals/4140-delayed-events-futures.md | 123 ++++++++++++----------- 1 file changed, 65 insertions(+), 58 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index a8c96ee9113..889927fbafc 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -48,19 +48,22 @@ In the introduction only an overview of considered options is given: - expiration with to-device messages/sfu polling. - expiration with custom synapse logic based on the client sync loop. -The preferred solution requires us to send an event to the homeserver in advance, -but let the homeserver decide the time/condition when its actually added to the DAG. -The condition for actually sending the delayed event could be a timeout or an external trigger -via an unauthenticated Synapse endpoint. -The Proposal section of this MSC will focus on how to achieve this. +This proposal covers queueing an event for later sending, the actual sending +is configured with a timeout on the homeserver. +This timeout can be altered by a synapse endpoint that can be used when +knowing about the corresponding `future_token`. +With this it is possible to restart the timer, to cancel sending the event and to send the event now. ## Proposal -To make this as generic as possible, the proposed solution is to allow sending events and delegate -the control of when to actually send these events to an external service or a timeout condition. +When an event is scheduled for sending the client gets a list of server generated tokens to delegate +the control of when to actually send these events to an external service. +When not using the tokens the default will be to send the event once the +timeout happens. This allows a very flexible way to mark events as expired. -The sender can define what event will be sent once the timeout condition is met. For state -events the timed out version of the event would be an event where the content communicates, that +The sender predefines the event that will be sent once the timeout condition is met. +For call state events the timed out version of the event would +be an event where the content communicates, that the users has left the call. This proposal also includes a way to refresh the timeout. Allowing to delay the event multiple times. @@ -89,9 +92,10 @@ to define `future_timeout` and `future_group_id` in their query parameters. - Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. All other futures will be discarded. - Every future group needs at least one timeout future to guarantee that all future expire eventually. - - If a timeout future is sent without a `future_group_id` a unique identifier will be generated by the + - If a timeout future is sent without a `future_group_id` a unique identifier will be generated as the `future_group_id` by the homeserver and is part of the `send_future` response. - - Group id's can only be used by one user. Reasons for this are, that this would basically allow full control over a future group once another matrix user knows the group id. It would also require to federate futures if the users are not on the same homeserver. + - Group id's can only be used by one user. Reasons for this are, that this would basically allow full control over the events that are part + of a future group once another matrix user know the group id. It would also require to federate futures if the users are not on the same homeserver. Both of the query parameters are optional but one of them has to be present. This gives us the following options: @@ -104,17 +108,19 @@ This gives us the following options: Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly and: -- The server will respond with a [`409`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409) - (`Conflict` This response is sent when a request conflicts with the current state of the server.) if - the client tries to send an action future without there being a timeout future with the same `future_group_id` +- The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) + if the client tries to send an action future without there being a timeout future with the same `future_group_id`. + The error code `M_UNKNOWN_FUTURE_GROUP`. - The server can optionally configure a maximum `timeout_duration` (In the order of one week dependent on how long they want to track futures) - The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, with a message + The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, with a matrix + error code `M_FUTURE_MAX_TIMEOUT_EXCEEDED` containing the maximum allowed `timeout_duration`) if the client tries to send a timeout future with a larger `timeout_duration`. -- The future is using a group_id that belongs to a future group from another user. In this case the homeserver sends a [`405`] (`Not Allowed`). +- The future is using a `future_group_id` that belongs to a future group from another user. In this case the homeserver sends a [`400`] (`Bad Request`) + with the matrix error code `M_FORBIDDEN` and a message that the user is not allowed to add another future to that future group. -The body is the same as sending a normal event. +The body is the same as [sending a normal event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) or [sending a state event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey). Power levels are evaluated for each event only once the trigger has occurred and it will be distributed/inserted into the DAG. This implies a future can fail if it violates power levels at the time it resolves. @@ -125,30 +131,35 @@ if the power level situation has changed at the time the future resolves.) The response will include a `send_token`, `cancel_token`, the associated `future_group_id` and an optional `refresh_token` but no `event_id` since the `event_id` depends on the `origin_server_ts` which is not yet determined. A timeout future will contain `refresh_token` but an action future will not. -```json +```jsonc { // always present - "send_token": "send_token", - "cancel_token": "cancel_token", - "future_group_id": "group_id", + "send_token": "future_send_token", + "cancel_token": "future_cancel_token", + "future_group_id": "future_group_id", // optional, only present if its a a timeout future response - "refresh_token": "refresh_token" + "refresh_token": "future_refresh_token" } ``` +We define a `future_token` as one of `future_send_token, future_cancel_token, future_refresh_token`. +Each `future_token` is a 128 bit Base64-encoded url safe UUID (It will be part of the url for sending updates to a future.) +The `future_group_id` is of the same format. All these fields are generated on the homeserver. + ### Delegating futures This MSC also proposes a `futures` endpoint. -The `token` can be used to call this public `futures` endpoint: -`POST /_matrix/client/v1/future/{token}` +The `future_token`s can be used to call this `futures` endpoint: +`POST /_matrix/client/v1/future/{future_token}` The information required to call this endpoint is minimal so that no metadata is leaked when sharing the refresh/send url with a third party. Since the refresh and send tokens are of the same format it is not even possible to evaluate -what that token is for when reading the https request log. -This unauthenticated endpoint allows to delegate resolving the future. -An SFU for instance, that tracks the current client connection state, could get a url that it -needs to call every X hours while a user is connected and a url it has to call once the user disconnects. +what that token is for when reading the `https` request log. +This endpoint allows to delegate resolving the future. +It is not authenticated by a Matrix Auth Token. Instead the `future_token` itself is the authentication mechanism. +An SFU for instance, that tracks the current client connection state, could be sent a url from the client that it +needs to call every X hours while a user is connected and a url it has to call once the user disconnects (using a `future_refresh_token` and the `future_send_token`). This way the SFU can be used as the source of truth for the call member room state even if the client gets closed or looses connection and without knowing anything about the Matrix call. @@ -156,15 +167,15 @@ The homeserver does the following when receiving a Future: - It checks for the validity of the request (based on the `future_timeout` and the `future_group_id` query parameters) and returns a `409`, `400` or `405` if necessary. -- It **generates** a `send_token`, a `cancel_token` and if not provided in the request a `future_group_id` and a optionally `refresh_token` and stores them alongside the time +- It **generates** a `future_send_token`, a `future_cancel_token` and if not provided in the request a `future_group_id` and a optionally `refresh_token` and stores them alongside the time of retrieval and the `timeout_duration`. - If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. - - If a `POST /_matrix/client/v1/future/{refresh_token}` is received, it + - If a `POST /_matrix/client/v1/future/{future_refresh_token}` is received, it **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - - If a `POST /_matrix/client/v1/future/{send_token}` is received, it **sends the associated action or timeout future** - and deletes any stored futures with the `group_id` associated with that token. - - If a `POST /_matrix/client/v1/future/{cancel_token}` is received, it **does NOT send any future** + - If a `POST /_matrix/client/v1/future/{future_send_token}` is received, it **sends the associated action or timeout future** + and deletes any stored futures with the `future_group_id` associated with that token. + - If a `POST /_matrix/client/v1/future/{future_cancel_token}` is received, it **does NOT send any future** and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). - If a `POST /_matrix/client/v1/future/{unknown_token}` is received the server responds with a `410` (Gone). An `unknown_token` either means that the service is making something up or that the service is using a @@ -187,14 +198,8 @@ So for each `future_group_id`, the homeserver will at most send one timeline eve - No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v1/future/{cancel_token}`. - Otherwise one of the timeout or action futures will be send. -**Rate limiting** the `POST /_matrix/client/v1/future/{token}`endpoint: - -- A malicious party can try to find a correct token by randomly sending requests to this endpoint. -- Homeservers should rate limit this endpoint so that one can at - most send `N` invalid tokens in a row and then will get a `429` (Too Many Requests) - response for `T` seconds. We allow `N` invalid tokens because it is not - easy to compute if a token is still valid or not so it is useful to ask for this. -- The recommended values for `T` are 10 seconds and for `N` 5. (It is spec conform to use other values.) +Its particularly important to **rate limit** the `POST /_matrix/client/v1/future/{future_token}`endpoint. +Otherwise, a malicious party can try to find a correct token by randomly sending requests to this endpoint. Timed messages, tea timers, reminders or ephemeral events could be implemented using this where clients send room events with @@ -226,7 +231,7 @@ Using `GET /_matrix/client/v1/future` it is possible to get the list of all runn This is an authenticated endpoint. It sends back the json of the final event content with the associated tokens. -```json +```jsonc [ { "url":"/_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}", @@ -235,11 +240,11 @@ of the final event content with the associated tokens. }, "response":{ // always present - "send_token": "send_token", - "cancel_token": "cancel_token", - "future_group_id": "group_id", + "send_token": "future_send_token", + "cancel_token": "future_cancel_token", + "future_group_id": "future_group_id", // optional if there is a timeout - "refresh_token": "token", + "refresh_token": "future_refresh_token", } }, ] @@ -329,7 +334,7 @@ First send (or generate the pdu when is available): `PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{eventType}/{txnId}` -```json +```jsonc { "m.text": "my msg" } @@ -338,7 +343,7 @@ is available): then send: `PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={10*60}&future_group_id={XYZ}` -```json +```jsonc { "redacts": "{event_id}" } @@ -362,7 +367,7 @@ be controversial if their return value is altered. Currently they always return -```json +```jsonc { "event_id":string } @@ -380,7 +385,7 @@ When sending a future the `event_id` would not be available: As a result the return type would change to: -```json +```jsonc { "event_id": string | undefined, "future_group_id": string | undefined, @@ -413,7 +418,7 @@ introduce this endpoint: `PUT /_matrix/client/v1/rooms/{roomId}/send/future/{txnId}` It allows to send a list of event contents. The body looks as following: -```json +```jsonc { "timeout": 10, @@ -451,17 +456,17 @@ This is a batch endpoint that sends timeout and action futures at the same time. The response will be a collection of all the futures with the same fields as in the initial proposal: -```json +```jsonc { "send_on_timeout": { - "send_token": "token", - "refresh_token": "token", - "cancel_token": "token" + "send_token": "future_send_token", + "refresh_token": "future_refresh_token", + "cancel_token": "future_cancel_token" }, // optional "send_on_action": { - "${action1}": { "send_token": "token" }, - "${action2}": { "send_token": "token" } + "${action1}": { "send_token": "future_send_token" }, + "${action2}": { "send_token": "future_send_token" } }, // optional @@ -496,7 +501,7 @@ The **Self-destructing messages** example be a single request: `PUT /_matrix/client/v1/rooms/{roomId}/send/future/{txnId}` -```json +```jsonc { "m.send_now":{ "type":"m.room.message", @@ -554,6 +559,8 @@ 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 even tell with which room or user it is interacting or what the token does (refresh vs send). +Servers SHOULD impose a maximum timeout value for future timeouts of not more than a month. + ## Unstable prefix Use `org.matrix.msc4140.` instead of `m.` as long as the MSC is not stable. From 2e7be463c2e9b2eb6f437bac751b313759c489a2 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 24 Jun 2024 14:51:00 +0200 Subject: [PATCH 30/85] Swap the alternative of reusing the send and state request with the main proposal of `send_future` and `state_future`. --- proposals/4140-delayed-events-futures.md | 88 ++++++++++++------------ 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 889927fbafc..29899027164 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -14,7 +14,7 @@ - [Self-destructing messages](#self-destructing-messages) - [Potential issues](#potential-issues) - [Alternatives](#alternatives) - - [Reusing the send/state endpoint](#reusing-the-sendstate-endpoint) + - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) - [Naming](#naming) @@ -73,14 +73,13 @@ anymore the timeout condition is met and the homeserver sends the event with the This translate to: _"only send the event when the client is not running its program anymore (not sending the heartbeat anymore)"_ We call those delayed events `Futures`. -New endpoints are introduced: +Futures reuse the `send` and `state` endpoint and extend them with an optional `future_timeout` and an optional `future_group_id` query parameter -`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` -`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/state/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` -Those behave like the normal `send`/`state` endpoints except that that they allow -to define `future_timeout` and `future_group_id` in their query parameters. +The two query parameters are used to configure the event scheduling: - `future_timeout: number` defines how long (in milliseconds) the homeserver will wait before sending the event into the room. **Note**, since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. @@ -129,7 +128,33 @@ if the power level situation has changed at the time the future resolves.) ### Response -The response will include a `send_token`, `cancel_token`, the associated `future_group_id` and an optional `refresh_token` but no `event_id` since the `event_id` depends on the `origin_server_ts` which is not yet determined. A timeout future will contain `refresh_token` but an action future will not. +When sending a future the `event_id` would not be available: + +- The `event_id` is using the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) which is + [calculated via the essential fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) + of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) +- Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) + we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. + +So the response will include a `send_token`, `cancel_token`, +the associated `future_group_id` and an optional `refresh_token` but no +`event_id` if the request was sent with the `future_timeout` and or `future_group_id` parameters. +A request without query parameters would +get a response with an `event_id` (as before). + +As a result the return type would change to: + +```jsonc +{ + "event_id": string | undefined, + "future_group_id": string | undefined, + "send_token": string | undefined, + "refresh_token": string | undefined, + "cancel_token": "string | undefined +} +``` + +For a future event the response looks as following: ```jsonc { @@ -145,6 +170,10 @@ The response will include a `send_token`, `cancel_token`, the associated `future We define a `future_token` as one of `future_send_token, future_cancel_token, future_refresh_token`. Each `future_token` is a 128 bit Base64-encoded url safe UUID (It will be part of the url for sending updates to a future.) The `future_group_id` is of the same format. All these fields are generated on the homeserver. +They can optionally be used to interact with futures and even delegate very fine grained control over the +future send time to other services by only sharing the tokens. + +The exact usage of those tokens is covered in the next section. ### Delegating futures @@ -355,47 +384,16 @@ This would redact the message with content: `"m.text": "my msg"` after 10minutes ## Alternatives -### Reusing the `send`/`state` endpoint - -Since the `send_future` and `state_future` endpoints are almost identical to the -normal `send` and `state` endpoints it comes to mind, that one could reuse them and allow adding the -query parameters `?future_timeout={timeout_duration}&future_group_id={group_id}` directly to the -`send` and `state` endpoint. - -This would be elegant but since those two endpoint are core to Matrix, changes to them might -be controversial if their return value is altered. - -Currently they always return +### Not reusing the `send`/`state` endpoint -```jsonc -{ - "event_id":string -} -``` - -as a non optional field. - -When sending a future the `event_id` would not be available: - -- The `event_id` is using the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) which is - [calculated via the essential fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) - of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) -- Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) - we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. - -As a result the return type would change to: +Alternatively new endpoints could be introduced to not oveload the `send` and `state` endpoint. +Those endpoints would be called: +`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` -```jsonc -{ - "event_id": string | undefined, - "future_group_id": string | undefined, - "send_token": string | undefined, - "refresh_token": string | undefined, - "cancel_token": "string | undefined -} -``` +`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` -dependent on the query parameters. +This keeps the response type of the `send` and `state` endpoint in tact and we get a different return type +the new `send_future` and `state_future` endpoint. ### Batch sending futures with custom endpoint From 5653fe1884d722a29e6044ee6f9c59e4bd1dc038 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 2 Jul 2024 17:21:25 +0200 Subject: [PATCH 31/85] add reference to MSC4143 (MatrixRTC) --- proposals/4140-delayed-events-futures.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 29899027164..851556818eb 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -25,6 +25,9 @@ The motivation for this MSC is: Updating call member events after the user disconnected by allowing to schedule/delay/timeout/expire events in a generic way. +It is best to first read [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) and in particular +the section: ["Reliability requirements for the room state"](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md#reliability-requirements-for-the-room-state) to understand the MatrixRTC usecase of this MSC. + It turns out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: - Updating call member events after the user disconnected. From af060cfc4085fa21c82e6170030573ca05390066 Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 15 Jul 2024 10:23:29 +0200 Subject: [PATCH 32/85] Meeting feedback: - remove token concept for authed EDU - authenticated delegation - simplified group_id + tokens into just one future_id - Mention "Last Will" mental model --- proposals/4140-delayed-events-futures.md | 315 ++++++++++++----------- 1 file changed, 163 insertions(+), 152 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 851556818eb..eb7657f44c1 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -3,30 +3,36 @@ - [MSC4140: Delayed events Futures](#msc4140-delayed-events-futures) - - [Proposal](#proposal) - - [Response](#response) - - [Delegating futures](#delegating-futures) - - [Getting running futures](#getting-running-futures) - - [Usecase specific considerations](#usecase-specific-considerations) - - [MatrixRTC](#matrixrtc) - - [Background](#background) - - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) - - [Self-destructing messages](#self-destructing-messages) - - [Potential issues](#potential-issues) - - [Alternatives](#alternatives) - - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) - - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) - - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) - - [Naming](#naming) - - [Security considerations](#security-considerations) - - [Unstable prefix](#unstable-prefix) - - [Dependencies](#dependencies) + - [Proposal](#proposal) + - [Response](#response) + - [Interacting with futures](#interacting-with-futures) + - [Delegating futures](#delegating-futures) + - [Batch sending](#batch-sending) + - [Getting running futures](#getting-running-futures) + - [Usecase specific considerations](#usecase-specific-considerations) + - [MatrixRTC](#matrixrtc) + - [Background](#background) + - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) + - [Self-destructing messages](#self-destructing-messages) + - [Potential issues](#potential-issues) + - [Alternatives](#alternatives) + - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) + - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) + - [Batch Response](#batch-response) + - [EventId template variable](#eventid-template-variable) + - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) + - [Naming](#naming) + - [Security considerations](#security-considerations) + - [Unstable prefix](#unstable-prefix) + - [Dependencies](#dependencies) -The motivation for this MSC is: Updating call member events after the user disconnected by allowing to schedule/delay/timeout/expire events in a generic way. +The motivation for this MSC is: Updating call member events after the user disconnected by allowing to +schedule/delay/timeout/expire events in a generic way. It is best to first read [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) and in particular -the section: ["Reliability requirements for the room state"](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md#reliability-requirements-for-the-room-state) to understand the MatrixRTC usecase of this MSC. +the section: ["Reliability requirements for the room state"](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md#reliability-requirements-for-the-room-state) +to understand the MatrixRTC usecase of this MSC. It turns out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: @@ -43,7 +49,8 @@ without this devices call membership. The same happens if the user force quits the client without pressing the hangup button. (For example closing a browser tab.) There are numerous possible solution to solve the call member event expiration. They are covered in detail -in the [Usecase specific considerations/MatrixRTC](#usecase-specific-considerations) section, because they are not part of the actual proposal. +in the [Usecase specific considerations/MatrixRTC](#usecase-specific-considerations) section, because they are not part +of the actual proposal. In the introduction only an overview of considered options is given: - expiration using timestamp logic. @@ -51,97 +58,111 @@ In the introduction only an overview of considered options is given: - expiration with to-device messages/sfu polling. - expiration with custom synapse logic based on the client sync loop. -This proposal covers queueing an event for later sending, the actual sending -is configured with a timeout on the homeserver. -This timeout can be altered by a synapse endpoint that can be used when -knowing about the corresponding `future_token`. -With this it is possible to restart the timer, to cancel sending the event and to send the event now. +This covers how events can be queued for sending after a "passive termination" event. +The client is not in a state anymore to actually send requests, the server detects this +(by missing heartbeat pings) and sendes the pre-sent event. +This is sometimes called "last will" in other technologies. ## Proposal -When an event is scheduled for sending the client gets a list of server generated tokens to delegate -the control of when to actually send these events to an external service. -When not using the tokens the default will be to send the event once the -timeout happens. +When an event is scheduled for sending the client gets a temporal so called `future_id` +to control of when to actually send these events. This allows a very flexible way to mark events as expired. -The sender predefines the event that will be sent once the timeout condition is met. -For call state events the timed out version of the event would +The sender predefines the event that will be sent once the condition "the client does not sent heartbeat pings" +anymore is met. +For call state events the future/"last will" version of the event would be an event where the content communicates, that the users has left the call. -This proposal also includes a way to refresh the timeout. Allowing to delay the event multiple times. -A periodic ping of the refreshing can be used as a heartbeat mechanism. Once the refresh ping is not send +Using the `future_id` the client can refresh the timeout. Allowing to delay the event multiple times. +A periodic refresh ping using this `future_id` can be used as a heartbeat mechanism. Once the refresh ping is not send anymore the timeout condition is met and the homeserver sends the event with the expired content information. This translate to: _"only send the event when the client is not running its program anymore (not sending the heartbeat anymore)"_ We call those delayed events `Futures`. -Futures reuse the `send` and `state` endpoint and extend them with an optional `future_timeout` and an optional `future_group_id` query parameter +Futures reuse the `send` and `state` endpoint and extend them with an optional `future_timeout` and an optional +`parent_future_id` query parameter. -`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{txnId}?future_timeout={timeout_duration}&parent_future_id={future_id}` -`PUT /_matrix/client/v1/rooms/{roomId}/state/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/state/{eventType}/{stateKey}?future_timeout={timeout_duration}&parent_future_id={future_id}` The two query parameters are used to configure the event scheduling: - `future_timeout: number` defines how long (in milliseconds) the homeserver will wait before sending - the event into the room. **Note**, since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. - - If this query parameter is not added the future will never expire and can only be send by the [external delegation endpoint](#delegating-futures). + the event into the room. **Note**, since the timeout can be refreshed and sending the future can be triggered via an + endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this + event will arrive in the room. + - If this query parameter is not added the future will never expire and can only be send by the [future interaction endpoint](#interacting-with-futures). We call such a future **action future**. - If set to a `number` (ms) we call the future **timeout future** -- `future_group_id: string` is optional if a `future_timeout` is a `number`. The purpose of this identifier is to group +- `parent_future_id: string` is optional if a `future_timeout` is a `number`. The purpose of this identifier is to group **multiple futures in one mutually exclusive group**. - Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. All other futures will be discarded. - Every future group needs at least one timeout future to guarantee that all future expire eventually. - - If a timeout future is sent without a `future_group_id` a unique identifier will be generated as the `future_group_id` by the - homeserver and is part of the `send_future` response. - - Group id's can only be used by one user. Reasons for this are, that this would basically allow full control over the events that are part - of a future group once another matrix user know the group id. It would also require to federate futures if the users are not on the same homeserver. + - The `parent_future_id` can only be set to a `future_id` that was created for a future form the same user. + Otherwise, this would allow full control over the events that are part + of a future group, other users could add optional futures, once another matrix user knows the `future_id`. + It would also require to federate futures if the users are not on the same homeserver. Both of the query parameters are optional but one of them has to be present. -This gives us the following options: +This gives the following options: -``` +```url ?future_timeout=10 - a timeout future in a new future group -?future_timeout=10&future_group_id="groupA" - a timeout future added to groupA -?future_group_id="groupA" - an action future added to groupA +?future_timeout=10&parent_future_id="groupA" - a timeout future added to groupA +?parent_future_id="groupA" - an action future added to groupA ``` -Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly and: +Two categories of futures arise: + +- `parent_future` vs `child_future`:\ + If a future is send with a `parent_future_id` it is a child. A child `future_id` can never be used + as the `parent_future_id` property. (servers will respond with an error). +- `timeout_future` vs `action_future`:\ + If a timeout duration is set its a timeout future otherwise we call it an action future. + Parent futures can only be timeouts but child futures can also be actions. + +Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly: - The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) - if the client tries to send an action future without there being a timeout future with the same `future_group_id`. - The error code `M_UNKNOWN_FUTURE_GROUP`. + if the client tries to send an action future without there being a timeout future with the same `parent_future_id`. + The error code `M_UNKNOWN_FUTURE_PARENT_FUTURE_ID`. - The server can optionally configure a maximum `timeout_duration` (In the order of one week dependent on how long they want to track futures) - The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, with a matrix - error code `M_FUTURE_MAX_TIMEOUT_EXCEEDED` + The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, + with a matrix error code `M_FUTURE_MAX_TIMEOUT_EXCEEDED` containing the maximum allowed `timeout_duration`) if the client tries to send a timeout future with a larger `timeout_duration`. -- The future is using a `future_group_id` that belongs to a future group from another user. In this case the homeserver sends a [`400`] (`Bad Request`) - with the matrix error code `M_FORBIDDEN` and a message that the user is not allowed to add another future to that future group. +- The future is using a `parent_future_id` that belongs to a future from another user. In this case the homeserver + sends a [`400`] (`Bad Request`) + with the matrix error code `M_FORBIDDEN` and a message that the user is not allowed to add another future to that + future group. -The body is the same as [sending a normal event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) or [sending a state event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey). +The body is the same as [sending a normal event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) +or [sending a state event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey). -Power levels are evaluated for each event only once the trigger has occurred and it will be distributed/inserted into the DAG. +Power levels are evaluated for each event only once the trigger has occurred and it will be distributed/inserted into the +DAG. This implies a future can fail if it violates power levels at the time it resolves. (It's also possible to successfully send a future the user has no permission to at the time of sending if the power level situation has changed at the time the future resolves.) ### Response -When sending a future the `event_id` would not be available: +When sending a future the `event_id` is not yet available: - The `event_id` is using the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) which is [calculated via the essential fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) -- Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) +- Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG + (required for call duration computation) we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. -So the response will include a `send_token`, `cancel_token`, -the associated `future_group_id` and an optional `refresh_token` but no -`event_id` if the request was sent with the `future_timeout` and or `future_group_id` parameters. +So the response will include a custom `future_id` but no `event_id`, +if the request was sent with the `future_timeout` and or `future_group_id` parameters. A request without query parameters would get a response with an `event_id` (as before). @@ -149,11 +170,8 @@ As a result the return type would change to: ```jsonc { - "event_id": string | undefined, - "future_group_id": string | undefined, - "send_token": string | undefined, - "refresh_token": string | undefined, - "cancel_token": "string | undefined + "event_id": "1234", // string | undefined + "future_id": "2345" //string | undefined, } ``` @@ -161,57 +179,44 @@ For a future event the response looks as following: ```jsonc { - // always present - "send_token": "future_send_token", - "cancel_token": "future_cancel_token", - "future_group_id": "future_group_id", - // optional, only present if its a a timeout future response - "refresh_token": "future_refresh_token" + "future_id": "1234" // string, } ``` -We define a `future_token` as one of `future_send_token, future_cancel_token, future_refresh_token`. -Each `future_token` is a 128 bit Base64-encoded url safe UUID (It will be part of the url for sending updates to a future.) -The `future_group_id` is of the same format. All these fields are generated on the homeserver. -They can optionally be used to interact with futures and even delegate very fine grained control over the -future send time to other services by only sharing the tokens. +Each `future_id` is a UUID similar to an `event_id`s. It is computed on the homeserver. +It can optionally be used to interact with futures. -The exact usage of those tokens is covered in the next section. +### Interacting with futures -### Delegating futures +This MSC also proposes a authenticaed EDU endpoint: `update_future`. +The `future_id` can be used to send an EDUs to the `update_future` endpoint: +`POST /_matrix/client/v1/update_future` +using the following EDU body: -This MSC also proposes a `futures` endpoint. -The `future_token`s can be used to call this `futures` endpoint: -`POST /_matrix/client/v1/future/{future_token}` - -The information required to call this endpoint is minimal so that -no metadata is leaked when sharing the refresh/send url with a third party. -Since the refresh and send tokens are of the same format it is not even possible to evaluate -what that token is for when reading the `https` request log. -This endpoint allows to delegate resolving the future. -It is not authenticated by a Matrix Auth Token. Instead the `future_token` itself is the authentication mechanism. -An SFU for instance, that tracks the current client connection state, could be sent a url from the client that it -needs to call every X hours while a user is connected and a url it has to call once the user disconnects (using a `future_refresh_token` and the `future_send_token`). -This way the SFU can be used as the source of truth for the call member room state even if the client -gets closed or looses connection and without knowing anything about the Matrix call. +```jsonc +{ + "future_id": "1234567890", // UUID + "action": "send" // "send"|"cancel"|"refresh, +} +``` The homeserver does the following when receiving a Future: -- It checks for the validity of the request (based on the `future_timeout` and the `future_group_id` query parameters) - and returns a `409`, `400` or `405` if necessary. -- It **generates** a `future_send_token`, a `future_cancel_token` and if not provided in the request a `future_group_id` and a optionally `refresh_token` and stores them alongside the time - of retrieval and the `timeout_duration`. -- If `future_timeout` was present, it **Starts a timer** for the `refresh_token`. +- It checks for the validity of the request (based on the `future_timeout` and the `parent_future_id` query parameters) + and returns a `409`, `400` or `405` if necessary. `future_id`s that already are connected to a parent cannot be used as + a `parent_future_id`. +- It **generates** a `future_id`. +- If `future_timeout` was present, it **Starts a timer** for the Future. - - If a `POST /_matrix/client/v1/future/{future_refresh_token}` is received, it + - If a `POST /_matrix/client/v1/update_future`, `{"action": "refresh"}` is received, it **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - - If a `POST /_matrix/client/v1/future/{future_send_token}` is received, it **sends the associated action or timeout future** - and deletes any stored futures with the `future_group_id` associated with that token. - - If a `POST /_matrix/client/v1/future/{future_cancel_token}` is received, it **does NOT send any future** + - If a `POST /_matrix/client/v1/update_future`, `{"action": "send"}` is received, it + **sends the associated action or timeout future** + and deletes any related future group (futures with the same `parent_future_id` or `future_id`). + - If a `POST /_matrix/client/v1/update_future`, `{"action": "cancel"}` is received, it **does NOT send any future** and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). - - If a `POST /_matrix/client/v1/future/{unknown_token}` is received the server responds with a `410` (Gone). - An `unknown_token` either means that the service is making something up or that the service is using a - token that is invalidated by now. + - If a `POST /_matrix/client/v1/update_future` `{"future_id": "unknown"}` is received the server responds + with a `410` (Gone). - If a timer times out, **it sends the timeout future**. - If the homeserver receives a _new state event_ with the same state key as existing futures the **futures get invalidated and the associated timers are stopped**. @@ -225,13 +230,26 @@ The homeserver does the following when receiving a Future: - After the homeservers sends a timeout future or action future, the associated timer and tokens is canceled/deleted. -So for each `future_group_id`, the homeserver will at most send one timeline event. +So for each `parent_future_id`, the homeserver will at most send one timeline event. - No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v1/future/{cancel_token}`. - Otherwise one of the timeout or action futures will be send. -Its particularly important to **rate limit** the `POST /_matrix/client/v1/future/{future_token}`endpoint. -Otherwise, a malicious party can try to find a correct token by randomly sending requests to this endpoint. +### Delegating futures + +It is useful for external services to also interact with futures. If a client disconnects an external service can +be the best source to activate the Future/"last will". + +This is not covered in this MSC but could be realized with scoped access tokens. +A scoped token for only the `update_future` endpoint and a subset of `future_id`s would be used. + +An SFU for instance, that tracks the current client connection state, could be sent a request from the client that it +needs to call every X hours while a user is connected and a request it has to call once the user disconnects +(using a `{"action": "refresh}` and a `{"action": "send"}` `future_update` request.). +This way the SFU can be used as the source of truth for the call member room state even if the client +gets closed or looses connection and without knowing anything about the Matrix call. + +### Batch sending Timed messages, tea timers, reminders or ephemeral events could be implemented using this where clients send room events with @@ -255,13 +273,13 @@ We do not include batch sending in the proposal of this MSC however since batch become a generic Matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) There is a [batch sending version](#batch-sending-futures-with-custom-endpoint) in the Alternatives section -that proposes a future specific group sending endpoint in case this is required sooner then its realistic to implement [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). +that proposes a future specific group sending endpoint in case this is required sooner then its realistic to implement +[MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). ### Getting running futures -Using `GET /_matrix/client/v1/future` it is possible to get the list of all running futures issues by the authenticated user. -This is an authenticated endpoint. It sends back the json -of the final event content with the associated tokens. +Using `GET /_matrix/client/v0/futures` it is possible to get the list of all running futures issues by the authenticated +user. This is an authenticated endpoint. It sends back the json of the final event content with the associated tokens. ```jsonc [ @@ -271,12 +289,9 @@ of the final event content with the associated tokens. ...event_body }, "response":{ - // always present - "send_token": "future_send_token", - "cancel_token": "future_cancel_token", - "future_group_id": "future_group_id", - // optional if there is a timeout - "refresh_token": "future_refresh_token", + "future_id": UUID, + "last_refresh": Time, + "timeout": Number | undefined, } }, ] @@ -284,7 +299,6 @@ of the final event content with the associated tokens. This can be used so clients can optionally display events that will be send in the future. -And to acquire cancel_tokens for then. For all usecases where the existence of a running future is also of interest for other room members, (like self-destructing messages) it is recommended to include @@ -330,7 +344,8 @@ Example: > (or a request to the SFU) to every user that has an active state event to check if > they are still online. Just to display the room tile properly. -For displaying the room list timeout based approaches are much more reasonable because this allows computing MatrixRTC metadata for a room to be synchronous. +For displaying the room list timeout based approaches are much more reasonable because this allows computing MatrixRTC +metadata for a room to be synchronous. The current solution updates the room state every X minutes. This is not elegant since we basically resend room state with the same content. @@ -354,7 +369,7 @@ to only send one event for joining and one for leaving (reliably) 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 + This significantly reduces the amount of calls for the `/update_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 @@ -405,13 +420,15 @@ HTTPS request. This is desired for self-destructing events and for MatrixRTC roo we want the guarantee, that the event itself and the future removing the event both reach the homeserver with one request. Otherwise there is a risk for the client to lose connection or crash between sending the event and the future which results in never expiring call membership or never destructing self-destructing messages. -This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. +This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` +endpoint is implemented. (Then the `future_timeout` and `future_group_id` could be added to the `PDUInfo` instead of the query parameters and everything could be send at once.) This would be the preferred solution since we currently don't have any other batch sending mechanism. It would however require lots of changes since a new widget action for futures would be needed. -With the current main proposal it is enough to add a `future_timeout` and `future_group_id` parameter to the send message widget action. +With the current main proposal it is enough to add a `future_timeout` and `future_group_id` parameter to the send message +widget action. The widget driver would then take care of calling `send` or `send_future` based on the presence of those fields. An alternative to the proposed solution that allows this kind of batch sending would be to @@ -453,21 +470,19 @@ event. This is a batch endpoint that sends timeout and action futures at the same time. -**Response** +#### Batch Response The response will be a collection of all the futures with the same fields as in the initial proposal: ```jsonc { "send_on_timeout": { - "send_token": "future_send_token", - "refresh_token": "future_refresh_token", - "cancel_token": "future_cancel_token" + "future_id": "future_id", }, // optional "send_on_action": { - "${action1}": { "send_token": "future_send_token" }, - "${action2}": { "send_token": "future_send_token" } + "${action1}": { "future_id": "future_id1" }, + "${action2}": { "future_id": "future_id2" } }, // optional @@ -475,20 +490,18 @@ The response will be a collection of all the futures with the same fields as in } ``` -We do not need a `future_group_id` since we will send one group in one request. - Working with futures is the same with this alternative. This means, -- `GET /_matrix/client/v1/future` getting running futures -- `POST /_matrix/client/v1/future/{token}` cancel, refreshing and sending futures +- `GET /_matrix/client/v1/futures` getting running futures +- `POST /_matrix/client/v1/update_future` to canceling, refreshing and sending futures uses the exact same endpoints. Also the behaviour of the homeserver on when to invalidate the furures is identical except, that we don't need the error code `409` anymore since the events are sent as a batch and there cannot be an action future without a timeout future. -**EventId template variable** +#### EventId template variable It would be useful to be able to send redactions and edits as one HTTP request. This would handle the cases where the futures need to reference the `send_now` event. @@ -534,6 +547,18 @@ 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 advantage is, that this does not require us to introduce a new refresh token type. +With cryptographic identities we however need the client to create the leave event. + +The timeout for syncs are much slower than what would be desirable (30s vs 5s). + +With a widget implementation for calls we cannot guarantee that the widget is running during the sync loop. +So one either has to move the hangup logic to the hosting client or let the widget run all the time. + +With a dedicated ping (independent to the sync loop) it is more felxibale and allows us to let the widget +execute the refresh. +If the widget dies, the call membership will disconnect. + ### Naming The following alternative names for this concept are considered @@ -541,30 +566,16 @@ The following alternative names for this concept are considered - Future - DelayedEvents - PostponedEvents +- LastWill ## Security considerations -We are using an unauthenticated endpoint to refresh the expirations. Since we use -generated tokens it is hard to guess a correct request and force sending one -of the Futures. (The homeserver has them but they can always send events in your name -as long as we do not have [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) - -It is an intentional decision to not provide an endpoint like -`PUT /_matrix/client/v1/future/room/{roomId}/event/{eventId}` -where any client with access to the room could also `end` or `refresh` -the expiration. With the token the client creating the future 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 -even tell with which room or user it is interacting or what the token does (refresh vs send). - Servers SHOULD impose a maximum timeout value for future timeouts of not more than a month. ## Unstable prefix Use `org.matrix.msc4140.` instead of `m.` as long as the MSC is not stable. -For the endpoints introduced in this MSC use the prefix `/org.matrix.msc4140/` and set the paths version string to unstable, instead of v#. +For the endpoints introduced in this MSC use the prefix `/org.matrix.msc4140/` and set the paths version string to unstable, +instead of v#. ## Dependencies From bff704cdd4f5e9e84a28543fe9a32c1b3a7783bd Mon Sep 17 00:00:00 2001 From: Timo Date: Mon, 22 Jul 2024 10:20:38 +0200 Subject: [PATCH 33/85] remove parent concept. --- proposals/4140-delayed-events-futures.md | 92 +++++++----------------- 1 file changed, 24 insertions(+), 68 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index eb7657f44c1..e2d0504c7ad 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -50,8 +50,8 @@ The same happens if the user force quits the client without pressing the hangup There are numerous possible solution to solve the call member event expiration. They are covered in detail in the [Usecase specific considerations/MatrixRTC](#usecase-specific-considerations) section, because they are not part -of the actual proposal. -In the introduction only an overview of considered options is given: +of this proposal. +Here, in the introduction, only an overview of considered options is given: - expiration using timestamp logic. - expiration using bots/appservices. @@ -66,7 +66,7 @@ This is sometimes called "last will" in other technologies. ## Proposal When an event is scheduled for sending the client gets a temporal so called `future_id` -to control of when to actually send these events. +to control when to actually send these events. This allows a very flexible way to mark events as expired. The sender predefines the event that will be sent once the condition "the client does not sent heartbeat pings" anymore is met. @@ -81,65 +81,28 @@ anymore the timeout condition is met and the homeserver sends the event with the This translate to: _"only send the event when the client is not running its program anymore (not sending the heartbeat anymore)"_ We call those delayed events `Futures`. -Futures reuse the `send` and `state` endpoint and extend them with an optional `future_timeout` and an optional -`parent_future_id` query parameter. +Futures reuse the `send` and `state` endpoint and extend them with an optional `future_timeout` query parameter. -`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{txnId}?future_timeout={timeout_duration}&parent_future_id={future_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{txnId}?future_timeout={timeout_duration}` -`PUT /_matrix/client/v1/rooms/{roomId}/state/{eventType}/{stateKey}?future_timeout={timeout_duration}&parent_future_id={future_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/state/{eventType}/{stateKey}?future_timeout={timeout_duration}` -The two query parameters are used to configure the event scheduling: +The `future_timeout` query parameter is used to configure the event scheduling: - `future_timeout: number` defines how long (in milliseconds) the homeserver will wait before sending the event into the room. **Note**, since the timeout can be refreshed and sending the future can be triggered via an endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this event will arrive in the room. - - If this query parameter is not added the future will never expire and can only be send by the [future interaction endpoint](#interacting-with-futures). - We call such a future **action future**. - - If set to a `number` (ms) we call the future **timeout future** -- `parent_future_id: string` is optional if a `future_timeout` is a `number`. The purpose of this identifier is to group - **multiple futures in one mutually exclusive group**. - - Only one of the events in such a group can ever reach the DAG/will be distributed by the homeserver. - All other futures will be discarded. - - Every future group needs at least one timeout future to guarantee that all future expire eventually. - - The `parent_future_id` can only be set to a `future_id` that was created for a future form the same user. - Otherwise, this would allow full control over the events that are part - of a future group, other users could add optional futures, once another matrix user knows the `future_id`. - It would also require to federate futures if the users are not on the same homeserver. - -Both of the query parameters are optional but one of them has to be present. -This gives the following options: - -```url -?future_timeout=10 - a timeout future in a new future group -?future_timeout=10&parent_future_id="groupA" - a timeout future added to groupA -?parent_future_id="groupA" - an action future added to groupA -``` - -Two categories of futures arise: - -- `parent_future` vs `child_future`:\ - If a future is send with a `parent_future_id` it is a child. A child `future_id` can never be used - as the `parent_future_id` property. (servers will respond with an error). -- `timeout_future` vs `action_future`:\ - If a timeout duration is set its a timeout future otherwise we call it an action future. - Parent futures can only be timeouts but child futures can also be actions. -Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly: +Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly. +Additionally the following can occur: -- The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) - if the client tries to send an action future without there being a timeout future with the same `parent_future_id`. - The error code `M_UNKNOWN_FUTURE_PARENT_FUTURE_ID`. - The server can optionally configure a maximum `timeout_duration` (In the order of one week dependent on how long they want to track futures) The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, with a matrix error code `M_FUTURE_MAX_TIMEOUT_EXCEEDED` containing the maximum allowed `timeout_duration`) if the client tries to send a timeout future with a larger `timeout_duration`. -- The future is using a `parent_future_id` that belongs to a future from another user. In this case the homeserver - sends a [`400`] (`Bad Request`) - with the matrix error code `M_FORBIDDEN` and a message that the user is not allowed to add another future to that - future group. The body is the same as [sending a normal event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) or [sending a state event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey). @@ -162,11 +125,11 @@ When sending a future the `event_id` is not yet available: we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. So the response will include a custom `future_id` but no `event_id`, -if the request was sent with the `future_timeout` and or `future_group_id` parameters. +if the request was sent with the `future_timeout` parameter. A request without query parameters would get a response with an `event_id` (as before). -As a result the return type would change to: +As a result the return type changes to: ```jsonc { @@ -202,19 +165,17 @@ using the following EDU body: The homeserver does the following when receiving a Future: -- It checks for the validity of the request (based on the `future_timeout` and the `parent_future_id` query parameters) - and returns a `409`, `400` or `405` if necessary. `future_id`s that already are connected to a parent cannot be used as - a `parent_future_id`. +- It checks for the validity of the request (based on the `future_timeout` query parameter) + and returns a `400` or `410` if necessary. - It **generates** a `future_id`. -- If `future_timeout` was present, it **Starts a timer** for the Future. +- It **Starts a timer** for the Future using the `future_timeout` value. - If a `POST /_matrix/client/v1/update_future`, `{"action": "refresh"}` is received, it **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - If a `POST /_matrix/client/v1/update_future`, `{"action": "send"}` is received, it **sends the associated action or timeout future** - and deletes any related future group (futures with the same `parent_future_id` or `future_id`). - If a `POST /_matrix/client/v1/update_future`, `{"action": "cancel"}` is received, it **does NOT send any future** - and deletes/invalidates the associated stored future. This can mean that a whole future group gets deleted (see below). + and deletes/invalidates the associated stored future. - If a `POST /_matrix/client/v1/update_future` `{"future_id": "unknown"}` is received the server responds with a `410` (Gone). - If a timer times out, **it sends the timeout future**. @@ -227,13 +188,8 @@ The homeserver does the following when receiving a Future: the content of the future but later with the content of _new state event_. - _new state event_ -> timeout: the _new state event_ will invalidate the future. -- After the homeservers sends a timeout future or action future, the associated - timer and tokens is canceled/deleted. - -So for each `parent_future_id`, the homeserver will at most send one timeline event. - -- No timeline event will be send in case all of the timeout futures in a future group are cancelled via `/_matrix/client/v1/future/{cancel_token}`. -- Otherwise one of the timeout or action futures will be send. +- After the homeservers sends a timeout future, the associated + timer is deleted. ### Delegating futures @@ -284,7 +240,7 @@ user. This is an authenticated endpoint. It sends back the json of the final eve ```jsonc [ { - "url":"/_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}", + "url":"/_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}", "body":{ ...event_body }, @@ -331,7 +287,7 @@ There are numerous approaches to solve such a situation. They split into two cat - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if it's expired. - Use Future events with a 10s timeout to send the disconnected from call - in less then 10s after the user is not anymore pinging the `/refresh` endpoint. + in less then 10s after the user is not anymore pinging the `/update_future` endpoint. (or delegate the disconnect action to a service attached to the SFU) - Use the client sync loop as a special case timeout for call member events. (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) @@ -388,7 +344,7 @@ is available): ``` then send: -`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={10*60}&future_group_id={XYZ}` +`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={10*60}` ```jsonc { @@ -406,9 +362,9 @@ This would redact the message with content: `"m.text": "my msg"` after 10minutes Alternatively new endpoints could be introduced to not oveload the `send` and `state` endpoint. Those endpoints would be called: -`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}` -`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}&future_group_id={group_id}` +`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}` This keeps the response type of the `send` and `state` endpoint in tact and we get a different return type the new `send_future` and `state_future` endpoint. @@ -422,12 +378,12 @@ with one request. Otherwise there is a risk for the client to lose connection or event and the future which results in never expiring call membership or never destructing self-destructing messages. This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. -(Then the `future_timeout` and `future_group_id` could be added +(Then the `future_timeout` could be added to the `PDUInfo` instead of the query parameters and everything could be send at once.) This would be the preferred solution since we currently don't have any other batch sending mechanism. It would however require lots of changes since a new widget action for futures would be needed. -With the current main proposal it is enough to add a `future_timeout` and `future_group_id` parameter to the send message +With the current main proposal it is enough to add a `future_timeout` to the send message widget action. The widget driver would then take care of calling `send` or `send_future` based on the presence of those fields. From fa461e21a39adcbbc676adf265fc74a20bf8488a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 23 Jul 2024 15:24:35 +0100 Subject: [PATCH 34/85] Refactor + unstable prefixes --- proposals/4140-delayed-events-futures.md | 480 ++++++++++++----------- 1 file changed, 259 insertions(+), 221 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index e2d0504c7ad..132b51855db 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,266 +1,205 @@ # MSC4140: Delayed events (Futures) - +This MSC proposes a mechanism by which a Matrix client can schedule an event (including a state event) to be sent into +a room on behalf of a user at a later time. -- [MSC4140: Delayed events Futures](#msc4140-delayed-events-futures) - - [Proposal](#proposal) - - [Response](#response) - - [Interacting with futures](#interacting-with-futures) - - [Delegating futures](#delegating-futures) - - [Batch sending](#batch-sending) - - [Getting running futures](#getting-running-futures) - - [Usecase specific considerations](#usecase-specific-considerations) - - [MatrixRTC](#matrixrtc) - - [Background](#background) - - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) - - [Self-destructing messages](#self-destructing-messages) - - [Potential issues](#potential-issues) - - [Alternatives](#alternatives) - - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) - - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) - - [Batch Response](#batch-response) - - [EventId template variable](#eventid-template-variable) - - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) - - [Naming](#naming) - - [Security considerations](#security-considerations) - - [Unstable prefix](#unstable-prefix) - - [Dependencies](#dependencies) +The client does not have to be running or in contact with the Homeserver at the time that the event is actually sent. - - -The motivation for this MSC is: Updating call member events after the user disconnected by allowing to -schedule/delay/timeout/expire events in a generic way. -It is best to first read [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) and in particular -the section: ["Reliability requirements for the room state"](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md#reliability-requirements-for-the-room-state) -to understand the MatrixRTC usecase of this MSC. +Once the event has been scheduled, the user's homeserver is responsible for actually sending the event at the appropriate +time and then distributing as normal via federation. -It turns out that there is a big overlap to other usecases in Matrix which can also be implemented using the proposed concept: - -- Updating call member events after the user disconnected. -- Sending scheduled messages (send at a specific time). -- Creating self-destructing events (by sending a delayed redact). + -Currently there is no mechanism for a client to reliably share that they are not part of a call anymore. -A network issue can disconnect the client. The call is not working anymore and for that user the call ended. -Since the only way to update an event is to post a new one the room state will not represent the correct call state -until the user rejoins and disconnects intentionally so the client is still online sending/updating the room state -without this devices call membership. +- [MSC4140: Delayed events Futures](#msc4140-delayed-events-futures) + - [Background and motivation](#background-and-motivation) + - [Proposal](#proposal) + - [Scheduling a delayed event](#scheduling-a-delayed-event) + - [Getting delayed events](#getting-delayed-events) + - [Managing delayed events](#managing-delayed-events) + - [Homeserver implementation details](#homeserver-implementation-details) + - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) + - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) + - [Use case specific considerations](#use-case-specific-considerations) + - [MatrixRTC](#matrixrtc) + - [Background](#background) + - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) + - [Self-destructing messages](#self-destructing-messages) + - [Potential issues](#potential-issues) + - [Alternatives](#alternatives) + - [Delegating futures](#delegating-futures) + - [Batch sending](#batch-sending) + - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) + - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) + - [Batch Response](#batch-response) + - [Allocating event ID at the point of scheduling the send](#allocating-event-id-at-the-point-of-scheduling-the-send) + - [EventId template variable](#eventid-template-variable) + - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) + - [Federated futures](#federated-futures) + - [MQTT style Last Will](#mqtt-style-last-will) + - [Naming](#naming) + - [Security considerations](#security-considerations) + - [Unstable prefix](#unstable-prefix) + - [Dependencies](#dependencies) -The same happens if the user force quits the client without pressing the hangup button. (For example closing a browser tab.) + -There are numerous possible solution to solve the call member event expiration. They are covered in detail -in the [Usecase specific considerations/MatrixRTC](#usecase-specific-considerations) section, because they are not part -of this proposal. -Here, in the introduction, only an overview of considered options is given: +## Background and motivation -- expiration using timestamp logic. -- expiration using bots/appservices. -- expiration with to-device messages/sfu polling. -- expiration with custom synapse logic based on the client sync loop. +This proposal originates from the needs of VoIP signalling in Matrix: -This covers how events can be queued for sending after a "passive termination" event. -The client is not in a state anymore to actually send requests, the server detects this -(by missing heartbeat pings) and sendes the pre-sent event. -This is sometimes called "last will" in other technologies. +The Client-Server API currently has a [Voice over IP module](https://spec.matrix.org/v1.11/client-server-api/#voice-over-ip) +that uses room messages to communicate the call state. However, it only allows for calls with two participants. -## Proposal +[MSC3401: Native Group VoIP Signalling](https://github.com/matrix-org/matrix-spec-proposals/pull/3401) proposes a scheme that +allows for more than two-participants by using room state events. -When an event is scheduled for sending the client gets a temporal so called `future_id` -to control when to actually send these events. -This allows a very flexible way to mark events as expired. -The sender predefines the event that will be sent once the condition "the client does not sent heartbeat pings" -anymore is met. -For call state events the future/"last will" version of the event would -be an event where the content communicates, that -the users has left the call. +In this arrangement each device signals its participant in a call by sending a state event that represents the device's +"membership" of a call. Once the device is no longer in the call, it sends a new state event to update the call state and say +that it is no longer a member. -Using the `future_id` the client can refresh the timeout. Allowing to delay the event multiple times. -A periodic refresh ping using this `future_id` can be used as a heartbeat mechanism. Once the refresh ping is not send -anymore the timeout condition is met and the homeserver sends the event with the expired content information. +This works well when the client is running and can send the state events as needed. However, if the client is not running and +able to communicate with the homeserver (e.g. the user closes the app or loses connection) the call state is not updated to +say that the participant has left. -This translate to: _"only send the event when the client is not running its program anymore (not sending the heartbeat anymore)"_ -We call those delayed events `Futures`. +The motivation for this MSC is to allow updating call member state events after the user disconnected by allowing to +schedule/delay/timeout/expire events in a generic way. -Futures reuse the `send` and `state` endpoint and extend them with an optional `future_timeout` query parameter. +The ["reliability requirements for the room state"](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md#reliability-requirements-for-the-room-state) +section of [MSC4143: MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) has more details on the use case. -`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{txnId}?future_timeout={timeout_duration}` +There are numerous possible solution to solve the call member event expiration. They are covered in detail +in the [Use case specific considerations/MatrixRTC](#use-case-specific-considerations) section, because they are not part +of this proposal. -`PUT /_matrix/client/v1/rooms/{roomId}/state/{eventType}/{stateKey}?future_timeout={timeout_duration}` +The proposal here allows a Matrix client to schedule a "hangup" state event to be sent after a specified time period. The client +can then periodically reset/restart the timer whilst it is running. If the client is no longer running or able to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. -The `future_timeout` query parameter is used to configure the event scheduling: +## Proposal -- `future_timeout: number` defines how long (in milliseconds) the homeserver will wait before sending - the event into the room. **Note**, since the timeout can be refreshed and sending the future can be triggered via an - endpoint (see: [Proposal/Delegating futures](#delegating-futures)) this value is not enough to predict the time this - event will arrive in the room. +We propose the following operations are added to the client-server API: -Possible error responses are all error responses that can occur when using the `send` and `state` endpoint accordingly. -Additionally the following can occur: +- Schedule an event to be sent at a later time +- Get a list of events that have been scheduled to send +- Refresh the timeout of a scheduled event +- Send the scheduled event immediately +- Cancel a scheduled event so that it is never sent -- The server can optionally configure a maximum `timeout_duration` - (In the order of one week dependent on how long they want to track futures) - The server will respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) (`Bad Request`, - with a matrix error code `M_FUTURE_MAX_TIMEOUT_EXCEEDED` - containing the maximum allowed `timeout_duration`) if the - client tries to send a timeout future with a larger `timeout_duration`. +At the point of an event being scheduled the homeserver is [unable to allocate the event ID](#allocating-event-id-at-the-point-of-scheduling-the-send). Instead, the homeserver allocates a _future ID_ to the scheduled event which is used during the above API operations. -The body is the same as [sending a normal event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) -or [sending a state event](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey). +### Scheduling a delayed event -Power levels are evaluated for each event only once the trigger has occurred and it will be distributed/inserted into the -DAG. -This implies a future can fail if it violates power levels at the time it resolves. -(It's also possible to successfully send a future the user has no permission to at the time of sending -if the power level situation has changed at the time the future resolves.) +We propose extending the existing +[`PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}`](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) +and +[`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey) +endpoints by adding an optional `future_timeout` query parameter. -### Response +The new query parameter is used to configure the event scheduling: -When sending a future the `event_id` is not yet available: +- `future_timeout` - Optional number of milliseconds the homeserver should wait before sending the event. If no `future_timeout` is provided, the event is sent immediately as normal. -- The `event_id` is using the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) which is - [calculated via the essential fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) - of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) -- Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG - (required for call duration computation) - we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. +The body of the request is the same as currently. -So the response will include a custom `future_id` but no `event_id`, -if the request was sent with the `future_timeout` parameter. -A request without query parameters would -get a response with an `event_id` (as before). +If a `future_timeout` is provided, the homeserver schedules the event to be sent with the specified delay and returns the _future ID_ in the `future_id` field (omitting the `event_id` as it is not available): -As a result the return type changes to: +```http +200 OK +Content-Type: application/json -```jsonc { - "event_id": "1234", // string | undefined - "future_id": "2345" //string | undefined, + "future_id": "1234567890" } ``` -For a future event the response looks as following: +The homeserver can optionally enforce a maximum timeout duration. If the requested timeout exceeds the maximum the homeserver +can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code `M_FUTURE_MAX_TIMEOUT_EXCEEDED` and the maximum allowed timeout (`timeout_duration` in milliseconds). + +For example the following specifies a maximum delay of 24 hours: + +```http +400 Bad Request +Content-Type: application/json -```jsonc { - "future_id": "1234" // string, + "errcode": "M_FUTURE_MAX_TIMEOUT_EXCEEDED", + "error": "The requested timeout exceeds the allowed maximum.", + "timeout_duration": 86400000 } ``` -Each `future_id` is a UUID similar to an `event_id`s. It is computed on the homeserver. -It can optionally be used to interact with futures. +### Getting delayed events -### Interacting with futures +We propose adding a new authenticated endpoint `GET /_matrix/client/v0/futures` to the client-server API to allow clients to get a list of all the events that have been scheduled to send in the future. -This MSC also proposes a authenticaed EDU endpoint: `update_future`. -The `future_id` can be used to send an EDUs to the `update_future` endpoint: -`POST /_matrix/client/v1/update_future` -using the following EDU body: +```http +HTTP 200 OK +Content-Type: application/json -```jsonc -{ - "future_id": "1234567890", // UUID - "action": "send" // "send"|"cancel"|"refresh, -} +[ + { + "url":"/_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}", + "body":{ + ...event_body + }, + "response":{ + "future_id": "1234567890", + "last_refresh": 1721732853284, // unix timestamp in milliseconds + "timeout": 15000 // milliseconds + } + } +] ``` -The homeserver does the following when receiving a Future: - -- It checks for the validity of the request (based on the `future_timeout` query parameter) - and returns a `400` or `410` if necessary. -- It **generates** a `future_id`. -- It **Starts a timer** for the Future using the `future_timeout` value. - - - If a `POST /_matrix/client/v1/update_future`, `{"action": "refresh"}` is received, it - **restarts the timer** with the stored `timeout_duration` for the associated timeout future. - - If a `POST /_matrix/client/v1/update_future`, `{"action": "send"}` is received, it - **sends the associated action or timeout future** - - If a `POST /_matrix/client/v1/update_future`, `{"action": "cancel"}` is received, it **does NOT send any future** - and deletes/invalidates the associated stored future. - - If a `POST /_matrix/client/v1/update_future` `{"future_id": "unknown"}` is received the server responds - with a `410` (Gone). - - If a timer times out, **it sends the timeout future**. - - If the homeserver receives a _new state event_ with the same state key as existing futures the - **futures get invalidated and the associated timers are stopped**. - - - There is no race condition here since a possible race between timeout and - the _new state event_ will always converge to the _new state event_: - - Timeout -> _new state event_: the room state will be updated twice. once by - the content of the future but later with the content of _new state event_. - - _new state event_ -> timeout: the _new state event_ will invalidate the future. - -- After the homeservers sends a timeout future, the associated - timer is deleted. +This can be used by clients to display events that have been scheduled to be sent in the future. -### Delegating futures +For use cases where the existence of a delayed event is also of interest for other room members, +(e.g. self-destructing messages) it is recommended to include this information in the original/affected event itself. -It is useful for external services to also interact with futures. If a client disconnects an external service can -be the best source to activate the Future/"last will". +### Managing delayed events -This is not covered in this MSC but could be realized with scoped access tokens. -A scoped token for only the `update_future` endpoint and a subset of `future_id`s would be used. +We propose adding a new authenticated client-server API endpoint `POST /_matrix/client/v1/update_future` to manage +delayed events that have already been scheduled. -An SFU for instance, that tracks the current client connection state, could be sent a request from the client that it -needs to call every X hours while a user is connected and a request it has to call once the user disconnects -(using a `{"action": "refresh}` and a `{"action": "send"}` `future_update` request.). -This way the SFU can be used as the source of truth for the call member room state even if the client -gets closed or looses connection and without knowing anything about the Matrix call. +The body of the request is a JSON object containing the following fields: -### Batch sending +- `future_id` - The `future_id` of the delayed event to update. +- `action` - The action to take on the delayed event. Must be one of: + - `send` - Send the delayed event immediately. + - `cancel` - Cancel the delayed event so that it is never sent. + - `refresh` - Restart the timeout of the delayed event. -Timed messages, tea timers, reminders or ephemeral events could be implemented -using this where clients send room events with -intentional mentions or a redaction as a future. +For example, the following would send the delayed event with `future_id` `1234567890` immediately: -In some scenarios it is important to allow to send an event with an associated -future at the same time. +```http +POST /_matrix/client/v1/update_future +Content-Type: application/json -- 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 `{}`. +{ + "future_id": "1234567890", + "action": "send" +} +``` -For this usecase batch sending of multiple futures would be desired. +### Homeserver implementation details -We do not include batch sending in the proposal of this MSC however since batch sending should -become a generic Matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) +#### Power levels are evaluated at the point of sending -There is a [batch sending version](#batch-sending-futures-with-custom-endpoint) in the Alternatives section -that proposes a future specific group sending endpoint in case this is required sooner then its realistic to implement -[MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). +Power levels are evaluated for each event only once the delay has occurred and it will be distributed/inserted into the +DAG. This implies a delayed event can fail if it violates power levels at the time the delay passes. -### Getting running futures +Conversely, it's also possible to successfully schedule an event that the user has no permission to at the time of sending +if the power level situation has changed at the time the delay passes. -Using `GET /_matrix/client/v0/futures` it is possible to get the list of all running futures issues by the authenticated -user. This is an authenticated endpoint. It sends back the json of the final event content with the associated tokens. +#### Delayed events are cancelled by a more recent state event -```jsonc -[ - { - "url":"/_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}", - "body":{ - ...event_body - }, - "response":{ - "future_id": UUID, - "last_refresh": Time, - "timeout": Number | undefined, - } - }, -] -``` +If a new state event is sent to the same room with the same (event type, state key) pair as a delayed event, the delayed event is cancelled. -This can be used so clients can optionally display events -that will be send in the future. +There is no race condition here since a possible race between timeout and the _new state event_ will always converge to the _new state event_: -For all usecases where the existence of a running future is also of interest for other room members, -(like self-destructing messages) it is recommended to include -this information in the effected event itself. +- timeout for _delayed event_ followed by _new state event_: the room state will be updated twice: once by the content of the delayed event but later with the content of _new state event_. +- _new state event_ followed by timeout for _delayed event_: the _new state event_ will cancel the outstanding _delayed event_. -## Usecase specific considerations +## Use case specific considerations ### MatrixRTC @@ -321,30 +260,31 @@ to only send one event for joining and one for leaving (reliably) - 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 +- 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 loses 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 `/update_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 -This MSC also allows to implement self-destructing messages: +This MSC also allows an implementation of "self-destructing" messages using redaction: First send (or generate the pdu when [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) is available): -`PUT /_matrix/client/v1/rooms/{roomId}/send/{eventType}/{eventType}/{txnId}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/m.room.message/{txnId}` ```jsonc { - "m.text": "my msg" + "msgtype": "m.text", + "body": "this message will self-redact in 10 minutes" } ``` then send: -`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?timeout={10*60}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/m.room.redaction/{txnId}?future_timeout=600000` ```jsonc { @@ -352,22 +292,63 @@ then send: } ``` -This would redact the message with content: `"m.text": "my msg"` after 10minutes. +This would redact the message with content: `"m.text": "my msg"` after 10 minutes. ## Potential issues ## Alternatives +### Delegating futures + +It is useful for external services to also interact with futures. If a client disconnects an external service can +be the best source to activate the Future/"last will". + +This is not covered in this MSC but could be realized with scoped access tokens. +A scoped token for only the `update_future` endpoint and a subset of `future_id`s would be used. + +An SFU for instance, that tracks the current client connection state, could be sent a request from the client that it +needs to call every X hours while a user is connected and a request it has to call once the user disconnects +(using a `{"action": "refresh}` and a `{"action": "send"}` `future_update` request.). +This way the SFU can be used as the source of truth for the call member room state even if the client +gets closed or looses connection and without knowing anything about the Matrix call. + +### Batch sending + +Timed messages, tea timers, reminders or ephemeral events could be implemented +using this where clients send room events with +intentional mentions or a redaction as a future. + +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 use case batch sending of multiple futures would be desired. + +We do not include batch sending in the proposal of this MSC however since batch sending should +become a generic Matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) + +There is a [batch sending version](#batch-sending-futures-with-custom-endpoint) in the Alternatives section +that proposes a future specific group sending endpoint in case this is required sooner then its realistic to implement +[MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). + ### Not reusing the `send`/`state` endpoint -Alternatively new endpoints could be introduced to not oveload the `send` and `state` endpoint. -Those endpoints would be called: +Alternatively new endpoints could be introduced to not overload the `send` and `state` endpoint. +Those endpoints could be called: `PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}` `PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}` -This keeps the response type of the `send` and `state` endpoint in tact and we get a different return type -the new `send_future` and `state_future` endpoint. +This would allow the response for the `send` and `state` endpoints intact and we get a different return type +for the new `send_future` and `state_future` endpoints. ### Batch sending futures with custom endpoint @@ -453,10 +434,20 @@ This means, - `POST /_matrix/client/v1/update_future` to canceling, refreshing and sending futures uses the exact same endpoints. -Also the behaviour of the homeserver on when to invalidate the furures is identical except, that +Also the behaviour of the homeserver on when to invalidate the futures is identical except, that we don't need the error code `409` anymore since the events are sent as a batch and there cannot be an action future without a timeout future. +#### Allocating event ID at the point of scheduling the send + +This was considered, but when sending a future the `event_id` is not yet available: + +The Matrix spec says that the `event_id` must use the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) +which is [calculated from the fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) +of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) + +Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. + #### EventId template variable It would be useful to be able to send redactions and edits as one HTTP request. @@ -481,7 +472,7 @@ The **Self-destructing messages** example be a single request: }, "m.timeout": 10*60, "m.send_on_timeout": { - "type":"m.room.readact", + "type":"m.room.redaction", "content":{ "redacts": "$m.send_now.event_id" } @@ -511,10 +502,22 @@ The timeout for syncs are much slower than what would be desirable (30s vs 5s). With a widget implementation for calls we cannot guarantee that the widget is running during the sync loop. So one either has to move the hangup logic to the hosting client or let the widget run all the time. -With a dedicated ping (independent to the sync loop) it is more felxibale and allows us to let the widget +With a dedicated ping (independent to the sync loop) it is more flexible and allows us to let the widget execute the refresh. If the widget dies, the call membership will disconnect. +### Federated futures + +The delayed/scheduled events could be sent over federation immediately and then have the receiving servers process them at the appropriate time. + +### MQTT style Last Will + +[MQTT](https://mqtt.org/) has the concept of a Will Message that is published by the server when a client disconnects. + +The client can set a Will Message when it connects to the server. If the client disconnects unexpectedly the server will publish the Will Message if the client is not back online within a specified time. + +A similar concept could be applied to Matrix by having the client specify a set of "Last Will" event(s) and have the homeserver trigger them if the client (possibly identified by device ID) does not send an API request within a specified time. + ### Naming The following alternative names for this concept are considered @@ -530,8 +533,43 @@ Servers SHOULD impose a maximum timeout value for future timeouts of not more th ## Unstable prefix -Use `org.matrix.msc4140.` instead of `m.` as long as the MSC is not stable. -For the endpoints introduced in this MSC use the prefix `/org.matrix.msc4140/` and set the paths version string to unstable, -instead of v#. +Whilst the MSC is in the proposal stage, the following should be used: + +- `org.matrix.msc4140.future_timeout` should be used instead of the `future_timeout` query parameter. +- `POST /_matrix/client/unstable/org.matrix.msc4140/update_future` should be used instead of the `POST /_matrix/client/v1/update_future` endpoint. +- `GET /_matrix/client/unstable/org.matrix.msc4140/futures` should be used instead of the `GET /_matrix/client/v0/futures` endpoint. +- The `M_UNKNOWN` `errcode` should be used instead of `M_FUTURE_MAX_TIMEOUT_EXCEEDED` as follows: + +```json +{ + "errcode": "M_UNKNOWN", + "error": "The requested timeout exceeds the allowed maximum.", + "org.matrix.msc4140.errcode": "M_FUTURE_MAX_TIMEOUT_EXCEEDED", + "org.matrix.msc4140.timeout_duration": 86400000 +} +``` + +instead of: + +```json +{ + "errcode": "M_FUTURE_MAX_TIMEOUT_EXCEEDED", + "error": "The requested timeout exceeds the allowed maximum.", + "timeout_duration": 86400000 +} +``` + +Additionally, the feature is to be advertised as unstable feature in the `GET /_matrix/client/versions` response, with the key `org.matrix.msc4140` set to `true`. So, the response could look then as following: + +```json +{ + "versions": ["..."], + "unstable_features": { + "org.matrix.msc4140": true + } +} +``` ## Dependencies + +None. From d195218391ae5310e1ab476be5fe490db0ea6b21 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 23 Jul 2024 15:46:22 +0100 Subject: [PATCH 35/85] Update to GET /futures --- proposals/4140-delayed-events-futures.md | 38 +++++++++++++++++------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 132b51855db..329e5e2925a 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -135,21 +135,37 @@ We propose adding a new authenticated endpoint `GET /_matrix/client/v0/futures` HTTP 200 OK Content-Type: application/json -[ - { - "url":"/_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}", - "body":{ - ...event_body - }, - "response":{ +{ + "futures": [ + { "future_id": "1234567890", - "last_refresh": 1721732853284, // unix timestamp in milliseconds - "timeout": 15000 // milliseconds + "room_id": "!roomid:example.com", + "event_type": "m.room.message", + "timeout": 15000, + "running_since": 1721732853284, + "content":{ + "msgtype": "m.text", + "body": "I am now offline" + } + }, + { + "future_id": "abcdefgh", + "room_id": "!roomid:example.com", + "event_type": "m.call.member", + "state_key": "@user:example.com_DEVICEID", + "timeout": 5000, + "running_since": 1721732853284, + "content":{ + "memberships": [] + } } - } -] + ] +} ``` +`running_since` is the timestamp (as unix time in milliseconds) when the delayed event was scheduled or last refreshed. +So, unless the delayed event is updated beforehand, the event will be sent after `running_since` + `timeout`. + This can be used by clients to display events that have been scheduled to be sent in the future. For use cases where the existence of a delayed event is also of interest for other room members, From b5ac9b22237e0c8b1a876342b235ebcae17e0191 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Tue, 23 Jul 2024 15:55:54 +0100 Subject: [PATCH 36/85] Standardise endpoint for interacting with futures --- proposals/4140-delayed-events-futures.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 329e5e2925a..42ad55d5214 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -129,7 +129,7 @@ Content-Type: application/json ### Getting delayed events -We propose adding a new authenticated endpoint `GET /_matrix/client/v0/futures` to the client-server API to allow clients to get a list of all the events that have been scheduled to send in the future. +We propose adding a new authenticated endpoint `GET /_matrix/client/v1/futures` to the client-server API to allow clients to get a list of all the events that have been scheduled to send in the future. ```http HTTP 200 OK @@ -173,18 +173,26 @@ For use cases where the existence of a delayed event is also of interest for oth ### Managing delayed events -We propose adding a new authenticated client-server API endpoint `POST /_matrix/client/v1/update_future` to manage +We propose adding a new authenticated client-server API endpoint at `POST /_matrix/client/v1/futures/{future_id}` to manage delayed events that have already been scheduled. The body of the request is a JSON object containing the following fields: -- `future_id` - The `future_id` of the delayed event to update. - `action` - The action to take on the delayed event. Must be one of: - `send` - Send the delayed event immediately. - `cancel` - Cancel the delayed event so that it is never sent. - `refresh` - Restart the timeout of the delayed event. -For example, the following would send the delayed event with `future_id` `1234567890` immediately: +For example, the following would send the delayed event with future ID `1234567890` immediately: + +```http +POST /_matrix/client/v1/futures/1234567890 +Content-Type: application/json + +{ + "action": "send" +} +``` ```http POST /_matrix/client/v1/update_future @@ -552,8 +560,8 @@ Servers SHOULD impose a maximum timeout value for future timeouts of not more th Whilst the MSC is in the proposal stage, the following should be used: - `org.matrix.msc4140.future_timeout` should be used instead of the `future_timeout` query parameter. -- `POST /_matrix/client/unstable/org.matrix.msc4140/update_future` should be used instead of the `POST /_matrix/client/v1/update_future` endpoint. -- `GET /_matrix/client/unstable/org.matrix.msc4140/futures` should be used instead of the `GET /_matrix/client/v0/futures` endpoint. +- `POST /_matrix/client/unstable/org.matrix.msc4140/futures/{future_id}` should be used instead of the `POST /_matrix/client/v1/futures/{future_id}` endpoint. +- `GET /_matrix/client/unstable/org.matrix.msc4140/futures` should be used instead of the `GET /_matrix/client/v1/futures` endpoint. - The `M_UNKNOWN` `errcode` should be used instead of `M_FUTURE_MAX_TIMEOUT_EXCEEDED` as follows: ```json From c07dd9b90fd95bdbb6b17a1fcff5952f4d7349d3 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 10:27:30 +0100 Subject: [PATCH 37/85] Make it clear that events can be cancelled --- proposals/4140-delayed-events-futures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 42ad55d5214..4fa3a542179 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,4 +1,4 @@ -# MSC4140: Delayed events (Futures) +# MSC4140: Cancellable delayed events (Futures) This MSC proposes a mechanism by which a Matrix client can schedule an event (including a state event) to be sent into a room on behalf of a user at a later time. @@ -10,7 +10,7 @@ time and then distributing as normal via federation. -- [MSC4140: Delayed events Futures](#msc4140-delayed-events-futures) +- [MSC4140: Cancellable delayed events (Futures)](#msc4140-cancellable-delayed-events-futures) - [Background and motivation](#background-and-motivation) - [Proposal](#proposal) - [Scheduling a delayed event](#scheduling-a-delayed-event) From b79e4e2762648dd4fb8f4b4839f350cb27c12302 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 15:53:57 +0100 Subject: [PATCH 38/85] Make wording more authoritative --- proposals/4140-delayed-events-futures.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 4fa3a542179..9966cece848 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -10,7 +10,7 @@ time and then distributing as normal via federation. -- [MSC4140: Cancellable delayed events (Futures)](#msc4140-cancellable-delayed-events-futures) +- [MSC4140: Cancellable delayed events Futures](#msc4140-cancellable-delayed-events-futures) - [Background and motivation](#background-and-motivation) - [Proposal](#proposal) - [Scheduling a delayed event](#scheduling-a-delayed-event) @@ -76,7 +76,7 @@ can then periodically reset/restart the timer whilst it is running. If the clien ## Proposal -We propose the following operations are added to the client-server API: +The following operations are added to the client-server API: - Schedule an event to be sent at a later time - Get a list of events that have been scheduled to send @@ -88,11 +88,11 @@ At the point of an event being scheduled the homeserver is [unable to allocate t ### Scheduling a delayed event -We propose extending the existing +An optional `future_timeout` query parameter is added to the existing [`PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}`](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) and [`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey) -endpoints by adding an optional `future_timeout` query parameter. +endpoints. The new query parameter is used to configure the event scheduling: @@ -129,7 +129,7 @@ Content-Type: application/json ### Getting delayed events -We propose adding a new authenticated endpoint `GET /_matrix/client/v1/futures` to the client-server API to allow clients to get a list of all the events that have been scheduled to send in the future. +A new authenticated client-server API endpoint `GET /_matrix/client/v1/futures` allow clients to get a list of all the events that have been scheduled to send in the future. ```http HTTP 200 OK @@ -173,8 +173,8 @@ For use cases where the existence of a delayed event is also of interest for oth ### Managing delayed events -We propose adding a new authenticated client-server API endpoint at `POST /_matrix/client/v1/futures/{future_id}` to manage -delayed events that have already been scheduled. +A new authenticated client-server API endpoint at `POST /_matrix/client/v1/futures/{future_id}` allows scheduled events +to be managed. The body of the request is a JSON object containing the following fields: From e125901c6809022ab24c6f7f23c6abd1a648047e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 15:55:30 +0100 Subject: [PATCH 39/85] Remove erroneous old example --- proposals/4140-delayed-events-futures.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 9966cece848..85c01eb5e4e 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -194,16 +194,6 @@ Content-Type: application/json } ``` -```http -POST /_matrix/client/v1/update_future -Content-Type: application/json - -{ - "future_id": "1234567890", - "action": "send" -} -``` - ### Homeserver implementation details #### Power levels are evaluated at the point of sending From 0443cd9509b0c05e2deaa46f02177766a3b8d669 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 16:05:16 +0100 Subject: [PATCH 40/85] Take view that clients don't act on behalf of a user --- proposals/4140-delayed-events-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 85c01eb5e4e..1610f0519e0 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,7 +1,7 @@ # MSC4140: Cancellable delayed events (Futures) This MSC proposes a mechanism by which a Matrix client can schedule an event (including a state event) to be sent into -a room on behalf of a user at a later time. +a room at a later time. The client does not have to be running or in contact with the Homeserver at the time that the event is actually sent. From 7df09198aa223b865c37002fb8616c73f2e30ecb Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 17:07:43 +0100 Subject: [PATCH 41/85] Use name delay_id and delayed_events --- proposals/4140-delayed-events-futures.md | 62 ++++++++++++------------ 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 1610f0519e0..7a31fbf8326 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -88,7 +88,7 @@ At the point of an event being scheduled the homeserver is [unable to allocate t ### Scheduling a delayed event -An optional `future_timeout` query parameter is added to the existing +An optional `delay` query parameter is added to the existing [`PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}`](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid) and [`PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`](https://spec.matrix.org/v1.11/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey) @@ -96,23 +96,23 @@ endpoints. The new query parameter is used to configure the event scheduling: -- `future_timeout` - Optional number of milliseconds the homeserver should wait before sending the event. If no `future_timeout` is provided, the event is sent immediately as normal. +- `delay` - Optional number of milliseconds the homeserver should wait before sending the event. If no `delay` is provided, the event is sent immediately as normal. The body of the request is the same as currently. -If a `future_timeout` is provided, the homeserver schedules the event to be sent with the specified delay and returns the _future ID_ in the `future_id` field (omitting the `event_id` as it is not available): +If a `delay` is provided, the homeserver schedules the event to be sent with the specified delay and returns the _delay ID_ in the `delay_id` field (omitting the `event_id` as it is not available): ```http 200 OK Content-Type: application/json { - "future_id": "1234567890" + "delay_id": "1234567890" } ``` -The homeserver can optionally enforce a maximum timeout duration. If the requested timeout exceeds the maximum the homeserver -can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code `M_FUTURE_MAX_TIMEOUT_EXCEEDED` and the maximum allowed timeout (`timeout_duration` in milliseconds). +The homeserver can optionally enforce a maximum delay duration. If the requested delay exceeds the maximum the homeserver +can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code `M_FUTURE_MAX_DELAY_EXCEEDED` and the maximum allowed delay (`max_delay` in milliseconds). For example the following specifies a maximum delay of 24 hours: @@ -121,15 +121,15 @@ For example the following specifies a maximum delay of 24 hours: Content-Type: application/json { - "errcode": "M_FUTURE_MAX_TIMEOUT_EXCEEDED", - "error": "The requested timeout exceeds the allowed maximum.", - "timeout_duration": 86400000 + "errcode": "M_FUTURE_MAX_DELAY_EXCEEDED", + "error": "The requested delay exceeds the allowed maximum.", + "max_delay": 86400000 } ``` ### Getting delayed events -A new authenticated client-server API endpoint `GET /_matrix/client/v1/futures` allow clients to get a list of all the events that have been scheduled to send in the future. +A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allow clients to get a list of all the events that have been scheduled to send in the future. ```http HTTP 200 OK @@ -138,10 +138,10 @@ Content-Type: application/json { "futures": [ { - "future_id": "1234567890", + "delay_id": "1234567890", "room_id": "!roomid:example.com", "event_type": "m.room.message", - "timeout": 15000, + "delay": 15000, "running_since": 1721732853284, "content":{ "msgtype": "m.text", @@ -149,11 +149,11 @@ Content-Type: application/json } }, { - "future_id": "abcdefgh", + "delay_id": "abcdefgh", "room_id": "!roomid:example.com", "event_type": "m.call.member", "state_key": "@user:example.com_DEVICEID", - "timeout": 5000, + "delay": 5000, "running_since": 1721732853284, "content":{ "memberships": [] @@ -164,7 +164,7 @@ Content-Type: application/json ``` `running_since` is the timestamp (as unix time in milliseconds) when the delayed event was scheduled or last refreshed. -So, unless the delayed event is updated beforehand, the event will be sent after `running_since` + `timeout`. +So, unless the delayed event is updated beforehand, the event will be sent after `running_since` + `delay`. This can be used by clients to display events that have been scheduled to be sent in the future. @@ -173,7 +173,7 @@ For use cases where the existence of a delayed event is also of interest for oth ### Managing delayed events -A new authenticated client-server API endpoint at `POST /_matrix/client/v1/futures/{future_id}` allows scheduled events +A new authenticated client-server API endpoint at `POST /_matrix/client/v1/delayed_events/{delay_id}` allows scheduled events to be managed. The body of the request is a JSON object containing the following fields: @@ -181,12 +181,12 @@ The body of the request is a JSON object containing the following fields: - `action` - The action to take on the delayed event. Must be one of: - `send` - Send the delayed event immediately. - `cancel` - Cancel the delayed event so that it is never sent. - - `refresh` - Restart the timeout of the delayed event. + - `restart` - Restart the timeout of the delayed event. -For example, the following would send the delayed event with future ID `1234567890` immediately: +For example, the following would send the delayed event with delay ID `1234567890` immediately: ```http -POST /_matrix/client/v1/futures/1234567890 +POST /_matrix/client/v1/delayed_events/1234567890 Content-Type: application/json { @@ -298,7 +298,7 @@ is available): ``` then send: -`PUT /_matrix/client/v1/rooms/{roomId}/send/m.room.redaction/{txnId}?future_timeout=600000` +`PUT /_matrix/client/v1/rooms/{roomId}/send/m.room.redaction/{txnId}?delay=600000` ```jsonc { @@ -318,7 +318,7 @@ It is useful for external services to also interact with futures. If a client di be the best source to activate the Future/"last will". This is not covered in this MSC but could be realized with scoped access tokens. -A scoped token for only the `update_future` endpoint and a subset of `future_id`s would be used. +A scoped token for only the `update_future` endpoint and a subset of `timeout_event_id`s would be used. An SFU for instance, that tracks the current client connection state, could be sent a request from the client that it needs to call every X hours while a user is connected and a request it has to call once the user disconnects @@ -549,17 +549,17 @@ Servers SHOULD impose a maximum timeout value for future timeouts of not more th Whilst the MSC is in the proposal stage, the following should be used: -- `org.matrix.msc4140.future_timeout` should be used instead of the `future_timeout` query parameter. -- `POST /_matrix/client/unstable/org.matrix.msc4140/futures/{future_id}` should be used instead of the `POST /_matrix/client/v1/futures/{future_id}` endpoint. -- `GET /_matrix/client/unstable/org.matrix.msc4140/futures` should be used instead of the `GET /_matrix/client/v1/futures` endpoint. -- The `M_UNKNOWN` `errcode` should be used instead of `M_FUTURE_MAX_TIMEOUT_EXCEEDED` as follows: +- `org.matrix.msc4140.delay` should be used instead of the `delay` query parameter. +- `POST /_matrix/client/unstable/org.matrix.msc4140/delayed_events/{delay_id}` should be used instead of the `POST /_matrix/client/v1/delayed_events/{delay_id}` endpoint. +- `GET /_matrix/client/unstable/org.matrix.msc4140/delayed_events` should be used instead of the `GET /_matrix/client/v1/delayed_events` endpoint. +- The `M_UNKNOWN` `errcode` should be used instead of `M_FUTURE_MAX_DELAY_EXCEEDED` as follows: ```json { "errcode": "M_UNKNOWN", - "error": "The requested timeout exceeds the allowed maximum.", - "org.matrix.msc4140.errcode": "M_FUTURE_MAX_TIMEOUT_EXCEEDED", - "org.matrix.msc4140.timeout_duration": 86400000 + "error": "The requested delay exceeds the allowed maximum.", + "org.matrix.msc4140.errcode": "M_FUTURE_MAX_DELAY_EXCEEDED", + "org.matrix.msc4140.max_delay": 86400000 } ``` @@ -567,9 +567,9 @@ instead of: ```json { - "errcode": "M_FUTURE_MAX_TIMEOUT_EXCEEDED", - "error": "The requested timeout exceeds the allowed maximum.", - "timeout_duration": 86400000 + "errcode": "M_FUTURE_MAX_DELAY_EXCEEDED", + "error": "The requested delay exceeds the allowed maximum.", + "max_delay": 86400000 } ``` From ff9514485e42b6b18d6df79ebbb1dda59fef212a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 17:34:22 +0100 Subject: [PATCH 42/85] Clean up more references to future --- proposals/4140-delayed-events-futures.md | 30 ++++++++++++------------ 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 7a31fbf8326..eae6a84b208 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -1,4 +1,4 @@ -# MSC4140: Cancellable delayed events (Futures) +# MSC4140: Cancellable delayed events This MSC proposes a mechanism by which a Matrix client can schedule an event (including a state event) to be sent into a room at a later time. @@ -10,7 +10,7 @@ time and then distributing as normal via federation. -- [MSC4140: Cancellable delayed events Futures](#msc4140-cancellable-delayed-events-futures) +- [MSC4140: Cancellable delayed events](#msc4140-cancellable-delayed-events) - [Background and motivation](#background-and-motivation) - [Proposal](#proposal) - [Scheduling a delayed event](#scheduling-a-delayed-event) @@ -84,7 +84,7 @@ The following operations are added to the client-server API: - Send the scheduled event immediately - Cancel a scheduled event so that it is never sent -At the point of an event being scheduled the homeserver is [unable to allocate the event ID](#allocating-event-id-at-the-point-of-scheduling-the-send). Instead, the homeserver allocates a _future ID_ to the scheduled event which is used during the above API operations. +At the point of an event being scheduled the homeserver is [unable to allocate the event ID](#allocating-event-id-at-the-point-of-scheduling-the-send). Instead, the homeserver allocates a _delay ID_ to the scheduled event which is used during the above API operations. ### Scheduling a delayed event @@ -112,7 +112,7 @@ Content-Type: application/json ``` The homeserver can optionally enforce a maximum delay duration. If the requested delay exceeds the maximum the homeserver -can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code `M_FUTURE_MAX_DELAY_EXCEEDED` and the maximum allowed delay (`max_delay` in milliseconds). +can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code `M_MAX_DELAY_EXCEEDED` and the maximum allowed delay (`max_delay` in milliseconds). For example the following specifies a maximum delay of 24 hours: @@ -121,7 +121,7 @@ For example the following specifies a maximum delay of 24 hours: Content-Type: application/json { - "errcode": "M_FUTURE_MAX_DELAY_EXCEEDED", + "errcode": "M_MAX_DELAY_EXCEEDED", "error": "The requested delay exceeds the allowed maximum.", "max_delay": 86400000 } @@ -357,9 +357,9 @@ that proposes a future specific group sending endpoint in case this is required Alternatively new endpoints could be introduced to not overload the `send` and `state` endpoint. Those endpoints could be called: -`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?future_timeout={timeout_duration}` +`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?delay={delay_ms}` -`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?future_timeout={timeout_duration}` +`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?delay={delay_ms}` This would allow the response for the `send` and `state` endpoints intact and we get a different return type for the new `send_future` and `state_future` endpoints. @@ -373,12 +373,12 @@ with one request. Otherwise there is a risk for the client to lose connection or event and the future which results in never expiring call membership or never destructing self-destructing messages. This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. -(Then the `future_timeout` could be added +(Then the `delay` could be added to the `PDUInfo` instead of the query parameters and everything could be send at once.) This would be the preferred solution since we currently don't have any other batch sending mechanism. It would however require lots of changes since a new widget action for futures would be needed. -With the current main proposal it is enough to add a `future_timeout` to the send message +With the current main proposal it is enough to add a `delay` to the send message widget action. The widget driver would then take care of calling `send` or `send_future` based on the presence of those fields. @@ -428,12 +428,12 @@ The response will be a collection of all the futures with the same fields as in ```jsonc { "send_on_timeout": { - "future_id": "future_id", + "delay_id": "delay_id", }, // optional "send_on_action": { - "${action1}": { "future_id": "future_id1" }, - "${action2}": { "future_id": "future_id2" } + "${action1}": { "delay_id": "delay_id1" }, + "${action2}": { "delay_id": "delay_id2" } }, // optional @@ -552,13 +552,13 @@ Whilst the MSC is in the proposal stage, the following should be used: - `org.matrix.msc4140.delay` should be used instead of the `delay` query parameter. - `POST /_matrix/client/unstable/org.matrix.msc4140/delayed_events/{delay_id}` should be used instead of the `POST /_matrix/client/v1/delayed_events/{delay_id}` endpoint. - `GET /_matrix/client/unstable/org.matrix.msc4140/delayed_events` should be used instead of the `GET /_matrix/client/v1/delayed_events` endpoint. -- The `M_UNKNOWN` `errcode` should be used instead of `M_FUTURE_MAX_DELAY_EXCEEDED` as follows: +- The `M_UNKNOWN` `errcode` should be used instead of `M_MAX_DELAY_EXCEEDED` as follows: ```json { "errcode": "M_UNKNOWN", "error": "The requested delay exceeds the allowed maximum.", - "org.matrix.msc4140.errcode": "M_FUTURE_MAX_DELAY_EXCEEDED", + "org.matrix.msc4140.errcode": "M_MAX_DELAY_EXCEEDED", "org.matrix.msc4140.max_delay": 86400000 } ``` @@ -567,7 +567,7 @@ instead of: ```json { - "errcode": "M_FUTURE_MAX_DELAY_EXCEEDED", + "errcode": "M_MAX_DELAY_EXCEEDED", "error": "The requested delay exceeds the allowed maximum.", "max_delay": 86400000 } From 93c932aa01faed18b81048fb30c2fb781b78a50e Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 17:38:29 +0100 Subject: [PATCH 43/85] event_type => type --- proposals/4140-delayed-events-futures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index eae6a84b208..f2d6111b246 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -140,7 +140,7 @@ Content-Type: application/json { "delay_id": "1234567890", "room_id": "!roomid:example.com", - "event_type": "m.room.message", + "type": "m.room.message", "delay": 15000, "running_since": 1721732853284, "content":{ @@ -151,7 +151,7 @@ Content-Type: application/json { "delay_id": "abcdefgh", "room_id": "!roomid:example.com", - "event_type": "m.call.member", + "type": "m.call.member", "state_key": "@user:example.com_DEVICEID", "delay": 5000, "running_since": 1721732853284, From 6cce3bd4050819451c5424d8688a53e83f3d8c89 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 18:21:00 +0100 Subject: [PATCH 44/85] Tidying --- proposals/4140-delayed-events-futures.md | 44 +++++++++++++----------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index f2d6111b246..045164585a9 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -31,10 +31,10 @@ time and then distributing as normal via federation. - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) - [Batch Response](#batch-response) - - [Allocating event ID at the point of scheduling the send](#allocating-event-id-at-the-point-of-scheduling-the-send) - [EventId template variable](#eventid-template-variable) + - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) - - [Federated futures](#federated-futures) + - [Federated delayed events](#federated-delayed-events) - [MQTT style Last Will](#mqtt-style-last-will) - [Naming](#naming) - [Security considerations](#security-considerations) @@ -79,10 +79,10 @@ can then periodically reset/restart the timer whilst it is running. If the clien The following operations are added to the client-server API: - Schedule an event to be sent at a later time -- Get a list of events that have been scheduled to send -- Refresh the timeout of a scheduled event -- Send the scheduled event immediately -- Cancel a scheduled event so that it is never sent +- Get a list of delayed events +- Restart the timer of a delayed event +- Send the delayed event immediately +- Cancel a delayed event so that it is never sent At the point of an event being scheduled the homeserver is [unable to allocate the event ID](#allocating-event-id-at-the-point-of-scheduling-the-send). Instead, the homeserver allocates a _delay ID_ to the scheduled event which is used during the above API operations. @@ -129,14 +129,15 @@ Content-Type: application/json ### Getting delayed events -A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allow clients to get a list of all the events that have been scheduled to send in the future. +A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of all the delayed events that +have been scheduled to send. ```http HTTP 200 OK Content-Type: application/json { - "futures": [ + "delayed_events": [ { "delay_id": "1234567890", "room_id": "!roomid:example.com", @@ -452,16 +453,6 @@ Also the behaviour of the homeserver on when to invalidate the futures is identi we don't need the error code `409` anymore since the events are sent as a batch and there cannot be an action future without a timeout future. -#### Allocating event ID at the point of scheduling the send - -This was considered, but when sending a future the `event_id` is not yet available: - -The Matrix spec says that the `event_id` must use the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) -which is [calculated from the fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) -of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) - -Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. - #### EventId template variable It would be useful to be able to send redactions and edits as one HTTP request. @@ -472,6 +463,7 @@ to reference the event to redact. For this reason, template variables are introduced that are only valid in `Future` events. `$m.send_now.event_id` in the content of one of the `send_on_action` and `send_on_timeout` this template variable can be used. + The **Self-destructing messages** example be a single request: `PUT /_matrix/client/v1/rooms/{roomId}/send/future/{txnId}` @@ -500,9 +492,19 @@ At this point the client has the id but the event is not in the DAG. So it would be trivial to sign both the event and the redaction/related event and then send them via `/send_pdus` (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)). +### Allocating the event ID at the point of scheduling the send + +This was considered, but when sending a future the `event_id` is not yet available: + +The Matrix spec says that the `event_id` must use the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) +which is [calculated from the fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) +of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) + +Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. + ### MSC4018 (use client sync loop) -[MSC4018](https://github.com/matrix-org/matrix-spec-proposals/pull/4018) also +[MSC4018: Reliable call membership](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 @@ -520,9 +522,9 @@ With a dedicated ping (independent to the sync loop) it is more flexible and all execute the refresh. If the widget dies, the call membership will disconnect. -### Federated futures +### Federated delayed events -The delayed/scheduled events could be sent over federation immediately and then have the receiving servers process them at the appropriate time. +The delayed events could be sent over federation immediately and then have the receiving servers process them at the appropriate time. ### MQTT style Last Will From ac2d2c5cfec3be5fb56003fce3f39a1a448f80c6 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 18:25:17 +0100 Subject: [PATCH 45/85] Pagination on GET endpoint --- proposals/4140-delayed-events-futures.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 045164585a9..440b9e5f889 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -132,6 +132,23 @@ Content-Type: application/json A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of all the delayed events that have been scheduled to send. +The endpoint accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as per the +[pagination convention](https://spec.matrix.org/v1.11/appendices/#pagination). The homeserver can choose a suitable page size. + +The response is a JSON object containing the following fields: + +- `delayed_events` - Required. An array of delayed events that have been scheduled to send. + - `delay_id` - Required. The ID of the delayed event. + - `room_id` - Required. The room ID of the delayed event. + - `type` - Required. The event type of the delayed event. + - `state_key` - Optional. The state key of the delayed event if it is a state event. + - `delay` - Required. The delay in milliseconds before the event is sent. + - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or last restarted. + - `content` - Required. The content of the delayed event. +- `next_batch` - Optional. A token that can be used to paginate the list of delayed events. + +For example: + ```http HTTP 200 OK Content-Type: application/json @@ -160,7 +177,8 @@ Content-Type: application/json "memberships": [] } } - ] + ], + "next_batch": "b12345" } ``` From 84e20fd05070698f8377f3ecc4957969338d89b9 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 24 Jul 2024 18:29:04 +0100 Subject: [PATCH 46/85] Add alternative of M_INVALID_PARAM --- proposals/4140-delayed-events-futures.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 440b9e5f889..81566cf718b 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -552,6 +552,10 @@ The client can set a Will Message when it connects to the server. If the client A similar concept could be applied to Matrix by having the client specify a set of "Last Will" event(s) and have the homeserver trigger them if the client (possibly identified by device ID) does not send an API request within a specified time. +### `M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED` + +The existing `M_INVALID_PARAM` error code could be used instead of introducing a new error code `M_MAX_DELAY_EXCEEDED`. + ### Naming The following alternative names for this concept are considered From ac3bd9de1d28a2caa47b4cde80d74752e2ca9c7e Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 25 Jul 2024 08:36:31 +0200 Subject: [PATCH 47/85] change order of manage <-> get sections --- proposals/4140-delayed-events-futures.md | 105 ++++++++++++----------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 81566cf718b..2e6242c6384 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -11,35 +11,36 @@ time and then distributing as normal via federation. - [MSC4140: Cancellable delayed events](#msc4140-cancellable-delayed-events) - - [Background and motivation](#background-and-motivation) - - [Proposal](#proposal) - - [Scheduling a delayed event](#scheduling-a-delayed-event) - - [Getting delayed events](#getting-delayed-events) - - [Managing delayed events](#managing-delayed-events) - - [Homeserver implementation details](#homeserver-implementation-details) - - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) - - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) - - [Use case specific considerations](#use-case-specific-considerations) - - [MatrixRTC](#matrixrtc) - - [Background](#background) - - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) - - [Self-destructing messages](#self-destructing-messages) - - [Potential issues](#potential-issues) - - [Alternatives](#alternatives) - - [Delegating futures](#delegating-futures) - - [Batch sending](#batch-sending) - - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) - - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) - - [Batch Response](#batch-response) - - [EventId template variable](#eventid-template-variable) - - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) - - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) - - [Federated delayed events](#federated-delayed-events) - - [MQTT style Last Will](#mqtt-style-last-will) - - [Naming](#naming) - - [Security considerations](#security-considerations) - - [Unstable prefix](#unstable-prefix) - - [Dependencies](#dependencies) + - [Background and motivation](#background-and-motivation) + - [Proposal](#proposal) + - [Scheduling a delayed event](#scheduling-a-delayed-event) + - [Managing delayed events](#managing-delayed-events) + - [Getting delayed events](#getting-delayed-events) + - [Homeserver implementation details](#homeserver-implementation-details) + - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) + - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) + - [Use case specific considerations](#use-case-specific-considerations) + - [MatrixRTC](#matrixrtc) + - [Background](#background) + - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) + - [Self-destructing messages](#self-destructing-messages) + - [Potential issues](#potential-issues) + - [Alternatives](#alternatives) + - [Delegating futures](#delegating-futures) + - [Batch sending](#batch-sending) + - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) + - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) + - [Batch Response](#batch-response) + - [EventId template variable](#eventid-template-variable) + - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) + - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) + - [Federated delayed events](#federated-delayed-events) + - [MQTT style Last Will](#mqtt-style-last-will) + - [M_INVALID_PARAM instead of M_MAX_DELAY_EXCEEDED](#m_invalid_param-instead-of-m_max_delay_exceeded) + - [Naming](#naming) + - [Security considerations](#security-considerations) + - [Unstable prefix](#unstable-prefix) + - [Dependencies](#dependencies) @@ -127,6 +128,29 @@ Content-Type: application/json } ``` +### Managing delayed events + +A new authenticated client-server API endpoint at `POST /_matrix/client/v1/delayed_events/{delay_id}` allows scheduled events +to be managed. + +The body of the request is a JSON object containing the following fields: + +- `action` - The action to take on the delayed event. Must be one of: + - `send` - Send the delayed event immediately. + - `cancel` - Cancel the delayed event so that it is never sent. + - `restart` - Restart the timeout of the delayed event. + +For example, the following would send the delayed event with delay ID `1234567890` immediately: + +```http +POST /_matrix/client/v1/delayed_events/1234567890 +Content-Type: application/json + +{ + "action": "send" +} +``` + ### Getting delayed events A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of all the delayed events that @@ -190,29 +214,6 @@ This can be used by clients to display events that have been scheduled to be sen For use cases where the existence of a delayed event is also of interest for other room members, (e.g. self-destructing messages) it is recommended to include this information in the original/affected event itself. -### Managing delayed events - -A new authenticated client-server API endpoint at `POST /_matrix/client/v1/delayed_events/{delay_id}` allows scheduled events -to be managed. - -The body of the request is a JSON object containing the following fields: - -- `action` - The action to take on the delayed event. Must be one of: - - `send` - Send the delayed event immediately. - - `cancel` - Cancel the delayed event so that it is never sent. - - `restart` - Restart the timeout of the delayed event. - -For example, the following would send the delayed event with delay ID `1234567890` immediately: - -```http -POST /_matrix/client/v1/delayed_events/1234567890 -Content-Type: application/json - -{ - "action": "send" -} -``` - ### Homeserver implementation details #### Power levels are evaluated at the point of sending From de77a90d2d965b1b5a4ae0c710e6eaf8570279e2 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 25 Jul 2024 08:44:19 +0200 Subject: [PATCH 48/85] Obey lint rules and more renames --- proposals/4140-delayed-events-futures.md | 80 +++++++++++++++--------- 1 file changed, 50 insertions(+), 30 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 2e6242c6384..6dbcbf18dec 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -51,29 +51,31 @@ This proposal originates from the needs of VoIP signalling in Matrix: The Client-Server API currently has a [Voice over IP module](https://spec.matrix.org/v1.11/client-server-api/#voice-over-ip) that uses room messages to communicate the call state. However, it only allows for calls with two participants. -[MSC3401: Native Group VoIP Signalling](https://github.com/matrix-org/matrix-spec-proposals/pull/3401) proposes a scheme that -allows for more than two-participants by using room state events. +[MSC3401: Native Group VoIP Signalling](https://github.com/matrix-org/matrix-spec-proposals/pull/3401) proposes a scheme +that allows for more than two-participants by using room state events. In this arrangement each device signals its participant in a call by sending a state event that represents the device's -"membership" of a call. Once the device is no longer in the call, it sends a new state event to update the call state and say -that it is no longer a member. +"membership" of a call. Once the device is no longer in the call, it sends a new state event to update the call state and +say that it is no longer a member. -This works well when the client is running and can send the state events as needed. However, if the client is not running and -able to communicate with the homeserver (e.g. the user closes the app or loses connection) the call state is not updated to -say that the participant has left. +This works well when the client is running and can send the state events as needed. However, if the client is not running +and able to communicate with the homeserver (e.g. the user closes the app or loses connection) the call state is not +updated to say that the participant has left. The motivation for this MSC is to allow updating call member state events after the user disconnected by allowing to schedule/delay/timeout/expire events in a generic way. The ["reliability requirements for the room state"](https://github.com/matrix-org/matrix-spec-proposals/blob/toger5/matrixRTC/proposals/4143-matrix-rtc.md#reliability-requirements-for-the-room-state) -section of [MSC4143: MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) has more details on the use case. +section of [MSC4143: MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) has more details on the +use case. There are numerous possible solution to solve the call member event expiration. They are covered in detail in the [Use case specific considerations/MatrixRTC](#use-case-specific-considerations) section, because they are not part of this proposal. -The proposal here allows a Matrix client to schedule a "hangup" state event to be sent after a specified time period. The client -can then periodically reset/restart the timer whilst it is running. If the client is no longer running or able to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. +The proposal here allows a Matrix client to schedule a "hangup" state event to be sent after a specified time period. +The client can then periodically reset/restart the timer whilst it is running. If the client is no longer running or able +to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. ## Proposal @@ -85,7 +87,8 @@ The following operations are added to the client-server API: - Send the delayed event immediately - Cancel a delayed event so that it is never sent -At the point of an event being scheduled the homeserver is [unable to allocate the event ID](#allocating-event-id-at-the-point-of-scheduling-the-send). Instead, the homeserver allocates a _delay ID_ to the scheduled event which is used during the above API operations. +At the point of an event being scheduled the homeserver is [unable to allocate the event ID](#allocating-the-event-id-at-the-point-of-scheduling-the-send). +Instead, the homeserver allocates a _delay ID_ to the scheduled event which is used during the above API operations. ### Scheduling a delayed event @@ -97,11 +100,13 @@ endpoints. The new query parameter is used to configure the event scheduling: -- `delay` - Optional number of milliseconds the homeserver should wait before sending the event. If no `delay` is provided, the event is sent immediately as normal. +- `delay` - Optional number of milliseconds the homeserver should wait before sending the event. If no `delay` is provided, +the event is sent immediately as normal. The body of the request is the same as currently. -If a `delay` is provided, the homeserver schedules the event to be sent with the specified delay and returns the _delay ID_ in the `delay_id` field (omitting the `event_id` as it is not available): +If a `delay` is provided, the homeserver schedules the event to be sent with the specified delay and returns the _delay ID_ +in the `delay_id` field (omitting the `event_id` as it is not available): ```http 200 OK @@ -113,7 +118,8 @@ Content-Type: application/json ``` The homeserver can optionally enforce a maximum delay duration. If the requested delay exceeds the maximum the homeserver -can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code `M_MAX_DELAY_EXCEEDED` and the maximum allowed delay (`max_delay` in milliseconds). +can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code +`M_MAX_DELAY_EXCEEDED` and the maximum allowed delay (`max_delay` in milliseconds). For example the following specifies a maximum delay of 24 hours: @@ -153,11 +159,13 @@ Content-Type: application/json ### Getting delayed events -A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of all the delayed events that +A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of +all the delayed events that have been scheduled to send. -The endpoint accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as per the -[pagination convention](https://spec.matrix.org/v1.11/appendices/#pagination). The homeserver can choose a suitable page size. +The endpoint accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as +per the [pagination convention](https://spec.matrix.org/v1.11/appendices/#pagination). The homeserver can choose a suitable +page size. The response is a JSON object containing the following fields: @@ -167,7 +175,8 @@ The response is a JSON object containing the following fields: - `type` - Required. The event type of the delayed event. - `state_key` - Optional. The state key of the delayed event if it is a state event. - `delay` - Required. The delay in milliseconds before the event is sent. - - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or last restarted. + - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or + last restarted. - `content` - Required. The content of the delayed event. - `next_batch` - Optional. A token that can be used to paginate the list of delayed events. @@ -226,11 +235,14 @@ if the power level situation has changed at the time the delay passes. #### Delayed events are cancelled by a more recent state event -If a new state event is sent to the same room with the same (event type, state key) pair as a delayed event, the delayed event is cancelled. +If a new state event is sent to the same room with the same (event type, state key) pair as a delayed event, the delayed +event is cancelled. -There is no race condition here since a possible race between timeout and the _new state event_ will always converge to the _new state event_: +There is no race condition here since a possible race between timeout and the _new state event_ will always converge to +the _new state event_: -- timeout for _delayed event_ followed by _new state event_: the room state will be updated twice: once by the content of the delayed event but later with the content of _new state event_. +- timeout for _delayed event_ followed by _new state event_: the room state will be updated twice: once by the content of + the delayed event but later with the content of _new state event_. - _new state event_ followed by timeout for _delayed event_: the _new state event_ will cancel the outstanding _delayed event_. ## Use case specific considerations @@ -338,11 +350,11 @@ It is useful for external services to also interact with futures. If a client di be the best source to activate the Future/"last will". This is not covered in this MSC but could be realized with scoped access tokens. -A scoped token for only the `update_future` endpoint and a subset of `timeout_event_id`s would be used. +A scoped token for only the `delayed_events` endpoint and a subset of `delay_id`s would be used. An SFU for instance, that tracks the current client connection state, could be sent a request from the client that it needs to call every X hours while a user is connected and a request it has to call once the user disconnects -(using a `{"action": "refresh}` and a `{"action": "send"}` `future_update` request.). +(using a `{"action": "refresh}` and a `{"action": "send"}` `delayed_events` request.). This way the SFU can be used as the source of truth for the call member room state even if the client gets closed or looses connection and without knowing anything about the Matrix call. @@ -519,7 +531,8 @@ The Matrix spec says that the `event_id` must use the [reference hash](https://s which is [calculated from the fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) -Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call duration computation) we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. +Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call +duration computation) we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. ### MSC4018 (use client sync loop) @@ -543,15 +556,19 @@ If the widget dies, the call membership will disconnect. ### Federated delayed events -The delayed events could be sent over federation immediately and then have the receiving servers process them at the appropriate time. +The delayed events could be sent over federation immediately and then have the receiving servers process them at the +appropriate time. ### MQTT style Last Will [MQTT](https://mqtt.org/) has the concept of a Will Message that is published by the server when a client disconnects. -The client can set a Will Message when it connects to the server. If the client disconnects unexpectedly the server will publish the Will Message if the client is not back online within a specified time. +The client can set a Will Message when it connects to the server. If the client disconnects unexpectedly the server will +publish the Will Message if the client is not back online within a specified time. -A similar concept could be applied to Matrix by having the client specify a set of "Last Will" event(s) and have the homeserver trigger them if the client (possibly identified by device ID) does not send an API request within a specified time. +A similar concept could be applied to Matrix by having the client specify a set of "Last Will" event(s) and have the +homeserver trigger them if the client (possibly identified by device ID) does not send an API request within a specified +time. ### `M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED` @@ -575,8 +592,10 @@ Servers SHOULD impose a maximum timeout value for future timeouts of not more th Whilst the MSC is in the proposal stage, the following should be used: - `org.matrix.msc4140.delay` should be used instead of the `delay` query parameter. -- `POST /_matrix/client/unstable/org.matrix.msc4140/delayed_events/{delay_id}` should be used instead of the `POST /_matrix/client/v1/delayed_events/{delay_id}` endpoint. -- `GET /_matrix/client/unstable/org.matrix.msc4140/delayed_events` should be used instead of the `GET /_matrix/client/v1/delayed_events` endpoint. +- `POST /_matrix/client/unstable/org.matrix.msc4140/delayed_events/{delay_id}` should be used instead of + the `POST /_matrix/client/v1/delayed_events/{delay_id}` endpoint. +- `GET /_matrix/client/unstable/org.matrix.msc4140/delayed_events` should be used instead of + the `GET /_matrix/client/v1/delayed_events` endpoint. - The `M_UNKNOWN` `errcode` should be used instead of `M_MAX_DELAY_EXCEEDED` as follows: ```json @@ -598,7 +617,8 @@ instead of: } ``` -Additionally, the feature is to be advertised as unstable feature in the `GET /_matrix/client/versions` response, with the key `org.matrix.msc4140` set to `true`. So, the response could look then as following: +Additionally, the feature is to be advertised as unstable feature in the `GET /_matrix/client/versions` response, with +the key `org.matrix.msc4140` set to `true`. So, the response could look then as following: ```json { From 677d6f3d332a132b175f83fa2ea4c5112fbfb484 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 09:58:41 +0100 Subject: [PATCH 49/85] TOC update --- proposals/4140-delayed-events-futures.md | 60 ++++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 6dbcbf18dec..26939f3fb3d 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -11,36 +11,36 @@ time and then distributing as normal via federation. - [MSC4140: Cancellable delayed events](#msc4140-cancellable-delayed-events) - - [Background and motivation](#background-and-motivation) - - [Proposal](#proposal) - - [Scheduling a delayed event](#scheduling-a-delayed-event) - - [Managing delayed events](#managing-delayed-events) - - [Getting delayed events](#getting-delayed-events) - - [Homeserver implementation details](#homeserver-implementation-details) - - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) - - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) - - [Use case specific considerations](#use-case-specific-considerations) - - [MatrixRTC](#matrixrtc) - - [Background](#background) - - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) - - [Self-destructing messages](#self-destructing-messages) - - [Potential issues](#potential-issues) - - [Alternatives](#alternatives) - - [Delegating futures](#delegating-futures) - - [Batch sending](#batch-sending) - - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) - - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) - - [Batch Response](#batch-response) - - [EventId template variable](#eventid-template-variable) - - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) - - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) - - [Federated delayed events](#federated-delayed-events) - - [MQTT style Last Will](#mqtt-style-last-will) - - [M_INVALID_PARAM instead of M_MAX_DELAY_EXCEEDED](#m_invalid_param-instead-of-m_max_delay_exceeded) - - [Naming](#naming) - - [Security considerations](#security-considerations) - - [Unstable prefix](#unstable-prefix) - - [Dependencies](#dependencies) + - [Background and motivation](#background-and-motivation) + - [Proposal](#proposal) + - [Scheduling a delayed event](#scheduling-a-delayed-event) + - [Managing delayed events](#managing-delayed-events) + - [Getting delayed events](#getting-delayed-events) + - [Homeserver implementation details](#homeserver-implementation-details) + - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) + - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) + - [Use case specific considerations](#use-case-specific-considerations) + - [MatrixRTC](#matrixrtc) + - [Background](#background) + - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) + - [Self-destructing messages](#self-destructing-messages) + - [Potential issues](#potential-issues) + - [Alternatives](#alternatives) + - [Delegating futures](#delegating-futures) + - [Batch sending](#batch-sending) + - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) + - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) + - [Batch Response](#batch-response) + - [EventId template variable](#eventid-template-variable) + - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) + - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) + - [Federated delayed events](#federated-delayed-events) + - [MQTT style Last Will](#mqtt-style-last-will) + - [M_INVALID_PARAM instead of M_MAX_DELAY_EXCEEDED](#m_invalid_param-instead-of-m_max_delay_exceeded) + - [Naming](#naming) + - [Security considerations](#security-considerations) + - [Unstable prefix](#unstable-prefix) + - [Dependencies](#dependencies) From 4caecd1928f68e9df515c4dff54ede08325a7e30 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 10:13:26 +0100 Subject: [PATCH 50/85] Additional notes on security considerations --- proposals/4140-delayed-events-futures.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 26939f3fb3d..baabec9f757 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -585,8 +585,13 @@ The following alternative names for this concept are considered ## Security considerations +All new endpoints are authenticated. + Servers SHOULD impose a maximum timeout value for future timeouts of not more than a month. +As described [above](#power-levels-are-evaluated-at-the-point-of-sending), the homeserver MUST evaluate and enforce the +power levels at the time of the delayed event being sent (i.e. added to the DAG). + ## Unstable prefix Whilst the MSC is in the proposal stage, the following should be used: From c482d581ab16e7e2e5110059febc21c9258fb05a Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 10:59:01 +0100 Subject: [PATCH 51/85] Add rate limiting requirements --- proposals/4140-delayed-events-futures.md | 57 ++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index baabec9f757..a8a4c20aaf1 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -19,6 +19,7 @@ time and then distributing as normal via federation. - [Homeserver implementation details](#homeserver-implementation-details) - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) + - [Rate-limiting at the point of sending](#rate-limiting-at-the-point-of-sending) - [Use case specific considerations](#use-case-specific-considerations) - [MatrixRTC](#matrixrtc) - [Background](#background) @@ -134,6 +135,22 @@ Content-Type: application/json } ``` +The homeserver SHOULD apply rate limiting to the scheduling of delayed events to provide mitigation against the +[High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat. + +The homeserver MAY apply a limit on the maximum number of outstanding delayed events in which case the Matrix error code +`M_MAX_DELAYED_EVENTS_EXCEEDED` can be returned: + +```http +400 Bad Request +Content-Type: application/json + +{ + "errcode": "M_MAX_DELAYED_EVENTS_EXCEEDED", + "error": "The maximum number of delayed events has been reached.", +} +``` + ### Managing delayed events A new authenticated client-server API endpoint at `POST /_matrix/client/v1/delayed_events/{delay_id}` allows scheduled events @@ -157,6 +174,9 @@ Content-Type: application/json } ``` +Where the `action` is `send`, the homeserver SHOULD apply rate limiting to provide mitigation against the +[High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat. + ### Getting delayed events A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of @@ -245,6 +265,19 @@ the _new state event_: the delayed event but later with the content of _new state event_. - _new state event_ followed by timeout for _delayed event_: the _new state event_ will cancel the outstanding _delayed event_. +#### Rate-limiting at the point of sending + +Further to the rate limiting of the API endpoints, the homeserver SHOULD apply rate limiting to the sending of delayed messages +at the point that they are entered into the DAG. + +This is to provide mitigation against the +[High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat where a malicious +actor could schedule a large volume of events ahead of time without exceeding a rate limit on the initial `PUT` request, +but has specified a `delay` that corresponds to a common point of time in the future. + +A limit on the maximum number of delayed events that can be outstanding at one time could also provide some mitigation against +this attack. + ## Use case specific considerations ### MatrixRTC @@ -592,6 +625,11 @@ Servers SHOULD impose a maximum timeout value for future timeouts of not more th As described [above](#power-levels-are-evaluated-at-the-point-of-sending), the homeserver MUST evaluate and enforce the power levels at the time of the delayed event being sent (i.e. added to the DAG). +The is a risk that this feature could be used by a malicious actor to circumvent existing rate limiting measures which +corresponds to the [High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) +threat. The homeserver SHOULD apply rate-limiting to both the scheduling of delayed events and the later sending to +mitigate this risk. + ## Unstable prefix Whilst the MSC is in the proposal stage, the following should be used: @@ -622,6 +660,25 @@ instead of: } ``` +- The `M_UNKNOWN` `errcode` should be used instead of `M_MAX_DELAYED_EVENTS_EXCEEDED` as follows: + +```json +{ + "errcode": "M_UNKNOWN", + "error": "The maximum number of delayed events has been reached.", + "org.matrix.msc4140.errcode": "M_MAX_DELAYED_EVENTS_EXCEEDED" +} +``` + +instead of: + +```json +{ + "errcode": "M_MAX_DELAYED_EVENTS_EXCEEDED", + "error": "The maximum number of delayed events has been reached." +} +``` + Additionally, the feature is to be advertised as unstable feature in the `GET /_matrix/client/versions` response, with the key `org.matrix.msc4140` set to `true`. So, the response could look then as following: From 9098fea5e5bc31d900c5ad4db92c604f6293df86 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 12:36:06 +0100 Subject: [PATCH 52/85] Describe MatrixRTC use case in terms of heartbeats Remove SFU delegation reference. --- proposals/4140-delayed-events-futures.md | 61 +++++++++++++++++++----- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index a8a4c20aaf1..e3a7c4ab5b2 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -203,7 +203,7 @@ The response is a JSON object containing the following fields: For example: ```http -HTTP 200 OK +200 OK Content-Type: application/json { @@ -334,17 +334,54 @@ This is currently not possible over federation since `unsigned.age` is not avail #### How this MSC would be used for MatrixRTC -With this proposal we can provide an elegant solution using actions and timeouts -to only send one event for joining and one for leaving (reliably) - -- 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 loses 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 `/update_future` endpoint since the SFU only needs to ping - once per session (per user) and every 2-5hours (instead of every `X` seconds.) +With this proposal the client can use delayed events to implement a "heartbeat" mechanism. + +On joining the call the client sends a "join" state event as normal to indicate that it is participating: + +e.g. + +```http +PUT /_matrix/client/v1/rooms/!wherever:example.com/state/m.call.member/@someone:example.com +Content-Type: application/json + +{ + "memberships": [ + { + ...membership data here... + } + ] +} +``` + +It then also schedules a delayed "leave" state event with `delay` of around 5-20 seconds that marks the end of its participation: + +```http +PUT /_matrix/client/v1/rooms/!wherever:example.com/state/m.call.member/@someone:example.com?delay=10000 +Content-Type: application/json + +{ + "memberships": [] +} +``` + +Let's say the homeserver returns a `delay_id` of `1234567890`. + +The client then periodically sends a "heartbeat" in the form of a "restart" of the delayed "leave" state event to keep +the call membership "alive". + +For example it could make the request every 5 seconds (or some other period less than the `delay`): + +```http +POST /_matrix/client/v1/delayed_events/1234567890 +Content-Type: application/json + +{ + "action": "restart" +} +``` + +This would have the effect that if the homeserver does not receive a "heartbeat" from the client for 10 seconds then +it will automatically send the "leave" state event for the client. ### Self-destructing messages From 7e06e856396ecb33ee3bfd6ffce21d2f050a3727 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 12:37:28 +0100 Subject: [PATCH 53/85] "leave" => "hangup" for consistency --- proposals/4140-delayed-events-futures.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index e3a7c4ab5b2..049bfd3f1bd 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -353,7 +353,7 @@ Content-Type: application/json } ``` -It then also schedules a delayed "leave" state event with `delay` of around 5-20 seconds that marks the end of its participation: +It then also schedules a delayed "hangup" state event with `delay` of around 5-20 seconds that marks the end of its participation: ```http PUT /_matrix/client/v1/rooms/!wherever:example.com/state/m.call.member/@someone:example.com?delay=10000 @@ -366,7 +366,7 @@ Content-Type: application/json Let's say the homeserver returns a `delay_id` of `1234567890`. -The client then periodically sends a "heartbeat" in the form of a "restart" of the delayed "leave" state event to keep +The client then periodically sends a "heartbeat" in the form of a "restart" of the delayed "hangup" state event to keep the call membership "alive". For example it could make the request every 5 seconds (or some other period less than the `delay`): @@ -381,7 +381,7 @@ Content-Type: application/json ``` This would have the effect that if the homeserver does not receive a "heartbeat" from the client for 10 seconds then -it will automatically send the "leave" state event for the client. +it will automatically send the "hangup" state event for the client. ### Self-destructing messages From a1b81213e2781758ca8097eb294ab663f6a2ea39 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 12:46:53 +0100 Subject: [PATCH 54/85] Wording clarification --- proposals/4140-delayed-events-futures.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 049bfd3f1bd..5a135c26086 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -59,9 +59,9 @@ In this arrangement each device signals its participant in a call by sending a s "membership" of a call. Once the device is no longer in the call, it sends a new state event to update the call state and say that it is no longer a member. -This works well when the client is running and can send the state events as needed. However, if the client is not running -and able to communicate with the homeserver (e.g. the user closes the app or loses connection) the call state is not -updated to say that the participant has left. +This works well when the client is running and can send the state events as needed. However, if the client is not able to +communicate with the homeserver (e.g. the user closes the app or loses connection) the call state is not updated to say +that the participant has left. The motivation for this MSC is to allow updating call member state events after the user disconnected by allowing to schedule/delay/timeout/expire events in a generic way. From 3a3a5b5fea265269068ccc423230fe817aea32ee Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 12:50:44 +0100 Subject: [PATCH 55/85] Additional reference to heartbeat --- proposals/4140-delayed-events-futures.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 5a135c26086..6c1a48d65c6 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -78,6 +78,10 @@ The proposal here allows a Matrix client to schedule a "hangup" state event to b The client can then periodically reset/restart the timer whilst it is running. If the client is no longer running or able to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. +Such an arrangement can also be described as a "heartbeat" mechanism. The client sends a "heartbeat" to the homeserver +in the form of a "restart" of the delayed event to keep the call "alive". If the homeserver does not receive a "heartbeat" +then it will automatically send the "hangup" event for the client. + ## Proposal The following operations are added to the client-server API: From 97d41412f3789af1c15e77b23a67e30f7a33152e Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 25 Jul 2024 15:06:14 +0200 Subject: [PATCH 56/85] TOC update (use monospace and dont list msc title --- proposals/4140-delayed-events-futures.md | 65 +++++++++++------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 6c1a48d65c6..275472526b7 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -9,40 +9,37 @@ Once the event has been scheduled, the user's homeserver is responsible for actu time and then distributing as normal via federation. - -- [MSC4140: Cancellable delayed events](#msc4140-cancellable-delayed-events) - - [Background and motivation](#background-and-motivation) - - [Proposal](#proposal) - - [Scheduling a delayed event](#scheduling-a-delayed-event) - - [Managing delayed events](#managing-delayed-events) - - [Getting delayed events](#getting-delayed-events) - - [Homeserver implementation details](#homeserver-implementation-details) - - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) - - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) - - [Rate-limiting at the point of sending](#rate-limiting-at-the-point-of-sending) - - [Use case specific considerations](#use-case-specific-considerations) - - [MatrixRTC](#matrixrtc) - - [Background](#background) - - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) - - [Self-destructing messages](#self-destructing-messages) - - [Potential issues](#potential-issues) - - [Alternatives](#alternatives) - - [Delegating futures](#delegating-futures) - - [Batch sending](#batch-sending) - - [Not reusing the send/state endpoint](#not-reusing-the-sendstate-endpoint) - - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) - - [Batch Response](#batch-response) - - [EventId template variable](#eventid-template-variable) - - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) - - [MSC4018 use client sync loop](#msc4018-use-client-sync-loop) - - [Federated delayed events](#federated-delayed-events) - - [MQTT style Last Will](#mqtt-style-last-will) - - [M_INVALID_PARAM instead of M_MAX_DELAY_EXCEEDED](#m_invalid_param-instead-of-m_max_delay_exceeded) - - [Naming](#naming) - - [Security considerations](#security-considerations) - - [Unstable prefix](#unstable-prefix) - - [Dependencies](#dependencies) - +- [Background and motivation](#background-and-motivation) +- [Proposal](#proposal) + - [Scheduling a delayed event](#scheduling-a-delayed-event) + - [Managing delayed events](#managing-delayed-events) + - [Getting delayed events](#getting-delayed-events) + - [Homeserver implementation details](#homeserver-implementation-details) + - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) + - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) + - [Rate-limiting at the point of sending](#rate-limiting-at-the-point-of-sending) +- [Use case specific considerations](#use-case-specific-considerations) + - [MatrixRTC](#matrixrtc) + - [Background](#background) + - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) + - [Self-destructing messages](#self-destructing-messages) +- [Potential issues](#potential-issues) +- [Alternatives](#alternatives) + - [Delegating futures](#delegating-futures) + - [Batch sending](#batch-sending) + - [Not reusing the `send`/`state` endpoint](#not-reusing-the-sendstate-endpoint) + - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) + - [Batch Response](#batch-response) + - [EventId template variable](#eventid-template-variable) + - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) + - [MSC4018 (use client sync loop)](#msc4018-use-client-sync-loop) + - [Federated delayed events](#federated-delayed-events) + - [MQTT style Last Will](#mqtt-style-last-will) + - [`M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED`](#m_invalid_param-instead-of-m_max_delay_exceeded) + - [Naming](#naming) +- [Security considerations](#security-considerations) +- [Unstable prefix](#unstable-prefix) +- [Dependencies](#dependencies) ## Background and motivation From 99c946764529605c726dc4390deb568e73f4fc24 Mon Sep 17 00:00:00 2001 From: Timo Date: Thu, 25 Jul 2024 15:06:31 +0200 Subject: [PATCH 57/85] be consistent with "restart" wording (we used reset, refresh and restart before) --- proposals/4140-delayed-events-futures.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 275472526b7..dfaaf2fafdb 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -72,7 +72,7 @@ in the [Use case specific considerations/MatrixRTC](#use-case-specific-considera of this proposal. The proposal here allows a Matrix client to schedule a "hangup" state event to be sent after a specified time period. -The client can then periodically reset/restart the timer whilst it is running. If the client is no longer running or able +The client can then periodically restarts the timer whilst it is running. If the client is no longer running or able to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. Such an arrangement can also be described as a "heartbeat" mechanism. The client sends a "heartbeat" to the homeserver @@ -236,7 +236,7 @@ Content-Type: application/json } ``` -`running_since` is the timestamp (as unix time in milliseconds) when the delayed event was scheduled or last refreshed. +`running_since` is the timestamp (as unix time in milliseconds) when the delayed event was scheduled or last restarted. So, unless the delayed event is updated beforehand, the event will be sent after `running_since` + `delay`. This can be used by clients to display events that have been scheduled to be sent in the future. @@ -425,7 +425,7 @@ A scoped token for only the `delayed_events` endpoint and a subset of `delay_id` An SFU for instance, that tracks the current client connection state, could be sent a request from the client that it needs to call every X hours while a user is connected and a request it has to call once the user disconnects -(using a `{"action": "refresh}` and a `{"action": "send"}` `delayed_events` request.). +(using a `{"action": "restart"}` and a `{"action": "send"}` `delayed_events` request.). This way the SFU can be used as the source of truth for the call member room state even if the client gets closed or looses connection and without knowing anything about the Matrix call. @@ -443,7 +443,7 @@ future at the same time. 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 + timeout change it back 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 `{}`. @@ -548,7 +548,7 @@ Working with futures is the same with this alternative. This means, - `GET /_matrix/client/v1/futures` getting running futures -- `POST /_matrix/client/v1/update_future` to canceling, refreshing and sending futures +- `POST /_matrix/client/v1/update_future` to canceling, restarting and sending futures uses the exact same endpoints. Also the behaviour of the homeserver on when to invalidate the futures is identical except, that @@ -613,7 +613,8 @@ 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 advantage is, that this does not require us to introduce a new refresh token type. +The advantage is, that this does not require us to introduce a new ping system (as we do by using `delayed_events` +restart action). With cryptographic identities we however need the client to create the leave event. The timeout for syncs are much slower than what would be desirable (30s vs 5s). @@ -622,7 +623,7 @@ With a widget implementation for calls we cannot guarantee that the widget is ru So one either has to move the hangup logic to the hosting client or let the widget run all the time. With a dedicated ping (independent to the sync loop) it is more flexible and allows us to let the widget -execute the refresh. +execute the timer restart. If the widget dies, the call membership will disconnect. ### Federated delayed events From 6f2aa5efbcbf8dc80a71a5c683b56ec422e08672 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 16:14:01 +0100 Subject: [PATCH 58/85] Add compatibility with Cryptographic Identities to potenail issues --- proposals/4140-delayed-events-futures.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index dfaaf2fafdb..f09052492d0 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -24,6 +24,7 @@ time and then distributing as normal via federation. - [How this MSC would be used for MatrixRTC](#how-this-msc-would-be-used-for-matrixrtc) - [Self-destructing messages](#self-destructing-messages) - [Potential issues](#potential-issues) + - [Compatibility with Cryptographic Identities](#compatibility-with-cryptographic-identities) - [Alternatives](#alternatives) - [Delegating futures](#delegating-futures) - [Batch sending](#batch-sending) @@ -413,6 +414,23 @@ This would redact the message with content: `"m.text": "my msg"` after 10 minute ## Potential issues +### Compatibility with Cryptographic Identities + +We are mindful that this proposal should be compatible with other proposals such as +[MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) which introduce mechanisms +to allow the recipient of an event to determine whether it was sent by a client as opposed to spoofed/injected by a +malicious homeserver. + +In the context of this proposal, the delayed events should be signed with the same cryptographic identity as the client +that scheduled them. + +This means that the content of the original scheduled event must be sent "as is" without modification by the homeserver. +The implication is an implementation details that client developers must be aware of: if the content of the delayed +event contains a timestamp then it would be the timestamp of when the event was originally scheduled rather than +anything later. + +However, the `origin_server_ts` of the delayed event should be the time that the event is actually sent by the homeserver. + ## Alternatives ### Delegating futures From 5e43db089fff6d65e59be9cd2dcfd9d1cdbb09ce Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 16:15:38 +0100 Subject: [PATCH 59/85] Add placeholder for MSC3277 --- proposals/4140-delayed-events-futures.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index f09052492d0..789a29460f3 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -673,6 +673,10 @@ The following alternative names for this concept are considered - PostponedEvents - LastWill +### MSC3277: Scheduled messages + +TODO + ## Security considerations All new endpoints are authenticated. From 84b8dc05ca89f0fd52c58afade2071caca068c46 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 18:10:55 +0100 Subject: [PATCH 60/85] Add note about MSC2716 batch sending --- proposals/4140-delayed-events-futures.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 789a29460f3..23ced08ecbf 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -474,6 +474,10 @@ There is a [batch sending version](#batch-sending-futures-with-custom-endpoint) that proposes a future specific group sending endpoint in case this is required sooner then its realistic to implement [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). +[MSC2716: Incrementally importing history into existing rooms](https://github.com/matrix-org/matrix-spec-proposals/pull/2716) +already proposes a `batch_send` endpoint. However, it is limited to application services however and focuses on historic +data. Since we also need the additional capability to use a template event_id parameter, this probably is not a good fit. + ### Not reusing the `send`/`state` endpoint Alternatively new endpoints could be introduced to not overload the `send` and `state` endpoint. From e7d09863e92538b891f5152ab02cc955f8f419e8 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 18:22:26 +0100 Subject: [PATCH 61/85] Add alternative of not providing a send action --- proposals/4140-delayed-events-futures.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 23ced08ecbf..9ddc0f7ebf1 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -681,6 +681,13 @@ The following alternative names for this concept are considered TODO +### Don't provide a `send` action + +Instead of providing a `send` action for delayed events, the client could cancel the outstanding delayed event and send +a new non-delayed event instead. + +This would simplify the API, but it's less efficient since the client would have to send two requests instead of one. + ## Security considerations All new endpoints are authenticated. From 850bf9e30f661aa6c1bc3923d0034d510bb4f667 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 18:22:39 +0100 Subject: [PATCH 62/85] Add alternative of using DELETE HTTP method --- proposals/4140-delayed-events-futures.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 9ddc0f7ebf1..66e3c3f1a5b 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -688,6 +688,12 @@ a new non-delayed event instead. This would simplify the API, but it's less efficient since the client would have to send two requests instead of one. +### Use `DELETE` HTTP method for `cancel` action + +Instead of providing a `cancel` action for delayed events, the client could send a `DELETE` request to the same endpoint. + +This feels more elegant, but the doesn't feel like a good suggestion for how the other actions are mapped. + ## Security considerations All new endpoints are authenticated. From e1b460a2663ed14c8efbedca26b297c48cbf0030 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 18:27:44 +0100 Subject: [PATCH 63/85] Add clarification about regular/non-state events not getting cancelled --- proposals/4140-delayed-events-futures.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 66e3c3f1a5b..b8f58308639 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -16,7 +16,7 @@ time and then distributing as normal via federation. - [Getting delayed events](#getting-delayed-events) - [Homeserver implementation details](#homeserver-implementation-details) - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) - - [Delayed events are cancelled by a more recent state event](#delayed-events-are-cancelled-by-a-more-recent-state-event) + - [Delayed state events are cancelled by a more recent state event](#delayed-state-events-are-cancelled-by-a-more-recent-state-event) - [Rate-limiting at the point of sending](#rate-limiting-at-the-point-of-sending) - [Use case specific considerations](#use-case-specific-considerations) - [MatrixRTC](#matrixrtc) @@ -255,7 +255,7 @@ DAG. This implies a delayed event can fail if it violates power levels at the ti Conversely, it's also possible to successfully schedule an event that the user has no permission to at the time of sending if the power level situation has changed at the time the delay passes. -#### Delayed events are cancelled by a more recent state event +#### Delayed state events are cancelled by a more recent state event If a new state event is sent to the same room with the same (event type, state key) pair as a delayed event, the delayed event is cancelled. @@ -267,6 +267,9 @@ the _new state event_: the delayed event but later with the content of _new state event_. - _new state event_ followed by timeout for _delayed event_: the _new state event_ will cancel the outstanding _delayed event_. +Note that this behaviour does not apply to regular (non-state) events as there is no concept of a `(type, state_key)` +pair that could be overwritten. + #### Rate-limiting at the point of sending Further to the rate limiting of the API endpoints, the homeserver SHOULD apply rate limiting to the sending of delayed messages From 114da1e31e753722a7a0643b478b9f63dbdd5389 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 18:33:40 +0100 Subject: [PATCH 64/85] Clarify contents of content field on GET response --- proposals/4140-delayed-events-futures.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index b8f58308639..02a6a760c03 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -199,7 +199,8 @@ The response is a JSON object containing the following fields: - `delay` - Required. The delay in milliseconds before the event is sent. - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or last restarted. - - `content` - Required. The content of the delayed event. + - `content` - Required. The content of the delayed event. This is the body of the original `PUT` request not a preview + of the full event after sending. - `next_batch` - Optional. A token that can be used to paginate the list of delayed events. For example: From 772590f6bac61bd026e8a60dc055b76637cce8f7 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 18:37:33 +0100 Subject: [PATCH 65/85] Expose transaction ID in GET response --- proposals/4140-delayed-events-futures.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 02a6a760c03..4f0a6a8c109 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -196,6 +196,7 @@ The response is a JSON object containing the following fields: - `room_id` - Required. The room ID of the delayed event. - `type` - Required. The event type of the delayed event. - `state_key` - Optional. The state key of the delayed event if it is a state event. + - `transaction_id` - Optional. The transaction ID of the delayed event if it is a regular (non-state) event. - `delay` - Required. The delay in milliseconds before the event is sent. - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or last restarted. @@ -217,6 +218,7 @@ Content-Type: application/json "type": "m.room.message", "delay": 15000, "running_since": 1721732853284, + "transaction_id": "abcdefgh", "content":{ "msgtype": "m.text", "body": "I am now offline" From 4a2ca4800d6e1f0debad654a8812d2e7ffc1bbd8 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 18:53:07 +0100 Subject: [PATCH 66/85] Add context on MSC3277 alternative --- proposals/4140-delayed-events-futures.md | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 4f0a6a8c109..ddc0de5744d 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -659,6 +659,24 @@ If the widget dies, the call membership will disconnect. The delayed events could be sent over federation immediately and then have the receiving servers process them at the appropriate time. +Downsides of this approach that have been considered: + +- "heartbeats"/restarts would need to distributed via the federation meaning more traffic and processing to be done. +- if another homeservers missed the "heartbeat"/restart then it might decide that the event is visible in the DAG whereas +other homeservers might have received it and come to a different conclusion. If the event was later cancelled then +resolving the inconsistency feels more complex than if the event was never sent in the first place. + +[MSC3277: Scheduled messages](https://github.com/matrix-org/matrix-spec-proposals/pull/3277) proposes a similar feature +and there is an extensive analysis of the pros and cons of this MSC vs MSC3277 +[here](https://github.com/matrix-org/matrix-spec-proposals/pull/4140#discussion_r1653083566). + +If we don't need modification of a delayed event after it has been scheduled, there is a benefit in +federating the scheduled event (adding it to the DAG immediately). It increases resilience: the sender's homeserver can +disconnect and the delayed message still will enter non-soft-failed state (will be sent). + +However, for the MatrixRTC use case we do need to be able to modify the event after it has been scheduled. As such, we +have discounted this approach. + ### MQTT style Last Will [MQTT](https://mqtt.org/) has the concept of a Will Message that is published by the server when a client disconnects. @@ -683,10 +701,6 @@ The following alternative names for this concept are considered - PostponedEvents - LastWill -### MSC3277: Scheduled messages - -TODO - ### Don't provide a `send` action Instead of providing a `send` action for delayed events, the client could cancel the outstanding delayed event and send From 37979cd47c3a3e919c35cf4b22477478b9169290 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 19:05:07 +0100 Subject: [PATCH 67/85] Add more context to MQTT style alternative --- proposals/4140-delayed-events-futures.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index ddc0de5744d..a3540cbadb8 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -688,6 +688,21 @@ A similar concept could be applied to Matrix by having the client specify a set homeserver trigger them if the client (possibly identified by device ID) does not send an API request within a specified time. +The main differentiator is that this type of approach might use the sync loop as the "heartbeat" equivalent similar to +MSC4018(https://github.com/matrix-org/matrix-spec-proposals/pull/4018). + +A benefit compared to this proposal is that theoretically there would be no additional network traffic overhead. + +Some complications: + +- in order that the isn't the additional network traffic the homeserver would need to proactively realise that a connection +has dropped. Depending on the network/load balancer stack this might be problematic. +- as an alternative the client could reduce the long poll timeout (from a typical 30s down to, say, 5s) which would +result in a traffic increase. +- As syncing is a per client concept the MatrixRTC app has to either run in the same process as the client so that a +MatrixRTC app failure triggers the client Last Will or the client has to observe the MatrixRTC app and simulate the Last +Will if the MatrixRTC app fails. + ### `M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED` The existing `M_INVALID_PARAM` error code could be used instead of introducing a new error code `M_MAX_DELAY_EXCEEDED`. From 195ab6a955a11d9cd0d067ad08e949ade3a7709c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Thu, 25 Jul 2024 19:10:49 +0100 Subject: [PATCH 68/85] Add alternative of typing notifications --- proposals/4140-delayed-events-futures.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index a3540cbadb8..7de22adbb43 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -729,6 +729,19 @@ Instead of providing a `cancel` action for delayed events, the client could send This feels more elegant, but the doesn't feel like a good suggestion for how the other actions are mapped. +### [Ab]use typing notifications + +Some exploration of using typing notifications to indicate that a user is still connected to a call was done. + +The idea of extending [MSC3038: Typed typing notifications](https://github.com/matrix-org/matrix-spec-proposals/pull/3038) +to allow for additional meta data (like device ID and call ID) was considered. + +A perceived benefit was that if the delay events were federated then the typing notification EDUs might provide an +efficient transport. + +However, as the conclusion was to [not federate the delayed events](#federated-delayed-events) this approach was +discounted in favour of a dedicated endpoint. + ## Security considerations All new endpoints are authenticated. From 0f8a2d12cd9456f2509172df5fae116d102ecc53 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 26 Jul 2024 14:01:20 +0100 Subject: [PATCH 69/85] Update TOC --- proposals/4140-delayed-events-futures.md | 1 + 1 file changed, 1 insertion(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 7de22adbb43..7cb50642c22 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -38,6 +38,7 @@ time and then distributing as normal via federation. - [MQTT style Last Will](#mqtt-style-last-will) - [`M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED`](#m_invalid_param-instead-of-m_max_delay_exceeded) - [Naming](#naming) + - [`M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED`](#m_invalid_param-instead-of-m_max_delay_exceeded) - [Security considerations](#security-considerations) - [Unstable prefix](#unstable-prefix) - [Dependencies](#dependencies) From 886f37844490a58e2f65d8090f6110d933bdae9c Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 2 Aug 2024 14:45:15 +0100 Subject: [PATCH 70/85] Fix TOC --- proposals/4140-delayed-events-futures.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 7cb50642c22..e7445b0cde3 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -38,7 +38,9 @@ time and then distributing as normal via federation. - [MQTT style Last Will](#mqtt-style-last-will) - [`M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED`](#m_invalid_param-instead-of-m_max_delay_exceeded) - [Naming](#naming) - - [`M_INVALID_PARAM` instead of `M_MAX_DELAY_EXCEEDED`](#m_invalid_param-instead-of-m_max_delay_exceeded) + - [Don't provide a `send` action](#dont-provide-a-send-action) + - [Use `DELETE` HTTP method for `cancel` action](#use-delete-http-method-for-cancel-action) + - [[Ab]use typing notifications](#abuse-typing-notifications) - [Security considerations](#security-considerations) - [Unstable prefix](#unstable-prefix) - [Dependencies](#dependencies) @@ -717,14 +719,14 @@ The following alternative names for this concept are considered - PostponedEvents - LastWill -### Don't provide a `send` action +### Don't provide a `send` action Instead of providing a `send` action for delayed events, the client could cancel the outstanding delayed event and send a new non-delayed event instead. This would simplify the API, but it's less efficient since the client would have to send two requests instead of one. -### Use `DELETE` HTTP method for `cancel` action +### Use `DELETE` HTTP method for `cancel` action Instead of providing a `cancel` action for delayed events, the client could send a `DELETE` request to the same endpoint. From 8523ed416fbe3db09cf3031cacf4feb3f1be2ee1 Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Fri, 2 Aug 2024 14:52:51 +0100 Subject: [PATCH 71/85] Note on alternative names for `running_since` --- proposals/4140-delayed-events-futures.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index e7445b0cde3..2a1beac69ac 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -745,6 +745,15 @@ efficient transport. However, as the conclusion was to [not federate the delayed events](#federated-delayed-events) this approach was discounted in favour of a dedicated endpoint. +### Alternative to `running_since` field + +Some alternatives for the `running_since` field on the `GET` response: + +- `delaying_from` +- `delayed_since` +- `delaying_since` +- `last_restart` - but this feels less clear than `running_since` for a delayed event that hasn't been restarted + ## Security considerations All new endpoints are authenticated. From 883e6b5d55b3ea0ecfc8eb0f86cfa336de07ed83 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 11:13:34 -0400 Subject: [PATCH 72/85] Revert "Expose transaction ID in GET response" This reverts commit 772590f6bac61bd026e8a60dc055b76637cce8f7. Clients should keep track of transaction IDs themselves. --- proposals/4140-delayed-events-futures.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 2a1beac69ac..40ac7daab40 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -199,7 +199,6 @@ The response is a JSON object containing the following fields: - `room_id` - Required. The room ID of the delayed event. - `type` - Required. The event type of the delayed event. - `state_key` - Optional. The state key of the delayed event if it is a state event. - - `transaction_id` - Optional. The transaction ID of the delayed event if it is a regular (non-state) event. - `delay` - Required. The delay in milliseconds before the event is sent. - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or last restarted. @@ -221,7 +220,6 @@ Content-Type: application/json "type": "m.room.message", "delay": 15000, "running_since": 1721732853284, - "transaction_id": "abcdefgh", "content":{ "msgtype": "m.text", "body": "I am now offline" From caece4d6710c50ab35be552cd749f679b3af3a3e Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 11 Sep 2024 13:40:42 -0400 Subject: [PATCH 73/85] Scope GET to only the requesting user's events --- proposals/4140-delayed-events-futures.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 40ac7daab40..15888592c09 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -185,8 +185,7 @@ Where the `action` is `send`, the homeserver SHOULD apply rate limiting to provi ### Getting delayed events A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of -all the delayed events that -have been scheduled to send. +all the delayed events owned by the requesting user that have been scheduled to send. The endpoint accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as per the [pagination convention](https://spec.matrix.org/v1.11/appendices/#pagination). The homeserver can choose a suitable From 28970ec477f5d0ed051dbd9fa208a741207ae97e Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 1 Oct 2024 15:55:44 +0200 Subject: [PATCH 74/85] remove all references to future (except where we actually talk about the future and not future_events) --- proposals/4140-delayed-events-futures.md | 84 ++++++++++++------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 15888592c09..6ffdf9eef51 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -26,10 +26,10 @@ time and then distributing as normal via federation. - [Potential issues](#potential-issues) - [Compatibility with Cryptographic Identities](#compatibility-with-cryptographic-identities) - [Alternatives](#alternatives) - - [Delegating futures](#delegating-futures) + - [Delegating delayed events](#delegating-delayed-events) - [Batch sending](#batch-sending) - [Not reusing the `send`/`state` endpoint](#not-reusing-the-sendstate-endpoint) - - [Batch sending futures with custom endpoint](#batch-sending-futures-with-custom-endpoint) + - [Batch delayed events with custom endpoint](#batch-delayed-events-with-custom-endpoint) - [Batch Response](#batch-response) - [EventId template variable](#eventid-template-variable) - [Allocating the event ID at the point of scheduling the send](#allocating-the-event-id-at-the-point-of-scheduling-the-send) @@ -78,7 +78,7 @@ of this proposal. The proposal here allows a Matrix client to schedule a "hangup" state event to be sent after a specified time period. The client can then periodically restarts the timer whilst it is running. If the client is no longer running or able to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. - +The client can then periodically restart the timer whilst it is running. If the client is no longer running or able Such an arrangement can also be described as a "heartbeat" mechanism. The client sends a "heartbeat" to the homeserver in the form of a "restart" of the delayed event to keep the call "alive". If the homeserver does not receive a "heartbeat" then it will automatically send the "hangup" event for the client. @@ -193,7 +193,8 @@ page size. The response is a JSON object containing the following fields: -- `delayed_events` - Required. An array of delayed events that have been scheduled to send. +- `delayed_events` - Required. An array of delayed events, sorted by `running_since + delay` in increasing order, that +have been scheduled to send. - `delay_id` - Required. The ID of the delayed event. - `room_id` - Required. The room ID of the delayed event. - `type` - Required. The event type of the delayed event. @@ -313,7 +314,7 @@ There are numerous approaches to solve such a situation. They split into two cat - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if it's expired. - Use Future events with a 10s timeout to send the disconnected from call - in less then 10s after the user is not anymore pinging the `/update_future` endpoint. + in less then 10s after the user is not anymore pinging the `/delayed_events` endpoint. (or delegate the disconnect action to a service attached to the SFU) - Use the client sync loop as a special case timeout for call member events. (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) @@ -439,9 +440,9 @@ However, the `origin_server_ts` of the delayed event should be the time that the ## Alternatives -### Delegating futures +### Delegating delayed events -It is useful for external services to also interact with futures. If a client disconnects an external service can +It is useful for external services to also interact with delayed events. If a client disconnects an external service can be the best source to activate the Future/"last will". This is not covered in this MSC but could be realized with scoped access tokens. @@ -455,12 +456,8 @@ gets closed or looses connection and without knowing anything about the Matrix c ### Batch sending -Timed messages, tea timers, reminders or ephemeral events could be implemented -using this where clients send room events with -intentional mentions or a redaction as a future. - In some scenarios it is important to allow to send an event with an associated -future at the same time. +delay at the same time. - One example would be redacting an event. It only makes sense to redact the event if it exists. @@ -471,13 +468,13 @@ future at the same time. but the event with content `{}` could fail. The state would not automatically reset to `{}`. -For this use case batch sending of multiple futures would be desired. +For this use case batch sending of multiple delayed events would be desired. We do not include batch sending in the proposal of this MSC however since batch sending should become a generic Matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) -There is a [batch sending version](#batch-sending-futures-with-custom-endpoint) in the Alternatives section -that proposes a future specific group sending endpoint in case this is required sooner then its realistic to implement +There is a [batch sending version](#batch-delayed-events-with-custom-endpoint) in the Alternatives section +that proposes a delayed event specific group sending endpoint in case this is required sooner then its realistic to implement [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). [MSC2716: Incrementally importing history into existing rooms](https://github.com/matrix-org/matrix-spec-proposals/pull/2716) @@ -488,34 +485,35 @@ data. Since we also need the additional capability to use a template event_id pa Alternatively new endpoints could be introduced to not overload the `send` and `state` endpoint. Those endpoints could be called: -`PUT /_matrix/client/v1/rooms/{roomId}/send_future/{eventType}/{txnId}?delay={delay_ms}` +`PUT /_matrix/client/v1/rooms/{roomId}/send_delayed_event/{eventType}/{txnId}?delay={delay_ms}` -`PUT /_matrix/client/v1/rooms/{roomId}/state_future/{eventType}/{stateKey}?delay={delay_ms}` +`PUT /_matrix/client/v1/rooms/{roomId}/state_delayed_event/{eventType}/{stateKey}?delay={delay_ms}` This would allow the response for the `send` and `state` endpoints intact and we get a different return type -for the new `send_future` and `state_future` endpoints. +for the new `send_delayed_event` and `state_delayed_event` endpoints. -### Batch sending futures with custom endpoint +### Batch sending delayed events with custom endpoint -The proposed solution does not allow to send events together with futures that reference them with one +The proposed solution does not allow to send events together with delayed events that reference them with one HTTPS request. This is desired for self-destructing events and for MatrixRTC room state events, where -we want the guarantee, that the event itself and the future removing the event both reach the homeserver +we want the guarantee, that the event itself and the delayed event removing the event both reach the homeserver with one request. Otherwise there is a risk for the client to lose connection or crash between sending the -event and the future which results in never expiring call membership or never destructing self-destructing messages. +event and the delayed event which results in never expiring call membership or never destructing self-destructing messages. This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` endpoint is implemented. (Then the `delay` could be added to the `PDUInfo` instead of the query parameters and everything could be send at once.) This would be the preferred solution since we currently don't have any other batch sending mechanism. -It would however require lots of changes since a new widget action for futures would be needed. +It would however require lots of changes since a new widget action for delayed events would be needed. With the current main proposal it is enough to add a `delay` to the send message widget action. -The widget driver would then take care of calling `send` or `send_future` based on the presence of those fields. +The widget driver would then take care of calling `send` or `send_delayed_event` based on the presence of those fields. An alternative to the proposed solution that allows this kind of batch sending would be to introduce this endpoint: -`PUT /_matrix/client/v1/rooms/{roomId}/send/future/{txnId}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/delayed_event/{txnId}` + It allows to send a list of event contents. The body looks as following: ```jsonc @@ -524,18 +522,18 @@ It allows to send a list of event contents. The body looks as following: "send_on_timeout": { "type":type, - "content":timeout_future_content + "content":timeout_delay_event_content }, // optional "send_on_action":{ "${action1}": { "type":type, - "content":action_future_content + "content":action_delay_event_content }, "${action2}": { "type":type, - "content":action_future_content + "content":action_delay_event_content }, ... }, @@ -545,16 +543,16 @@ It allows to send a list of event contents. The body looks as following: } ``` -We are sending the timeout and the group id inside the body and combine the timeout future -and the action future into one event. +We are sending the timeout and the group id inside the body and combine the timeout delayed event +and the action delayed event into one event. Each of the `sendEventBody` objects are exactly the same as sending a normal event. -This is a batch endpoint that sends timeout and action futures at the same time. +This is a batch endpoint that sends timeout and action delayed events at the same time. #### Batch Response -The response will be a collection of all the futures with the same fields as in the initial proposal: +The response will be a collection of all the delayed events with the same fields as in the initial proposal: ```jsonc { @@ -572,22 +570,22 @@ The response will be a collection of all the futures with the same fields as in } ``` -Working with futures is the same with this alternative. +Working with delayed events is the same with this alternative. This means, -- `GET /_matrix/client/v1/futures` getting running futures -- `POST /_matrix/client/v1/update_future` to canceling, restarting and sending futures +- `GET /_matrix/client/v1/delayed_events` getting running delayed events +- `POST /_matrix/client/v1/delayed_events` to canceling, restarting and sending delayed events uses the exact same endpoints. -Also the behaviour of the homeserver on when to invalidate the futures is identical except, that +Also the behaviour of the homeserver on when to invalidate the delayed events is identical except, that we don't need the error code `409` anymore since the events are sent as a batch and there cannot be -an action future without a timeout future. +an action delayed event without a timeout delayed event. #### EventId template variable It would be useful to be able to send redactions and edits as one HTTP request. -This would handle the cases where the futures need to reference the `send_now` event. -For instance, sending a self-destructing message where the redaction timeout future needs +This would handle the cases where the delayed events need to reference the `send_now` event. +For instance, sending a self-destructing message where the redaction timeout delayed events needs to reference the event to redact. For this reason, template variables are introduced that are only valid in `Future` events. @@ -596,7 +594,7 @@ For this reason, template variables are introduced that are only valid in `Futur The **Self-destructing messages** example be a single request: -`PUT /_matrix/client/v1/rooms/{roomId}/send/future/{txnId}` +`PUT /_matrix/client/v1/rooms/{roomId}/send/delayed_event/{txnId}` ```jsonc { @@ -624,14 +622,14 @@ and then send them via `/send_pdus` (see: [MSC4080: Cryptographic Identities](ht ### Allocating the event ID at the point of scheduling the send -This was considered, but when sending a future the `event_id` is not yet available: +This was considered, but when sending a delayed event the `event_id` is not yet available: The Matrix spec says that the `event_id` must use the [reference hash](https://spec.matrix.org/v1.10/rooms/v11/#event-ids) which is [calculated from the fields](https://spec.matrix.org/v1.10/server-server-api/#calculating-the-reference-hash-for-an-event) of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call -duration computation) we cannot compute the `event_id` when using the send endpoint when the future has not yet resolved. +duration computation) we cannot compute the `event_id` when using the send endpoint when the dealyed event has not yet resolved. ### MSC4018 (use client sync loop) @@ -755,7 +753,7 @@ Some alternatives for the `running_since` field on the `GET` response: All new endpoints are authenticated. -Servers SHOULD impose a maximum timeout value for future timeouts of not more than a month. +Servers SHOULD impose a maximum timeout value for delay timeouts of not more than a month. As described [above](#power-levels-are-evaluated-at-the-point-of-sending), the homeserver MUST evaluate and enforce the power levels at the time of the delayed event being sent (i.e. added to the DAG). From 2f57b0b0246d2fa44cd5f462463504b1db4b66d6 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 1 Oct 2024 15:56:05 +0200 Subject: [PATCH 75/85] add terminated events section to GET endpoint --- proposals/4140-delayed-events-futures.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 6ffdf9eef51..29a825745db 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -205,6 +205,25 @@ have been scheduled to send. - `content` - Required. The content of the delayed event. This is the body of the original `PUT` request not a preview of the full event after sending. - `next_batch` - Optional. A token that can be used to paginate the list of delayed events. +- `terminated_events` - Required. An array of finalized delayed events, that have either been sent or resulted in an error. + - `delayed_event` - Required. Describes the original delayed event in the same format as the `delayed_events` array. + - `termination_reason` - Required `"timeout"|"send"|"canceled"|"error"`. + - `"timeout"`: the event got sent because the delay timed out + - `"send"`: the event got sent because the send endpoint was called. + - `"canceled"`: the event got canceled using the `cancel` endpoint + - `"error"`: the delay timed out but the event sending failed. + - `error` - Optional Error. A matrix error (as defined by [Standard error response](https://spec.matrix.org/v1.11/client-server-api/#standard-error-response)) + to explain why this event failed to get sent. + - `event_id` - Optional EventId. The `event_id` this event got when it was sent. + - `origin_server_ts` - Optional Timestamp. The timestamp the event was sent. +- `next_terminated_batch` - Optional. A token that can be used to paginate the list of finalized events. + +The batch size and the amount of termiated events that stay on the homeserver can be chosen, by the homeserver. +The recommended values are: + +- `terminated_events` retention: 7days +- `terminated_events` batch size: 10 +- `terminated_events` max cached events: 1000 For example: From 9d5c93a0514cef23c59f5c36f0cceba1b0e75888 Mon Sep 17 00:00:00 2001 From: Timo Date: Tue, 1 Oct 2024 15:56:24 +0200 Subject: [PATCH 76/85] use case specific considerations details --- proposals/4140-delayed-events-futures.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 29a825745db..f7447d8f3a9 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -308,6 +308,11 @@ this attack. ## Use case specific considerations +Delayed events can be used for lots of different features, tea timers, reminders or ephemeral events could be implemented +using this where clients send room events with +intentional mentions or a redaction as a delayed event. +It can even be used to send temporal power levels/mutes or bans. + ### MatrixRTC In this section an overview is given how this MSC is used in [MSC4143: MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) From da3d75e5fe339b8d9cdc1a2b973aa713afffacf3 Mon Sep 17 00:00:00 2001 From: Timo Date: Wed, 2 Oct 2024 13:21:08 +0200 Subject: [PATCH 77/85] remove batch sending endpoint since that does not make sense in the short term and longer term send_pdus is the better solution. --- proposals/4140-delayed-events-futures.md | 128 ----------------------- 1 file changed, 128 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index f7447d8f3a9..58548511619 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -516,134 +516,6 @@ Those endpoints could be called: This would allow the response for the `send` and `state` endpoints intact and we get a different return type for the new `send_delayed_event` and `state_delayed_event` endpoints. -### Batch sending delayed events with custom endpoint - -The proposed solution does not allow to send events together with delayed events that reference them with one -HTTPS request. This is desired for self-destructing events and for MatrixRTC room state events, where -we want the guarantee, that the event itself and the delayed event removing the event both reach the homeserver -with one request. Otherwise there is a risk for the client to lose connection or crash between sending the -event and the delayed event which results in never expiring call membership or never destructing self-destructing messages. -This would be solved once [MSC4080](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) and the `/send_pdus` -endpoint is implemented. -(Then the `delay` could be added -to the `PDUInfo` instead of the query parameters and everything could be send at once.) - -This would be the preferred solution since we currently don't have any other batch sending mechanism. -It would however require lots of changes since a new widget action for delayed events would be needed. -With the current main proposal it is enough to add a `delay` to the send message -widget action. -The widget driver would then take care of calling `send` or `send_delayed_event` based on the presence of those fields. - -An alternative to the proposed solution that allows this kind of batch sending would be to -introduce this endpoint: -`PUT /_matrix/client/v1/rooms/{roomId}/send/delayed_event/{txnId}` - -It allows to send a list of event contents. The body looks as following: - -```jsonc -{ - "timeout": 10, - - "send_on_timeout": { - "type":type, - "content":timeout_delay_event_content - }, - - // optional - "send_on_action":{ - "${action1}": { - "type":type, - "content":action_delay_event_content - }, - "${action2}": { - "type":type, - "content":action_delay_event_content - }, - ... - }, - - // optional - "send_now": content, -} -``` - -We are sending the timeout and the group id inside the body and combine the timeout delayed event -and the action delayed event into one event. -Each of the `sendEventBody` objects are exactly the same as sending a normal -event. - -This is a batch endpoint that sends timeout and action delayed events at the same time. - -#### Batch Response - -The response will be a collection of all the delayed events with the same fields as in the initial proposal: - -```jsonc -{ - "send_on_timeout": { - "delay_id": "delay_id", - }, - // optional - "send_on_action": { - "${action1}": { "delay_id": "delay_id1" }, - "${action2}": { "delay_id": "delay_id2" } - }, - - // optional - "send_now": { "eventId": "id_hash" } -} -``` - -Working with delayed events is the same with this alternative. -This means, - -- `GET /_matrix/client/v1/delayed_events` getting running delayed events -- `POST /_matrix/client/v1/delayed_events` to canceling, restarting and sending delayed events - -uses the exact same endpoints. -Also the behaviour of the homeserver on when to invalidate the delayed events is identical except, that -we don't need the error code `409` anymore since the events are sent as a batch and there cannot be -an action delayed event without a timeout delayed event. - -#### EventId template variable - -It would be useful to be able to send redactions and edits as one HTTP request. -This would handle the cases where the delayed events need to reference the `send_now` event. -For instance, sending a self-destructing message where the redaction timeout delayed events needs -to reference the event to redact. - -For this reason, template variables are introduced that are only valid in `Future` events. -`$m.send_now.event_id` in the content of one of the `send_on_action` and -`send_on_timeout` this template variable can be used. - -The **Self-destructing messages** example be a single request: - -`PUT /_matrix/client/v1/rooms/{roomId}/send/delayed_event/{txnId}` - -```jsonc -{ - "m.send_now":{ - "type":"m.room.message", - "content":{ - "m.text": "my msg" - } - }, - "m.timeout": 10*60, - "m.send_on_timeout": { - "type":"m.room.redaction", - "content":{ - "redacts": "$m.send_now.event_id" - } - } -} -``` - -With cryptographic identities events would be presigned. -The server will first send the finalized event to the client. -At this point the client has the id but the event is not in the DAG. -So it would be trivial to sign both the event and the redaction/related event -and then send them via `/send_pdus` (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)). - ### Allocating the event ID at the point of scheduling the send This was considered, but when sending a delayed event the `event_id` is not yet available: From f7e4e9ba568f25aa6bec647eb0d68028b9e7d0da Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 4 Oct 2024 13:33:03 +0200 Subject: [PATCH 78/85] Clean up iteration with two significant changes: - we have two GET endpoints `/shedueled` `/terminated` now. - The rule for when a state delayed event is cancelled changed to include a sender user condition. --- proposals/4140-delayed-events-futures.md | 213 ++++++++++++++--------- 1 file changed, 131 insertions(+), 82 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 58548511619..cfbe00730bb 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -58,7 +58,7 @@ that allows for more than two-participants by using room state events. In this arrangement each device signals its participant in a call by sending a state event that represents the device's "membership" of a call. Once the device is no longer in the call, it sends a new state event to update the call state and -say that it is no longer a member. +communicate that the device is no longer a member. This works well when the client is running and can send the state events as needed. However, if the client is not able to communicate with the homeserver (e.g. the user closes the app or loses connection) the call state is not updated to say @@ -75,13 +75,13 @@ There are numerous possible solution to solve the call member event expiration. in the [Use case specific considerations/MatrixRTC](#use-case-specific-considerations) section, because they are not part of this proposal. -The proposal here allows a Matrix client to schedule a "hangup" state event to be sent after a specified time period. -The client can then periodically restarts the timer whilst it is running. If the client is no longer running or able -to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. -The client can then periodically restart the timer whilst it is running. If the client is no longer running or able +This proposal enables a Matrix client to schedule a "hangup" state event to be sent after a specified time period. +The client can then periodically restarts the timer whilst it the client is running. If the client is no longer running +or able to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. + Such an arrangement can also be described as a "heartbeat" mechanism. The client sends a "heartbeat" to the homeserver -in the form of a "restart" of the delayed event to keep the call "alive". If the homeserver does not receive a "heartbeat" -then it will automatically send the "hangup" event for the client. +in the form of a "restart" of the delayed event to keep the call "alive". +The homeserver will automatically send the "hangup" if it does not receive a "heartbeat". ## Proposal @@ -94,7 +94,7 @@ The following operations are added to the client-server API: - Cancel a delayed event so that it is never sent At the point of an event being scheduled the homeserver is [unable to allocate the event ID](#allocating-the-event-id-at-the-point-of-scheduling-the-send). -Instead, the homeserver allocates a _delay ID_ to the scheduled event which is used during the above API operations. +Instead, the homeserver allocates a `delay_id` to the scheduled event which is used during the above API operations. ### Scheduling a delayed event @@ -111,8 +111,8 @@ the event is sent immediately as normal. The body of the request is the same as currently. -If a `delay` is provided, the homeserver schedules the event to be sent with the specified delay and returns the _delay ID_ -in the `delay_id` field (omitting the `event_id` as it is not available): +If a `delay` is provided, the homeserver schedules the event to be sent with the specified delay and responds with a +`delay_id` field (omitting the `event_id` as it is not available): ```http 200 OK @@ -140,7 +140,7 @@ Content-Type: application/json } ``` -The homeserver SHOULD apply rate limiting to the scheduling of delayed events to provide mitigation against the +The homeserver **should** apply rate limiting to the scheduling of delayed events to provide mitigation against the [High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat. The homeserver MAY apply a limit on the maximum number of outstanding delayed events in which case the Matrix error code @@ -163,7 +163,8 @@ to be managed. The body of the request is a JSON object containing the following fields: -- `action` - The action to take on the delayed event. Must be one of: +- `action` - The action to take on the delayed event.\ +Must be one of: - `send` - Send the delayed event immediately. - `cancel` - Cancel the delayed event so that it is never sent. - `restart` - Restart the timeout of the delayed event. @@ -179,44 +180,49 @@ Content-Type: application/json } ``` -Where the `action` is `send`, the homeserver SHOULD apply rate limiting to provide mitigation against the +Where the `action` is `send`, the homeserver **should** apply rate limiting to provide mitigation against the [High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat. ### Getting delayed events -A new authenticated client-server API endpoint `GET /_matrix/client/v1/delayed_events` allows clients to get a list of -all the delayed events owned by the requesting user that have been scheduled to send. +New authenticated client-server API endpoints `GET /_matrix/client/v1/delayed_events/scheduled` and +`GET /_matrix/client/v1/delayed_events/terminated` allows clients to get a list of +all the delayed events owned by the requesting user that have been scheduled to send, have been sent or fail to send. -The endpoint accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as +The endpoints accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as per the [pagination convention](https://spec.matrix.org/v1.11/appendices/#pagination). The homeserver can choose a suitable page size. The response is a JSON object containing the following fields: -- `delayed_events` - Required. An array of delayed events, sorted by `running_since + delay` in increasing order, that -have been scheduled to send. - - `delay_id` - Required. The ID of the delayed event. - - `room_id` - Required. The room ID of the delayed event. - - `type` - Required. The event type of the delayed event. - - `state_key` - Optional. The state key of the delayed event if it is a state event. - - `delay` - Required. The delay in milliseconds before the event is sent. - - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or - last restarted. - - `content` - Required. The content of the delayed event. This is the body of the original `PUT` request not a preview - of the full event after sending. -- `next_batch` - Optional. A token that can be used to paginate the list of delayed events. -- `terminated_events` - Required. An array of finalized delayed events, that have either been sent or resulted in an error. - - `delayed_event` - Required. Describes the original delayed event in the same format as the `delayed_events` array. - - `termination_reason` - Required `"timeout"|"send"|"canceled"|"error"`. - - `"timeout"`: the event got sent because the delay timed out - - `"send"`: the event got sent because the send endpoint was called. - - `"canceled"`: the event got canceled using the `cancel` endpoint - - `"error"`: the delay timed out but the event sending failed. - - `error` - Optional Error. A matrix error (as defined by [Standard error response](https://spec.matrix.org/v1.11/client-server-api/#standard-error-response)) - to explain why this event failed to get sent. - - `event_id` - Optional EventId. The `event_id` this event got when it was sent. - - `origin_server_ts` - Optional Timestamp. The timestamp the event was sent. -- `next_terminated_batch` - Optional. A token that can be used to paginate the list of finalized events. +- For the `GET /_matrix/client/v1/delayed_events/scheduled` endpoint: + - `delayed_events` - Required. An array of delayed events that have been scheduled to send, + sorted by `running_since + delay` in increasing order (event that will timeout soonest first). + - `delay_id` - Required. The ID of the delayed event. + - `room_id` - Required. The room ID of the delayed event. + - `type` - Required. The event type of the delayed event. + - `state_key` - Optional. The state key of the delayed event if it is a state event. + - `delay` - Required. The delay in milliseconds before the event is sent. + - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or + last restarted. + - `content` - Required. The content of the delayed event. This is the body of the original `PUT` request not a preview + of the full event after sending. + - `next_batch` - Optional. A token that can be used to paginate the list of delayed events. + +- For the `GET /_matrix/client/v1/delayed_events/terminated` endpoint: + - `terminated_events` - Required. An array of finalized delayed events, that have either been sent or resulted in an error, + sorted by `origin_server_ts` in decreasing order (latest terminated event first). + - `delayed_event` - Required. Describes the original delayed event in the same format as the `delayed_events` array. + - `termination_reason` - Required `"timeout"|"send"|"canceled"|"error"`. + - `"timeout"`: the event got sent because the delay timed out + - `"send"`: the event got sent because the send endpoint was called. + - `"canceled"`: the event got canceled using the `cancel` endpoint + - `"error"`: the delay timed out but the event sending failed. + - `error` - Optional Error. A matrix error (as defined by [Standard error response](https://spec.matrix.org/v1.11/client-server-api/#standard-error-response)) + to explain why this event failed to get sent. + - `event_id` - Optional EventId. The `event_id` this event got when it was sent. + - `origin_server_ts` - Optional Timestamp. The timestamp the event was sent. + - `next_terminated_batch` - Optional. A token that can be used to paginate the list of finalized events. The batch size and the amount of termiated events that stay on the homeserver can be chosen, by the homeserver. The recommended values are: @@ -225,6 +231,9 @@ The recommended values are: - `terminated_events` batch size: 10 - `terminated_events` max cached events: 1000 +There is no guarantee for a client that all events will be available in the +`terminated_events` list if they exceed the limits of their homeserver. + For example: ```http @@ -275,15 +284,28 @@ For use cases where the existence of a delayed event is also of interest for oth Power levels are evaluated for each event only once the delay has occurred and it will be distributed/inserted into the DAG. This implies a delayed event can fail if it violates power levels at the time the delay passes. -Conversely, it's also possible to successfully schedule an event that the user has no permission to at the time of sending -if the power level situation has changed at the time the delay passes. +Conversely, it's also possible to successfully schedule an event that the user has no permission to at the time of sending. +If the power level situation has changed at the time the delay passes the event can even reach the DAG. #### Delayed state events are cancelled by a more recent state event -If a new state event is sent to the same room with the same (event type, state key) pair as a delayed event, the delayed -event is cancelled. +> [!NOTE] +> Special rule for delayed state events: +> A delayed event `D` gets cancelled if: +> +> - `D` is a state event with key `k` and type `t` from sender `s`. +> - A new state event `N` with type `t` and key `k` is sent into the room. +> - The sender of `D` is different to the sender `N`. + +If a new state event is sent to the same room at the same entry (event_type-state_key pair) as a delayed event by a +**different matrix user**, any delayed event for this entry (event_type-state_key pair) is cancelled. -There is no race condition here since a possible race between timeout and the _new state event_ will always converge to +This only happens if its a state update from a different user. If its the same user the delayed event will not get cancelled. +If the same user is updating the state which has associate delayed events this user is in control of the delayed events. +They can just cancel and check the events manually using the `/delayed_events` and the `/delayed_events/scheduled` endpoint. + +In the case where the delayed event gets cancelled due to a different user updateting the same state, there +is no race condition here since a possible race between timeout and the _new state event_ will always converge to the _new state event_: - timeout for _delayed event_ followed by _new state event_: the room state will be updated twice: once by the content of @@ -295,8 +317,8 @@ pair that could be overwritten. #### Rate-limiting at the point of sending -Further to the rate limiting of the API endpoints, the homeserver SHOULD apply rate limiting to the sending of delayed messages -at the point that they are entered into the DAG. +Further to the rate limiting of the API endpoints, the homeserver **should** apply rate limiting to the sending +of delayed messages at the point that they are inserted into the DAG. This is to provide mitigation against the [High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat where a malicious @@ -309,7 +331,7 @@ this attack. ## Use case specific considerations Delayed events can be used for lots of different features, tea timers, reminders or ephemeral events could be implemented -using this where clients send room events with +using delayed events, where clients send room events with intentional mentions or a redaction as a delayed event. It can even be used to send temporal power levels/mutes or bans. @@ -337,7 +359,7 @@ There are numerous approaches to solve such a situation. They split into two cat - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if it's expired. - - Use Future events with a 10s timeout to send the disconnected from call + - Use delayed events with a 10s timeout to send the disconnected from call in less then 10s after the user is not anymore pinging the `/delayed_events` endpoint. (or delegate the disconnect action to a service attached to the SFU) - Use the client sync loop as a special case timeout for call member events. @@ -356,8 +378,10 @@ metadata for a room to be synchronous. The current solution updates the room state every X minutes. This is not elegant since we basically resend room state with the same content. -In large calls this could result in huge traffic/large DAGs (100 call members -implies 100 state events every X minutes.) X cannot be a long duration because +In large calls this could result in lots of traffic and increase the size of the room DAG + +A call with 100 call members implies 100 state events every X minutes. X cannot be a +long duration because it is the duration after which we can consider the event as expired. Improper disconnects would result in the user being displayed as "still in the call" for X minutes (we want this to be as short as possible!) @@ -386,7 +410,8 @@ Content-Type: application/json } ``` -It then also schedules a delayed "hangup" state event with `delay` of around 5-20 seconds that marks the end of its participation: +Before sending the join event it also schedules a delayed "hangup" state event with `delay` of around 5-20 seconds that +marks the end of its participation: ```http PUT /_matrix/client/v1/rooms/!wherever:example.com/state/m.call.member/@someone:example.com?delay=10000 @@ -416,6 +441,9 @@ Content-Type: application/json This would have the effect that if the homeserver does not receive a "heartbeat" from the client for 10 seconds then it will automatically send the "hangup" state event for the client. +Since the delayed event is sent first. A client can guarantee (at the time they are sending +the join event) that it will eventually leave. + ### Self-destructing messages This MSC also allows an implementation of "self-destructing" messages using redaction: @@ -449,43 +477,59 @@ This would redact the message with content: `"m.text": "my msg"` after 10 minute We are mindful that this proposal should be compatible with other proposals such as [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) which introduce mechanisms -to allow the recipient of an event to determine whether it was sent by a client as opposed to spoofed/injected by a -malicious homeserver. +to allow the recipient of an event to determine whether it was sent by a client as opposed to have been spoofed/injected +by a malicious homeserver. In the context of this proposal, the delayed events should be signed with the same cryptographic identity as the client that scheduled them. This means that the content of the original scheduled event must be sent "as is" without modification by the homeserver. -The implication is an implementation details that client developers must be aware of: if the content of the delayed +The consequence is an implementation detail that client developers must be aware of: if the content of the delayed event contains a timestamp then it would be the timestamp of when the event was originally scheduled rather than anything later. However, the `origin_server_ts` of the delayed event should be the time that the event is actually sent by the homeserver. +This is a general probelen that ariases with the introduction +of [Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). +A user can intentionally, or caused by network conditions, delay the signing and sending of an event. +A possible solution would be the introduction of a `signing_ts` (in the signed section) and keep the `origin_server_ts` +in the unsigned section. +Both are reasonable data points that clients might want to use. +This would solve issues related to delayed events since +it would make it transparent to clients, when an event was scheduled and when it was distributed over federation. + ## Alternatives ### Delegating delayed events It is useful for external services to also interact with delayed events. If a client disconnects an external service can -be the best source to activate the Future/"last will". +be the best source to send the delayed event/"last will". This is not covered in this MSC but could be realized with scoped access tokens. -A scoped token for only the `delayed_events` endpoint and a subset of `delay_id`s would be used. - -An SFU for instance, that tracks the current client connection state, could be sent a request from the client that it -needs to call every X hours while a user is connected and a request it has to call once the user disconnects -(using a `{"action": "restart"}` and a `{"action": "send"}` `delayed_events` request.). -This way the SFU can be used as the source of truth for the call member room state even if the client -gets closed or looses connection and without knowing anything about the Matrix call. +A scoped token that only allows to interact with the `delayed_events` endpoint and only with a subset of `delay_id`s +would be used. + +With this, an SFU, that tracks the current client connection state, could be given the power to control the delayed event. +The client shares the scoped token and the required details, so that the SFU can call the +refresh endpoint while a user is connected +and can call the delayed event `send` request once the user disconnects +(using a `{"action": "restart"}` and a `{"action": "send"}` `/delayed_events` request.). +This way the SFU can be used as the source of truth for the call member room state event without knowing anything about +the Matrix call. + +Since the SFU has a much lower chance of running into a network issue, we can significantly reduce the use of the +`{"action": "restart"}` calls. Instead of calling the `/delayed_events` endpoint every couple of seconds we can set the +timeout to be long (e.g. 6 hours) and expect the SFU to not forget sending the `{"action": "send"}` action +when it detects a disconnecting client. ### Batch sending In some scenarios it is important to allow to send an event with an associated delay 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 +- 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 delayed 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 change it back to `{}`. If we have two separate request sending `A` could work @@ -497,12 +541,8 @@ For this use case batch sending of multiple delayed events would be desired. We do not include batch sending in the proposal of this MSC however since batch sending should become a generic Matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) -There is a [batch sending version](#batch-delayed-events-with-custom-endpoint) in the Alternatives section -that proposes a delayed event specific group sending endpoint in case this is required sooner then its realistic to implement -[MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). - [MSC2716: Incrementally importing history into existing rooms](https://github.com/matrix-org/matrix-spec-proposals/pull/2716) -already proposes a `batch_send` endpoint. However, it is limited to application services however and focuses on historic +already proposes a `batch_send` endpoint. However, it is limited to application services and focuses on historic data. Since we also need the additional capability to use a template event_id parameter, this probably is not a good fit. ### Not reusing the `send`/`state` endpoint @@ -548,15 +588,24 @@ With a dedicated ping (independent to the sync loop) it is more flexible and all execute the timer restart. If the widget dies, the call membership will disconnect. +Additionally the specification should not include specific +custom server rules if possible. +Send an event in the name of a user based on the client sync loop if there is an event with a specific type and specific +content, is quiet a specific server behaviour and also would not work well with encrypted state events and cryptographic +identities. +This proposal is a general behaviour valid for all event types. + ### Federated delayed events -The delayed events could be sent over federation immediately and then have the receiving servers process them at the -appropriate time. +Delayed events could be sent over federation immediately and then have the receiving servers process (send down to clients) +them at the appropriate time. Downsides of this approach that have been considered: -- "heartbeats"/restarts would need to distributed via the federation meaning more traffic and processing to be done. -- if another homeservers missed the "heartbeat"/restart then it might decide that the event is visible in the DAG whereas +- each individual "heartbeats"/restarts would need to distributed via the federation meaning more traffic and processing +to be done. +- if another homeservers missed the federated "heartbeat"/restart message, then it might decide that the event is visible +to clients whereas other homeservers might have received it and come to a different conclusion. If the event was later cancelled then resolving the inconsistency feels more complex than if the event was never sent in the first place. @@ -621,7 +670,7 @@ This would simplify the API, but it's less efficient since the client would have Instead of providing a `cancel` action for delayed events, the client could send a `DELETE` request to the same endpoint. -This feels more elegant, but the doesn't feel like a good suggestion for how the other actions are mapped. +This feels more elegant, but it doesn't feel like a good suggestion for how the other actions are mapped. ### [Ab]use typing notifications @@ -649,14 +698,14 @@ Some alternatives for the `running_since` field on the `GET` response: All new endpoints are authenticated. -Servers SHOULD impose a maximum timeout value for delay timeouts of not more than a month. +Servers **should** impose a maximum timeout value for delay timeouts of not more than a month. -As described [above](#power-levels-are-evaluated-at-the-point-of-sending), the homeserver MUST evaluate and enforce the +As described [above](#power-levels-are-evaluated-at-the-point-of-sending), the homeserver **must** evaluate and enforce the power levels at the time of the delayed event being sent (i.e. added to the DAG). -The is a risk that this feature could be used by a malicious actor to circumvent existing rate limiting measures which +This has the risk, that this feature could be used by a malicious actor to circumvent existing rate limiting measures which corresponds to the [High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) -threat. The homeserver SHOULD apply rate-limiting to both the scheduling of delayed events and the later sending to +threat. The homeserver **should** apply rate-limiting to both the scheduling of delayed events and the later sending to mitigate this risk. ## Unstable prefix @@ -708,8 +757,8 @@ instead of: } ``` -Additionally, the feature is to be advertised as unstable feature in the `GET /_matrix/client/versions` response, with -the key `org.matrix.msc4140` set to `true`. So, the response could look then as following: +Additionally, the feature is to be advertised as an unstable feature in the `GET /_matrix/client/versions` response, with +the key `org.matrix.msc4140` set to `true`. So, the response could then look as follows: ```json { From b8e317f0c2b12f32b7dbca5b1f30f4bc2d1ed6c3 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 4 Oct 2024 16:46:25 +0200 Subject: [PATCH 79/85] terminated -> finalised --- proposals/4140-delayed-events-futures.md | 73 +++++++++++++++++------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index cfbe00730bb..d363e751af8 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -186,7 +186,7 @@ Where the `action` is `send`, the homeserver **should** apply rate limiting to p ### Getting delayed events New authenticated client-server API endpoints `GET /_matrix/client/v1/delayed_events/scheduled` and -`GET /_matrix/client/v1/delayed_events/terminated` allows clients to get a list of +`GET /_matrix/client/v1/delayed_events/finalised` allows clients to get a list of all the delayed events owned by the requesting user that have been scheduled to send, have been sent or fail to send. The endpoints accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as @@ -209,32 +209,30 @@ The response is a JSON object containing the following fields: of the full event after sending. - `next_batch` - Optional. A token that can be used to paginate the list of delayed events. -- For the `GET /_matrix/client/v1/delayed_events/terminated` endpoint: - - `terminated_events` - Required. An array of finalized delayed events, that have either been sent or resulted in an error, - sorted by `origin_server_ts` in decreasing order (latest terminated event first). +- For the `GET /_matrix/client/v1/delayed_events/finalised` endpoint: + - `finalised_events` - Required. An array of finalised delayed events, that have either been sent or resulted in an error, + sorted by `origin_server_ts` in decreasing order (latest finalised event first). - `delayed_event` - Required. Describes the original delayed event in the same format as the `delayed_events` array. - - `termination_reason` - Required `"timeout"|"send"|"canceled"|"error"`. - - `"timeout"`: the event got sent because the delay timed out - - `"send"`: the event got sent because the send endpoint was called. - - `"canceled"`: the event got canceled using the `cancel` endpoint - - `"error"`: the delay timed out but the event sending failed. - - `error` - Optional Error. A matrix error (as defined by [Standard error response](https://spec.matrix.org/v1.11/client-server-api/#standard-error-response)) - to explain why this event failed to get sent. - - `event_id` - Optional EventId. The `event_id` this event got when it was sent. + - `outcome`: "send"|"cancel", + - `reason`: "error"|"action"|"delay" + - `error`: Optional Error. A matrix error (as defined by [Standard error response](https://spec.matrix.org/v1.11/client-server-api/#standard-error-response)) + to explain why this event failed to get sent. The Error can either be the `M_CANCELLED_BY_STATE_UPDATE` or any of the + Errors from the client server send and state endpoints. + - `event_id` - Optional EventId. The `event_id` this event got in case it was sent. - `origin_server_ts` - Optional Timestamp. The timestamp the event was sent. - - `next_terminated_batch` - Optional. A token that can be used to paginate the list of finalized events. + - `next_batch` - Optional. A token that can be used to paginate the list of finalised events. The batch size and the amount of termiated events that stay on the homeserver can be chosen, by the homeserver. The recommended values are: -- `terminated_events` retention: 7days -- `terminated_events` batch size: 10 -- `terminated_events` max cached events: 1000 +- finalised events retention: 7days +- finalised_events batch size: 10 +- finalised_events max cached events: 1000 There is no guarantee for a client that all events will be available in the -`terminated_events` list if they exceed the limits of their homeserver. +finalised events list if they exceed the limits of their homeserver. -For example: +An example for a response to the `GET /_matrix/client/v1/delayed_events/scheduled` endpoint: ```http 200 OK @@ -269,8 +267,7 @@ Content-Type: application/json } ``` -`running_since` is the timestamp (as unix time in milliseconds) when the delayed event was scheduled or last restarted. -So, unless the delayed event is updated beforehand, the event will be sent after `running_since` + `delay`. +Unless the delayed event is updated beforehand, the event will be sent after `running_since` + `delay`. This can be used by clients to display events that have been scheduled to be sent in the future. @@ -312,6 +309,18 @@ the _new state event_: the delayed event but later with the content of _new state event_. - _new state event_ followed by timeout for _delayed event_: the _new state event_ will cancel the outstanding _delayed event_. +The finalised delayed event as represented by the finalised list of the GET endpoint (See:[Getting delayed events](#getting-delayed-events)) +will be stored with the following outcome: + +```json +"outcome": "canceled", +"reason": "error", +"error": { + "errorcode": "M_CANCELLED_BY_STATE_UPDATE", + "error":"The delayed event did not get send because a different user updated the same state event. + So the scheduled event might change it in an undesired way."} +``` + Note that this behaviour does not apply to regular (non-state) events as there is no concept of a `(type, state_key)` pair that could be overwritten. @@ -572,8 +581,7 @@ duration computation) we cannot compute the `event_id` when using the send endpo [MSC4018: Reliable call membership](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. +inform about the call termination or using the call app ping/refresh loop like we propose here. The advantage is, that this does not require us to introduce a new ping system (as we do by using `delayed_events` restart action). @@ -757,6 +765,27 @@ instead of: } ``` +- The `M_UNKNOWN` `errcode` should be used instead of `M_CANCELLED_BY_STATE_UPDATE` as follows: + +```json +{ + "errcode": "M_UNKNOWN", + "org.matrix.msc4140.errcode": "M_CANCELLED_BY_STATE_UPDATE", + "error":"The delayed event did not get send because a different user updated the same state event. + So the scheduled event might change it in an undesired way." + } +``` + +instead of: + +```json +{ + "errcode": "M_CANCELLED_BY_STATE_UPDATE", + "error":"The delayed event did not get send because a different user updated the same state event. + So the scheduled event might change it in an undesired way." + } +``` + Additionally, the feature is to be advertised as an unstable feature in the `GET /_matrix/client/versions` response, with the key `org.matrix.msc4140` set to `true`. So, the response could then look as follows: From b4999954132867c4a40a0f3e093a48756741dd79 Mon Sep 17 00:00:00 2001 From: Timo Date: Fri, 4 Oct 2024 16:49:18 +0200 Subject: [PATCH 80/85] andrews changes --- proposals/4140-delayed-events-futures.md | 203 +++++++++++------------ 1 file changed, 101 insertions(+), 102 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index d363e751af8..0b75b280306 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -6,7 +6,7 @@ a room at a later time. The client does not have to be running or in contact with the Homeserver at the time that the event is actually sent. Once the event has been scheduled, the user's homeserver is responsible for actually sending the event at the appropriate -time and then distributing as normal via federation. +time and then distributing it as normal via federation. - [Background and motivation](#background-and-motivation) @@ -54,7 +54,7 @@ The Client-Server API currently has a [Voice over IP module](https://spec.matri that uses room messages to communicate the call state. However, it only allows for calls with two participants. [MSC3401: Native Group VoIP Signalling](https://github.com/matrix-org/matrix-spec-proposals/pull/3401) proposes a scheme -that allows for more than two-participants by using room state events. +that allows for more than two participants by using room state events. In this arrangement each device signals its participant in a call by sending a state event that represents the device's "membership" of a call. Once the device is no longer in the call, it sends a new state event to update the call state and @@ -76,8 +76,8 @@ in the [Use case specific considerations/MatrixRTC](#use-case-specific-considera of this proposal. This proposal enables a Matrix client to schedule a "hangup" state event to be sent after a specified time period. -The client can then periodically restarts the timer whilst it the client is running. If the client is no longer running -or able to communicate then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. +The client can then periodically restart the timer whilst it is running. If the client is no longer running +or able to communicate, then the timer would expire and the homeserver would send the "hangup" event on behalf of the client. Such an arrangement can also be described as a "heartbeat" mechanism. The client sends a "heartbeat" to the homeserver in the form of a "restart" of the delayed event to keep the call "alive". @@ -109,7 +109,7 @@ The new query parameter is used to configure the event scheduling: - `delay` - Optional number of milliseconds the homeserver should wait before sending the event. If no `delay` is provided, the event is sent immediately as normal. -The body of the request is the same as currently. +The body of the request is the same as it is currently. If a `delay` is provided, the homeserver schedules the event to be sent with the specified delay and responds with a `delay_id` field (omitting the `event_id` as it is not available): @@ -123,11 +123,11 @@ Content-Type: application/json } ``` -The homeserver can optionally enforce a maximum delay duration. If the requested delay exceeds the maximum the homeserver -can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) and a Matrix error code -`M_MAX_DELAY_EXCEEDED` and the maximum allowed delay (`max_delay` in milliseconds). +The homeserver can optionally enforce a maximum delay duration. If the requested delay exceeds the maximum, the homeserver +can respond with a [`400`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400) status code +and a body with a Matrix error code `M_MAX_DELAY_EXCEEDED` and the maximum allowed delay (`max_delay` in milliseconds). -For example the following specifies a maximum delay of 24 hours: +For example, the following specifies a maximum delay of 24 hours: ```http 400 Bad Request @@ -143,7 +143,7 @@ Content-Type: application/json The homeserver **should** apply rate limiting to the scheduling of delayed events to provide mitigation against the [High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat. -The homeserver MAY apply a limit on the maximum number of outstanding delayed events in which case the Matrix error code +The homeserver **may** apply a limit on the maximum number of outstanding delayed events in which case the Matrix error code `M_MAX_DELAYED_EVENTS_EXCEEDED` can be returned: ```http @@ -187,7 +187,7 @@ Where the `action` is `send`, the homeserver **should** apply rate limiting to p New authenticated client-server API endpoints `GET /_matrix/client/v1/delayed_events/scheduled` and `GET /_matrix/client/v1/delayed_events/finalised` allows clients to get a list of -all the delayed events owned by the requesting user that have been scheduled to send, have been sent or fail to send. +all the delayed events owned by the requesting user that have been scheduled to send, have been sent, or failed to be sent. The endpoints accepts a query parameter `from` which is a token that can be used to paginate the list of delayed events as per the [pagination convention](https://spec.matrix.org/v1.11/appendices/#pagination). The homeserver can choose a suitable @@ -196,16 +196,16 @@ page size. The response is a JSON object containing the following fields: - For the `GET /_matrix/client/v1/delayed_events/scheduled` endpoint: - - `delayed_events` - Required. An array of delayed events that have been scheduled to send, + - `delayed_events` - Required. An array of delayed events that have been scheduled to be sent, sorted by `running_since + delay` in increasing order (event that will timeout soonest first). - `delay_id` - Required. The ID of the delayed event. - `room_id` - Required. The room ID of the delayed event. - `type` - Required. The event type of the delayed event. - `state_key` - Optional. The state key of the delayed event if it is a state event. - - `delay` - Required. The delay in milliseconds before the event is sent. - - `running_since` - Required. The timestamp (as unix time in milliseconds) when the delayed event was scheduled or + - `delay` - Required. The delay in milliseconds before the event is to be sent. + - `running_since` - Required. The timestamp (as Unix time in milliseconds) when the delayed event was scheduled or last restarted. - - `content` - Required. The content of the delayed event. This is the body of the original `PUT` request not a preview + - `content` - Required. The content of the delayed event. This is the body of the original `PUT` request, not a preview of the full event after sending. - `next_batch` - Optional. A token that can be used to paginate the list of delayed events. @@ -216,13 +216,13 @@ The response is a JSON object containing the following fields: - `outcome`: "send"|"cancel", - `reason`: "error"|"action"|"delay" - `error`: Optional Error. A matrix error (as defined by [Standard error response](https://spec.matrix.org/v1.11/client-server-api/#standard-error-response)) - to explain why this event failed to get sent. The Error can either be the `M_CANCELLED_BY_STATE_UPDATE` or any of the + to explain why this event failed to be sent. The Error can either be the `M_CANCELLED_BY_STATE_UPDATE` or any of the Errors from the client server send and state endpoints. - `event_id` - Optional EventId. The `event_id` this event got in case it was sent. - `origin_server_ts` - Optional Timestamp. The timestamp the event was sent. - `next_batch` - Optional. A token that can be used to paginate the list of finalised events. -The batch size and the amount of termiated events that stay on the homeserver can be chosen, by the homeserver. +The batch size and the amount of terminated events that stay on the homeserver can be chosen, by the homeserver. The recommended values are: - finalised events retention: 7days @@ -271,8 +271,8 @@ Unless the delayed event is updated beforehand, the event will be sent after `ru This can be used by clients to display events that have been scheduled to be sent in the future. -For use cases where the existence of a delayed event is also of interest for other room members, -(e.g. self-destructing messages) it is recommended to include this information in the original/affected event itself. +For use cases where the existence of a delayed event is also of interest for other room members +(e.g. self-destructing messages), it is recommended to include this information in the original/affected event itself. ### Homeserver implementation details @@ -281,8 +281,8 @@ For use cases where the existence of a delayed event is also of interest for oth Power levels are evaluated for each event only once the delay has occurred and it will be distributed/inserted into the DAG. This implies a delayed event can fail if it violates power levels at the time the delay passes. -Conversely, it's also possible to successfully schedule an event that the user has no permission to at the time of sending. -If the power level situation has changed at the time the delay passes the event can even reach the DAG. +Conversely, it's also possible to successfully schedule an event that the user has no permission to send at the time of sending. +If the power level situation has changed at the time the delay passes, the event can even reach the DAG. #### Delayed state events are cancelled by a more recent state event @@ -294,14 +294,14 @@ If the power level situation has changed at the time the delay passes the event > - A new state event `N` with type `t` and key `k` is sent into the room. > - The sender of `D` is different to the sender `N`. -If a new state event is sent to the same room at the same entry (event_type-state_key pair) as a delayed event by a -**different matrix user**, any delayed event for this entry (event_type-state_key pair) is cancelled. +If a new state event is sent to the same room at the same entry (`event_type`, `state_key` pair) as a delayed event by a +**different matrix user**, any delayed event for this entry (`event_type`, `state_key` pair) is cancelled. -This only happens if its a state update from a different user. If its the same user the delayed event will not get cancelled. -If the same user is updating the state which has associate delayed events this user is in control of the delayed events. +This only happens if its a state update from a different user. If it is from the same user, the delayed event will not get cancelled. +If the same user is updating the state which has associated delayed events, this user is in control of those delayed events. They can just cancel and check the events manually using the `/delayed_events` and the `/delayed_events/scheduled` endpoint. -In the case where the delayed event gets cancelled due to a different user updateting the same state, there +In the case where the delayed event gets cancelled due to a different user updating the same state, there is no race condition here since a possible race between timeout and the _new state event_ will always converge to the _new state event_: @@ -321,7 +321,7 @@ will be stored with the following outcome: So the scheduled event might change it in an undesired way."} ``` -Note that this behaviour does not apply to regular (non-state) events as there is no concept of a `(type, state_key)` +Note that this behaviour does not apply to regular (non-state) events as there is no concept of a (`event_type`, `state_key`) pair that could be overwritten. #### Rate-limiting at the point of sending @@ -339,20 +339,20 @@ this attack. ## Use case specific considerations -Delayed events can be used for lots of different features, tea timers, reminders or ephemeral events could be implemented +Delayed events can be used for many different features: tea timers, reminders, or ephemeral events could be implemented using delayed events, where clients send room events with intentional mentions or a redaction as a delayed event. It can even be used to send temporal power levels/mutes or bans. ### MatrixRTC -In this section an overview is given how this MSC is used in [MSC4143: MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) +In this section, an overview is given how this MSC is used in [MSC4143: MatrixRTC](https://github.com/matrix-org/matrix-spec-proposals/pull/4143) and alternative expiration systems are evaluated. #### Background MatrixRTC makes it necessary to have real time information about the current MatrixRTC session. -To properly display room tiles and header in the room list (or compute a list of ongoing calls) need to know: +To properly display room tiles and header in the room list (or compute a list of ongoing calls), it's required to know: - If there is a running session. - What type that session has. @@ -365,46 +365,43 @@ There are numerous approaches to solve such a situation. They split into two cat - Ask the users if they are still connected. - Ask an RTC backend (SFU) who is connected. - Timeout based - - Update the room state every x seconds. This allows clients to check how long an event has not been updated and ignore it if it's expired. - Use delayed events with a 10s timeout to send the disconnected from call - in less then 10s after the user is not anymore pinging the `/delayed_events` endpoint. - (or delegate the disconnect action to a service attached to the SFU) - - Use the client sync loop as a special case timeout for call member events. - (See [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)) + in less then 10s after the user is not anymore pinging the `/delayed_events` endpoint + (or delegate the disconnect action to a service attached to the SFU). + - Use the client sync loop as a special case timeout for call member events + (see [Alternatives/MSC4018 (use client sync loop))](#msc4018-use-client-sync-loop)). -Polling based solution have a big overhead in complexity and network requests on the clients. -Example: +Polling based solutions have a large overhead in complexity and network requests on the clients. +For example: > A room list with 100 rooms where there has been a call before in every room > (or there is an ongoing call) would require the client to send a to-device message > (or a request to the SFU) to every user that has an active state event to check if -> they are still online. Just to display the room tile properly. +> they are still online. All this is just to display the room tile properly. -For displaying the room list timeout based approaches are much more reasonable because this allows computing MatrixRTC +For displaying the room list, timeout based approaches are much more reasonable because they allow computing MatrixRTC metadata for a room to be synchronous. The current solution updates the room state every X minutes. -This is not elegant since we basically resend room state with the same content. -In large calls this could result in lots of traffic and increase the size of the room DAG +This is not elegant since room state gets repeatedly sent with the same content. +In large calls, this could result in high traffic and increase the size of the room DAG. A call with 100 call members implies 100 state events every X minutes. X cannot be a long duration because -it is the duration after which we can consider the event as expired. Improper +it is the duration after which the event can be considered expired. Improper disconnects would result in the user being displayed as "still in the call" for -X minutes (we want this to be as short as possible!) +X minutes (which should be as short as possible). -Additionally this approach requires perfect server client time synchronization to compute the expiration. +Additionally, this approach requires perfect server client time synchronization to compute the expiration. This is currently not possible over federation since `unsigned.age` is not available over federation. #### How this MSC would be used for MatrixRTC -With this proposal the client can use delayed events to implement a "heartbeat" mechanism. - -On joining the call the client sends a "join" state event as normal to indicate that it is participating: +With this proposal, the client can use delayed events to implement a "heartbeat" mechanism. -e.g. +On joining the call, the client sends a "join" state event as normal to indicate that it is participating: ```http PUT /_matrix/client/v1/rooms/!wherever:example.com/state/m.call.member/@someone:example.com @@ -419,7 +416,7 @@ Content-Type: application/json } ``` -Before sending the join event it also schedules a delayed "hangup" state event with `delay` of around 5-20 seconds that +Before sending the join event, it also schedules a delayed "hangup" state event with `delay` of around 5-20 seconds that marks the end of its participation: ```http @@ -447,17 +444,17 @@ Content-Type: application/json } ``` -This would have the effect that if the homeserver does not receive a "heartbeat" from the client for 10 seconds then +This would have the effect that if the homeserver does not receive a "heartbeat" from the client for 10 seconds, then it will automatically send the "hangup" state event for the client. -Since the delayed event is sent first. A client can guarantee (at the time they are sending +Since the delayed event is sent first, a client can guarantee (at the time they are sending the join event) that it will eventually leave. ### Self-destructing messages This MSC also allows an implementation of "self-destructing" messages using redaction: -First send (or generate the pdu when +First send (or generate the PDU when [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) is available): `PUT /_matrix/client/v1/rooms/{roomId}/send/m.room.message/{txnId}` @@ -484,7 +481,7 @@ This would redact the message with content: `"m.text": "my msg"` after 10 minute ### Compatibility with Cryptographic Identities -We are mindful that this proposal should be compatible with other proposals such as +Ideally, this proposal should be compatible with other proposals such as [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080) which introduce mechanisms to allow the recipient of an event to determine whether it was sent by a client as opposed to have been spoofed/injected by a malicious homeserver. @@ -494,12 +491,12 @@ that scheduled them. This means that the content of the original scheduled event must be sent "as is" without modification by the homeserver. The consequence is an implementation detail that client developers must be aware of: if the content of the delayed -event contains a timestamp then it would be the timestamp of when the event was originally scheduled rather than +event contains a timestamp, then it would be the timestamp of when the event was originally scheduled rather than anything later. However, the `origin_server_ts` of the delayed event should be the time that the event is actually sent by the homeserver. -This is a general probelen that ariases with the introduction +This is a general problem that arises with the introduction of [Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080). A user can intentionally, or caused by network conditions, delay the signing and sending of an event. A possible solution would be the introduction of a `signing_ts` (in the signed section) and keep the `origin_server_ts` @@ -512,24 +509,25 @@ it would make it transparent to clients, when an event was scheduled and when it ### Delegating delayed events -It is useful for external services to also interact with delayed events. If a client disconnects an external service can +It is useful for external services to also interact with delayed events. If a client disconnects, an external service can be the best source to send the delayed event/"last will". This is not covered in this MSC but could be realized with scoped access tokens. A scoped token that only allows to interact with the `delayed_events` endpoint and only with a subset of `delay_id`s would be used. -With this, an SFU, that tracks the current client connection state, could be given the power to control the delayed event. -The client shares the scoped token and the required details, so that the SFU can call the -refresh endpoint while a user is connected +With this, an SFU that tracks the current client connection state could be given the power to control the delayed event. +The client would share the scoped token and the required details, so that the SFU can call the +`refresh` endpoint while a user is connected and can call the delayed event `send` request once the user disconnects (using a `{"action": "restart"}` and a `{"action": "send"}` `/delayed_events` request.). -This way the SFU can be used as the source of truth for the call member room state event without knowing anything about +This way, the SFU can be used as the source of truth for the call member room state event without knowing anything about the Matrix call. -Since the SFU has a much lower chance of running into a network issue, we can significantly reduce the use of the -`{"action": "restart"}` calls. Instead of calling the `/delayed_events` endpoint every couple of seconds we can set the -timeout to be long (e.g. 6 hours) and expect the SFU to not forget sending the `{"action": "send"}` action +Since the SFU has a much lower chance of running into a network issue, +`{"action": "restart"}` calls may be sent much more infrequently. +Instead of calling the `/delayed_events` endpoint every couple of seconds, a delayed event's +timeout can be set to be long (e.g. 6 hours), as the SFU can be expected to not forget sending the `{"action": "send"}` action when it detects a disconnecting client. ### Batch sending @@ -538,32 +536,33 @@ In some scenarios it is important to allow to send an event with an associated delay 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 delayed redact is received + It might be important to have the guarantee that the delayed 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 change it back to `{}`. If we have two separate request sending `A` could work +- In the case of a state event, a user might want to set the state to `A` and after a + timeout change it back to `{}`. By using two separate requests, sending `A` could work, but the event with content `{}` could fail. The state would not automatically reset to `{}`. -For this use case batch sending of multiple delayed events would be desired. +For this use case, batch sending of multiple delayed events would be desired. -We do not include batch sending in the proposal of this MSC however since batch sending should +Batch sending is not included in the proposal of this MSC however since batch sending should become a generic Matrix concept as proposed with `/send_pdus`. (see: [MSC4080: Cryptographic Identities](https://github.com/matrix-org/matrix-spec-proposals/pull/4080)) [MSC2716: Incrementally importing history into existing rooms](https://github.com/matrix-org/matrix-spec-proposals/pull/2716) already proposes a `batch_send` endpoint. However, it is limited to application services and focuses on historic -data. Since we also need the additional capability to use a template event_id parameter, this probably is not a good fit. +data. Since the additional capability to use a template `event_id` parameter is also needed, this probably is not a good fit. ### Not reusing the `send`/`state` endpoint -Alternatively new endpoints could be introduced to not overload the `send` and `state` endpoint. +Alternatively, new endpoints could be introduced to not overload the `send` and `state` endpoint. Those endpoints could be called: + `PUT /_matrix/client/v1/rooms/{roomId}/send_delayed_event/{eventType}/{txnId}?delay={delay_ms}` `PUT /_matrix/client/v1/rooms/{roomId}/state_delayed_event/{eventType}/{stateKey}?delay={delay_ms}` -This would allow the response for the `send` and `state` endpoints intact and we get a different return type -for the new `send_delayed_event` and `state_delayed_event` endpoints. +This would allow the response for the `send` and `state` endpoints to remain as they are currently, +and to have a different return type for the new `send_delayed_event` and `state_delayed_event` endpoints. ### Allocating the event ID at the point of scheduling the send @@ -574,45 +573,45 @@ which is [calculated from the fields](https://spec.matrix.org/v1.10/server-serve of an event including the `origin_server_timestamp` as defined in [this list](https://spec.matrix.org/v1.10/rooms/v11/#client-considerations) Since the `origin_server_timestamp` should be the timestamp the event has when entering the DAG (required for call -duration computation) we cannot compute the `event_id` when using the send endpoint when the dealyed event has not yet resolved. +duration computation), the `event_id` cannot be computed when using the `send` endpoint before the delayed event has resolved. ### MSC4018 (use client sync loop) [MSC4018: Reliable call membership](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/refresh loop like we propose here. +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/refresh loop as proposed earlier in this MSC. -The advantage is, that this does not require us to introduce a new ping system (as we do by using `delayed_events` -restart action). -With cryptographic identities we however need the client to create the leave event. +The advantage is that this does not require introducing a new ping system +(as is proposed here by using the `delayed_events` restart action). +Though with cryptographic identities, the client needs to create the leave event. The timeout for syncs are much slower than what would be desirable (30s vs 5s). -With a widget implementation for calls we cannot guarantee that the widget is running during the sync loop. +With a widget implementation for calls, it cannot be guaranteed that the widget is running during the sync loop. So one either has to move the hangup logic to the hosting client or let the widget run all the time. -With a dedicated ping (independent to the sync loop) it is more flexible and allows us to let the widget +A dedicated ping (independent to the sync loop) is more flexible and allows for the widget to execute the timer restart. If the widget dies, the call membership will disconnect. -Additionally the specification should not include specific +Additionally, the specification should not include specific custom server rules if possible. -Send an event in the name of a user based on the client sync loop if there is an event with a specific type and specific -content, is quiet a specific server behaviour and also would not work well with encrypted state events and cryptographic +Sending an event on behalf of a user based on the client sync loop if there is an event with a specific type and specific +content is quite a server-specific behaviour, and also would not work well with encrypted state events and cryptographic identities. This proposal is a general behaviour valid for all event types. ### Federated delayed events -Delayed events could be sent over federation immediately and then have the receiving servers process (send down to clients) +Delayed events could be sent over federation immediately and then have the receiving servers process (sent down to clients) them at the appropriate time. -Downsides of this approach that have been considered: +Downsides of this approach that have been considered are that: -- each individual "heartbeats"/restarts would need to distributed via the federation meaning more traffic and processing +- individual "heartbeats"/restarts would need to distributed via federation, meaning more traffic and processing to be done. -- if another homeservers missed the federated "heartbeat"/restart message, then it might decide that the event is visible +- if any homeservers missed the federated "heartbeat"/restart message, then they might decide that the event is visible to clients whereas other homeservers might have received it and come to a different conclusion. If the event was later cancelled then resolving the inconsistency feels more complex than if the event was never sent in the first place. @@ -621,36 +620,36 @@ resolving the inconsistency feels more complex than if the event was never sent and there is an extensive analysis of the pros and cons of this MSC vs MSC3277 [here](https://github.com/matrix-org/matrix-spec-proposals/pull/4140#discussion_r1653083566). -If we don't need modification of a delayed event after it has been scheduled, there is a benefit in +If it's not needed to allow modification of a delayed event after it has been scheduled, there is a benefit in federating the scheduled event (adding it to the DAG immediately). It increases resilience: the sender's homeserver can disconnect and the delayed message still will enter non-soft-failed state (will be sent). -However, for the MatrixRTC use case we do need to be able to modify the event after it has been scheduled. As such, we -have discounted this approach. +However, for the MatrixRTC use case it's requried to be able to modify the event after it has been scheduled. As such, +this approach has been discounted. ### MQTT style Last Will [MQTT](https://mqtt.org/) has the concept of a Will Message that is published by the server when a client disconnects. -The client can set a Will Message when it connects to the server. If the client disconnects unexpectedly the server will +The client can set a Will Message when it connects to the server. If the client disconnects unexpectedly, the server will publish the Will Message if the client is not back online within a specified time. -A similar concept could be applied to Matrix by having the client specify a set of "Last Will" event(s) and have the +A similar concept could be applied to Matrix by having the client specify a set of "Last Will" events and have the homeserver trigger them if the client (possibly identified by device ID) does not send an API request within a specified time. The main differentiator is that this type of approach might use the sync loop as the "heartbeat" equivalent similar to -MSC4018(https://github.com/matrix-org/matrix-spec-proposals/pull/4018). +[MSC4018](https://github.com/matrix-org/matrix-spec-proposals/pull/4018). A benefit compared to this proposal is that theoretically there would be no additional network traffic overhead. -Some complications: +Some complications are: -- in order that the isn't the additional network traffic the homeserver would need to proactively realise that a connection +- in order to avoid additional network traffic, the homeserver would need to proactively realise that a connection has dropped. Depending on the network/load balancer stack this might be problematic. -- as an alternative the client could reduce the long poll timeout (from a typical 30s down to, say, 5s) which would +- as an alternative, the client could reduce the long poll timeout (from a typical 30s down to, say, 5s) which would result in a traffic increase. -- As syncing is a per client concept the MatrixRTC app has to either run in the same process as the client so that a +- As syncing is a per-client concept, the MatrixRTC app has to either run in the same process as the client so that a MatrixRTC app failure triggers the client Last Will or the client has to observe the MatrixRTC app and simulate the Last Will if the MatrixRTC app fails. @@ -660,7 +659,7 @@ The existing `M_INVALID_PARAM` error code could be used instead of introducing a ### Naming -The following alternative names for this concept are considered +The following alternative names for this concept are considered: - Future - DelayedEvents @@ -687,15 +686,15 @@ Some exploration of using typing notifications to indicate that a user is still The idea of extending [MSC3038: Typed typing notifications](https://github.com/matrix-org/matrix-spec-proposals/pull/3038) to allow for additional meta data (like device ID and call ID) was considered. -A perceived benefit was that if the delay events were federated then the typing notification EDUs might provide an +A perceived benefit was that if the delay events were federated, then the typing notification EDUs might provide an efficient transport. -However, as the conclusion was to [not federate the delayed events](#federated-delayed-events) this approach was +However, as the conclusion was to [not federate the delayed events](#federated-delayed-events), this approach was discounted in favour of a dedicated endpoint. ### Alternative to `running_since` field -Some alternatives for the `running_since` field on the `GET` response: +Some alternatives for the `running_since` field on the `GET` response are: - `delaying_from` - `delayed_since` @@ -711,7 +710,7 @@ Servers **should** impose a maximum timeout value for delay timeouts of not more As described [above](#power-levels-are-evaluated-at-the-point-of-sending), the homeserver **must** evaluate and enforce the power levels at the time of the delayed event being sent (i.e. added to the DAG). -This has the risk, that this feature could be used by a malicious actor to circumvent existing rate limiting measures which +This has the risk that this feature could be used by a malicious actor to circumvent existing rate limiting measures which corresponds to the [High Volume of Messages](https://spec.matrix.org/v1.11/appendices/#threat-high-volume-of-messages) threat. The homeserver **should** apply rate-limiting to both the scheduling of delayed events and the later sending to mitigate this risk. From 8dc05a4904ce6c97c746152217a3b35c2fb250a5 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Mon, 7 Oct 2024 13:22:17 -0400 Subject: [PATCH 81/85] requried -> required --- proposals/4140-delayed-events-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 0b75b280306..4a51136bd86 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -624,7 +624,7 @@ If it's not needed to allow modification of a delayed event after it has been sc federating the scheduled event (adding it to the DAG immediately). It increases resilience: the sender's homeserver can disconnect and the delayed message still will enter non-soft-failed state (will be sent). -However, for the MatrixRTC use case it's requried to be able to modify the event after it has been scheduled. As such, +However, for the MatrixRTC use case it's required to be able to modify the event after it has been scheduled. As such, this approach has been discounted. ### MQTT style Last Will From 0a777f4a86ab12ea4808b62ed9c85494933c763c Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 8 Oct 2024 15:20:54 -0400 Subject: [PATCH 82/85] Change example of "canceled" outcome to "cancel" --- proposals/4140-delayed-events-futures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 4a51136bd86..7a656796084 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -313,7 +313,7 @@ The finalised delayed event as represented by the finalised list of the GET endp will be stored with the following outcome: ```json -"outcome": "canceled", +"outcome": "cancel", "reason": "error", "error": { "errorcode": "M_CANCELLED_BY_STATE_UPDATE", From a09a883d9a013ac4b6ffddebd7ea87a827d211b9 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Wed, 9 Oct 2024 11:13:04 -0400 Subject: [PATCH 83/85] Minor formatting changes --- proposals/4140-delayed-events-futures.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 7a656796084..fe2e8767baa 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -213,8 +213,8 @@ The response is a JSON object containing the following fields: - `finalised_events` - Required. An array of finalised delayed events, that have either been sent or resulted in an error, sorted by `origin_server_ts` in decreasing order (latest finalised event first). - `delayed_event` - Required. Describes the original delayed event in the same format as the `delayed_events` array. - - `outcome`: "send"|"cancel", - - `reason`: "error"|"action"|"delay" + - `outcome`: `"send"|"cancel"` + - `reason`: `"error"|"action"|"delay"` - `error`: Optional Error. A matrix error (as defined by [Standard error response](https://spec.matrix.org/v1.11/client-server-api/#standard-error-response)) to explain why this event failed to be sent. The Error can either be the `M_CANCELLED_BY_STATE_UPDATE` or any of the Errors from the client server send and state endpoints. @@ -225,9 +225,9 @@ The response is a JSON object containing the following fields: The batch size and the amount of terminated events that stay on the homeserver can be chosen, by the homeserver. The recommended values are: -- finalised events retention: 7days -- finalised_events batch size: 10 -- finalised_events max cached events: 1000 +- `finalised_events` retention: 7 days +- `finalised_events` batch size: 10 +- `finalised_events` max cached events: 1000 There is no guarantee for a client that all events will be available in the finalised events list if they exceed the limits of their homeserver. From 72a808e62003ac14d3a3d6e1478a9bcc4897ce47 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 22 Oct 2024 09:31:19 -0400 Subject: [PATCH 84/85] Allow servers to discard returned finalised events --- proposals/4140-delayed-events-futures.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index fe2e8767baa..0d7d59580dd 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -231,6 +231,8 @@ The recommended values are: There is no guarantee for a client that all events will be available in the finalised events list if they exceed the limits of their homeserver. +Additionally, a homeserver may discard finalised delayed events that have been returned by a +`GET /_matrix/client/v1/delayed_events/finalised` response. An example for a response to the `GET /_matrix/client/v1/delayed_events/scheduled` endpoint: From d1a37f06fd8a3c0efb6cc35fee1646e057247dec Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 22 Oct 2024 09:33:23 -0400 Subject: [PATCH 85/85] Define finalised events for /sync & /transactions Also define a sync filter --- proposals/4140-delayed-events-futures.md | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/proposals/4140-delayed-events-futures.md b/proposals/4140-delayed-events-futures.md index 0d7d59580dd..62dfb925b39 100644 --- a/proposals/4140-delayed-events-futures.md +++ b/proposals/4140-delayed-events-futures.md @@ -14,6 +14,8 @@ time and then distributing it as normal via federation. - [Scheduling a delayed event](#scheduling-a-delayed-event) - [Managing delayed events](#managing-delayed-events) - [Getting delayed events](#getting-delayed-events) + - [On demand](#on-demand) + - [On push](#on-push) - [Homeserver implementation details](#homeserver-implementation-details) - [Power levels are evaluated at the point of sending](#power-levels-are-evaluated-at-the-point-of-sending) - [Delayed state events are cancelled by a more recent state event](#delayed-state-events-are-cancelled-by-a-more-recent-state-event) @@ -185,6 +187,8 @@ Where the `action` is `send`, the homeserver **should** apply rate limiting to p ### Getting delayed events +#### On demand + New authenticated client-server API endpoints `GET /_matrix/client/v1/delayed_events/scheduled` and `GET /_matrix/client/v1/delayed_events/finalised` allows clients to get a list of all the delayed events owned by the requesting user that have been scheduled to send, have been sent, or failed to be sent. @@ -276,6 +280,26 @@ This can be used by clients to display events that have been scheduled to be sen For use cases where the existence of a delayed event is also of interest for other room members (e.g. self-destructing messages), it is recommended to include this information in the original/affected event itself. +#### On push + +A new optional key, `finalised_events`, is added to the response body of `/sync`. The shape of its +value is equivalent to that of the response body of `GET /_matrix/client/v1/delayed_events/finalised`. +It is an array of the syncing user's delayed events that were sent or failed to be sent after the +`since` timestamp parameter of the associated `/sync` request, or all of them for full `/sync`s. +When no such delayed events exist, the `finalised_events` key is absent from the `/sync` response. + +A new key, `finalised_events`, is defined for `POST /_matrix/client/v3/user/{userId}/filter`. +Its value is a boolean which, if set to `false`, causes an associated `/sync` response to exclude +any `finalised_events` key it may have otherwise included. + +The only delayed events included in `finalised_events` are ones that have been retained by the homeserver, +as per the same retention policies as for the `GET /_matrix/client/v1/delayed_events/finalised` endpoint. +Additionally, a homeserver may discard finalised delayed events that have been returned by a `/sync` response. + +The `finalised_events` key is added to the request bodies of the appservice API `/transactions` endpoint. +It has the same content as the key for `/sync`, and contains all of the target appservice's delayed events +that were sent or failed to be sent since the previous transaction. + ### Homeserver implementation details #### Power levels are evaluated at the point of sending @@ -726,6 +750,8 @@ Whilst the MSC is in the proposal stage, the following should be used: the `POST /_matrix/client/v1/delayed_events/{delay_id}` endpoint. - `GET /_matrix/client/unstable/org.matrix.msc4140/delayed_events` should be used instead of the `GET /_matrix/client/v1/delayed_events` endpoint. +- `org.matrix.msc4140.finalised_events` should be used as keys of `/sync`, `/transactions`, and + `/filter` instead of `finalised_events`. - The `M_UNKNOWN` `errcode` should be used instead of `M_MAX_DELAY_EXCEEDED` as follows: ```json