Merge branch 'markjh/room_tags' into markjh/client_config
commit
e76068a2a6
@ -0,0 +1,68 @@
|
|||||||
|
swagger: '2.0'
|
||||||
|
info:
|
||||||
|
title: "Matrix Client-Server v1 Room Inviting API"
|
||||||
|
version: "1.0.0"
|
||||||
|
host: localhost:8008
|
||||||
|
schemes:
|
||||||
|
- https
|
||||||
|
- http
|
||||||
|
basePath: /_matrix/client/api/v1
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
securityDefinitions:
|
||||||
|
accessToken:
|
||||||
|
type: apiKey
|
||||||
|
description: The user_id or application service access_token
|
||||||
|
name: access_token
|
||||||
|
in: query
|
||||||
|
paths:
|
||||||
|
"/rooms/{roomId}/join":
|
||||||
|
post:
|
||||||
|
summary: Start the requesting user participating in a particular room.
|
||||||
|
description: |-
|
||||||
|
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
|
||||||
|
allowed to see all current state events in the room, and all subsequent
|
||||||
|
events associated with the room until the user leaves the room.
|
||||||
|
|
||||||
|
After a user has joined a room, the room will appear as an entry in the
|
||||||
|
response of the |initialSync| API.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room identifier or room alias to join.
|
||||||
|
required: true
|
||||||
|
x-example: "#monkeys:matrix.org"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: |-
|
||||||
|
The room has been joined.
|
||||||
|
|
||||||
|
The joined room ID must be returned in the ``room_id`` field.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{"room_id": "!d41d8cd:matrix.org"}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
403:
|
||||||
|
description: |-
|
||||||
|
You do not have permission to join the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejection are:
|
||||||
|
|
||||||
|
- The room is invite-only and the user was not invited.
|
||||||
|
- The user has been banned from the room.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."}
|
||||||
|
429:
|
||||||
|
description: This request was rate-limited.
|
||||||
|
schema:
|
||||||
|
"$ref": "definitions/error.yaml"
|
||||||
|
x-alias:
|
||||||
|
canonical-link: "post-matrix-client-api-v1-rooms-roomid-join"
|
||||||
|
aliases:
|
||||||
|
- /join/{roomId}
|
@ -0,0 +1,92 @@
|
|||||||
|
swagger: '2.0'
|
||||||
|
info:
|
||||||
|
title: "Matrix Client-Server v1 Room Leaving API"
|
||||||
|
version: "1.0.0"
|
||||||
|
host: localhost:8008
|
||||||
|
schemes:
|
||||||
|
- https
|
||||||
|
- http
|
||||||
|
basePath: /_matrix/client/api/v1
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
securityDefinitions:
|
||||||
|
accessToken:
|
||||||
|
type: apiKey
|
||||||
|
description: The user_id or application service access_token
|
||||||
|
name: access_token
|
||||||
|
in: query
|
||||||
|
paths:
|
||||||
|
"/rooms/{roomId}/leave":
|
||||||
|
post:
|
||||||
|
summary: Stop the requesting user participating in a particular room.
|
||||||
|
description: |-
|
||||||
|
This API stops a user participating in a particular room.
|
||||||
|
|
||||||
|
If the user was already in the room, they will no longer be able to see
|
||||||
|
new events in the room. If the room requires an invite to join, they
|
||||||
|
will need to be re-invited before they can re-join.
|
||||||
|
|
||||||
|
If the user was invited to the room, but had not joined, this call
|
||||||
|
serves to reject the invite.
|
||||||
|
|
||||||
|
The user will still be allowed to retrieve history from the room which
|
||||||
|
they were previously allowed to see.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room identifier to leave.
|
||||||
|
required: true
|
||||||
|
x-example: "!nkl290a:matrix.org"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: |-
|
||||||
|
The room has been left.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
429:
|
||||||
|
description: This request was rate-limited.
|
||||||
|
schema:
|
||||||
|
"$ref": "definitions/error.yaml"
|
||||||
|
"/rooms/{roomId}/forget":
|
||||||
|
post:
|
||||||
|
summary: Stop the requesting user remembering about a particular room.
|
||||||
|
description: |-
|
||||||
|
This API stops a user remembering about a particular room.
|
||||||
|
|
||||||
|
In general, history is a first class citizen in Matrix. After this API
|
||||||
|
is called, however, a user will no longer be able to retrieve history
|
||||||
|
for this room. If all users on a homeserver forget a room, the room is
|
||||||
|
eligible for deletion from that homeserver.
|
||||||
|
|
||||||
|
If the user is currently joined to the room, they will implicitly leave
|
||||||
|
the room as part of this API call.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room identifier to forget.
|
||||||
|
required: true
|
||||||
|
x-example: "!au1ba7o:matrix.org"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: |-
|
||||||
|
The room has been forgotten.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
429:
|
||||||
|
description: This request was rate-limited.
|
||||||
|
schema:
|
||||||
|
"$ref": "definitions/error.yaml"
|
@ -1 +0,0 @@
|
|||||||
../../../event-schemas/schema/v1/core-event-schema
|
|
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"title": "Event",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "EventContent",
|
||||||
|
"description": "The content of this event. The fields in this object will vary depending on the type of event."
|
||||||
|
},
|
||||||
|
"origin_server_ts": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "Timestamp in milliseconds on originating homeserver when this event was sent."
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The MXID of the user who sent this event."
|
||||||
|
},
|
||||||
|
"state_key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional. This key will only be present for state events. A unique key which defines the overwriting semantics for this piece of room state."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The type of event."
|
||||||
|
},
|
||||||
|
"unsigned": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "Unsigned",
|
||||||
|
"description": "Information about this event which was not sent by the originating homeserver",
|
||||||
|
"properties": {
|
||||||
|
"age": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"description": "Time in milliseconds since the event was sent."
|
||||||
|
},
|
||||||
|
"prev_content": {
|
||||||
|
"title": "EventContent",
|
||||||
|
"type": "object",
|
||||||
|
"description": "Optional. The previous ``content`` for this state. This will be present only for state events appearing in the ``timeline``. If this is not a state event, or there is no previous content, this key will be missing."
|
||||||
|
},
|
||||||
|
"replaces_state": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional. The event_id of the previous event for this state. This will be present only for state events appearing in the ``timeline``. If this is not a state event, or there is no previous content, this key will be missing."
|
||||||
|
},
|
||||||
|
"transaction_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional. The transaction ID set when this message was sent. This key will only be present for message events sent by the device calling this API."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"events": {
|
|
||||||
"type": "array",
|
|
||||||
"description": "List of event ids",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"allOf": [{"$ref":"definitions/room_event_batch.json"}],
|
"allOf": [{"$ref":"definitions/event_batch.json"}],
|
||||||
"properties": {
|
"properties": {
|
||||||
"limited": {
|
"limited": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "Whether there are more events on the server"
|
"description": "True if the number of events returned was limited by the ``limit`` on the filter"
|
||||||
},
|
},
|
||||||
"prev_batch": {
|
"prev_batch": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "If the batch was limited then this is a token that can be supplied to the server to retrieve more events"
|
"description": "If the batch was limited then this is a token that can be supplied to the server to retrieve earlier events"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
Goals of Key-Distribution in Matrix
|
||||||
|
===================================
|
||||||
|
|
||||||
|
* No Central Authority: Users should not need to trust a central authority
|
||||||
|
when determining the authenticity of keys.
|
||||||
|
|
||||||
|
* Easy to Add New Devices: It should be easy for a user to start using a
|
||||||
|
new device.
|
||||||
|
|
||||||
|
* Possible to discover MITM: It should be possible for a user to determine if
|
||||||
|
they are being MITM.
|
||||||
|
|
||||||
|
* Lost Devices: It should be possible for a user to recover if they lose all
|
||||||
|
their devices.
|
||||||
|
|
||||||
|
* No Copying Keys: Keys should be per device and shouldn't leave the device
|
||||||
|
they were created on.
|
||||||
|
|
||||||
|
A Possible Mechanism for Key Distribution
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
Basic API for setting up keys on a server:
|
||||||
|
|
||||||
|
https://github.com/matrix-org/matrix-doc/pull/24
|
||||||
|
|
||||||
|
Client shouldn't trust the keys unless they have been verified, e.g by
|
||||||
|
comparing fingerprints.
|
||||||
|
|
||||||
|
If a user adds a new device it should some yet to be specified protocol
|
||||||
|
communicate with an old device and obtain a cross-signature from the old
|
||||||
|
device for its public key.
|
||||||
|
|
||||||
|
The new device can then present the cross-signed key to all the devices
|
||||||
|
that the user is in conversations with. Those devices should then include
|
||||||
|
the new device into those conversations.
|
||||||
|
|
||||||
|
If the user cannot cross-sign the new key, e.g. because their old device
|
||||||
|
is lost or stolen. Then they will need to reauthenticate their conversations
|
||||||
|
out of band, e.g by comparing fingerprints.
|
||||||
|
|
||||||
|
|
||||||
|
Goals of End-to-end encryption in Matrix
|
||||||
|
========================================
|
||||||
|
|
||||||
|
* Access to Chat History: Users should be able to see the history of a
|
||||||
|
conversation on a new device. User should be able to control who can
|
||||||
|
see their chat history and how much of the chat history they can see.
|
||||||
|
|
||||||
|
* Forward Secrecy of Discarded Chat History: Users should be able to discard
|
||||||
|
history from their device, once they have discarded the history it should be
|
||||||
|
impossible for an adversary to recover that history.
|
||||||
|
|
||||||
|
* Forward Secrecy of Future Messages: Users should be able to recover from
|
||||||
|
disclosure of the chat history on their device.
|
||||||
|
|
||||||
|
* Deniablity of Chat History: It should not be possible to prove to a third
|
||||||
|
party that a given user sent a message.
|
||||||
|
|
||||||
|
* Authenticity of Chat History: It should be possible to prove amoungst
|
||||||
|
the members of a chat that a message sent by a user was authored by that
|
||||||
|
user.
|
||||||
|
|
||||||
|
|
||||||
|
Bonus Goals:
|
||||||
|
|
||||||
|
* Traffic Analysis: It would be nice if the protocol was resilient to traffic
|
||||||
|
or metadata analysis. However it's not something we want to persue if it
|
||||||
|
harms the usability of the protocol. It might be cool if there was a
|
||||||
|
way for the user to could specify the trade off between performance and
|
||||||
|
resilience to traffic analysis that they wanted.
|
||||||
|
|
||||||
|
|
||||||
|
A Possible Design for Group Chat using Olm
|
||||||
|
==========================================
|
||||||
|
|
||||||
|
Protecting the secrecy of history
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
Each message sent by a client has a 32-bit counter. This counter increments
|
||||||
|
by one for each message sent by the client. This counter is used to advance a
|
||||||
|
ratchet. The ratchet is split into a vector four 256-bit values,
|
||||||
|
:math:`R_{n,j}` for :math:`j \in {0,1,2,3}`. The ratchet can be advanced as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
\begin{align}
|
||||||
|
R_{2^24n,0} &= H_1\left(R_{2^24(i-1),0}\right) \\
|
||||||
|
R_{2^24n,1} &= H_2\left(R_{2^24(i-1),0}\right) \\
|
||||||
|
R_{2^16n,1} &= H_1\left(R_{2^16(i-1),1}\right) \\
|
||||||
|
R_{2^16n,2} &= H_2\left(R_{2^16(i-1),1}\right) \\
|
||||||
|
R_{2^8i,2} &= H_1\left(R_{2^8(i-1),2}\right) \\
|
||||||
|
R_{2^8i,3} &= H_2\left(R_{2^8(i-1),2}\right) \\
|
||||||
|
R_{i,3} &= H_1\left(R_{(i-1),3}\right)
|
||||||
|
\end{align}
|
||||||
|
|
||||||
|
Where :math:`H_1` and :math:`H_2` are different hash functions. For example
|
||||||
|
:math:`H_1` could be :math:`HMAC\left(X,\text{"\textbackslash x01"}\right)` and
|
||||||
|
:math:`H_2` could be :math:`HMAC\left(X,\text{"\textbackslash x02"}\right)`.
|
||||||
|
|
||||||
|
So every :math:`2^24` iterations :math:`R_{n,1}` is reseeded from :math:`R_{n,0}`.
|
||||||
|
Every :math:`2^16` iterations :math:`R_{n,2}` is reseeded from :math:`R_{n,1}`.
|
||||||
|
Every :math:`2^8` iterations :math:`R_{n,3}` is reseeded from :math:`R_{n,2}`.
|
||||||
|
|
||||||
|
This scheme allows the ratchet to be advanced an arbitrary amount forwards
|
||||||
|
while needing only 1024 hash computations.
|
||||||
|
|
||||||
|
This the value of the ratchet is hashed to generate the keys used to encrypt
|
||||||
|
each mesage.
|
||||||
|
|
||||||
|
A client can decrypt chat history onwards from the earliest value of the
|
||||||
|
ratchet it is aware of. But cannot decrypt history from before that point
|
||||||
|
without reversing the hash function.
|
||||||
|
|
||||||
|
This allows a client to share its ability to decrypt chat history with another
|
||||||
|
from a point in the conversation onwards by giving a copy of the ratchet at
|
||||||
|
that point in the conversation.
|
||||||
|
|
||||||
|
A client can discard history by advancing a ratchet to beyond the last message
|
||||||
|
they want to discard and then forgetting all previous values of the ratchet.
|
||||||
|
|
||||||
|
Proving and denying the authenticity of history
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Client sign the messages they send using a Ed25519 key generated per
|
||||||
|
conversation. That key, along with the ratchet key, is distributed
|
||||||
|
to other clients using 1:1 olm ratchets. Those 1:1 ratchets are started using
|
||||||
|
Triple Diffie-Hellman which provides authenticity of the messages to the
|
||||||
|
participants and deniability of the messages to third parties. Therefore
|
||||||
|
any keys shared over those keys inherit the same levels of deniability and
|
||||||
|
authenticity.
|
||||||
|
|
||||||
|
Protecting the secrecy of future messages
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
A client would need to generate new keys if it wanted to prevent access to
|
||||||
|
messages beyond a given point in the conversation. It must generate new keys
|
||||||
|
whenever someone leaves the room. It should generate new keys periodically
|
||||||
|
anyway.
|
||||||
|
|
||||||
|
The frequency of key generation in a large room may need to be restricted to
|
||||||
|
keep the frequency of messages broadcast over the individual 1:1 channels
|
||||||
|
low.
|
@ -0,0 +1,285 @@
|
|||||||
|
WebSockets API
|
||||||
|
==============
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
------------
|
||||||
|
This document is a proposal for a WebSockets-based client-server API. It is not
|
||||||
|
intended to replace the REST API, but rather to complement it and provide an
|
||||||
|
alternative interface for certain operations.
|
||||||
|
|
||||||
|
The primary goal is to offer a more efficient interface than the REST API: by
|
||||||
|
using a bidirectional protocol such as WebSockets we can avoid the overheads
|
||||||
|
involved in long-polling (SSL negotiation, HTTP headers, etc). In doing so we
|
||||||
|
will reduce the latency between server and client by allowing the server to
|
||||||
|
send events as soon as they arrive, rather than having to wait for a poll from
|
||||||
|
the client.
|
||||||
|
|
||||||
|
Handshake
|
||||||
|
---------
|
||||||
|
1. Instead of calling ``/sync``, the client makes a websocket request to
|
||||||
|
``/_matrix/client/rN/stream``, passing the query parameters ``access_token``
|
||||||
|
and ``since``, and optionally ``filter`` - all of which have the same
|
||||||
|
meaning as for ``/sync``.
|
||||||
|
|
||||||
|
* The client sets the ``Sec-WebSocket-Protocol`` to ``m.json``. (Servers may
|
||||||
|
offer alternative encodings; at present only the JSON encoding is
|
||||||
|
specified but in future we will specify alternative encodings.)
|
||||||
|
|
||||||
|
#. The server returns the websocket handshake; the socket is then connected.
|
||||||
|
|
||||||
|
If the server does not return a valid websocket handshake, this indicates that
|
||||||
|
the server or an intermediate proxy does not support WebSockets. In this case,
|
||||||
|
the client should fall back to polling the ``/sync`` REST endpoint.
|
||||||
|
|
||||||
|
Example
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Client request:
|
||||||
|
|
||||||
|
.. code:: http
|
||||||
|
|
||||||
|
GET /_matrix/client/v2_alpha/stream?access_token=123456&since=s72594_4483_1934 HTTP/1.1
|
||||||
|
Host: matrix.org
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: Upgrade
|
||||||
|
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
|
||||||
|
Sec-WebSocket-Protocol: m.json
|
||||||
|
Sec-WebSocket-Version: 13
|
||||||
|
Origin: https://matrix.org
|
||||||
|
|
||||||
|
Server response:
|
||||||
|
|
||||||
|
.. code:: http
|
||||||
|
|
||||||
|
HTTP/1.1 101 Switching Protocols
|
||||||
|
Upgrade: websocket
|
||||||
|
Connection: Upgrade
|
||||||
|
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
|
||||||
|
Sec-WebSocket-Protocol: m.json
|
||||||
|
|
||||||
|
|
||||||
|
Update Notifications
|
||||||
|
--------------------
|
||||||
|
Once the socket is connected, the server begins streaming updates over the
|
||||||
|
websocket. The server sends Update notifications about new messages or state
|
||||||
|
changes. To make it easy for clients to parse, Update notifications have the
|
||||||
|
same structure as the response to ``/sync``: an object with the following
|
||||||
|
members:
|
||||||
|
|
||||||
|
============= ========== ===================================================
|
||||||
|
Key Type Description
|
||||||
|
============= ========== ===================================================
|
||||||
|
next_batch string The batch token to supply in the ``since`` param of
|
||||||
|
the next /sync request. This is not required for
|
||||||
|
streaming of events over the WebSocket, but is
|
||||||
|
provided so that clients can reconnect if the
|
||||||
|
socket is disconnected.
|
||||||
|
presence Presence The updates to the presence status of other users.
|
||||||
|
rooms Rooms Updates to rooms.
|
||||||
|
============= ========== ===================================================
|
||||||
|
|
||||||
|
Example
|
||||||
|
~~~~~~~
|
||||||
|
Message from the server:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"next_batch": "s72595_4483_1934",
|
||||||
|
"presence": {
|
||||||
|
"events": []
|
||||||
|
},
|
||||||
|
"rooms": {
|
||||||
|
"join": {},
|
||||||
|
"invite": {},
|
||||||
|
"leave": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Client-initiated operations
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
The client can perform certain operations by sending a websocket message to
|
||||||
|
the server. Such a "Request" message should be a JSON-encoded object with
|
||||||
|
the following members:
|
||||||
|
|
||||||
|
============= ========== ===================================================
|
||||||
|
Key Type Description
|
||||||
|
============= ========== ===================================================
|
||||||
|
id string A unique identifier for this request
|
||||||
|
method string Specifies the name of the operation to be
|
||||||
|
performed; see below for available operations
|
||||||
|
param object The parameters for the requested operation.
|
||||||
|
============= ========== ===================================================
|
||||||
|
|
||||||
|
The server responds to a client Request with a Response message. This is a
|
||||||
|
JSON-encoded object with the following members:
|
||||||
|
|
||||||
|
============= ========== ===================================================
|
||||||
|
Key Type Description
|
||||||
|
============= ========== ===================================================
|
||||||
|
id string The same as the value in the corresponding Request
|
||||||
|
object. The presence of the ``id`` field
|
||||||
|
distinguishes a Response message from an Update
|
||||||
|
notification.
|
||||||
|
result object On success, the results of the request.
|
||||||
|
error object On error, an object giving the resons for the
|
||||||
|
error. This has the same structure as the "standard
|
||||||
|
error response" for the Matrix API: an object with
|
||||||
|
the fields ``errcode`` and ``error``.
|
||||||
|
============= ========== ===================================================
|
||||||
|
|
||||||
|
Request methods
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
It is not intended that all operations which are available via the REST API
|
||||||
|
will be available via the WebSockets API, but a few simple, common operations
|
||||||
|
will be exposed. The initial operations will be as follows.
|
||||||
|
|
||||||
|
``ping``
|
||||||
|
^^^^^^^^
|
||||||
|
This is a no-op which clients may use to keep their connection alive.
|
||||||
|
|
||||||
|
The request ``params`` and the response ``result`` should be empty.
|
||||||
|
|
||||||
|
``send``
|
||||||
|
^^^^^^^^
|
||||||
|
Send a message event to a room. The parameters are as follows:
|
||||||
|
|
||||||
|
============= ========== ===================================================
|
||||||
|
Parameter Type Description
|
||||||
|
============= ========== ===================================================
|
||||||
|
room_id string **Required.** The room to send the event to
|
||||||
|
event_type string **Required.** The type of event to send.
|
||||||
|
content object **Required.** The content of the event.
|
||||||
|
============= ========== ===================================================
|
||||||
|
|
||||||
|
The result is as follows:
|
||||||
|
|
||||||
|
============= ========== ===================================================
|
||||||
|
Key Type Description
|
||||||
|
============= ========== ===================================================
|
||||||
|
event_id string A unique identifier for the event.
|
||||||
|
============= ========== ===================================================
|
||||||
|
|
||||||
|
The ``id`` from the Request message is used as the transaction ID by the
|
||||||
|
server.
|
||||||
|
|
||||||
|
``state``
|
||||||
|
^^^^^^^^^
|
||||||
|
Update the state on a room.
|
||||||
|
|
||||||
|
============= ========== ===================================================
|
||||||
|
Parameter Type Description
|
||||||
|
============= ========== ===================================================
|
||||||
|
room_id string **Required.** The room to set the state in
|
||||||
|
event_type string **Required.** The type of event to send.
|
||||||
|
state_key string **Required.** The state_key for the state to send.
|
||||||
|
content object **Required.** The content of the event.
|
||||||
|
============= ========== ===================================================
|
||||||
|
|
||||||
|
The result is as follows:
|
||||||
|
|
||||||
|
============= ========== ===================================================
|
||||||
|
Key Type Description
|
||||||
|
============= ========== ===================================================
|
||||||
|
event_id string A unique identifier for the event.
|
||||||
|
============= ========== ===================================================
|
||||||
|
|
||||||
|
|
||||||
|
Example
|
||||||
|
~~~~~~~
|
||||||
|
Client request:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "12345",
|
||||||
|
"method": "send",
|
||||||
|
"params": {
|
||||||
|
"room_id": "!d41d8cd:matrix.org",
|
||||||
|
"event_type": "m.room.message",
|
||||||
|
"content": {
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "hello"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Server response:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "12345",
|
||||||
|
"result": {
|
||||||
|
"event_id": "$66697273743031:matrix.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Alternative server response, in case of error:
|
||||||
|
|
||||||
|
.. code:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "12345",
|
||||||
|
"error": {
|
||||||
|
"errcode": "M_MISSING_PARAM",
|
||||||
|
"error": "Missing parameter: event_type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Rationale
|
||||||
|
---------
|
||||||
|
Alternatives to WebSockets include HTTP/2, CoAP, and simply rolling our own
|
||||||
|
protocol over raw TCP sockets. However, the need to implement browser-based
|
||||||
|
clients essentially reduces our choice to WebSockets. HTTP/2 streams will
|
||||||
|
probably provide an interesting alternative in the future, but current browsers
|
||||||
|
do not appear to give javascript applications low-level access to the protocol.
|
||||||
|
|
||||||
|
Concerning the continued use of the JSON encoding: we prefer to focus on the
|
||||||
|
transition to WebSockets initially. Replacing JSON with a compact
|
||||||
|
representation such as CBOR, MessagePack, or even just compressed JSON will be
|
||||||
|
a likely extension for the future. The support for negotiation of subprotocols
|
||||||
|
within WebSockets should make this a simple transition once time permits.
|
||||||
|
|
||||||
|
The number of methods available for client requests is deliberately limited, as
|
||||||
|
each method requires code to be written to map it onto the equivalent REST
|
||||||
|
implementation. Some REST methods - for instance, user registration and login -
|
||||||
|
would be pointless to expose via WebSockets. It is likely, however, that we
|
||||||
|
will increate the number of methods available via the WebSockets API as it
|
||||||
|
becomes clear which would be most useful.
|
||||||
|
|
||||||
|
Open questions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Throttling
|
||||||
|
~~~~~~~~~~
|
||||||
|
At least in v2 sync, clients are inherently self-throttling - if they do not
|
||||||
|
poll quickly enough, events will be dropped from the next result. This proposal
|
||||||
|
raises the possibility that events will be produced more quickly than they can
|
||||||
|
be sent to the client; backlogs will build up on the server and/or in the
|
||||||
|
intermediate network, which will not only lead to high latency on events being
|
||||||
|
delivered, but will lead to responses to client requests also being delayed.
|
||||||
|
|
||||||
|
We may need to implement some sort of throttling mechanism by which the server
|
||||||
|
can start to drop events. The difficulty is in knowing when to start dropping
|
||||||
|
events. A few ideas:
|
||||||
|
|
||||||
|
* Use websocket pings to measure the RTT; if it starts to increase, start
|
||||||
|
dropping events. But this requires knowledge of the base RTT, and a useful
|
||||||
|
model of what constitutes an excessive increase.
|
||||||
|
|
||||||
|
* Have the client acknowledge each batch of events, and use a window to ensure
|
||||||
|
the number of outstanding batches is limited. This is annoying as it requires
|
||||||
|
the client to have to acknowledge batches - and it's not clear what the right
|
||||||
|
window size is: we want a big window for long fat networks (think of mobile
|
||||||
|
clients), but a small one for one with lower latency.
|
||||||
|
|
||||||
|
* Start dropping events if the server's TCP buffer starts filling up. This has
|
||||||
|
the advantage of delegating the congestion-detection to TCP (which already
|
||||||
|
has a number of algorithms to deal with it, to greater or lesser
|
||||||
|
effectiveness), but relies on homeservers being hosted on OSes which use
|
||||||
|
sensible TCP congestion-avoidance algorithms, and more critically, an ability
|
||||||
|
to read the fill level of the TCP send buffer.
|
@ -1,17 +1,8 @@
|
|||||||
|
{% import 'tables.tmpl' as tables -%}
|
||||||
|
|
||||||
{{common_event.title}} Fields
|
{{common_event.title}} Fields
|
||||||
{{(7 + common_event.title | length) * title_kind}}
|
{{(7 + common_event.title | length) * title_kind}}
|
||||||
|
|
||||||
{{common_event.desc | wrap(80)}}
|
{{common_event.desc | wrap(80)}}
|
||||||
|
|
||||||
================== ================= ===========================================
|
{{ tables.paramtable(common_event.rows, ["Key", "Type", "Description"]) }}
|
||||||
Key Type Description
|
|
||||||
================== ================= ===========================================
|
|
||||||
{% for row in common_event.rows -%}
|
|
||||||
{# -#}
|
|
||||||
{# Row type needs to prepend spaces to line up with the type column (19 ch) -#}
|
|
||||||
{# Desc needs to prepend the required text (maybe) and prepend spaces too -#}
|
|
||||||
{# It also needs to then wrap inside the desc col (43 ch width) -#}
|
|
||||||
{# -#}
|
|
||||||
{{row.key}}{{row.type|indent(19-row.key|length)}}{{row.desc | indent(18 - (row.type|length)) |wrap(43) |indent_block(37)}}
|
|
||||||
{% endfor -%}
|
|
||||||
================== ================= ===========================================
|
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
{#
|
||||||
|
# A set of macros for generating RST tables
|
||||||
|
#}
|
||||||
|
|
||||||
|
|
||||||
|
{#
|
||||||
|
# write a table for a list of parameters.
|
||||||
|
#
|
||||||
|
# 'rows' is the list of parameters. Each row should have the keys
|
||||||
|
# 'key', 'type', and 'desc'.
|
||||||
|
#}
|
||||||
|
{% macro paramtable(rows, titles=["Parameter", "Type", "Description"]) -%}
|
||||||
|
{{ split_paramtable({None: rows}, titles) }}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
{#
|
||||||
|
# write a table for the request parameters, split by location.
|
||||||
|
# 'rows_by_loc' is a map from location to a list of parameters.
|
||||||
|
#
|
||||||
|
# As a special case, if a key of 'rows_by_loc' is 'None', no title row is
|
||||||
|
# written for that location. This is used by the standard 'paramtable' macro.
|
||||||
|
#}
|
||||||
|
{% macro split_paramtable(rows_by_loc,
|
||||||
|
titles=["Parameter", "Type", "Description"]) -%}
|
||||||
|
|
||||||
|
{% set rowkeys = ['key', 'type', 'desc'] %}
|
||||||
|
{% set titlerow = {'key': titles[0], 'type': titles[1], 'desc': titles[2]} %}
|
||||||
|
|
||||||
|
{# We need the rows flattened into a single list. Abuse the 'sum' filter to
|
||||||
|
# join arrays instead of add numbers. -#}
|
||||||
|
{% set flatrows = rows_by_loc.values()|sum(start=[]) -%}
|
||||||
|
|
||||||
|
{# Figure out the widths of the columns. The last column is always 50 characters
|
||||||
|
# wide; the others default to 10, but stretch if there is wider text in the
|
||||||
|
# column. -#}
|
||||||
|
{% set fieldwidths = (([titlerow] + flatrows) |
|
||||||
|
fieldwidths(rowkeys[0:-1], [10, 10])) + [50] -%}
|
||||||
|
|
||||||
|
{{ tableheader(fieldwidths) }}
|
||||||
|
{{ tablerow(fieldwidths, titlerow, rowkeys) }}
|
||||||
|
{{ tableheader(fieldwidths) }}
|
||||||
|
{% for loc in rows_by_loc -%}
|
||||||
|
|
||||||
|
{% if loc != None -%}
|
||||||
|
{{ tablespan(fieldwidths, "*" ~ loc ~ " parameters*") }}
|
||||||
|
{% endif -%}
|
||||||
|
|
||||||
|
{% for row in rows_by_loc[loc] -%}
|
||||||
|
{{ tablerow(fieldwidths, row, rowkeys) }}
|
||||||
|
{% endfor -%}
|
||||||
|
{% endfor -%}
|
||||||
|
|
||||||
|
{{ tableheader(fieldwidths) }}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{#
|
||||||
|
# Write a table header row, for the given column widths
|
||||||
|
#}
|
||||||
|
{% macro tableheader(widths) -%}
|
||||||
|
{% for arg in widths -%}
|
||||||
|
{{"="*arg}} {% endfor -%}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{#
|
||||||
|
# Write a normal table row. Each of 'widths' and 'keys' should be sequences
|
||||||
|
# of the same length; 'widths' defines the column widths, and 'keys' the
|
||||||
|
# attributes of 'row' to look up for values to put in the columns.
|
||||||
|
#}
|
||||||
|
{% macro tablerow(widths, row, keys) -%}
|
||||||
|
{% for key in keys -%}
|
||||||
|
{% set value=row[key] -%}
|
||||||
|
{% if not loop.last -%}
|
||||||
|
{# the first few columns need space after them -#}
|
||||||
|
{{ value }}{{" "*(1+widths[loop.index0]-value|length) -}}
|
||||||
|
{% else -%}
|
||||||
|
{# the last column needs wrapping and indenting (by the sum of the widths of
|
||||||
|
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) -}}
|
||||||
|
{% endif -%}
|
||||||
|
{% endfor -%}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{#
|
||||||
|
# write a tablespan row. This is a single value which spans the entire table.
|
||||||
|
#}
|
||||||
|
{% macro tablespan(widths, value) -%}
|
||||||
|
{{value}}
|
||||||
|
{# we write a trailing space to stop the separator being misinterpreted
|
||||||
|
# as a header line. -#}
|
||||||
|
{{"-"*(widths|sum + widths|length -1)}} {% endmacro %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue