matrix.org/content/blog/2018/10/2018-10-16-usage-of-the-mat...

8.2 KiB

+++ title = "Usage of the matrix-js-sdk" path = "/blog/2018/10/16/usage-of-the-matrix-js-sdk"

[taxonomies] author = ["Ben Parsons"] category = ["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:

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.