Tutorials

9 posts tagged with "Tutorials" (See all Category)

Atom Feed

Low Bandwidth Matrix: An implementation guide

10.06.2021 17:08 — Tutorials Kegan Dougal
Last update: 10.06.2021 11:28

Disclaimer: Low bandwidth Matrix is experimental, not yet standardised, and subject to change without notice.

This guide is for Matrix developers who want to support MSC3079: Low Bandwidth CS API in their clients/servers. Please read the experimental MSC if you want to learn more about what is happening at a protocol level. If you want a high level overview of low bandwidth Matrix and why you should care, watch the 12 minute demo on Matrix Live.

Matrix currently uses HTTP APIs with JSON data to communicate from the client to the server. This is widely supported but is not very bandwidth efficient. This means that the protocol is slower, more costly and less able to be used on low bandwidth links (e.g 2G networks) which are common in certain parts of the world. MSC3079 defines a low bandwidth protocol using CoAP and CBOR instead of HTTP and JSON respectively. In the future homeservers will natively support some form of low bandwidth protocol. However, at present, no homeserver natively supports MSC3079. Therefore, this guide will set up a low bandwidth proxy server which can be put in front of any Matrix homeserver (Synapse, Dendrite, Conduit, etc) to make it MSC3079-compatible. This guide will also configure an Android device to speak MSC3079.

Low bandwidth Matrix currently does not support web browsers due to their inability to send UDP traffic. You do not need to be running a homeserver to follow this tutorial.

🔗Setting up a low bandwidth proxy for your homeserver

Prerequisites:

  • Go 1.13+
  • openssl to generate a self-signed DTLS certificate, or an existing certificate you want to use.
  • Linux or Mac user

Steps:

  • Clone the repo: git clone https://github.com/matrix-org/lb.git
  • Build the low bandwidth proxy: go build ./cmd/proxy
  • Generate a elliptic curve DTLS key/certificate: (we use curve keys as they are smaller than RSA keys, but both work.)
    openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem
    openssl req -new -x509 -key private-key.pem -out cert.pem -days 365
    # you now have cert.pem and private-key.pem
    
  • Run it pointing at matrix.org:
    ./proxy -local 'https://matrix-client.matrix.org' \
    --tls-cert cert.pem --tls-key private-key.pem \
    --advertise "http://127.0.0.1:8008" \
    --dtls-bind-addr :8008
    
  • You should see something like this:
    INFO[0000] Listening on :8008/tcp to reverse proxy from http://127.0.0.1:8008 to https://matrix-client.matrix.org - HTTPS enabled: false 
    INFO[0000] Listening for DTLS on :8008 - ACK piggyback period: 5s
    

Mac users: If you are having trouble generating EC certificates, make sure you are using OpenSSL and not LibreSSL which comes by default: openssl version. To use OpenSSL, brew install openssl which then dumps the binary to /usr/local/opt/openssl/bin/openssl.

To test it is working correctly:

# build command line tools we can use to act as a low bandwidth client
go build ./cmd/jc
go build ./cmd/coap

# do a CoAP GET request to matrix.org via the proxy
./coap -X GET -k 'http://localhost:8008/_matrix/client/versions' | ./jc -c2j '-'

{"unstable_features":{"io.element.e2ee_forced.private":false,"io.element.e2ee_forced.public":false,"io.element.e2ee_forced.trusted_private":false,"org.matrix.e2e_cross_signing":true,"org.matrix.label_based_filtering":true,"org.matrix.msc2432":true,"org.matrix.msc3026.busy_presence":false,"uk.half-shot.msc2666":true},"versions":["r0.0.1","r0.1.0","r0.2.0","r0.3.0","r0.4.0","r0.5.0","r0.6.0"]}

If this doesn't work:

  • Check the proxy logs for errors (e.g bad hostname)
  • Try adding -v to ./coap (e.g bad method or path)
  • Run the proxy with SSLKEYLOGFILE=ssl.log and inspect the decrypted traffic using Wireshark.

Otherwise, congratulations! You now have a low bandwidth proxy! You can connect to your proxy just like you would to matrix.org or any other homeserver.

🔗Security considerations

  • The proxy acts as a man in the middle and can read all non-E2EE traffic, including login credentials. DO NOT USE UNTRUSTED LOW BANDWIDTH PROXY SERVERS. Only use proxy servers run by yourself or the homeserver admins.

🔗Further reading

🔗Setting up a custom Element Android

We'll add low bandwidth matrix to Element Android and iOS by default once it's standardised - but while things are still experimental, here's a guide for how to build Element Android to do it yourself if you feel the urge. This can be used as inspiration for other Matrix clients too.

Prerequisites:

  • Android Studio

Steps:

  • Clone the repo: git clone https://github.com/vector-im/element-android.git
  • Checkout kegan/lb: git checkout kegan/lb. This branch replaces all HTTP traffic going to /_matrix/client/* with LB traffic. /_matrix/media traffic is left untouched. This branch also disables TLS checks entirely so self-signed certificates will work.
  • Clone the low bandwidth repo if you haven't already: git clone https://github.com/matrix-org/lb.git
  • In the low bandwidth repo, build the mobile bindings:
    go get golang.org/x/mobile/cmd/gomobile
    cd mobile
    # if gomobile isn't on your path, then ~/go/bin/gomobile
    gomobile bind -target=android
    
  • Copy the output files to a directory in the Element Android repo which Gradle will pick up:
    mkdir $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    cp mobile-sources.jar $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    cp mobile.aar $PATH_TO_ELEMENT_ANDROID_REPO/matrix-sdk-android/libs
    
  • Open the project in Android Studio.
  • Build and run on a device/emulator.
  • Configure the proxy's --advertise address. If you are running on a local device, restart the proxy with an --advertise of your machines LAN IP e.g 192.168.1.2 instead of 127.0.0.1. If you are running on an emulator, restart the proxy with an --advertise of the host IP: 10.0.2.2. The URL scheme should be https not http, else image loading won't work as Element Android won't download media over http.
  • Login to your matrix.org account via the proxy with the --advertise address as the HS URL e.g https://192.168.1.2:8008 or https://10.0.2.2:8008. The port is important.

To verify it is running via low bandwidth:

  • Install Wireshark.
  • Restart the proxy with the environment variable SSLKEYLOGFILE=ssl.log.
  • Run tcpdump on the right interface e.g: sudo tcpdump -i en0 -s 0 -v port 8008 -w lb.pcap
  • Force stop the android app to forcibly close any existing DTLS connections.
  • Re-open the app.
  • Open lb.pcap in Wireshark and set ssl.log as the Pre-Master Secret log filename via Preferences -> Protocols -> TLS -> Pre-Master Secret log filename.
  • Check there is DTLS/CoAP traffic.

🔗Performance

To send a single 'Hello World' message to /room/$room_id/send/m.room.message/$txn_id and receive the response, including connection setup:

ProtocolNum packetsTotal bytes
HTTP2+JSON436533
CoAP+CBOR61440

🔗Limitations

  • CoAP OBSERVE is not enabled by default. This extension allows the server to push data to the client so the client doesn't need to long-poll. It is not yet enabled because of the risk of state synchronisation issues between the proxy and the client. If the proxy gets restarted, the client will not receive sync updates until it refreshes its subscription, which happens infrequently. During this time the client is not aware that anything is wrong.
  • CoAP uses Blockwise Transfer to download large responses. Each block must be ACKed before the next block can be sent. This is less efficient than TCP which has a Receive Window which allows multiple in-flight packets at once. This means CoAP is worse at downloading large responses, requiring more round trips to completely send the data.
  • The current version of /sync sends back much more data than is strictly necessary. This means the initial sync can be slower than expected. On a low kbps link this can flood the network with so much data that the sync stream begins to fall behind. Future work will look to optimise the sync API.
  • The proxy currently doesn't implement the low bandwidth response in /versions.

Usage of matrix-nio (Python Sans IO)

03.07.2019 00:00 — Tutorials Ben Parsons

Canonical version of this article at https://matrix.org/docs/guides/usage-of-matrix-nio

This article concerns matrix-nio, and asyncio. We'll build a simple "echo bot", meaning a bot which replies to messages with the text it has just read. Note that this article does not cover E2EE with matrix-nio.

🔗Instantiation and Login

First create a new venv, and install matrix-nio via pip. On the command line, run:

python3 -m venv env
source env/bin/activate
pip install matrix-nio

Next, create a new Python file, and open it for editing. We'll import everything we require for this tutorial:

from importlib import util
import asyncio
from nio import (AsyncClient, SyncResponse, RoomMessageText)

We're importing asyncio so we can use the AsyncClient class from matrix-nio.

Create a new instance of AsyncClient by passing the homeserver and username as arguments:

async_client = AsyncClient(
    "https://matrix.org", "%%YOUR-USERNAME-HERE%%"
)

Then login, and await the response:

response = await async_client.login("%%YOUR-PASSWORD-HERE%%")
print(response)

Of course, we are using an async client, and awaiting the response. Because of this, we must call the async_client.login() from an async method, like so:

async def main():
    response = await async_client.login("%%YOUR-PASSWORD-HERE%%")
    print(response)

asyncio.run(main())

Note that for versions of Python before 3.7 the asyncio syntax must be:

async def main():
    response = await async_client.login("%%YOUR-PASSWORD-HERE%%")
    print(response)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

The remainder of this tutorial assumes you are running everything from an async method.

The response string should look like:

Logged in as @pyconweb-bot:matrix.org, device id: ZBLAJHLKVP.

🔗Get into a /sync loop

To get updates from a Matrix homeserver to the client, the client makes a request to the /sync endpoint. In the matrix-nio AsyncClient, this is wrapped by the sync() method. We can get the latest updates:

sync_response = await async_client.sync(30000)

30000 means we will wait up to 30 seconds before returning. sync_response will now contain a Python object containing a mapping of the (JSON) response from the Matrix homeserver. We'll inspect this response in the next section.

In fact, we expect there to be updates regularly, so let's create a very simple loop:

while (True):
    sync_response = await async_client.sync(30000)
    print(sync_response) # note that this could be LARGE!
    # do some reading from sync_response

In this way, every time there is a response (i.e. new events) from the homeserver, they are made available in sync_response for processing, and we loop again.

🔗Explore the sync response object

sync_response can contain multitudes, depending on the rooms this user is part of, or has been part of. sync_response.rooms.join contains updates for the rooms which the current user is "joined to" (meaning, is a member of.)

Of these joined rooms, we are (perhaps!) most interested in the events on the timeline. These are stored in timeline.events, see below:

if len(sync_response.rooms.join) > 0:

    joins = sync_response.rooms.join
    for room_id in joins:
        for event in joins[room_id].timeline.events:
            print(event)

Message events are a specific type of event which contain an Instant Messenger message. We can check the type before proceeding:

for event in joins[room_id].timeline.events:
    if isinstance(event, RoomMessageText):
        print (event.body)

In these cases, where the event is a message to a room, the body field will contain the message text.

🔗Isolate specific message event objects

Knowing that we can get the message text from an event, we can read it to determine a response. Let's make a new variable and have it store some string we'll check for:

response_string = "!replybot"

Now let's suppose we're in our /sync loop, and just received an event. We can filter messages that are meant for our bot as follows:

if len(sync_response.rooms.join) > 0:
    joins = sync_response.rooms.join
    for room_id in joins:
        for event in joins[room_id].timeline.events:
            if hasattr(event, 'body') and event.body.startswith(response_string):
                print(event)

🔗Use room_send

To send messages, matrix-nio provides a room_send() method. There are three arguments:

  • the room_id
  • the message type, we will use "m.room.message"
  • a JSON object representing the content of the message

Let's improve the example above, by sending back a message to echo the ones we isolated above:

joins = sync_response.rooms.join
for room_id in joins:
    for event in joins[room_id].timeline.events:
        if hasattr(event, 'body') and event.body.startswith(response_string):
            response_body = event.body.replace(response_string, "").strip()
            content = {
               "body": response_body,
               "msgtype": "m.text"
            }
            await async_client.room_send(room_id, 'm.room.message', content)

Now whenever the bot receives a message "!replybot some message" it will send back "some message".

🔗Use of /sync next_batch tokens

Finally, let's consider the importance of next_batch tokens. Whenever you receive a response from the /sync endpoint, the response will contain a "next_batch" field, which you then pass on the next request to ensure you have the latest messages. matrix-nio keeps track of this automatically, so it doesn't get repeated messages. However, when you stop the program and call the .sync() method again, how can you tell it where to start from? First let's get the latest next_batch token:

async def main():
    response = await async_client.login("%%YOUR-USERNAME-HERE%%", "")

    while (True):
        sync_response = await async_client.sync(30000)
        print(sync_response.next_batch) # this is the token

Then we'll write the token to a file:

async def main():
    response = await async_client.login("%%YOUR-USERNAME-HERE%%", "")

    while (True):
        sync_response = await async_client.sync(30000)

        # we write the token to a file here
        with open("next_batch","w") as next_batch_token:
            next_batch_token.write(sync_response.next_batch)

Once that token is written, we know we can re-use it for the first /sync/ request next time:

async def main():
    response = await async_client.login("%%YOUR-USERNAME-HERE%%", "")

    # we read the previously-written token...
    with open ("next_batch","r") as next_batch_token:
        # ... and well async_client to use it
        async_client.next_batch = next_batch_token.read()

    while (True):
        sync_response = await async_client.sync(30000)
        with open("next_batch","w") as next_batch_token:
            next_batch_token.write(sync_response.next_batch)

🔗Conclusion

With this, you can see that in very few lines, it's possible to write a working Matrix bot in Python, using matrix-nio.

Bridging Matrix with WhatsApp running on a VM

26.02.2019 00:00 — Tutorials Ben Parsons

This guide will live with the documentation at https://matrix.org/docs/guides/whatsapp-bridging-mautrix-whatsapp, but you can find the text below.


Matrix is:

an open standard for interoperable, decentralised, real-time communication.

In this article we'll benefit from all three of these attributes:

  • interoperable: we'll see how Matrix can be made to interact with WhatsApp
  • decentralised: you can perform this on your own server while still enjoying the benefits of being connected to the rest of the Matrix federation
  • real-time communication: we'll see how to send and receive messages in real-time

🔗Install your homeserver and install mautrix-whatsapp, the WhatsApp bridge

Firstly, you need to have a Matrix homeserver installed. If you don't currently have one, take a look at the instructions at Installing Synapse, and also in the Synapse README.

Next, install mautrix-whatsapp by following the instructions at mautrix-whatsapp/wiki.

If you are starting from scratch, I suggest you take a look at matrix-docker-ansible-deploy, as this project will enable you to deploy Synapse, mautrix-whatsapp and other components easily.

For example, if you have an existing deployment using matrix-docker-ansible-deploy, you can add mautrix-whatsapp simply by adding the following line to your configuration file:

matrix_mautrix_whatsapp_enabled: true

... and re-running the setup:

ansible-playbook -i inventory/hosts setup.yml --tags=setup-all

Either way, you will soon have a functioning Matrix Synapse homeserver and mautrix-whatsapp installed with it. Next, we will set up an Android VM.

🔗Set up an Android VM

The best way to run an Android Virtual Machine is to use the Android Studio tools from Google. First, install Android Studio, making sure to follow the post-install steps, as they will install additional tools we need, including AVD Manager.

Once installed, run AVD manager by choosing Tools -> AVD Manager from the menu.

Follow the steps to create a new virtual machine, in this example I have a Nexus 5X running Android 9, but almost any configuration is fine here. Make sure that you give the device access to the Play Store.

🔗Install WhatsApp and sign-in

Launch the Virtual Device, the open the Play Store and sign in. Now use the Play Store to install WhatsApp on the Virtual Device.

You will be asked to verify your phone number, use your number on another device to complete this step.

🔗Setup mautrix-whatsapp bridge

Now that you have WhatsApp working in a VM, and Matrix working on your server, it's time to bridge them together!

Per the instructions at mautrix-whatsapp/wiki, you must start a new chat with @whatsappbot:<yourdomain>. Type login to begin the authentication process.

mautrix-whatsapp operates by using the WhatsApp Web feature of WhatsApp - which means it uses a QR code that you must now scan on the device running WhatsApp - which in your case is the AVD. In order to scan the presented QR code, set your AVD camera to passthrough the camera device on your host machine - see the images below.

Once this is complete, you can type sync, to start bridging contacts, and sync --create to automatically create room invites.

And that's it! You may need to take a little time to watch the sync happen, particularly if you have a very large number of chats on the WhatsApp side, but there is no further configuration needed.

🔗Demo!

Usage of the matrix-js-sdk

16.10.2018 00:00 — Tutorials Ben Parsons

We have a brand new, exciting guide page offering an introduction to matrix-js-sdk. This guide will live with the documentation at https://matrix.org/docs/guides/usage-of-the-matrix-js-sdk,  but you can find the text below.


Matrix allows open real-time communications over the Internet using HTTP and JSON. This makes developing clients to connect to Matrix servers really easy! Because it's open, and uses simple syntax for messages, you can connect Matrix to anything that communicates over a standard HTTP interface - later projects in this series will explore ideas such as building bots, performing machine learning on message content, and connecting IoT devices such as Philips Hue lights.

🔗Making a Matrix Client

Let's explore how we would make a very simple Matrix client, with only the ability to perform an initial sync, and to get member lists and the timeline for rooms of our choice.

This article will explore the Matrix Client-Server API, making use of the matrix-js-sdk. Later articles may discuss making the underlying calls. Specifically we will cover:

  • login
  • simple syncing
  • listening for timeline events
  • access the `MatrixInMemoryStore`
  • sending messages to rooms
  • how to respond to specific messages, as a bot would
We'll use Node.js as our environment, though the matrix-js-sdk can also be used directly in the browser.

Before we start, make sure you have Node.js and NPM installed: follow instructions at nodejs.org for your platform. Then create a new directory to work in:

mkdir my-first-matrix-client
cd my-first-matrix-client

🔗Let's write JavaScript

Once you're ready, the first thing to do is install the matrix-js-sdk from NPM:

npm install matrix-js-sdk

We include the SDK in our source exactly as expected:

import sdk from 'matrix-js-sdk';

🔗Login with an access token

Instantiate a new client object and use an access token to login:

const client = sdk.createClient({
    baseUrl: "https://matrix.org",
    accessToken: "....MDAxM2lkZW50aWZpZXIga2V5CjAwMTBjaWQgZ2Vu....",
    userId: "@USERID:matrix.org"
});
// note that we use the full MXID for the userId value

(jsdoc for createClient)

If you are logged into Riot, you can find an access token for the logged-in user on the Settings page.

If the homeserver you're logging in to supports logging in with a password, you can also retrieve an access token programmatically using the API. To do this, create a new client with no authentication parameters, then call client.login() with "m.login.password":

const client = sdk.createClient("https://matrix.org");
client.login("m.login.password", {"user": "USERID", "password": "hunter2"}).then((response) => {
    console.log(response.access_token);
});

In any case, once logged in either with a password or an access token, the client can get the current access token via:

console.log(client.getAccessToken());

Note: it is essential to keep this access token safe, as it allows complete access to your Matrix account!

🔗Sync and Listen

Next we start the client, this sets up the connection to the server and allows us to begin syncing:

client.startClient();

Perform a first sync, and listen for the response, to get the latest state from the homeserver:

client.once('sync', function(state, prevState, res) {
    console.log(state); // state will be 'PREPARED' when the client is ready to use
});

Once the sync is complete, we can add listeners for events. We could listen to ALL incoming events, but that would be a lot of traffic, and too much for our simple example. If you want to listen to all events, you can add a listen as follows:

client.on("event", function(event){
    console.log(event.getType());
    console.log(event);
})

Instead, let's just listen to events happening on the timeline of rooms for which our user is a member:

client.on("Room.timeline", function(event, room, toStartOfTimeline) {
    console.log(event.event);
});

🔗Access the Store

When we created a new client with sdk.createClient(), an instance of the default store, MatrixInMemoryStore was created and enabled. When we sync, or instruct otherwise our client to fetch data, the data is automatically added to the store.

To access the store, we use accessor methods. For example, to get a list of rooms in which our user is joined:

// client.client.getRooms() returns an array of room objects
var rooms = client.getRooms();
rooms.forEach(room => {
    console.log(room.roomId);
});

(jsdoc for client.getRooms)

More usefully, we could get a list of members for each of these rooms:

var rooms = client.getRooms();
rooms.forEach(room => {
    var members = room.getJoinedMembers();
    members.forEach(member => {
        console.log(member.name);
    });
});

For each room, we can inspect the timeline in the store:

var rooms = client.getRooms();
rooms.forEach(room => {
    room.timeline.forEach(t => {
        console.log(JSON.stringify(t.event.content));
    });
});

🔗Send messages to rooms

To send a message, we create a content object, and specify a room to send to. In this case, I've taken the room ID of #test:matrix.org, and used it as an example:

var testRoomId = "!jhpZBTbckszblMYjMK:matrix.org";

var content = {
    "body": "Hello World",
    "msgtype": "m.text"
};

client.sendEvent(testRoomId, "m.room.message", content, "").then((res) => {
   // message sent successfully
}).catch((err) => {
    console.log(err);
}

(jsdoc for client.sendEvent)

Knowing this, we can put together message listening and message sending, to build a bot which just echos back any message starting with a "!":

var testRoomId = "!jhpZBTbckszblMYjMK:matrix.org";

client.on("Room.timeline", function(event, room, toStartOfTimeline) {
    // we know we only want to respond to messages
    if (event.getType() !== "m.room.message") {
        return;
    }
 
    // we are only interested in messages from the test room, which start with "!"
    if (event.getRoomId() === testRoomId && event.getContent().body[0] === '!') {
        sendNotice(event.event.content.body);
    }
});

function sendNotice(body) {
    var content = {
        "body": body.substring(1),
        "msgtype": "m.notice"
    };
    client.sendEvent(testRoomId, "m.room.message", content, "", (err, res) => {
        console.log(err);
    });
}

Take a look at the msgtype in the object above. In the previous example, we used "m.text" for this field, but now we're using "m.notice". Bots will often use "m.notice" to differentiate their messages. This allows the client to render notices differently, for example Riot, the most popular client, renders notices with a more pale text colour.

🔗Further

There is much, much more to Matrix, the Client-Server API and the matrix-js-sdk, but this guide should give some understanding of simple usage. In subsequent guides we'll cover more detail and also explore projects you can build on top, such as IoT controls and chatbot interfaces. For now you can take a look at other examples in the matrix-js-sdk itself, and also the Matrix Client-Server API which it implements.

iOS: Welcome to MatrixKit

24.04.2015 00:00 — Tutorials Emmanuel Rohee

Historically we've had two projects for iOS:

  • MatrixSDK: a low level library to interact with a Matrix homeserver
  • Console: an example Matrix client based on MatrixSDK

The primary intention of Console was to demonstrate how to use MatrixSDK to write a Matrix client app. However, this split isn't helpful for developers who want higher level modules that provides UIViewControllers ready to use in an existing app, with no need to manage low level communications with the Matrix homeserver.

It is where the MatrixKit project started. MatrixKit sits between MatrixSDK and your existing iOS app.

It provides customisable UIViewControllers a developer can integrate in their app.  If you want to add to your app a screen to chat in a room, you just need to use the MXKRoomViewController.

We made MatrixKit so that the components it provides are easy to integrate but also easy to customise. We do not have yet full samples of customisation as we've been focused on the library core, but here are a few examples:

MXKRoomViewController  JSQMessagesViewController

You probably recognise the theme of the first one, as it's what we use in the Console app today. The second one is the iOS7-style look and feel from JSQMessagesViewController. With few lines of code we connected it to MatrixKit data models. Yes, data models provided by MatrixKit are reusable too.

MatrixKit is also highly extensible. If you want to create new table cells to render messages, new views, new view controllers, etc, you will find a place to hook them into the MatrixKit code.

MatrixKit repository is here: https://github.com/matrix-org/matrix-ios-kit and it is available via CocoaPods (the MatrixKit pod).

In parallel of MatrixKit, we did some spring-cleaning - the official Matrix.org iOS offerings are now split into three github repos. One for each deliverable:

Other releases:

Today, we released MatrixSDK 0.4.0 (changes). Update your pods :)

Console 0.4.0 (changes) is in the Apple submission process. This will be the first version of the app using MatrixKit. Aesthetically, there is no change since the previous version. The app is more stable due to all the data abstractions and management improvements provided by MatrixKit.

If you're an iOS developer, please have a go with MatrixKit and let us know on #ios:matrix.org how you get on!

Monitoring Synapse Metrics with Prometheus

23.04.2015 00:00 — Tutorials Matthew Hodgson

🔗Note: This blog post is outdated, and an up-to-date tutorial is located on the synapse project repo

Synapse has had support for exporting a comprehensive range of metrics via HTTP since 0.8.1 - we added this to help quantify the benefits of all the performance work which is going on currently in advance of Synapse 0.9. If you're interested in monitoring your own synapse and seeing what's going on using something like Prometheus, Leo just wrote a quick tutorial on getting up and running:

🔗How to monitor Synapse metrics using Prometheus

1: Install prometheus:
Follow instructions at http://prometheus.io/docs/introduction/install/
2: Enable synapse metrics:
Simply setting a (local) port number will enable it. Pick a port. prometheus itself defaults to 9090, so starting just above that for locally monitored services seems reasonable. E.g. 9092:

Add to homeserver.yaml

metrics_port: 9092

Restart synapse

3: Check out synapse-prometheus-config
https://github.com/matrix-org/synapse-prometheus-config
4: Add synapse.html and synapse.rules
The .html file needs to appear in prometheus's consoles directory, and the .rules file needs to be invoked somewhere in the main config file. A symlink to each from the git checkout into the prometheus directory might be easiest to ensure git pull keeps it updated.
5: Add a prometheus target for synapse
This is easiest if prometheus runs on the same machine as synapse, as it can then just use localhost:
global: {'{'}
  rule_file: "synapse.rules"
{'}'}

job: {'{'} name: "synapse"

target_group: {'{'} target: "http://localhost:9092/" {'}'} {'}'}

6: Start prometheus:
./prometheus -config.file=prometheus.conf
7: Wait a few seconds for it to start and perform the first scrape,
then visit the console:
http://server-where-prometheus-runs:9090/consoles/synapse.html

And the end result looks something like...

Prometheus screenshot

...amongst many many other system & application metrics.

You can grab the latest version of the tutorial at https://github.com/matrix-org/synapse/blob/master/docs/metrics-howto.md - thanks to Leo for writing it up. Any questions, let us know!

Synapse 0.8.0, Android 0.2.2 SDK & App, iOS 0.3.1 SDK & App, JavaScript SDK 0.0.1 released! (oh my!)

09.03.2015 00:00 — Tutorials Matthew Hodgson

Hi all,

What with the chaos of Mobile World Congress last week we seem to have been hoarding releases - so here's what's been happening!

Synapse 0.8.0 was released this afternoon. This is a major performance/stability release, with lots of good stuff going on - especially adding more customisable push notification support APIs for iOS/Android; support for registering accounts from mobile devices; extensions to the new Application Service API and lots of federation improvements and bug fixes. Please upgrade at your earliest convenience.

Meanwhile, we quietly released the Matrix Console Android example app to the Google Play last week, alongside v0.2.2 of the Android SDK - release notes below. There'll be a new version of the Android Console app out tomorrow, but mentioning here for completeness and to share the release notes. Also, the iOS SDK is now on v0.3.1, with lots of performance and usability improvements.

Finally, we have a whole new official Matrix client SDK for JavaScript, all packaged up ready for use by Node developers and JS purists alike as an NPM with minimal dependencies. Meanwhile, the matrix-angular-sdk has been switched to use matrix-js-sdk from now on. You can use the plain JS API with a npm install matrix-js-sdk and then:

var sdk = require("matrix-js-sdk");
var client = sdk.createClient("https://matrix.org");
client.publicRooms(function(err, data) {'{'}
    console.log("Public Rooms: %s", JSON.stringify(data));
{'}'});

Meanwhile, release notes for all & sundry lie below.


Changes in synapse v0.8.0 (2015-03-06)
======================================

General:

* Add support for registration fallback. This is a page hosted on the server
  which allows a user to register for an account, regardless of what client
  they are using (e.g. mobile devices).

* Added new default push rules and made them configurable by clients:

  * Suppress all notice messages.
  * Notify when invited to a new room.
  * Notify for messages that don't match any rule.
  * Notify on incoming call.

Federation:

* Added per host server side rate-limiting of incoming federation requests.
* Added a ``/get_missing_events/`` API to federation to reduce number of
  ``/events/`` requests.

Configuration:

* Added configuration option to disable registration:
  ``disable_registration``.
* Added configuration option to change soft limit of number of open file
  descriptors: ``soft_file_limit``.
* Make ``tls_private_key_path`` optional when running with ``no_tls``.

Application services:

* Application services can now poll on the CS API ``/events`` for their events,
  by providing their application service ``access_token``.
* Added exclusive namespace support to application services API.


Changes in Matrix Android SDK in 0.2.2 (2015-02-27)
===============================================

-----
 SDK
-----
  
-----------------
 Matrix Console
-----------------
Improvements:
 * Exif management : the uploaded image is rotated according to the exif metadata
   (if the device has enough free memory).
 * Add a piechart while downloading an image 
 * Add JSON representation of a message (tap on its row, “Message details”
 * The public rooms list is now sorted according to the number of members.

Features:
 * Add configuration and skeleton classes for receiving GCM messages
 * Add REST client for pushers API with add method for HTTP pushers.
 * Add the account creation.

Bug fixes:
 * Reset the image thumbnail when a row is reused.
 * SYAND-30 Notification should be away when entering a room.
 * Some images thumbnails were downloaded several times.
 * Restore the foreground service
 * The medias cache was not cleared after logging out.
 * The client crashed when joining #anime:matrix.org.
 * SYAND-29 Messages in delivery status are not seen
 * Some user display names were their matrix IDs.
 * The room name/ topic were invalid when inviting to a room.


Changes in Matrix iOS SDK in 0.3.1 (2015-03-03)
===============================================

-----
 SDK
-----
Improvements:
 * Improved push notifications documentation.
 * MXSession: Slightly randomise reconnection times by up to 3s to prevent all
   Matrix clients from retrying requests to the homeserver at the same time.
 * Improved logs
 
Bug fixes:
 * SYIOS-90 - iOS can receive & display messages multiple times when on bad connections
 
-----------------
 Matrix Console
-----------------
Improvements:
 * Fixed warnings with 64bits builds.
 * Room history: Improve scrolling handling when keyboard appears.
 * Contacts: Prompt user when local contacts tab is selected if constact sync is disabled.
 
Bug fixes:
 * Fix crash when switching rooms while the event stream is resuming.
 * SYIOS-69 - On Screen Keyboard can end up hiding the most recent messages in a room.
 * SYIOS-98 - Crash when attempting to attach image on iPad
 
Changes in Matrix iOS SDK in 0.3.0 (2015-02-23)
===============================================

-----
 SDK
-----
Breaks:
 * [MXSession initWithMatrixRestClient: andStore: ] and the onStoreDataReady argument in
   [MXSession start:] has been removed. The SDK client can now use the asynchronous
   [MXSession setStore:] method to define a store and getting notified when the SDK can
   read cached data from it. (SYIOS-62)
 * MXStore implementations must now implement [MXStore openWithCredentials].
 * All MXRestClient methods now return MXHTTPOperation objects.
 
Improvements:
 * Created the MXSession.notificationCenter component: it indicates when an event must be
   notified to the user according to user's push rules settings.
 * MXFileStore: Improved loading performance by 8x.
 * Added an option (MXSession.loadPresenceBeforeCompletingSessionStart) to refresh presence
   data in background when starting a session.
 * Created MXLogger to redirect NSLog to file and to log crashes or uncaught exception.
 * MXRestClient: Added [MXRestClient registerFallback].
 * Logs: Make all NSLog calls follows the same format.
 
Features:
 * SYIOS-40 - Any HTTP request can fail due to rate-limiting on the server, and need to be retried.
 * SYIOS-81 - Ability to send messages in the background.
 
Bug fixes:
 * SYIOS-67 - We should synthesise identicons for users with no avatar.
 * MXSession: Fixed crash when closing the MXSession before the end of initial Sync.

Introduction to Application Services

02.03.2015 00:00 — Tutorials Kegan Dougal

This post has now been edited into a guide - you can find the source in github, and the formatted guide on the matrix.org website!


Hi everyone. I'm Kegan, one of the core developers on Matrix. I'd like to explain a bit more about one of the upcoming features in Matrix: Application Services. This is an entirely new component in the Matrix architecture which gives great power (along with great responsibility!) to the wielder.

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 for protocol bridges. As you may know, we have an IRC bridge bot on matrix.org which resides as a user on #matrix, #matrix-dev, #openwebrtc and #vuc which bridges into freenode. There is nothing special about this bot; it is just treated as a client. However, this limits the things the bot can do. For example, the bot cannot reserve the virtual user IDs it creates, and cannot lazily bridge arbitrary IRC rooms on-the-fly. By using application services, the IRC bot can do both of these things. This would allow any Matrix user to join a room alias which doesn't exist: say #irc.freenode.python:matrix.org, which would then tell the application service to create a new Matrix room, make the alias for it, join #python on freenode and bridge into it. Any IRC user on #python would then be provisioned as a virtual user e.g. @irc.freenode.alice:matrix.org. This also allows PMs to be sent directly to @irc.freenode.alice:matrix.org, no matter what channel Alice is on.

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/<transaction>", 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. homeserver.yaml. 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 registration.yaml 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/<tid> 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/<alias>")
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 post 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 Application Service API section in the spec, or the raw drafts and spec in https://github.com/matrix-org/matrix-doc/.  The AS API is not yet frozen, so feedback is very welcome!

Bridging Matrix & SIP via Verto

30.11.2014 00:00 — Tutorials Matthew Hodgson

One of the final remaining missing bits of Matrix today is specifying and implementing the Application Service (AS) APIs which allow you to easily extend Matrix with custom server-side functionality. The AS APIs should let you perform any arbitrary manipulation on chatroom contents, modulo end-to-end encryption constraints - e.g. machine translation; archiving/searching contents; interactive automated services; conferencing; firing push notifications and other hooks; etc. If you really want to look behind the curtain, the bug tracking the development (somewhat out-of-date) is at SPEC-34.

However, the most obvious use case for this is gatewaying Matrix rooms through to existing communication platforms (XMPP, SIP, non-standardised systems) - which is obviously key to Matrix's overall goal of defragmenting communication.  And the good news is that even though the AS APIs don't yet exist, we can still make good progress here through the existing client-server API.  Anyone who's hung around chat systems like IRC should be familiar with the idea of bots - non-human users who participate in chatrooms and but perform arbitrary automated functionality - and you can go quite a long way in using the 'bot' idiom to add automatic functionality to Matrix.

[In fact the first AS API we'll be adding will probably be simply extending the client-server API with some additional privileges to allow homeserver admins to hook up services to their server which are essentially privileged bots (e.g. ability to autojoin rooms created on that server with high power-level; ability to flag themselves as an invisible 'service bot'; ability to monitor events from rooms without joining them: useful for read-only services such as sending push notifications, adding search/archive functionality; etc).  This should also be familiar to IRC users, as it's similar to the model that IRC Services uses.]

So, we already have a few bots hanging around prototyping out bridging to other systems, which hopefully should evolve into full Application Services (where it makes sense; sometimes a bot is good enough).  For instance, we have the Matrix/IRC Bridge thanks to tm604 and LeoNerd.  The way this works is simply a bot which joins IRC channels and their Matrix room equivalents; watching the messages on both sides of the bridge and relaying them to the other side, creating virtual users as required.  In future we can be smarter about having the bridge talk on behalf of actual users, or letting actual users control their virtual users, but it's good enough as a first cut.

So for Friday's VUC 517, we decided at the last minute (on Tuesday) to make as much of VUC accessible via Matrix as possible.  One side of this was hooking up the Jitsi Video Bridge to be accessible by talking to the underlying XMPP server - the other side was hooking up via SIP to the ZipDX audio bridge that is used for audio-only participants in the conference.  Both of these would be done as Matrix bots - a virtual user that you could voice/video call 1:1 over Matrix which would then route your call into VUC appropriately.

By Thursday, the Jitsi bot got to the point of being able to place calls and see a single video stream (picked at random), but the video uplink wasn't getting through and actually selecting the right stream to watch (or viewing multiple streams) wasn't in place either.  I'm sure there'll be a similar blogpost once it's finished, so I'm not going to talk about it further here.  Meanwhile, on Thursday night we hacked together a separate bot to jack into the ZipDX bridge via SIP.  Tim Panton's suggestion was to use FreeSWITCH's mod_verto for this, and after Anthony Minesalle and Mike Jerris from FreeSWITCH had popped up on #matrix:matrix.org on Tuesday to find out what we're up to, this seemed like serendipity.

We hadn't played with mod_verto before, although had been pointed at it by someone on IRC shortly after we released Matrix - it's a cool project from the FreeSWITCH dev team that exposes a simple JSON-RPC interface for call signalling over websockets, providing a much more suitable way for WebRTC developers to get calls in and out of FreeSWITCH than shoehorning a SIP stack into their browser.  In this respect it's quite similar to Matrix, but there are some huge differences:

  • Verto doesn't (yet) do federation - either for message-passing (like XMPP) or history-replication (like Matrix or XMPP FMUCs).  In fact, Matrix fundamentally competes more with JSON-RPC at OSI layer 5 by defining a standardised RESTful API for federated state synchronisation - which so happens to define some datatypes for VoIP signalling; whereas Verto currently seems to be focused solely on the application-layer problem of VoIP signalling.
  • Verto is a generic RPC API with method names like verto.invite, verto.answer, verto.media, verto.bye etc. for handling call signalling.  It's obviously readily extensible to any other functionality expressed as an RPC invocation.  The Matrix client-server API however passes around event objects within the context of a room - it's message passing rather than RPC.
  • Matrix's VoIP events support trickle-ICE; announcing ICE candidates from WebRTC as and when they become available.  This good is for speedy call-setup (you don't have to wait for all ICE to complete before setting up the call) and to support call continuity when roaming between different IP networks (in theory).  However, Verto currently requires ICE candidates to be presented monolithically - it hasn't made sense to implement trickle-ICE as FreeSWITCH's core engine doesn't support it yet.
  • Verto looks to be wired to speak JSON-RPC over Websockets, whereas Matrix deliberately uses plain old HTTP as its baseline for maximum simplicity and compatibility in PUTting and GETting JSON events around
  • Verto could be an interoperable standard but the API hasn't been documented yet (unless I've totally missed it) - to build the bot we looked at the websockets tab in Chrome's network inspector and drew some inferences from the JSON seen in a call placed using the FreeSWITCH Verto demo site, which was very intuitive and almost self-documenting.  Meanwhile, the (minimal) doc for Matrix's events is up at http://matrix.org/docs/spec/#voice-over-ip.
Verto has a huge advantage however, in that FreeSWITCH has a mod_verto today, and doesn't have a mod_matrix - so one can use mod_verto right now as an easy way to get VoIP calls in and out of FreeSWITCH from the web and bridge them to SIP!  So, when writing a Matrix<->SIP bridging bot for VUC, Verto turned out to be a really nice way to quickly get something up and running.  The end result is at https://github.com/matrix-org/synapse/blob/develop/contrib/vertobot/bot.pl - a (precisely!) 300 line perl script built on LeoNerd's Net-Async-Matrix and Net::Async::Protocol::WebSocket which logs into Matrix and routes any 1:1 audio calls it receives through to the defined mod_verto service.  Currently it gleefully hardcodes in the destination extension it calls and the caller-ID, but this could trivially be extended.  It also chokes on SSL WebSockets, so your mod_verto needs to be listening unencrypted on port 8081.

The task of mapping between Matrix m.call.* VoIP events and Verto verto.* RPC methods was almost entirely trivial (although I hasten to add that Matrix's and Verto's were developed completely independently - it's just that there are only so many ways to express VoIP signalling in JSON!)

  • Matrix's m.call.invite is equivalent to the combination of verto.invite + verto.media (but may lack ICE candidates)
  • Matrix's m.call.candidates has no direct equivalent, so has to be coalesced and merged into verto.media
  • Matrix's m.call.answer is equivalent to verto.answer (but may lack ICE candidates)
  • Matrix's m.room.displayname is equivalent to verto.display (assuming I understand verto.display)
  • Matrix's m.call.hangup is equivalent to verto.bye
  • …and these are the only verto RPCs we mapped.
For the demo itself, we obviously needed a FreeSWITCH with mod_verto all up and running to hook into the ZipDX bridge: our friends at Truphone were good enough to provide one at zero notice (Thanks James, Andy, Giacomo!), and we were up and running.

Unfortunately we did hit some problems: Net::Async::Matrix has a few quirks which LeoNerd is working out currently; the bot doesn't coalesce the trickle-ICE properly currently causing a race-condition that means ICE setup may fail; Matthew's use of IO::Async was a bit buggy; and moreover we didn't have time to implement DTMF… which was a bit of a shame as you can only unmute yourself on the ZipDX bridge via DTMF *5!

But in general, the mini-hackathon was a success and it was great fun to be able to listen into VUC via the bridge and demonstrate the first ever Matrix<->SIP call!  The bot ran as @vucbot:matrix.org, although is turned off now as there's no VUC to listen to, and the FreeSWITCH & bot were only deployed temporarily.  Once the kinks mentioned above are sorted out we'll hopefully set it running again permanently!  And hopefully this little bot is an exciting precursor to more robust Matrix bridges and application services in the months to come...

If you're crazy enough to want to try to run the bot yourself, then it should actually be quite simple to get up and running:

# grab synapse if you don't have it already git clone https://github.com/matrix-org/synapse.git synapse-develop cd synapse-develop

🔗you'll need the develop branch, as we haven't released a build with vertobot in it yet

git checkout develop cd contrib/vertobot

🔗you'll need cpanm to install the perl dependencies

cpan -i App::cpanminus cpanm --installdeps .

🔗manually install a develop version of Net::Async::Matrix as cpanm can't figure it out, seemingly

cpanm --force PEVANS/Net-Async-Matrix-0.11_002

🔗(you may need to also replace the 'croak' for the "Already have a room with ID" error with 'warn' in Net::Async::Matrix if the bot crashes with this error)

🔗create a username account for your bot on a Matrix homeserver somewhere at this point

🔗set up a config file

cp config.yaml mybot.yaml

🔗edit mybot.yaml to taste - at the least you must specify the login & password & homeserver for your bot!

🔗run it!

./bot.pl -c mybot.yaml

Finally, huge thanks to everyone to helped make the VUC bridging escapade work out - Emil Ivov at Jitsi, James Body, Andy Smith and Giacomo Vacca at Truphone, Anthony Minesalle & Mike Jerris & Brian West at FreeSWITCH for writing freeswitch and mod_verto, Tim Panton for the VUC intro and suggestion of mod_verto, Randy Resnick & Michael Graves at VUC itself, and of course the Matrix team for glueing our side of it together!

Looking forward to lots more ambitious cross-protocol gatewaying and federation in future!