Merge branch 'master' into macaroons

pull/977/head
Daniel Wagner-Hall 9 years ago
commit 1abb82d6e0

5
.gitignore vendored

@ -1,3 +1,8 @@
scripts/gen
scripts/continuserv/continuserv
scripts/speculator/speculator
templating/out
*.pyc
supporting-docs/_site
supporting-docs/.sass-cache
api/node_modules

@ -0,0 +1,129 @@
swagger: '2.0'
info:
title: "Matrix Client-Server v1 Room Membership API"
version: "1.0.0"
host: localhost:8008
schemes:
- https
- http
basePath: /_matrix/client/api/v1
consumes:
- application/json
produces:
- application/json
securityDefinitions:
accessToken:
type: apiKey
description: The user_id or application service access_token
name: access_token
in: query
paths:
"/rooms/{roomId}/join":
post:
summary: Start the requesting user participating in a particular room.
description: |-
This API starts a user participating in a particular room, if that user
is allowed to participate in that room. After this call, the client is
allowed to see all current state events in the room, and all subsequent
events associated with the room until the user leaves the room.
After a user has joined a room, the room will appear as an entry in the
response of the |initialSync| API.
security:
- accessToken: []
parameters:
- in: path
type: string
name: roomId
description: The room identifier or room alias to join.
required: true
x-example: "#monkeys:matrix.org"
responses:
200:
description: |-
The room has been joined.
The joined room ID must be returned in the ``room_id`` field.
examples:
application/json: |-
{"room_id": "!d41d8cd:matrix.org"}
schema:
type: object
403:
description: |-
You do not have permission to join the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejection are:
- The room is invite-only and the user was not invited.
- The user has been banned from the room.
examples:
application/json: |-
{"errcode": "M_FORBIDDEN", "error": "You are not invited to this room."}
429:
description: This request was rate-limited.
schema:
"$ref": "definitions/error.yaml"
x-alias:
canonical-link: "post-matrix-client-api-v1-rooms-roomid-join"
aliases:
- /join/{roomId}
"/rooms/{roomId}/invite":
post:
summary: Invite a user to participate in a particular room.
# It's a crying shame that I don't know how to force line breaks.
description: |-
This API invites a user to participate in a particular room.
They do not start participating in the room until they actually join the
room.
This serves two purposes; firstly, to notify the user that the room
exists (and that their presence is requested). Secondly, some rooms can
only be joined if a user is invited to join it; sending the invite gives
that user permission to join the room.
Only users currently in a particular room can invite other users to
join that room.
security:
- accessToken: []
parameters:
- in: path
type: string
name: roomId
description: The room identifier (not alias) to which to invite the user.
required: true
x-example: "!d41d8cd:matrix.org"
- in: body
name: user_id
required: true
schema:
type: object
example: |-
{
"user_id": "@cheeky_monkey:matrix.org"
}
properties:
user_id:
type: string
description: The fully qualified user ID of the invitee.
required: ["user_id"]
responses:
200:
description: The user has been invited to join the room.
examples:
application/json: |-
{}
schema:
type: object # empty json object
403:
description: |-
You do not have permission to invite the user to the room. A meaningful ``errcode`` and description error text will be returned. Example reasons for rejections are:
- The invitee has been banned from the room.
- The invitee is already a member of the room.
- The inviter is not currently in the room.
- The inviter's power level is insufficient to invite users to the room.
examples:
application/json: |-
{"errcode": "M_FORBIDDEN", "error": "@cheeky_monkey:matrix.org is banned from the room"}
429:
description: This request was rate-limited.
schema:
"$ref": "definitions/error.yaml"

@ -206,4 +206,3 @@ paths:
title: PresenceEvent
allOf:
- "$ref": "definitions/event.yaml"

@ -285,8 +285,11 @@ paths:
chunk:
type: array
description: |-
A list of the most recent messages for this room. This
array will consist of at most ``limit`` elements.
If the user is a member of the room this will be a
list of the most recent messages for this room. If
the user has left the room this will be the
messages that preceeded them leaving. This array
will consist of at most ``limit`` elements.
items:
type: object
title: RoomEvent
@ -296,8 +299,10 @@ paths:
state:
type: array
description: |-
A list of state events representing the current state
of the room.
If the user is a member of the room this will be the
current state of the room as a list of events. If the
user has left the room this will be the state of the
room when they left it.
items:
title: StateEvent
type: object
@ -347,4 +352,4 @@ paths:
allOf:
- "$ref": "definitions/event.yaml"
404:
description: The event was not found or you do not have permission to read this event.
description: The event was not found or you do not have permission to read this event.

@ -0,0 +1,13 @@
{
"type": "m.receipt",
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
"content": {
"$1435641916114394fHBLK:matrix.org": {
"read": {
"@rikj:jki.re": {
"ts": 1436451550453
}
}
}
}
}

@ -0,0 +1,12 @@
{
"age": 242352,
"content": {
"alias": "#somewhere:localhost"
},
"state_key": "",
"origin_server_ts": 1431961217939,
"event_id": "$WLGTSEFSEF:localhost",
"type": "m.room.canonical_alias",
"room_id": "!Cuyf34gef24t:localhost",
"user_id": "@example:localhost"
}

@ -0,0 +1,12 @@
{
"age": 242352,
"content": {
"history_visibility": "shared"
},
"state_key": "",
"origin_server_ts": 1431961217939,
"event_id": "$WLGTSEFSEF:localhost",
"type": "m.room.history_visibility",
"room_id": "!Cuyf34gef24t:localhost",
"user_id": "@example:localhost"
}

@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"title": "Receipt Event",
"description": "Informs the client of new receipts.",
"properties": {
"content": {
"type": "object",
"description": "The event ids which the receipts relate to.",
"patternProperties": {
"^\\$": {
"type": "object",
"description": "The types of the receipts.",
"additionalProperties": {
"type": "object",
"description": "User ids of the receipts",
"patternProperties": {
"^@": {
"type": "object",
"properties": {
"ts": {
"type": "number",
"description": "The timestamp the receipt was sent at"
}
}
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"type": {
"type": "string",
"enum": ["m.receipt"]
},
"room_id": {
"type": "string"
}
},
"required": ["room_id", "type", "content"]
}

@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"title": "Informs the room as to which alias is the canonical one.",
"description": "This event is used to inform the room about which alias should be considered the canonical one. This could be for display purposes or as suggestion to users which alias to use to advertise the room.",
"allOf": [{
"$ref": "core#/definitions/state_event"
}],
"properties": {
"content": {
"type": "object",
"properties": {
"alias": {
"type": "string",
"description": "The canonical alias."
}
},
"required": ["alias"]
},
"state_key": {
"type": "string",
"description": "A zero-length string.",
"pattern": "^$"
},
"type": {
"type": "string",
"enum": ["m.room.canonical_alias"]
}
}
}

@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"title": "Controls visibility of history.",
"description": "This event controls whether a member of a room can see the events that happened in a room from before they joined.",
"allOf": [{
"$ref": "core#/definitions/state_event"
}],
"properties": {
"content": {
"type": "object",
"properties": {
"history_visibility": {
"type": "string",
"description": "Who can see the room history.",
"enum": ["invited","joined","shared"]
}
},
"required": ["history_visibility"]
},
"state_key": {
"type": "string",
"description": "A zero-length string.",
"pattern": "^$"
},
"type": {
"type": "string",
"enum": ["m.room.history_visibility"]
}
}
}

@ -0,0 +1,6 @@
continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
To run it, you must install the `go` tool. You will also need to install fsnotify by running:
`go get gopkg.in/fsnotify.v1`
You can then run continuserv by running:
`go run main.go`

@ -0,0 +1,144 @@
// continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
// It will always serve the most recent version of the spec, and may block an HTTP request until regeneration is finished.
// It does not currently pre-empt stale generations, but will block until they are complete.
package main
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"sync"
"sync/atomic"
fsnotify "gopkg.in/fsnotify.v1"
)
var (
port = flag.Int("port", 8000, "Port on which to serve HTTP")
toServe atomic.Value // Always contains valid []byte to serve. May be stale unless wg is zero.
wg sync.WaitGroup // Indicates how many updates are pending.
mu sync.Mutex // Prevent multiple updates in parallel.
)
func main() {
flag.Parse()
w, err := fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Error making watcher: %v", err)
}
dir, err := os.Getwd()
if err != nil {
log.Fatalf("Error getting wd: %v", err)
}
for ; !exists(path.Join(dir, ".git")); dir = path.Dir(dir) {
if dir == "/" {
log.Fatalf("Could not find git root")
}
}
filepath.Walk(dir, makeWalker(w))
wg.Add(1)
populateOnce(dir)
ch := make(chan struct{}, 100) // Buffered to ensure we can multiple-increment wg for pending writes
go doPopulate(ch, dir)
go watchFS(ch, w)
http.HandleFunc("/", serve)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}
func watchFS(ch chan struct{}, w *fsnotify.Watcher) {
for {
select {
case e := <-w.Events:
if filter(e) {
wg.Add(1)
fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name)
ch <- struct{}{}
}
}
}
}
func makeWalker(w *fsnotify.Watcher) filepath.WalkFunc {
return func(path string, _ os.FileInfo, err error) error {
if err != nil {
log.Fatalf("Error walking: %v", err)
}
if err := w.Add(path); err != nil {
log.Fatalf("Failed to add watch: %v", err)
}
return nil
}
}
// Return true if event should trigger re-population
func filter(e fsnotify.Event) bool {
// vim is *really* noisy about how it writes files
if e.Op != fsnotify.Write {
return false
}
// Avoid some temp files that vim writes
if strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".swp") || strings.HasPrefix(e.Name, ".") {
return false
}
// Avoid infinite cycles being caused by writing actual output
if strings.Contains(e.Name, "/tmp/") || strings.Contains(e.Name, "/gen/") {
return false
}
return true
}
func serve(w http.ResponseWriter, req *http.Request) {
wg.Wait()
b := toServe.Load().([]byte)
w.Write(b)
}
func populateOnce(dir string) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
cmd := exec.Command("python", "gendoc.py")
cmd.Dir = path.Join(dir, "scripts")
var b bytes.Buffer
cmd.Stderr = &b
err := cmd.Run()
if err != nil {
toServe.Store([]byte(fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String()).Error()))
return
}
specBytes, err := ioutil.ReadFile(path.Join(dir, "scripts", "gen", "specification.html"))
if err != nil {
toServe.Store([]byte(fmt.Errorf("error reading spec: %v", err).Error()))
return
}
toServe.Store(specBytes)
}
func doPopulate(ch chan struct{}, dir string) {
for _ = range ch {
populateOnce(dir)
}
}
func exists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}

@ -32,17 +32,23 @@ def rst2html(i, o):
)
def run_through_template(input):
null = open(os.devnull, 'w')
subprocess.check_output(
[
'python', 'build.py',
"-i", "matrix_templates",
"-o", "../scripts/tmp",
"../scripts/"+input
],
stderr=null,
cwd="../templating",
)
tmpfile = './tmp/output'
try:
with open(tmpfile, 'w') as out:
subprocess.check_output(
[
'python', 'build.py',
"-i", "matrix_templates",
"-o", "../scripts/tmp",
"../scripts/"+input
],
stderr=out,
cwd="../templating",
)
except subprocess.CalledProcessError as e:
with open(tmpfile, 'r') as f:
print f.read()
raise
def prepare_env():
try:
@ -65,16 +71,19 @@ def main():
run_through_template("tmp/howto.rst")
rst2html("tmp/full_spec.rst", "gen/specification.html")
rst2html("tmp/howto.rst", "gen/howtos.html")
cleanup_env()
if "--nodelete" not in sys.argv:
cleanup_env()
if __name__ == '__main__':
if len(sys.argv) > 1:
# we accept no args, so they don't know what they're doing!
if len(sys.argv) > 1 and sys.argv[1:] != ["--nodelete"]:
# we accept almost no args, so they don't know what they're doing!
print "gendoc.py - Generate the Matrix specification as HTML."
print "Usage:"
print " python gendoc.py"
print " python gendoc.py [--nodelete]"
print ""
print "The specification can then be found in the gen/ folder."
print ("If --nodelete was specified, intermediate files will be "
"present in the tmp/ folder.")
print ""
print "Requirements:"
print " - This script requires Jinja2 and rst2html (docutils)."

@ -0,0 +1,9 @@
speculator allows you to preview pull requests to the matrix.org specification.
It serves the following HTTP endpoints:
- / lists open pull requests
- /spec/123 which renders the spec as html at pull request 123.
- /diff/rst/123 which gives a diff of the spec's rst at pull request 123.
To run it, you must install the `go` tool, and run:
`go run main.go`

@ -0,0 +1,270 @@
// speculator allows you to preview pull requests to the matrix.org specification.
// It serves the following HTTP endpoints:
// - / lists open pull requests
// - /spec/123 which renders the spec as html at pull request 123.
// - /diff/rst/123 which gives a diff of the spec's rst at pull request 123.
// It is currently woefully inefficient, and there is a lot of low hanging fruit for improvement.
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"net/http"
"os"
"os/exec"
"path"
"strconv"
"strings"
"syscall"
)
type PullRequest struct {
Number int
Base Commit
Head Commit
Title string
User User
HTMLURL string `json:"html_url"`
}
type Commit struct {
SHA string
Repo RequestRepo
}
type RequestRepo struct {
CloneURL string `json:"clone_url"`
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
var (
port = flag.Int("port", 9000, "Port on which to listen for HTTP")
allowedMembers map[string]bool
)
const pullsPrefix = "https://api.github.com/repos/matrix-org/matrix-doc/pulls"
func gitClone(url string) (string, error) {
dst := path.Join("/tmp/matrix-doc", strconv.FormatInt(rand.Int63(), 10))
cmd := exec.Command("git", "clone", url, dst)
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("error cloning repo: %v", err)
}
return dst, nil
}
func gitCheckout(path, sha string) error {
cmd := exec.Command("git", "checkout", sha)
cmd.Dir = path
err := cmd.Run()
if err != nil {
return fmt.Errorf("error checking out repo: %v", err)
}
return nil
}
func lookupPullRequest(prNumber string) (*PullRequest, error) {
resp, err := http.Get(fmt.Sprintf("%s/%s", pullsPrefix, prNumber))
defer resp.Body.Close()
if err != nil {
return nil, fmt.Errorf("error getting pulls: %v", err)
}
dec := json.NewDecoder(resp.Body)
var pr PullRequest
if err := dec.Decode(&pr); err != nil {
return nil, fmt.Errorf("error decoding pulls: %v", err)
}
return &pr, nil
}
func generate(dir string) error {
cmd := exec.Command("python", "gendoc.py", "--nodelete")
cmd.Dir = path.Join(dir, "scripts")
var b bytes.Buffer
cmd.Stderr = &b
err := cmd.Run()
if err != nil {
return fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())
}
return nil
}
func writeError(w http.ResponseWriter, err error) {
w.WriteHeader(500)
io.WriteString(w, fmt.Sprintf("%v\n", err))
}
// generateAt generates spec from repo at sha.
// Returns the path where the generation was done.
func generateAt(repo, sha string) (dst string, err error) {
dst, err = gitClone(repo)
if err != nil {
return
}
if err = gitCheckout(dst, sha); err != nil {
return
}
err = generate(dst)
return
}
func serveSpec(w http.ResponseWriter, req *http.Request) {
parts := strings.Split(req.URL.Path, "/")
if len(parts) != 3 {
w.WriteHeader(400)
io.WriteString(w, fmt.Sprintf("Invalid path passed: %v expect /pull/123", req.URL.Path))
return
}
pr, err := lookupPullRequest(parts[2])
if err != nil {
writeError(w, 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 !allowedMembers[pr.User.Login] {
w.WriteHeader(403)
io.WriteString(w, fmt.Sprintf("%q is not a trusted pull requester", pr.User.Login))
return
}
dst, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA)
defer os.RemoveAll(dst)
if err != nil {
writeError(w, err)
return
}
b, err := ioutil.ReadFile(path.Join(dst, "scripts/gen/specification.html"))
if err != nil {
writeError(w, fmt.Errorf("Error reading spec: %v", err))
return
}
w.Write(b)
}
func serveRstDiff(w http.ResponseWriter, req *http.Request) {
parts := strings.Split(req.URL.Path, "/")
if len(parts) != 4 {
w.WriteHeader(400)
io.WriteString(w, fmt.Sprintf("Invalid path passed: %v expect /diff/rst/123", req.URL.Path))
return
}
pr, err := lookupPullRequest(parts[3])
if err != nil {
writeError(w, 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 !allowedMembers[pr.User.Login] {
w.WriteHeader(403)
io.WriteString(w, fmt.Sprintf("%q is not a trusted pull requester", pr.User.Login))
return
}
base, err := generateAt(pr.Base.Repo.CloneURL, pr.Base.SHA)
defer os.RemoveAll(base)
if err != nil {
writeError(w, err)
return
}
head, err := generateAt(pr.Head.Repo.CloneURL, pr.Head.SHA)
defer os.RemoveAll(head)
if err != nil {
writeError(w, err)
return
}
diffCmd := exec.Command("diff", "-u", path.Join(base, "scripts", "tmp", "full_spec.rst"), path.Join(head, "scripts", "tmp", "full_spec.rst"))
var diff bytes.Buffer
diffCmd.Stdout = &diff
if err := ignoreExitCodeOne(diffCmd.Run()); err != nil {
writeError(w, fmt.Errorf("error running diff: %v", err))
return
}
w.Write(diff.Bytes())
}
func listPulls(w http.ResponseWriter, req *http.Request) {
resp, err := http.Get(pullsPrefix)
if err != nil {
writeError(w, err)
return
}
defer resp.Body.Close()
dec := json.NewDecoder(resp.Body)
var pulls []PullRequest
if err := dec.Decode(&pulls); err != nil {
writeError(w, err)
return
}
if len(pulls) == 0 {
io.WriteString(w, "No pull requests found")
return
}
s := "<body><ul>"
for _, pull := range pulls {
s += fmt.Sprintf(`<li>%d: <a href="%s">%s</a>: <a href="%s">%s</a>: <a href="spec/%d">spec</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)
}
s += "</ul></body>"
io.WriteString(w, s)
}
func ignoreExitCodeOne(err error) error {
if err == nil {
return err
}
if exiterr, ok := err.(*exec.ExitError); ok {
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() == 1 {
return nil
}
}
}
return err
}
func main() {
flag.Parse()
// It would be great to read this from github, but there's no convenient way to do so.
// Most of these memberships are "private", so would require some kind of auth.
allowedMembers = map[string]bool{
"dbkr": true,
"erikjohnston": true,
"illicitonion": true,
"Kegsay": true,
"NegativeMjark": true,
}
http.HandleFunc("/spec/", serveSpec)
http.HandleFunc("/diff/rst/", serveRstDiff)
http.HandleFunc("/healthz", serveText("ok"))
http.HandleFunc("/", listPulls)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}
func serveText(s string) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, s)
}
}

@ -879,54 +879,20 @@ The keys contained in ``m.room.power_levels`` determine the levels required for
certain operations such as kicking, banning and sending state events. See
`m.room.power_levels`_ for more information.
Joining rooms
~~~~~~~~~~~~~
.. TODO-doc What does the home server have to do to join a user to a room?
- See SPEC-30.
Users need to join a room in order to send and receive events in that room. A
user can join a room by making a request to |/join/<room_alias_or_id>|_ with::
{}
Alternatively, a user can make a request to |/rooms/<room_id>/join|_ with the
same request content. This is only provided for symmetry with the other
membership APIs: ``/rooms/<room id>/invite`` and ``/rooms/<room id>/leave``. If
a room alias was specified, it will be automatically resolved to a room ID,
which will then be joined. The room ID that was joined will be returned in
response::
{
"room_id": "!roomid:domain"
}
The membership state for the joining user can also be modified directly to be
``join`` by sending the following request to
``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
{
"membership": "join"
}
See the `Room events`_ section for more information on ``m.room.member``.
After the user has joined a room, they will receive subsequent events in that
room. This room will now appear as an entry in the |initialSync|_ API.
-------------
Users need to be a member of a room in order to send and receive events in that
room. There are several states in which a user may be, in relation to a room:
Some rooms enforce that a user is *invited* to a room before they can join that
room. Other rooms will allow anyone to join the room even if they have not
received an invite.
- Unrelated (the user cannot send or receive events in the room)
- Invited (the user has been invited to participate in the room, but is not
yet participating)
- Joined (the user can send and receive events in the room)
- Banned (the user is not allowed to join the room)
Inviting users
~~~~~~~~~~~~~~
.. TODO-doc Invite-join dance
- Outline invite join dance. What is it? Why is it required? How does it work?
- What does the home server have to do?
Some rooms require that users be invited to it before they can join; others
allow anyone to join.
The purpose of inviting users to a room is to notify them that the room exists
so they can choose to become a member of that room. Some rooms require that all
users who join a room are previously invited to it (an "invite-only" room).
Whether a given room is an "invite-only" room is determined by the room config
key ``m.room.join_rules``. It can have one of the following values:
@ -936,26 +902,7 @@ key ``m.room.join_rules``. It can have one of the following values:
``invite``
This room can only be joined if you were invited.
Only users who have a membership state of ``join`` in a room can invite new
users to said room. The person being invited must not be in the ``join`` state
in the room. The fully-qualified user ID must be specified when inviting a
user, as the user may reside on a different home server. To invite a user, send
the following request to |/rooms/<room_id>/invite|_, which will manage the
entire invitation process::
{
"user_id": "<user id to invite>"
}
Alternatively, the membership state for this user in this room can be modified
directly by sending the following request to
``/rooms/<room id>/state/m.room.member/<url encoded user id>``::
{
"membership": "invite"
}
See the `Room events`_ section for more information on ``m.room.member``.
{{membership_http_api}}
Leaving rooms
~~~~~~~~~~~~~

@ -0,0 +1,81 @@
Receipts
========
Receipts are used to publish which events in a room the user or their devices
have interacted with. For example, which events the user has read.
For efficiency this is done as "up to" markers, i.e. marking a particular event
as, say, ``read`` indicates the user has read all events *up to* that event.
Client-Server API
-----------------
Clients will receive receipts in the following format::
{
"type": "m.receipt",
"room_id": <room_id>,
"content": {
<event_id>: {
<receipt_type>: {
<user_id>: { "ts": <ts>, ... },
...
}
},
...
}
}
For example::
{
"type": "m.receipt",
"room_id": "!KpjVgQyZpzBwvMBsnT:matrix.org",
"content": {
"$1435641916114394fHBLK:matrix.org": {
"read": {
"@erikj:jki.re": { "ts": 1436451550453 },
...
}
},
...
}
}
For efficiency, receipts are batched into one event per room. In the initialSync
and v2 sync APIs the receipts are listed in a seperate top level ``receipts``
key.
Each ``user_id``, ``receipt_type`` pair must be associated with only a single
``event_id``.
New receipts that come down the event streams are deltas. Deltas update
existing mappings, clobbering based on ``user_id``, ``receipt_type`` pairs.
A client can update the markers for its user by issuing a request::
POST /_matrix/client/v2_alpha/rooms/<room_id>/receipt/read/<event_id>
Where the contents of the ``POST`` will be included in the content sent to
other users. The server will automatically set the ``ts`` field.
Server-Server API
-----------------
Receipts are sent across federation as EDUs with type ``m.receipt``. The
format of the EDUs are::
{
<room_id>: {
<receipt_type>: {
<user_id>: { <content> }
},
...
},
...
}
These are always sent as deltas to previously sent reciepts.

@ -0,0 +1,24 @@
Room History Visibility
=======================
Whether a member of a room can see the events that happened in a room from
before they joined the room is controlled by the ``history_visibility`` key
of the ``m.room.history_visibility`` state event. The valid values for
``history_visibility`` are:
- ``shared``
- ``invited``
- ``joined``
By default if no ``history_visibility`` is set it is assumed to be ``shared``.
The rules governing whether a user is allowed to see an event depend solely on
the state of the room at that event:
1. If the user was joined, allow.
2. If the user was invited and the ``history_visibility`` was set to
``invited`` or ``shared``, allow.
3. If the user was neither invited nor joined but the ``history_visibility``
was set to ``shared``, allow.
4. Otherwise, deny.

@ -0,0 +1,16 @@
# Site settings
title: Matrix
email: webmaster@matrix.org
description: > # this means to ignore newlines until "baseurl:"
Matrix.org documentation
baseurl: "/docs/guides" # the subpath of your site, e.g. /blog/
url: "http://matrix.org" # the base hostname & protocol for your site
twitter_username: matrixdotorg
github_username: matrix-org
# Build settings
markdown: kramdown
#defaults:
#permalink: /:categories/:title.html # can use this when/if we use jekyll for all docs
permalink: :title.html

@ -0,0 +1 @@
<div id="footer"><div id="footerContent">&copy 2014-2015 Matrix.org</div></div>

@ -0,0 +1,16 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %}</title>
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
<link rel="stylesheet" href="http://matrix.org/site.css">
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.baseurl }}">
<link rel="stylesheet" href="{{ "/css/site_overrides.css" | prepend: site.baseurl }}">
<link rel="stylesheet" href="{{ "/css/basic.css" | prepend: site.baseurl }}">
<link rel="stylesheet" href="{{ "/css/nature.css" | prepend: site.baseurl }}">
<link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend: site.baseurl | prepend: site.url }}">
<link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" />

@ -0,0 +1,22 @@
<div id="header">
<div id="headerContent">
<a href="/#about2" class="navButton">About</a>
<a href="/docs/howtos" class="navButton">HOWTOs</a>
<a href="/docs/spec" class="navButton">Spec</a>
<a href="/docs/api" class="navButton">APIs</a>
<a href="/code" class="navButton">Code</a>
<a href="/jira" class="navButton">JIRA</a>
<a href="/blog/faq" class="navButton">FAQ</a>
<a href="/blog" class="navButton">Blog</a>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-54779209-1', 'auto');
ga('send', 'pageview');
</script>
</div>
</div>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
{% include head.html %}
</head>
<body>
<div class="page-content" id="page">
{% include nav.html %}
<div class="wrapper" id="wrapper">
<div class="document_foo" id="document">
{{ content }}
</div>
</div>
<div class="push"></div>
</div>
{% include footer.html %}
</body>
</html>

@ -0,0 +1,14 @@
---
layout: default
---
<div class="post">
<header class="post-header">
<h1 class="post-title">{{ page.title }}</h1>
</header>
<article class="post-content">
{{ content }}
</article>
</div>

@ -0,0 +1,6 @@
---
layout: default
---
{{ content }}

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2011 Greg Thornton, http://xdissent.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

@ -0,0 +1,97 @@
Overview
========
This plugin adds `ReStructuredText`_ support to `Jekyll`_ and `Octopress`_.
It renders ReST in posts and pages, and provides a custom directive to
support Octopress-compatible syntax highlighting.
Requirements
============
* Jekyll *or* Octopress >= 2.0
* Docutils
* Pygments
* `RbST`_
Installation
============
1. Install Docutils and Pygments.
The most convenient way is to use virtualenv_burrito:
::
$ curl -s https://raw.github.com/brainsik/virtualenv-burrito/master/virtualenv-burrito.sh | bash
$ source /Users/xdissent/.venvburrito/startup.sh
$ mkvirtualenv jekyll-rst
$ pip install docutils pygments
2. Install RbST.
If you use `bundler`_ with Octopress, add ``gem 'RbST'`` to
your ``Gemfile`` in the ``development`` group, then run
``bundle install``. Otherwise, ``gem install RbST``.
3. Install the plugin.
For Jekyll:
::
$ cd <jekyll-project-path>
$ git submodule add https://github.com/xdissent/jekyll-rst.git _plugins/jekyll-rst
For Octopress:
::
$ cd <octopress-project-path>
$ git submodule add https://github.com/xdissent/jekyll-rst.git plugins/jekyll-rst
4. Start blogging in ReStructuredText. Any file with the ``.rst`` extension
will be parsed as ReST and rendered into HTML.
.. note:: Be sure to activate the ``jekyll-rst`` virtualenv before generating
the site by issuing a ``workon jekyll-rst``. I suggest you follow `Harry
Marr's advice`_ and create a ``.venv`` file that will automatically
activate the ``jekyll-rst`` virtualenv when you ``cd`` into your project.
Source Code Highlighting
========================
A ``code-block`` ReST directive is registered and aliased as ``sourcecode``.
It adds syntax highlighting to code blocks in your documents::
.. code-block:: ruby
# Output "I love ReST"
say = "I love ReST"
puts say
Optional arguments exist to supply a caption, link, and link title::
.. code-block:: console
:caption: Read Hacker News on a budget
:url: http://news.ycombinator.com
:title: Hacker News
$ curl http://news.ycombinator.com | less
Octopress already includes style sheets for syntax highlighting, but you'll
need to generate one yourself if using Jekyll::
$ pygmentize -S default -f html > css/pygments.css
Octopress Tips
==============
* Use ``.. more`` in your ReST documents to indicate where Octopress's
``excerpt`` tag should split your content for summary views.
.. _ReStructuredText: http://docutils.sourceforge.net/rst.html
.. _Jekyll: http://jekyllrb.com/
.. _Octopress: http://octopress.com/
.. _RbST: http://rubygems.org/gems/RbST
.. _bundler: http://gembundler.com/
.. _Harry Marr's advice: http://hmarr.com/2010/jan/19/making-virtualenv-play-nice-with-git/

@ -0,0 +1,30 @@
require 'rbst'
module Jekyll
class RestConverter < Converter
safe true
priority :low
def matches(ext)
ext =~ /rst/i
end
def output_ext(ext)
".html"
end
def convert(content)
RbST.executables = {:html => "#{File.expand_path(File.dirname(__FILE__))}/rst2html.py"}
RbST.new(content).to_html(:part => :fragment, :initial_header_level => 2)
end
end
module Filters
def restify(input)
site = @context.registers[:site]
converter = site.getConverterImpl(Jekyll::RestConverter)
converter.convert(input)
end
end
end

@ -0,0 +1,97 @@
# Define a new directive `code-block` (aliased as `sourcecode`) that uses the
# `pygments` source highlighter to render code in color.
#
# Incorporates code from the `Pygments`_ documentation for `Using Pygments in
# ReST documents`_ and `Octopress`_.
#
# .. _Pygments: http://pygments.org/
# .. _Using Pygments in ReST documents: http://pygments.org/docs/rstdirective/
# .. _Octopress: http://octopress.org/
import re
import os
import md5
import __main__
# Absolute path to pygments cache dir
PYGMENTS_CACHE_DIR = os.path.abspath(os.path.join(os.path.dirname(__main__.__file__), '../../.pygments-cache'))
# Ensure cache dir exists
if not os.path.exists(PYGMENTS_CACHE_DIR):
os.makedirs(PYGMENTS_CACHE_DIR)
from pygments.formatters import HtmlFormatter
from docutils import nodes
from docutils.parsers.rst import directives, Directive
from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer
class Pygments(Directive):
""" Source code syntax hightlighting.
"""
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
string_opts = ['title', 'url', 'caption']
option_spec = dict([(key, directives.unchanged) for key in string_opts])
has_content = True
def run(self):
self.assert_has_content()
try:
lexer_name = self.arguments[0]
lexer = get_lexer_by_name(lexer_name)
except ValueError:
# no lexer found - use the text one instead of an exception
lexer_name = 'text'
lexer = TextLexer()
formatter = HtmlFormatter()
# Construct cache filename
cache_file = None
content_text = u'\n'.join(self.content)
cache_file_name = '%s-%s.html' % (lexer_name, md5.new(content_text).hexdigest())
cached_path = os.path.join(PYGMENTS_CACHE_DIR, cache_file_name)
# Look for cached version, otherwise parse
if os.path.exists(cached_path):
cache_file = open(cached_path, 'r')
parsed = cache_file.read()
else:
parsed = highlight(content_text, lexer, formatter)
# Strip pre tag and everything outside it
pres = re.compile("<pre>(.+)<\/pre>", re.S)
stripped = pres.search(parsed).group(1)
# Create tabular code with line numbers
table = '<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers">'
lined = ''
for idx, line in enumerate(stripped.splitlines(True)):
table += '<span class="line-number">%d</span>\n' % (idx + 1)
lined += '<span class="line">%s</span>' % line
table += '</pre></td><td class="code"><pre><code class="%s">%s</code></pre></td></tr></table></div>' % (lexer_name, lined)
# Add wrapper with optional caption and link
code = '<figure class="code">'
if self.options:
caption = ('<span>%s</span>' % self.options['caption']) if 'caption' in self.options else ''
title = self.options['title'] if 'title' in self.options else 'link'
link = ('<a href="%s">%s</a>' % (self.options['url'], title)) if 'url' in self.options else ''
if caption or link:
code += '<figcaption>%s %s</figcaption>' % (caption, link)
code += '%s</figure>' % table
# Write cache
if cache_file is None:
cache_file = open(cached_path, 'w')
cache_file.write(parsed)
cache_file.close()
return [nodes.raw('', code, format='html')]
directives.register_directive('code-block', Pygments)
directives.register_directive('sourcecode', Pygments)

@ -0,0 +1,39 @@
#!/usr/bin/env python
# :Author: David Goodger, the Pygments team, Guenter Milde
# :Date: $Date: $
# :Copyright: This module has been placed in the public domain.
# This is a merge of the `Docutils`_ `rst2html` front end with an extension
# suggestion taken from the `Pygments`_ documentation, reworked specifically
# for `Octopress`_.
#
# .. _Pygments: http://pygments.org/
# .. _Docutils: http://docutils.sourceforge.net/
# .. _Octopress: http://octopress.org/
"""
A front end to docutils, producing HTML with syntax colouring using pygments
"""
try:
import locale
locale.setlocale(locale.LC_ALL, '')
except:
pass
from transform import transform
from docutils.writers.html4css1 import Writer
from docutils.core import default_description
from directives import Pygments
description = ('Generates (X)HTML documents from standalone reStructuredText '
'sources. Uses `pygments` to colorize the content of'
'"code-block" directives. Needs an adapted stylesheet'
+ default_description)
def main():
return transform(writer=Writer(), part='html_body')
if __name__ == '__main__':
print(main())

@ -0,0 +1,42 @@
import sys
from docutils.core import publish_parts
from optparse import OptionParser
from docutils.frontend import OptionParser as DocutilsOptionParser
from docutils.parsers.rst import Parser
def transform(writer=None, part=None):
p = OptionParser(add_help_option=False)
# Collect all the command line options
docutils_parser = DocutilsOptionParser(components=(writer, Parser()))
for group in docutils_parser.option_groups:
p.add_option_group(group.title, None).add_options(group.option_list)
p.add_option('--part', default=part)
opts, args = p.parse_args()
settings = dict({
'file_insertion_enabled': False,
'raw_enabled': False,
'doctitle_xform': False,
})
if len(args) == 1:
try:
content = open(args[0], 'r').read()
except IOError:
content = args[0]
else:
content = sys.stdin.read()
parts = publish_parts(
source=content,
writer=writer,
settings_overrides=settings
)
if 'html_body' in parts:
return parts['html_body']
if opts.part in parts:
return parts[opts.part]
return ''

@ -0,0 +1,51 @@
module Jekyll
class ProjectVersionTag < Liquid::Tag
NO_GIT_MESSAGE = 'Oops, are you sure this is a git project?'
UNABLE_TO_PARSE_MESSAGE = 'Sorry, could not read project version at the moment'
def render(context)
if git_repo?
current_version.chomp
else
NO_GIT_MESSAGE
end
end
private
def current_version
@_current_version ||= begin
# attempt to find the latest tag, falling back to last commit
version = git_describe || parse_head
version || UNABLE_TO_PARSE_MESSAGE
end
end
def git_describe
tagged_version = %x{ git describe --tags --always }
if command_succeeded?
tagged_version
end
end
def parse_head
head_commitish = %x{ git rev-parse --short HEAD }
if command_succeeded?
head_commitish
end
end
def command_succeeded?
!$?.nil? && $?.success?
end
def git_repo?
system('git rev-parse')
end
end
end
Liquid::Template.register_tag('project_version', Jekyll::ProjectVersionTag)

@ -0,0 +1,652 @@
---
layout: post
title: Client-Server API
categories: guides
---
|
.. figure:: http://matrix.org/matrix.png
:width: 305px
:height: 130px
:alt: [matrix]
:align: center
How to use the client-server API
================================
.. NOTE::
The git version of this document is {% project_version %}
This guide focuses on how the client-server APIs *provided by the reference
home server* can be used. Since this is specific to a home server
implementation, there may be variations in relation to registering/logging in
which are not covered in extensive detail in this guide.
If you haven't already, get a home server up and running on
``http://localhost:8008``.
Accounts
========
Before you can send and receive messages, you must **register** for an account.
If you already have an account, you must **login** into it.
.. NOTE::
`Try out the fiddle`__
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/register_login
Registration
------------
The aim of registration is to get a user ID and access token which you will need
when accessing other APIs::
curl -XPOST -d '{"user":"example", "password":"wordpass", "type":"m.login.password"}' "http://localhost:8008/_matrix/client/api/v1/register"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
"home_server": "localhost",
"user_id": "@example:localhost"
}
NB: If a ``user`` is not specified, one will be randomly generated for you.
If you do not specify a ``password``, you will be unable to login to the account
if you forget the ``access_token``.
Implementation note: The matrix specification does not enforce how users
register with a server. It just specifies the URL path and absolute minimum
keys. The reference home server uses a username/password to authenticate user,
but other home servers may use different methods. This is why you need to
specify the ``type`` of method.
Login
-----
The aim when logging in is to get an access token for your existing user ID::
curl -XGET "http://localhost:8008/_matrix/client/api/v1/login"
{
"flows": [
{
"type": "m.login.password"
}
]
}
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
"home_server": "localhost",
"user_id": "@example:localhost"
}
Implementation note: Different home servers may implement different methods for
logging in to an existing account. In order to check that you know how to login
to this home server, you must perform a ``GET`` first and make sure you
recognise the login type. If you do not know how to login, you can
``GET /login/fallback`` which will return a basic webpage which you can use to
login. The reference home server implementation support username/password login,
but other home servers may support different login methods (e.g. OAuth2).
Communicating
=============
In order to communicate with another user, you must **create a room** with that
user and **send a message** to that room.
.. NOTE::
`Try out the fiddle`__
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/create_room_send_msg
Creating a room
---------------
If you want to send a message to someone, you have to be in a room with them. To
create a room::
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=YOUR_ACCESS_TOKEN"
{
"room_alias": "#tutorial:localhost",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
The "room alias" is a human-readable string which can be shared with other users
so they can join a room, rather than the room ID which is a randomly generated
string. You can have multiple room aliases per room.
.. TODO(kegan)
How to add/remove aliases from an existing room.
Sending messages
----------------
You can now send messages to this room::
curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN"
{
"event_id": "YUwRidLecu"
}
The event ID returned is a unique ID which identifies this message.
NB: There are no limitations to the types of messages which can be exchanged.
The only requirement is that ``"msgtype"`` is specified. The Matrix
specification outlines the following standard types: ``m.text``, ``m.image``,
``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for
more information on these types.
Users and rooms
===============
Each room can be configured to allow or disallow certain rules. In particular,
these rules may specify if you require an **invitation** from someone already in
the room in order to **join the room**. In addition, you may also be able to
join a room **via a room alias** if one was set up.
.. NOTE::
`Try out the fiddle`__
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/room_memberships
Inviting a user to a room
-------------------------
You can directly invite a user to a room like so::
curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/invite?access_token=YOUR_ACCESS_TOKEN"
This informs ``@myfriend:localhost`` of the room ID
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
Joining a room via an invite
----------------------------
If you receive an invite, you can join the room::
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/join?access_token=YOUR_ACCESS_TOKEN"
NB: Only the person invited (``@myfriend:localhost``) can change the membership
state to ``"join"``. Repeatedly joining a room does nothing.
Joining a room via an alias
---------------------------
Alternatively, if you know the room alias for this room and the room config
allows it, you can directly join a room via the alias::
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=YOUR_ACCESS_TOKEN"
{
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
You will need to use the room ID when sending messages, not the room alias.
NB: If the room is configured to be an invite-only room, you will still require
an invite in order to join the room even though you know the room alias. As a
result, it is more common to see a room alias in relation to a public room,
which do not require invitations.
Getting events
==============
An event is some interesting piece of data that a client may be interested in.
It can be a message in a room, a room invite, etc. There are many different ways
of getting events, depending on what the client already knows.
.. NOTE::
`Try out the fiddle`__
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/event_stream
Getting all state
-----------------
If the client doesn't know any information on the rooms the user is
invited/joined on, they can get all the user's state for all rooms::
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=YOUR_ACCESS_TOKEN"
{
"end": "s39_18_0",
"presence": [
{
"content": {
"last_active_ago": 1061436,
"user_id": "@example:localhost"
},
"type": "m.presence"
}
],
"rooms": [
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"@example:localhost": 10,
"default": 0
},
"event_id": "wAumPSTsWF",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.power_levels",
"user_id": "@example:localhost"
},
{
"content": {
"join_rule": "public"
},
"event_id": "jrLVqKHKiI",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.join_rules",
"user_id": "@example:localhost"
},
{
"content": {
"level": 10
},
"event_id": "WpmTgsNWUZ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.add_state_level",
"user_id": "@example:localhost"
},
{
"content": {
"level": 0
},
"event_id": "qUMBJyKsTQ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.send_event_level",
"user_id": "@example:localhost"
},
{
"content": {
"ban_level": 5,
"kick_level": 5
},
"event_id": "YAaDmKvoUW",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.ops_levels",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "RJbPMtCutf",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409665586730,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"body": "hello",
"hsob_ts": 1409665660439,
"msgtype": "m.text"
},
"event_id": "YUwRidLecu",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"ts": 1409665660439,
"type": "m.room.message",
"user_id": "@example:localhost"
},
{
"content": {
"membership": "invite"
},
"event_id": "YjNuBKnPsb",
"membership": "invite",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@myfriend:localhost",
"ts": 1409666426819,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join",
"prev": "join"
},
"event_id": "KWwdDjNZnm",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666551582,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
],
"end": "s39_18_0",
"start": "t1-11_18_0"
},
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state": [
{
"content": {
"creator": "@example:localhost"
},
"event_id": "dMUoqVTZca",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.create",
"user_id": "@example:localhost"
},
{
"content": {
"@example:localhost": 10,
"default": 0
},
"event_id": "wAumPSTsWF",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.power_levels",
"user_id": "@example:localhost"
},
{
"content": {
"join_rule": "public"
},
"event_id": "jrLVqKHKiI",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.join_rules",
"user_id": "@example:localhost"
},
{
"content": {
"level": 10
},
"event_id": "WpmTgsNWUZ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.add_state_level",
"user_id": "@example:localhost"
},
{
"content": {
"level": 0
},
"event_id": "qUMBJyKsTQ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.send_event_level",
"user_id": "@example:localhost"
},
{
"content": {
"ban_level": 5,
"kick_level": 5
},
"event_id": "YAaDmKvoUW",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.ops_levels",
"user_id": "@example:localhost"
},
{
"content": {
"membership": "invite"
},
"event_id": "YjNuBKnPsb",
"membership": "invite",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@myfriend:localhost",
"ts": 1409666426819,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
]
}
]
}
This returns all the room information the user is invited/joined on, as well as
all of the presences relevant for these rooms. This can be a LOT of data. You
may just want the most recent event for each room. This can be achieved by
applying query parameters to ``limit`` this request::
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN"
{
"end": "s39_18_0",
"presence": [
{
"content": {
"last_active_ago": 1279484,
"user_id": "@example:localhost"
},
"type": "m.presence"
}
],
"rooms": [
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
],
"end": "s39_18_0",
"start": "t10-30_18_0"
},
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state": [
{
"content": {
"creator": "@example:localhost"
},
"event_id": "dMUoqVTZca",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.create",
"user_id": "@example:localhost"
},
{
"content": {
"@example:localhost": 10,
"default": 0
},
"event_id": "wAumPSTsWF",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.power_levels",
"user_id": "@example:localhost"
},
{
"content": {
"join_rule": "public"
},
"event_id": "jrLVqKHKiI",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.join_rules",
"user_id": "@example:localhost"
},
{
"content": {
"level": 10
},
"event_id": "WpmTgsNWUZ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.add_state_level",
"user_id": "@example:localhost"
},
{
"content": {
"level": 0
},
"event_id": "qUMBJyKsTQ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.send_event_level",
"user_id": "@example:localhost"
},
{
"content": {
"ban_level": 5,
"kick_level": 5
},
"event_id": "YAaDmKvoUW",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.ops_levels",
"user_id": "@example:localhost"
},
{
"content": {
"membership": "invite"
},
"event_id": "YjNuBKnPsb",
"membership": "invite",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@myfriend:localhost",
"ts": 1409666426819,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
]
}
]
}
Getting live state
------------------
Once you know which rooms the client has previously interacted with, you need to
listen for incoming events. This can be done like so::
curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN"
{
"chunk": [],
"end": "s39_18_0",
"start": "s39_18_0"
}
This will block waiting for an incoming event, timing out after several seconds.
Even if there are no new events (as in the example above), there will be some
pagination stream response keys. The client should make subsequent requests
using the value of the ``"end"`` key (in this case ``s39_18_0``) as the ``from``
query parameter e.g. ``http://localhost:8008/_matrix/client/api/v1/events?access
_token=YOUR_ACCESS_TOKEN&from=s39_18_0``. This value should be stored so when the
client reopens your app after a period of inactivity, you can resume from where
you got up to in the event stream. If it has been a long period of inactivity,
there may be LOTS of events waiting for the user. In this case, you may wish to
get all state instead and then resume getting live state from a newer end token.
NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
in milliseconds. A timeout of 0 will not block.
Example application
-------------------
The following example demonstrates registration and login, live event streaming,
creating and joining rooms, sending messages, getting member lists and getting
historical messages for a room. This covers most functionality of a messaging
application.
.. NOTE::
`Try out the fiddle`__
.. __: http://jsfiddle.net/gh/get/jquery/1.8.3/matrix-org/matrix-doc/tree/master/supporting-docs/howtos/jsfiddles/example_app

@ -0,0 +1,120 @@
---
layout: post
title: Getting involved
categories: guides
---
# How can I get involved?
Matrix is an ecosystem consisting of several apps written by lots of people. We at Matrix.org have written one server and a few clients, and people in the community have also written several clients, servers, and Application Services. We are collecting [a list of all known Matrix-apps](https://matrix.org/blog/try-matrix-now/).
|
You have a few options when it comes to getting involved: if you just want to use Matrix, you can [register an account on a public server using a public webclient](#reg). If you have a virtual private server (VPS) or similar, you might want to [run a server and/or client yourself](#run). If you want to look under the hood, you can [checkout the code and modify it - or write your own client or server](#checkout). Or you can write an [Application Service](#as), for example a bridge to an existing ecosystem.
|
<a class="anchor" id="reg"></a>
## Access Matrix via a public webclient/server
The easiest thing to do if you want to just have a play, is to use our reference webclient and create a user on the matrix.org homeserver. You do that by visiting http://matrix.org/beta/, selecting "Create account" and choosing your userID and password on the next page. You can also add your email, but this is optional (adding it will make it easier for your friends to find your Matrix user in the future).
|
At the bottom of the account creation page, you can pick the homeserver and identity server you want to use. In this case, we are using matrix.org's homeserver, so just leave it as-is. Your full Matrix-userID will be formed partly from the hostname your server is running as, and partly from an userID you specify when you create the account. For example, if you put bob as your userID, your full Matrix-userID will be @bob:matrix.org ("bob on matrix.org").
|
You can use multiple clients with the same user, so you might want to also look at our [Android](https://github.com/matrix-org/matrix-android-console) or [iOS](https://github.com/matrix-org/matrix-ios-console) clients for your mobile phone!
|
<a class="anchor" id="run"></a>
## Run a server and/or client yourself
### Running your own client:
You can run your own Matrix client; there are [numerous clients available](https://matrix.org/blog/try-matrix-now/). You can take Matrix.org's [reference client](https://github.com/matrix-org/matrix-angular-sdk) and use it as-is - or modify it any way you want! Since it's written in JavaScript, running a client is [really easy](https://github.com/matrix-org/matrix-angular-sdk#running)!
|
### Running your own homeserver:
One of the core features of Matrix is that anyone can run a homeserver and join the federated network on equal terms (there is no hierarchy). If you want to set up your own homeserver, please see the relevant docs of the server you want to run. If you want to run Matrix.org's reference homeserver, please consult the [readme of the Synapse project](https://github.com/matrix-org/synapse/blob/master/README.rst).
|
Note that Synapse comes with a bundled Matrix.org webclient - but you can tell it to use your [development checkout snapshot instead](https://github.com/matrix-org/matrix-angular-sdk#matrix-angular-sdk) (or to not run a webclient at all via the "web_client: false" config option).
|
<a class="anchor" id="checkout"></a>
## Checkout our code - or write your own
As described above, you can clone our code and [run a server and/or client yourself](#run). Infact, all the code that we at Matrix.org write is available from [our github](http://github.com/matrix-org) - and other servers and clients may also be open sourced - see [our list of all known Matrix-apps](https://matrix.org/blog/try-matrix-now/).
|
You can also implement your own client or server - after all, Matrix is at its core "just" a specification of a protocol.
|
### Write your own client:
The [client-server API spec](http://matrix.org/docs/howtos/client-server.html) describes what API calls are available to clients, but a quick step-by-step guide would include:
|
1. Get a user either by registering your user in an existing client or running the [new-user script](https://github.com/matrix-org/synapse/blob/master/scripts/register_new_matrix_user) if you are running your own Synapse homeserver.
2. Assuming the homeserver you are using allows logins by password, log in via the login API:
```
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login"
```
3. If successful, this returns the following, including an `access_token`:
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
"home_server": "localhost",
"user_id": "@example:localhost"
}
4. This ``access_token`` will be used for authentication for the rest of your API calls. Potentially the next step you want is to make a call to the initialSync API and get the last x events from each room your user is in (via the limit parameter):
```
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN"
```
5. In addition to the last x events for all the rooms the user is interested in, this returns all the presences relevant for these rooms. Once you know which rooms the client has previously interacted with, you need to listen for incoming events. This can be done like so:
```
curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN"
```
6. This request will block waiting for an incoming event, timing out after several seconds if there is no event, returning something like this:
{
"chunk": [],
"end": "s39_18_0",
"start": "s39_18_0"
}
7. Even if there are no new events (as in the example above), there will be some pagination stream response keys. The client should make subsequent requests using the value of the "end" key (in this case s39_18_0) as the from query parameter e.g.
```
http://localhost:8008/_matrix/client/api/v1/events?access _token=YOUR_ACCESS_TOKEN&from=s39_18_0
```
8. This ensures that you only get new events. Now you have initial rooms and presence, and a stream of events - a good client should be able to process all these events and present them to the user. And potentially you might want to add functionality to generate events as well (such as messages from the user, for example) - again please consult the [client-server API spec](http://matrix.org/docs/howtos/client-server.html)!
### Write your own server:
We are still working on the server-server spec, so the best thing to do if you are interested in writing a server, is to come talk to us in [#matrix:matrix.org](https://matrix.org/beta/#/room/%23matrix:matrix.org).
If you are interested in how federation works, please see the [federation API chapter in the spec](http://matrix.org/docs/spec/#federation-api).
|
<a class="anchor" id="as"></a>
## Write an Application Service:
Information about Application services and how they can be used can be found in the [Application services](./application_services.html) document! (This is based on Kegan's excellent blog posts, but now lives here so it can be kept up-to-date!)

@ -0,0 +1,413 @@
---
layout: post
title: FAQ
date: 2015-08-19 16:58:07
categories: guides
---
<link href="/docs/guides/css/faq.css" type="text/css" rel="stylesheet" />
# FAQ
{:.no_toc}
Categories
----------
{:.no_toc}
[General](#general)
[Quick Start](#quick-start)
[Standard](#standard)
[APIs](#apis)
[Reference Implementations](#reference-implementations)
|
FAQ Content
-----------
{:.no_toc}
* TOC
{:toc}
### General
##### What is Matrix?
Matrix is an ambitious new open standard for open, distributed,
real-time communication over IP. It defines interoperable Instant
Messaging and VoIP, providing pragmatic HTTP APIs and open source
reference implementations for creating and running your own real-time
communication infrastructure.
##### What is Matrix's Mission?
Matrix.org's initial inspiration and goal has been to fix the problem of
fragmented IP communications. But Matrix's real potential and ultimate
mission is to be a generic messaging and data synchronisation system for
the web - allowing people, services and devices to easily communicate
with each other with full history.
##### What does Matrix provide?
Today Matrix provides a new [open standard](/docs/spec),
[APIs](/docs/api) to integrate a service to the Matrix ecosystem and
reference [open source
implementations](http://github.com/matrix-org/synapse) of the standard.
##### What does this mean for users?
The aim is to provide an analogous ecosystem to email - one where you
can communicate with pretty much anyone, without caring what app or
server they are using, using whichever app & server you chose to use,
and a nice neutral identity system like an e-mail address or phone
number to discover people to talk to.
##### What kind of company is Matrix.org?
Matrix is an open initiative which acts as a neutral custodian of the
Matrix standard. It's not actually incorporated anywhere at the moment
but we are looking at the best legal structure for the future. We are
committed to keeping the Matrix project open.
##### Who is funding Matrix.org?
We have been given permission by our employers, Amdocs, to work on
Matrix as an independent non-profit initiative.
##### Who is building Matrix?
We're a team of ~10 people with decades of experience building custom
VoIP and Messaging apps for mobile network operators. Most of us have
day jobs at Amdocs or OpenMarket, but we are supported by a mix of
freelancers and volunteers.
##### Why are you called Matrix?
We are called Matrix because we provide a structure in which all
communication can be matrixed together.
##### Why have you released this as open source?
We believe that any open standard defining interoperable communication
needs to be justified, demonstrated and validated with transparent open
source implementations. For Matrix to achieve its mission of making all
communications services interoperable we believe it needs to be truly
open, giving people access to take all the code we produce and to use
and build on top of it.
##### What do you mean by open?
Matrix is an open standard, meaning that we have freely published the
details for how to interface with Matrix compliant servers and clients,
and encourage anyone and everyone to interface with them.  We also
ensure the standard is not encumbered by any known patent licensing
requirements.
|
Matrix is also open source, meaning that we have released the source
code of the reference servers and clients to the public domain under the
[Apache Licence v2](http://www.apache.org/licenses/LICENSE-2.0.html), to
encourage anyone and everyone to run their own servers and clients, and
enhance them and contribute their enhancements as they see fit.
##### What does federated mean?
Federation allows separate deployments of a communication service to
communicate with each other - for instance a mail server run by Google
federates with a mail server run by Microsoft when you send email from
@gmail.com to @outlook.com.
|
Federation is different to interoperability, as interoperable clients
may simply be running on the same deployment - whereas in federation the
deployments themselves are exchanging data in a compatible manner.
|
Matrix provides open federation - meaning that anyone on the internet
can join into the Matrix ecosystem by deploying their own server.
##### How is this like e-mail?
When email was first set up in the early 80s, companies like Compuserve
and AT&T and Sprint set up isolated email communities which only allowed
you to exchange mail with users on the same system.  If you got your
email from one service and your friend from another, then you couldn't
message each other.  This is basically the situation we're in today with
VoIP and IM.
##### Why has no-one done this before?
There have been several attempts before including SIP, XMPP and RCS.
 All of these have had some level of success, but
technological/usability/economic factors have ended up limiting their
success in providing true open federation.
##### What is the difference between Matrix and IRC?
We love IRC.  In fact, as of today the core Matrix team still uses it as
our primary communication tool. Between us we've written IRCds, IRC bots
and admined dreamforge, UnrealIRCd, epona, ircservices and several
others.  That said, it has some limitations that Matrix seeks to improve
on:
- Text only
- No history
- No multiple-device support
- No presence support
- Fragmented identity model
- No open federation
- No standard APIs, just an archaic TCP line protocol
- Non-standardised federation protocol
- No built-in end-to-end encryption
- Disruptive net-splits
- Non-extensible
##### What is the difference between Matrix and XMPP?
The Matrix team used XMPP (Openfire, ejabberd, spectrum, asmack,
XMPPFramework) for IM before starting to experiment with open HTTP APIs
as an alternative.   The main issues with XMPP that drove us in this
direction were:
- Not particularly web-friendly - you can't easily speak XMPP from a
web browser. (N.B. Nowadays you have options like XMPP-FTW and
Stanza.io that help loads with letting browsers talk XMPP).
- Single logical server per MUC is a single point of control and
availability. (MUCs can be distributed over multiple physical
servers, but they still sit behind a single logical JID and domain.
FMUC improves this with a similar approach to Matrix, but at time of
writing there are no open implementations.)
- History synchronisation is very much a second class citizen feature
- Stanzas aren't framed or reliably delivered without extensions. (See
[wiki.xmpp.org](http://wiki.xmpp.org/web/Myths#Myth_Four:_XMPP_is_unreliable_without_a_bunch_of_extensions.)
for an XMPP take on this)
- Multiple device support is limited. (Apparently Carbons and MAM help
with this)
- Baseline feature set is so minimal that fragmentation of features
between clients and servers is common
- No strong identity system (i.e. no standard E2E PKI, unless you
count X.509 certs, which [are
questionable](http://www.thoughtcrime.org/blog/ssl-and-the-future-of-authenticity/))
- Not particularly well designed for mobile use cases: push;
bandwidth-efficient transports. (Since the time of writing a [Push
XEP has appeared](http://xmpp.org/extensions/xep-0357.html), and
[wiki.xmpp.org](http://wiki.xmpp.org/web/Myths#Myth_Three:_It.27s_too_bandwidth-inefficient_for_mobile.)
claims that XMPP runs fine over a 9600bps + 30s latency link.)
The whole subject of XMPP vs Matrix seems to bring out the worst in
people. We think of the standards as being quite different; at its core
Matrix can be thought of as an eventually consistent global JSON db with
an HTTP API and pubsub semantics - whilst XMPP can be thought of as a
message passing protocol. You can use them both to build chat systems;
you can use them both to build pubsub systems; each comes with different
tradeoffs. Matrix has a 'kitchen sink' baseline of functionality; XMPP
has a deliberately minimal baseline set of functionality. If XMPP does
what you need it to do, then we're genuinely happy for you :) Meanwhile,
rather than competing, an XMPP Bridge like [Skaverat's xmpptrix
beta](https://github.com/SkaveRat/xmpptrix) has potential to let both
environments coexist and make the most of each other's benefits.
##### What is the difference between Matrix and PSYC?
PSYC is a open federated messaging protocol loosely inspired by IRC.  In
version 1 it was a standalone protocol, and in version 2 it is being
reutilised as the messaging layer on top of GNUnet.  We honestly don't
know that much about it, beyond trying to use psycd as an XMPP\<-\>IRC
bridge in 2010. Matrix differentiates primarily by providing simple HTTP
APIs rather than the more exotic compact line protocol in PSYC v1 or the
complicated GNUnet stack in v2.  Meanwhile, Matrix doesn't provide of
the metadata protection guarantees that GNUnet/PSYC aims for.
|
See [http://about.psyc.eu/Matrix](http://about.psyc.eu/Matrix) for
PSYC's views on Matrix.
##### What is the difference between Matrix and Tox?
Tox.im looks to be a very cool clone of Skype - a fully decentralised
peer-to-peer network.  Matrix is deliberately not peer-to-peer; instead
each user has a well-defined homeserver which stores his data and that
he can depend upon.  Matrix provides HTTP APIs; Tox.im provides C APIs.
 We haven't actually played with Tox at all yet.
##### How does Matrix compare with something like Trillian or Pidgin?
Trillian and Pidgin and similar aggregating IM clients merge all your IM
activity into a single user experience.  However, your history and
identity is still fragmented across the networks.  People can't find you
easily, and your history is fragmented (other than on the device
where the client runs).   And rather than being able to chose the right
app for the job when communicating with people, you are pushed towards
relying on a specific aggregation app.
##### What Matrix compliant apps are there?
None yet, other than our examples.  It's early days :)
##### Why do you think existing apps will ever join this?
We firmly believe it is what is right for the consumer. As people begin
to use interoperable communications tools service providers will see the
benefit and compete on quality of service, security and features rather
than relying on locking people into their walled garden. We believe as
soon as users see the availability and benefits of interoperable
services they will demand it.
##### Why aren't you doing this through the IETF? or W3C? or 3GPP?
We do recognise the advantages of working with existing standards
bodies. We have been focused on writing code and getting it out. As
Matrix matures it may well be appropriate to work with an official
standard body.
|
### Quick Start
##### How do I get an account and get started?
The quickest way is to just jump to the demo webclient at
[http://matrix.org/beta](http://matrix.org/beta) and sign up.  Please note that you can point the
webclient to access any homeserver - you don't have to use matrix.org,
although as of day 1, matrix.org is the only communal homeserver
available.
##### What can I actually do with this?
The demo webclient provides a simple chatroom interface to Matrix -
letting the user interact with users and rooms anywhere within the
Matrix federation.  Text and image messages are supported, and basic
voice-only VoIP calling via WebRTC is supported in one-to-one rooms.
##### How do I connect my homeserver to the public Matrix network?
See
[http://github.com/matrix-org/synapse](http://github.com/matrix-org/synapse)
for details
##### How do I Matrix-enable my existing app?
See the [Client-Server API
HOWTO](http://matrix.org/docs/howtos/client-server.html) for an example
of how to use Matrix's client-server API to let your app communicate
with users via Matrix.  We're currently working out the best way to
integrate your application's existing identity system with Matrix.
##### How can I write a client on Matrix?
See the [Client-Server API
HOWTO](http://matrix.org/docs/howtos/client-server.html) and the [API
docs](/docs/api) and [the Spec](/docs/spec) for all the details you need
to write a client.
##### *How can I help out with this?*
Install synapse and tell us how you get on. Critique the spec.  Write
clients.  Just come say hi on [\#matrix:matrix.org](/alpha) or the
[mailing lists](/mailman/listinfo/matrix-users)!
##### Where can I get support?
[\#matrix:matrix.org](/alpha), \#matrix on irc.freenode.net or
the [mailing lists](/mailman/listinfo/matrix-users) are your best bets.
##### How do I register custom matrix event types?
We're not yet managing a registry of custom matrix event types.  If you
have any particularly good ones you want to tell the world about, please
use the [mailing list](/mailman/listinfo/matrix-users) for now.
##### How mature is this?
We started working on Matrix in July 2014, and have opened it to the
public in September 2014.  It's early days, and under no circumstances
should you use Matrix or Synapse for anything other than experimentation
and learning at this point.  Obviously the spec and apps are maturing
rapidly, but as of the time of writing APIs are not frozen and the apps
are very much a work in progress.
|
*Sorry, the FAQ is still work in progress, the rest of it will up
soon!* *In the mean time, don't hesitate to get in touch on
[\#matrix:matrix.org](/alpha) or the [mailing
lists](/mailman/listinfo/matrix-users)!*
|
### Standard
##### What is a home server?
##### What is an identity sever?
##### Where do my conversations get stored?
##### What is a 3PID?
##### How do you do VoIP calls on Matrix?
##### Can I log into other homeservers with my username and password?
##### Why Apache Licence?
##### Can I write a Matrix homeserver?
##### How secure is this?
##### Why aren't you using an ORM layer like SqlAlchemy?  
|
### APIs
##### How do I join the global Matrix federation?
##### What ports do I have to open up to join the global Matrix federation?
|
### Reference Implementations
##### What is Matrix built on - and why?
##### How do I run my own home server?
##### Can I run my own identity server?
Yes - the reference implementation is
[sydent](https://github.com/matrix-org/sydent) and you can run your own
ID server cluster that tracks 3rd party to Matrix ID mappings. If you
want your server to participate in the global replicated Matrix ID
service then please get in touch with us. Meanwhile, we are looking at
ways of decentralising the 'official' Matrix identity service so that
identity servers are 100% decentralised and can openly federate with
each other. **N.B. that you can use Matrix without ever using the
identity service - it exists only to map 3rd party IDs (e.g. email
addresses) to matrix IDs to aid user discovery**.
##### What is Synapse?
##### Why is Synapse in Python/Twisted?
##### What are Synapse's platform requirements?
##### What are the Synapse webclient's requirements?
##### Where is the mobile app?
##### What decides the room member order on the webclient?
|
Any other question? Please contact us on
[\#matrix:matrix.org](/alpha) or the [mailing
lists](/mailman/listinfo/matrix-users)!

@ -0,0 +1,143 @@
---
layout: post
title: Application services
categories: guides
---
# Application services
Application services are distinct modules which which sit alongside a home server providing arbitrary extensible functionality decoupled from the home server implementation. Just like the rest of Matrix, they communicate via HTTP using JSON. Application services function in a very similar way to traditional clients, but they are given much more power than a normal client. They can reserve entire namespaces of room aliases and user IDs for their own purposes. They can silently monitor events in rooms, or any events directed at any user ID. This power allows application services to have extremely useful abilities which overall enhance the end user experience.
|
One of the main use cases for application services is protocol bridges. Our Matrix server on Matrix.org links in to various IRC channels and networks. This functionality was initially implemented as a simple bot which resided as a user on the Matrix rooms we wanted to link to freenode channels (#matrix, #matrix-dev, #openwebrtc and #vuc etc). There was nothing special about this bot; it is just treated as a client. However, as we started to rely on it more and more though, we realised that there were things that were impossible for simple client-side bots to do by themselves - for example, the bot could not reserve the virtual user IDs it wanted to create, and could not lazily bridge arbitrary IRC rooms on-the-fly - and this spurred the development of Application Services.
|
### Some of the features of the IRC application service we have since implemented include:
- Specific channel-to-matrix room bridging : This is what the original IRC bot did. You can specify specific channels and specific room IDs, and messages will be bridged.
- Dynamic channel-to-matrix room bridging> : This allows Matrix users to join any channel on an IRC network, rather than being forced to use one of the specific channels configured.
- Two-way PM support : IRC users can PM the virtual "M-" users and private Matrix rooms will be created. Likewise, Matrix users can invite the virtual "@irc_Nick:domain" user IDs to a room and a PM to the IRC nick will be made.
- IRC nick changing support: Matrix users are no longer forced to use "M-" nicks and can change them by sending "!nick" messages directly to the bridge.
- Ident support: This allows usernames to be authenticated for virtual IRC clients, which means IRC bans can be targeted at the Matrix user rather than the entire application service.
|
### The use of the Application Services API means:
- The bot can reserve user IDs. This prevents humans from registering for @irc_... user IDs which would then clash with the operation of the bot.
- The bot can reserve room aliases. This prevents humans from register for #irc_... aliases which would then clash with the operation of the bot.
- The bot can trivially manage hundreds of users. Events are pushed to the application service directly. If you tried to do this as a client-side bot, you would need one event stream connection per virtual user.
- The bot can lazily create rooms on demand. This means Matrix users can join non-existent room aliases and have the application service quickly track an IRC channel and create a room with that alias, allowing the join request to succeed.
|
### Implementation details:
- Written in Node.js, designed to be run using <code>forever</code>.
- Built on the generic <a href="http://github.com/matrix-org/matrix-appservice-node">matrix-appservice-node</a> framework.
- Supports sending metrics in statsd format.
- Uses matrix-appservice-node to provide a standardised interface when writing application services, rather than an explicit web framework (though under the hood matrix-appservice-node is using Express).
|
At present, the IRC application service is in beta, and is being run on #matrix and #matrix-dev. If you want to give it a go, <a title="Matrix-IRC Application Service" href="https://github.com/matrix-org/matrix-appservice-irc">check it out on Github</a>!
|
# What Application services can do for you
Application services have enormous potential for creating new and exciting ways to transform and enhance the core Matrix protocol. For example, you could aggregate information from multiple rooms into a summary room, or create throwaway virtual user accounts to proxy messages for a fixed user ID on-the-fly. As you may expect, all of this power assumes a high degree of trust between application services and home servers. Only home server admins can allow an application service to link up with their home server, and the application service is in no way federated to other home servers. You can think of application services as additional logic on the home server itself, without messing around with the book-keeping that home servers have to do. This makes adding useful functionality very easy.
|
### Example
The application service (AS) API itself uses webhooks to communicate from the home server to the AS:
- Room Alias Query API : The home server hits a URL on your application server to see if a room alias exists.
- User Query API : The home server hits a URL on your application server to see if a user ID exists.
- Push API : The home server hits a URL on your application server to notify you of new events for your users and rooms.
A very basic application service may want to log all messages in rooms which have an alias starting with "#logged_" (side note: logging won't work if these rooms are using end-to-end encryption).
Here's an example of a very basic application service using Python (with Flask and Requests) which logs room activity:
# app_service.py:
import json, requests  # we will use this later
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/transactions/&lt;transaction&gt;", methods=["PUT"])
def on_receive_events(transaction):
events = request.get_json()["events"]
for event in events:
print "User: %s Room: %s" % (event["user_id"], event["room_id"])
print "Event Type: %s" % event["type"]
print "Content: %s" % event["content"]
return jsonify({})
if __name__ == "__main__":
app.run()
Set your new application service running on port 5000 with:
python app_service.py
The home server needs to know that the application service exists before it will send requests to it. This is done via a registration YAML file which is specified in Synapse's main config file e.g. <code>homeserver.yaml</code>. The server admin needs to add the application service registration configuration file as an entry to this file.
# homeserver.yaml
app_service_config_files:
- "/path/to/appservice/registration.yaml"
NB: Note the "-" at the start; this indicates a list element. The registration file <code>registration.yaml</code> should look like:
# registration.yaml
# this is the base URL of the application service
url: "http://localhost:5000"
# This is the token that the AS should use as its access_token when using the Client-Server API
# This can be anything you want.
as_token: wfghWEGh3wgWHEf3478sHFWE
# This is the token that the HS will use when sending requests to the AS.
# This can be anything you want.
hs_token: ugw8243igya57aaABGFfgeyu
# this is the local part of the desired user ID for this AS (in this case @logging:localhost)
sender_localpart: logging
namespaces:
users: []
rooms: []
aliases:
- exclusive: false
regex: "#logged_.*"
**You will need to restart the home server after editing the config file before it will take effect.**
|
To test everything is working correctly, go ahead and explicitly create a room with the alias "#logged_test:localhost" and send a message into the room: the HS will relay the message to the AS by PUTing to /transactions/&lt;tid&gt; and you should see your AS print the event on the terminal. This will monitor any room which has an alias prefix of "#logged_", but it won't lazily create room aliases if they don't already exist. This means it will only log messages in the room you created before: #logged_test:localhost. Try joining the room "#logged_test2:localhost" without creating it, and it will fail. Let's fix that and add in lazy room creation:
@app.route("/rooms/&lt;alias&gt;")
def query_alias(alias):
alias_localpart = alias.split(":")[0][1:]
requests.post(
# NB: "TOKEN" is the as_token referred to in registration.yaml
"http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=TOKEN",
json.dumps({
"room_alias_name": alias_localpart
}),
headers={"Content-Type":"application/json"}
)
return jsonify({})
This makes the application service lazily create a room with the requested alias whenever the HS queries the AS for the existence of that alias (when users try to join that room), allowing any room with the alias prefix #logged_ to be sent to the AS. Now try joining the room "#logged_test2:localhost" and it will work as you'd expect.  You can see that if this were a real bridge, the AS would have checked for the existence of #logged_test2 in the remote network, and then lazily-created it in Matrix as required.
|
Application services are powerful components which extend the functionality of home servers, but they are limited. They can only ever function in a "passive" way. For example, you cannot implement an application service which censors swear words in rooms, because there is no way to prevent the event from being sent. Aside from the fact that censoring will not work when using end-to-end encryption, all federated home servers would also need to reject the event in order to stop developing an inconsistent event graph. To "actively" monitor events, another component called a "Policy Server" is required, which is beyond the scope of this post.  Also, Application Services can result in a performance bottleneck, as all events on the homeserver must be ordered and sent to the registered application services.  If you are bridging huge amounts of traffic, you may be better off having your bridge directly talk the Server-Server federation API rather than the simpler Application Service API.
I hope this demonstrates how easy it is to create an application service, along with a few ideas of the kinds of things you can do with them. Obvious uses include build protocol bridges, search engines, invisible bots, etc. For more information on the AS HTTP API, check out the new <a href="http://matrix.org/docs/spec/#application-service-api">Application Service API</a> section in the spec, or the raw drafts and spec in <a href="https://github.com/matrix-org/matrix-doc/" target="_blank">https://github.com/matrix-org/matrix-doc/</a>.

@ -0,0 +1,18 @@
---
layout: default
---
{% include basic.css %}
{% include nature.css %}
<div class="post">
<header class="post-header">
<div class="post-title">{{ page.title }}</div>
<p class="post-meta">{{ page.date | date: "%b %-d, %Y" }}{% if page.author %} • {{ page.author }}{% endif %}{% if page.meta %} • {{ page.meta }}{% endif %}</p>
</header>
<article class="post-content">
{{ content }}
</article>
</div>

@ -0,0 +1,204 @@
/**
* Reset some basic elements
*/
body, h1, h2, h3, h4, h5, h6,
p, blockquote, pre, hr,
dl, dd, ol, ul, figure {
margin: 0;
padding: 0;
}
/**
* Basic styling
*/
body {
font-family: $base-font-family;
font-size: $base-font-size;
line-height: $base-line-height;
font-weight: 300;
color: $text-color;
background-color: $background-color;
-webkit-text-size-adjust: 100%;
}
/**
* Set `margin-bottom` to maintain vertical rhythm
*/
h1, h2, h3, h4, h5, h6,
p, blockquote, pre,
ul, ol, dl, figure,
%vertical-rhythm {
margin-bottom: $spacing-unit / 2;
}
/**
* Images
*/
img {
max-width: 100%;
vertical-align: middle;
}
/**
* Figures
*/
figure > img {
display: block;
}
figcaption {
font-size: $small-font-size;
}
/**
* Lists
*/
ul, ol {
margin-left: $spacing-unit;
}
li {
> ul,
> ol {
margin-bottom: 0;
}
}
/**
* Headings
*/
h1, h2, h3, h4, h5, h6 {
font-weight: 300;
}
/**
* Links
*/
a {
color: $brand-color;
text-decoration: none;
&:visited {
color: darken($brand-color, 15%);
}
&:hover {
color: $text-color;
text-decoration: underline;
}
}
/**
* Blockquotes
*/
blockquote {
color: $grey-color;
border-left: 4px solid $grey-color-light;
padding-left: $spacing-unit / 2;
font-size: 18px;
letter-spacing: -1px;
font-style: italic;
> :last-child {
margin-bottom: 0;
}
}
/**
* Code formatting
*/
pre,
code {
font-size: 15px;
border: 1px solid $grey-color-light;
border-radius: 3px;
background-color: #eef;
}
code {
padding: 1px 5px;
}
pre {
padding: 8px 12px;
overflow-x: scroll;
> code {
border: 0;
padding-right: 0;
padding-left: 0;
}
}
/**
* Wrapper
*/
.wrapper {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
margin-right: auto;
margin-left: auto;
padding-right: $spacing-unit;
padding-left: $spacing-unit;
@extend %clearfix;
@include media-query($on-laptop) {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
max-width: calc(#{$content-width} - (#{$spacing-unit}));
padding-right: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
}
/**
* Clearfix
*/
%clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
/**
* Icons
*/
.icon {
> svg {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle;
path {
fill: $grey-color;
}
}
}

@ -0,0 +1,236 @@
/**
* Site header
*/
.site-header {
border-top: 5px solid $grey-color-dark;
border-bottom: 1px solid $grey-color-light;
min-height: 56px;
// Positioning context for the mobile navigation icon
position: relative;
}
.site-title {
font-size: 26px;
line-height: 56px;
letter-spacing: -1px;
margin-bottom: 0;
float: left;
&,
&:visited {
color: $grey-color-dark;
}
}
.site-nav {
float: right;
line-height: 56px;
.menu-icon {
display: none;
}
.page-link {
color: $text-color;
line-height: $base-line-height;
// Gaps between nav items, but not on the first one
&:not(:first-child) {
margin-left: 20px;
}
}
@include media-query($on-palm) {
position: absolute;
top: 9px;
right: 30px;
background-color: $background-color;
border: 1px solid $grey-color-light;
border-radius: 5px;
text-align: right;
.menu-icon {
display: block;
float: right;
width: 36px;
height: 26px;
line-height: 0;
padding-top: 10px;
text-align: center;
> svg {
width: 18px;
height: 15px;
path {
fill: $grey-color-dark;
}
}
}
.trigger {
clear: both;
display: none;
}
&:hover .trigger {
display: block;
padding-bottom: 5px;
}
.page-link {
display: block;
padding: 5px 10px;
}
}
}
/**
* Site footer
*/
.site-footer {
border-top: 1px solid $grey-color-light;
padding: $spacing-unit 0;
}
.footer-heading {
font-size: 18px;
margin-bottom: $spacing-unit / 2;
}
.contact-list,
.social-media-list {
list-style: none;
margin-left: 0;
}
.footer-col-wrapper {
font-size: 15px;
color: $grey-color;
margin-left: -$spacing-unit / 2;
@extend %clearfix;
}
.footer-col {
float: left;
margin-bottom: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
.footer-col-1 {
width: -webkit-calc(35% - (#{$spacing-unit} / 2));
width: calc(35% - (#{$spacing-unit} / 2));
}
.footer-col-2 {
width: -webkit-calc(20% - (#{$spacing-unit} / 2));
width: calc(20% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(45% - (#{$spacing-unit} / 2));
width: calc(45% - (#{$spacing-unit} / 2));
}
@include media-query($on-laptop) {
.footer-col-1,
.footer-col-2 {
width: -webkit-calc(50% - (#{$spacing-unit} / 2));
width: calc(50% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
@include media-query($on-palm) {
.footer-col {
float: none;
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
/**
* Page content
*/
.page-content {
padding: $spacing-unit 0;
}
.page-heading {
font-size: 20px;
}
.post-list {
margin-left: 0;
list-style: none;
> li {
margin-bottom: $spacing-unit;
}
}
.post-meta {
font-size: $small-font-size;
color: $grey-color;
}
.post-link {
display: block;
font-size: 24px;
}
/**
* Posts
*/
.post-header {
margin-bottom: $spacing-unit;
}
.post-title {
font-size: 42px;
letter-spacing: -1px;
line-height: 1;
@include media-query($on-laptop) {
font-size: 36px;
}
}
.post-content {
margin-bottom: $spacing-unit;
h2 {
font-size: 32px;
@include media-query($on-laptop) {
font-size: 28px;
}
}
h3 {
font-size: 26px;
@include media-query($on-laptop) {
font-size: 22px;
}
}
h4 {
font-size: 20px;
@include media-query($on-laptop) {
font-size: 18px;
}
}
}

@ -0,0 +1,67 @@
/**
* Syntax highlighting styles
*/
.highlight {
background: #fff;
@extend %vertical-rhythm;
.c { color: #998; font-style: italic } // Comment
.err { color: #a61717; background-color: #e3d2d2 } // Error
.k { font-weight: bold } // Keyword
.o { font-weight: bold } // Operator
.cm { color: #998; font-style: italic } // Comment.Multiline
.cp { color: #999; font-weight: bold } // Comment.Preproc
.c1 { color: #998; font-style: italic } // Comment.Single
.cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special
.gd { color: #000; background-color: #fdd } // Generic.Deleted
.gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific
.ge { font-style: italic } // Generic.Emph
.gr { color: #a00 } // Generic.Error
.gh { color: #999 } // Generic.Heading
.gi { color: #000; background-color: #dfd } // Generic.Inserted
.gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific
.go { color: #888 } // Generic.Output
.gp { color: #555 } // Generic.Prompt
.gs { font-weight: bold } // Generic.Strong
.gu { color: #aaa } // Generic.Subheading
.gt { color: #a00 } // Generic.Traceback
.kc { font-weight: bold } // Keyword.Constant
.kd { font-weight: bold } // Keyword.Declaration
.kp { font-weight: bold } // Keyword.Pseudo
.kr { font-weight: bold } // Keyword.Reserved
.kt { color: #458; font-weight: bold } // Keyword.Type
.m { color: #099 } // Literal.Number
.s { color: #d14 } // Literal.String
.na { color: #008080 } // Name.Attribute
.nb { color: #0086B3 } // Name.Builtin
.nc { color: #458; font-weight: bold } // Name.Class
.no { color: #008080 } // Name.Constant
.ni { color: #800080 } // Name.Entity
.ne { color: #900; font-weight: bold } // Name.Exception
.nf { color: #900; font-weight: bold } // Name.Function
.nn { color: #555 } // Name.Namespace
.nt { color: #000080 } // Name.Tag
.nv { color: #008080 } // Name.Variable
.ow { font-weight: bold } // Operator.Word
.w { color: #bbb } // Text.Whitespace
.mf { color: #099 } // Literal.Number.Float
.mh { color: #099 } // Literal.Number.Hex
.mi { color: #099 } // Literal.Number.Integer
.mo { color: #099 } // Literal.Number.Oct
.sb { color: #d14 } // Literal.String.Backtick
.sc { color: #d14 } // Literal.String.Char
.sd { color: #d14 } // Literal.String.Doc
.s2 { color: #d14 } // Literal.String.Double
.se { color: #d14 } // Literal.String.Escape
.sh { color: #d14 } // Literal.String.Heredoc
.si { color: #d14 } // Literal.String.Interpol
.sx { color: #d14 } // Literal.String.Other
.sr { color: #009926 } // Literal.String.Regex
.s1 { color: #d14 } // Literal.String.Single
.ss { color: #990073 } // Literal.String.Symbol
.bp { color: #999 } // Name.Builtin.Pseudo
.vc { color: #008080 } // Name.Variable.Class
.vg { color: #008080 } // Name.Variable.Global
.vi { color: #008080 } // Name.Variable.Instance
.il { color: #099 } // Literal.Number.Integer.Long
}

@ -0,0 +1,11 @@
---
layout: page
title: About
permalink: /about/
---
This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](http://jekyllrb.com/)
You can find the source code for the Jekyll new theme at: [github.com/jglovier/jekyll-new](https://github.com/jglovier/jekyll-new)
You can find the source code for Jekyll at [github.com/jekyll/jekyll](https://github.com/jekyll/jekyll)

@ -0,0 +1,512 @@
/*
* basic.css
* ~~~~~~~~~
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- main layout ----------------------------------------------------------- */
div.clearer {
clear: both;
}
/* -- relbar ---------------------------------------------------------------- */
div.related {
width: 100%;
font-size: 90%;
}
div.related h3 {
display: none;
}
div.related ul {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
}
div.related li {
display: inline;
}
div.related li.right {
float: right;
margin-right: 5px;
}
/* -- sidebar --------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 10px 5px 0 10px;
}
div.sphinxsidebar {
float: left;
width: 230px;
margin-left: -100%;
font-size: 90%;
}
div.sphinxsidebar ul {
list-style: none;
}
div.sphinxsidebar ul ul,
div.sphinxsidebar ul.want-points {
margin-left: 20px;
list-style: square;
}
div.sphinxsidebar ul ul {
margin-top: 0;
margin-bottom: 0;
}
div.sphinxsidebar form {
margin-top: 10px;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
img {
border: 0;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
}
ul.search li a {
font-weight: bold;
}
ul.search li div.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable {
width: 100%;
}
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable dl, table.indextable dd {
margin-top: 0;
margin-bottom: 0;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
div.modindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
div.genindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
/* -- general body styles --------------------------------------------------- */
a.headerlink {
visibility: hidden;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
div.document p.caption {
text-align: inherit;
}
div.document td {
text-align: left;
}
.field-list ul {
padding-left: 1em;
}
.first {
margin-top: 0 !important;
}
p.rubric {
margin-top: 30px;
font-weight: bold;
}
.align-left {
text-align: left;
}
.align-center {
clear: both;
text-align: center;
}
.align-right {
text-align: right;
}
/* -- sidebars -------------------------------------------------------------- */
div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
padding: 7px 7px 0 7px;
background-color: #ffe;
width: 40%;
float: right;
}
p.sidebar-title {
font-weight: bold;
}
/* -- topics ---------------------------------------------------------------- */
div.topic {
border: 1px solid #ccc;
padding: 7px 7px 0 7px;
margin: 10px 0 10px 0;
}
p.topic-title {
font-size: 1.1em;
font-weight: bold;
margin-top: 10px;
}
/* -- admonitions ----------------------------------------------------------- */
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 7px;
}
div.admonition dt {
font-weight: bold;
}
div.admonition dl {
margin-bottom: 0;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
div.document p.centered {
text-align: center;
margin-top: 25px;
}
/* -- tables ---------------------------------------------------------------- */
table.docutils {
border: 0;
border-collapse: collapse;
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 1px solid #aaa;
}
table.field-list td, table.field-list th {
border: 0 !important;
}
table.footnote td, table.footnote th {
border: 0 !important;
}
th {
text-align: left;
padding-right: 5px;
}
table.citation {
border-left: solid 1px gray;
margin-left: 1px;
}
table.citation td {
border-bottom: none;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
list-style: decimal;
}
ol.loweralpha {
list-style: lower-alpha;
}
ol.upperalpha {
list-style: upper-alpha;
}
ol.lowerroman {
list-style: lower-roman;
}
ol.upperroman {
list-style: upper-roman;
}
dl {
margin-bottom: 15px;
}
dd p {
margin-top: 0px;
}
dd ul, dd table {
margin-bottom: 10px;
}
dd {
margin-top: 3px;
margin-bottom: 10px;
margin-left: 30px;
}
dt:target, .highlighted {
background-color: #fbe54e;
}
dl.glossary dt {
font-weight: bold;
font-size: 1.1em;
}
.field-list ul {
margin: 0;
padding-left: 1em;
}
.field-list p {
margin: 0;
}
.refcount {
color: #060;
}
.optional {
font-size: 1.3em;
}
.versionmodified {
font-style: italic;
}
.system-message {
background-color: #fda;
padding: 5px;
border: 3px solid red;
}
.footnote:target {
background-color: #ffa
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
.classifier {
font-style: oblique;
}
/* -- code displays --------------------------------------------------------- */
pre {
overflow: auto;
font-size: 1.1em;
}
td.linenos pre {
padding: 5px 0px;
border: 0;
background-color: transparent;
color: #aaa;
}
table.highlighttable {
margin-left: 0.5em;
}
table.highlighttable td {
padding: 0 0.5em 0 0.5em;
}
tt.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
tt.descclassname {
background-color: transparent;
}
tt.xref, a tt {
background-color: transparent;
font-weight: bold;
}
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
background-color: transparent;
}
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family: sans-serif;
}
div.viewcode-block:target {
margin: -1px -10px;
padding: 0 10px;
}
/* -- math display ---------------------------------------------------------- */
img.math {
vertical-align: middle;
}
div.document div.math p {
text-align: center;
}
span.eqno {
float: right;
}
/* -- printout stylesheet --------------------------------------------------- */
@media print {
div.document,
div.documentwrapper,
div.bodywrapper {
margin: 0 !important;
width: 100%;
}
div.sphinxsidebar,
div.related,
div.footer,
#top-link {
display: none;
}
}

@ -0,0 +1,40 @@
.toc {
background: #f9f9f9;
border: 1px solid #aaaaaa;
display: table;
padding-top: 16px;
padding-right: 16px;
padding-bottom: 16px;
list-style: none;
color: #2EA3F2;
}
ul {
padding-left: 30px;
}
p {
padding-left: 30px;
color: #666;
}
h3 {
padding-top: 48px;
color: #333;
}
h4, h5 {
border-top: 40px solid transparent;
margin-top: -40px;
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
font-style: italic;
font-size: 16px;
padding-top: 16px;
color: #333;
}

@ -0,0 +1,52 @@
---
# Only the main Sass file needs front matter (the dashes are enough)
---
@charset "utf-8";
// Our variables
$base-font-family: Helvetica, Arial, sans-serif;
$base-font-size: 16px;
$small-font-size: $base-font-size * 0.875;
$base-line-height: 1.5;
$spacing-unit: 30px;
$text-color: #111;
$background-color: #fdfdfd;
$brand-color: #2a7ae2;
$grey-color: #828282;
$grey-color-light: lighten($grey-color, 40%);
$grey-color-dark: darken($grey-color, 25%);
// Width of the content area
$content-width: 1340px;
$on-palm: 600px;
$on-laptop: 800px;
// Using media queries with like this:
// @include media-query($on-palm) {
// .wrapper {
// padding-right: $spacing-unit / 2;
// padding-left: $spacing-unit / 2;
// }
// }
@mixin media-query($device) {
@media screen and (max-width: $device) {
@content;
}
}
// Import partials from `sass_dir` (defaults to `_sass`)
@import
"base",
"layout",
"syntax-highlighting"
;

@ -0,0 +1,285 @@
/*
* nature.css_t
* ~~~~~~~~~~~~
*
* Sphinx stylesheet -- nature theme.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- page layout ----------------------------------------------------------- */
body {
font-family: Arial, sans-serif;
font-size: 100%;
/*background-color: #111;*/
color: #555;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
hr {
border: 1px solid #B1B4B6;
}
/*
div.document {
background-color: #eee;
}
*/
div.document {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
font-size: 0.9em;
}
div.footer {
color: #555;
width: 100%;
padding: 13px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #444;
text-decoration: underline;
}
div.related {
background-color: #6BA81E;
line-height: 32px;
color: #fff;
text-shadow: 0px 1px 0 #444;
font-size: 0.9em;
}
div.related a {
color: #E2F3CC;
}
div.sphinxsidebar {
font-size: 0.75em;
line-height: 1.5em;
}
div.sphinxsidebarwrapper{
padding: 20px 0;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: Arial, sans-serif;
color: #222;
font-size: 1.2em;
font-weight: normal;
margin: 0;
padding: 5px 10px;
background-color: #ddd;
text-shadow: 1px 1px 0 white
}
div.sphinxsidebar h4{
font-size: 1.1em;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p {
color: #888;
padding: 5px 20px;
}
div.sphinxsidebar p.topless {
}
div.sphinxsidebar ul {
margin: 10px 20px;
padding: 0;
color: #000;
}
div.sphinxsidebar a {
color: #444;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar input[type=text]{
margin-left: 20px;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #005B81;
text-decoration: none;
}
a:hover {
color: #E32E00;
text-decoration: underline;
}
div.document h1,
div.document h2,
div.document h3,
div.document h4,
div.document h5,
div.document h6 {
font-family: Arial, sans-serif;
background-color: #BED4EB;
font-weight: normal;
color: #212224;
margin: 30px 0px 10px 0px;
padding: 5px 0 5px 10px;
text-shadow: 0px 1px 0 white
}
div.document h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
div.document h2 { font-size: 150%; background-color: #C8D5E3; }
div.document h3 { font-size: 120%; background-color: #D8DEE3; }
div.document h4 { font-size: 110%; background-color: #D8DEE3; }
div.document h5 { font-size: 100%; background-color: #D8DEE3; }
div.document h6 { font-size: 100%; background-color: #D8DEE3; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.document p, div.document dd, div.document li {
line-height: 1.5em;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.highlight{
background-color: white;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 10px;
background-color: White;
color: #222;
line-height: 1.2em;
border: 1px solid #C6C9CB;
font-size: 1.1em;
margin: 1.5em 0 1.5em 0;
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
-moz-box-shadow: 1px 1px 1px #d8d8d8;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
font-size: 1.1em;
font-family: monospace;
}
.viewcode-back {
font-family: Arial, sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}
p {
margin: 0;
}
ul li dd {
margin-top: 0;
}
ul li dl {
margin-bottom: 0;
}
li dl dd {
margin-bottom: 0;
}
dd ul {
padding-left: 0;
}
li dd ul {
margin-bottom: 0;
}
table {
margin-top: 10px;
margin-bottom: 10px;
border: 0;
border-collapse: collapse;
}
td[colspan]:not([colspan="1"]) {
background: #eeeeee;
}
thead {
background: #eeeeee;
}

@ -0,0 +1,57 @@
p {
line-height: 1.5;
color: #666;
}
a.anchor {
display: block;
position: relative;
top: -50px;
visibility: hidden;
}
a:link {
color: #2EA3F2;
text-decoration: none;
}
a:visited {
color: #2EA3F2;
text-decoration: none;
}
a:active {
color: #2EA3F2;
text-decoration: none;
}
a:hover {
color: #2EA3F2;
text-decoration: underline;
}
h1,h2,h3,h4,h5 {
color: #333;
}
.page-content {
min-height: 100%;
height: auto !important; /* This line and the next line are not necessary unless you need IE6 support */
height: 100%;
margin: 0 auto -33px; /* the bottom margin is the negative value of the footer's height */
padding-bottom: 0;
padding-top: 0;
}
.push {
height: 33px;
}
#page {
margin-bottom: -33px;
}
#document {
margin-top: 10px; /* We want a little whitespace before the page content starts */
}

@ -0,0 +1,30 @@
---
layout: null
---
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>{{ site.title | xml_escape }}</title>
<description>{{ site.description | xml_escape }}</description>
<link>{{ site.url }}{{ site.baseurl }}/</link>
<atom:link href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" rel="self" type="application/rss+xml"/>
<pubDate>{{ site.time | date_to_rfc822 }}</pubDate>
<lastBuildDate>{{ site.time | date_to_rfc822 }}</lastBuildDate>
<generator>Jekyll v{{ jekyll.version }}</generator>
{% for post in site.posts limit:10 %}
<item>
<title>{{ post.title | xml_escape }}</title>
<description>{{ post.content | xml_escape }}</description>
<pubDate>{{ post.date | date_to_rfc822 }}</pubDate>
<link>{{ post.url | prepend: site.baseurl | prepend: site.url }}</link>
<guid isPermaLink="true">{{ post.url | prepend: site.baseurl | prepend: site.url }}</guid>
{% for tag in post.tags %}
<category>{{ tag | xml_escape }}</category>
{% endfor %}
{% for cat in post.categories %}
<category>{{ cat | xml_escape }}</category>
{% endfor %}
</item>
{% endfor %}
</channel>
</rss>

@ -0,0 +1,14 @@
---
layout: default
---
<div class="home">
<h1>Guides</h1>
<p>Here is a collection of guides that might help you get involved with Matrix.</p>
<p>First, there is the <a href="./getting_involved.html" title="Getting Involved">Getting Involved</a> guide, which explains various ways of getting started with Matrix.</p>
<p>The <a href="./client-server.html" title="Client-Server API">Client-Server API</a> guide explains in detail how to use the CS API, which is useful if you want to write a client (or modify an existing one) - or if you're just interested in how it works "under the hood".</p>
<p>The <a href="./application_services.html" title="Application services">Application services</a> guide introduces and explains Application services, and what they can be used for.
<p>Finally there is the <a href="./faq.html" title="FAQ">FAQ</a>, which tries to answer all your questions relating to Matrix!</p>
</div>

@ -78,8 +78,13 @@ def main(input_module, file_stream=None, out_dir=None, verbose=False):
def wrap(input, wrap=80, initial_indent=""):
if len(input) == 0:
return initial_indent
# TextWrapper collapses newlines into single spaces; we do our own
# splitting on newlines to prevent this, so that newlines can actually
# be intentionally inserted in text.
input_lines = input.split('\n\n')
wrapper = TextWrapper(initial_indent=initial_indent, width=wrap)
return wrapper.fill(input)
output_lines = [wrapper.fill(line) for line in input_lines]
return '\n\n'.join(output_lines)
# make Jinja aware of the templates and filters
env = Environment(

@ -90,6 +90,12 @@ class MatrixSections(Sections):
title_kind="~"
)
def render_membership_http_api(self):
return self._render_http_api_group(
"membership",
title_kind="~"
)
def render_room_events(self):
def filterFn(eventType):
return (

@ -1,5 +1,10 @@
``{{endpoint.method}} {{endpoint.path}}``
{{(5 + (endpoint.path | length) + (endpoint.method | length)) * title_kind}}
{% if "alias_for_path" in endpoint -%}
``{{endpoint.path}}`` is an alias for `{{endpoint.alias_for_path}}`_.
.. _`{{endpoint.alias_for_path}}`: #{{endpoint.alias_link}}
{% else -%}
{{endpoint.desc | wrap(80)}}
@ -46,6 +51,20 @@ Example request::
{{endpoint.example.req | indent_block(2)}}
Example response::
{% if endpoint.example.responses|length > 0 -%}
Response{{"s" if endpoint.example.responses|length > 1 else "" }}:
{% endif -%}
{% for res in endpoint.example.responses -%}
**Status code {{res["code"]}}:**
{{endpoint.example.res | indent_block(2)}}
{{res["description"]}}
Example::
{{res["example"] | indent_block(2)}}
{% endfor %}
{% endif -%}

@ -30,7 +30,7 @@ def get_json_schema_object_fields(obj, enforce_title=False):
}
tables = [fields]
props = obj.get("properties")
props = obj.get("properties", obj.get("patternProperties"))
parents = obj.get("allOf")
if not props and not parents:
raise Exception(
@ -105,18 +105,20 @@ class MatrixUnits(Units):
for path in api["paths"]:
for method in api["paths"][path]:
single_api = api["paths"][path][method]
full_path = api.get("basePath", "") + path
endpoint = {
"title": single_api.get("summary", ""),
"desc": single_api.get("description", single_api.get("summary", "")),
"method": method.upper(),
"path": api.get("basePath", "") + path,
"path": full_path,
"requires_auth": "security" in single_api,
"rate_limited": 429 in single_api.get("responses", {}),
"req_params": [],
"res_tables": [],
"example": {
"req": "",
"res": ""
"responses": [],
"good_response": ""
}
}
self.log(".o.O.o. Endpoint: %s %s" % (method, path))
@ -177,11 +179,19 @@ class MatrixUnits(Units):
endpoint["req_param_by_loc"][p["loc"]] = []
endpoint["req_param_by_loc"][p["loc"]].append(p)
# add example response if it has one
res = single_api["responses"][200] # get the 200 OK response
endpoint["example"]["res"] = res.get("examples", {}).get(
"application/json", ""
)
good_response = None
for code, res in single_api.get("responses", {}).items():
if not good_response and code == 200:
good_response = res
description = res.get("description", "")
example = res.get("examples", {}).get("application/json", "")
if description and example:
endpoint["example"]["responses"].append({
"code": code,
"description": description,
"example": example,
})
# form example request if it has one. It "has one" if all params
# have either "x-example" or a "schema" with an "example".
params_missing_examples = [
@ -216,25 +226,39 @@ class MatrixUnits(Units):
)
# add response params if this API has any.
res_type = Units.prop(res, "schema/type")
if res_type and res_type not in ["object", "array"]:
# response is a raw string or something like that
endpoint["res_tables"].append({
"title": None,
"rows": [{
"key": res["schema"].get("name", ""),
"type": res_type,
"desc": res.get("description", "")
}]
})
elif res_type and Units.prop(res, "schema/properties"): # object
res_tables = get_json_schema_object_fields(res["schema"])
for table in res_tables:
if "no-table" not in table:
endpoint["res_tables"].append(table)
if good_response:
res_type = Units.prop(good_response, "schema/type")
if res_type and res_type not in ["object", "array"]:
# response is a raw string or something like that
endpoint["res_tables"].append({
"title": None,
"rows": [{
"key": good_response["schema"].get("name", ""),
"type": res_type,
"desc": res.get("description", "")
}]
})
elif res_type and Units.prop(good_response, "schema/properties"):
# response is an object:
schema = good_response["schema"]
res_tables = get_json_schema_object_fields(schema)
for table in res_tables:
if "no-table" not in table:
endpoint["res_tables"].append(table)
endpoints.append(endpoint)
aliases = single_api.get("x-alias", None)
if aliases:
alias_link = aliases["canonical-link"]
for alias in aliases["aliases"]:
endpoints.append({
"method": method.upper(),
"path": alias,
"alias_for_path": full_path,
"alias_link": alias_link
})
return {
"base": api.get("basePath"),
"group": group_name,

Loading…
Cancel
Save