Merge branch 'master' of github.com:matrix-org/matrix-doc into erikj/disable_federation

erikj/disable_federation
Erik Johnston 9 years ago
commit 3b4c3522e6

@ -6,6 +6,34 @@
.. in Jenkins. Comments like this are ignored by both RST and the templating .. in Jenkins. Comments like this are ignored by both RST and the templating
.. system. Add the newest release notes beneath this comment. .. 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) Specification changes in v0.1.0 (2015-06-01)
============================================ ============================================
- First numbered release. - First numbered release.

@ -34,7 +34,7 @@ def check_parameter(filepath, request, parameter):
example = None example = None
try: try:
example_json = schema.get('example') example_json = schema.get('example')
if example_json: if example_json and not schema.get("format") == "byte":
example = json.loads(example_json) example = json.loads(example_json)
except Exception as e: except Exception as e:
raise ValueError("Error parsing JSON example request for %r" % ( raise ValueError("Error parsing JSON example request for %r" % (

@ -15,16 +15,22 @@ paths:
summary: Upload some content to the content repository. summary: Upload some content to the content repository.
produces: ["application/json"] produces: ["application/json"]
parameters: 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 - in: body
name: content name: "<content>"
description: The content to be uploaded. description: The content to be uploaded.
required: true required: true
schema: schema:
type: string type: string
example: "<bytes>"
format: byte format: byte
responses: responses:
200: 200:
description: Information about the uploaded content. description: The MXC URI for the uploaded content.
schema: schema:
type: object type: object
required: ["content_uri"] required: ["content_uri"]
@ -32,6 +38,11 @@ paths:
content_uri: content_uri:
type: string type: string
description: "The MXC URI to the uploaded content." description: "The MXC URI to the uploaded content."
examples:
"application/json": |-
{
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
}
"/download/{serverName}/{mediaId}": "/download/{serverName}/{mediaId}":
get: get:
summary: "Download content from the content repository." summary: "Download content from the content repository."
@ -40,18 +51,27 @@ paths:
- in: path - in: path
type: string type: string
name: serverName name: serverName
x-example: matrix.org
required: true required: true
description: | description: |
The server name from the ``mxc://`` URI (the authoritory component) The server name from the ``mxc://`` URI (the authoritory component)
- in: path - in: path
type: string type: string
name: mediaId name: mediaId
x-example: ascERGshawAWawugaAcauga
required: true required: true
description: | description: |
The media ID from the ``mxc://`` URI (the path component) The media ID from the ``mxc://`` URI (the path component)
responses: responses:
200: 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: schema:
type: file type: file
"/thumbnail/{serverName}/{mediaId}": "/thumbnail/{serverName}/{mediaId}":
@ -63,30 +83,44 @@ paths:
type: string type: string
name: serverName name: serverName
required: true required: true
x-example: matrix.org
description: | description: |
The server name from the ``mxc://`` URI (the authoritory component) The server name from the ``mxc://`` URI (the authoritory component)
- in: path - in: path
type: string type: string
name: mediaId name: mediaId
x-example: ascERGshawAWawugaAcauga
required: true required: true
description: | description: |
The media ID from the ``mxc://`` URI (the path component) The media ID from the ``mxc://`` URI (the path component)
- in: query - in: query
type: integer type: integer
x-example: 64
name: width 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 - in: query
type: integer type: integer
x-example: 64
name: height 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 - in: query
type: string type: string
enum: ["crop", "scale"] enum: ["crop", "scale"]
name: method name: method
x-example: "scale"
description: The desired resizing method. description: The desired resizing method.
responses: responses:
200: 200:
description: "A thumbnail of the requested content." 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: schema:
type: file type: file

@ -267,6 +267,12 @@ paths:
type: string type: string
description: "The user's membership state in this room." description: "The user's membership state in this room."
enum: ["invite", "join", "leave", "ban"] 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: messages:
type: object type: object
title: PaginationChunk title: PaginationChunk

@ -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"

@ -0,0 +1,68 @@
swagger: '2.0'
info:
title: "Matrix Client-Server v1 Voice over IP API"
version: "1.0.0"
host: localhost:8008
schemes:
- https
- http
basePath: /_matrix/client/api/v1
consumes:
- application/json
produces:
- application/json
securityDefinitions:
accessToken:
type: apiKey
description: The user_id or application service access_token
name: access_token
in: query
paths:
"/turnServer":
get:
summary: Obtain TURN server credentials.
description: |-
This API provides credentials for the client to use when initiating
calls.
security:
- accessToken: []
responses:
200:
description: The TURN server credentials.
examples:
application/json: |-
{
"username":"1443779631:@user:example.com",
"password":"JlKfBy1QwLrO20385QyAtEyIv0=",
"uris":[
"turn:turn.example.com:3478?transport=udp",
"turn:10.20.30.40:3478?transport=tcp",
"turns:10.20.30.40:443?transport=tcp"
],
"ttl":86400
}
schema:
type: object
properties:
username:
type: string
description: |-
The username to use.
password:
type: string
description: |-
The password to use.
uris:
type: array
items:
type: string
description: A list of TURN URIs
ttl:
type: integer
description: The time-to-live in seconds
required: ["username", "password", "uris", "ttl"]
429:
description: This request was rate-limited.
schema:
"$ref": "definitions/error.yaml"

@ -0,0 +1,68 @@
swagger: '2.0'
info:
title: "Matrix Client-Server v2 Receipts API"
version: "1.0.0"
host: localhost:8008
schemes:
- https
- http
basePath: /_matrix/client/v2_alpha
consumes:
- application/json
produces:
- application/json
securityDefinitions:
accessToken:
type: apiKey
description: The user_id or application service access_token
name: access_token
in: query
paths:
"/rooms/{roomId}/receipt/{receiptType}/{eventId}":
post:
summary: Send a receipt for the given event ID.
description: |-
This API updates the marker for the given receipt type to the event ID
specified.
security:
- accessToken: []
parameters:
- in: path
type: string
name: roomId
description: The room in which to send the event.
required: true
x-example: "!wefuh21ffskfuh345:example.com"
- in: path
type: string
name: receiptType
description: The type of receipt to send.
required: true
x-example: "m.read"
enum: ["m.read"]
- in: path
type: string
name: eventId
description: The event ID to acknowledge up to.
required: true
x-example: "$1924376522eioj:example.com"
- in: body
description: |-
Extra receipt information to attach to ``content`` if any. The
server will automatically set the ``ts`` field.
schema:
type: object
example: |-
{}
responses:
200:
description: The receipt was sent.
examples:
application/json: |-
{}
schema:
type: object # empty json object
429:
description: This request was rate-limited.
schema:
"$ref": "definitions/error.yaml"

@ -10,6 +10,6 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"nopt": "^3.0.2", "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) { if (!err) {
return; return;
} }
console.log(metadata);
console.error(err); console.error(err);
process.exit(1); process.exit(1);
}; };
@ -46,11 +45,12 @@ if (isDir) {
files.forEach(function(f) { files.forEach(function(f) {
var suffix = ".yaml"; var suffix = ".yaml";
if (f.indexOf(suffix, f.length - suffix.length) > 0) { 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) { if (!err) {
console.log("%s is valid.", f); console.log("%s is valid.", f);
} }
else { else {
console.error("%s is not valid.", f);
errFn(err, api, metadata); errFn(err, api, metadata);
} }
}); });
@ -59,12 +59,12 @@ if (isDir) {
}); });
} }
else{ else{
parser.parse(opts.schema, function(err, api, metadata) { parser.validate(opts.schema, function(err, api) {
if (!err) { if (!err) {
console.log("%s is valid", opts.schema); console.log("%s is valid", opts.schema);
} }
else { else {
errFn(err, api, metadata); errFn(err, api);
} }
}); });
}; };

@ -3,7 +3,7 @@
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org", "room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
"content": { "content": {
"$1435641916114394fHBLK:matrix.org": { "$1435641916114394fHBLK:matrix.org": {
"read": { "m.read": {
"@rikj:jki.re": { "@rikj:jki.re": {
"ts": 1436451550453 "ts": 1436451550453
} }

@ -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": { "properties": {
"content": { "content": {
"type": "object", "type": "object",
"description": "The event ids which the receipts relate to.",
"patternProperties": { "patternProperties": {
"^\\$": { "^\\$": {
"type": "object", "type": "object",
"description": "The types of the receipts.", "x-pattern": "$EVENT_ID",
"additionalProperties": { "title": "Receipts",
"type": "object", "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.",
"description": "User ids of the receipts", "properties": {
"patternProperties": { "m.read": {
"^@": { "type": "object",
"type": "object", "title": "Users",
"properties": { "description": "A collection of users who have sent ``m.read`` receipts for this event.",
"ts": { "patternProperties": {
"type": "number", "^@": {
"description": "The timestamp the receipt was sent at" "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": {
"type": "string", "type": "string",
"enum": ["m.room.member"] "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"
}
}
}
} }
} }
} }

@ -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" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
fsnotify "gopkg.in/fsnotify.v1" fsnotify "gopkg.in/fsnotify.v1"
) )
@ -67,7 +68,6 @@ func watchFS(ch chan struct{}, w *fsnotify.Watcher) {
select { select {
case e := <-w.Events: case e := <-w.Events:
if filter(e) { if filter(e) {
wg.Add(1)
fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name) fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name)
ch <- struct{}{} ch <- struct{}{}
} }
@ -98,6 +98,11 @@ func filter(e fsnotify.Event) bool {
return false 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 // Avoid infinite cycles being caused by writing actual output
if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") { if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") {
return false return false
@ -133,8 +138,20 @@ func populateOnce(dir string) {
} }
func doPopulate(ch chan struct{}, dir string) { func doPopulate(ch chan struct{}, dir string) {
for _ = range ch { var pending int
populateOnce(dir) 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; border-bottom: 1px solid #ac9;
} }
p {
margin: 0;
}
ul li dd { ul li dd {
margin-top: 0; margin-top: 0;
} }
@ -282,3 +278,9 @@ td[colspan]:not([colspan="1"]) {
thead { thead {
background: #eeeeee; background: #eeeeee;
} }
div.admonition-rationale {
background-color: #efe;
border: 1px solid #ccc;
}

@ -24,6 +24,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
) )
type PullRequest struct { type PullRequest struct {
@ -58,24 +59,42 @@ func (u *User) IsTrusted() bool {
return allowedMembers[u.Login] 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() err := cmd.Run()
if err != nil { if err != nil {
return "", fmt.Errorf("error cloning repo: %v", err) return "", fmt.Errorf("error cloning repo: %v", err)
} }
return dst, nil return directory, nil
} }
func gitCheckout(path, sha string) error { 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 cmd.Dir = path
err := cmd.Run() err := cmd.Run()
if err != nil { 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 return nil
} }
@ -119,10 +138,18 @@ func writeError(w http.ResponseWriter, code int, err error) {
io.WriteString(w, fmt.Sprintf("%v\n", err)) io.WriteString(w, fmt.Sprintf("%v\n", err))
} }
type server struct {
matrixDocCloneURL string
}
// generateAt generates spec from repo at sha. // generateAt generates spec from repo at sha.
// Returns the path where the generation was done. // Returns the path where the generation was done.
func generateAt(repo, sha string) (dst string, err error) { func (s *server) generateAt(sha string) (dst string, err error) {
dst, err = gitClone(repo) err = gitFetchAndMerge(s.matrixDocCloneURL)
if err != nil {
return
}
dst, err = gitClone(s.matrixDocCloneURL, true)
if err != nil { if err != nil {
return return
} }
@ -135,21 +162,28 @@ func generateAt(repo, sha string) (dst string, err error) {
return return
} }
func serveSpec(w http.ResponseWriter, req *http.Request) { func (s *server) serveSpec(w http.ResponseWriter, req *http.Request) {
pr, err := lookupPullRequest(*req.URL, "/spec") var sha string
if err != nil {
writeError(w, 400, err)
return
}
// We're going to run whatever Python is specified in the pull request, which if strings.ToLower(req.URL.Path) == "/spec/head" {
// may do bad things, so only trust people we trust. sha = "HEAD"
if err := checkAuth(pr); err != nil { } else {
writeError(w, 403, err) pr, err := lookupPullRequest(*req.URL, "/spec")
return 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
} }
dst, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA) dst, err := s.generateAt(sha)
defer os.RemoveAll(dst) defer os.RemoveAll(dst)
if err != nil { if err != nil {
writeError(w, 500, err) writeError(w, 500, err)
@ -171,7 +205,7 @@ func checkAuth(pr *PullRequest) error {
return nil 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") pr, err := lookupPullRequest(*req.URL, "/diff/rst")
if err != nil { if err != nil {
writeError(w, 400, err) writeError(w, 400, err)
@ -185,14 +219,14 @@ func serveRSTDiff(w http.ResponseWriter, req *http.Request) {
return return
} }
base, err := generateAt(pr.Base.Repo.CloneURL, pr.Base.SHA) base, err := s.generateAt(pr.Base.SHA)
defer os.RemoveAll(base) defer os.RemoveAll(base)
if err != nil { if err != nil {
writeError(w, 500, err) writeError(w, 500, err)
return return
} }
head, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA) head, err := s.generateAt(pr.Head.SHA)
defer os.RemoveAll(head) defer os.RemoveAll(head)
if err != nil { if err != nil {
writeError(w, 500, err) writeError(w, 500, err)
@ -209,7 +243,7 @@ func serveRSTDiff(w http.ResponseWriter, req *http.Request) {
w.Write(diff.Bytes()) 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") pr, err := lookupPullRequest(*req.URL, "/diff/html")
if err != nil { if err != nil {
writeError(w, 400, err) writeError(w, 400, err)
@ -223,14 +257,14 @@ func serveHTMLDiff(w http.ResponseWriter, req *http.Request) {
return return
} }
base, err := generateAt(pr.Base.Repo.CloneURL, pr.Base.SHA) base, err := s.generateAt(pr.Base.SHA)
defer os.RemoveAll(base) defer os.RemoveAll(base)
if err != nil { if err != nil {
writeError(w, 500, err) writeError(w, 500, err)
return return
} }
head, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA) head, err := s.generateAt(pr.Head.SHA)
defer os.RemoveAll(head) defer os.RemoveAll(head)
if err != nil { if err != nil {
writeError(w, 500, err) 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>`, 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) 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) io.WriteString(w, s)
} }
@ -317,11 +351,19 @@ func main() {
"Kegsay": true, "Kegsay": true,
"NegativeMjark": true, "NegativeMjark": true,
} }
http.HandleFunc("/spec/", serveSpec) rand.Seed(time.Now().Unix())
http.HandleFunc("/diff/rst/", serveRSTDiff) masterCloneDir, err := gitClone(matrixDocCloneURL, false)
http.HandleFunc("/diff/html/", serveHTMLDiff) 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("/healthz", serveText("ok"))
http.HandleFunc("/", listPulls) http.HandleFunc("/", listPulls)
fmt.Printf("Listening on port %d\n", *port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
} }

@ -1,3 +1,93 @@
Feature Profiles 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 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 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. 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 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 useful and provide a canonical reference to how Matrix is evolving. Our end
goal is to mirror WHATWG's `Living Standard 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 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 and support a new global real-time communication ecosystem. The intention is to
provide an open decentralised pubsub layer for the internet for securely provide an open decentralised pubsub layer for the internet for securely
persisting and publishing/subscribing JSON objects. persisting and publishing/subscribing JSON objects. This specification is the
ongoing result of standardising the APIs used by the various components of the
This specification is the ongoing result of standardising the APIs used by the Matrix ecosystem to communicate with one another.
various components of the Matrix ecosystem to communicate with one another.
The principles that Matrix attempts to follow are: The principles that Matrix attempts to follow are:
@ -182,6 +180,8 @@ of a "Room".
Event Graphs Event Graphs
~~~~~~~~~~~~ ~~~~~~~~~~~~
.. _sect:event-graph:
Events exchanged in the context of a room are stored in a directed acyclic 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 (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 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 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 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. reside on the domain specified. Room IDs are not meant to be human readable.
They are case-sensitive. They are case-sensitive. The following conceptual diagram shows an
``m.room.message`` event being sent to the room ``!qporfwt:matrix.org``::
The following conceptual diagram shows an ``m.room.message`` event being sent to
the room ``!qporfwt:matrix.org``::
{ @alice:matrix.org } { @bob:domain.com } { @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 Federation maintains *shared data structures* per-room between multiple home
servers. The data is split into ``message events`` and ``state events``. servers. The data is split into ``message events`` and ``state events``.
``Message events`` describe transient 'once-off' activity in a room such as an Message events:
instant messages, VoIP call setups, file transfers, etc. They generally describe These describe transient 'once-off' activity in a room such as an
communication activity. 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 events:
('state') related to a room, such as the room's name, topic, membership, These describe updates to a given piece of persistent information
participating servers, etc. State is modelled as a lookup table of key/value ('state') related to a room, such as the room's name, topic, membership,
pairs per room, with each key being a tuple of ``state_key`` and ``event type``. participating servers, etc. State is modelled as a lookup table of key/value
Each state event updates the value of a given key. 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 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 preceding and including a given event in the graph. Where events describe the
same state, a merge conflict algorithm is applied. The state resolution same state, a merge conflict algorithm is applied. The state resolution
algorithm is transitive and does not depend on server state, as it must 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 consistently select the same event irrespective of the server or the order the
events were received in. events were received in. Events are signed by the originating server (the
signature includes the parent relations, type, depth and payload hash) and are
Events are signed by the originating server (the signature includes the parent pushed over federation to the participating servers in a room, currently using
relations, type, depth and payload hash) and are pushed over federation to the full mesh topology. Servers may also request backfill of events over federation
participating servers in a room, currently using full mesh topology. Servers may from the other servers participating in a room.
also request backfill of events over federation from the other servers
participating in a room.
Room Aliases 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 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 users. A Matrix "Identity" describes both the user ID and any other existing IDs
from third party namespaces *linked* to their account. from third party namespaces *linked* to their account.
Matrix users can *link* third-party IDs (3PIDs) such as email addresses, social 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 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 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. 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 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 federated cluster of trusted "Identity Servers" (IS) are used to verify the 3PID
and persist and replicate the mappings. 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 the Matrix ecosystem. However, without one clients will not be able to look up
user IDs using 3PIDs. 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 Profiles
~~~~~~~~ ~~~~~~~~
@ -410,6 +363,10 @@ dedicated API. The API is symmetrical to managing Profile data.
API Standards 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 The mandatory baseline for communication in Matrix is exchanging JSON objects
over HTTP APIs. HTTPS is mandated as the baseline for server-server over HTTP APIs. HTTPS is mandated as the baseline for server-server
(federation) communication. HTTPS is recommended for client-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 HTTP clients. More efficient optional transports for client-server
communication will in future be supported as optional extensions - e.g. a communication will in future be supported as optional extensions - e.g. a
packed binary encoding over stream-cipher encrypted TCP socket for packed binary encoding over stream-cipher encrypted TCP socket for
low-bandwidth/low-roundtrip mobile usage. 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
.. TODO MUST be encoded as UTF-8. Clients are authenticated using opaque
We need to specify capability negotiation for extensible transports ``access_token`` strings (see `Client Authentication`_ for details), passed as a
query string parameter on all requests.
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
Any errors which occur at the Matrix API level MUST return a "standard error Any errors which occur at the Matrix API level MUST return a "standard error
response". This is a JSON object which looks like:: 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.recaptcha``
- ``m.login.oauth2`` - ``m.login.oauth2``
- ``m.login.email.identity`` - ``m.login.email.identity``
- ``m.login.token``
- ``m.login.dummy`` - ``m.login.dummy``
Password-based Password-based
@ -228,6 +229,37 @@ To respond to this type, reply with an auth dict as follows::
"response": "<captcha response>" "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 OAuth2-based
~~~~~~~~~~~~ ~~~~~~~~~~~~
:Type: :Type:
@ -395,6 +427,8 @@ the complete dataset is provided in "chunk".
Events Events
------ ------
.. _sect:events:
Overview 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. 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 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 protocol. This stripped down event is thereafter returned anytime a client or
remote server requests it. 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
Events that have been redacted include a ``redacted_because`` key whose value redacted include a ``redacted_because`` key whose value is the event that caused
is the event that caused it to be redacted, which may include a reason. 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.
.. TODO .. TODO
Currently, only room admins can redact events by sending a ``m.room.redaction`` Currently, only room admins can redact events by sending a ``m.room.redaction``
@ -677,12 +708,10 @@ one of the following event types:
.. TODO .. TODO
Need to update m.room.power_levels to reflect new power levels formatting Need to update m.room.power_levels to reflect new power levels formatting
The redaction event should be added under the key ``redacted_because``. The redaction event should be added under the key ``redacted_because``. When a
client receives a redaction event it should change the redacted event
When a client receives a redaction event it should change the redacted event
in the same way a server does. in the same way a server does.
Rooms Rooms
----- -----
@ -762,10 +791,47 @@ options which can be set when creating a room:
Description: Description:
Allows clients to add keys to the content of ``m.room.create``. 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:: Example::
{ {
"visibility": "public", "preset": "public_chat",
"room_alias_name": "thepub", "room_alias_name": "thepub",
"name": "The Grand Duke Pub", "name": "The Grand Duke Pub",
"topic": "All about happy hour", "topic": "All about happy hour",
@ -836,18 +902,14 @@ Permissions
Permissions for rooms are done via the concept of power levels - to do any 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 action in a room a user must have a suitable power level. Power levels are
stored as state events in a given room. 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
The power levels required for operations and the power levels for users are both a default and specific users' power levels can be set.
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 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 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 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 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. 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 The keys contained in ``m.room.power_levels`` determine the levels required for
certain operations such as kicking, banning and sending state events. See certain operations such as kicking, banning and sending state events. See
`m.room.power_levels`_ for more information. `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) - Banned (the user is not allowed to join the room)
Some rooms require that users be invited to it before they can join; others Some rooms require that users be invited to it before they can join; others
allow anyone to join. 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
Whether a given room is an "invite-only" room is determined by the room config following values:
key ``m.room.join_rules``. It can have one of the following values:
``public`` ``public``
This room is free for anyone to join without an invite. This room is free for anyone to join without an invite.
@ -1075,6 +1136,27 @@ Profiles
{{profile_http_api}} {{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 Security
-------- --------

@ -4,11 +4,9 @@ Application Service API
The Matrix client-server API and server-server APIs provide the means to The Matrix client-server API and server-server APIs provide the means to
implement a consistent self-contained federated messaging fabric. However, they implement a consistent self-contained federated messaging fabric. However, they
provide limited means of implementing custom server-side behaviour in Matrix provide limited means of implementing custom server-side behaviour in Matrix
(e.g. gateways, filters, extensible hooks etc). (e.g. gateways, filters, extensible hooks etc). The Application Service API
defines a standard API to allow such extensible functionality to be implemented
The Application Service API defines a standard API to allow such extensible irrespective of the underlying homeserver implementation.
functionality to be implemented irrespective of the underlying homeserver
implementation.
.. TODO-spec .. TODO-spec
Add in Client-Server services? Overview of bots? Seems weird to be in the 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. "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 They cannot prevent events from being sent, nor can they modify the content of
the event being sent. 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
In order to observe events from a homeserver, the homeserver needs to be application service. This is achieved by manually configuring the homeserver
configured to pass certain types of traffic to the application service. This with information about the AS.
is achieved by manually configuring the homeserver with information about the
AS..
.. NOTE:: .. NOTE::
Previously, application services could register with a homeserver via HTTP 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 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. 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
If the port is absent then the server is discovered by looking up a record for the DNS name. If this record does not exist then the server is
``_matrix._tcp`` SRV record for the DNS name. If this record does not exist discovered by looking up an AAAA or A record on the DNS name and taking the
then the server is discovered by looking up an AAAA or A record on the DNS default fallback port number of 8448.
name and taking the default fallback port number of 8448.
Home servers may use SRV records to load balance requests between multiple TLS Home servers may use SRV records to load balance requests between multiple TLS
endpoints or to failover to another endpoint if an endpoint fails. endpoints or to failover to another endpoint if an endpoint fails.

@ -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 Content repository
================== ==================
HTTP API .. _module:content:
--------
Uploads are POSTed to a resource which returns a token which is used to GET This module allows users to upload content to their homeserver which is
the download. Uploads are POSTed to the sender's local homeserver, but are retrievable from other homeservers. Its' purpose is to allow users to share
downloaded from the recipient's local homeserver, which must thus first transfer attachments in a room. Content locations are represented as Matrix Content (MXC)
the content from the origin homeserver using the same API (unless the origin URIs. They look like::
and destination homeservers are the same). The upload/download API is::
=> POST /_matrix/media/v1/upload HTTP/1.1 mxc://<server-name>/<media-id>
Content-Type: <media-type>
<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 Uploads are POSTed to a resource on the user's local homeserver which returns a
Content-Type: application/json 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_repo_http_api}}
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>
Thumbnails
~~~~~~~~~~
The thumbnail methods are "crop" and "scale". "scale" tries to return an 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 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 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 the requested size. The client should scale the image if it needs to fit
within a given rectangle. within a given rectangle.
Server behaviour
----------------
Homeservers may generate thumbnails for content uploaded to remote Homeservers may generate thumbnails for content uploaded to remote
homeservers themselves or may rely on the remote homeserver to thumbnail homeservers themselves or may rely on the remote homeserver to thumbnail
the content. Homeservers may return thumbnails of a different size to that the content. Homeservers may return thumbnails of a different size to that
@ -56,21 +48,27 @@ Homeservers must never upscale images.
Security considerations Security considerations
----------------------- -----------------------
Clients may try to upload very large files. Homeservers should not store files The HTTP GET endpoint does not require any authentication. Knowing the URL of
that are too large and should not serve them to clients. the content is sufficient to retrieve the content, even if the entity isn't in
the room.
Homeservers have additional concerns:
- 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 very large images. Homeservers should not attempt to - Clients may try to upload very large images. Homeservers should not attempt to
generate thumbnails for images that are too large. generate thumbnails for images that are too large.
Remote homeservers may host very large files or images. Homeserver should not - Remote homeservers may host very large files or images. Homeservers should not
proxy or thumbnail large files or images from remote homeservers. proxy or thumbnail large files or images from remote homeservers.
Clients may try to upload a large number of files. Homeservers should limit the - 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. 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. - 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. Homeservers should restrict the number and size of remote files that it caches.
Clients or remote homeservers may try to upload malicious files targeting - Clients or remote homeservers may try to upload malicious files targeting
vulnerabilities in either the homeserver thumbnailing or the client decoders. vulnerabilities in either the homeserver thumbnailing or the client decoders.

@ -1,6 +1,8 @@
End-to-End Encryption End-to-End Encryption
===================== =====================
.. _module:e2e:
.. TODO-doc .. TODO-doc
- Why is this needed. - Why is this needed.
- Overview of process - Overview of process

@ -1,6 +1,8 @@
Room History Visibility Room History Visibility
----------------------- -----------------------
.. _module:history-visibility:
Whether a member of a room can see the events that happened in a room from Whether a member of a room can see the events that happened in a room from
before they joined the room is controlled by the ``history_visibility`` key before they joined the room is controlled by the ``history_visibility`` key
of the ``m.room.history_visibility`` state event. The valid values for of the ``m.room.history_visibility`` state event. The valid values for

@ -1,6 +1,8 @@
Instant Messaging Instant Messaging
================= =================
.. _module:im:
Events Events
------ ------

@ -1,63 +1,112 @@
Presence 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 User's presence state is represented by the ``presence`` key, which is an enum
"availability" of that user, suitable for display on other user's clients. of one of the following:
This is transmitted as an ``m.presence`` event and is one of the few events
which are sent *outside the context of a room*. The basic piece of presence
information is represented by the ``presence`` key, which is an enum of one
of the following:
- ``online`` : The default state when the user is connected to an event - ``online`` : The default state when the user is connected to an event
stream. stream.
- ``unavailable`` : The user is not reachable at this time. - ``unavailable`` : The user is not reachable at this time e.g. they are
- ``offline`` : The user is not connected to an event stream. 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 - ``free_for_chat`` : The user is generally willing to receive messages
moreso than default. 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 Events
------ ------
{{presence_events}} {{presence_events}}
Presence HTTP API Client behaviour
----------------- ----------------
.. TODO-spec
- Define how users receive presence invites, and how they accept/decline them Clients can manually set/get their presence/presence list using the HTTP APIs
listed below.
{{presence_http_api}} {{presence_http_api}}
Events on Change of Profile Information Idle timeout
--------------------------------------- ~~~~~~~~~~~~
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 Clients SHOULD implement an "idle timeout". This is a timer which fires after
propagation event to occur, informing likely-interested parties of the new a period of inactivity on the client. The definition of inactivity varies
values. This change is conveyed using two separate mechanisms: 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
- a ``m.room.member`` event is sent to every room the user is a member of, timer fires it should set the presence state to ``unavailable``. When the user
to update the ``displayname`` and ``avatar_url``. becomes active again (e.g. by moving the mouse) the client should set the
- a ``m.presence`` presence status update is sent, again containing the new values of the presence state to ``online``. A timeout value between 1 and 5 minutes is
``displayname`` and ``avatar_url`` keys, in addition to the required recommended.
``presence`` key containing the current presence state of the user.
Server behaviour
Both of these should be done automatically by the home server when a user ----------------
successfully changes their displayname or avatar URL fields.
Each user's home server stores a "presence list" per user. Once a user accepts
Additionally, when home servers emit room membership events for their own a presence list, both user's HSes must track the subscription.
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 Propagating profile information
perform extra roundtrips to query it. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
-----------------------
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 (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). 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 Rules also have an identifier, ``rule_id``, which is a string. The ``rule_id``
unique within the kind of rule and scope: rule_ids need not be unique between is unique within the kind of rule and scope: ``rule_ids`` need not be unique
rules of the same kind on different devices. 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
A home server may also have server default rules of each kind and in each scope. lower priority than user-defined rules in each scope. Server default rules (and
Server default rules are lower priority than user-defined rules in each scope. only server default rules) begin with a dot ('.') character. In addition, all
Server default rules (and only server default rules) begin with a dot ('.') rules may be enabled or disabled. Disabled rules never match.
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 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 (that is to say, the default action is "dont-notify"). Events that the user sent

@ -1,6 +1,8 @@
Push Notifications Push Notifications
================== ==================
.. _module:push:
Overview Overview
-------- --------

@ -1,64 +1,64 @@
Receipts Receipts
-------- ========
Receipts are used to publish which events in a room the user or their devices
have interacted with. For example, which events the user has read. For
efficiency this is done as "up to" markers, i.e. marking a particular event
as, say, ``read`` indicates the user has read all events *up to* that event.
Client-Server API
~~~~~~~~~~~~~~~~~
Clients will receive receipts in the following format::
{
"type": "m.receipt",
"room_id": <room_id>,
"content": {
<event_id>: {
<receipt_type>: {
<user_id>: { "ts": <ts>, ... },
...
}
},
...
}
}
For example:: .. _module:receipts:
{ This module adds in support for receipts. These receipts are a form of
"type": "m.receipt", acknowledgement of an event. This module defines a single acknowledgement:
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org", ``m.read`` which indicates that the user has read up to a given event.
"content": {
"$1435641916114394fHBLK:matrix.org": { Sending a receipt for each event can result in sending large amounts of traffic
"read": { to a homeserver. To prevent this from becoming a problem, receipts are implemented
"@erikj:jki.re": { "ts": 1436451550453 }, 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.
...
}
}
For efficiency, receipts are batched into one event per room. In the initialSync Events
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 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. single ``event_id``.
Deltas update existing mappings, clobbering based on ``user_id``,
``receipt_type`` pairs.
{{m_receipt_event}}
A client can update the markers for its user by issuing a request:: Client behaviour
----------------
POST /_matrix/client/v2_alpha/rooms/<room_id>/receipt/read/<event_id> 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::
Where the contents of the ``POST`` will be included in the content sent to Client receives m.receipt:
other users. The server will automatically set the ``ts`` field. user = @alice:example.com
receipt_type = m.read
event_id = $aaa:example.com
Client receives another m.receipt:
user = @alice:example.com
receipt_type = m.read
event_id = $bbb:example.com
Server-Server API The client should replace the older acknowledgement for $aaa:example.com with
~~~~~~~~~~~~~~~~~ this one for $bbb:example.com
Clients should send read receipts when there is some certainty that the event in
question has been **displayed** to the user. Simply receiving an event does not
provide enough certainty that the user has seen the event. The user SHOULD need
to *take some action* such as viewing the room that the event was sent to or
dismissing a notification in order for the event to count as "read".
A client can update the markers for its user by interacting with the following
HTTP APIs.
{{v2_receipts_http_api}}
Server behaviour
----------------
For efficiency, receipts SHOULD be batched into one event per room before
delivering them to clients.
Receipts are sent across federation as EDUs with type ``m.receipt``. The Receipts are sent across federation as EDUs with type ``m.receipt``. The
format of the EDUs are:: 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 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> .. _Event Graph: `sect:event-graph`_
Content: { "typing": true, "timeout": N }
# timeout is in milliseconds; suggested no more than 20 or 30 seconds
This should be re-sent by the client to continue informing the server the user Events
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.
To set "I am no longer typing":: {{m_typing_event}}
PUT ../rooms/<room_id>/typing/<user_id> Client behaviour
Content: { "typing": false } ----------------
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.
{ {{typing_http_api}}
"type": "m.typing",
"room_id": "!room-id-here:matrix.org",
"content": {
"user_ids": ["list of", "every user", "who is", "currently typing"]
}
}
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 APIs Server behaviour
~~~~~~~~~~~ ----------------
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", "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 This does not contain timing information so it is up to originating homeservers
originating HSes to ensure they eventually send "stop" notifications. to ensure they eventually send "stop" notifications.
.. TODO .. TODO
((This will eventually need addressing, as part of the wider typing/presence ((This will eventually need addressing, as part of the wider typing/presence
timer addition work)) 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 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 .. _module:voip:
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 This module outlines how two users in a room can set up a Voice over IP (VoIP)
with exactly two participants as currently the WebRTC standard is based around call to each other. Voice and video calls are built upon the WebRTC 1.0 standard.
two-party communication. 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}} {{voip_events}}
Message Exchange Client behaviour
~~~~~~~~~~~~~~~~ ----------------
A call is set up with messages exchanged as follows:
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. Calls are negotiated according to the WebRTC specification.
Glare 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 "Glare" is a problem which occurs when two users call each other at roughly the
better experience for the users if their calls are connected if it is clear same time. This results in the call failing to set up as there already is an
that their intention is to set up a call with one another. 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
In Matrix, calls are to rooms rather than users (even if those rooms may only same algorithm then they will both select the same call and the call will be
contain one other user) so we consider calls which are to the same room. The successfully connected.
rules for dealing with such a situation are as follows:
- If an invite to a room is received whilst the client is preparing to send an As calls are "placed" to rooms rather than users, the glare resolution algorithm
invite to the same room, the client should cancel its outgoing call and outlined below is only considered for calls which are to the same room. The
instead automatically accept the incoming call on behalf of the user. algorithm is as follows:
- 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 - If an ``m.call.invite`` to a room is received whilst the client is
lexicographical comparison of the call IDs of the two calls and use the **preparing to send** an ``m.call.invite`` to the same room:
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 client should cancel its outgoing call and instead
automatically accept the incoming call on behalf of the user.
- If an ``m.call.invite`` to a room is received **after the client has sent**
an ``m.call.invite`` to the same room and is waiting for a response:
* the client should perform a lexicographical comparison of the call IDs of
the two calls and use the *lesser* of the two calls, aborting the
greater. If the incoming call is the lesser, the client should accept
this call on behalf of the user.
The call setup should appear seamless to the user as if they had simply placed 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 setup for use on a call should be transferred and used for the call that
replaces it. 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 main: # arbitrary name to identify this build target
files: # the sort order of files to cat files: # the sort order of files to cat
- 0-intro.rst - 0-intro.rst
- { 1: 0-feature_profiles.rst }
- 1-client_server_api.rst - 1-client_server_api.rst
- { 1: 0-events.rst } - { 1: 0-events.rst }
- { 1: 0-event_signing.rst } - { 1: 0-event_signing.rst }
- 2-modules.rst - 2-modules.rst
- { 1: 0-feature_profiles.rst }
- { 1: "group:modules" } # reference a group of files - { 1: "group:modules" } # reference a group of files
- 3-application_service_api.rst - 3-application_service_api.rst
- 4-server_server_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 # check the input files and substitute in sections where required
log("Parsing input template: %s" % file_stream.name) 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 # do sanity checking on the template to make sure they aren't reffing things
# which will never be replaced with a section. # which will never be replaced with a section.
ast = env.parse(temp_str) ast = env.parse(temp_str)
@ -140,7 +140,7 @@ def main(input_module, file_stream=None, out_dir=None, verbose=False):
with open( with open(
os.path.join(out_dir, os.path.basename(file_stream.name)), "w" os.path.join(out_dir, os.path.basename(file_stream.name)), "w"
) as f: ) as f:
f.write(output) f.write(output.encode("utf-8"))
log("Output file for: %s" % file_stream.name) log("Output file for: %s" % file_stream.name)
check_unaccessed("units", units) check_unaccessed("units", units)

@ -31,18 +31,18 @@ Response format:
{% for table in endpoint.res_tables -%} {% for table in endpoint.res_tables -%}
{{"``"+table.title+"``" if table.title else "" }} {{"``"+table.title+"``" if table.title else "" }}
================== ================= =========================================== =================== ================= ==========================================
Param Type Description Param Type Description
================== ================= =========================================== =================== ================= ==========================================
{% for row in table.rows -%} {% 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 -#} {# 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 -%}
================== ================= =========================================== =================== ================= ==========================================
{% endfor %} {% endfor %}
{% endif -%} {% endif -%}

@ -19,6 +19,7 @@ import yaml
V1_CLIENT_API = "../api/client-server/v1" V1_CLIENT_API = "../api/client-server/v1"
V1_EVENT_EXAMPLES = "../event-schemas/examples/v1" V1_EVENT_EXAMPLES = "../event-schemas/examples/v1"
V1_EVENT_SCHEMA = "../event-schemas/schema/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" CORE_EVENT_SCHEMA = "../event-schemas/schema/v1/core-event-schema"
CHANGELOG = "../CHANGELOG.rst" CHANGELOG = "../CHANGELOG.rst"
TARGETS = "../specification/targets.yaml" TARGETS = "../specification/targets.yaml"
@ -49,8 +50,17 @@ def get_json_schema_object_fields(obj, enforce_title=False):
} }
tables = [fields] tables = [fields]
props = obj.get("properties", obj.get("patternProperties"))
parents = obj.get("allOf") 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: if not props and not parents:
raise Exception( raise Exception(
"Object %s has no properties or parents." % obj "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]["type"] == "object":
if props[key_name].get("additionalProperties"): if props[key_name].get("additionalProperties"):
# not "really" an object, just a KV store # not "really" an object, just a KV store
value_type = ( prop_val = props[key_name]["additionalProperties"]["type"]
"{string: %s}" % if prop_val == "object":
props[key_name]["additionalProperties"]["type"] 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: else:
nested_object = get_json_schema_object_fields( nested_object = get_json_schema_object_fields(
props[key_name], props[key_name],
@ -151,6 +168,13 @@ class MatrixUnits(Units):
# assign value expected for this param # assign value expected for this param
val_type = param.get("type") # integer/string 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 refType = Units.prop(param, "schema/$ref/") # Error,Event
schemaFmt = Units.prop(param, "schema/format") # bytes e.g. uploads schemaFmt = Units.prop(param, "schema/format") # bytes e.g. uploads
if not val_type and refType: if not val_type and refType:
@ -253,17 +277,27 @@ class MatrixUnits(Units):
if good_response: if good_response:
self.log("Found a 200 response for this API") self.log("Found a 200 response for this API")
res_type = Units.prop(good_response, "schema/type") 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"]: if res_type and res_type not in ["object", "array"]:
# response is a raw string or something like that # response is a raw string or something like that
endpoint["res_tables"].append({ good_table = {
"title": None, "title": None,
"rows": [{ "rows": [{
"key": good_response["schema"].get("name", ""), "key": "<" + res_type + ">" if not res_name else res_name,
"type": res_type, "type": res_type,
"desc": res.get("description", ""), "desc": res.get("description", ""),
"req_str": "" "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"): elif res_type and Units.prop(good_response, "schema/properties"):
# response is an object: # response is an object:
schema = good_response["schema"] schema = good_response["schema"]
@ -320,18 +354,27 @@ class MatrixUnits(Units):
} }
def load_swagger_apis(self): def load_swagger_apis(self):
path = V1_CLIENT_API paths = [
V1_CLIENT_API, V2_CLIENT_API
]
apis = {} apis = {}
for filename in os.listdir(path): for path in paths:
if not filename.endswith(".yaml"): 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 continue
self.log("Reading swagger API: %s" % filename) for filename in os.listdir(path):
with open(os.path.join(path, filename), "r") as f: if not filename.endswith(".yaml"):
# strip .yaml continue
group_name = filename[:-5] self.log("Reading swagger API: %s" % filename)
api = yaml.load(f.read()) with open(os.path.join(path, filename), "r") as f:
api["__meta"] = self._load_swagger_meta(api, group_name) # strip .yaml
apis[group_name] = api 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 return apis
def load_common_event_fields(self): def load_common_event_fields(self):

Loading…
Cancel
Save