Merge branch 'master' into speculator-htmldiff
commit
2434dfaf1c
@ -0,0 +1,118 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def import_error(module, package, debian, error):
|
||||||
|
sys.stderr.write((
|
||||||
|
"Error importing %(module)s: %(error)r\n"
|
||||||
|
"To install %(module)s run:\n"
|
||||||
|
" pip install %(package)s\n"
|
||||||
|
"or on Debian run:\n"
|
||||||
|
" sudo apt-get install python-%(debian)s\n"
|
||||||
|
) % locals())
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import jsonschema
|
||||||
|
except ImportError as e:
|
||||||
|
import_error("jsonschema", "jsonschema", "jsonschema", e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError as e:
|
||||||
|
import_error("yaml", "PyYAML", "yaml", e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def check_parameter(filepath, request, parameter):
|
||||||
|
schema = parameter.get("schema")
|
||||||
|
example = None
|
||||||
|
try:
|
||||||
|
example_json = schema.get('example')
|
||||||
|
if example_json:
|
||||||
|
example = json.loads(example_json)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Error parsing JSON example request for %r" % (
|
||||||
|
request
|
||||||
|
), e)
|
||||||
|
fileurl = "file://" + os.path.abspath(filepath)
|
||||||
|
if example and schema:
|
||||||
|
try:
|
||||||
|
print ("Checking request schema for: %r %r" % (
|
||||||
|
filepath, request
|
||||||
|
))
|
||||||
|
# Setting the 'id' tells jsonschema where the file is so that it
|
||||||
|
# can correctly resolve relative $ref references in the schema
|
||||||
|
schema['id'] = fileurl
|
||||||
|
jsonschema.validate(example, schema)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Error validating JSON schema for %r %r" % (
|
||||||
|
request, code
|
||||||
|
), e)
|
||||||
|
|
||||||
|
|
||||||
|
def check_response(filepath, request, code, response):
|
||||||
|
example = None
|
||||||
|
try:
|
||||||
|
example_json = response.get('examples', {}).get('application/json')
|
||||||
|
if example_json:
|
||||||
|
example = json.loads(example_json)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Error parsing JSON example response for %r %r" % (
|
||||||
|
request, code
|
||||||
|
), e)
|
||||||
|
schema = response.get('schema')
|
||||||
|
fileurl = "file://" + os.path.abspath(filepath)
|
||||||
|
if example and schema:
|
||||||
|
try:
|
||||||
|
print ("Checking response schema for: %r %r %r" % (
|
||||||
|
filepath, request, code
|
||||||
|
))
|
||||||
|
# Setting the 'id' tells jsonschema where the file is so that it
|
||||||
|
# can correctly resolve relative $ref references in the schema
|
||||||
|
schema['id'] = fileurl
|
||||||
|
jsonschema.validate(example, schema)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Error validating JSON schema for %r %r" % (
|
||||||
|
request, code
|
||||||
|
), e)
|
||||||
|
|
||||||
|
|
||||||
|
def check_swagger_file(filepath):
|
||||||
|
with open(filepath) as f:
|
||||||
|
swagger = yaml.load(f)
|
||||||
|
|
||||||
|
for path, path_api in swagger.get('paths', {}).items():
|
||||||
|
|
||||||
|
for method, request_api in path_api.items():
|
||||||
|
request = "%s %s" % (method.upper(), path)
|
||||||
|
for parameter in request_api.get('parameters', ()):
|
||||||
|
if parameter['in'] == 'body':
|
||||||
|
check_parameter(filepath, request, parameter)
|
||||||
|
|
||||||
|
try:
|
||||||
|
responses = request_api['responses']
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError("No responses for %r" % (request,))
|
||||||
|
for code, response in responses.items():
|
||||||
|
check_response(filepath, request, code, response)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
paths = sys.argv[1:]
|
||||||
|
if not paths:
|
||||||
|
paths = []
|
||||||
|
for (root, dirs, files) in os.walk(os.curdir):
|
||||||
|
for filename in files:
|
||||||
|
if filename.endswith(".yaml"):
|
||||||
|
paths.append(os.path.join(root, filename))
|
||||||
|
for path in paths:
|
||||||
|
try:
|
||||||
|
check_swagger_file(path)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Error checking file %r" % (path,), e)
|
||||||
@ -0,0 +1 @@
|
|||||||
|
v1-event-schema/core-event-schema
|
||||||
@ -1,7 +0,0 @@
|
|||||||
type: object
|
|
||||||
description: A Matrix Event
|
|
||||||
properties:
|
|
||||||
event_id:
|
|
||||||
type: string
|
|
||||||
description: An event ID.
|
|
||||||
required: ["event_id"]
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
type: object
|
|
||||||
description: A Matrix Room Event
|
|
||||||
properties:
|
|
||||||
event_id:
|
|
||||||
type: string
|
|
||||||
description: An event ID.
|
|
||||||
room_id:
|
|
||||||
type: string
|
|
||||||
required: ["event_id", "room_id"]
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
type: object
|
|
||||||
description: A Matrix State Event
|
|
||||||
properties:
|
|
||||||
event_id:
|
|
||||||
type: string
|
|
||||||
description: An event ID.
|
|
||||||
room_id:
|
|
||||||
type: string
|
|
||||||
state_key:
|
|
||||||
type: string
|
|
||||||
required: ["event_id", "room_id", "state_key"]
|
|
||||||
@ -0,0 +1,147 @@
|
|||||||
|
swagger: '2.0'
|
||||||
|
info:
|
||||||
|
title: "Matrix Client-Server v1 Registration and Login 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:
|
||||||
|
"/login":
|
||||||
|
post:
|
||||||
|
summary: Authenticates the user.
|
||||||
|
description: |-
|
||||||
|
Authenticates the user by password, and issues an access token they can
|
||||||
|
use to authorize themself in subsequent requests.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
example: |-
|
||||||
|
{
|
||||||
|
"username": "cheeky_monkey",
|
||||||
|
"password": "ilovebananas"
|
||||||
|
}
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: The fully qualified user ID or just local part of the user ID, to log in.
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: The user's password.
|
||||||
|
required: ["username", "password"]
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The user has been authenticated.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{
|
||||||
|
"user_id": "@cheeky_monkey:matrix.org",
|
||||||
|
"access_token": "abc123",
|
||||||
|
"home_server": "matrix.org"
|
||||||
|
}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: string
|
||||||
|
description: The fully-qualified Matrix ID that has been registered.
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
An access token for the account.
|
||||||
|
This access token can then be used to authorize other requests.
|
||||||
|
The access token may expire at some point, and if so, it SHOULD come with a ``refresh_token``.
|
||||||
|
There is no specific error message to indicate that a request has failed because
|
||||||
|
an access token has expired; instead, if a client has reason to believe its
|
||||||
|
access token is valid, and it receives an auth error, they should attempt to
|
||||||
|
refresh for a new token on failure, and retry the request with the new token.
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
# TODO: Work out how to linkify /tokenrefresh
|
||||||
|
description: |-
|
||||||
|
(optional) A ``refresh_token`` may be exchanged for a new ``access_token`` using the /tokenrefresh API endpoint.
|
||||||
|
home_server:
|
||||||
|
type: string
|
||||||
|
description: The hostname of the Home Server on which the account has been registered.
|
||||||
|
403:
|
||||||
|
description: |-
|
||||||
|
The login attempt failed. For example, the password may have been incorrect.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{"errcode": "M_FORBIDDEN"}
|
||||||
|
429:
|
||||||
|
description: This request was rate-limited.
|
||||||
|
schema:
|
||||||
|
"$ref": "definitions/error.yaml"
|
||||||
|
"/tokenrefresh":
|
||||||
|
post:
|
||||||
|
summary: Exchanges a refresh token for an access token.
|
||||||
|
description: |-
|
||||||
|
Exchanges a refresh token for a new access token.
|
||||||
|
This is intended to be used if the access token has expired.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: body
|
||||||
|
name: body
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
example: |-
|
||||||
|
{
|
||||||
|
"refresh_token": "a1b2c3"
|
||||||
|
}
|
||||||
|
properties:
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
description: The refresh token which was issued by the server.
|
||||||
|
required: ["refresh_token"]
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: |-
|
||||||
|
The refresh token was accepted, and a new access token has been issued.
|
||||||
|
The passed refresh token is no longer valid and cannot be used.
|
||||||
|
A new refresh token will have been returned unless some policy does
|
||||||
|
not allow the user to continue to renew their session.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{
|
||||||
|
"access_token": "bearwithme123",
|
||||||
|
"refresh_token": "exchangewithme987"
|
||||||
|
}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
An access token for the account.
|
||||||
|
This access token can then be used to authorize other requests.
|
||||||
|
The access token may expire at some point, and if so, it SHOULD come with a ``refresh_token``.
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
description: (optional) A ``refresh_token`` may be exchanged for a new ``access_token`` using the TODO Linkify /tokenrefresh API endpoint.
|
||||||
|
403:
|
||||||
|
description: |-
|
||||||
|
The exchange attempt failed. For example, the refresh token may have already been used.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{"errcode": "M_FORBIDDEN"}
|
||||||
|
429:
|
||||||
|
description: This request was rate-limited.
|
||||||
|
schema:
|
||||||
|
"$ref": "definitions/error.yaml"
|
||||||
@ -0,0 +1,448 @@
|
|||||||
|
swagger: '2.0'
|
||||||
|
info:
|
||||||
|
title: "Matrix Client-Server v1 Rooms 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}/state/{eventType}/{stateKey}":
|
||||||
|
get:
|
||||||
|
summary: Get the state identified by the type and key.
|
||||||
|
description: |-
|
||||||
|
Looks up the contents of a state event in a room. If the user is
|
||||||
|
joined to the room then the state is taken from the current
|
||||||
|
state of the room. If the user has left the room then the state is
|
||||||
|
taken from the state of the room when they left.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room to look up the state in.
|
||||||
|
required: true
|
||||||
|
x-example: "!636q39766251:example.com"
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: eventType
|
||||||
|
description: The type of state to look up.
|
||||||
|
required: true
|
||||||
|
x-example: "m.room.name"
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: stateKey
|
||||||
|
description: The key of the state to look up. Defaults to the empty string.
|
||||||
|
required: true
|
||||||
|
x-example: ""
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The content of the state event.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{"name": "Example room name"}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
404:
|
||||||
|
description: The room has no state with the given type or key.
|
||||||
|
403:
|
||||||
|
description: >
|
||||||
|
You aren't a member of the room and weren't previously a
|
||||||
|
member of the room.
|
||||||
|
|
||||||
|
"/rooms/{roomId}/state":
|
||||||
|
get:
|
||||||
|
summary: Get all state events in the current state of a room.
|
||||||
|
description: |-
|
||||||
|
Get the state events for the current state of a room.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room to look up the state for.
|
||||||
|
required: true
|
||||||
|
x-example: "!636q39766251:example.com"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The current state of the room
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"age": 7148266897,
|
||||||
|
"content": {
|
||||||
|
"join_rule": "public"
|
||||||
|
},
|
||||||
|
"event_id": "$14259997323TLwtb:example.com",
|
||||||
|
"origin_server_ts": 1425999732392,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.join_rules",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 6547561012,
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto",
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$1426600438280zExKY:example.com",
|
||||||
|
"membership": "join",
|
||||||
|
"origin_server_ts": 1426600438277,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "@alice:example.com",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 7148267200,
|
||||||
|
"content": {
|
||||||
|
"creator": "@alice:example.com"
|
||||||
|
},
|
||||||
|
"event_id": "$14259997320KhbwJ:example.com",
|
||||||
|
"origin_server_ts": 1425999732089,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.create",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 1622568720,
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
|
||||||
|
"displayname": "Bob",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$1431525430134MxlLX:example.com",
|
||||||
|
"origin_server_ts": 1431525430569,
|
||||||
|
"replaces_state": "$142652023736BSXcM:example.com",
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "@bob:example.com",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@bob:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 7148267004,
|
||||||
|
"content": {
|
||||||
|
"ban": 50,
|
||||||
|
"events": {
|
||||||
|
"m.room.name": 100,
|
||||||
|
"m.room.power_levels": 100
|
||||||
|
},
|
||||||
|
"events_default": 0,
|
||||||
|
"kick": 50,
|
||||||
|
"redact": 50,
|
||||||
|
"state_default": 50,
|
||||||
|
"users": {
|
||||||
|
"@alice:example.com": 100
|
||||||
|
},
|
||||||
|
"users_default": 0
|
||||||
|
},
|
||||||
|
"event_id": "$14259997322mqfaq:example.com",
|
||||||
|
"origin_server_ts": 1425999732285,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
title: RoomState
|
||||||
|
description: |-
|
||||||
|
If the user is a member of the room this will be the
|
||||||
|
current state of the room as a list of events. If the user
|
||||||
|
has left the room then this will be the state of the room
|
||||||
|
when they left as a list of events.
|
||||||
|
items:
|
||||||
|
title: StateEvent
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- "$ref": "core-event-schema/state_event.json"
|
||||||
|
403:
|
||||||
|
description: >
|
||||||
|
You aren't a member of the room and weren't previously a
|
||||||
|
member of the room.
|
||||||
|
|
||||||
|
"/rooms/{roomId}/initialSync":
|
||||||
|
get:
|
||||||
|
summary: Snapshot the current state of a room and its most recent messages.
|
||||||
|
description: |-
|
||||||
|
Get a copy of the current state and the most recent messages in a room.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room to get the data.
|
||||||
|
required: true
|
||||||
|
x-example: "!636q39766251:example.com"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The current state of the room
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{
|
||||||
|
"membership": "join",
|
||||||
|
"messages": {
|
||||||
|
"chunk": [
|
||||||
|
{
|
||||||
|
"age": 343513403,
|
||||||
|
"content": {
|
||||||
|
"body": "foo",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$14328044851tzTJS:example.com",
|
||||||
|
"origin_server_ts": 1432804485886,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 343511809,
|
||||||
|
"content": {
|
||||||
|
"body": "bar",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$14328044872spjFg:example.com",
|
||||||
|
"origin_server_ts": 1432804487480,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"user_id": "@bob:example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": "s3456_9_0",
|
||||||
|
"start": "t44-3453_9_0"
|
||||||
|
},
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state": [
|
||||||
|
{
|
||||||
|
"age": 7148266897,
|
||||||
|
"content": {
|
||||||
|
"join_rule": "public"
|
||||||
|
},
|
||||||
|
"event_id": "$14259997323TLwtb:example.com",
|
||||||
|
"origin_server_ts": 1425999732392,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.join_rules",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 6547561012,
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto",
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$1426600438280zExKY:example.com",
|
||||||
|
"membership": "join",
|
||||||
|
"origin_server_ts": 1426600438277,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "@alice:example.com",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 7148267200,
|
||||||
|
"content": {
|
||||||
|
"creator": "@alice:example.com"
|
||||||
|
},
|
||||||
|
"event_id": "$14259997320KhbwJ:example.com",
|
||||||
|
"origin_server_ts": 1425999732089,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.create",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 1622568720,
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
|
||||||
|
"displayname": "Bob",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$1431525430134MxlLX:example.com",
|
||||||
|
"origin_server_ts": 1431525430569,
|
||||||
|
"replaces_state": "$142652023736BSXcM:example.com",
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "@bob:example.com",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@bob:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 7148267004,
|
||||||
|
"content": {
|
||||||
|
"ban": 50,
|
||||||
|
"events": {
|
||||||
|
"m.room.name": 100,
|
||||||
|
"m.room.power_levels": 100
|
||||||
|
},
|
||||||
|
"events_default": 0,
|
||||||
|
"kick": 50,
|
||||||
|
"redact": 50,
|
||||||
|
"state_default": 50,
|
||||||
|
"users": {
|
||||||
|
"@alice:example.com": 100
|
||||||
|
},
|
||||||
|
"users_default": 0
|
||||||
|
},
|
||||||
|
"event_id": "$14259997322mqfaq:example.com",
|
||||||
|
"origin_server_ts": 1425999732285,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"visibility": "private"
|
||||||
|
}
|
||||||
|
schema:
|
||||||
|
title: RoomInfo
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
room_id:
|
||||||
|
type: string
|
||||||
|
description: "The ID of this room."
|
||||||
|
membership:
|
||||||
|
type: string
|
||||||
|
description: "The user's membership state in this room."
|
||||||
|
enum: ["invite", "join", "leave", "ban"]
|
||||||
|
messages:
|
||||||
|
type: object
|
||||||
|
title: PaginationChunk
|
||||||
|
description: "The pagination chunk for this room."
|
||||||
|
properties:
|
||||||
|
start:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
A token which correlates to the first value in ``chunk``.
|
||||||
|
Used for pagination.
|
||||||
|
end:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
A token which correlates to the last value in ``chunk``.
|
||||||
|
Used for pagination.
|
||||||
|
chunk:
|
||||||
|
type: array
|
||||||
|
description: |-
|
||||||
|
If the user is a member of the room this will be a
|
||||||
|
list of the most recent messages for this room. If
|
||||||
|
the user has left the room this will be the
|
||||||
|
messages that preceeded them leaving. This array
|
||||||
|
will consist of at most ``limit`` elements.
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
title: RoomEvent
|
||||||
|
allOf:
|
||||||
|
- "$ref": "core-event-schema/room_event.json"
|
||||||
|
required: ["start", "end", "chunk"]
|
||||||
|
state:
|
||||||
|
type: array
|
||||||
|
description: |-
|
||||||
|
If the user is a member of the room this will be the
|
||||||
|
current state of the room as a list of events. If the
|
||||||
|
user has left the room this will be the state of the
|
||||||
|
room when they left it.
|
||||||
|
items:
|
||||||
|
title: StateEvent
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- "$ref": "core-event-schema/state_event.json"
|
||||||
|
visibility:
|
||||||
|
type: string
|
||||||
|
enum: ["private", "public"]
|
||||||
|
description: |-
|
||||||
|
Whether this room is visible to the ``/publicRooms`` API
|
||||||
|
or not."
|
||||||
|
required: ["room_id", "membership"]
|
||||||
|
403:
|
||||||
|
description: >
|
||||||
|
You aren't a member of the room and weren't previously a
|
||||||
|
member of the room.
|
||||||
|
|
||||||
|
"/rooms/{roomId}/members":
|
||||||
|
get:
|
||||||
|
summary: Get the m.room.member events for the room.
|
||||||
|
description:
|
||||||
|
Get the list of members for this room.
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room to get the member events for.
|
||||||
|
required: true
|
||||||
|
x-example: "!636q39766251:example.com"
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: |-
|
||||||
|
A list of members of the room. If you are joined to the room then
|
||||||
|
this will be the current members of the room. If you have left te
|
||||||
|
room then this will be the members of the room when you left.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{
|
||||||
|
"chunk": [
|
||||||
|
{
|
||||||
|
"age": 6547561012,
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.com/fzysBrHpPEeTGANCVLXWXNMI#auto",
|
||||||
|
"displayname": null,
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$1426600438280zExKY:example.com",
|
||||||
|
"membership": "join",
|
||||||
|
"origin_server_ts": 1426600438277,
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "@alice:example.com",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@alice:example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"age": 1622568720,
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.com/GCmhgzMPRjqgpODLsNQzVuHZ#auto",
|
||||||
|
"displayname": "Bob",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$1431525430134MxlLX:example.com",
|
||||||
|
"origin_server_ts": 1431525430569,
|
||||||
|
"replaces_state": "$142652023736BSXcM:example.com",
|
||||||
|
"room_id": "!636q39766251:example.com",
|
||||||
|
"state_key": "@bob:example.com",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"user_id": "@bob:example.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
chunk:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
title: MemberEvent
|
||||||
|
type: object
|
||||||
|
allOf:
|
||||||
|
- "$ref": "v1-event-schema/m.room.member"
|
||||||
|
403:
|
||||||
|
description: >
|
||||||
|
You aren't a member of the room and weren't previously a
|
||||||
|
member of the room.
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
../../../event-schemas/schema/v1
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
Macaroon Caveats
|
||||||
|
================
|
||||||
|
|
||||||
|
`Macaroons`_ are issued by Matrix servers as authorization tokens. Macaroons may be restricted by adding caveats to them.
|
||||||
|
|
||||||
|
.. _Macaroons: http://theory.stanford.edu/~ataly/Papers/macaroons.pdf
|
||||||
|
|
||||||
|
Caveats can only be used for reducing the scope of a token, never for increasing it. Servers are required to reject any macroon with a caveat that they do not understand.
|
||||||
|
|
||||||
|
Some caveats are specified in this specification, and must be understood by all servers. The use of non-standard caveats is allowed.
|
||||||
|
|
||||||
|
All caveats must take the form:
|
||||||
|
|
||||||
|
`key` `operator` `value`
|
||||||
|
where `key` is a non-empty string drawn from the character set [A-Za-z0-9_]
|
||||||
|
`operator` is a non-empty string which does not contain whitespace
|
||||||
|
`value` is a non-empty string
|
||||||
|
And these are joined by single space characters.
|
||||||
|
|
||||||
|
Specified caveats:
|
||||||
|
|
||||||
|
+-------------+--------------------------------------------------+------------------------------------------------------------------------------------------------+
|
||||||
|
| Caveat name | Description | Legal Values |
|
||||||
|
+-------------+--------------------------------------------------+------------------------------------------------------------------------------------------------+
|
||||||
|
| gen | Generation of the macaroon caveat spec. | 1 |
|
||||||
|
| user_id | ID of the user for which this macaroon is valid. | Pure equality check. Operator must be =. |
|
||||||
|
| type | The purpose of this macaroon. | access - used to authorize any action except token refresh |
|
||||||
|
| refresh - only used to authorize a token refresh |
|
||||||
|
| time | Time before/after which this macaroon is valid. | A POSIX timestamp in milliseconds (in UTC). |
|
||||||
|
| Operator < means the macaroon is valid before the timestamp, as interpreted by the server. |
|
||||||
|
| Operator > means the macaroon is valid after the timestamp, as interpreted by the server. |
|
||||||
|
| Operator == means the macaroon is valid at exactly the timestamp, as interpreted by the server.|
|
||||||
|
| Note that exact equality of time is largely meaningless. |
|
||||||
|
+-------------+--------------------------------------------------+------------------------------------------------------------------------------------------------+
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
#! /usr/bin/env python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
def import_error(module, package, debian, error):
|
||||||
|
sys.stderr.write((
|
||||||
|
"Error importing %(module)s: %(error)r\n"
|
||||||
|
"To install %(module)s run:\n"
|
||||||
|
" pip install %(package)s\n"
|
||||||
|
"or on Debian run:\n"
|
||||||
|
" sudo apt-get install python-%(debian)s\n"
|
||||||
|
) % locals())
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import jsonschema
|
||||||
|
except ImportError as e:
|
||||||
|
import_error("jsonschema", "jsonschema", "jsonschema", e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError as e:
|
||||||
|
import_error("yaml", "PyYAML", "yaml", e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def check_example_file(examplepath, schemapath):
|
||||||
|
with open(examplepath) as f:
|
||||||
|
example = yaml.load(f)
|
||||||
|
|
||||||
|
with open(schemapath) as f:
|
||||||
|
schema = yaml.load(f)
|
||||||
|
|
||||||
|
fileurl = "file://" + os.path.abspath(schemapath)
|
||||||
|
|
||||||
|
print ("Checking schema for: %r %r" % (examplepath, schemapath))
|
||||||
|
# Setting the 'id' tells jsonschema where the file is so that it
|
||||||
|
# can correctly resolve relative $ref references in the schema
|
||||||
|
schema['id'] = fileurl
|
||||||
|
try:
|
||||||
|
jsonschema.validate(example, schema)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Error validating JSON schema for %r %r" % (
|
||||||
|
examplepath, schemapath
|
||||||
|
), e)
|
||||||
|
|
||||||
|
|
||||||
|
def check_example_dir(exampledir, schemadir):
|
||||||
|
errors = []
|
||||||
|
for root, dirs, files in os.walk(exampledir):
|
||||||
|
for filename in files:
|
||||||
|
if filename.startswith("."):
|
||||||
|
# Skip over any vim .swp files.
|
||||||
|
continue
|
||||||
|
examplepath = os.path.join(root, filename)
|
||||||
|
schemapath = examplepath.replace(exampledir, schemadir)
|
||||||
|
try:
|
||||||
|
check_example_file(examplepath, schemapath)
|
||||||
|
except Exception as e:
|
||||||
|
errors.append(sys.exc_info())
|
||||||
|
for (exc_type, exc_value, exc_trace) in errors:
|
||||||
|
traceback.print_exception(exc_type, exc_value, exc_trace)
|
||||||
|
if errors:
|
||||||
|
raise ValueError("Error validating examples")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
check_example_dir("examples", "schema")
|
||||||
|
except:
|
||||||
|
sys.exit(1)
|
||||||
@ -1,88 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
||||||
"definitions": {
|
|
||||||
"event": {
|
|
||||||
"title": "Event",
|
|
||||||
"description": "The basic set of fields all events must have.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"event_id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The globally unique event identifier."
|
|
||||||
},
|
|
||||||
"user_id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "Contains the fully-qualified ID of the user who *sent* this event."
|
|
||||||
},
|
|
||||||
"content": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body."
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The type of event. This SHOULD be namespaced similar to Java package naming conventions e.g. 'com.example.subdomain.event.type'"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["event_id", "user_id", "content", "type"]
|
|
||||||
},
|
|
||||||
"room_event": {
|
|
||||||
"type": "object",
|
|
||||||
"title": "Room Event",
|
|
||||||
"description": "In addition to the Event fields, Room Events MUST have the following additional field.",
|
|
||||||
"allOf":[{
|
|
||||||
"$ref": "#/definitions/event"
|
|
||||||
}],
|
|
||||||
"properties": {
|
|
||||||
"room_id": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The ID of the room associated with this event."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["room_id"]
|
|
||||||
},
|
|
||||||
"state_event": {
|
|
||||||
"type": "object",
|
|
||||||
"title": "State Event",
|
|
||||||
"description": "In addition to the Room Event fields, State Events have the following additional fields.",
|
|
||||||
"allOf":[{
|
|
||||||
"$ref": "#/definitions/room_event"
|
|
||||||
}],
|
|
||||||
"properties": {
|
|
||||||
"state_key": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event."
|
|
||||||
},
|
|
||||||
"prev_content": {
|
|
||||||
"type": "object",
|
|
||||||
"description": "Optional. The previous ``content`` for this event. If there is no previous content, this key will be missing."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["state_key"]
|
|
||||||
},
|
|
||||||
"msgtype_infos": {
|
|
||||||
"image_info": {
|
|
||||||
"type": "object",
|
|
||||||
"title": "ImageInfo",
|
|
||||||
"description": "Metadata about an image.",
|
|
||||||
"properties": {
|
|
||||||
"size": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "Size of the image in bytes."
|
|
||||||
},
|
|
||||||
"w": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "The width of the image in pixels."
|
|
||||||
},
|
|
||||||
"h": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "The height of the image in pixels."
|
|
||||||
},
|
|
||||||
"mimetype": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The mimetype of the image, e.g. ``image/jpeg``."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
.
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"title": "Event",
|
||||||
|
"description": "The basic set of fields all events must have.",
|
||||||
|
"properties": {
|
||||||
|
"content": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "The fields in this object will vary depending on the type of event. When interacting with the REST API, this is the HTTP body."
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The type of event. This SHOULD be namespaced similar to Java package naming conventions e.g. 'com.example.subdomain.event.type'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"title": "ImageInfo",
|
||||||
|
"description": "Metadata about an image.",
|
||||||
|
"properties": {
|
||||||
|
"size": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Size of the image in bytes."
|
||||||
|
},
|
||||||
|
"w": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The width of the image in pixels."
|
||||||
|
},
|
||||||
|
"h": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The height of the image in pixels."
|
||||||
|
},
|
||||||
|
"mimetype": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The mimetype of the image, e.g. ``image/jpeg``."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"title": "Room Event",
|
||||||
|
"description": "In addition to the Event fields, Room Events MUST have the following additional field.",
|
||||||
|
"allOf":[{
|
||||||
|
"$ref": "core-event-schema/event.json"
|
||||||
|
}],
|
||||||
|
"properties": {
|
||||||
|
"room_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The ID of the room associated with this event."
|
||||||
|
},
|
||||||
|
"event_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The globally unique event identifier."
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Contains the fully-qualified ID of the user who *sent* this event."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["room_id"]
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"title": "State Event",
|
||||||
|
"description": "In addition to the Room Event fields, State Events have the following additional fields.",
|
||||||
|
"allOf":[{
|
||||||
|
"$ref": "core-event-schema/room_event.json"
|
||||||
|
}],
|
||||||
|
"properties": {
|
||||||
|
"state_key": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A unique key which defines the overwriting semantics for this piece of room state. This value is often a zero-length string. The presence of this key makes this event a State Event."
|
||||||
|
},
|
||||||
|
"prev_content": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Optional. The previous ``content`` for this event. If there is no previous content, this key will be missing."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["state_key"]
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
.
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
(cd event-schemas/ && ./check_examples.py)
|
||||||
|
(cd api && ./check_examples.py)
|
||||||
|
(cd scripts && ./gendoc.py)
|
||||||
|
(cd api && npm install && node validator.js -s "client-server/v1")
|
||||||
|
(cd event-schemas/ && ./check.sh)
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
pre.code .comment, code .comment { color: green }
|
||||||
|
pre.code .keyword, code .keyword { color: darkred; font-weight: bold }
|
||||||
|
pre.code .name.builtin, code .name.builtin { color: darkred; font-weight: bold }
|
||||||
|
pre.code .literal.number, code .literal.number { color: blue }
|
||||||
|
pre.code .name.tag, code .name.tag { color: darkgreen }
|
||||||
|
pre.code .literal.string, code .literal.string { color: darkblue }
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
Events
|
||||||
|
======
|
||||||
|
|
||||||
|
All communication in Matrix is expressed in the form of data objects called
|
||||||
|
Events. These are the fundamental building blocks common to the client-server,
|
||||||
|
server-server and application-service APIs, and are described below.
|
||||||
|
|
||||||
|
{{common_event_fields}}
|
||||||
|
|
||||||
|
{{common_room_event_fields}}
|
||||||
|
|
||||||
|
{{common_state_event_fields}}
|
||||||
|
|
||||||
|
|
||||||
|
Room Events
|
||||||
|
-----------
|
||||||
|
.. NOTE::
|
||||||
|
This section is a work in progress.
|
||||||
|
|
||||||
|
This specification outlines several standard event types, all of which are
|
||||||
|
prefixed with ``m.``
|
||||||
|
|
||||||
|
{{m_room_aliases_event}}
|
||||||
|
|
||||||
|
{{m_room_canonical_alias_event}}
|
||||||
|
|
||||||
|
{{m_room_create_event}}
|
||||||
|
|
||||||
|
{{m_room_history_visibility_event}}
|
||||||
|
|
||||||
|
{{m_room_join_rules_event}}
|
||||||
|
|
||||||
|
{{m_room_member_event}}
|
||||||
|
|
||||||
|
{{m_room_power_levels_event}}
|
||||||
|
|
||||||
|
{{m_room_redaction_event}}
|
||||||
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
Feature Profiles
|
||||||
|
================
|
||||||
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
Modules
|
||||||
|
=======
|
||||||
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
Events
|
|
||||||
======
|
|
||||||
|
|
||||||
All communication in Matrix is expressed in the form of data objects called
|
|
||||||
Events. These are the fundamental building blocks common to the client-server,
|
|
||||||
server-server and application-service APIs, and are described below.
|
|
||||||
|
|
||||||
{{common_event_fields}}
|
|
||||||
|
|
||||||
{{common_room_event_fields}}
|
|
||||||
|
|
||||||
{{common_state_event_fields}}
|
|
||||||
|
|
||||||
|
|
||||||
Room Events
|
|
||||||
-----------
|
|
||||||
.. NOTE::
|
|
||||||
This section is a work in progress.
|
|
||||||
|
|
||||||
This specification outlines several standard event types, all of which are
|
|
||||||
prefixed with ``m.``
|
|
||||||
|
|
||||||
{{room_events}}
|
|
||||||
|
|
||||||
m.room.message msgtypes
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. TODO-spec
|
|
||||||
How a client should handle unknown message types.
|
|
||||||
|
|
||||||
|
|
||||||
Each `m.room.message`_ MUST have a ``msgtype`` key which identifies the type
|
|
||||||
of message being sent. Each type has their own required and optional keys, as
|
|
||||||
outlined below.
|
|
||||||
|
|
||||||
{{msgtype_events}}
|
|
||||||
|
|
||||||
Presence Events
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
{{presence_events}}
|
|
||||||
|
|
||||||
Each user has the concept of presence information. This encodes the
|
|
||||||
"availability" of that user, suitable for display on other user's clients.
|
|
||||||
This is transmitted as an ``m.presence`` event and is one of the few events
|
|
||||||
which are sent *outside the context of a room*. The basic piece of presence
|
|
||||||
information is represented by the ``presence`` key, which is an enum of one
|
|
||||||
of the following:
|
|
||||||
|
|
||||||
- ``online`` : The default state when the user is connected to an event
|
|
||||||
stream.
|
|
||||||
- ``unavailable`` : The user is not reachable at this time.
|
|
||||||
- ``offline`` : The user is not connected to an event stream.
|
|
||||||
- ``free_for_chat`` : The user is generally willing to receive messages
|
|
||||||
moreso than default.
|
|
||||||
- ``hidden`` : Behaves as offline, but allows the user to see the client
|
|
||||||
state anyway and generally interact with client features. (Not yet
|
|
||||||
implemented in synapse).
|
|
||||||
|
|
||||||
In addition, the server maintains a timestamp of the last time it saw a
|
|
||||||
pro-active event from the user; either sending a message to a room, or
|
|
||||||
changing presence state from a lower to a higher level of availability
|
|
||||||
(thus: changing state from ``unavailable`` to ``online`` counts as a
|
|
||||||
proactive event, whereas in the other direction it will not). This timestamp
|
|
||||||
is presented via a key called ``last_active_ago``, which gives the relative
|
|
||||||
number of milliseconds since the message is generated/emitted that the user
|
|
||||||
was last seen active.
|
|
||||||
|
|
||||||
|
|
||||||
Events on Change of Profile Information
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Because the profile displayname and avatar information are likely to be used in
|
|
||||||
many places of a client's display, changes to these fields cause an automatic
|
|
||||||
propagation event to occur, informing likely-interested parties of the new
|
|
||||||
values. This change is conveyed using two separate mechanisms:
|
|
||||||
|
|
||||||
- a ``m.room.member`` event is sent to every room the user is a member of,
|
|
||||||
to update the ``displayname`` and ``avatar_url``.
|
|
||||||
- a ``m.presence`` presence status update is sent, again containing the new values of the
|
|
||||||
``displayname`` and ``avatar_url`` keys, in addition to the required
|
|
||||||
``presence`` key containing the current presence state of the user.
|
|
||||||
|
|
||||||
Both of these should be done automatically by the home server when a user
|
|
||||||
successfully changes their displayname or avatar URL fields.
|
|
||||||
|
|
||||||
Additionally, when home servers emit room membership events for their own
|
|
||||||
users, they should include the displayname and avatar URL fields in these
|
|
||||||
events so that clients already have these details to hand, and do not have to
|
|
||||||
perform extra roundtrips to query it.
|
|
||||||
|
|
||||||
Voice over IP
|
|
||||||
-------------
|
|
||||||
Matrix can also be used to set up VoIP calls. This is part of the core
|
|
||||||
specification, although is at a relatively early stage. Voice (and video) over
|
|
||||||
Matrix is built on the WebRTC 1.0 standard.
|
|
||||||
|
|
||||||
Call events are sent to a room, like any other event. This means that clients
|
|
||||||
must only send call events to rooms with exactly two participants as currently
|
|
||||||
the WebRTC standard is based around two-party communication.
|
|
||||||
|
|
||||||
{{voip_events}}
|
|
||||||
|
|
||||||
Message Exchange
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
A call is set up with messages exchanged as follows:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Caller Callee
|
|
||||||
m.call.invite ----------->
|
|
||||||
m.call.candidate -------->
|
|
||||||
[more candidates events]
|
|
||||||
User answers call
|
|
||||||
<------ m.call.answer
|
|
||||||
[...]
|
|
||||||
<------ m.call.hangup
|
|
||||||
|
|
||||||
Or a rejected call:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
Caller Callee
|
|
||||||
m.call.invite ----------->
|
|
||||||
m.call.candidate -------->
|
|
||||||
[more candidates events]
|
|
||||||
User rejects call
|
|
||||||
<------- m.call.hangup
|
|
||||||
|
|
||||||
Calls are negotiated according to the WebRTC specification.
|
|
||||||
|
|
||||||
|
|
||||||
Glare
|
|
||||||
~~~~~
|
|
||||||
This specification aims to address the problem of two users calling each other
|
|
||||||
at roughly the same time and their invites crossing on the wire. It is a far
|
|
||||||
better experience for the users if their calls are connected if it is clear
|
|
||||||
that their intention is to set up a call with one another.
|
|
||||||
|
|
||||||
In Matrix, calls are to rooms rather than users (even if those rooms may only
|
|
||||||
contain one other user) so we consider calls which are to the same room.
|
|
||||||
|
|
||||||
The rules for dealing with such a situation are as follows:
|
|
||||||
|
|
||||||
- If an invite to a room is received whilst the client is preparing to send an
|
|
||||||
invite to the same room, the client should cancel its outgoing call and
|
|
||||||
instead automatically accept the incoming call on behalf of the user.
|
|
||||||
- If an invite to a room is received after the client has sent an invite to
|
|
||||||
the same room and is waiting for a response, the client should perform a
|
|
||||||
lexicographical comparison of the call IDs of the two calls and use the
|
|
||||||
lesser of the two calls, aborting the greater. If the incoming call is the
|
|
||||||
lesser, the client should accept this call on behalf of the user.
|
|
||||||
|
|
||||||
The call setup should appear seamless to the user as if they had simply placed
|
|
||||||
a call and the other party had accepted. Thusly, any media stream that had been
|
|
||||||
setup for use on a call should be transferred and used for the call that
|
|
||||||
replaces it.
|
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
Identity Servers
|
||||||
|
================
|
||||||
|
.. NOTE::
|
||||||
|
This section is a work in progress.
|
||||||
|
|
||||||
|
.. TODO-doc Dave
|
||||||
|
- 3PIDs and identity server, functions
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
Room History Visibility
|
Room History Visibility
|
||||||
=======================
|
-----------------------
|
||||||
|
|
||||||
Whether a member of a room can see the events that happened in a room from
|
Whether a member of a room can see the events that happened in a room from
|
||||||
before they joined the room is controlled by the ``history_visibility`` key
|
before they joined the room is controlled by the ``history_visibility`` key
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
Instant Messaging
|
||||||
|
=================
|
||||||
|
|
||||||
|
Events
|
||||||
|
------
|
||||||
|
|
||||||
|
{{m_room_message_event}}
|
||||||
|
|
||||||
|
{{m_room_message_feedback_event}}
|
||||||
|
|
||||||
|
{{m_room_name_event}}
|
||||||
|
|
||||||
|
{{m_room_topic_event}}
|
||||||
|
|
||||||
|
m.room.message msgtypes
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. TODO-spec
|
||||||
|
How a client should handle unknown message types.
|
||||||
|
|
||||||
|
|
||||||
|
Each `m.room.message`_ MUST have a ``msgtype`` key which identifies the type
|
||||||
|
of message being sent. Each type has their own required and optional keys, as
|
||||||
|
outlined below.
|
||||||
|
|
||||||
|
{{msgtype_events}}
|
||||||
|
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
Presence
|
||||||
|
========
|
||||||
|
|
||||||
|
Each user has the concept of presence information. This encodes the
|
||||||
|
"availability" of that user, suitable for display on other user's clients.
|
||||||
|
This is transmitted as an ``m.presence`` event and is one of the few events
|
||||||
|
which are sent *outside the context of a room*. The basic piece of presence
|
||||||
|
information is represented by the ``presence`` key, which is an enum of one
|
||||||
|
of the following:
|
||||||
|
|
||||||
|
- ``online`` : The default state when the user is connected to an event
|
||||||
|
stream.
|
||||||
|
- ``unavailable`` : The user is not reachable at this time.
|
||||||
|
- ``offline`` : The user is not connected to an event stream.
|
||||||
|
- ``free_for_chat`` : The user is generally willing to receive messages
|
||||||
|
moreso than default.
|
||||||
|
- ``hidden`` : Behaves as offline, but allows the user to see the client
|
||||||
|
state anyway and generally interact with client features. (Not yet
|
||||||
|
implemented in synapse).
|
||||||
|
|
||||||
|
In addition, the server maintains a timestamp of the last time it saw a
|
||||||
|
pro-active event from the user; either sending a message to a room, or
|
||||||
|
changing presence state from a lower to a higher level of availability
|
||||||
|
(thus: changing state from ``unavailable`` to ``online`` counts as a
|
||||||
|
proactive event, whereas in the other direction it will not). This timestamp
|
||||||
|
is presented via a key called ``last_active_ago``, which gives the relative
|
||||||
|
number of milliseconds since the message is generated/emitted that the user
|
||||||
|
was last seen active.
|
||||||
|
|
||||||
|
Events
|
||||||
|
------
|
||||||
|
|
||||||
|
{{presence_events}}
|
||||||
|
|
||||||
|
Presence HTTP API
|
||||||
|
-----------------
|
||||||
|
.. TODO-spec
|
||||||
|
- Define how users receive presence invites, and how they accept/decline them
|
||||||
|
|
||||||
|
{{presence_http_api}}
|
||||||
|
|
||||||
|
|
||||||
|
Events on Change of Profile Information
|
||||||
|
---------------------------------------
|
||||||
|
Because the profile displayname and avatar information are likely to be used in
|
||||||
|
many places of a client's display, changes to these fields cause an automatic
|
||||||
|
propagation event to occur, informing likely-interested parties of the new
|
||||||
|
values. This change is conveyed using two separate mechanisms:
|
||||||
|
|
||||||
|
- a ``m.room.member`` event is sent to every room the user is a member of,
|
||||||
|
to update the ``displayname`` and ``avatar_url``.
|
||||||
|
- a ``m.presence`` presence status update is sent, again containing the new values of the
|
||||||
|
``displayname`` and ``avatar_url`` keys, in addition to the required
|
||||||
|
``presence`` key containing the current presence state of the user.
|
||||||
|
|
||||||
|
Both of these should be done automatically by the home server when a user
|
||||||
|
successfully changes their displayname or avatar URL fields.
|
||||||
|
|
||||||
|
Additionally, when home servers emit room membership events for their own
|
||||||
|
users, they should include the displayname and avatar URL fields in these
|
||||||
|
events so that clients already have these details to hand, and do not have to
|
||||||
|
perform extra roundtrips to query it.
|
||||||
|
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
Voice over IP
|
||||||
|
-------------
|
||||||
|
Matrix can also be used to set up VoIP calls. This is part of the core
|
||||||
|
specification, although is at a relatively early stage. Voice (and video) over
|
||||||
|
Matrix is built on the WebRTC 1.0 standard. Call events are sent to a room, like
|
||||||
|
any other event. This means that clients must only send call events to rooms
|
||||||
|
with exactly two participants as currently the WebRTC standard is based around
|
||||||
|
two-party communication.
|
||||||
|
|
||||||
|
{{voip_events}}
|
||||||
|
|
||||||
|
Message Exchange
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
A call is set up with messages exchanged as follows:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Caller Callee
|
||||||
|
[Place Call]
|
||||||
|
m.call.invite ----------->
|
||||||
|
m.call.candidate -------->
|
||||||
|
[..candidates..] -------->
|
||||||
|
[Answers call]
|
||||||
|
<--------------- m.call.answer
|
||||||
|
[Call is active and ongoing]
|
||||||
|
<--------------- m.call.hangup
|
||||||
|
|
||||||
|
Or a rejected call:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
Caller Callee
|
||||||
|
m.call.invite ------------>
|
||||||
|
m.call.candidate --------->
|
||||||
|
[..candidates..] --------->
|
||||||
|
[Rejects call]
|
||||||
|
<-------------- m.call.hangup
|
||||||
|
|
||||||
|
Calls are negotiated according to the WebRTC specification.
|
||||||
|
|
||||||
|
|
||||||
|
Glare
|
||||||
|
~~~~~
|
||||||
|
This specification aims to address the problem of two users calling each other
|
||||||
|
at roughly the same time and their invites crossing on the wire. It is a far
|
||||||
|
better experience for the users if their calls are connected if it is clear
|
||||||
|
that their intention is to set up a call with one another.
|
||||||
|
|
||||||
|
In Matrix, calls are to rooms rather than users (even if those rooms may only
|
||||||
|
contain one other user) so we consider calls which are to the same room. The
|
||||||
|
rules for dealing with such a situation are as follows:
|
||||||
|
|
||||||
|
- If an invite to a room is received whilst the client is preparing to send an
|
||||||
|
invite to the same room, the client should cancel its outgoing call and
|
||||||
|
instead automatically accept the incoming call on behalf of the user.
|
||||||
|
- If an invite to a room is received after the client has sent an invite to
|
||||||
|
the same room and is waiting for a response, the client should perform a
|
||||||
|
lexicographical comparison of the call IDs of the two calls and use the
|
||||||
|
lesser of the two calls, aborting the greater. If the incoming call is the
|
||||||
|
lesser, the client should accept this call on behalf of the user.
|
||||||
|
|
||||||
|
The call setup should appear seamless to the user as if they had simply placed
|
||||||
|
a call and the other party had accepted. Thusly, any media stream that had been
|
||||||
|
setup for use on a call should be transferred and used for the call that
|
||||||
|
replaces it.
|
||||||
|
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
targets:
|
||||||
|
main: # arbitrary name to identify this build target
|
||||||
|
files: # the sort order of files to cat
|
||||||
|
- 0-intro.rst
|
||||||
|
- { 1: 0-feature_profiles.rst }
|
||||||
|
- 1-client_server_api.rst
|
||||||
|
- { 1: 0-events.rst }
|
||||||
|
- { 1: 0-event_signing.rst }
|
||||||
|
- 2-modules.rst
|
||||||
|
- { 1: "group:modules" } # reference a group of files
|
||||||
|
- 3-application_service_api.rst
|
||||||
|
- 4-server_server_api.rst
|
||||||
|
- 5-identity_servers.rst
|
||||||
|
- 6-appendices.rst
|
||||||
|
groups: # reusable blobs of files when prefixed with 'group:'
|
||||||
|
modules:
|
||||||
|
- modules/instant_messaging.rst
|
||||||
|
- modules/voip_events.rst
|
||||||
|
- modules/typing_notifications.rst
|
||||||
|
- modules/receipts.rst
|
||||||
|
- modules/presence.rst
|
||||||
|
- modules/content_repo.rst
|
||||||
|
- modules/end_to_end_encryption.rst
|
||||||
|
- modules/history_visibility.rst
|
||||||
|
- modules/push_overview.rst
|
||||||
|
# relative depth
|
||||||
|
- { 1: [modules/push_cs_api.rst , modules/push_push_gw_api.rst] }
|
||||||
|
|
||||||
|
title_styles: ["=", "-", "~", "+", "^"]
|
||||||
|
|
||||||
|
# The templating system doesn't know the right title style to use when generating
|
||||||
|
# RST. These symbols are 'relative' to say "make a sub-title" (-1), "make a title
|
||||||
|
# at the same level (0)", or "make a title one above (+1)". The gendoc script
|
||||||
|
# will inspect this file and replace these relative styles with actual title
|
||||||
|
# styles. The templating system will also inspect this file to know which symbols
|
||||||
|
# to inject.
|
||||||
|
relative_title_styles:
|
||||||
|
subtitle: "<"
|
||||||
|
sametitle: "/"
|
||||||
|
supertitle: ">"
|
||||||
Loading…
Reference in New Issue