From 8df0cfeae0f4612f8417665265c845438f42dd6f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 26 Aug 2022 20:53:28 +0100 Subject: [PATCH 1/8] s/room/rooms (#1215) * s/room/rooms Fixes #979 * Create 1215.clarification --- changelogs/client_server/newsfragments/1215.clarification | 1 + data/api/client-server/joining.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/client_server/newsfragments/1215.clarification diff --git a/changelogs/client_server/newsfragments/1215.clarification b/changelogs/client_server/newsfragments/1215.clarification new file mode 100644 index 00000000..3ccb2333 --- /dev/null +++ b/changelogs/client_server/newsfragments/1215.clarification @@ -0,0 +1 @@ +Fix various typos throughout the specification. diff --git a/data/api/client-server/joining.yaml b/data/api/client-server/joining.yaml index 70e7444e..48d630c4 100644 --- a/data/api/client-server/joining.yaml +++ b/data/api/client-server/joining.yaml @@ -111,7 +111,7 @@ paths: post: summary: Start the requesting user participating in a particular room. description: |- - *Note that this API takes either a room ID or alias, unlike* `/room/{roomId}/join`. + *Note that this API takes either a room ID or alias, unlike* `/rooms/{roomId}/join`. This API starts a user participating in a particular room, if that user is allowed to participate in that room. After this call, the client is From a6990ff27c096b206ffd19c4e26b9418458a08b2 Mon Sep 17 00:00:00 2001 From: David Robertson Date: Tue, 6 Sep 2022 17:21:51 +0100 Subject: [PATCH 2/8] Fix spacing of mapping types (#1230) * Fix spacing of mapping types * Changelog --- changelogs/internal/newsfragments/1230.clarification | 1 + layouts/partials/openapi/render-object-table.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/internal/newsfragments/1230.clarification diff --git a/changelogs/internal/newsfragments/1230.clarification b/changelogs/internal/newsfragments/1230.clarification new file mode 100644 index 00000000..736fbd12 --- /dev/null +++ b/changelogs/internal/newsfragments/1230.clarification @@ -0,0 +1 @@ +Fix the spacing of mapping types generated from the OpenAPI spec. diff --git a/layouts/partials/openapi/render-object-table.html b/layouts/partials/openapi/render-object-table.html index b5e332dd..b49cb3e8 100644 --- a/layouts/partials/openapi/render-object-table.html +++ b/layouts/partials/openapi/render-object-table.html @@ -73,7 +73,7 @@ */}} {{ if reflect.IsMap $property.additionalProperties }} {{ if $property.additionalProperties.title }} - {{ $type = delimit (slice "{ string: " $property.additionalProperties.title "}" ) "" }} + {{ $type = delimit (slice "{string: " $property.additionalProperties.title "}" ) "" }} {{ end }} {{ end }} From e406bd94f63c1c0682ccccf46603f96843e6c48b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 12 Sep 2022 16:34:51 -0600 Subject: [PATCH 3/8] Spec MSC2285: Private read receipts (#1216) * Convert `m.receipt.yaml` to traditional YAML * Spec MSC2285 (private read receipts) * Add some obvious copyright headers * Add changelog entries * Appease the linter Apparently it hates it when you do this. * Allow m.fully_read on /receipts * Apply suggestions from code review Co-authored-by: Matthew Hodgson Co-authored-by: Matthew Hodgson --- .../newsfragments/1216.feature.1 | 1 + .../newsfragments/1216.feature.2 | 1 + .../newsfragments/1216.feature.3 | 1 + content/client-server-api/modules/push.md | 14 ++- .../client-server-api/modules/read_markers.md | 19 ++-- content/client-server-api/modules/receipts.md | 39 ++++++- data/api/client-server/read_markers.yaml | 15 ++- data/api/client-server/receipts.yaml | 14 ++- data/event-schemas/examples/m.receipt.yaml | 5 + data/event-schemas/schema/m.receipt.yaml | 103 +++++++++--------- 10 files changed, 146 insertions(+), 66 deletions(-) create mode 100644 changelogs/client_server/newsfragments/1216.feature.1 create mode 100644 changelogs/client_server/newsfragments/1216.feature.2 create mode 100644 changelogs/client_server/newsfragments/1216.feature.3 diff --git a/changelogs/client_server/newsfragments/1216.feature.1 b/changelogs/client_server/newsfragments/1216.feature.1 new file mode 100644 index 00000000..fb130e2c --- /dev/null +++ b/changelogs/client_server/newsfragments/1216.feature.1 @@ -0,0 +1 @@ +Add `m.read.private` receipts, as per [MSC2285](https://github.com/matrix-org/matrix-spec-proposals/pull/2285). \ No newline at end of file diff --git a/changelogs/client_server/newsfragments/1216.feature.2 b/changelogs/client_server/newsfragments/1216.feature.2 new file mode 100644 index 00000000..27760b75 --- /dev/null +++ b/changelogs/client_server/newsfragments/1216.feature.2 @@ -0,0 +1 @@ +Make `m.fully_read` optional on `/read_markers`, as per [MSC2285](https://github.com/matrix-org/matrix-spec-proposals/pull/2285). \ No newline at end of file diff --git a/changelogs/client_server/newsfragments/1216.feature.3 b/changelogs/client_server/newsfragments/1216.feature.3 new file mode 100644 index 00000000..0f6eeb07 --- /dev/null +++ b/changelogs/client_server/newsfragments/1216.feature.3 @@ -0,0 +1 @@ +Allow `m.fully_read` markers to be set from `/receipts`, as per [MSC2285](https://github.com/matrix-org/matrix-spec-proposals/pull/2285). \ No newline at end of file diff --git a/content/client-server-api/modules/push.md b/content/client-server-api/modules/push.md index 401bcd66..0ad035db 100644 --- a/content/client-server-api/modules/push.md +++ b/content/client-server-api/modules/push.md @@ -107,7 +107,19 @@ determined by the push rules which apply to an event. When the user updates their read receipt (either by using the API or by sending an event), notifications prior to and including that event MUST -be marked as read. +be marked as read. Note that users can send both an `m.read` and +`m.read.private` receipt, both of which are capable of clearing notifications. + +If the user has both `m.read` and `m.read.private` set in the room then +the receipt which is more recent/ahead must be used to determine where +the user has read up to. For example, given an oldest-first set of events A, +B, C, and D the `m.read` receipt could be at event C and `m.read.private` +at event A - the user is considered to have read up to event C. If the +`m.read.private` receipt is then updated to point to B or C, the user's +notification state doesn't change (the `m.read` receipt is still more +ahead), however if the `m.read.private` receipt were to be updated to +event D then the user has read up to D (the `m.read` receipt is now +behind the `m.read.private` receipt). ##### Push Rules diff --git a/content/client-server-api/modules/read_markers.md b/content/client-server-api/modules/read_markers.md index 4ee07938..5487710b 100644 --- a/content/client-server-api/modules/read_markers.md +++ b/content/client-server-api/modules/read_markers.md @@ -31,12 +31,16 @@ The client cannot update fully read markers by directly modifying the `m.fully_read` account data event. Instead, the client must make use of the read markers API to change the values. +{{< changed-in v="1.4" >}} `m.read.private` receipts can now be sent from +`/read_markers`. + The read markers API can additionally update the user's read receipt -(`m.read`) location in the same operation as setting the fully read -marker location. This is because read receipts and read markers are -commonly updated at the same time, and therefore the client might wish -to save an extra HTTP call. Providing an `m.read` location performs the -same task as a request to `/receipt/m.read/$event:example.org`. +(`m.read` or `m.read.private`) location in the same operation as setting +the fully read marker location. This is because read receipts and read +markers are commonly updated at the same time, and therefore the client +might wish to save an extra HTTP call. Providing `m.read` and/or +`m.read.private` performs the same task as a request to +[`/receipt/{receiptType}/{eventId}`](#post_matrixclientv3roomsroomidreceiptreceipttypeeventid). {{% http-api spec="client-server" api="read_markers" %}} @@ -44,8 +48,9 @@ same task as a request to `/receipt/m.read/$event:example.org`. The server MUST prevent clients from setting `m.fully_read` directly in room account data. The server must additionally ensure that it treats -the presence of `m.read` in the `/read_markers` request the same as how -it would for a request to `/receipt/m.read/$event:example.org`. +the presence of `m.read` and `m.read.private` in the `/read_markers` +request the same as how it would for a request to +[`/receipt/{receiptType}/{eventId}`](#post_matrixclientv3roomsroomidreceiptreceipttypeeventid). Upon updating the `m.fully_read` event due to a request to `/read_markers`, the server MUST send the updated account data event diff --git a/content/client-server-api/modules/receipts.md b/content/client-server-api/modules/receipts.md index 1abd45a5..9f80ff60 100644 --- a/content/client-server-api/modules/receipts.md +++ b/content/client-server-api/modules/receipts.md @@ -4,10 +4,14 @@ type: module ### Receipts +{{< changed-in v="1.4" >}} Added private read receipts. + This module adds in support for receipts. These receipts are a form of -acknowledgement of an event. This module defines a single -acknowledgement: `m.read` which indicates that the user has read up to a -given event. +acknowledgement of an event. This module defines the `m.read` receipt +for indicating that the user has read up to a given event, and `m.read.private` +to achieve the same purpose without any other user being aware. Primarily, +`m.read.private` is intended to clear [notifications](#receiving-notifications) +without advertising read-up-to status to others. Sending a receipt for each event can result in sending large amounts of traffic to a homeserver. To prevent this from becoming a problem, @@ -59,12 +63,36 @@ following HTTP APIs. {{% http-api spec="client-server" api="receipts" %}} +##### Private read receipts + +{{% added-in v="1.4" %}} + +Some users would like to mark a room as read, clearing their [notification counts](#receiving-notifications), +but not give away the fact that they've read a particular message yet. To +achieve this, clients can send `m.read.private` receipts instead of `m.read` +to do exactly that: clear notifications and not broadcast the receipt to +other users. + +Servers MUST NOT send the `m.read.private` receipt to any other user than the +one which originally sent it. + +Between `m.read` and `m.read.private`, the receipt which is more "ahead" or +"recent" is used when determining the highest read-up-to mark. See the +[notifications](#receiving-notifications) section for more information on +how this affects notification counts. + +If a client sends an `m.read` receipt which is "behind" the `m.read.private` +receipt, other users will see that change happen but the sending user will +not have their notification counts rewound to that point in time. While +uncommon, it is considered valid to have an `m.read` (public) receipt lag +several messages behind the `m.read.private` receipt, for example. + #### Server behaviour For efficiency, receipts SHOULD be batched into one event per room before delivering them to clients. -Receipts are sent across federation as EDUs with type `m.receipt`. The +Some receipts are sent across federation as EDUs with type `m.receipt`. The format of the EDUs are: ``` @@ -80,7 +108,8 @@ format of the EDUs are: ``` These are always sent as deltas to previously sent receipts. Currently -only a single `` should be used: `m.read`. +only a single `` should be used: `m.read`. `m.read.private` +MUST NOT appear in this federated `m.receipt` EDU. #### Security considerations diff --git a/data/api/client-server/read_markers.yaml b/data/api/client-server/read_markers.yaml index ccfd0657..d5993f67 100644 --- a/data/api/client-server/read_markers.yaml +++ b/data/api/client-server/read_markers.yaml @@ -1,4 +1,5 @@ # Copyright 2018 New Vector Ltd +# Copyright 2022 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -56,6 +57,9 @@ paths: The event ID the read marker should be located at. The event MUST belong to the room. example: "$somewhere:example.org" + x-changedInMatrixVersion: + 1.4: | + This property is no longer required. "m.read": type: string description: |- @@ -63,11 +67,18 @@ paths: equivalent to calling `/receipt/m.read/$elsewhere:example.org` and is provided here to save that extra call. example: "$elsewhere:example.org" - required: ['m.fully_read'] + "m.read.private": + x-addedInMatrixVersion: "1.4" + type: string + description: |- + The event ID to set the *private* read receipt location at. This + equivalent to calling `/receipt/m.read.private/$elsewhere:example.org` + and is provided here to save that extra call. + example: "$elsewhere:example.org" responses: 200: description: |- - The read marker, and read receipt if provided, have been updated. + The read marker, and read receipt(s) if provided, have been updated. schema: type: object properties: {} diff --git a/data/api/client-server/receipts.yaml b/data/api/client-server/receipts.yaml index df720cbd..a4dabdd2 100644 --- a/data/api/client-server/receipts.yaml +++ b/data/api/client-server/receipts.yaml @@ -1,4 +1,5 @@ # Copyright 2016 OpenMarket Ltd +# Copyright 2022 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -46,10 +47,19 @@ paths: - in: path type: string name: receiptType - description: The type of receipt to send. + description: |- + The type of receipt to send. This can also be `m.fully_read` as an + alternative to [`/read_makers`](/client-server-api/#post_matrixclientv3roomsroomidread_markers). + + Note that `m.fully_read` does not appear under `m.receipt`: this endpoint + effectively calls `/read_markers` internally when presented with a receipt + type of `m.fully_read`. required: true x-example: "m.read" - enum: ["m.read"] + x-changedInMatrixVersion: + 1.4: | + Allow `m.read.private` receipts and `m.fully_read` markers to be set. + enum: ["m.read", "m.read.private", "m.fully_read"] - in: path type: string name: eventId diff --git a/data/event-schemas/examples/m.receipt.yaml b/data/event-schemas/examples/m.receipt.yaml index c52d8540..17e5a67e 100644 --- a/data/event-schemas/examples/m.receipt.yaml +++ b/data/event-schemas/examples/m.receipt.yaml @@ -7,6 +7,11 @@ "@rikj:jki.re": { "ts": 1436451550453 } + }, + "m.read.private": { + "@self:example.org": { + "ts": 1661384801651 + } } } } diff --git a/data/event-schemas/schema/m.receipt.yaml b/data/event-schemas/schema/m.receipt.yaml index 366aaf28..b9ec7e4e 100644 --- a/data/event-schemas/schema/m.receipt.yaml +++ b/data/event-schemas/schema/m.receipt.yaml @@ -1,49 +1,54 @@ -{ - "type": "object", - "title": "Receipt Event", - "description": "Informs the client of new receipts.", - "allOf": [{ - "$ref": "core-event-schema/event.yaml" - }], - "properties": { - "content": { - "type": "object", - "patternProperties": { - "^\\$": { - "type": "object", - "x-pattern": "$EVENT_ID", - "title": "Receipts", - "description": "The mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of the event being acknowledged and *not* an ID for the receipt itself.", - "properties": { - "m.read": { - "type": "object", - "title": "Users", - "description": "A collection of users who have sent `m.read` receipts for this event.", - "patternProperties": { - "^@": { - "type": "object", - "title": "Receipt", - "description": "The mapping of user ID to receipt. The user ID is the entity who sent this receipt.", - "x-pattern": "$USER_ID", - "properties": { - "ts": { - "type": "integer", - "format": "int64", - "description": "The timestamp the receipt was sent at." - } - } - } - } - } - } - } - }, - "additionalProperties": false - }, - "type": { - "type": "string", - "enum": ["m.receipt"] - } - }, - "required": ["type", "content"] -} +type: object +title: Receipt Event +description: Informs the client of new receipts. +x-changedInMatrixVersion: + 1.4: | + Added `m.read.private` receipts to the event's `content`. +allOf: + - $ref: "core-event-schema/event.yaml" +properties: + content: + type: object + patternProperties: + "^\\$": + type: object + x-pattern: "$EVENT_ID" + title: Receipts + description: |- + The mapping of event ID to a collection of receipts for this + event ID. The event ID is the ID of the event being acknowledged + and *not* an ID for the receipt itself. + properties: + "m.read": + type: object + title: Users + description: |- + A collection of users who have sent `m.read` receipts for + this event. + patternProperties: + "^@": &receiptUserMap + type: object + title: Receipt + description: |- + The mapping of user ID to receipt. The user ID is the + entity who sent this receipt. + x-pattern: "$USER_ID" + properties: + ts: + type: integer + format: int64 + description: The timestamp the receipt was sent at. + "m.read.private": + type: object + title: Own User + description: |- + Similar to `m.read`, the users who have sent `m.read.private` + receipts for this event. Due to the nature of private read + receipts, this should only ever have the current user's ID. + patternProperties: + "^@": *receiptUserMap + additionalProperties: false + type: + type: string + enum: ["m.receipt", "m.receipt.private"] +required: ["type", "content"] From 1c0101ce4c171a7c2e61c31c01c4ca800438612c Mon Sep 17 00:00:00 2001 From: Patrick Cloke Date: Fri, 16 Sep 2022 08:50:19 -0400 Subject: [PATCH 4/8] Put commas between enum values. --- changelogs/client_server/newsfragments/1240.clarification | 1 + layouts/partials/openapi/render-object-table.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/client_server/newsfragments/1240.clarification diff --git a/changelogs/client_server/newsfragments/1240.clarification b/changelogs/client_server/newsfragments/1240.clarification new file mode 100644 index 00000000..d88de5eb --- /dev/null +++ b/changelogs/client_server/newsfragments/1240.clarification @@ -0,0 +1 @@ +Clarify enum values by separating possible values with commas. diff --git a/layouts/partials/openapi/render-object-table.html b/layouts/partials/openapi/render-object-table.html index b49cb3e8..51271481 100644 --- a/layouts/partials/openapi/render-object-table.html +++ b/layouts/partials/openapi/render-object-table.html @@ -89,7 +89,7 @@ {{ if $required }}Required: {{end -}} {{ $property.description | markdownify -}} - {{ if eq $type "enum"}}

One of: {{ $property.enum }}.

{{ end -}} + {{ if eq $type "enum"}}

One of: [{{ delimit $property.enum ", " }}].

{{ end -}} {{ if (index $property "x-addedInMatrixVersion") }}{{ partial "added-in" (dict "v" (index $property "x-addedInMatrixVersion")) }}{{ end -}} {{ if (index $property "x-changedInMatrixVersion") }}{{ partial "changed-in" (dict "changes_dict" (index $property "x-changedInMatrixVersion")) }}{{ end -}} From 58e6900891595c07f1aeb9943c507bd7da1bf916 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff <1389908+richvdh@users.noreply.github.com> Date: Wed, 21 Sep 2022 14:41:28 +0100 Subject: [PATCH 5/8] Spec event edits (#1211) Per matrix-org/matrix-spec-proposals#2676 --- .../client_server/newsfragments/1211.feature | 1 + content/client-server-api/_index.md | 12 +- .../modules/event_replacements.md | 330 ++++++++++++++++++ 3 files changed, 333 insertions(+), 10 deletions(-) create mode 100644 changelogs/client_server/newsfragments/1211.feature create mode 100644 content/client-server-api/modules/event_replacements.md diff --git a/changelogs/client_server/newsfragments/1211.feature b/changelogs/client_server/newsfragments/1211.feature new file mode 100644 index 00000000..92f1bf1b --- /dev/null +++ b/changelogs/client_server/newsfragments/1211.feature @@ -0,0 +1 @@ +Add `m.replace` relations (event edits), as per [MSC2676](https://github.com/matrix-org/matrix-spec-proposals/pull/2676). diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index 3d80c849..b1b55159 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -1955,16 +1955,6 @@ rooms, or the relationship missing properties required by the schema below. Clie handling such invalid relationships should show the events independently of each other, optionally with an error message. -{{% boxes/note %}} -While this specification describes an `m.relates_to` object containing a `rel_type`, there -is not currently any relationship type which uses this structure. Replies, described below, -form their relationship outside of the `rel_type` as a legacy type of relationship. Future -versions of the specification might change replies to better match the relationship structures. - -Custom `rel_type`s can, and should, still use the schema described above for relevant -behaviour. -{{% /boxes/note %}} - `m.relates_to` is defined as follows: {{% definition path="api/client-server/definitions/m.relates_to" %}} @@ -1974,6 +1964,7 @@ behaviour. This specification describes the following relationship types: * [Rich replies](#rich-replies) (**Note**: does not use `rel_type`). +* [Event replacements](#event-replacements). #### Aggregations @@ -2643,3 +2634,4 @@ systems. {{< cs-module name="server_notices" >}} {{< cs-module name="moderation_policies" >}} {{< cs-module name="spaces" >}} +{{< cs-module name="event_replacements" >}} \ No newline at end of file diff --git a/content/client-server-api/modules/event_replacements.md b/content/client-server-api/modules/event_replacements.md new file mode 100644 index 00000000..52597abd --- /dev/null +++ b/content/client-server-api/modules/event_replacements.md @@ -0,0 +1,330 @@ +--- +type: module +--- + +### Event replacements + +{{% added-in v="1.4" %}} + +Event replacements, or "message edit events", are events that use an [event +relationship](#forming-relationships-between-events) +with a `rel_type` of `m.replace`, which indicates that the original event is +intended to be replaced. + +An example of a message edit event might look like this: + +```json +{ + "type": "m.room.message", + "content": { + "body": "* Hello! My name is bar", + "msgtype": "m.text", + "m.new_content": { + "body": "Hello! My name is bar", + "msgtype": "m.text" + }, + "m.relates_to": { + "rel_type": "m.replace", + "event_id": "$some_event_id" + } + }, + // ... other fields required by events +} +``` + +The `content` of the replacement must contain a `m.new_content` property which +defines the replacement `content`. The normal `content` properties (`body`, +`msgtype` etc.) provide a fallback for clients which do not understand +replacement events. + +`m.new_content` can include any properties that would normally be found in +an event's content property, such as `formatted_body` (see [`m.room.message` +`msgtypes`](#mroommessage-msgtypes)). + +#### Validity of replacement events + +There are a number of requirements on replacement events, which must be satisfied for the replacement to be considered valid: + + * As with all event relationships, the original event and replacement event + must have the same `room_id` (i.e. you cannot send an event in + one room and then an edited version in a different room). + + * The original event and replacement event must have the same `sender` + (i.e. you cannot edit someone else's messages). + + * The replacement and original events must have the same `type` (i.e. you + cannot change the original event's type). + + * The replacement and original events must not have a `state_key` property + (i.e. you cannot edit state events at all). + + * The original event must not, itself, have a `rel_type` of `m.replace` + (i.e. you cannot edit an edit — though you can send multiple edits for a + single original event). + + * The replacement event (once decrypted, if appropriate) must have an + `m.new_content` property. + +If any of these criteria are not satisfied, implementations should ignore the +replacement event (the content of the original should not be replaced, and the +edit should not be included in the server-side aggregation). + +Note that the [`msgtype`](#mroommessage-msgtypes) property of replacement +`m.room.message` events does *not* need to be the same as in the original event. For +example, it is legitimate to replace an `m.text` event with an `m.emote`. + +#### Editing encrypted events + +If the original event was [encrypted](#end-to-end-encryption), the replacement +should be too. In that case, `m.new_content` is placed in the content of the +encrypted payload. As with all event relationships, the `m.relates_to` property +must be sent in the unencrypted (cleartext) part of the event. + +For example, a replacement for an encrypted event might look like this: + +```json +{ + "type": "m.room.encrypted", + "content": { + "m.relates_to": { + "rel_type": "m.replace", + "event_id": "$some_event_id" + }, + "algorithm": "m.megolm.v1.aes-sha2", + "sender_key": "", + "device_id": "", + "session_id": "", + "ciphertext": "" + } + // irrelevant fields not shown +} +``` + +... and, once decrypted, the payload might look like this: + +```json +{ + "type": "m.room.", + "room_id": "!some_room_id", + "content": { + "body": "* Hello! My name is bar", + "msgtype": "m.text", + "m.new_content": { + "body": "Hello! My name is bar", + "msgtype": "m.text" + } + } +} +``` + +Note that: + + * There is no `m.relates_to` property in the encrypted payload. If there was, it would be ignored. + * There is no `m.new_content` property in the cleartext content of the `m.room.encrypted` event. As above, if there was then it would be ignored. + +{{% boxes/note %}} +The payload of an encrypted replacement event must be encrypted as normal, including +ratcheting any [Megolm](#mmegolmv1aes-sha2) session as normal. The original Megolm +ratchet entry should **not** be re-used. +{{% /boxes/note %}} + + +#### Applying `m.new_content` + +When applying a replacement, the `content` of the original event is treated as +being overwritten entirely by `m.new_content`, with the exception of `m.relates_to`, +which is left *unchanged*. Any `m.relates_to` property within `m.new_content` +is ignored. + +{{% boxes/note %}} +Note that server implementations must not *actually* overwrite +the original event's `content`: instead the server presents it as being overwritten +when it is served over the client-server API. See [Server-side replacement of content](#server-side-replacement-of-content) +below. +{{% /boxes/note %}} + +For example, given a pair of events: + +```json +{ + "event_id": "$original_event", + "type": "m.room.message", + "content": { + "body": "I really like cake", + "msgtype": "m.text", + "formatted_body": "I really like cake", + } +} +``` + +```json +{ + "event_id": "$edit_event", + "type": "m.room.message", + "content": { + "body": "* I really like *chocolate* cake", + "msgtype": "m.text", + "m.new_content": { + "body": "I really like *chocolate* cake", + "msgtype": "m.text", + "com.example.extension_property": "chocolate" + }, + "m.relates_to": { + "rel_type": "m.replace", + "event_id": "$original_event_id" + } + } +} +``` + +... then the end result is an event as shown below: + +```json +{ + "event_id": "$original_event", + "type": "m.room.message", + "content": { + "body": "I really like *chocolate* cake", + "msgtype": "m.text", + "com.example.extension_property": "chocolate" + } +} +``` + +Note that `formatted_body` is now absent, because it was absent in the +replacement event. + +#### Server behaviour + +##### Server-side aggregation of `m.replace` relationships + +Note that there can be multiple events with an `m.replace` relationship to a +given event (for example, if an event is edited multiple times). These should +be [aggregated](#aggregations) by the homeserver. + +The aggregation format of `m.replace` relationships gives the `event_id`, +`origin_server_ts`, and `sender` of the **most recent** replacement event. The +most recent event is determined by comparing `origin_server_ts`; if two or more +replacement events have identical `origin_server_ts`, the event with the +lexicographically largest `event_id` is treated as more recent. + +This aggregation is bundled under the `unsigned` property as `m.relations` for any +event that is the target of an `m.replace` relationship. For example: + +```json +{ + "event_id": "$original_event_id", + // irrelevant fields not shown + "unsigned": { + "m.relations": { + "m.replace": { + "event_id": "$latest_edit_event_id", + "origin_server_ts": 1649772304313, + "sender": "@editing_user:localhost" + } + } + } +} +``` + +If the original event is +[redacted](#redactions), any +`m.replace` relationship should **not** be bundled with it (whether or not any +subsequent replacements are themselves redacted). Note that this behaviour is +specific to the `m.replace` relationship. See also [redactions of edited +events](#redactions-of-edited-events) below. + +##### Server-side replacement of content + +Whenever an `m.replace` is to be bundled with an event as above, the server +should also modify the content of the original event according to the +`m.new_content` of the most recent replacement event (determined as above). + +An exception applies to [`GET /_matrix/client/v3/rooms/{roomId}/event/{eventId}`](#get_matrixclientv3roomsroomideventeventid), +which should return the unmodified event (though the relationship should still +be bundled, as described above). + +#### Client behaviour + +Clients can often ignore `m.replace` events, because any events returned +by the server to the client will be updated by the server to account for +subsequent edits. + +However, clients should apply the replacement themselves when the server is +unable to do so. This happens in the following situations: + + * The client has already received and stored the original event before the + message edit event arrives. + + * The original event (and hence its replacement) are encrypted. + +Client authors are reminded to take note of the requirements for [Validity of +message edit events](#validity-of-message-edit-events), and to ignore any +invalid edit events that are received. + +##### Permalinks + +When creating [links](/appendices/#uris) to events (also known as permalinks), +clients build links which reference the event that the creator of the permalink +is viewing at that point (which might be a message edit event). + +The client viewing the permalink should resolve this reference to the original +event, and then display the most recent version of that event. + +#### Redactions of edited events + +When an event using a `rel_type` of `m.replace` is [redacted](#redactions), it +removes that edit revision. This has little effect if there were subsequent +edits. However, if it was the most recent edit, the event is in effect +reverted to its content before the redacted edit. + +Redacting the *original* message in effect removes the message, including all +subsequent edits, from the visible timeline. In this situation, homeservers +will return an empty `content` for the original event as with any other +redacted event, and as +[above](#server-side-aggregation-of-mreplace-relationships) the replacement +events will not be bundled with the original event. Note that the subsequent edits are +not actually redacted themselves: they simply serve no purpose within the visible timeline. + +#### Edits of replies + +Some particular constraints apply to events which replace a +[reply](#rich-replies). In particular: + + * In contrast to the original reply, there should be no `m.in_reply_to` + property in the the `m.relates_to` object, since it would be redundant (see + [Applying `m.new_content`](#applying-mnew_content) above, which notes that + the original event's `m.relates_to` is preserved), as well as being contrary + to the spirit of the event relationships mechanism which expects only one + "parent" per event. + + * `m.new_content` should **not** contain any [reply + fallback](#fallbacks-for-rich-replies), + since it is assumed that any client which can handle edits can also display + replies natively. However, the `content` of the replacement event should provide + fallback content for clients which support neither rich replies nor edits. + +An example of an edit to a reply is as follows: + +```json +{ + "type": "m.room.message", + // irrelevant fields not shown + "content": { + "body": "> <@alice:example.org> question\n\n* reply", + "msgtype": "m.text", + "format": "org.matrix.custom.html", + "formatted_body": "
In reply to @alice:example.org
question
* reply", + "m.new_content": { + "body": "reply", + "msgtype": "m.text", + "format": "org.matrix.custom.html", + "formatted_body": "reply" + }, + "m.relates_to": { + "rel_type": "m.replace", + "event_id": "$original_reply_event" + } + } +} +``` From e7e376142e38c2559866998bb2d3f26f6bfb46b5 Mon Sep 17 00:00:00 2001 From: Andrew Morgan <1342360+anoadragon453@users.noreply.github.com> Date: Thu, 22 Sep 2022 11:25:32 +0100 Subject: [PATCH 6/8] Fix typos in the spec related to account data (#1243) --- .../newsfragments/1243.clarification | 1 + .../client-server-api/modules/account_data.md | 4 +- content/client-server-api/modules/push.md | 2 +- content/client-server-api/modules/secrets.md | 8 +-- data/api/client-server/account-data.yaml | 59 ++++++++++--------- 5 files changed, 38 insertions(+), 36 deletions(-) create mode 100644 changelogs/client_server/newsfragments/1243.clarification diff --git a/changelogs/client_server/newsfragments/1243.clarification b/changelogs/client_server/newsfragments/1243.clarification new file mode 100644 index 00000000..ca5f3aea --- /dev/null +++ b/changelogs/client_server/newsfragments/1243.clarification @@ -0,0 +1 @@ +Fix various typos throughout the specification. \ No newline at end of file diff --git a/content/client-server-api/modules/account_data.md b/content/client-server-api/modules/account_data.md index 2bb466f0..9926adc5 100644 --- a/content/client-server-api/modules/account_data.md +++ b/content/client-server-api/modules/account_data.md @@ -17,10 +17,10 @@ data with the same `type`. #### Events The client receives the account data as events in the `account_data` -sections of a `/sync`. +sections of a [`/sync`](#get_matrixclientv3sync) response. These events can also be received in a `/events` response or in the -`account_data` section of a room in `/sync`. `m.tag` events appearing in +`account_data` section of a room in a `/sync` response. `m.tag` events appearing in `/events` will have a `room_id` with the room the tags are for. #### Client Behaviour diff --git a/content/client-server-api/modules/push.md b/content/client-server-api/modules/push.md index 0ad035db..5cce9449 100644 --- a/content/client-server-api/modules/push.md +++ b/content/client-server-api/modules/push.md @@ -780,7 +780,7 @@ per-device using the APIs below. ##### Push Rules: Events When a user changes their push rules a `m.push_rules` event is sent to -all clients in the `account_data` section of their next `/sync` request. +all clients in the `account_data` section of their next [`/sync`](#get_matrixclientv3sync) request. The content of the event is the current push rules for the user. {{% event event="m.push_rules" %}} diff --git a/content/client-server-api/modules/secrets.md b/content/client-server-api/modules/secrets.md index 47de938a..6fe8586a 100644 --- a/content/client-server-api/modules/secrets.md +++ b/content/client-server-api/modules/secrets.md @@ -30,7 +30,7 @@ clients can access, depending on what keys are given to them. ##### Key storage Each key has an ID, and the description of the key is stored in the -user's account\_data using the event type +user's account data using the event type `m.secret_storage.key.[key ID]`. The contents of the account data for the key will include an `algorithm` property, which indicates the encryption algorithm used, as well as a `name` property, which is a @@ -51,7 +51,7 @@ Other properties depend on the encryption algorithm, and are described below. A key can be marked as the "default" key by setting the user's -account\_data with event type `m.secret_storage.default_key` to an +account data with event type `m.secret_storage.default_key` to an object that has the ID of the key as its `key` property. The default key will be used to encrypt all secrets that the user would expect to be available on all their clients. Unless the user specifies otherwise, @@ -71,8 +71,8 @@ default key. ##### Secret storage -Encrypted data is stored in the user's account\_data using the event -type defined by the feature that uses the data. The account\_data will +Encrypted data is stored in the user's account data using the event +type defined by the feature that uses the data. The account data will have an `encrypted` property that is a map from key ID to an object. The algorithm from the `m.secret_storage.key.[key ID]` data for the given key defines how the other properties are interpreted, though it's diff --git a/data/api/client-server/account-data.yaml b/data/api/client-server/account-data.yaml index 98bda9ee..57e78c71 100644 --- a/data/api/client-server/account-data.yaml +++ b/data/api/client-server/account-data.yaml @@ -29,11 +29,12 @@ securityDefinitions: paths: "/user/{userId}/account_data/{type}": put: - summary: Set some account_data for the user. + summary: Set some account data for the user. description: |- - Set some account_data for the client. This config is only visible to the user - that set the account_data. The config will be synced to clients in the - top-level `account_data`. + Set some account data for the client. This config is only visible to the user + that set the account data. The config will be available to clients through the + top-level `account_data` field in the homeserver response to + [/sync](#get_matrixclientv3sync). operationId: setAccountData security: - accessToken: [] @@ -43,7 +44,7 @@ paths: name: userId required: true description: |- - The ID of the user to set account_data for. The access token must be + The ID of the user to set account data for. The access token must be authorized to make requests for this user ID. x-example: "@alice:example.com" - in: path @@ -51,14 +52,14 @@ paths: name: type required: true description: |- - The event type of the account_data to set. Custom types should be + The event type of the account data to set. Custom types should be namespaced to avoid clashes. x-example: "org.example.custom.config" - in: body name: content required: true description: |- - The content of the account_data + The content of the account data. schema: type: object example: { @@ -66,7 +67,7 @@ paths: responses: 200: description: - The account_data was successfully added. + The account data was successfully added. examples: application/json: {} schema: @@ -107,10 +108,10 @@ paths: tags: - User data get: - summary: Get some account_data for the user. + summary: Get some account data for the user. description: |- - Get some account_data for the client. This config is only visible to the user - that set the account_data. + Get some account data for the client. This config is only visible to the user + that set the account data. operationId: getAccountData security: - accessToken: [] @@ -120,7 +121,7 @@ paths: name: userId required: true description: |- - The ID of the user to get account_data for. The access token must be + The ID of the user to get account data for. The access token must be authorized to make requests for this user ID. x-example: "@alice:example.com" - in: path @@ -128,7 +129,7 @@ paths: name: type required: true description: |- - The event type of the account_data to get. Custom types should be + The event type of the account data to get. Custom types should be namespaced to avoid clashes. x-example: "org.example.custom.config" responses: @@ -157,7 +158,7 @@ paths: examples: application/json: { "errcode": "M_NOT_FOUND", - "error": "Room account data not found." + "error": "Account data not found." } schema: $ref: "../client-server/definitions/errors/error.yaml" @@ -165,11 +166,11 @@ paths: - User data "/user/{userId}/rooms/{roomId}/account_data/{type}": put: - summary: Set some account_data for the user that is specific to a room. + summary: Set some account data for the user that is specific to a room. description: |- - Set some account_data for the client on a given room. This config is only - visible to the user that set the account_data. The config will be synced to - clients in the per-room `account_data`. + Set some account data for the client on a given room. This config is only + visible to the user that set the account data. The config will be delivered to + clients in the per-room entries via [/sync](#get_matrixclientv3sync). operationId: setAccountDataPerRoom security: - accessToken: [] @@ -179,7 +180,7 @@ paths: name: userId required: true description: |- - The ID of the user to set account_data for. The access token must be + The ID of the user to set account data for. The access token must be authorized to make requests for this user ID. x-example: "@alice:example.com" - in: path @@ -187,21 +188,21 @@ paths: name: roomId required: true description: |- - The ID of the room to set account_data on. + The ID of the room to set account data on. x-example: "!726s6s6q:example.com" - in: path type: string name: type required: true description: |- - The event type of the account_data to set. Custom types should be + The event type of the account data to set. Custom types should be namespaced to avoid clashes. x-example: "org.example.custom.room.config" - in: body name: content required: true description: |- - The content of the account_data + The content of the account data. schema: type: object example: { @@ -209,7 +210,7 @@ paths: responses: 200: description: - The account_data was successfully added. + The account data was successfully added. examples: application/json: {} schema: @@ -251,10 +252,10 @@ paths: tags: - User data get: - summary: Get some account_data for the user that is specific to a room. + summary: Get some account data for the user that is specific to a room. description: |- - Get some account_data for the client on a given room. This config is only - visible to the user that set the account_data. + Get some account data for the client on a given room. This config is only + visible to the user that set the account data. operationId: getAccountDataPerRoom security: - accessToken: [] @@ -264,7 +265,7 @@ paths: name: userId required: true description: |- - The ID of the user to set account_data for. The access token must be + The ID of the user to get account data for. The access token must be authorized to make requests for this user ID. x-example: "@alice:example.com" - in: path @@ -272,14 +273,14 @@ paths: name: roomId required: true description: |- - The ID of the room to get account_data for. + The ID of the room to get account data for. x-example: "!726s6s6q:example.com" - in: path type: string name: type required: true description: |- - The event type of the account_data to get. Custom types should be + The event type of the account data to get. Custom types should be namespaced to avoid clashes. x-example: "org.example.custom.room.config" responses: From 7ab3aecf29fc0fd1daba7842db1cf95461edde5e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 22 Sep 2022 16:04:50 +0100 Subject: [PATCH 7/8] Update profile.yaml (#1238) * Update profile.yaml * Create 1238.clarification * Update changelogs/client_server/newsfragments/1238.clarification Co-authored-by: Alexey Rusakov Co-authored-by: Alexey Rusakov --- changelogs/client_server/newsfragments/1238.clarification | 1 + data/api/client-server/profile.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelogs/client_server/newsfragments/1238.clarification diff --git a/changelogs/client_server/newsfragments/1238.clarification b/changelogs/client_server/newsfragments/1238.clarification new file mode 100644 index 00000000..3ccb2333 --- /dev/null +++ b/changelogs/client_server/newsfragments/1238.clarification @@ -0,0 +1 @@ +Fix various typos throughout the specification. diff --git a/data/api/client-server/profile.yaml b/data/api/client-server/profile.yaml index e8bdc4de..9865e020 100644 --- a/data/api/client-server/profile.yaml +++ b/data/api/client-server/profile.yaml @@ -195,7 +195,7 @@ paths: x-example: "@alice:example.com" responses: 200: - description: The avatar URL for this user. + description: The profile information for this user. examples: application/json: { "avatar_url": "mxc://matrix.org/SDGdghriugerRg", From 6c6c60284526b2b1dc52d076e5bc6a49236aae1a Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 26 Sep 2022 14:39:43 -0600 Subject: [PATCH 8/8] Clarify that refreshed access tokens don't invalidate the scope of txnid (#1236) --- .../client_server/newsfragments/1236.clarification | 1 + content/client-server-api/_index.md | 11 +++++++++-- .../definitions/client_event_without_room_id.yaml | 2 +- data/api/client-server/redaction.yaml | 2 +- data/api/client-server/room_send.yaml | 2 +- data/api/client-server/to_device.yaml | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 changelogs/client_server/newsfragments/1236.clarification diff --git a/changelogs/client_server/newsfragments/1236.clarification b/changelogs/client_server/newsfragments/1236.clarification new file mode 100644 index 00000000..36052275 --- /dev/null +++ b/changelogs/client_server/newsfragments/1236.clarification @@ -0,0 +1 @@ +Reinforce the relationship of refreshed access tokens to transaction IDs. \ No newline at end of file diff --git a/content/client-server-api/_index.md b/content/client-server-api/_index.md index b1b55159..b29aeb32 100644 --- a/content/client-server-api/_index.md +++ b/content/client-server-api/_index.md @@ -213,12 +213,19 @@ See the [Server Notices](#server-notices) module for more information. The client-server API typically uses `HTTP PUT` to submit requests with a client-generated transaction identifier. This means that these -requests are idempotent. The scope of a transaction identifier is a -particular access token. It **only** serves to identify new requests +requests are idempotent. It **only** serves to identify new requests from retransmits. After the request has finished, the `{txnId}` value should be changed (how is not specified; a monotonically increasing integer is recommended). +The scope of a transaction ID is a "client session", where that session +is identified by a particular access token. When [refreshing](#refreshing-access-tokens) +an access token, the transaction ID's scope is retained. This means that +if a client with token `A` uses `TXN1` as their transaction ID, refreshes +the token to `B`, and uses `TXN1` again it'll be assumed to be a duplicate +request and ignored. If the client logs out and back in between the `A` and +`B` tokens, `TXN1` could be used once for each. + Some API endpoints may allow or require the use of `POST` requests without a transaction ID. Where this is optional, the use of a `PUT` request is strongly recommended. diff --git a/data/api/client-server/definitions/client_event_without_room_id.yaml b/data/api/client-server/definitions/client_event_without_room_id.yaml index c4db8b0e..0decb6f3 100644 --- a/data/api/client-server/definitions/client_event_without_room_id.yaml +++ b/data/api/client-server/definitions/client_event_without_room_id.yaml @@ -94,7 +94,7 @@ properties: } transaction_id: description: | - The client-supplied transaction ID, for example, provided via + The client-supplied [transaction ID](/client-server-api/#transaction-identifiers), for example, provided via `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`, if the client being given the event is the same one which sent it. type: string diff --git a/data/api/client-server/redaction.yaml b/data/api/client-server/redaction.yaml index 37ff9741..8fa9cbef 100644 --- a/data/api/client-server/redaction.yaml +++ b/data/api/client-server/redaction.yaml @@ -62,7 +62,7 @@ paths: name: txnId type: string description: |- - The transaction ID for this event. Clients should generate a + The [transaction ID](/client-server-api/#transaction-identifiers) for this event. Clients should generate a unique ID; it will be used by the server to ensure idempotency of requests. required: true x-example: "37" diff --git a/data/api/client-server/room_send.yaml b/data/api/client-server/room_send.yaml index 28591a3a..e38d2ebd 100644 --- a/data/api/client-server/room_send.yaml +++ b/data/api/client-server/room_send.yaml @@ -58,7 +58,7 @@ paths: name: txnId type: string description: |- - The transaction ID for this event. Clients should generate an + The [transaction ID](/client-server-api/#transaction-identifiers) for this event. Clients should generate an ID unique across requests with the same access token; it will be used by the server to ensure idempotency of requests. required: true diff --git a/data/api/client-server/to_device.yaml b/data/api/client-server/to_device.yaml index fb0c1d37..266593e1 100644 --- a/data/api/client-server/to_device.yaml +++ b/data/api/client-server/to_device.yaml @@ -48,7 +48,7 @@ paths: name: txnId type: string description: |- - The transaction ID for this event. Clients should generate an + The [transaction ID](/client-server-api/#transaction-identifiers) for this event. Clients should generate an ID unique across requests with the same access token; it will be used by the server to ensure idempotency of requests. required: true