Tutorial

Building a chat app with Actyx Pond

In this tutorial, we are going to build a small chat app on top of the Actyx Platform. You might be tempted to skip it because you are not building chats in real-life — give it a chance. The techniques that you will learn in this tutorial are fundamental to building any app on the platform, and mastering them will give you a good understanding of its capabilities.

Before we start

The tutorial is divided into three sections:

  • The first section, Setup for the tutorial, will explain how to set up your ActyxOS nodes and give you a starting point to follow the tutorial
  • The Overview section will teach you the fundamentals of Actyx: nodes, events, and fishes
  • The last section, Building the chat, will teach you the most common techniques in Actyx development

You don’t have to complete all of the sections at once to get value out of this tutorial. Try to get as far as you can — even if it’s just one or two sections.

Prerequisites

In order to get the most out of this tutorial, it is helpful if you have basic knowledge of HTML, CSS, and TypeScript. But since this is a fairly simple app you should be able to follow along even if you are coming from a different programming language.

Typescript in 5 minutes

If you haven't worked in TypeScript before, we can recommend this guide to brush up on the basics.

The best way to experience this tutorial is using multiple devices. In addition to your PC, you will need a second device running Android.

mDNS and Client Isolation

If you have disabled mDNS in your network, you will have to ensure your devices can initially connect to the internet. If you have enabled Client Isolation, this tutorial will not work.

Setup for the tutorial

Now, let's get right to it. In this section we will turn your devices into ActyxOS nodes, configure the nodes, and set up the basic directory structure for the app we will build.

Setup a Docker device

If you don't have Docker installed on your PC already, you can install it from here.

ActyxOS on Docker is publicly available on Docker Hub. To download and run the latest version please execute the following command from your CLI.

docker run --name actyxos -it --rm -e AX_DEV_MODE=1 -v actyxos-data:/data --privileged -p 4001:4001 -p 4457:4457 -p 4243:4243 -p 4454:4454 actyx/os

If you get stuck or want to learn more about ActyxOS on Docker check out this guide.

Setup an Android device

ActyxOS on Android is publicly available from the Google Play Store. Just open the Google Play store on your Android device, search for ActyxOS and install it. To start ActyxOS, just open the app like any other.

If you get stuck or want to learn more about ActyxOS on Android check out this guide

Configure your nodes

Now that you have two devices running ActyxOS, note their IP addresses. On Android, you can find the IP address from the ActyxOS System Info tab or directly from your settings. For your local machine, it depends on the operating system that you are running. A quick online search should do the job.

On your local machine now install the ActyxOS Node Manager, which you can download from downloads.actyx.com. Once installed, use it to connect to each of the ActyxOS nodes using the devices' IP addresses. Then navigate to the Settings tab and paste the following settings for the com.actyx.os namespace, choose a displayName of your choice, and then click Save.

{
"general": {
"bootstrapNodes": [
"/ip4/3.125.108.42/tcp/4001/ipfs/QmUD1mA3Y8qSQB34HmgSNcxDss72UHW2kzQy7RdVstN2hH"
],
"displayName": "Display Name",
"swarmKey": "L2tleS9zd2FybS9wc2svMS4wLjAvCi9iYXNlMTYvCmQ3YjBmNDFjY2ZlYTEyM2FkYTJhYWI0MmY2NjRjOWUyNWUwZWYyZThmNGJjNjJlOTg3NmE3NDU1MTc3ZWQzOGIK"
},
"licensing": {
"os": "development",
"apps": {}
},
"services": {
"consoleService": {},
"eventService": {
"readOnly": false,
"topic": "SampleTopic"
},
"dockerRuntime": {},
"webViewRuntime": {}
}
}

This is what it should look like approximately:

Set node settings using the ActyxOS Node Manager

If everything has worked, you should see the ActyxOS node running on both devices as shown below:

Correctly running ActyxOS node in Node Manager

Setup a web app project

In order to be able to run, test and build the chat app you are going to need Node.js and npm, which you can install from here.

We are now going to setup a simple web app project using Parcel. Somewhere on your computer create a directory called chat.

In that directory create a file called package.json and add the following content:

{
"name": "decentralized-chat",
"version": "1.0.0",
"description": "A decentralized chat",
"scripts": {
"build": "tsc && parcel build index.html --public-url .",
"start": "tsc && parcel index.html"
},
"dependencies": {
"@actyx/pond": "^2.0.1"
},
"devDependencies": {
"@types/node": "^14.0.27",
"parcel-bundler": "^1.12.4",
"typescript": "^3.9.7"
}
}

Create another file called tsconfig.json with the following content:

{
"compilerOptions": {
"esModuleInterop": true,
"sourceMap": true
}
}

Now create a file called index.html and add the following:

<html>
<head>
</head>
<body>
<p>A chat is coming soon!</p>
</body>
<script src="./index.js"></script>
</html>

Finally, create a file named index.ts with the following content:

console.log('Hello, world!')

To test that everything works, open a terminal, navigate to the chat directory and run npm install and then npm run start. This is what you should see in your terminal.

npm run start

If you now navigate to http://localhost:1234 in your browser and open the Developer Tools you should see this:

Chat in browser

Help, I’m stuck!

If you get stuck, get help in the Actyx Developer Chat or e-mail us at [email protected].

Overview

Now that you’re set up, let’s get an overview of the Actyx platform!

What is ActyxOS?

ActyxOS is a multi-node operating system that allows you to build edge native applications running in a swarm of nodes (devices). Specifically you can:

  1. Run one ore more apps on each node using the ActyxOS runtimes
  2. Access always-available localhost APIs such as the Event Service
  3. Count on automatic dissemination and persistence of data in the swarm

ActyxOS schematic

ActyxOS enables a completely decentral architecture that allows you to build apps that always run. Your apps always run because they run locally (on the edge) and only interact with localhost APIs. Currently ActyxOS offers two APIs:

  • The Event Service API at http://localhost:4454/api/v1/events allows you to publish and receive events in the swarm of nodes
  • The Console Service API at http://localhost:4457/api/v1/logs allows you to generate logs for monitoring and debugging

What is Actyx Pond?

Actyx Pond is an application framework for building apps that run on ActyxOS. It is currently available for the TypeScript programming language. Support for further languages, including C#/.NET is planned. Here is how to works:

  1. You implement the business logic of your application by writing so-called fishes and run those in ActyxOS apps
  2. Actyx Pond then automatically synchronizes the state of all fishes throughout the swarm of nodes

Actyx Pond schematic

What is interesting about Actyx Pond is that it allows you to forget completely about how to synchronize state between nodes in the swarm. This happens, for example, when one of the nodes goes offline for a while. As soon as it comes back up, Actyx Pond automatically reconciles what happened between all the nodes while they were disconnected from each other.

Eventual consistency for a partition tolerant system

Formally speaking, Actyx Pond provides eventual consistency for logic implemented on the partition tolerant ActyxOS. You can learn more about the theoretical concepts underlying ActyxOS and Actyx Pond here.

Building the chat

Let's now have a look at how to use ActyxOS and Actyx Pond to build our decentralized chat app.

To implement and run our chat app we need to do three things:

  1. Install ActyxOS on each node (or device). Already done!
  2. Implement our chat logic as a fish
  3. Package and run our chat app

Steps for building the chat

Chat logic

Our chat has a very simple logic. Any participant can send messages and receives messages sent by all other participants. When a participant joins the chat, he should also receive all past messages that were sent when he wasn't part of the chat.

The way to implement this using Actyx Pond is to write a so-called fish. A fish is a state-machine. It has a state which it updates when it receives information from other fishes.

Let's start by defining types for the chat fish's state and the events it can receive. Events it receives from other chat fishes are strings (chat messages). The state of the fish will then be an array of strings. In the index.ts file, add the following two lines of code:

type ChatEvent = string
type ChatState = ChatEvent[]

When a fish first starts up, it won't have received any chat messages yet. So let's define the initial state as an empty array:

const INITIAL_STATE: ChatState = []

Now comes the actual logic of our chat, namely how to calculate the chat state (which we will show to the user), from the events we have received. We do this by writing a so-called onEvent function. In this case, we will simply add the chat messages (ChatEvent) we have received to our state (ChatState):

function onEvent(state: ChatState, event: ChatEvent) {
state.push(event);
return state;
}

This is the complete chat logic. Let's now turn this into a fish.

The chat fish

In Actyx Pond you implement a fish by creating an object with a couple of properties. You must provide the fish with an ID, an initial state, the onEvent function and information about where to get the chat messages from, a so-called event stream tag.

First, add the following imports to the top of the index.ts file:

import { FishId, Pond, Fish, Tag } from '@actyx/pond'

Now that we have done that, we create the tag for our chat messages and then define the fish itself:

const chatTag = Tag<ChatEvent>('ChatMessage')
const ChatFish: Fish<ChatState, ChatEvent> = ({
fishId: FishId.of('ax.example.chat', 'MyChatFish', 0),
initialState: INITIAL_STATE,
onEvent: onEvent,
where: chatTag
})

The user interface

Lastly, we need to build a user interface and hook up our fish. Let's implement a very simple user interface showing the chat messages, an input field to type a message and a button to send the message.

Open up the index.html file and adjust the contents of the head and body sections as follows:

<html>
<head>
<title>Chat App</title>
<style>
body {
padding: 20px;
}
pre {
height: 300px;
padding: 10px;
background-color: #d9d9d9;
overflow-y: auto;
}
button {
margin-top: 10px;
}
button,
input {
width: 100%;
height: 30px;
}
</style>
</head>
<body>
<pre id="messages"></pre>
<input id="message" type="text" />
<button id="send">send</button>
</body>
<script src="./index.js" type="text/javascript"></script>
</html>

The last thing we have to do is to hook up the user interface to the fish. We want to

  1. Show all chat messages, i.e. fish's state in the pre element
  2. Send out a chat message event when the user clicks the Send button

In the index.ts file, add the following code:

Pond.default().then(pond => {
// Select UI elements in the DOM
const messagesTextArea = document.getElementById('messages')
const messageInput = <HTMLInputElement>document.getElementById('message')
const sendButton = document.getElementById('send')
function clearInputAndSendToStream() {
// When click on send button get the text written in the input field
const message = messageInput.value
messageInput.value = ''
// Send the message to a stream tagged with our chat tag
pond.emit(chatTag, message)
}
sendButton.addEventListener('click', clearInputAndSendToStream)
// Observe our chat fish. This means that our callback function will
// be called anytime the state of the fish changes
pond.observe(ChatFish, state => {
// Get the `pre` element and add all chat messages to that element
messagesTextArea.innerHTML = state.join('\n')
// Scroll the element to the bottom when it is updated
messagesTextArea.scrollTop = messagesTextArea.scrollHeight
});
}).catch(console.log)

To test that everything works navigate to the chat directory, run npm run start and open http://localhost:1234. You should now see the chat app. If you started ActyxOS on Docker on your local machine, you should now also be able to send messages. However, you still don't have anyone to chat with.

Package and run the app

In order to run the chat app on your Android device, you need to package and deploy it.

To tell ActyxOS about the app, add an app manifest file called ax-manifest.yml to the chat directory. Add the following contents:

manifestVersion: "1.0"
type: web
id: com.actyx.example.chat
version: 1.0.0
displayName: Chat
description: "Peer-to-Peer Chat"
dist: ./dist/
main: ./dist/index.html
settingsSchema: { default: { } }

Before packaging the app, run npm run build to create a distribution version of the web app (which will be placed in the dist directory). If everything works you should see something like this:

npm run build

Now use the ActyxOS Node Manager to package and deploy the app (use the path to the chat directory):

Package app with Node Manager

Deploy app with Node Manager

If you open ActyxOS on the Android device, you should now see the chat app. You can click and open the app and should now be able to chat back and forth with your local machine!

Open chat on Android

And just like that we have built a multi-node application that would traditionally have required a web server and a shared database or pub-sub broker 🎉

Here is what you can try out now:

  • Do the chat messages get shared between nodes?
  • Do I see history if I restart the app?
  • What happens if I chat or restart in airplane mode?
  • How are messages sorted after I reconnect a device?

We hope that you are now starting to experience the power of edge native applications and the Actyx Platform. If you are keen to dive a bit deeper, check out the following further resources.

Download the code

You can download the complete code for this tutorial from GitHub.

Further resources

Join our Discord chat

Feel free to join our Actyx Developer Chat on Discord. We would love to hear about what you want to build on the Actyx platform.