Category – Tutorials
8 posts tagged with "Tutorials" (See all categories)

Usage of matrix-nio (Python Sans IO)

2019-07-03 — Tutorials — 

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

2019-02-26 — Tutorials — 

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 togther!

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

2018-10-16 — Tutorials — 

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:

importsdkfrom'matrix-js-sdk';

Login with an access token

Instantiate a new client object and use an access token to login:
constclient=sdk.createClient({
<span class="na" style="color: #008080;">baseUrl</span>
:"https://matrix.org",
<span class="na" style="color: #008080;">accessToken</span>
:"....MDAxM2lkZW50aWZpZXIga2V5CjAwMTBjaWQgZ2Vu....",
<span class="na" style="color: #008080;">userId</span>
:"@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":

constclient=sdk.createClient("https://matrix.org");client.login("m.login.password",{"user":"USERID","password":"hunter2"}).then((response)=>{
<span class="nx">console</span>
.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){
<span class="nx">console</span>
.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){
<span class="nx">console</span>
.log(event.getType());
<span class="nx">console</span>
.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){
<span class="nx">console</span>
.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 objectsvarrooms=client.getRooms();rooms.forEach(room=>{
<span class="nx">console</span>
.log(room.roomId);});
(jsdoc for client.getRooms)

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

varrooms=client.getRooms();rooms.forEach(room=>{
<span class="kd" style="font-weight: bold;">var</span>
members=room.getJoinedMembers();
<span class="nx">members</span>
.forEach(member=>{
    <span class="nx">console</span>
.log(member.name);
<span class="p">{'}'});</span>
});
For each room, we can inspect the timeline in the store:
varrooms=client.getRooms();rooms.forEach(room=>{
<span class="nx">room</span>
.timeline.forEach(t=>{
    <span class="nx">console</span>
.log(JSON.stringify(t.event.content));
<span class="p">{'}'});</span>
});

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:
vartestRoomId="!jhpZBTbckszblMYjMK:matrix.org";varcontent={
<span class="s2" style="color: #d14;">"body"</span>
:"Hello World",
<span class="s2" style="color: #d14;">"msgtype"</span>
:"m.text"};client.sendEvent(testRoomId,"m.room.message",content,"").then((res)=>{// message sent successfully}).catch((err)=>{
<span class="nx">console</span>
.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 "!":

vartestRoomId="!jhpZBTbckszblMYjMK:matrix.org";client.on("Room.timeline",function(event,room,toStartOfTimeline){
<span class="c1" style="color: #998; font-style: italic;">// we know we only want to respond to messages</span>

<span class="k" style="font-weight: bold;">if</span>
(event.getType()!=="m.room.message"){
    <span class="k" style="font-weight: bold;">return</span>
;
<span class="p">{'}'}</span>


<span class="c1" style="color: #998; font-style: italic;">// we are only intested in messages from the test room, which start with "!"</span>

<span class="k" style="font-weight: bold;">if</span>
(event.getRoomId()===testRoomId&&event.getContent().body[0]==='!'){
    <span class="nx">sendNotice</span>
(event.event.content.body);
<span class="p">{'}'}</span>
});functionsendNotice(body){
<span class="kd" style="font-weight: bold;">var</span>
content={
    <span class="s2" style="color: #d14;">"body"</span>
:body.substring(1),
    <span class="s2" style="color: #d14;">"msgtype"</span>
:"m.notice"
<span class="p">{'}'};</span>

<span class="nx">client</span>
.sendEvent(testRoomId,"m.room.message",content,"",(err,res)=>{
    <span class="nx">console</span>
.log(err);
<span class="p">{'}'});</span>
}
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

2015-04-24 — Tutorials — 

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

2015-04-23 — Tutorials — 

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/develop/docs/metrics-howto.rst - 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!)

2015-03-09 — Tutorials — 

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.
NextPage 2