From e406bd94f63c1c0682ccccf46603f96843e6c48b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Mon, 12 Sep 2022 16:34:51 -0600 Subject: [PATCH] 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"]