Merge branch 'master' into rav/is_token_grammar

pull/977/head
Richard van der Hoff 6 years ago
commit a544b68298

@ -0,0 +1,79 @@
# Copyright 2018 New Vector Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
swagger: '2.0'
info:
title: "Matrix Client-Server Read Marker API"
version: "1.0.0"
host: localhost:8008
schemes:
- https
- http
basePath: /_matrix/client/%CLIENT_MAJOR_VERSION%
consumes:
- application/json
produces:
- application/json
securityDefinitions:
$ref: definitions/security.yaml
paths:
"/rooms/{roomId}/read_markers":
post:
summary: Set the position of the read marker for a room.
description: |-
Sets the position of the read marker for a given room, and optionally
the read receipt's location.
operationId: setReadMarker
security:
- accessToken: []
parameters:
- in: path
type: string
name: roomId
description: The room ID to set the read marker in for the user.
required: true
x-example: "!somewhere:domain.com"
- in: body
name: body
description: The read marker and optional read receipt locations.
required: true
schema:
type: object
properties:
"m.fully_read":
type: string
description: |-
The event ID the read marker should be located at. The
event MUST belong to the room.
example: "$somewhere:domain.com"
"m.read":
type: string
description: |-
The event ID to set the read receipt location at. This is
equivalent to calling ``/receipt/m.read/$elsewhere:domain.com``
and is provided here to save that extra call.
example: "$elsewhere:domain.com"
required: ['m.fully_read']
responses:
200:
description: |-
The read marker, and read receipt if provided, have been updated.
schema:
type: object
properties: {}
429:
description: This request was rate-limited.
schema:
"$ref": "definitions/errors/rate_limited.yaml"
tags:
- Read Markers

@ -0,0 +1 @@
Add "rich replies" - a way for users to better represent the conversation thread they are referencing in their messages.

@ -0,0 +1 @@
POST ``/rooms/{roomId}/read_markers``

@ -0,0 +1 @@
Clarify and generalise the language used for describing pagination.

@ -0,0 +1,8 @@
{
"$ref": "core/event.json",
"type": "m.fully_read",
"room_id": "!somewhere:domain.com",
"content": {
"event_id": "$someplace:domain.com"
}
}

@ -18,8 +18,16 @@ properties:
description: Size of the image in bytes.
type: integer
thumbnail_url:
description: The URL to a thumbnail of the image.
description: |-
The URL to a thumbnail of the image. Only present if the
thumbnail is unencrypted.
type: string
thumbnail_file:
description: |-
Information on the encrypted thumbnail file, as specified in
|encrypted_files|_. Only present if the thumbnail is encrypted.
title: EncryptedFile
type: object
thumbnail_info:
allOf:
- $ref: thumbnail_info.yaml

@ -0,0 +1,29 @@
{
"type": "object",
"title": "Read Marker Location Event",
"description": "The current location of the user's read marker in a room. This event appears in the user's room account data for the room the marker is applicable for.",
"allOf": [{
"$ref": "core-event-schema/event.yaml"
}],
"properties": {
"content": {
"type": "object",
"properties": {
"event_id": {
"type": "string",
"description": "The event the user's read marker is located at in the room."
}
},
"required": ["event_id"]
},
"type": {
"type": "string",
"enum": ["m.fully_read"]
},
"room_id": {
"type": "string",
"description": "The room ID the read marker applies to."
}
},
"required": ["type", "room_id", "content"]
}

@ -27,12 +27,17 @@ properties:
- m.audio
type: string
url:
description: The URL to the audio clip.
description: Required if the file is not encrypted. The URL to the audio clip.
type: string
file:
description: |-
Required if the file is encrypted. Information on the encrypted
file, as specified in |encrypted_files|_.
title: EncryptedFile
type: object
required:
- msgtype
- body
- url
type: object
type:
enum:

@ -21,8 +21,16 @@ properties:
description: The size of the file in bytes.
type: integer
thumbnail_url:
description: The URL to the thumbnail of the file.
description: |-
The URL to the thumbnail of the file. Only present if the
thumbnail is unencrypted.
type: string
thumbnail_file:
description: |-
Information on the encrypted thumbnail file, as specified in
|encrypted_files|_. Only present if the thumbnail is encrypted.
title: EncryptedFile
type: object
thumbnail_info:
allOf:
- $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml
@ -34,12 +42,17 @@ properties:
- m.file
type: string
url:
description: The URL to the file.
description: Required if the file is unencrypted. The URL to the file.
type: string
file:
description: |-
Required if the file is encrypted. Information on the encrypted
file, as specified in |encrypted_files|_.
title: EncryptedFile
type: object
required:
- msgtype
- body
- url
- filename
type: object
type:

@ -17,12 +17,17 @@ properties:
- m.image
type: string
url:
description: The URL to the image.
description: Required if the file is unencrypted. The URL to the image.
type: string
file:
description: |-
Required if the file is encrypted. Information on the encrypted
file, as specified in |encrypted_files|_.
title: EncryptedFile
type: object
required:
- msgtype
- body
- url
type: object
type:
enum:

@ -19,8 +19,16 @@ properties:
type: object
properties:
thumbnail_url:
description: The URL to a thumbnail of the location being represented.
description: |-
The URL to the thumbnail of the file. Only present if the
thumbnail is unencrypted.
type: string
thumbnail_file:
description: |-
Information on the encrypted thumbnail file, as specified in
|encrypted_files|_. Only present if the thumbnail is encrypted.
title: EncryptedFile
type: object
thumbnail_info:
allOf:
- $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml

@ -27,8 +27,16 @@ properties:
description: The size of the video in bytes.
type: integer
thumbnail_url:
description: The URL to an image thumbnail of the video clip.
description: |-
The URL to an image thumbnail of the video clip. Only present if the
thumbnail is unencrypted.
type: string
thumbnail_file:
description: |-
Information on the encrypted thumbnail file, as specified in
|encrypted_files|_. Only present if the thumbnail is encrypted.
title: EncryptedFile
type: object
thumbnail_info:
allOf:
- $ref: core-event-schema/msgtype_infos/thumbnail_info.yaml
@ -40,12 +48,17 @@ properties:
- m.video
type: string
url:
description: The URL to the video clip.
description: Required if the file is unencrypted. The URL to the video clip.
type: string
file:
description: |-
Required if the file is encrypted. Information on the encrypted
file, as specified in |encrypted_files|_.
title: EncryptedFile
type: object
required:
- msgtype
- body
- url
type: object
type:
enum:

@ -318,6 +318,46 @@ table.citation td {
border-bottom: none;
}
table.colwidths-auto caption {
font-family: 'Inconsolata', monospace;
font-weight: 800;
font-size: 120%;
padding: 5px;
text-align: left;
margin-bottom: 2px;
}
ol, li {
margin: 0px 0px 0px 30px !important;
}
p.httpheaders {
font-weight: 800;
font-size: 120%;
padding: 5px;
text-align: left;
margin-bottom: 2px;
}
table.colwidths-auto {
width:100%;
margin-top: 20px;
}
table.colwidths-auto tr td:nth-child(1) {
width: 15%;
}
table.colwidths-auto tr td:nth-child(2) {
width: 15%;
font-family: 'Inconsolata', monospace;
}
table.colwidths-auto tr td:nth-child(3) {
width: 70%;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {

@ -273,6 +273,7 @@ table {
td[colspan]:not([colspan="1"]) {
background: #eeeeee;
text-transform: capitalize;
}
thead {

@ -6,8 +6,7 @@
{{common_event.desc}}
{% for table in common_event.tables %}
{{"``"+table.title+"``" if table.title else "" }}
{{ tables.paramtable(table.rows, ["Key", "Type", "Description"]) }}
{{ tables.paramtable(table.rows, ["Key", "Type", "Description"], (table.title or "")) }}
{% endfor %}

@ -5,16 +5,15 @@
{% if (event.typeof | length) %}
*{{event.typeof}}*
{{event.typeof_info}}
{{event.typeof_info | indent_block(4)}}
{% endif -%}
{{event.desc}}
{% for table in event.content_fields %}
{{"``"+table.title+"``" if table.title else "" }}
{{ tables.paramtable(table.rows, [(table.title or "Content") ~ " Key", "Type", "Description"]) }}
{{ tables.paramtable(table.rows, [(table.title or "Content") ~ " Key", "Type", "Description"], (table.title or "")) }}
{% endfor %}
Example{% if examples | length > 1 %}s{% endif %}:

@ -13,13 +13,16 @@
{{":Rate-limited: Yes." if endpoint.rate_limited else "" }}
{{":Requires auth: Yes." if endpoint.requires_auth else "" }}
.. class:: httpheaders
Request format:
{% if (endpoint.req_param_by_loc | length) %}
{{ tables.split_paramtable(endpoint.req_param_by_loc) }}
{% if (endpoint.req_body_tables) %}
{% for table in endpoint.req_body_tables -%}
{{"``"+table.title+"``" if table.title else "" }}
{{ tables.paramtable(table.rows) }}
{{ tables.paramtable(table.rows, caption=(table.title or "")) }}
{% endfor -%}
{% endif -%}
@ -28,22 +31,30 @@ Request format:
{% endif %}
{% if endpoint.res_headers is not none -%}
.. class:: httpheaders
Response headers:
{{ tables.paramtable(endpoint.res_headers.rows) }}
{% endif -%}
{% if endpoint.res_tables|length > 0 -%}
.. class:: httpheaders
Response format:
{% for table in endpoint.res_tables -%}
{{"``"+table.title+"``" if table.title else "" }}
{{ tables.paramtable(table.rows) }}
{{ tables.paramtable(table.rows, caption=(table.title or "")) }}
{% endfor %}
{% endif -%}
.. class:: httpheaders
Example request:
.. code:: http
@ -51,6 +62,9 @@ Example request:
{{endpoint.example.req | indent_block(2)}}
{% if endpoint.responses|length > 0 -%}
.. class:: httpheaders
Response{{"s" if endpoint.responses|length > 1 else "" }}:
{% endif -%}
@ -63,6 +77,8 @@ Response{{"s" if endpoint.responses|length > 1 else "" }}:
{% if res["example"] -%}
.. class:: httpheaders
Example
.. code:: json

@ -4,9 +4,8 @@
{{(4 + event.msgtype | length) * title_kind}}
{{event.desc | wrap(80)}}
{% for table in event.content_fields -%}
{{"``"+table.title+"``" if table.title else "" }}
{{ tables.paramtable(table.rows, [(table.title or "Content") ~ " Key", "Type", "Description"]) }}
{{ tables.paramtable(table.rows, [(table.title or "Content") ~ " Key", "Type", "Description"], (table.title or "")) }}
{% endfor %}
Example:

@ -8,8 +8,8 @@
#
# 'rows' is the list of parameters. Each row should be a TypeTableRow.
#}
{% macro paramtable(rows, titles=["Parameter", "Type", "Description"]) -%}
{{ split_paramtable({None: rows}, titles) }}
{% macro paramtable(rows, titles=["Parameter", "Type", "Description"], caption="") -%}
{{ split_paramtable({None: rows}, titles, caption) }}
{% endmacro %}
@ -21,7 +21,7 @@
# written for that location. This is used by the standard 'paramtable' macro.
#}
{% macro split_paramtable(rows_by_loc,
titles=["Parameter", "Type", "Description"]) -%}
titles=["Parameter", "Type", "Description"], caption="") -%}
{% set rowkeys = ['key', 'title', 'desc'] %}
{% set titlerow = {'key': titles[0], 'title': titles[1], 'desc': titles[2]} %}
@ -36,6 +36,9 @@
{% set fieldwidths = (([titlerow] + flatrows) |
fieldwidths(rowkeys[0:-1], [10, 10])) + [50] -%}
{{".. table:: "}}{{ caption }}
{{" :widths: auto"}}
{{""}}
{{ tableheader(fieldwidths) }}
{{ tablerow(fieldwidths, titlerow, rowkeys) }}
{{ tableheader(fieldwidths) }}
@ -59,7 +62,7 @@
# Write a table header row, for the given column widths
#}
{% macro tableheader(widths) -%}
{% for arg in widths -%}
{{" "}}{% for arg in widths -%}
{{"="*arg}} {% endfor -%}
{% endmacro %}
@ -71,7 +74,7 @@
# attributes of 'row' to look up for values to put in the columns.
#}
{% macro tablerow(widths, row, keys) -%}
{% for key in keys -%}
{{" "}}{% for key in keys -%}
{% set value=row[key] -%}
{% if not loop.last -%}
{# the first few columns need space after them -#}
@ -81,7 +84,7 @@
the preceding columns, plus the number of preceding columns (for the
separators)) -#}
{{ value | wrap(widths[loop.index0]) |
indent_block(widths[0:-1]|sum + loop.index0) -}}
indent_block(widths[0:-1]|sum + loop.index0 + 2) -}}
{% endif -%}
{% endfor -%}
{% endmacro %}
@ -93,10 +96,10 @@
# write a tablespan row. This is a single value which spans the entire table.
#}
{% macro tablespan(widths, value) -%}
{{value}}
{{" "}}{{value}}
{# we write a trailing space to stop the separator being misinterpreted
# as a header line. -#}
{{"-"*(widths|sum + widths|length -1)}} {% endmacro %}
{{" "}}{{"-"*(widths|sum + widths|length -1)}} {% endmacro %}

@ -138,9 +138,6 @@ Some requests have unique error codes:
:``M_INVALID_ROOM_STATE``:
Sent when the intial state given to the ``createRoom`` API is invalid.
:``M_BAD_PAGINATION``:
Encountered when specifying bad pagination query parameters.
:``M_THREEPID_IN_USE``:
Sent when a threepid given to an API cannot be used because the same threepid is already in use.
@ -1018,39 +1015,21 @@ Pagination is the process of dividing a dataset into multiple discrete pages.
Matrix makes use of pagination to allow clients to view extremely large datasets.
These datasets are not limited to events in a room (for example clients may want
to paginate a list of rooms in addition to events within those rooms). Regardless
of *what* is being paginated, there is a common underlying API which is used to
to give clients a consistent way of selecting subsets of a potentially changing
dataset. Requests pass in ``from``, ``to``, ``dir`` and ``limit`` parameters
which describe where to read from the stream. ``from`` and ``to`` are opaque
textual 'stream tokens' which describe the current position in the dataset.
The ``dir`` parameter is an enum representing the direction of events to return:
either ``f`` orwards or ``b`` ackwards. The response returns new ``start`` and
``end`` stream token values which can then be passed to subsequent requests to
continue pagination. Not all endpoints will make use of all the parameters
outlined here: see the specific endpoint in question for more information.
Pagination Request Query Parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Query parameters:
from:
$streamtoken - The opaque token to start streaming from.
to:
$streamtoken - The opaque token to end streaming at. Typically,
clients will not know the item of data to end at, so this will usually be
omitted.
limit:
integer - An integer representing the maximum number of items to
return.
dir:
f|b - The direction to return events in. Typically this is ``b`` to paginate
backwards in time.
'START' and 'END' are placeholder values used in these examples to describe the
start and end of the dataset respectively.
Unless specified, the default pagination parameters are ``from=START``,
``to=END``, without a limit set.
of what is being paginated, there is a common approach which is used to give
clients an easy way of selecting subsets of a potentially changing dataset. Each
endpoint that uses pagination may use different parameters. However the theme
among them is that they take a ``from`` and ``to`` token, and occasionally
a ``limit`` and ``dir``. Together, these parameters describe the position in a
data set, where ``from`` and ``to`` are known as "stream tokens" matching the
regular expression ``[a-zA-Z0-9.=_-]+``. If supported, the ``dir`` defines the
direction of events to return: either forwards (``f``) or backwards (``b``).
The response may contain tokens that can be used for retrieving results before
or after the returned set. These tokens may be called `start` or `prev_batch`
for retrieving the previous result set, or `end`, `next_batch` or `next_token`
for retrieving the next result set.
In the following examples, 'START' and 'END' are placeholders to signify the
start and end of the data sets respectively.
For example, if an endpoint had events E1 -> E15. The client wants the last 5
events and doesn't know any previous events::
@ -1067,8 +1046,8 @@ events and doesn't know any previous events::
Another example: a public room list has rooms R1 -> R17. The client is showing 5
rooms at a time on screen, and is on page 2. They want to
now show page 3 (rooms R11 -> 15)::
rooms at a time on screen, and is on page 2. They want to now show page 3 (rooms
R11 -> 15)::
S E
| 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | stream token
@ -1086,20 +1065,17 @@ token from the initial request was '9' which corresponded to R10. When the 2nd
request was made, R10 did not appear again, even though from=9 was specified. If
you know the token, you already have the data.
Pagination Response
~~~~~~~~~~~~~~~~~~~
Responses to pagination requests MUST follow the format::
{
"chunk": [ ... , Responses , ... ],
"start" : $streamtoken,
"end" : $streamtoken
}
Where $streamtoken is an opaque token which can be used in another query to
get the next set of results. The "start" and "end" keys can only be omitted if
the complete dataset is provided in "chunk".
Responses for pagination-capable endpoints SHOULD have a ``chunk`` array alongside
the applicable stream tokens to represent the result set.
In general, when the end of a result set is reached the applicable stream token
will be excluded from the response. For example, if a user was backwards-paginating
events in a room they'd eventually reach the first event in the room. In this scenario,
the ``prev_batch`` token would be excluded from the response. Some paginated
endpoints are open-ended in one direction, such as endpoints which expose an event
stream for an active room. In this case, it is not possible for the client to reach
the true "end" of the data set and therefore should always be presented with a token
to keep moving forwards.
.. _`filter`:

@ -39,3 +39,10 @@ Client Behaviour
----------------
{{account_data_cs_http_api}}
Server Behaviour
----------------
Servers MUST reject clients from setting account data for event types that
the server manages. Currently, this only includes `m.fully_read`_.

@ -224,6 +224,145 @@ process:
.. |device_lists| replace:: ``device_lists``
.. _`device_lists`: `device_lists_sync`_
Sending encrypted attachments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When encryption is enabled in a room, files should be uploaded encrypted on
the homeserver.
In order to achieve this, a client should generate a single-use 256-bit AES
key, and encrypt the file using AES-CTR. The counter should be 64-bit long,
starting at 0 and prefixed by a random 64-bit Initialization Vector (IV), which
together form a 128-bit unique counter block.
.. Warning::
An IV must never be used multiple times with the same key. This implies that
if there are multiple files to encrypt in the same message, typically an
image and its thumbnail, the files must not share both the same key and IV.
Then, the encrypted file can be uploaded to the homeserver.
The key and the IV must be included in the room event along with the resulting
``mxc://`` in order to allow recipients to decrypt the file. As the event
containing those will be Megolm encrypted, the server will never have access to
the decrypted file.
A hash of the ciphertext must also be included, in order to prevent the homeserver from
changing the file content.
A client should send the data as an encrypted ``m.room.message`` event, using
either ``m.file`` as the msgtype, or the appropriate msgtype for the file
type. The key is sent using the `JSON Web Key`_ format, with a `W3C
extension`_.
.. anchor for link from m.message api spec
.. |encrypted_files| replace:: End-to-end encryption
.. _encrypted_files:
Extensions to ``m.message`` msgtypes
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
This module adds ``file`` and ``thumbnail_file`` properties, of type
``EncryptedFile``, to ``m.message`` msgtypes that reference files, such as
`m.file`_ and `m.image`_, replacing the ``url`` and ``thumbnail_url``
properties.
.. todo: generate this from a swagger definition?
``EncryptedFile``
========= ================ =====================================================
Parameter Type Description
========= ================ =====================================================
url string **Required.** The URL to the file.
key JWK **Required.** A `JSON Web Key`_ object.
iv string **Required.** The Initialisation Vector used by
AES-CTR, encoded as unpadded base64.
hashes {string: string} **Required.** A map from an algorithm name to a hash
of the ciphertext, encoded as unpadded base64. Clients
should support the SHA-256 hash, which uses the key
``sha256``.
v string **Required.** Version of the encrypted attachments
protocol. Must be ``v2``.
========= ================ =====================================================
``JWK``
========= ========= ============================================================
Parameter Type Description
========= ========= ============================================================
key string **Required.** Key type. Must be ``oct``.
key_opts [string] **Required.** Key operations. Must at least contain
``encrypt`` and ``decrypt``.
alg string **Required.** Algorithm. Must be ``A256CTR``.
k string **Required.** The key, encoded as urlsafe unpadded base64.
ext boolean **Required.** Extractable. Must be ``true``. This is a
`W3C extension`_.
========= ========= ============================================================
Example:
.. code :: json
{
"content": {
"body": "something-important.jpg",
"file": {
"url": "mxc://domain.com/FHyPlCeYUSFFxlgbQYZmoEoe",
"mimetype": "image/jpeg",
"v": "v2",
"key": {
"alg": "A256CTR",
"ext": true,
"k": "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0",
"key_ops": ["encrypt","decrypt"],
"kty": "oct"
},
"iv": "w+sE15fzSc0AAAAAAAAAAA",
"hashes": {
"sha256": "fdSLu/YkRx3Wyh3KQabP3rd6+SFiKg5lsJZQHtkSAYA"
}
},
"info": {
"mimetype": "image/jpeg",
"h": 1536,
"size": 422018,
"thumbnail_file": {
"hashes": {
"sha256": "/NogKqW5bz/m8xHgFiH5haFGjCNVmUIPLzfvOhHdrxY"
},
"iv": "U+k7PfwLr6UAAAAAAAAAAA",
"key": {
"alg": "A256CTR",
"ext": true,
"k": "RMyd6zhlbifsACM1DXkCbioZ2u0SywGljTH8JmGcylg",
"key_ops": ["encrypt", "decrypt"],
"kty": "oct"
},
"mimetype": "image/jpeg",
"url": "mxc://domain.com/pmVJxyxGlmxHposwVSlOaEOv",
"v": "v2"
},
"thumbnail_info": {
"h": 768,
"mimetype": "image/jpeg",
"size": 211009,
"w": 432
},
"w": 864
},
"msgtype": "m.image"
},
"event_id": "$143273582443PhrSn:domain.com",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:domain.com",
"sender": "@example:domain.com",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}
Claiming one-time keys
~~~~~~~~~~~~~~~~~~~~~~
@ -583,6 +722,8 @@ Example response:
.. _curve25519: https://cr.yp.to/ecdh.html
.. _`Olm specification`: http://matrix.org/docs/spec/olm.html
.. _`Megolm specification`: http://matrix.org/docs/spec/megolm.html
.. _`JSON Web Key`: https://tools.ietf.org/html/rfc7517#appendix-A.3
.. _`W3C extension`: https://w3c.github.io/webcrypto/#iana-section-jwk
.. _`Signing JSON`: ../appendices.html#signing-json

@ -112,6 +112,13 @@ surrounding text due to Rich Text Editors. HTML included in events should otherw
such as having appropriate closing tags, appropriate attributes (considering the custom ones
defined in this specification), and generally valid structure.
A special tag, ``mx-reply``, may appear on rich replies (described below) and should be
allowed if, and only if, the tag appears as the very first tag in the ``formatted_body``.
The tag cannot be nested and cannot be located after another tag in the tree. Because the
tag contains HTML, an ``mx-reply`` is expected to have a partner closing tag and should
be treated similar to a ``div``. Clients that support rich replies will end up stripping
the tag and its contents and therefore may wish to exclude the tag entirely.
.. Note::
A future iteration of the specification will support more powerful and extensible
message formatting options, such as the proposal `MSC1225 <https://github.com/matrix-org/matrix-doc/issues/1225>`_.
@ -346,6 +353,192 @@ change unexpectedly.
an English-language implementation on them all? See
https://matrix.org/jira/browse/SPEC-425.
Forming relationships between events
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In some cases, events may wish to reference other events. This could be to form
a thread of messages for the user to follow along with, or to provide more context
as to what a particular event is describing. Currently, the only kind of relation
defined is a "rich reply" where a user may reference another message to create a
thread-like conversation.
Relationships are defined under an ``m.relates_to`` key in the event's ``content``.
If the event is of the type ``m.room.encrypted``, the ``m.relates_to`` key MUST NOT
be covered by the encryption and instead be put alongside the encryption information
held in the ``content``.
Rich replies
++++++++++++
Users may wish to reference another message when forming their own message, and
clients may wish to better embed the referenced message for the user to have a
better context for the conversation being had. This sort of embedding another
message in a message is known as a "rich reply", or occasionally just a "reply".
A rich reply is formed through use of an ``m.relates_to`` relation for ``m.in_reply_to``
where a single key, ``event_id``, is used to reference the event being replied to.
The referenced event ID SHOULD belong to the same room where the reply is being sent.
Clients should be cautious of the event ID belonging to another room, or being invalid
entirely. Rich replies can only be constructed in the form of ``m.room.message`` events
with a ``msgtype`` of ``m.text`` or ``m.notice``. Due to the fallback requirements, rich
replies cannot be constructed for types of ``m.emote``, ``m.file``, etc. Rich replies
may reference any other ``m.room.message`` event, however. Rich replies may reference
another event which also has a rich reply, infinitely.
An ``m.in_reply_to`` relationship looks like the following::
{
...
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "<body including fallback>",
"format": "org.matrix.custom.html",
"formatted_body": "<HTML including fallback>",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$another:event.com"
}
}
}
}
Fallbacks and event representation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Some clients may not have support for rich replies and therefore need a fallback
to use instead. Clients that do not support rich replies should render the event
as if rich replies were not special.
Clients that do support rich replies MUST provide the fallback format on replies,
and MUST strip the fallback before rendering the reply. Rich replies MUST have
a ``format`` of ``org.matrix.custom.html`` and therefore a ``formatted_body``
alongside the ``body`` and appropriate ``msgtype``. The specific fallback text
is different for each ``msgtype``, however the general format for the ``body`` is:
.. code-block:: text
> <@alice:example.org> This is the original body
This is where the reply goes
The ``formatted_body`` should use the following template:
.. code-block:: html
<mx-reply>
<blockquote>
<a href="https://matrix.to/#/!somewhere:domain.com/$event:domain.com">In reply to</a>
<a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a>
<br />
<!-- This is where the related event's HTML would be. -->
</blockquote>
</mx-reply>
This is where the reply goes.
If the related event does not have a ``formatted_body``, the event's ``body`` should
be considered after encoding any HTML special characters. Note that the ``href`` in
both of the anchors use a `matrix.to URI <../appendices.html#matrix-to-navigation>`_.
Stripping the fallback
``````````````````````
Clients which support rich replies MUST strip the fallback from the event before
rendering the event. This is because the text provided in the fallback cannot be
trusted to be an accurate representation of the event. After removing the fallback,
clients are recommended to represent the event referenced by ``m.in_reply_to``
similar to the fallback's representation, although clients do have creative freedom
for their user interface. Clients should prefer the ``formatted_body`` over the
``body``, just like with other ``m.room.message`` events.
To strip the fallback on the ``body``, the client should iterate over each line of
the string, removing any lines that start with the fallback prefix ("> ",
including the space, without quotes) and stopping when a line is encountered without
the prefix. This prefix is known as the "fallback prefix sequence".
To strip the fallback on the ``formatted_body``, the client should remove the
entirety of the ``mx-reply`` tag.
Fallback for ``m.text``, ``m.notice``, and unrecognised message types
`````````````````````````````````````````````````````````````````````
Using the prefix sequence, the first line of the related event's ``body`` should
be prefixed with the user's ID, followed by each line being prefixed with the fallback
prefix sequence. For example::
> <@alice:example.org> This is the first line
> This is the second line
This is the reply
The ``formatted_body`` uses the template defined earlier in this section.
Fallback for ``m.emote``
````````````````````````
Similar to the fallback for ``m.text``, each line gets prefixed with the fallback
prefix sequence. However an asterisk should be inserted before the user's ID, like
so::
> * <@alice:example.org> feels like today is going to be a great day
This is the reply
The ``formatted_body`` has a subtle difference for the template where the asterisk
is also inserted ahead of the user's ID:
.. code-block:: html
<mx-reply>
<blockquote>
<a href="https://matrix.to/#/!somewhere:domain.com/$event:domain.com">In reply to</a>
* <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a>
<br />
<!-- This is where the related event's HTML would be. -->
</blockquote>
</mx-reply>
This is where the reply goes.
Fallback for ``m.image``, ``m.video``, ``m.audio``, and ``m.file``
``````````````````````````````````````````````````````````````````
The related event's ``body`` would be a file name, which may not be very descriptive.
The related event should additionally not have a ``format`` or ``formatted_body``
in the ``content`` - if the event does have a ``format`` and/or ``formatted_body``,
those fields should be ignored. Because the filename alone may not be descriptive,
the related event's ``body`` should be considered to be ``"sent a file."`` such that
the output looks similar to the following::
> <@alice:example.org> sent a file.
This is the reply
.. code-block:: html
<mx-reply>
<blockquote>
<a href="https://matrix.to/#/!somewhere:domain.com/$event:domain.com">In reply to</a>
<a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a>
<br />
sent a file.
</blockquote>
</mx-reply>
This is where the reply goes.
For ``m.image``, the text should be ``"sent an image."``. For ``m.video``, the text
should be ``"sent a video."``. For ``m.audio``, the text should be ``"sent an audio file"``.
Server behaviour
----------------

@ -0,0 +1,67 @@
.. Copyright 2018 New Vector Ltd
..
.. Licensed under the Apache License, Version 2.0 (the "License");
.. you may not use this file except in compliance with the License.
.. You may obtain a copy of the License at
..
.. http://www.apache.org/licenses/LICENSE-2.0
..
.. Unless required by applicable law or agreed to in writing, software
.. distributed under the License is distributed on an "AS IS" BASIS,
.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
.. See the License for the specific language governing permissions and
.. limitations under the License.
Fully read markers
==================
.. _module:read-markers:
The history for a given room may be split into three sections: messages the
user has read (or indicated they aren't interested in them), messages the user
might have read some but not others, and messages the user hasn't seen yet.
The "fully read marker" (also known as a "read marker") marks the last event
of the first section, whereas the user's read receipt marks the last event of
the second section.
Events
------
The user's fully read marker is kept as an event in the room's `account data`_.
The event may be read to determine the user's current fully read marker location
in the room, and just like other account data events the event will be pushed down
the event stream when updated.
The fully read marker is kept under an ``m.fully_read`` event. If the event does
not exist on the user's account data, the fully read marker should be considered
to be the user's read receipt location.
{{m_fully_read_event}}
Client behaviour
----------------
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.
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 ``/receipts/m.read/$event:domain.com``.
{{read_markers_cs_http_api}}
Server behaviour
----------------
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 ``/receipts/m.read/$event:domain.com``.
Upon updating the ``m.fully_read`` event due to a request to ``/read_markers``,
the server MUST send the updated account data event through to the client via
the event stream (eg: ``/sync``), provided any applicable filters are also
satisfied.
.. _`account data`: #client-config

@ -45,6 +45,7 @@ groups: # reusable blobs of files when prefixed with 'group:'
- modules/voip_events.rst
- modules/typing_notifications.rst
- modules/receipts.rst
- modules/read_markers.rst
- modules/presence.rst
- modules/content_repo.rst
- modules/send_to_device.rst

Loading…
Cancel
Save