Merge branch 'master' into daniel/threepidinvites-2
commit
d2c56fb7a3
@ -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,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,68 @@
|
|||||||
|
swagger: '2.0'
|
||||||
|
info:
|
||||||
|
title: "Matrix Client-Server v1 Voice over IP 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:
|
||||||
|
"/turnServer":
|
||||||
|
get:
|
||||||
|
summary: Obtain TURN server credentials.
|
||||||
|
description: |-
|
||||||
|
This API provides credentials for the client to use when initiating
|
||||||
|
calls.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The TURN server credentials.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{
|
||||||
|
"username":"1443779631:@user:example.com",
|
||||||
|
"password":"JlKfBy1QwLrO20385QyAtEyIv0=",
|
||||||
|
"uris":[
|
||||||
|
"turn:turn.example.com:3478?transport=udp",
|
||||||
|
"turn:10.20.30.40:3478?transport=tcp",
|
||||||
|
"turns:10.20.30.40:443?transport=tcp"
|
||||||
|
],
|
||||||
|
"ttl":86400
|
||||||
|
}
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
The username to use.
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: |-
|
||||||
|
The password to use.
|
||||||
|
uris:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: A list of TURN URIs
|
||||||
|
ttl:
|
||||||
|
type: integer
|
||||||
|
description: The time-to-live in seconds
|
||||||
|
required: ["username", "password", "uris", "ttl"]
|
||||||
|
429:
|
||||||
|
description: This request was rate-limited.
|
||||||
|
schema:
|
||||||
|
"$ref": "definitions/error.yaml"
|
||||||
|
|
@ -0,0 +1,68 @@
|
|||||||
|
swagger: '2.0'
|
||||||
|
info:
|
||||||
|
title: "Matrix Client-Server v2 Receipts API"
|
||||||
|
version: "1.0.0"
|
||||||
|
host: localhost:8008
|
||||||
|
schemes:
|
||||||
|
- https
|
||||||
|
- http
|
||||||
|
basePath: /_matrix/client/v2_alpha
|
||||||
|
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}/receipt/{receiptType}/{eventId}":
|
||||||
|
post:
|
||||||
|
summary: Send a receipt for the given event ID.
|
||||||
|
description: |-
|
||||||
|
This API updates the marker for the given receipt type to the event ID
|
||||||
|
specified.
|
||||||
|
security:
|
||||||
|
- accessToken: []
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: roomId
|
||||||
|
description: The room in which to send the event.
|
||||||
|
required: true
|
||||||
|
x-example: "!wefuh21ffskfuh345:example.com"
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: receiptType
|
||||||
|
description: The type of receipt to send.
|
||||||
|
required: true
|
||||||
|
x-example: "m.read"
|
||||||
|
enum: ["m.read"]
|
||||||
|
- in: path
|
||||||
|
type: string
|
||||||
|
name: eventId
|
||||||
|
description: The event ID to acknowledge up to.
|
||||||
|
required: true
|
||||||
|
x-example: "$1924376522eioj:example.com"
|
||||||
|
- in: body
|
||||||
|
description: |-
|
||||||
|
Extra receipt information to attach to ``content`` if any. The
|
||||||
|
server will automatically set the ``ts`` field.
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
example: |-
|
||||||
|
{}
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: The receipt was sent.
|
||||||
|
examples:
|
||||||
|
application/json: |-
|
||||||
|
{}
|
||||||
|
schema:
|
||||||
|
type: object # empty json object
|
||||||
|
429:
|
||||||
|
description: This request was rate-limited.
|
||||||
|
schema:
|
||||||
|
"$ref": "definitions/error.yaml"
|
@ -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,564 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
#
|
||||||
|
# htmldiff - present a diff marked version of two html documents
|
||||||
|
#
|
||||||
|
# Copyright (c) 1998-2006 MACS, Inc.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2007 SiSco, Inc.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
# a copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
# the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# See http://www.themacs.com for more information.
|
||||||
|
#
|
||||||
|
# usage: htmldiff [[-c] [-l] [-o] oldversion newversion [output]]
|
||||||
|
#
|
||||||
|
# -c - disable metahtml comment processing
|
||||||
|
# -o - disable outputting of old text
|
||||||
|
# -l - use navindex to create sequence of diffs
|
||||||
|
# oldversion - the previous version of the document
|
||||||
|
# newversion - the newer version of the document
|
||||||
|
# output - a filename to place the output in. If omitted, the output goes to
|
||||||
|
# standard output.
|
||||||
|
#
|
||||||
|
# if invoked with no options or arguments, operates as a CGI script. It then
|
||||||
|
# takes the following parameters:
|
||||||
|
#
|
||||||
|
# oldfile - the URL of the original file
|
||||||
|
# newfile - the URL of the new file
|
||||||
|
# mhtml - a flag to indicate whether it should be aware of MetaHTML comments.
|
||||||
|
#
|
||||||
|
# requires GNU diff utility
|
||||||
|
# also requires the perl modules Getopt::Std
|
||||||
|
#
|
||||||
|
# NOTE: The markup created by htmldiff may not validate against the HTML 4.0
|
||||||
|
# DTD. This is because the algorithm is realtively simple, and there are
|
||||||
|
# places in the markup content model where the span element is not allowed.
|
||||||
|
# Htmldiff is NOT aware of these places.
|
||||||
|
#
|
||||||
|
# $Source: /u/sources/public/2009/htmldiff/htmldiff.pl,v $
|
||||||
|
# $Revision: 1.1 $
|
||||||
|
#
|
||||||
|
# $Log: htmldiff.pl,v $
|
||||||
|
# Revision 1.1 2014/01/06 08:04:51 dom
|
||||||
|
# added copy of htmldiff perl script since aptest.com repo no longer available
|
||||||
|
#
|
||||||
|
# Revision 1.5 2008/03/05 13:23:16 ahby
|
||||||
|
# Fixed a problem with leading whitespace before markup.
|
||||||
|
#
|
||||||
|
# Revision 1.4 2007/12/13 13:09:16 ahby
|
||||||
|
# Updated copyright and license.
|
||||||
|
#
|
||||||
|
# Revision 1.3 2007/12/13 12:53:34 ahby
|
||||||
|
# Changed use of span to ins and del
|
||||||
|
#
|
||||||
|
# Revision 1.2 2002/02/13 16:27:23 ahby
|
||||||
|
# Changed processing model.
|
||||||
|
# Improved handling of old text and changed styles.
|
||||||
|
#
|
||||||
|
# Revision 1.1 2000/07/12 12:20:04 ahby
|
||||||
|
# Updated to remove empty spans - this fixes validation problems under
|
||||||
|
# strict.
|
||||||
|
#
|
||||||
|
# Revision 1.11 1999/12/08 19:46:45 ahby
|
||||||
|
# Fixed validation errors introduced by placing markup where it didn't
|
||||||
|
# belong.
|
||||||
|
#
|
||||||
|
# Revision 1.10 1999/10/18 13:42:58 ahby
|
||||||
|
# Added -o to the usage message.
|
||||||
|
#
|
||||||
|
# Revision 1.9 1999/05/04 12:29:11 ahby
|
||||||
|
# Added an option to turn off the display of old text.
|
||||||
|
#
|
||||||
|
# Revision 1.8 1999/04/09 14:37:27 ahby
|
||||||
|
# Fixed a perl syntax error.
|
||||||
|
#
|
||||||
|
# Revision 1.7 1999/04/09 14:35:49 ahby
|
||||||
|
# Added reference to MACS homepage.
|
||||||
|
#
|
||||||
|
# Revision 1.6 1999/04/09 14:35:09 ahby
|
||||||
|
# Added comment about validity of generated markup.
|
||||||
|
#
|
||||||
|
# Revision 1.5 1999/02/22 22:17:54 ahby
|
||||||
|
# Changed to use stylesheets.
|
||||||
|
# Changed to rely upon span.
|
||||||
|
# Changed to work around content model problems.
|
||||||
|
#
|
||||||
|
# Revision 1.4 1999/02/08 02:32:22 ahby
|
||||||
|
# Added a copyright statement.
|
||||||
|
#
|
||||||
|
# Revision 1.3 1999/02/08 02:30:40 ahby
|
||||||
|
# Added header processing.
|
||||||
|
#
|
||||||
|
# Revision 1.2 1998/12/10 17:31:31 ahby
|
||||||
|
# Fixed to escape less-thans in change blocks and to not permit change
|
||||||
|
# markup within specific elements (like TITLE).
|
||||||
|
#
|
||||||
|
# Revision 1.1 1998/11/26 00:09:22 ahby
|
||||||
|
# Initial revision
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
use Getopt::Std;
|
||||||
|
|
||||||
|
sub usage {
|
||||||
|
print STDERR "htmldiff [-c] [-o] oldversion newversion [output]\n";
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub url_encode {
|
||||||
|
my $str = shift;
|
||||||
|
$str =~ s/([\x00-\x1f\x7F-\xFF])/
|
||||||
|
sprintf ('%%%02x', ord ($1))/eg;
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
# markit - diff-mark the streams
|
||||||
|
#
|
||||||
|
# markit(file1, file2)
|
||||||
|
#
|
||||||
|
# markit relies upon GNUdiff to mark up the text.
|
||||||
|
#
|
||||||
|
# The markup is encoded using special control sequences:
|
||||||
|
#
|
||||||
|
# a block wrapped in control-a is deleted text
|
||||||
|
# a block wrapped in control-b is old text
|
||||||
|
# a block wrapped in control-c is new text
|
||||||
|
#
|
||||||
|
# The main processing loop attempts to wrap the text blocks in appropriate
|
||||||
|
# SPANs based upon the type of text that it is.
|
||||||
|
#
|
||||||
|
# When the loop encounters a < in the text, it stops the span. Then it outputs
|
||||||
|
# the element that is defined, then it restarts the span.
|
||||||
|
|
||||||
|
sub markit {
|
||||||
|
my $retval = "";
|
||||||
|
my($file1) = shift;
|
||||||
|
my($file2) = shift;
|
||||||
|
# my $old="<span class=\\\"diff-old-a\\\">deleted text: </span>%c'\012'%c'\001'%c'\012'%<%c'\012'%c'\001'%c'\012'";
|
||||||
|
my $old="%c'\012'%c'\001'%c'\012'%<%c'\012'%c'\001'%c'\012'";
|
||||||
|
my $new="%c'\012'%c'\003'%c'\012'%>%c'\012'%c'\003'%c'\012'";
|
||||||
|
my $unchanged="%=";
|
||||||
|
my $changed="%c'\012'%c'\001'%c'\012'%<%c'\012'%c'\001'%c'\012'%c'\004'%c'\012'%>%c'\012'%c'\004'%c'\012'";
|
||||||
|
if ($opt_o) {
|
||||||
|
$old = "";
|
||||||
|
$changed = "%c'\012'%c'\004'%c'\012'%>%c'\012'%c'\004'%c'\012'";
|
||||||
|
}
|
||||||
|
# my $old="%c'\002'<font color=\\\"purple\\\" size=\\\"-2\\\">deleted text:</font><s>%c'\012'%c'\001'%c'\012'%<%c'\012'%c'\001'%c'\012'</s>%c'\012'%c'\002'";
|
||||||
|
# my $new="%c'\002'<font color=\\\"purple\\\"><u>%c'\012'%c'\002'%>%c'\002'</u></font>%c'\002'%c'\012'";
|
||||||
|
# my $unchanged="%=";
|
||||||
|
# my $changed="%c'\002'<s>%c'\012'%c'\001'%c'\012'%<%c'\012'%c'\001'%c'\012'</s><font color=\\\"purple\\\"><u>%c'\002'%c'\012'%>%c'\012'%c'\002'</u></font>%c'\002'%c'\012'";
|
||||||
|
|
||||||
|
my @span;
|
||||||
|
$span[0]="</span>";
|
||||||
|
$span[1]="<del class=\"diff-old\">";
|
||||||
|
$span[2]="<del class=\"diff-old\">";
|
||||||
|
$span[3]="<ins class=\"diff-new\">";
|
||||||
|
$span[4]="<ins class=\"diff-chg\">";
|
||||||
|
|
||||||
|
my @diffEnd ;
|
||||||
|
$diffEnd[1] = '</del>';
|
||||||
|
$diffEnd[2] = '</del>';
|
||||||
|
$diffEnd[3] = '</ins>';
|
||||||
|
$diffEnd[4] = '</ins>';
|
||||||
|
|
||||||
|
my $diffcounter = 0;
|
||||||
|
|
||||||
|
open(FILE, qq(diff -d --old-group-format="$old" --new-group-format="$new" --changed-group-format="$changed" --unchanged-group-format="$unchanged" $file1 $file2 |)) || die("Diff failed: $!");
|
||||||
|
# system (qq(diff --old-group-format="$old" --new-group-format="$new" --changed-group-format="$changed" --unchanged-group-format="$unchanged" $file1 $file2 > /tmp/output));
|
||||||
|
|
||||||
|
my $state = 0;
|
||||||
|
my $inblock = 0;
|
||||||
|
my $temp = "";
|
||||||
|
my $lineCount = 0;
|
||||||
|
|
||||||
|
# strategy:
|
||||||
|
#
|
||||||
|
# process the output of diff...
|
||||||
|
#
|
||||||
|
# a link with control A-D means the start/end of the corresponding ordinal
|
||||||
|
# state (1-4). Resting state is state 0.
|
||||||
|
#
|
||||||
|
# While in a state, accumulate the contents for that state. When exiting the
|
||||||
|
# state, determine if it is appropriate to emit the contents with markup or
|
||||||
|
# not (basically, if the accumulated buffer contains only empty lines or lines
|
||||||
|
# with markup, then we don't want to emit the wrappers. We don't need them.
|
||||||
|
#
|
||||||
|
# Note that if there is markup in the "old" block, that markup is silently
|
||||||
|
# removed. It isn't really that interesting, and it messes up the output
|
||||||
|
# something fierce.
|
||||||
|
|
||||||
|
while (<FILE>) {
|
||||||
|
my $anchor = $opt_l ? qq[<a tabindex="$diffcounter">] : "" ;
|
||||||
|
my $anchorEnd = $opt_l ? q[</a>] : "" ;
|
||||||
|
$lineCount ++;
|
||||||
|
if ($state == 0) { # if we are resting and we find a marker,
|
||||||
|
# then we must be entering a block
|
||||||
|
if (m/^([\001-\004])/) {
|
||||||
|
$state = ord($1);
|
||||||
|
$_ = "";
|
||||||
|
}
|
||||||
|
# if (m/^\001/) {
|
||||||
|
# $state = 1;
|
||||||
|
# s/^/$span[1]/;
|
||||||
|
# } elsif (m/^\002/) {
|
||||||
|
# $state = 2;
|
||||||
|
# s/^/$span[2]/;
|
||||||
|
# } elsif (m/^\003/) {
|
||||||
|
# $state = 3;
|
||||||
|
# s/^/$span[3]/;
|
||||||
|
# } elsif (m/^\004/) {
|
||||||
|
# $state = 4;
|
||||||
|
# s/^/$span[4]/;
|
||||||
|
# }
|
||||||
|
} else {
|
||||||
|
# if we are in "old" state, remove markup
|
||||||
|
if (($state == 1) || ($state == 2)) {
|
||||||
|
s/\<.*\>//; # get rid of any old markup
|
||||||
|
s/\</</g; # escape any remaining STAG or ETAGs
|
||||||
|
s/\>/>/g;
|
||||||
|
}
|
||||||
|
# if we found another marker, we must be exiting the state
|
||||||
|
if (m/^([\001-\004])/) {
|
||||||
|
if ($temp ne "") {
|
||||||
|
$_ = $span[$state] . $anchor . $temp . $anchorEnd . $diffEnd[$state] . "\n";
|
||||||
|
$temp = "";
|
||||||
|
} else {
|
||||||
|
$_ = "" ;
|
||||||
|
}
|
||||||
|
$state = 0;
|
||||||
|
} elsif (m/^\s*\</) { # otherwise, is this line markup?
|
||||||
|
# if it is markup AND we haven't seen anything else yet,
|
||||||
|
# then we will emit the markup
|
||||||
|
if ($temp eq "") {
|
||||||
|
$retval .= $_;
|
||||||
|
$_ = "";
|
||||||
|
} else { # we wrap it with the state switches and hold it
|
||||||
|
s/^/$anchorEnd$diffEnd[$state]/;
|
||||||
|
s/$/$span[$state]$anchor/;
|
||||||
|
$temp .= $_;
|
||||||
|
$_ = "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (m/.+/) {
|
||||||
|
$temp .= $_;
|
||||||
|
$_ = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s/\001//g;
|
||||||
|
s/\002//g;
|
||||||
|
s/\003//g;
|
||||||
|
s/\004//g;
|
||||||
|
if ($_ !~ m/^$/) {
|
||||||
|
$retval .= $_;
|
||||||
|
}
|
||||||
|
$diffcounter++;
|
||||||
|
}
|
||||||
|
close FILE;
|
||||||
|
$retval =~ s/$span[1]\n+$diffEnd[1]//g;
|
||||||
|
$retval =~ s/$span[2]\n+$diffEnd[2]//g;
|
||||||
|
$retval =~ s/$span[3]\n+$diffEnd[3]//g;
|
||||||
|
$retval =~ s/$span[4]\n+$diffEnd[4]//g;
|
||||||
|
$retval =~ s/$span[1]\n*$//g;
|
||||||
|
$retval =~ s/$span[2]\n*$//g;
|
||||||
|
$retval =~ s/$span[3]\n*$//g;
|
||||||
|
$retval =~ s/$span[4]\n*$//g;
|
||||||
|
return $retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub splitit {
|
||||||
|
my $filename = shift;
|
||||||
|
my $headertmp = shift;
|
||||||
|
my $inheader=0;
|
||||||
|
my $preformatted=0;
|
||||||
|
my $inelement=0;
|
||||||
|
my $retval = "";
|
||||||
|
my $styles = q(<style type='text/css'>
|
||||||
|
.diff-old-a {
|
||||||
|
font-size: smaller;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-new { background-color: yellow; }
|
||||||
|
.diff-chg { background-color: lime; }
|
||||||
|
.diff-new:before,
|
||||||
|
.diff-new:after
|
||||||
|
{ content: "\2191" }
|
||||||
|
.diff-chg:before, .diff-chg:after
|
||||||
|
{ content: "\2195" }
|
||||||
|
.diff-old { text-decoration: line-through; background-color: #FBB; }
|
||||||
|
.diff-old:before,
|
||||||
|
.diff-old:after
|
||||||
|
{ content: "\2193" }
|
||||||
|
:focus { border: thin red solid}
|
||||||
|
</style>
|
||||||
|
);
|
||||||
|
if ($opt_t) {
|
||||||
|
$styles .= q(
|
||||||
|
<script type="text/javascript">
|
||||||
|
<!--
|
||||||
|
function setOldDisplay() {
|
||||||
|
for ( var s = 0; s < document.styleSheets.length; s++ ) {
|
||||||
|
var css = document.styleSheets[s];
|
||||||
|
var mydata ;
|
||||||
|
try { mydata = css.cssRules ;
|
||||||
|
if ( ! mydata ) mydata = css.rules;
|
||||||
|
for ( var r = 0; r < mydata.length; r++ ) {
|
||||||
|
if ( mydata[r].selectorText == '.diff-old' ) {
|
||||||
|
mydata[r].style.display = ( mydata[r].style.display == '' ) ? 'none'
|
||||||
|
: '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {} ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
-->
|
||||||
|
</script>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($stripheader) {
|
||||||
|
open(HEADER, ">$headertmp");
|
||||||
|
}
|
||||||
|
|
||||||
|
my $incomment = 0;
|
||||||
|
my $inhead = 1;
|
||||||
|
open(FILE, $filename) || die("File $filename cannot be opened: $!");
|
||||||
|
while (<FILE>) {
|
||||||
|
if ($inhead == 1) {
|
||||||
|
if (m/\<\/head/i) {
|
||||||
|
print HEADER $styles;
|
||||||
|
}
|
||||||
|
if (m/\<body/i) {
|
||||||
|
$inhead = 0;
|
||||||
|
print HEADER;
|
||||||
|
if ($opt_t) {
|
||||||
|
print HEADER q(
|
||||||
|
<form action=""><input type="button" onclick="setOldDisplay()" value="Show/Hide Old Content" /></form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
close HEADER;
|
||||||
|
} else {
|
||||||
|
print HEADER;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($incomment) {
|
||||||
|
if (m;-->;) {
|
||||||
|
$incomment = 0;
|
||||||
|
s/.*-->//;
|
||||||
|
} else {
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m;<!--;) {
|
||||||
|
while (m;<!--.*-->;) {
|
||||||
|
s/<!--.*?-->//;
|
||||||
|
}
|
||||||
|
if (m;<!--; ) {
|
||||||
|
$incomment = 1;
|
||||||
|
s/<!--.*//;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m/\<pre/i) {
|
||||||
|
$preformatted = 1;
|
||||||
|
}
|
||||||
|
if (m/\<\/pre\>/i) {
|
||||||
|
$preformatted = 0;
|
||||||
|
}
|
||||||
|
if ($preformatted) {
|
||||||
|
$retval .= $_;
|
||||||
|
} elsif ($mhtmlcomments && /^;;;/) {
|
||||||
|
$retval .= $_;
|
||||||
|
} else {
|
||||||
|
my @list = split(' ');
|
||||||
|
foreach $element (@list) {
|
||||||
|
if ($element =~ m/\<H[1-6]/i) {
|
||||||
|
# $inheader = 1;
|
||||||
|
}
|
||||||
|
if ($inheader == 0) {
|
||||||
|
$element =~ s/</\n</g;
|
||||||
|
$element =~ s/^\n//;
|
||||||
|
$element =~ s/>/>\n/g;
|
||||||
|
$element =~ s/\n$//;
|
||||||
|
$element =~ s/>\n([.,:!]+)/>$1/g;
|
||||||
|
}
|
||||||
|
if ($element =~ m/\<\/H[1-6]\>/i) {
|
||||||
|
$inheader = 0;
|
||||||
|
}
|
||||||
|
$retval .= "$element";
|
||||||
|
$inelement += ($element =~ s/</</g);
|
||||||
|
$inelement -= ($element =~ s/>/>/g);
|
||||||
|
if ($inelement < 0) {
|
||||||
|
$inelement = 0;
|
||||||
|
}
|
||||||
|
if (($inelement == 0) && ($inheader == 0)) {
|
||||||
|
$retval .= "\n";
|
||||||
|
} else {
|
||||||
|
$retval .= " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
undef @list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$retval .= "\n";
|
||||||
|
close FILE;
|
||||||
|
return $retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mhtmlcomments = 1;
|
||||||
|
|
||||||
|
sub cli {
|
||||||
|
getopts("clto") || usage();
|
||||||
|
|
||||||
|
if ($opt_c) {$mhtmlcomments = 0;}
|
||||||
|
|
||||||
|
if (@ARGV < 2) { usage(); }
|
||||||
|
|
||||||
|
$file1 = $ARGV[0];
|
||||||
|
$file2 = $ARGV[1];
|
||||||
|
$file3 = $ARGV[2];
|
||||||
|
|
||||||
|
$tmp = splitit($file1, $headertmp1);
|
||||||
|
open (FILE, ">$tmp1");
|
||||||
|
print FILE $tmp;
|
||||||
|
close FILE;
|
||||||
|
|
||||||
|
$tmp = splitit($file2, $headertmp2);
|
||||||
|
open (FILE, ">$tmp2");
|
||||||
|
print FILE $tmp;
|
||||||
|
close FILE;
|
||||||
|
|
||||||
|
$output = "";
|
||||||
|
|
||||||
|
if ($stripheader) {
|
||||||
|
open(FILE, $headertmp2);
|
||||||
|
while (<FILE>) {
|
||||||
|
$output .= $_;
|
||||||
|
}
|
||||||
|
close(FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output .= markit($tmp1, $tmp2);
|
||||||
|
|
||||||
|
if ($file3) {
|
||||||
|
open(FILE, ">$file3");
|
||||||
|
print FILE $output;
|
||||||
|
close FILE;
|
||||||
|
} else {
|
||||||
|
print $output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub cgi {
|
||||||
|
# use LWP::UserAgent;
|
||||||
|
# use CGI;
|
||||||
|
|
||||||
|
my $query = new CGI;
|
||||||
|
my $url1 = $query->param("oldfile");
|
||||||
|
my $url2 = $query->param("newfile");
|
||||||
|
my $mhtml = $query->param("mhtml");
|
||||||
|
|
||||||
|
my $file1 = "/tmp/htdcgi1.$$";
|
||||||
|
my $file2 = "/tmp/htdcgi2.$$";
|
||||||
|
|
||||||
|
my $ua = new LWP::UserAgent;
|
||||||
|
$ua->agent("MACS, Inc. HTMLdiff/0.9 " . $ua->agent);
|
||||||
|
|
||||||
|
# Create a request
|
||||||
|
|
||||||
|
my $req1 = new HTTP::Request GET => $url1;
|
||||||
|
|
||||||
|
my $res1 = $ua->request($req1, $file1);
|
||||||
|
if ($res1->is_error) {
|
||||||
|
print $res1->error_as_HTML();
|
||||||
|
print "<p>The URL $url1 could not be found. Please check it and try again.</p>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $req2 = new HTTP::Request GET => $url2;
|
||||||
|
|
||||||
|
my $res2 = $ua->request($req2, $file2);
|
||||||
|
if ($res2->is_error) {
|
||||||
|
print $res2->error_as_HTML();
|
||||||
|
print "<p>The URL $url2 could not be found. Please check it and try again.</p>";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$split1 = splitit($file1, $headertmp1);
|
||||||
|
open (FILE, ">$tmp1");
|
||||||
|
print FILE $split1;
|
||||||
|
close FILE;
|
||||||
|
|
||||||
|
$split2 = splitit($file2, $headertmp2);
|
||||||
|
open (FILE, ">$tmp2");
|
||||||
|
print FILE $split2;
|
||||||
|
close FILE;
|
||||||
|
|
||||||
|
$output = "";
|
||||||
|
|
||||||
|
if ($stripheader) {
|
||||||
|
open(FILE, $headertmp2);
|
||||||
|
while (<FILE>) {
|
||||||
|
$output .= $_;
|
||||||
|
}
|
||||||
|
close(FILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output .= markit($tmp1, $tmp2);
|
||||||
|
|
||||||
|
my $base=$res2->base;
|
||||||
|
|
||||||
|
if ($base !~ /\/$/) {
|
||||||
|
$base =~ s/[^\/]*$//;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $output !~ /<base/i ) {
|
||||||
|
$output =~ s/<head>/<head>\n<base href="$base">/i ||
|
||||||
|
$output =~ s/<html>/<html>\n<base href="$base">/i ;
|
||||||
|
}
|
||||||
|
|
||||||
|
print $query->header(-type=>'text/html',-nph=>1);
|
||||||
|
print $output;
|
||||||
|
|
||||||
|
unlink $file1;
|
||||||
|
unlink $file2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp1="/tmp/htdtmp1.$$";
|
||||||
|
$headertmp1="/tmp/htdhtmp1.$$";
|
||||||
|
$tmp2="/tmp/htdtmp2.$$";
|
||||||
|
$headertmp2="/tmp/htdhtmp2.$$";
|
||||||
|
$stripheader = 1;
|
||||||
|
|
||||||
|
if (@ARGV == 0) {
|
||||||
|
cgi(); # if no arguments, we must be operating as a cgi script
|
||||||
|
} else {
|
||||||
|
cli(); # if there are arguments, then we are operating as a CLI
|
||||||
|
}
|
||||||
|
|
||||||
|
unlink $tmp1;
|
||||||
|
unlink $headertmp1;
|
||||||
|
unlink $tmp2;
|
||||||
|
unlink $headertmp2;
|
@ -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,93 @@
|
|||||||
|
Feature Profiles
|
||||||
|
================
|
||||||
|
|
||||||
|
.. sect:feature-profiles:
|
||||||
|
|
||||||
|
Matrix supports many different kinds of clients: from embedded IoT devices to
|
||||||
|
desktop clients. Not all clients can provide the same feature sets as other
|
||||||
|
clients e.g. due to lack of physical hardware such as not having a screen.
|
||||||
|
Clients can fall into one of several profiles and each profile contains a set
|
||||||
|
of features that the client MUST support. This section details a set of
|
||||||
|
"feature profiles". Clients are expected to implement a profile in its entirety
|
||||||
|
in order for it to be classified as that profile.
|
||||||
|
|
||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
===================================== ========== ========== ========== ========== ==========
|
||||||
|
Module / Profile Web Mobile Desktop CLI Embedded
|
||||||
|
===================================== ========== ========== ========== ========== ==========
|
||||||
|
`Instant Messaging`_ Required Required Required Required Optional
|
||||||
|
`Presence`_ Required Required Required Required Optional
|
||||||
|
`Push Notifications`_ Optional Required Optional Optional Optional
|
||||||
|
`Receipts`_ Required Required Required Required Optional
|
||||||
|
`Typing Notifications`_ Required Required Required Required Optional
|
||||||
|
`VoIP`_ Required Required Required Optional Optional
|
||||||
|
`Content Repository`_ Required Required Required Optional Optional
|
||||||
|
`Managing History Visibility`_ Required Required Required Required Optional
|
||||||
|
`End-to-End Encryption`_ Optional Optional Optional Optional Optional
|
||||||
|
===================================== ========== ========== ========== ========== ==========
|
||||||
|
|
||||||
|
*Please see each module for more details on what clients need to implement.*
|
||||||
|
|
||||||
|
.. _End-to-End Encryption: `module:e2e`_
|
||||||
|
.. _Instant Messaging: `module:im`_
|
||||||
|
.. _Presence: `module:presence`_
|
||||||
|
.. _Push Notifications: `module:push`_
|
||||||
|
.. _Receipts: `module:receipts`_
|
||||||
|
.. _Typing Notifications: `module:typing`_
|
||||||
|
.. _VoIP: `module:voip`_
|
||||||
|
.. _Content Repository: `module:content`_
|
||||||
|
.. _Managing History Visibility: `module:history-visibility`_
|
||||||
|
|
||||||
|
Clients
|
||||||
|
-------
|
||||||
|
|
||||||
|
Stand-alone web (``Web``)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is a web page which heavily uses Matrix for communication. Single-page web
|
||||||
|
apps would be classified as a stand-alone web client, as would multi-page web
|
||||||
|
apps which use Matrix on nearly every page.
|
||||||
|
|
||||||
|
Mobile (``Mobile``)
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is a Matrix client specifically designed for consumption on mobile devices.
|
||||||
|
This is typically a mobile app but need not be so provided the feature set can
|
||||||
|
be reached (e.g. if a mobile site could display push notifications it could be
|
||||||
|
classified as a mobile client).
|
||||||
|
|
||||||
|
Desktop (``Desktop``)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is a native GUI application which can run in its own environment outside a
|
||||||
|
browser.
|
||||||
|
|
||||||
|
Command Line Interface (``CLI``)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is a client which is used via a text-based terminal.
|
||||||
|
|
||||||
|
Embedded (``Embedded``)
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is a client which is embedded into another application or an embedded
|
||||||
|
device.
|
||||||
|
|
||||||
|
Application
|
||||||
|
+++++++++++
|
||||||
|
|
||||||
|
This is a Matrix client which is embedded in another website, e.g. using
|
||||||
|
iframes. These embedded clients are typically for a single purpose
|
||||||
|
related to the website in question, and are not intended to be fully-fledged
|
||||||
|
communication apps.
|
||||||
|
|
||||||
|
Device
|
||||||
|
++++++
|
||||||
|
|
||||||
|
This is a client which is typically running on an embedded device such as a
|
||||||
|
kettle, fridge or car. These clients tend to perform a few operations and run
|
||||||
|
in a resource constrained environment. Like embedded applications, they are
|
||||||
|
not intended to be fully-fledged communication systems.
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
|||||||
Receipts
|
|
||||||
========
|
|
||||||
|
|
||||||
Receipts are used to publish which events in a room the user or their devices
|
|
||||||
have interacted with. For example, which events the user has read.
|
|
||||||
|
|
||||||
For efficiency this is done as "up to" markers, i.e. marking a particular event
|
|
||||||
as, say, ``read`` indicates the user has read all events *up to* that event.
|
|
||||||
|
|
||||||
Client-Server API
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Clients will receive receipts in the following format::
|
|
||||||
|
|
||||||
{
|
|
||||||
"type": "m.receipt",
|
|
||||||
"room_id": <room_id>,
|
|
||||||
"content": {
|
|
||||||
<event_id>: {
|
|
||||||
<receipt_type>: {
|
|
||||||
<user_id>: { "ts": <ts>, ... },
|
|
||||||
...
|
|
||||||
}
|
|
||||||
},
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
For example::
|
|
||||||
|
|
||||||
{
|
|
||||||
"type": "m.receipt",
|
|
||||||
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
|
|
||||||
"content": {
|
|
||||||
"$1435641916114394fHBLK:matrix.org": {
|
|
||||||
"read": {
|
|
||||||
"@erikj:jki.re": { "ts": 1436451550453 },
|
|
||||||
...
|
|
||||||
}
|
|
||||||
},
|
|
||||||
...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
For efficiency, receipts are batched into one event per room. In the initialSync
|
|
||||||
and v2 sync APIs the receipts are listed in a seperate top level ``receipts``
|
|
||||||
key.
|
|
||||||
|
|
||||||
Each ``user_id``, ``receipt_type`` pair must be associated with only a single
|
|
||||||
``event_id``.
|
|
||||||
|
|
||||||
New receipts that come down the event streams are deltas. Deltas update
|
|
||||||
existing mappings, clobbering based on ``user_id``, ``receipt_type`` pairs.
|
|
||||||
|
|
||||||
|
|
||||||
A client can update the markers for its user by issuing a request::
|
|
||||||
|
|
||||||
POST /_matrix/client/v2_alpha/rooms/<room_id>/receipt/read/<event_id>
|
|
||||||
|
|
||||||
Where the contents of the ``POST`` will be included in the content sent to
|
|
||||||
other users. The server will automatically set the ``ts`` field.
|
|
||||||
|
|
||||||
|
|
||||||
Server-Server API
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Receipts are sent across federation as EDUs with type ``m.receipt``. The
|
|
||||||
format of the EDUs are::
|
|
||||||
|
|
||||||
{
|
|
||||||
<room_id>: {
|
|
||||||
<receipt_type>: {
|
|
||||||
<user_id>: { <content> }
|
|
||||||
},
|
|
||||||
...
|
|
||||||
},
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
These are always sent as deltas to previously sent reciepts.
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
Identity Servers
|
||||||
|
================
|
||||||
|
.. NOTE::
|
||||||
|
This section is a work in progress.
|
||||||
|
|
||||||
|
.. TODO-doc Dave
|
||||||
|
- 3PIDs and identity server, functions
|
||||||
|
|
@ -0,0 +1,56 @@
|
|||||||
|
Module Heading
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. _module:short-name:
|
||||||
|
|
||||||
|
A short summary of the module. What features does this module provide? An anchor
|
||||||
|
should be specified at the top of the module using the format ``module:name``.
|
||||||
|
|
||||||
|
Complicated modules may wish to have architecture diagrams or event flows
|
||||||
|
(e.g. VoIP call flows) here. Custom subsections can be included but they should
|
||||||
|
be used *sparingly* to reduce the risk of putting client or server behaviour
|
||||||
|
information in these custom sections.
|
||||||
|
|
||||||
|
Events
|
||||||
|
------
|
||||||
|
List the new event types introduced by this module, if any. If there are no
|
||||||
|
new events, this section can be omitted. Event types should be done as
|
||||||
|
subsections. This section is intended to document the "common shared event
|
||||||
|
structure" between client and server. Deviations from this shared structure
|
||||||
|
should be documented in the relevant behaviour section.
|
||||||
|
|
||||||
|
``m.example.event.type``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
There should be JSON Schema docs for this event. Once there is JSON schema,
|
||||||
|
there will be a template variable with dots in the event type replaced with
|
||||||
|
underscores and the suffix ``_event``. You can insert a template like so:
|
||||||
|
|
||||||
|
{{m_example_event_type_event}}
|
||||||
|
|
||||||
|
Client behaviour
|
||||||
|
----------------
|
||||||
|
List any new HTTP endpoints. These endpoints should be documented using Swagger.
|
||||||
|
Once there is Swagger, there will be a template variable based on the name of
|
||||||
|
the YAML file with the suffix ``_http_api``. You can insert a template for
|
||||||
|
swagger docs like so:
|
||||||
|
|
||||||
|
{{name-of-yaml-file-without-file-ext_http_api}}
|
||||||
|
|
||||||
|
List the steps the client needs to take to
|
||||||
|
correctly process this module. List what data structures the client should be
|
||||||
|
storing in order to aid implementation.
|
||||||
|
|
||||||
|
Server behaviour
|
||||||
|
----------------
|
||||||
|
Does the server need to handle any of the new events in a special way (e.g.
|
||||||
|
typing timeouts, presence). Advice on how to persist events and/or requests are
|
||||||
|
recommended to aid implementation. Federation-specific logic should be included
|
||||||
|
here.
|
||||||
|
|
||||||
|
Security considerations
|
||||||
|
-----------------------
|
||||||
|
This includes privacy leaks: for example leaking presence info. How do
|
||||||
|
misbehaving clients or servers impact this module? This section should always be
|
||||||
|
included, if only to say "we've thought about it but there isn't anything to do
|
||||||
|
here".
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
End-to-End Encryption
|
End-to-End Encryption
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
|
.. _module:e2e:
|
||||||
|
|
||||||
.. TODO-doc
|
.. TODO-doc
|
||||||
- Why is this needed.
|
- Why is this needed.
|
||||||
- Overview of process
|
- Overview of process
|
@ -1,5 +1,7 @@
|
|||||||
Room History Visibility
|
Room History Visibility
|
||||||
=======================
|
-----------------------
|
||||||
|
|
||||||
|
.. _module: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,29 @@
|
|||||||
|
Instant Messaging
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. _module:im:
|
||||||
|
|
||||||
|
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,65 @@
|
|||||||
|
Presence
|
||||||
|
========
|
||||||
|
|
||||||
|
.. _module: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.
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
Push Notifications
|
Push Notifications
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
.. _module:push:
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
--------
|
--------
|
||||||
|
|
@ -0,0 +1,84 @@
|
|||||||
|
Receipts
|
||||||
|
========
|
||||||
|
|
||||||
|
.. _module:receipts:
|
||||||
|
|
||||||
|
This module adds in support for receipts. These receipts are a form of
|
||||||
|
acknowledgement of an event. This module defines a single acknowledgement:
|
||||||
|
``m.read`` which indicates that the user has read up to a given event.
|
||||||
|
|
||||||
|
Sending a receipt for each event can result in sending large amounts of traffic
|
||||||
|
to a homeserver. To prevent this from becoming a problem, receipts are implemented
|
||||||
|
using "up to" markers. This marker indicates that the acknowledgement applies
|
||||||
|
to all events "up to and including" the event specified. For example, marking
|
||||||
|
an event as "read" would indicate that the user had read all events *up to* the
|
||||||
|
referenced event.
|
||||||
|
|
||||||
|
Events
|
||||||
|
------
|
||||||
|
Each ``user_id``, ``receipt_type`` pair must be associated with only a
|
||||||
|
single ``event_id``.
|
||||||
|
|
||||||
|
{{m_receipt_event}}
|
||||||
|
|
||||||
|
Client behaviour
|
||||||
|
----------------
|
||||||
|
|
||||||
|
In v1 ``/initialSync``, receipts are listed in a separate top level ``receipts``
|
||||||
|
key. In v2 ``/sync``, receipts are contained in the ``ephemeral`` block for a
|
||||||
|
room. New receipts that come down the event streams are deltas which update
|
||||||
|
existing mappings. Clients should replace older receipt acknowledgements based
|
||||||
|
on ``user_id`` and ``receipt_type`` pairs. For example::
|
||||||
|
|
||||||
|
Client receives m.receipt:
|
||||||
|
user = @alice:example.com
|
||||||
|
receipt_type = m.read
|
||||||
|
event_id = $aaa:example.com
|
||||||
|
|
||||||
|
Client receives another m.receipt:
|
||||||
|
user = @alice:example.com
|
||||||
|
receipt_type = m.read
|
||||||
|
event_id = $bbb:example.com
|
||||||
|
|
||||||
|
The client should replace the older acknowledgement for $aaa:example.com with
|
||||||
|
this one for $bbb:example.com
|
||||||
|
|
||||||
|
Clients should send read receipts when there is some certainty that the event in
|
||||||
|
question has been **displayed** to the user. Simply receiving an event does not
|
||||||
|
provide enough certainty that the user has seen the event. The user SHOULD need
|
||||||
|
to *take some action* such as viewing the room that the event was sent to or
|
||||||
|
dismissing a notification in order for the event to count as "read".
|
||||||
|
|
||||||
|
A client can update the markers for its user by interacting with the following
|
||||||
|
HTTP APIs.
|
||||||
|
|
||||||
|
{{v2_receipts_http_api}}
|
||||||
|
|
||||||
|
Server behaviour
|
||||||
|
----------------
|
||||||
|
|
||||||
|
For efficiency, receipts SHOULD be batched into one event per room before
|
||||||
|
delivering them to clients.
|
||||||
|
|
||||||
|
Receipts are sent across federation as EDUs with type ``m.receipt``. The
|
||||||
|
format of the EDUs are::
|
||||||
|
|
||||||
|
{
|
||||||
|
<room_id>: {
|
||||||
|
<receipt_type>: {
|
||||||
|
<user_id>: { <content> }
|
||||||
|
},
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
These are always sent as deltas to previously sent receipts. Currently only a
|
||||||
|
single ``<receipt_type>`` should be used: ``m.read``.
|
||||||
|
|
||||||
|
Security considerations
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
As receipts are sent outside the context of the event graph, there are no
|
||||||
|
integrity checks performed on the contents of ``m.receipt`` events.
|
||||||
|
|
@ -0,0 +1,101 @@
|
|||||||
|
Voice over IP
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. _module:voip:
|
||||||
|
|
||||||
|
This module outlines how two users in a room can set up a Voice over IP (VoIP)
|
||||||
|
call to each other. Voice and video calls are built upon the WebRTC 1.0 standard.
|
||||||
|
Call signalling is achieved by sending `message events`_ to the room. As a result,
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. _message events: `sect:events`_
|
||||||
|
|
||||||
|
Events
|
||||||
|
------
|
||||||
|
|
||||||
|
{{voip_events}}
|
||||||
|
|
||||||
|
Client behaviour
|
||||||
|
----------------
|
||||||
|
|
||||||
|
A call is set up with message events 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
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
"Glare" is a problem which occurs when two users call each other at roughly the
|
||||||
|
same time. This results in the call failing to set up as there already is an
|
||||||
|
incoming/outgoing call. A glare resolution algorithm can be used to determine
|
||||||
|
which call to hangup and which call to answer. If both clients implement the
|
||||||
|
same algorithm then they will both select the same call and the call will be
|
||||||
|
successfully connected.
|
||||||
|
|
||||||
|
|
||||||
|
As calls are "placed" to rooms rather than users, the glare resolution algorithm
|
||||||
|
outlined below is only considered for calls which are to the same room. The
|
||||||
|
algorithm is as follows:
|
||||||
|
|
||||||
|
- If an ``m.call.invite`` to a room is received whilst the client is
|
||||||
|
**preparing to send** an ``m.call.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 ``m.call.invite`` to a room is received **after the client has sent**
|
||||||
|
an ``m.call.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. This means any media stream that had been
|
||||||
|
setup for use on a call should be transferred and used for the call that
|
||||||
|
replaces it.
|
||||||
|
|
||||||
|
Server behaviour
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The homeserver MAY provide a TURN server which clients can use to contact the
|
||||||
|
remote party. The following HTTP API endpoints will be used by clients in order
|
||||||
|
to get information about the TURN server.
|
||||||
|
|
||||||
|
{{voip_http_api}}
|
||||||
|
|
||||||
|
|
||||||
|
Security considerations
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Calls should only be placed to rooms with one other user in them. If they are
|
||||||
|
placed to group chat rooms it is possible that another user will intercept and
|
||||||
|
answer the call.
|
||||||
|
|
@ -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-client_server_api.rst
|
||||||
|
- { 1: 0-events.rst }
|
||||||
|
- { 1: 0-event_signing.rst }
|
||||||
|
- 2-modules.rst
|
||||||
|
- { 1: 0-feature_profiles.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