Creating a Chat Application: Chapter One

20 Dec 18

Introduction

One of the many possible use cases for Diffusion™ is scalable real-time text chat.

This tutorial is the first part of a series on implementing chat using Diffusion. We will start by implementing a very basic system, which we can do quickly with Diffusion’s time series topics.

In later parts, we will show how Diffusion features can be used to implement a more flexible architecture, manage security using Diffusion authentication, and enable third-party login.

Getting Started

Platform

Node.js

In this tutorial, we will use the Node.js JavaScript runtime.

Angular 7

We will use Angular 7 as our front-end framework. This tutorial assumes you are familiar with Angular, so we won’t explain the Angular parts of the implementation in detail.

A good introduction to Angular is available from its developers at https://angular.io/tutorial. See also our tutorial on Using Diffusion with Angular.

Diffusion 6.2

This tutorial uses Diffusion 6.2. To follow along, you will need to set up a Diffusion 6.2 server instance. There are two ways to do this:

  • You can download Diffusion, install it, and run the server on your workstation. The download comes with a restricted default license, which will let you run this tutorial with up to 5 chat sessions. You can visit our Community Hub to get a community license which allows more sessions.
  • You can sign up for Diffusion Cloud and let Push Technology host your server. The free tier is fine to run this tutorial, and supports up to 100 users.

Setup

To get started, install Node.js and npm, if you don’t have them already. You can then run npm with these parameters to install Angular globally on your system:

npm install -g @angular/cli

From the directory in which you wish to create the project, run:

ng new basic-chat

This creates a new basic-chat directory containing the Angular project.

From the new directory, use npm to install the Diffusion client into your project:

npm install diffusion

Getting The Code

The full code for this tutorial can be found at https://github.com/pushtechnology/chat-tutorial/tree/chapter1.

Basic Chat

You can now create a component in Angular that will host the basic chat.

From the sub-folder /src/app, run:

ng generate component chat

Open the file /src/app/chat/chat.component.ts and change the existing imports to these:

import { Component, OnInit } from '@angular/core';
import * as diffusion from 'diffusion';

If it is not already there, add implements OnInit to the ChatComponent class with these fields.

export class ChatComponent implements OnInit {

    private chatSession: diffusion.Session;
    public chatLog: ChatMessage[] = new Array<ChatMessage>();

    constructor() { }
  }

Your editor might now inform you that the type ChatMessage does not exist yet. Let’s create the type outside of ChatComponent.

As a minimum, each chat message needs:

  • an identifier so we know who sent the message
  • a timestamp
  • the message content stored as text

Create the type by adding the following code at the end of the file:

class ChatMessage {
  constructor(public readonly author: string,
   public readonly timestamp: number,
    public readonly content: string) { }
}

Back to ChatComponent, the first function will be ngOnInit() which will
connect to your Diffusion server.

  async ngOnInit() {
    try {
      this.chatSession = await diffusion.connect({
        host: 'localhost',
        port: 8080,
        principal: 'control',
        credentials: 'password'
      });
   }
}

This code assumes you are running the server locally, with the default security configuration. You will need to edit the host, port, principal and credentials for your Diffusion server. If you are using Diffusion Cloud, you can get all the information required from the Diffusion service’s dashboard, under Overview and Security.

In order to ensure that the session unsubscribes and closes when the window gets closed, also insert the following code inside the ngOnInit() method:

window.addEventListener('unload', () => {
    if (this.chatSession !== null) {
      this.chatSession.close();
    }
  });

This will create a listener for the unload event of the window element, which gets triggered when the browser tab closes.

To store chat messages on the Diffusion server, we will use a Diffusion topic. A topic is what Diffusion uses to store and distribute data.

Most Diffusion topic types store only the most recent value, but for a chat application, it would be good for a client which has just connected to be able to retrieve the history of past messages. The  time series topic type is designed for just this sort of situation. With a time series topic, we can store a chronological series of chat messages.

Using the await keyword, add a topic to be used as the chat channel. Use a topic specification to create the topic and specify what properties it should have.

  await this.chatSession.topics.add(
        'Demos/Chat/Channel',
        new diffusion.topics.TopicSpecification(diffusion.topics.TopicType.TIME_SERIES)
          .withProperty('TIME_SERIES_EVENT_VALUE_TYPE', 'json')
          .withProperty('TIME_SERIES_SUBSCRIPTION_RANGE', 'limit 10')
          .withProperty('TIME_SERIES_RETAINED_RANGE', 'limit 100')
      );

Each topic is identified by its own unique path. In this case, the path is Demos/Chat/Channel.

The chat message is stored as a JSON value on the server.

For this simple tutorial, we will only retain the last 100 messages on the server. We specify that with the TIME_SERIES_RETAINED_RANGE property.

We don’t want each client to attempt to retrieve the full message history when it connects. Each client will only attempt to retrieve the most recent ten messages. We specify that with the TIME_SERIES_SUBSCRIPTION_RANGE property.

Once the topic has been added successfully, the client can subscribe to it to see updates, using a value stream.

  this.chatSession.addStream('Demos/Chat/Channel', diffusion.datatypes.json())
        .on('value', (
          topic: string,
          specification: diffusion.TopicSpecification,
          newValue: diffusion.Event,
          oldValue: diffusion.Event
        ) => {
          // listener function goes here
        });
      this.chatSession.select('Demos/Chat/Channel');

When a new message is received as an update to the time series topic, the client will handle it using the listener function we declared earlier. Let’s put them into the ChatMessage array.

 this.chatSession.addStream('Demos/Chat/Channel', diffusion.datatypes.json())
        .on('value', (
          topic: string,
          specification: diffusion.TopicSpecification,
          newValue: diffusion.Event,
          oldValue: diffusion.Event
        ) => {
          this.chatLog.push(new ChatMessage(
            newValue.value.get().author,
            newValue.timestamp,
            newValue.value.get().content
          ));
        });
      this.chatSession.select('Demos/Chat/Channel');

This code will push a new ChatMessage item with the information we got from newValue into our chatLog, and Angular will update the front end to show the new message to the user.

This completes the code designed to handle and receive messages.

Now let’s give the client the ability to send messages to the chat channel. Create a method inside ChatComponent which takes a string parameter:

export class ChatComponent implements OnInit {
  sendMessage(message: string) {
    this.chatSession.timeseries.append('Demos/Chat/Channel', {
      content: message,
      author: this.chatSession.sessionID
    });
  }
}

Note the use of the Diffusion sessionID to distinguish between different clients. Each client session that connects to the server is assigned a different Diffusion sessionID.

This completes the TypeScript part of the application.

Now we need to display chats in the browser and provide a way for the user to enter new messages. Go to the template ./chat/chat.component.html and replace the placeholder with the following HTML:

<div id="chatLogHost">
  <ul>
    <li *ngFor="let chatItem of chatLog">{{chatItem.author}}: {{chatItem.content}}</li>
  </ul>
</div>
<div id="textInputHost">
  <input id="textInput" #textInput placeholder="Type your message here ..." autofocus (keyup.enter)="sendMessage(textInput.value); textInput.value='';"
    type="text">
</div>

This displays each message in the chatLog array as a list.

Below that, it adds an input box with a key-up event, which sends the value it contains to the sendMessage function as a parameter, then clears the value field again.

Improving the client

This chat is very basic in both functionality and design, but it is a great starting point. Here are a couple of simple improvements we can make.

Automatically removing the topic when there are no more subscribers

Let’s suppose we want to delete a channel when it stops being active. This can be implemented with Diffusion’s automatic topic removal feature.

Add the following property to the topic specification used when creating the channel:

.withProperty('REMOVAL', 'when subscriptions < 1 for 10m')

This means that the chat channel topic will be removed if there are no clients connected for ten minutes.

Error handling

As explained in Best practice for developing clients, Diffusion runs on an asynchronous programming model. You should handle unexpected responses to any asynchronous calls.

This code demonstrates a very basic error handler which logs any Diffusion errors to the console.

catch (error) {
      if (error.id && error.message) {
        console.error(`Error: ID ${error.id} - ${error.message} - More: %o`, error);
      } else {
        console.error(error);
      }
    }

Next steps

As you can see, Diffusion makes it easy to get a basic chat implementation up and running.

In this version, each client has access to the server at the ‘control’ security level, and there is only support for one chat channel. In future parts of this tutorial, we will implement a more realistic architecture.


The Diffusion Intelligent Data Platform manages, optimizes, and integrates data among devices, systems, and applications. Push Technology pioneered and is the sole provider of real-time delta-data streaming™ technology that powers mission-critical business applications worldwide. Leading brands use Push Technology to fuel revenue growth, customer engagement, and business operations. The products, Diffusion® and Diffusion Cloud™, are available on-premise, in-the-cloud, or in a hybrid configuration, to fit the specific business and infrastructure requirements of the applications operating in today’s mobile obsessed, everything connected world. Learn how Push Technology can reduce infrastructure costs, and increase speed, efficiency, and reliability, of your web, mobile, and IoT application.

LEARN MORE: Case Studies and Developer Resources