Merge branch 'master' of github.com:matrix-org/matrix-doc into erikj/disable_federation
This commit is contained in:
commit
3b4c3522e6
37 changed files with 1138 additions and 423 deletions
|
@ -6,6 +6,34 @@
|
|||
.. in Jenkins. Comments like this are ignored by both RST and the templating
|
||||
.. system. Add the newest release notes beneath this comment.
|
||||
|
||||
Specification changes in v0.2.0 (2015-10-02)
|
||||
============================================
|
||||
|
||||
This update fundamentally restructures the specification. The specification has
|
||||
been split into more digestible "modules" which each describe a particular
|
||||
function (e.g. typing). This was done in order make the specification easier to
|
||||
maintain and help define which modules are mandatory for certain types
|
||||
of clients. Types of clients along with the mandatory modules can be found in a
|
||||
new "Feature Profiles" section. This update also begins to aggressively
|
||||
standardise on using Swagger and JSON Schema to document HTTP endpoints and
|
||||
Events respectively. It also introduces a number of new concepts to Matrix.
|
||||
|
||||
Additions:
|
||||
- New section: Feature Profiles.
|
||||
- New section: Receipts.
|
||||
- New section: Room history visibility.
|
||||
- New event: ``m.receipt``.
|
||||
- New event: ``m.room.canonical_alias``
|
||||
- New event: ``m.room.history_visibility``
|
||||
- New keys: ``/createRoom`` - allows room "presets" using ``preset`` and
|
||||
``initial_state`` keys.
|
||||
- New endpoint: ``/tokenrefresh`` - Related to refreshing access tokens.
|
||||
|
||||
Modifications:
|
||||
- Convert most of the older HTTP APIs to Swagger documentation.
|
||||
- Convert most of the older event formats to JSON Schema.
|
||||
- Move selected client-server sections to be "Modules".
|
||||
|
||||
Specification changes in v0.1.0 (2015-06-01)
|
||||
============================================
|
||||
- First numbered release.
|
||||
|
|
|
@ -34,7 +34,7 @@ def check_parameter(filepath, request, parameter):
|
|||
example = None
|
||||
try:
|
||||
example_json = schema.get('example')
|
||||
if example_json:
|
||||
if example_json and not schema.get("format") == "byte":
|
||||
example = json.loads(example_json)
|
||||
except Exception as e:
|
||||
raise ValueError("Error parsing JSON example request for %r" % (
|
||||
|
|
|
@ -15,16 +15,22 @@ paths:
|
|||
summary: Upload some content to the content repository.
|
||||
produces: ["application/json"]
|
||||
parameters:
|
||||
- in: header
|
||||
name: Content-Type
|
||||
type: string
|
||||
description: The content type of the file being uploaded
|
||||
x-example: "Content-Type: audio/mpeg"
|
||||
- in: body
|
||||
name: content
|
||||
name: "<content>"
|
||||
description: The content to be uploaded.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
example: "<bytes>"
|
||||
format: byte
|
||||
responses:
|
||||
200:
|
||||
description: Information about the uploaded content.
|
||||
description: The MXC URI for the uploaded content.
|
||||
schema:
|
||||
type: object
|
||||
required: ["content_uri"]
|
||||
|
@ -32,6 +38,11 @@ paths:
|
|||
content_uri:
|
||||
type: string
|
||||
description: "The MXC URI to the uploaded content."
|
||||
examples:
|
||||
"application/json": |-
|
||||
{
|
||||
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
|
||||
}
|
||||
"/download/{serverName}/{mediaId}":
|
||||
get:
|
||||
summary: "Download content from the content repository."
|
||||
|
@ -40,18 +51,27 @@ paths:
|
|||
- in: path
|
||||
type: string
|
||||
name: serverName
|
||||
x-example: matrix.org
|
||||
required: true
|
||||
description: |
|
||||
The server name from the ``mxc://`` URI (the authoritory component)
|
||||
- in: path
|
||||
type: string
|
||||
name: mediaId
|
||||
x-example: ascERGshawAWawugaAcauga
|
||||
required: true
|
||||
description: |
|
||||
The media ID from the ``mxc://`` URI (the path component)
|
||||
responses:
|
||||
200:
|
||||
description: "The content downloaded."
|
||||
description: "The content that was previously uploaded."
|
||||
headers:
|
||||
Content-Type:
|
||||
description: "The content type of the file that was previously uploaded."
|
||||
type: "string"
|
||||
Content-Disposition:
|
||||
description: "The name of the file that was previously uploaded, if set."
|
||||
type: "string"
|
||||
schema:
|
||||
type: file
|
||||
"/thumbnail/{serverName}/{mediaId}":
|
||||
|
@ -63,30 +83,44 @@ paths:
|
|||
type: string
|
||||
name: serverName
|
||||
required: true
|
||||
x-example: matrix.org
|
||||
description: |
|
||||
The server name from the ``mxc://`` URI (the authoritory component)
|
||||
- in: path
|
||||
type: string
|
||||
name: mediaId
|
||||
x-example: ascERGshawAWawugaAcauga
|
||||
required: true
|
||||
description: |
|
||||
The media ID from the ``mxc://`` URI (the path component)
|
||||
- in: query
|
||||
type: integer
|
||||
x-example: 64
|
||||
name: width
|
||||
description: The desired width of the thumbnail.
|
||||
description: |-
|
||||
The *desired* width of the thumbnail. The actual thumbnail may not
|
||||
match the size specified.
|
||||
- in: query
|
||||
type: integer
|
||||
x-example: 64
|
||||
name: height
|
||||
description: The desired height of the thumbnail.
|
||||
description: |-
|
||||
The *desired* height of the thumbnail. The actual thumbnail may not
|
||||
match the size specified.
|
||||
- in: query
|
||||
type: string
|
||||
enum: ["crop", "scale"]
|
||||
name: method
|
||||
x-example: "scale"
|
||||
description: The desired resizing method.
|
||||
responses:
|
||||
200:
|
||||
description: "A thumbnail of the requested content."
|
||||
headers:
|
||||
Content-Type:
|
||||
description: "The content type of the thumbnail."
|
||||
type: "string"
|
||||
enum: ["image/jpeg", "image/png"]
|
||||
schema:
|
||||
type: file
|
||||
|
||||
|
|
|
@ -267,6 +267,12 @@ paths:
|
|||
type: string
|
||||
description: "The user's membership state in this room."
|
||||
enum: ["invite", "join", "leave", "ban"]
|
||||
invite:
|
||||
type: object
|
||||
title: "InviteEvent"
|
||||
description: "The invite event if ``membership`` is ``invite``"
|
||||
allOf:
|
||||
- "$ref": "v1-event-schema/m.room.member"
|
||||
messages:
|
||||
type: object
|
||||
title: PaginationChunk
|
||||
|
|
77
api/client-server/v1/typing.yaml
Normal file
77
api/client-server/v1/typing.yaml
Normal file
|
@ -0,0 +1,77 @@
|
|||
swagger: '2.0'
|
||||
info:
|
||||
title: "Matrix Client-Server v1 Typing 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}/typing/{userId}":
|
||||
put:
|
||||
summary: Informs the server that the user has started or stopped typing.
|
||||
description: |-
|
||||
This tells the server that the user is typing for the next N
|
||||
milliseconds where N is the value specified in the ``timeout`` key.
|
||||
Alternatively, if ``typing`` is ``false``, it tells the server that the
|
||||
user has stopped typing.
|
||||
security:
|
||||
- accessToken: []
|
||||
parameters:
|
||||
- in: path
|
||||
type: string
|
||||
name: userId
|
||||
description: The user who has started to type.
|
||||
required: true
|
||||
x-example: "@alice:example.com"
|
||||
- in: path
|
||||
type: string
|
||||
name: roomId
|
||||
description: The room in which the user is typing.
|
||||
required: true
|
||||
x-example: "!wefh3sfukhs:example.com"
|
||||
- in: body
|
||||
name: typingState
|
||||
description: The current typing state.
|
||||
required: true
|
||||
schema:
|
||||
type: object
|
||||
example: |-
|
||||
{
|
||||
"typing": true,
|
||||
"timeout": 30000
|
||||
}
|
||||
properties:
|
||||
typing:
|
||||
type: boolean
|
||||
description: |-
|
||||
Whether the user is typing or not. If ``false``, the ``timeout``
|
||||
key can be omitted.
|
||||
timeout:
|
||||
type: integer
|
||||
description: The length of time in milliseconds to mark this user as typing.
|
||||
required: ["typing"]
|
||||
responses:
|
||||
200:
|
||||
description: The new typing state was set.
|
||||
examples:
|
||||
application/json: |-
|
||||
{}
|
||||
schema:
|
||||
type: object # empty json object
|
||||
429:
|
||||
description: This request was rate-limited.
|
||||
schema:
|
||||
"$ref": "definitions/error.yaml"
|
||||
|
68
api/client-server/v1/voip.yaml
Normal file
68
api/client-server/v1/voip.yaml
Normal file
|
@ -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"
|
||||
|
68
api/client-server/v2_alpha/receipts.yaml
Normal file
68
api/client-server/v2_alpha/receipts.yaml
Normal file
|
@ -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"
|
|
@ -10,6 +10,6 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"nopt": "^3.0.2",
|
||||
"swagger-parser": "^2.4.1"
|
||||
"swagger-parser": "^3.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,10 @@ if (!opts.schema) {
|
|||
}
|
||||
|
||||
|
||||
var errFn = function(err, api, metadata) {
|
||||
var errFn = function(err, api) {
|
||||
if (!err) {
|
||||
return;
|
||||
}
|
||||
console.log(metadata);
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
};
|
||||
|
@ -46,11 +45,12 @@ if (isDir) {
|
|||
files.forEach(function(f) {
|
||||
var suffix = ".yaml";
|
||||
if (f.indexOf(suffix, f.length - suffix.length) > 0) {
|
||||
parser.parse(path.join(opts.schema, f), function(err, api, metadata) {
|
||||
parser.validate(path.join(opts.schema, f), function(err, api, metadata) {
|
||||
if (!err) {
|
||||
console.log("%s is valid.", f);
|
||||
}
|
||||
else {
|
||||
console.error("%s is not valid.", f);
|
||||
errFn(err, api, metadata);
|
||||
}
|
||||
});
|
||||
|
@ -59,12 +59,12 @@ if (isDir) {
|
|||
});
|
||||
}
|
||||
else{
|
||||
parser.parse(opts.schema, function(err, api, metadata) {
|
||||
parser.validate(opts.schema, function(err, api) {
|
||||
if (!err) {
|
||||
console.log("%s is valid", opts.schema);
|
||||
}
|
||||
else {
|
||||
errFn(err, api, metadata);
|
||||
errFn(err, api);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
|
||||
"content": {
|
||||
"$1435641916114394fHBLK:matrix.org": {
|
||||
"read": {
|
||||
"m.read": {
|
||||
"@rikj:jki.re": {
|
||||
"ts": 1436451550453
|
||||
}
|
||||
|
|
7
event-schemas/examples/v1/m.typing
Normal file
7
event-schemas/examples/v1/m.typing
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "m.typing",
|
||||
"room_id": "!z0mnsuiwhifuhwwfw:matrix.org",
|
||||
"content": {
|
||||
"user_ids": ["@alice:matrix.org", "@bob:example.com"]
|
||||
}
|
||||
}
|
|
@ -5,26 +5,32 @@
|
|||
"properties": {
|
||||
"content": {
|
||||
"type": "object",
|
||||
"description": "The event ids which the receipts relate to.",
|
||||
"patternProperties": {
|
||||
"^\\$": {
|
||||
"type": "object",
|
||||
"description": "The types of the receipts.",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"description": "User ids of the receipts",
|
||||
"patternProperties": {
|
||||
"^@": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ts": {
|
||||
"type": "number",
|
||||
"description": "The timestamp the receipt was sent at"
|
||||
"x-pattern": "$EVENT_ID",
|
||||
"title": "Receipts",
|
||||
"description": "The mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of the event being acknowledged and *not* an ID for the receipt itself.",
|
||||
"properties": {
|
||||
"m.read": {
|
||||
"type": "object",
|
||||
"title": "Users",
|
||||
"description": "A collection of users who have sent ``m.read`` receipts for this event.",
|
||||
"patternProperties": {
|
||||
"^@": {
|
||||
"type": "object",
|
||||
"title": "Receipt",
|
||||
"description": "The mapping of user ID to receipt. The user ID is the entity who sent this receipt.",
|
||||
"x-pattern": "$USER_ID",
|
||||
"properties": {
|
||||
"ts": {
|
||||
"type": "number",
|
||||
"description": "The timestamp the receipt was sent at."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -32,6 +32,26 @@
|
|||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["m.room.member"]
|
||||
},
|
||||
"invite_room_state": {
|
||||
"type": "array",
|
||||
"description": "A subset of the state of the room at the time of the invite, if ``membership`` is ``invite``",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"title": "StateEvent",
|
||||
"description": "A stripped down state event, with only the ``type``, ``state_key`` and ``content`` keys.",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"state_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
28
event-schemas/schema/v1/m.typing
Normal file
28
event-schemas/schema/v1/m.typing
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"type": "object",
|
||||
"title": "Typing Event",
|
||||
"description": "Informs the client of the list of users currently typing.",
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "The list of user IDs typing in this room, if any."
|
||||
}
|
||||
},
|
||||
"required": ["user_ids"]
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["m.typing"]
|
||||
},
|
||||
"room_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "room_id", "content"]
|
||||
}
|
|
@ -17,6 +17,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
fsnotify "gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
@ -67,7 +68,6 @@ func watchFS(ch chan struct{}, w *fsnotify.Watcher) {
|
|||
select {
|
||||
case e := <-w.Events:
|
||||
if filter(e) {
|
||||
wg.Add(1)
|
||||
fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name)
|
||||
ch <- struct{}{}
|
||||
}
|
||||
|
@ -98,6 +98,11 @@ func filter(e fsnotify.Event) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Ignore the .git directory - It's very noisy
|
||||
if strings.Contains(e.Name, "/.git/") {
|
||||
return false
|
||||
}
|
||||
|
||||
// Avoid infinite cycles being caused by writing actual output
|
||||
if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") {
|
||||
return false
|
||||
|
@ -133,8 +138,20 @@ func populateOnce(dir string) {
|
|||
}
|
||||
|
||||
func doPopulate(ch chan struct{}, dir string) {
|
||||
for _ = range ch {
|
||||
populateOnce(dir)
|
||||
var pending int
|
||||
for {
|
||||
select {
|
||||
case <-ch:
|
||||
if pending == 0 {
|
||||
wg.Add(1)
|
||||
}
|
||||
pending++
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
if pending > 0 {
|
||||
pending = 0
|
||||
populateOnce(dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -244,10 +244,6 @@ div.viewcode-block:target {
|
|||
border-bottom: 1px solid #ac9;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul li dd {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
@ -282,3 +278,9 @@ td[colspan]:not([colspan="1"]) {
|
|||
thead {
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
div.admonition-rationale {
|
||||
background-color: #efe;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PullRequest struct {
|
||||
|
@ -58,24 +59,42 @@ func (u *User) IsTrusted() bool {
|
|||
return allowedMembers[u.Login]
|
||||
}
|
||||
|
||||
const pullsPrefix = "https://api.github.com/repos/matrix-org/matrix-doc/pulls"
|
||||
const (
|
||||
pullsPrefix = "https://api.github.com/repos/matrix-org/matrix-doc/pulls"
|
||||
matrixDocCloneURL = "https://github.com/matrix-org/matrix-doc.git"
|
||||
)
|
||||
|
||||
func gitClone(url string, shared bool) (string, error) {
|
||||
directory := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10))
|
||||
cmd := exec.Command("git", "clone", url, directory)
|
||||
if shared {
|
||||
cmd.Args = append(cmd.Args, "--shared")
|
||||
}
|
||||
|
||||
func gitClone(url string) (string, error) {
|
||||
dst := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10))
|
||||
cmd := exec.Command("git", "clone", url, dst)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error cloning repo: %v", err)
|
||||
}
|
||||
return dst, nil
|
||||
return directory, nil
|
||||
}
|
||||
|
||||
func gitCheckout(path, sha string) error {
|
||||
cmd := exec.Command("git", "checkout", sha)
|
||||
return runGitCommand(path, []string{"checkout", sha})
|
||||
}
|
||||
|
||||
func gitFetchAndMerge(path string) error {
|
||||
if err := runGitCommand(path, []string{"fetch"}); err != nil {
|
||||
return err
|
||||
}
|
||||
return runGitCommand(path, []string{"merge"})
|
||||
}
|
||||
|
||||
func runGitCommand(path string, args []string) error {
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = path
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error checking out repo: %v", err)
|
||||
return fmt.Errorf("error running %q: %v", strings.Join(cmd.Args, " "), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -119,10 +138,18 @@ func writeError(w http.ResponseWriter, code int, err error) {
|
|||
io.WriteString(w, fmt.Sprintf("%v\n", err))
|
||||
}
|
||||
|
||||
type server struct {
|
||||
matrixDocCloneURL string
|
||||
}
|
||||
|
||||
// generateAt generates spec from repo at sha.
|
||||
// Returns the path where the generation was done.
|
||||
func generateAt(repo, sha string) (dst string, err error) {
|
||||
dst, err = gitClone(repo)
|
||||
func (s *server) generateAt(sha string) (dst string, err error) {
|
||||
err = gitFetchAndMerge(s.matrixDocCloneURL)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dst, err = gitClone(s.matrixDocCloneURL, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -135,21 +162,28 @@ func generateAt(repo, sha string) (dst string, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func serveSpec(w http.ResponseWriter, req *http.Request) {
|
||||
pr, err := lookupPullRequest(*req.URL, "/spec")
|
||||
if err != nil {
|
||||
writeError(w, 400, err)
|
||||
return
|
||||
func (s *server) serveSpec(w http.ResponseWriter, req *http.Request) {
|
||||
var sha string
|
||||
|
||||
if strings.ToLower(req.URL.Path) == "/spec/head" {
|
||||
sha = "HEAD"
|
||||
} else {
|
||||
pr, err := lookupPullRequest(*req.URL, "/spec")
|
||||
if err != nil {
|
||||
writeError(w, 400, err)
|
||||
return
|
||||
}
|
||||
|
||||
// We're going to run whatever Python is specified in the pull request, which
|
||||
// may do bad things, so only trust people we trust.
|
||||
if err := checkAuth(pr); err != nil {
|
||||
writeError(w, 403, err)
|
||||
return
|
||||
}
|
||||
sha = pr.Head.SHA
|
||||
}
|
||||
|
||||
// We're going to run whatever Python is specified in the pull request, which
|
||||
// may do bad things, so only trust people we trust.
|
||||
if err := checkAuth(pr); err != nil {
|
||||
writeError(w, 403, err)
|
||||
return
|
||||
}
|
||||
|
||||
dst, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA)
|
||||
dst, err := s.generateAt(sha)
|
||||
defer os.RemoveAll(dst)
|
||||
if err != nil {
|
||||
writeError(w, 500, err)
|
||||
|
@ -171,7 +205,7 @@ func checkAuth(pr *PullRequest) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func serveRSTDiff(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *server) serveRSTDiff(w http.ResponseWriter, req *http.Request) {
|
||||
pr, err := lookupPullRequest(*req.URL, "/diff/rst")
|
||||
if err != nil {
|
||||
writeError(w, 400, err)
|
||||
|
@ -185,14 +219,14 @@ func serveRSTDiff(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
base, err := generateAt(pr.Base.Repo.CloneURL, pr.Base.SHA)
|
||||
base, err := s.generateAt(pr.Base.SHA)
|
||||
defer os.RemoveAll(base)
|
||||
if err != nil {
|
||||
writeError(w, 500, err)
|
||||
return
|
||||
}
|
||||
|
||||
head, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA)
|
||||
head, err := s.generateAt(pr.Head.SHA)
|
||||
defer os.RemoveAll(head)
|
||||
if err != nil {
|
||||
writeError(w, 500, err)
|
||||
|
@ -209,7 +243,7 @@ func serveRSTDiff(w http.ResponseWriter, req *http.Request) {
|
|||
w.Write(diff.Bytes())
|
||||
}
|
||||
|
||||
func serveHTMLDiff(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *server) serveHTMLDiff(w http.ResponseWriter, req *http.Request) {
|
||||
pr, err := lookupPullRequest(*req.URL, "/diff/html")
|
||||
if err != nil {
|
||||
writeError(w, 400, err)
|
||||
|
@ -223,14 +257,14 @@ func serveHTMLDiff(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
base, err := generateAt(pr.Base.Repo.CloneURL, pr.Base.SHA)
|
||||
base, err := s.generateAt(pr.Base.SHA)
|
||||
defer os.RemoveAll(base)
|
||||
if err != nil {
|
||||
writeError(w, 500, err)
|
||||
return
|
||||
}
|
||||
|
||||
head, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA)
|
||||
head, err := s.generateAt(pr.Head.SHA)
|
||||
defer os.RemoveAll(head)
|
||||
if err != nil {
|
||||
writeError(w, 500, err)
|
||||
|
@ -287,7 +321,7 @@ func listPulls(w http.ResponseWriter, req *http.Request) {
|
|||
s += fmt.Sprintf(`<li>%d: <a href="%s">%s</a>: <a href="%s">%s</a>: <a href="spec/%d">spec</a> <a href="diff/html/%d">spec diff</a> <a href="diff/rst/%d">rst diff</a></li>`,
|
||||
pull.Number, pull.User.HTMLURL, pull.User.Login, pull.HTMLURL, pull.Title, pull.Number, pull.Number, pull.Number)
|
||||
}
|
||||
s += "</ul></body>"
|
||||
s += `</ul><div><a href="spec/head">View the spec at head</a></div></body>`
|
||||
io.WriteString(w, s)
|
||||
}
|
||||
|
||||
|
@ -317,11 +351,19 @@ func main() {
|
|||
"Kegsay": true,
|
||||
"NegativeMjark": true,
|
||||
}
|
||||
http.HandleFunc("/spec/", serveSpec)
|
||||
http.HandleFunc("/diff/rst/", serveRSTDiff)
|
||||
http.HandleFunc("/diff/html/", serveHTMLDiff)
|
||||
rand.Seed(time.Now().Unix())
|
||||
masterCloneDir, err := gitClone(matrixDocCloneURL, false)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
s := server{masterCloneDir}
|
||||
http.HandleFunc("/spec/", s.serveSpec)
|
||||
http.HandleFunc("/diff/rst/", s.serveRSTDiff)
|
||||
http.HandleFunc("/diff/html/", s.serveHTMLDiff)
|
||||
http.HandleFunc("/healthz", serveText("ok"))
|
||||
http.HandleFunc("/", listPulls)
|
||||
|
||||
fmt.Printf("Listening on port %d\n", *port)
|
||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +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.
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ Introduction
|
|||
The Matrix specification is still evolving: the APIs are not yet frozen
|
||||
and this document is in places a work in progress or stale. We have made every
|
||||
effort to clearly flag areas which are still being finalised.
|
||||
|
||||
We're publishing it at this point because it's complete enough to be more than
|
||||
useful and provide a canonical reference to how Matrix is evolving. Our end
|
||||
goal is to mirror WHATWG's `Living Standard
|
||||
|
@ -34,10 +33,9 @@ Matrix is a set of open APIs for open-federated Instant Messaging (IM), Voice
|
|||
over IP (VoIP) and Internet of Things (IoT) communication, designed to create
|
||||
and support a new global real-time communication ecosystem. The intention is to
|
||||
provide an open decentralised pubsub layer for the internet for securely
|
||||
persisting and publishing/subscribing JSON objects.
|
||||
|
||||
This specification is the ongoing result of standardising the APIs used by the
|
||||
various components of the Matrix ecosystem to communicate with one another.
|
||||
persisting and publishing/subscribing JSON objects. This specification is the
|
||||
ongoing result of standardising the APIs used by the various components of the
|
||||
Matrix ecosystem to communicate with one another.
|
||||
|
||||
The principles that Matrix attempts to follow are:
|
||||
|
||||
|
@ -182,6 +180,8 @@ of a "Room".
|
|||
Event Graphs
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. _sect:event-graph:
|
||||
|
||||
Events exchanged in the context of a room are stored in a directed acyclic graph
|
||||
(DAG) called an ``event graph``. The partial ordering of this graph gives the
|
||||
chronological ordering of events within the room. Each event in the graph has a
|
||||
|
@ -214,10 +214,8 @@ which have the form::
|
|||
There is exactly one room ID for each room. Whilst the room ID does contain a
|
||||
domain, it is simply for globally namespacing room IDs. The room does NOT
|
||||
reside on the domain specified. Room IDs are not meant to be human readable.
|
||||
They are case-sensitive.
|
||||
|
||||
The following conceptual diagram shows an ``m.room.message`` event being sent to
|
||||
the room ``!qporfwt:matrix.org``::
|
||||
They are case-sensitive. The following conceptual diagram shows an
|
||||
``m.room.message`` event being sent to the room ``!qporfwt:matrix.org``::
|
||||
|
||||
{ @alice:matrix.org } { @bob:domain.com }
|
||||
| ^
|
||||
|
@ -258,28 +256,28 @@ the room ``!qporfwt:matrix.org``::
|
|||
Federation maintains *shared data structures* per-room between multiple home
|
||||
servers. The data is split into ``message events`` and ``state events``.
|
||||
|
||||
``Message events`` describe transient 'once-off' activity in a room such as an
|
||||
instant messages, VoIP call setups, file transfers, etc. They generally describe
|
||||
communication activity.
|
||||
Message events:
|
||||
These describe transient 'once-off' activity in a room such as an
|
||||
instant messages, VoIP call setups, file transfers, etc. They generally
|
||||
describe communication activity.
|
||||
|
||||
``State events`` describe updates to a given piece of persistent information
|
||||
('state') related to a room, such as the room's name, topic, membership,
|
||||
participating servers, etc. State is modelled as a lookup table of key/value
|
||||
pairs per room, with each key being a tuple of ``state_key`` and ``event type``.
|
||||
Each state event updates the value of a given key.
|
||||
State events:
|
||||
These describe updates to a given piece of persistent information
|
||||
('state') related to a room, such as the room's name, topic, membership,
|
||||
participating servers, etc. State is modelled as a lookup table of key/value
|
||||
pairs per room, with each key being a tuple of ``state_key`` and ``event type``.
|
||||
Each state event updates the value of a given key.
|
||||
|
||||
The state of the room at a given point is calculated by considering all events
|
||||
preceding and including a given event in the graph. Where events describe the
|
||||
same state, a merge conflict algorithm is applied. The state resolution
|
||||
algorithm is transitive and does not depend on server state, as it must
|
||||
consistently select the same event irrespective of the server or the order the
|
||||
events were received in.
|
||||
|
||||
Events are signed by the originating server (the signature includes the parent
|
||||
relations, type, depth and payload hash) and are pushed over federation to the
|
||||
participating servers in a room, currently using full mesh topology. Servers may
|
||||
also request backfill of events over federation from the other servers
|
||||
participating in a room.
|
||||
events were received in. Events are signed by the originating server (the
|
||||
signature includes the parent relations, type, depth and payload hash) and are
|
||||
pushed over federation to the participating servers in a room, currently using
|
||||
full mesh topology. Servers may also request backfill of events over federation
|
||||
from the other servers participating in a room.
|
||||
|
||||
|
||||
Room Aliases
|
||||
|
@ -324,12 +322,10 @@ Users in Matrix are identified via their matrix user ID (MXID). However,
|
|||
existing 3rd party ID namespaces can also be used in order to identify Matrix
|
||||
users. A Matrix "Identity" describes both the user ID and any other existing IDs
|
||||
from third party namespaces *linked* to their account.
|
||||
|
||||
Matrix users can *link* third-party IDs (3PIDs) such as email addresses, social
|
||||
network accounts and phone numbers to their user ID. Linking 3PIDs creates a
|
||||
mapping from a 3PID to a user ID. This mapping can then be used by Matrix
|
||||
users in order to discover the MXIDs of their contacts.
|
||||
|
||||
In order to ensure that the mapping from 3PID to user ID is genuine, a globally
|
||||
federated cluster of trusted "Identity Servers" (IS) are used to verify the 3PID
|
||||
and persist and replicate the mappings.
|
||||
|
@ -338,49 +334,6 @@ Usage of an IS is not required in order for a client application to be part of
|
|||
the Matrix ecosystem. However, without one clients will not be able to look up
|
||||
user IDs using 3PIDs.
|
||||
|
||||
Presence
|
||||
~~~~~~~~
|
||||
|
||||
Each user has the concept of presence information. This encodes:
|
||||
|
||||
* Whether the user is currently online
|
||||
* How recently the user was last active (as seen by the server)
|
||||
* Whether a given client considers the user to be currently idle
|
||||
* Arbitrary information about the user's current status (e.g. "in a meeting").
|
||||
|
||||
This information is collated from both per-device (online; idle; last_active) and
|
||||
per-user (status) data, aggregated by the user's homeserver and transmitted as
|
||||
an ``m.presence`` event. This is one of the few events which are sent *outside
|
||||
the context of a room*. Presence events are sent to all users who subscribe to
|
||||
this user's presence through a presence list or by sharing membership of a room.
|
||||
|
||||
.. TODO
|
||||
How do we let users hide their presence information?
|
||||
|
||||
.. TODO
|
||||
The last_active specifics should be moved to the detailed presence event section
|
||||
|
||||
Last activity is tracked by the server maintaining a timestamp of the last time
|
||||
it saw a pro-active event from the user. Any event which could be triggered by a
|
||||
human using the application is considered pro-active (e.g. sending an event to a
|
||||
room). An example of a non-proactive client activity would be a client setting
|
||||
'idle' presence status, or polling for events. 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.
|
||||
|
||||
N.B. in v1 API, status/online/idle state are muxed into a single 'presence'
|
||||
field on the ``m.presence`` event.
|
||||
|
||||
Presence Lists
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Each user's home server stores a "presence list". This stores a list of user IDs
|
||||
whose presence the user wants to follow.
|
||||
|
||||
To be added to this list, the user being added must be invited by the list owner
|
||||
and accept the invitation. Once accepted, both user's HSes track the
|
||||
subscription.
|
||||
|
||||
|
||||
Profiles
|
||||
~~~~~~~~
|
||||
|
@ -410,6 +363,10 @@ dedicated API. The API is symmetrical to managing Profile data.
|
|||
API Standards
|
||||
-------------
|
||||
|
||||
.. TODO
|
||||
Need to specify any HMAC or access_token lifetime/ratcheting tricks
|
||||
We need to specify capability negotiation for extensible transports
|
||||
|
||||
The mandatory baseline for communication in Matrix is exchanging JSON objects
|
||||
over HTTP APIs. HTTPS is mandated as the baseline for server-server
|
||||
(federation) communication. HTTPS is recommended for client-server
|
||||
|
@ -417,20 +374,11 @@ communication, although HTTP may be supported as a fallback to support basic
|
|||
HTTP clients. More efficient optional transports for client-server
|
||||
communication will in future be supported as optional extensions - e.g. a
|
||||
packed binary encoding over stream-cipher encrypted TCP socket for
|
||||
low-bandwidth/low-roundtrip mobile usage.
|
||||
|
||||
.. TODO
|
||||
We need to specify capability negotiation for extensible transports
|
||||
|
||||
For the default HTTP transport, all API calls use a Content-Type of
|
||||
``application/json``. In addition, all strings MUST be encoded as UTF-8.
|
||||
|
||||
Clients are authenticated using opaque ``access_token`` strings (see
|
||||
`Client Authentication`_ for details), passed as a query string parameter on
|
||||
all requests.
|
||||
|
||||
.. TODO
|
||||
Need to specify any HMAC or access_token lifetime/ratcheting tricks
|
||||
low-bandwidth/low-roundtrip mobile usage. For the default HTTP transport, all
|
||||
API calls use a Content-Type of ``application/json``. In addition, all strings
|
||||
MUST be encoded as UTF-8. Clients are authenticated using opaque
|
||||
``access_token`` strings (see `Client Authentication`_ for details), passed as a
|
||||
query string parameter on all requests.
|
||||
|
||||
Any errors which occur at the Matrix API level MUST return a "standard error
|
||||
response". This is a JSON object which looks like::
|
||||
|
|
|
@ -197,6 +197,7 @@ This specification defines the following login types:
|
|||
- ``m.login.recaptcha``
|
||||
- ``m.login.oauth2``
|
||||
- ``m.login.email.identity``
|
||||
- ``m.login.token``
|
||||
- ``m.login.dummy``
|
||||
|
||||
Password-based
|
||||
|
@ -228,6 +229,37 @@ To respond to this type, reply with an auth dict as follows::
|
|||
"response": "<captcha response>"
|
||||
}
|
||||
|
||||
Token-based
|
||||
~~~~~~~~~~~
|
||||
:Type:
|
||||
``m.login.token``
|
||||
:Description:
|
||||
The client submits a username and token.
|
||||
|
||||
To respond to this type, reply with an auth dict as follows::
|
||||
|
||||
{
|
||||
"type": "m.login.token",
|
||||
"user": "<user_id or user localpart>",
|
||||
"token": "<token>",
|
||||
"txn_id": "<client generated nonce>"
|
||||
}
|
||||
|
||||
The ``nonce`` should be a random string generated by the client for the
|
||||
request. The same ``nonce`` should be used if retrying the request.
|
||||
|
||||
There are many ways a client may receive a ``token``, including via an email or
|
||||
from an existing logged in device.
|
||||
|
||||
The ``txn_id`` may be used by the server to disallow other devices from using
|
||||
the token, thus providing "single use" tokens while still allowing the device
|
||||
to retry the request. This would be done by tying the token to the ``txn_id``
|
||||
server side, as well as potentially invalidating the token completely once the
|
||||
device has successfully logged in (e.g. when we receive a request from the
|
||||
newly provisioned access_token).
|
||||
|
||||
The ``token`` must be a macaroon.
|
||||
|
||||
OAuth2-based
|
||||
~~~~~~~~~~~~
|
||||
:Type:
|
||||
|
@ -395,6 +427,8 @@ the complete dataset is provided in "chunk".
|
|||
Events
|
||||
------
|
||||
|
||||
.. _sect:events:
|
||||
|
||||
Overview
|
||||
~~~~~~~~
|
||||
|
||||
|
@ -640,13 +674,10 @@ to add keys that are, for example offensive or illegal. Since some events
|
|||
cannot be simply deleted, e.g. membership events, we instead 'redact' events.
|
||||
This involves removing all keys from an event that are not required by the
|
||||
protocol. This stripped down event is thereafter returned anytime a client or
|
||||
remote server requests it.
|
||||
|
||||
Events that have been redacted include a ``redacted_because`` key whose value
|
||||
is the event that caused it to be redacted, which may include a reason.
|
||||
|
||||
Redacting an event cannot be undone, allowing server owners to delete the
|
||||
offending content from the databases.
|
||||
remote server requests it. Redacting an event cannot be undone, allowing server
|
||||
owners to delete the offending content from the databases. Events that have been
|
||||
redacted include a ``redacted_because`` key whose value is the event that caused
|
||||
it to be redacted, which may include a reason.
|
||||
|
||||
.. TODO
|
||||
Currently, only room admins can redact events by sending a ``m.room.redaction``
|
||||
|
@ -677,12 +708,10 @@ one of the following event types:
|
|||
.. TODO
|
||||
Need to update m.room.power_levels to reflect new power levels formatting
|
||||
|
||||
The redaction event should be added under the key ``redacted_because``.
|
||||
|
||||
When a client receives a redaction event it should change the redacted event
|
||||
The redaction event should be added under the key ``redacted_because``. When a
|
||||
client receives a redaction event it should change the redacted event
|
||||
in the same way a server does.
|
||||
|
||||
|
||||
Rooms
|
||||
-----
|
||||
|
||||
|
@ -762,10 +791,47 @@ options which can be set when creating a room:
|
|||
Description:
|
||||
Allows clients to add keys to the content of ``m.room.create``.
|
||||
|
||||
``preset``
|
||||
Type:
|
||||
String
|
||||
Optional:
|
||||
Yes
|
||||
Value:
|
||||
``private_chat``, ``trusted_private_chat`` or ``public_chat``
|
||||
Description:
|
||||
Convenience parameter for setting various default state events based on a
|
||||
preset.
|
||||
|
||||
Three presets are defined:
|
||||
|
||||
- ``private_chat``: Sets the ``join_rules`` to ``invite`` and
|
||||
``history_visibility`` to ``shared``
|
||||
- ``trusted_private_chat``: Set the ``join_rules`` to ``invite``,
|
||||
``history_visibility`` to ``shared`` and gives all invitees the same
|
||||
power level as the creator.
|
||||
- ``public_chat``: Sets the ``join_rules`` to ``public`` and
|
||||
``history_visibility`` to ``shared``
|
||||
|
||||
``initial_state``
|
||||
Type:
|
||||
List
|
||||
Optional:
|
||||
Yes
|
||||
Value:
|
||||
A list of state events to set in the new room.
|
||||
Description:
|
||||
Allows the user to override the default state events set in the new room.
|
||||
|
||||
The expected format of the state events are an object with ``type``,
|
||||
``state_key`` and ``content`` keys set.
|
||||
|
||||
Takes precedence over events set by ``presets``, but gets overriden by
|
||||
``name`` and ``topic`` keys.
|
||||
|
||||
Example::
|
||||
|
||||
{
|
||||
"visibility": "public",
|
||||
"preset": "public_chat",
|
||||
"room_alias_name": "thepub",
|
||||
"name": "The Grand Duke Pub",
|
||||
"topic": "All about happy hour",
|
||||
|
@ -836,18 +902,14 @@ Permissions
|
|||
|
||||
Permissions for rooms are done via the concept of power levels - to do any
|
||||
action in a room a user must have a suitable power level. Power levels are
|
||||
stored as state events in a given room.
|
||||
|
||||
The power levels required for operations and the power levels for users are
|
||||
defined in ``m.room.power_levels``, where both a default and specific users'
|
||||
power levels can be set.
|
||||
|
||||
stored as state events in a given room. The power levels required for operations
|
||||
and the power levels for users are defined in ``m.room.power_levels``, where
|
||||
both a default and specific users' power levels can be set.
|
||||
By default all users have a power level of 0, other than the room creator whose
|
||||
power level defaults to 100. Users can grant other users increased power levels
|
||||
up to their own power level. For example, user A with a power level of 50 could
|
||||
increase the power level of user B to a maximum of level 50. Power levels for
|
||||
users are tracked per-room even if the user is not present in the room.
|
||||
|
||||
The keys contained in ``m.room.power_levels`` determine the levels required for
|
||||
certain operations such as kicking, banning and sending state events. See
|
||||
`m.room.power_levels`_ for more information.
|
||||
|
@ -864,10 +926,9 @@ room. There are several states in which a user may be, in relation to a room:
|
|||
- Banned (the user is not allowed to join the room)
|
||||
|
||||
Some rooms require that users be invited to it before they can join; others
|
||||
allow anyone to join.
|
||||
|
||||
Whether a given room is an "invite-only" room is determined by the room config
|
||||
key ``m.room.join_rules``. It can have one of the following values:
|
||||
allow anyone to join. Whether a given room is an "invite-only" room is
|
||||
determined by the room config key ``m.room.join_rules``. It can have one of the
|
||||
following values:
|
||||
|
||||
``public``
|
||||
This room is free for anyone to join without an invite.
|
||||
|
@ -1075,6 +1136,27 @@ Profiles
|
|||
|
||||
{{profile_http_api}}
|
||||
|
||||
Events on Change of Profile Information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Because the profile display name 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 display name or avatar URL fields.
|
||||
|
||||
Additionally, when home servers emit room membership events for their own
|
||||
users, they should include the display name and avatar URL fields in these
|
||||
events so that clients already have these details to hand, and do not have to
|
||||
perform extra round trips to query it.
|
||||
|
||||
Security
|
||||
--------
|
||||
|
||||
|
|
|
@ -4,11 +4,9 @@ Application Service API
|
|||
The Matrix client-server API and server-server APIs provide the means to
|
||||
implement a consistent self-contained federated messaging fabric. However, they
|
||||
provide limited means of implementing custom server-side behaviour in Matrix
|
||||
(e.g. gateways, filters, extensible hooks etc).
|
||||
|
||||
The Application Service API defines a standard API to allow such extensible
|
||||
functionality to be implemented irrespective of the underlying homeserver
|
||||
implementation.
|
||||
(e.g. gateways, filters, extensible hooks etc). The Application Service API
|
||||
defines a standard API to allow such extensible functionality to be implemented
|
||||
irrespective of the underlying homeserver implementation.
|
||||
|
||||
.. TODO-spec
|
||||
Add in Client-Server services? Overview of bots? Seems weird to be in the spec
|
||||
|
@ -18,12 +16,10 @@ Passive Application Services
|
|||
----------------------------
|
||||
"Passive" application services can only observe events from a given home server.
|
||||
They cannot prevent events from being sent, nor can they modify the content of
|
||||
the event being sent.
|
||||
|
||||
In order to observe events from a homeserver, the homeserver needs to be
|
||||
configured to pass certain types of traffic to the application service. This
|
||||
is achieved by manually configuring the homeserver with information about the
|
||||
AS..
|
||||
the event being sent. In order to observe events from a homeserver, the
|
||||
homeserver needs to be configured to pass certain types of traffic to the
|
||||
application service. This is achieved by manually configuring the homeserver
|
||||
with information about the AS.
|
||||
|
||||
.. NOTE::
|
||||
Previously, application services could register with a homeserver via HTTP
|
||||
|
|
|
@ -59,13 +59,11 @@ and an optional TLS port.
|
|||
.. **
|
||||
|
||||
If the port is present then the server is discovered by looking up an AAAA or
|
||||
A record for the DNS name and connecting to the specified TLS port.
|
||||
|
||||
If the port is absent then the server is discovered by looking up a
|
||||
``_matrix._tcp`` SRV record for the DNS name. If this record does not exist
|
||||
then the server is discovered by looking up an AAAA or A record on the DNS
|
||||
name and taking the default fallback port number of 8448.
|
||||
|
||||
A record for the DNS name and connecting to the specified TLS port. If the port
|
||||
is absent then the server is discovered by looking up a ``_matrix._tcp`` SRV
|
||||
record for the DNS name. If this record does not exist then the server is
|
||||
discovered by looking up an AAAA or A record on the DNS name and taking the
|
||||
default fallback port number of 8448.
|
||||
Home servers may use SRV records to load balance requests between multiple TLS
|
||||
endpoints or to failover to another endpoint if an endpoint fails.
|
||||
|
||||
|
|
56
specification/modules/_template.rst
Normal file
56
specification/modules/_template.rst
Normal file
|
@ -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,44 +1,33 @@
|
|||
Content repository
|
||||
==================
|
||||
|
||||
HTTP API
|
||||
--------
|
||||
.. _module:content:
|
||||
|
||||
Uploads are POSTed to a resource which returns a token which is used to GET
|
||||
the download. Uploads are POSTed to the sender's local homeserver, but are
|
||||
downloaded from the recipient's local homeserver, which must thus first transfer
|
||||
the content from the origin homeserver using the same API (unless the origin
|
||||
and destination homeservers are the same). The upload/download API is::
|
||||
This module allows users to upload content to their homeserver which is
|
||||
retrievable from other homeservers. Its' purpose is to allow users to share
|
||||
attachments in a room. Content locations are represented as Matrix Content (MXC)
|
||||
URIs. They look like::
|
||||
|
||||
=> POST /_matrix/media/v1/upload HTTP/1.1
|
||||
Content-Type: <media-type>
|
||||
mxc://<server-name>/<media-id>
|
||||
|
||||
<media>
|
||||
<server-name> : The name of the homeserver where this content originated, e.g. matrix.org
|
||||
<media-id> : An opaque ID which identifies the content.
|
||||
|
||||
<= HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Uploads are POSTed to a resource on the user's local homeserver which returns a
|
||||
token which is used to GET the download. Content is downloaded from the
|
||||
recipient's local homeserver, which must first transfer the content from the
|
||||
origin homeserver using the same API (unless the origin and destination
|
||||
homeservers are the same).
|
||||
|
||||
{ "content-uri": "mxc://<server-name>/<media-id>" }
|
||||
Client behaviour
|
||||
----------------
|
||||
|
||||
=> GET /_matrix/media/v1/download/<server-name>/<media-id> HTTP/1.1
|
||||
Clients can upload and download content using the following HTTP APIs.
|
||||
|
||||
<= HTTP/1.1 200 OK
|
||||
Content-Type: <media-type>
|
||||
Content-Disposition: attachment;filename=<upload-filename>
|
||||
|
||||
<media>
|
||||
|
||||
Clients can get thumbnails by supplying a desired width and height and
|
||||
thumbnailing method::
|
||||
|
||||
=> GET /_matrix/media/v1/thumbnail/<server_name>
|
||||
/<media-id>?width=<w>&height=<h>&method=<m> HTTP/1.1
|
||||
|
||||
<= HTTP/1.1 200 OK
|
||||
Content-Type: image/jpeg or image/png
|
||||
|
||||
<thumbnail>
|
||||
{{content_repo_http_api}}
|
||||
|
||||
Thumbnails
|
||||
~~~~~~~~~~
|
||||
The thumbnail methods are "crop" and "scale". "scale" tries to return an
|
||||
image where either the width or the height is smaller than the requested
|
||||
size. The client should then scale and letterbox the image if it needs to
|
||||
|
@ -47,6 +36,9 @@ width and height are close to the requested size and the aspect matches
|
|||
the requested size. The client should scale the image if it needs to fit
|
||||
within a given rectangle.
|
||||
|
||||
Server behaviour
|
||||
----------------
|
||||
|
||||
Homeservers may generate thumbnails for content uploaded to remote
|
||||
homeservers themselves or may rely on the remote homeserver to thumbnail
|
||||
the content. Homeservers may return thumbnails of a different size to that
|
||||
|
@ -56,21 +48,27 @@ Homeservers must never upscale images.
|
|||
Security considerations
|
||||
-----------------------
|
||||
|
||||
Clients may try to upload very large files. Homeservers should not store files
|
||||
that are too large and should not serve them to clients.
|
||||
The HTTP GET endpoint does not require any authentication. Knowing the URL of
|
||||
the content is sufficient to retrieve the content, even if the entity isn't in
|
||||
the room.
|
||||
|
||||
Clients may try to upload very large images. Homeservers should not attempt to
|
||||
generate thumbnails for images that are too large.
|
||||
Homeservers have additional concerns:
|
||||
|
||||
Remote homeservers may host very large files or images. Homeserver should not
|
||||
proxy or thumbnail large files or images from remote homeservers.
|
||||
- Clients may try to upload very large files. Homeservers should not store files
|
||||
that are too large and should not serve them to clients.
|
||||
|
||||
Clients may try to upload a large number of files. Homeservers should limit the
|
||||
number and total size of media that can be uploaded by clients.
|
||||
- Clients may try to upload very large images. Homeservers should not attempt to
|
||||
generate thumbnails for images that are too large.
|
||||
|
||||
Clients may try to access a large number of remote files through a homeserver.
|
||||
Homeservers should restrict the number and size of remote files that it caches.
|
||||
- Remote homeservers may host very large files or images. Homeservers should not
|
||||
proxy or thumbnail large files or images from remote homeservers.
|
||||
|
||||
Clients or remote homeservers may try to upload malicious files targeting
|
||||
vulnerabilities in either the homeserver thumbnailing or the client decoders.
|
||||
- Clients may try to upload a large number of files. Homeservers should limit the
|
||||
number and total size of media that can be uploaded by clients.
|
||||
|
||||
- Clients may try to access a large number of remote files through a homeserver.
|
||||
Homeservers should restrict the number and size of remote files that it caches.
|
||||
|
||||
- Clients or remote homeservers may try to upload malicious files targeting
|
||||
vulnerabilities in either the homeserver thumbnailing or the client decoders.
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
End-to-End Encryption
|
||||
=====================
|
||||
|
||||
.. _module:e2e:
|
||||
|
||||
.. TODO-doc
|
||||
- Why is this needed.
|
||||
- Overview of process
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
Room History Visibility
|
||||
-----------------------
|
||||
|
||||
.. _module:history-visibility:
|
||||
|
||||
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
|
||||
of the ``m.room.history_visibility`` state event. The valid values for
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
Instant Messaging
|
||||
=================
|
||||
|
||||
.. _module:im:
|
||||
|
||||
Events
|
||||
------
|
||||
|
||||
|
|
|
@ -1,63 +1,112 @@
|
|||
Presence
|
||||
========
|
||||
|
||||
.. _module:presence:
|
||||
|
||||
Each user has the concept of presence information. This encodes:
|
||||
|
||||
* Whether the user is currently online
|
||||
* How recently the user was last active (as seen by the server)
|
||||
* Whether a given client considers the user to be currently idle
|
||||
* Arbitrary information about the user's current status (e.g. "in a meeting").
|
||||
|
||||
This information is collated from both per-device (``online``, ``idle``,
|
||||
``last_active``) and per-user (status) data, aggregated by the user's homeserver
|
||||
and transmitted as an ``m.presence`` event. This is one of the few events which
|
||||
are sent *outside the context of a room*. Presence events are sent to all users
|
||||
who subscribe to this user's presence through a presence list or by sharing
|
||||
membership of a room.
|
||||
|
||||
A presence list is a list of user IDs whose presence the user wants to follow.
|
||||
To be added to this list, the user being added must be invited by the list owner
|
||||
who must accept the invitation.
|
||||
|
||||
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:
|
||||
User's presence state 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.
|
||||
- ``unavailable`` : The user is not reachable at this time e.g. they are
|
||||
idle.
|
||||
- ``offline`` : The user is not connected to an event stream or is
|
||||
explicitly suppressing their profile information from being sent.
|
||||
- ``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
|
||||
Client behaviour
|
||||
----------------
|
||||
|
||||
Clients can manually set/get their presence/presence list using the HTTP APIs
|
||||
listed below.
|
||||
|
||||
{{presence_http_api}}
|
||||
|
||||
Idle timeout
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Clients SHOULD implement an "idle timeout". This is a timer which fires after
|
||||
a period of inactivity on the client. The definition of inactivity varies
|
||||
depending on the client. For example, web implementations may determine
|
||||
inactivity to be not moving the mouse for a certain period of time. When this
|
||||
timer fires it should set the presence state to ``unavailable``. When the user
|
||||
becomes active again (e.g. by moving the mouse) the client should set the
|
||||
presence state to ``online``. A timeout value between 1 and 5 minutes is
|
||||
recommended.
|
||||
|
||||
Server behaviour
|
||||
----------------
|
||||
|
||||
Each user's home server stores a "presence list" per user. Once a user accepts
|
||||
a presence list, both user's HSes must track the subscription.
|
||||
|
||||
Propagating profile information
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Because the profile display name and avatar information are likely to be used in
|
||||
many places of a client's display, changes to these fields SHOULD cause an
|
||||
automatic propagation event to occur, informing likely-interested parties of the
|
||||
new values. One of these change mechanisms SHOULD be via ``m.presence`` events.
|
||||
These events should set ``displayname`` and ``avatar_url`` to the new values
|
||||
along with the presence-specific keys. This SHOULD be done automatically by the
|
||||
home server when a user successfully changes their display name or avatar URL.
|
||||
|
||||
.. admonition:: Rationale
|
||||
|
||||
The intention for sending this information in ``m.presence`` is so that any
|
||||
"user list" can display the *current* name/presence for a user ID outside the
|
||||
scope of a room e.g. for a user page. This is bundled into a single event for
|
||||
several reasons. The user's display name can change per room. This
|
||||
event provides the "canonical" name for the user. In addition, the name is
|
||||
bundled into a single event for the ease of client implementations. If this
|
||||
was not done, the client would need to search all rooms for their own
|
||||
membership event to pull out the display name.
|
||||
|
||||
|
||||
Last active ago
|
||||
~~~~~~~~~~~~~~~
|
||||
The server maintains a timestamp of the last time it saw a
|
||||
pro-active event from the user. A pro-active event may be sending a message to a
|
||||
room or changing presence state to a higher level of availability. Levels of
|
||||
availability are defined from low to high as follows:
|
||||
|
||||
- ``offline``
|
||||
- ``unavailable``
|
||||
- ``online``
|
||||
- ``free_for_chat``
|
||||
|
||||
Based on this list, changing state from ``unavailable`` to ``online`` counts as
|
||||
a pro-active event, whereas ``online`` to ``unavailable`` does not. This
|
||||
timestamp is presented via a key called ``last_active_ago`` which gives the
|
||||
relative number of milliseconds since the pro-active event.
|
||||
|
||||
Security considerations
|
||||
-----------------------
|
||||
|
||||
|
||||
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.
|
||||
Presence information is shared with all users who share a room with the target
|
||||
user. In large public rooms this could be undesirable.
|
||||
|
||||
|
|
|
@ -99,16 +99,13 @@ be redundant. Actions for the highest priority rule and only that rule apply
|
|||
(for example, a set_tweak action in a lower priority rule will not apply if a
|
||||
higher priority rule matches, even if that rule does not specify any tweaks).
|
||||
|
||||
Rules also have an identifier, rule_id, which is a string. The rule_id is
|
||||
unique within the kind of rule and scope: rule_ids need not be unique between
|
||||
rules of the same kind on different devices.
|
||||
|
||||
A home server may also have server default rules of each kind and in each scope.
|
||||
Server default rules are lower priority than user-defined rules in each scope.
|
||||
Server default rules (and only server default rules) begin with a dot ('.')
|
||||
character.
|
||||
|
||||
In addition, all rules may be enabled or disabled. Disabled rules never match.
|
||||
Rules also have an identifier, ``rule_id``, which is a string. The ``rule_id``
|
||||
is unique within the kind of rule and scope: ``rule_ids`` need not be unique
|
||||
between rules of the same kind on different devices. A home server may also have
|
||||
server default rules of each kind and in each scope. Server default rules are
|
||||
lower priority than user-defined rules in each scope. Server default rules (and
|
||||
only server default rules) begin with a dot ('.') character. In addition, all
|
||||
rules may be enabled or disabled. Disabled rules never match.
|
||||
|
||||
If no rules match an event, the Home Server should not notify for the message
|
||||
(that is to say, the default action is "dont-notify"). Events that the user sent
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
Push Notifications
|
||||
==================
|
||||
|
||||
.. _module:push:
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
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.
|
||||
.. _module:receipts:
|
||||
|
||||
Client-Server API
|
||||
~~~~~~~~~~~~~~~~~
|
||||
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.
|
||||
|
||||
Clients will receive receipts in the following format::
|
||||
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.
|
||||
|
||||
{
|
||||
"type": "m.receipt",
|
||||
"room_id": <room_id>,
|
||||
"content": {
|
||||
<event_id>: {
|
||||
<receipt_type>: {
|
||||
<user_id>: { "ts": <ts>, ... },
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
Events
|
||||
------
|
||||
Each ``user_id``, ``receipt_type`` pair must be associated with only a
|
||||
single ``event_id``.
|
||||
|
||||
For example::
|
||||
{{m_receipt_event}}
|
||||
|
||||
{
|
||||
"type": "m.receipt",
|
||||
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
|
||||
"content": {
|
||||
"$1435641916114394fHBLK:matrix.org": {
|
||||
"read": {
|
||||
"@erikj:jki.re": { "ts": 1436451550453 },
|
||||
...
|
||||
}
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
Client behaviour
|
||||
----------------
|
||||
|
||||
For efficiency, receipts are batched into one event per room. In the initialSync
|
||||
and v2 sync APIs the receipts are listed in a separate 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.
|
||||
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
|
||||
|
||||
A client can update the markers for its user by issuing a request::
|
||||
Client receives another m.receipt:
|
||||
user = @alice:example.com
|
||||
receipt_type = m.read
|
||||
event_id = $bbb:example.com
|
||||
|
||||
POST /_matrix/client/v2_alpha/rooms/<room_id>/receipt/read/<event_id>
|
||||
The client should replace the older acknowledgement for $aaa:example.com with
|
||||
this one for $bbb:example.com
|
||||
|
||||
Where the contents of the ``POST`` will be included in the content sent to
|
||||
other users. The server will automatically set the ``ts`` field.
|
||||
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.
|
||||
|
||||
Server-Server API
|
||||
~~~~~~~~~~~~~~~~~
|
||||
{{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::
|
||||
|
@ -73,5 +73,12 @@ format of the EDUs are::
|
|||
...
|
||||
}
|
||||
|
||||
These are always sent as deltas to previously sent receipts.
|
||||
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.
|
||||
|
||||
|
|
|
@ -1,47 +1,46 @@
|
|||
Typing Notifications
|
||||
--------------------
|
||||
====================
|
||||
|
||||
Client APIs
|
||||
~~~~~~~~~~~
|
||||
.. _module:typing:
|
||||
|
||||
To set "I am typing for the next N msec"::
|
||||
Users may wish to be informed when another user is typing in a room. This can be
|
||||
achieved using typing notifications. These are ephemeral events scoped to a
|
||||
``room_id``. This means they do not form part of the `Event Graph`_ but still
|
||||
have a ``room_id`` key.
|
||||
|
||||
PUT .../rooms/<room_id>/typing/<user_id>
|
||||
Content: { "typing": true, "timeout": N }
|
||||
# timeout is in milliseconds; suggested no more than 20 or 30 seconds
|
||||
.. _Event Graph: `sect:event-graph`_
|
||||
|
||||
This should be re-sent by the client to continue informing the server the user
|
||||
is still typing; a safety margin of 5 seconds before the expected
|
||||
timeout runs out is recommended. Just keep declaring a new timeout, it will
|
||||
replace the old one.
|
||||
Events
|
||||
------
|
||||
|
||||
To set "I am no longer typing"::
|
||||
{{m_typing_event}}
|
||||
|
||||
PUT ../rooms/<room_id>/typing/<user_id>
|
||||
Content: { "typing": false }
|
||||
Client behaviour
|
||||
----------------
|
||||
|
||||
Client Events
|
||||
~~~~~~~~~~~~~
|
||||
When a client receives an ``m.typing`` event, it MUST use the user ID list to
|
||||
**REPLACE** its knowledge of every user who is currently typing. The reason for
|
||||
this is that the server *does not remember* users who are not currently typing
|
||||
as that list gets big quickly. The client should mark as not typing any user ID
|
||||
who is not in that list.
|
||||
|
||||
All room members will receive an event on the event stream::
|
||||
It is recommended that clients store a ``boolean`` indicating whether the user
|
||||
is typing or not. Whilst this value is ``true`` a timer should fire periodically
|
||||
every N seconds to send a typing HTTP request. The value of N is recommended to
|
||||
be no more than 20-30 seconds. This request should be re-sent by the client to
|
||||
continue informing the server the user is still typing. As subsequent
|
||||
requests will replace older requests, a safety margin of 5 seconds before the
|
||||
expected timeout runs out is recommended. When the user stops typing, the
|
||||
state change of the ``boolean`` to ``false`` should trigger another HTTP request
|
||||
to inform the server that the user has stopped typing.
|
||||
|
||||
{
|
||||
"type": "m.typing",
|
||||
"room_id": "!room-id-here:matrix.org",
|
||||
"content": {
|
||||
"user_ids": ["list of", "every user", "who is", "currently typing"]
|
||||
}
|
||||
}
|
||||
{{typing_http_api}}
|
||||
|
||||
The client must use this list to *REPLACE* its knowledge of every user who is
|
||||
currently typing. The reason for this is that the server DOES NOT remember
|
||||
users who are not currently typing, as that list gets big quickly. The client
|
||||
should mark as not typing, any user ID who is not in that list.
|
||||
Server behaviour
|
||||
----------------
|
||||
|
||||
Server APIs
|
||||
~~~~~~~~~~~
|
||||
|
||||
Servers will emit EDUs in the following form::
|
||||
Servers MUST emit typing EDUs in a different form to ``m.typing`` events which
|
||||
are shown to clients. This form looks like::
|
||||
|
||||
{
|
||||
"type": "m.typing",
|
||||
|
@ -52,10 +51,16 @@ Servers will emit EDUs in the following form::
|
|||
}
|
||||
}
|
||||
|
||||
Server EDUs don't (currently) contain timing information; it is up to
|
||||
originating HSes to ensure they eventually send "stop" notifications.
|
||||
This does not contain timing information so it is up to originating homeservers
|
||||
to ensure they eventually send "stop" notifications.
|
||||
|
||||
.. TODO
|
||||
((This will eventually need addressing, as part of the wider typing/presence
|
||||
timer addition work))
|
||||
|
||||
Security considerations
|
||||
-----------------------
|
||||
|
||||
Clients may not wish to inform everyone in a room that they are typing and
|
||||
instead only specific users in the room.
|
||||
|
||||
|
|
|
@ -1,17 +1,26 @@
|
|||
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.
|
||||
=============
|
||||
|
||||
.. _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}}
|
||||
|
||||
Message Exchange
|
||||
~~~~~~~~~~~~~~~~
|
||||
A call is set up with messages exchanged as follows:
|
||||
Client behaviour
|
||||
----------------
|
||||
|
||||
A call is set up with message events exchanged as follows:
|
||||
|
||||
::
|
||||
|
||||
|
@ -38,29 +47,55 @@ Or a rejected call:
|
|||
|
||||
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:
|
||||
"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.
|
||||
|
||||
- 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
|
||||
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.
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ 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: 0-feature_profiles.rst }
|
||||
- { 1: "group:modules" } # reference a group of files
|
||||
- 3-application_service_api.rst
|
||||
- 4-server_server_api.rst
|
||||
|
|
|
@ -122,7 +122,7 @@ def main(input_module, file_stream=None, out_dir=None, verbose=False):
|
|||
|
||||
# check the input files and substitute in sections where required
|
||||
log("Parsing input template: %s" % file_stream.name)
|
||||
temp_str = file_stream.read()
|
||||
temp_str = file_stream.read().decode("utf-8")
|
||||
# do sanity checking on the template to make sure they aren't reffing things
|
||||
# which will never be replaced with a section.
|
||||
ast = env.parse(temp_str)
|
||||
|
@ -140,7 +140,7 @@ def main(input_module, file_stream=None, out_dir=None, verbose=False):
|
|||
with open(
|
||||
os.path.join(out_dir, os.path.basename(file_stream.name)), "w"
|
||||
) as f:
|
||||
f.write(output)
|
||||
f.write(output.encode("utf-8"))
|
||||
log("Output file for: %s" % file_stream.name)
|
||||
check_unaccessed("units", units)
|
||||
|
||||
|
|
|
@ -31,18 +31,18 @@ Response format:
|
|||
{% for table in endpoint.res_tables -%}
|
||||
{{"``"+table.title+"``" if table.title else "" }}
|
||||
|
||||
================== ================= ===========================================
|
||||
=================== ================= ==========================================
|
||||
Param Type Description
|
||||
================== ================= ===========================================
|
||||
=================== ================= ==========================================
|
||||
{% for row in table.rows -%}
|
||||
{# -#}
|
||||
{# Row type needs to prepend spaces to line up with the type column (19 ch) -#}
|
||||
{# Row type needs to prepend spaces to line up with the type column (20 ch) -#}
|
||||
{# Desc needs to prepend the required text (maybe) and prepend spaces too -#}
|
||||
{# It also needs to then wrap inside the desc col (43 ch width) -#}
|
||||
{# It also needs to then wrap inside the desc col (42 ch width) -#}
|
||||
{# -#}
|
||||
{{row.key}}{{row.type|indent(19-row.key|length)}}{{row.desc|wrap(43,row.req_str | indent(18 - (row.type|length))) |indent_block(37)}}
|
||||
{{row.key}}{{row.type|indent(20-row.key|length)}}{{row.desc|wrap(42,row.req_str | indent(18 - (row.type|length))) |indent_block(38)}}
|
||||
{% endfor -%}
|
||||
================== ================= ===========================================
|
||||
=================== ================= ==========================================
|
||||
|
||||
{% endfor %}
|
||||
{% endif -%}
|
||||
|
|
|
@ -19,6 +19,7 @@ import yaml
|
|||
V1_CLIENT_API = "../api/client-server/v1"
|
||||
V1_EVENT_EXAMPLES = "../event-schemas/examples/v1"
|
||||
V1_EVENT_SCHEMA = "../event-schemas/schema/v1"
|
||||
V2_CLIENT_API = "../api/client-server/v2_alpha"
|
||||
CORE_EVENT_SCHEMA = "../event-schemas/schema/v1/core-event-schema"
|
||||
CHANGELOG = "../CHANGELOG.rst"
|
||||
TARGETS = "../specification/targets.yaml"
|
||||
|
@ -49,8 +50,17 @@ def get_json_schema_object_fields(obj, enforce_title=False):
|
|||
}
|
||||
tables = [fields]
|
||||
|
||||
props = obj.get("properties", obj.get("patternProperties"))
|
||||
parents = obj.get("allOf")
|
||||
props = obj.get("properties")
|
||||
if not props:
|
||||
props = obj.get("patternProperties")
|
||||
if props:
|
||||
# try to replace horrible regex key names with pretty x-pattern ones
|
||||
for key_name in props.keys():
|
||||
pretty_key = props[key_name].get("x-pattern")
|
||||
if pretty_key:
|
||||
props[pretty_key] = props[key_name]
|
||||
del props[key_name]
|
||||
if not props and not parents:
|
||||
raise Exception(
|
||||
"Object %s has no properties or parents." % obj
|
||||
|
@ -70,10 +80,17 @@ def get_json_schema_object_fields(obj, enforce_title=False):
|
|||
if props[key_name]["type"] == "object":
|
||||
if props[key_name].get("additionalProperties"):
|
||||
# not "really" an object, just a KV store
|
||||
value_type = (
|
||||
"{string: %s}" %
|
||||
props[key_name]["additionalProperties"]["type"]
|
||||
)
|
||||
prop_val = props[key_name]["additionalProperties"]["type"]
|
||||
if prop_val == "object":
|
||||
nested_object = get_json_schema_object_fields(
|
||||
props[key_name]["additionalProperties"],
|
||||
enforce_title=True
|
||||
)
|
||||
value_type = "{string: %s}" % nested_object[0]["title"]
|
||||
if not nested_object[0].get("no-table"):
|
||||
tables += nested_object
|
||||
else:
|
||||
value_type = "{string: %s}" % prop_val
|
||||
else:
|
||||
nested_object = get_json_schema_object_fields(
|
||||
props[key_name],
|
||||
|
@ -151,6 +168,13 @@ class MatrixUnits(Units):
|
|||
|
||||
# assign value expected for this param
|
||||
val_type = param.get("type") # integer/string
|
||||
|
||||
if param.get("enum"):
|
||||
val_type = "enum"
|
||||
desc += (
|
||||
" One of: %s" % json.dumps(param.get("enum"))
|
||||
)
|
||||
|
||||
refType = Units.prop(param, "schema/$ref/") # Error,Event
|
||||
schemaFmt = Units.prop(param, "schema/format") # bytes e.g. uploads
|
||||
if not val_type and refType:
|
||||
|
@ -253,17 +277,27 @@ class MatrixUnits(Units):
|
|||
if good_response:
|
||||
self.log("Found a 200 response for this API")
|
||||
res_type = Units.prop(good_response, "schema/type")
|
||||
res_name = Units.prop(good_response, "schema/name")
|
||||
if res_type and res_type not in ["object", "array"]:
|
||||
# response is a raw string or something like that
|
||||
endpoint["res_tables"].append({
|
||||
good_table = {
|
||||
"title": None,
|
||||
"rows": [{
|
||||
"key": good_response["schema"].get("name", ""),
|
||||
"key": "<" + res_type + ">" if not res_name else res_name,
|
||||
"type": res_type,
|
||||
"desc": res.get("description", ""),
|
||||
"req_str": ""
|
||||
}]
|
||||
})
|
||||
}
|
||||
if good_response.get("headers"):
|
||||
for (header_name, header) in good_response.get("headers").iteritems():
|
||||
good_table["rows"].append({
|
||||
"key": header_name,
|
||||
"type": "Header<" + header["type"] + ">",
|
||||
"desc": header["description"],
|
||||
"req_str": ""
|
||||
})
|
||||
endpoint["res_tables"].append(good_table)
|
||||
elif res_type and Units.prop(good_response, "schema/properties"):
|
||||
# response is an object:
|
||||
schema = good_response["schema"]
|
||||
|
@ -320,18 +354,27 @@ class MatrixUnits(Units):
|
|||
}
|
||||
|
||||
def load_swagger_apis(self):
|
||||
path = V1_CLIENT_API
|
||||
paths = [
|
||||
V1_CLIENT_API, V2_CLIENT_API
|
||||
]
|
||||
apis = {}
|
||||
for filename in os.listdir(path):
|
||||
if not filename.endswith(".yaml"):
|
||||
for path in paths:
|
||||
is_v2 = (path == V2_CLIENT_API)
|
||||
if not os.path.exists(V2_CLIENT_API):
|
||||
self.log("Skipping v2 apis: %s does not exist." % V2_CLIENT_API)
|
||||
continue
|
||||
self.log("Reading swagger API: %s" % filename)
|
||||
with open(os.path.join(path, filename), "r") as f:
|
||||
# strip .yaml
|
||||
group_name = filename[:-5]
|
||||
api = yaml.load(f.read())
|
||||
api["__meta"] = self._load_swagger_meta(api, group_name)
|
||||
apis[group_name] = api
|
||||
for filename in os.listdir(path):
|
||||
if not filename.endswith(".yaml"):
|
||||
continue
|
||||
self.log("Reading swagger API: %s" % filename)
|
||||
with open(os.path.join(path, filename), "r") as f:
|
||||
# strip .yaml
|
||||
group_name = filename[:-5].replace("-", "_")
|
||||
if is_v2:
|
||||
group_name = "v2_" + group_name
|
||||
api = yaml.load(f.read())
|
||||
api["__meta"] = self._load_swagger_meta(api, group_name)
|
||||
apis[group_name] = api
|
||||
return apis
|
||||
|
||||
def load_common_event_fields(self):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue