Usage of the matrix-js-sdk

2018-10-16 — 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:
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 interested 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.