Customer Support Live Chat with VanillaJS and Stream Chat

8 min read
Eze S.
Eze S.
Published March 4, 2020 Updated December 21, 2020

How much do you love when you can just jump on a company's website and chat with one of their representatives to take care of your business; no picking up the phone, no waiting days for an email back??

Customer support live chat is a critical tool for every forward-thinking business. In this tutorial, you’ll learn how to build your own customer support live chat with Pure Javascript and Stream Chat, in just a few lines of code.

Let’s get started!

You'll need the following tools to code along with this tutorial:

Stream Chat is a real-time chat API/SDK that allows you to build secure, scalable, and sustainable chat applications in a few lines of code, without bothering with the underlying infrastructure. It’s simple and easy to use. The best part is that the API documentation that explains how to accomplish your goals is not complicated to understand!

Now that we know a bit about Stream Chat, let’s sign up on Stream and get all set up to build our chat application!

Setting Up Stream Chat

The first step to set up a Stream Chat app is to sign up and get your application credentials. Visit the Stream Chat website to sign up.

You can register with your GitHub account or fill out a sign-up form in less than one minute! Click on the SIGNUP button on the Stream Website:

Sign up

Then, when the pop-up shows up, fill out the form or use the Sign In with GitHub option:

After registering successfully, you should be logged-in to your Stream dashboard where you’ll find your app credentials:

So, what is important here?

  • Your API Key
  • API Secret

Make sure you keep them safe, as we are going to use them in the future.

Sneak Peak

That’s all the information we need to make Stream work with our application! Here is a demo of what we’ll be building:

Setting Up the User Interface

Before we start digging in, let's set the stage and take a look at the structure of our app:

.
├── admin.html
├── admin.js
├── app.js
├── config.js
├── index.html
├── index.js
└── style.css

We’ll have both a frontend and a backend interface for the application. The frontend (index.html) will be the page from which customers will contact the business, while the backend (admin.html) will be where the administrator will respond to received messages.

Let’s start by creating the HTML pages for the backend and the frontend, respectively. Create an admin.html file and add the following code to it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=\, initial-scale=1.0">
    <title>Stream Chat</title>

    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
    <link href="style.css" rel="stylesheet" id="bootstrap-css">

</head>
<body>

    <div class="container">
        <div class="row">
            <div class="chatbox chatbox22 chatbox--tray">
                <div class="chatbox__title">
                    <h5><a href="javascript:void()">Need help?</a></h5>
                    <button class="chatbox__title__close">
                        <span>
                    <svg viewBox="0 0 12 12" width="12px" height="12px">
                        <line stroke="#FFFFFF" x1="11.75" y1="0.25" x2="0.25" y2="11.75"></line>
                        <line stroke="#FFFFFF" x1="11.75" y1="11.75" x2="0.25" y2="0.25"></line>
                    </svg>
                </span>
                    </button>
                </div>
                <div class="chatbox__body">
                    <ul class="chatbox__body__message chatbox__body__message--right" id="right-msg"></ul>
                    <ul class="chatbox__body__message chatbox__body__message--left" id="left-msg"></ul>
                </div>
                <div class="panel-footer">
                    <div class="input-group">
                        <input id="messageText" type="text" class="input-sm chat_set_height" placeholder="Type your message here..." tabindex="0" dir="ltr" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" contenteditable="true" />
                    </div>
                </div>
            </div>
    
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/stream-chat"></script>
    <script type="text/javascript" src="app.js"></script>
    <script type="text/javascript" src="config.js"></script>
    <script type="text/javascript" src="admin.js"></script>
    </script>
        

    </script>
    </body>
</html>

Next, create an index.html file and add the following code to it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=\, initial-scale=1.0">
    <title>Stream Live Chat</title>

    
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
    <link href="style.css" rel="stylesheet" id="bootstrap-css">

</head>
<body>

    <div class="container">
        <div class="row">
            <div class="chatbox chatbox22 chatbox--tray">
                <div class="chatbox__title">
                    <h5><a href="javascript:void()">Need help?</a></h5>
                    <button class="chatbox__title__close">
                        <span>
                    <svg viewBox="0 0 12 12" width="12px" height="12px">
                        <line stroke="#FFFFFF" x1="11.75" y1="0.25" x2="0.25" y2="11.75"></line>
                        <line stroke="#FFFFFF" x1="11.75" y1="11.75" x2="0.25" y2="0.25"></line>
                    </svg>
                </span>
                    </button>
                </div>
                <div class="chatbox__body">
                    <ul id="right-msg"></ul>
                    <ul id="left-msg"></ul>
                </div>
                <div class="panel-footer">
                    <div class="input-group">
                        <input id="messageText" type="text" class="input-sm chat_set_height" placeholder="Type your message here..." tabindex="0" dir="ltr" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" contenteditable="true" />
                    </div>
                </div>
            </div>
    
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/stream-chat"></script>
    <script type="text/javascript" src="app.js"></script>
    <script type="text/javascript" src="config.js"></script>
    <script type="text/javascript" src="index.js">
    </script>
        

    </script>
    </body>
</html>

You’ll notice that the most significant differences here are the title and the fact that we referenced admin.js file in the admin.html page file, and the index.js file in the index.html page file.

To be able to access all the goodness of Stream Chat, you'll need to include the Stream Chat CDN in the HTML template, as we’ve done already in the template above, using the following:

html
1
<script src="https://cdn.jsdelivr.net/npm/stream-chat"></script>

To begin working on the magic that makes it all happen, let’s take a look at the customer chat section of the app in the index.js file:

const messsageText = document.getElementById("messageText")
const apiKey = ""
const client = new StreamChat(apiKey);

const init = (url, username)=>{
    fetch(url,{
       headers: {
           'Content-Type': 'application/json'
       },
       method: "post",
       body: JSON.stringify({username})
   }).then((data)=>{
       return data.json()
   }).then((response)=>{
       const { token }  = response
        Chat(token, username)
   })
}

function Chat(token, id){

    client.setUser(
        {
            id,
            name: 'Client',
            image: 'https://getstream.io/random_svg/?name=John',
        },
        token
    );
    
    const channel = client.channel('messaging', '', {
        // add as many custom fields as you'd like
        image: 'https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png',
        name: "Talk About Anything",
        members: ['admin', 'daniel'],
      });
    
    // fetch the channel state, subscribe to future updates
    const state = channel.watch();
    
    // What is our current channel state? We get to know that from here
    async function getState(){
        return await state
    }
    
    // Get historical messages
    getState().then((data)=>{
        data.messages.map((message)=>{
            singleMessageDisplay(message)
            })
    })
    
    // Listen for new messages
    channel.on('message.new', event => {
        const message = channel.state.messages[channel.state.messages.length-1]
        singleMessageDisplay(message)
    });
        
    
    // Send message to the channel
    messsageText.addEventListener("keypress", (e)=>{
        if ( e.keyCode==13 ){
            // clean message input a bit
            const text = e.target.value.replace(/</g, "&lt;").replace(/>/g, "&gt;").trim();
            if (text === "") {
                return -1; //empty messages cannot be sent
            }else{
                channel.sendMessage({
                    text: e.target.value
                })
                e.target.value = ""
            }
    
        }
    })
    
    
    // Push single message to display. This function pushes a chat bubble to the UI when you hit enter and there is network connection
    const singleMessageDisplay = (message) => {
        if ( message.user.id === client.user.id){
            const div = document.createElement('div')
            div.className = "chatbox__body__message chatbox__body__message--right"        
            div.innerHTML = BubbleTemplate(message.user.name, message.user.id, message.text, message.created_at, client.user.image)
            document.getElementById('right-msg').appendChild(div)
        }
    
        if (message.user.id !== client.user.id){
            const div = document.createElement('div')
            div.className = "chatbox__body__message chatbox__body__message--left"        
            div.innerHTML = BubbleTemplate(message.user.name, message.user.id, message.text, message.created_at, message.user.image)
            document.getElementById('left-msg').appendChild(div)
        }
    }
    

}

const url = "localhost:3002/get-token"
const username = "daniel"

init(url, username)

Initializing a Stream Chat Client and Grabbing Message Text

We'll explore the index.js file bit by bit, to understand what is going here. We start by initializing a new Stream Chat client with the API key you got from your dashboard. The messageText is the input that receives the user's text input:

js
1
2
3
const messsageText = document.getElementById('messageText'); const apiKey = 'baskjfskljh'; const client = new StreamChat(apiKey);

Setting the User

Once we have selected the messageText, set our API key, and initialized a Stream Chat client, we can use that information to set our user. In the code below, the init function takes a user and a url as parameters and passes them to the Chat function (defined a little later), where the user is set, and all the chat functionality happens:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const init = (url, username) => { fetch(url, { headers: { 'Content-Type': 'application/json' }, method: 'post', body: JSON.stringify({ username }) }) .then(data => { return data.json(); }) .then(response => { const { token } = response; Chat(token, username); }); };

To get the token we need to make an API call to a backend API; we are using the fetch API for this call.

Getting the Token

To make use of the basic API for getting the token, which we’ve made especially for this tutorial, run the following, which clones the repository and installs the required packages:

Building your own app? Get early access to our Livestream or Video Calling API and launch in days!
sh
1
git clone https://github.com/ezesundayeze/stream-single-token-generate.git && npm install

Ensure you have NodeJS installed for this API to work.

Next, add your API key and secret in your .env file:

API_KEY=*********
API_SECRET=**************

Diving Deeper into the API

In case you are curious, here is the code for the API:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const express = require('express'); const { StreamChat } = require('stream-chat'); const cors = require('cors'); require('dotenv').config(); const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cors()); const client = new StreamChat(process.env.API_KEY, process.env.API_SECRET); app.post('/get-token/', (req, res, next) => { const token = client.createToken(req.body.username); res.json({ token }); next(); }); var port = process.env.PORT || 3002; app.listen(port, () => console.log('server started'));

At this point, you can run node index.js to start the API server.

The app should be running on this url port: localhost:3002/get-token.

That’s it!

We’l be making the API call to this url: localhost:3002/get-token;
all that you need to send to the API is the username:

js
1
2
3
const url = 'localhost:3002/get-token'; const username = 'client'; init(url, username);

We’ll send this username as a JSON object to the API, and it’ll return the token, which we’ll use to identify and set the user in the Chat function (which we made use of in the index.js file, above):

js
1
2
3
4
5
6
7
8
9
10
11
function Chat(token, id){ client.setUser( { id, name: 'Client', image: 'https://getstream.io/random_svg/?name=John', }, token ); ...

The Chat function takes two parameters a token and the user_id (the user_id is the username used to authenticate the user), and we are calling it in the init function (in the index.js file, above) and passing the data to it after the authentication is complete:

js
1
2
3
4
5
6
... }).then((response)=>{ const { token } = response Chat(token, username) }) ...

Once the user is set, the next step is to create a channel for a one-to-one chat between the customer and the administrator.

Creating a Channel

Creating a one-on-one chat with Stream Chat is as simple as (1) specifying exactly two members and (2) setting the channel title as an empty string:

js
1
2
3
4
5
6
7
const channel = client.channel('messaging', '', { // add as many custom fields as you'd like image: 'https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png', name: 'Talk About Anything', members: ['admin', 'client'] });

For simplicity, we hardcoded the username; in your live app, you can get the user to enter their name before they start the chat and, with that, you can make the chat unique to just that customer and the admin!

The final channel specification you'll want to make is to watch the channel for changes, new messages, etc:

js
1
2
// fetch the channel state, subscribe to future updates const state = channel.watch();

Getting Previous Messages

Let’s get all the messages in a conversation between two persons and push them to the UI! With that said, you may not always want to get the last message; you can choose to pull up any messages you have ever had with a user or you can start a new session each time the customer comes back:

js
1
2
3
4
5
6
7
8
9
10
async function getState() { return await state; } // Get historical messages getState().then(data => { data.messages.map(message => { singleMessageDisplay(message); }); });

What if a new message comes in?

Listening for New Messages

We listen to new messages with the Stream Event Listener function; with that, we can pick the last message object and push to the UI:

js
1
2
3
4
5
// Listen for new messages channel.on('message.new', event => { const message = channel.state.messages[channel.state.messages.length - 1]; // last message object singleMessageDisplay(message); // push message object to the UI. });

Now is when we actually send the message from the interface to Stream. We check for keypress on the messageText input box if the keypress is the Enter key (with keyCode 13), the message contains an actual text then send the message to the channel using the function channel.sendMessage():

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Send message to the channel messsageText.addEventListener('keypress', e => { if (e.keyCode == 13) { // clean message input a bit const text = e.target.value .replace(/</g, '<') .replace(/>/g, '>') .trim(); if (text === '') { return -1; //empty messages cannot be sent } else { channel.sendMessage({ text: e.target.value }); e.target.value = ''; } } });

Finally, once the new message hits the Stream Chat API, we will push it to the chatbox with this template:

js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Push single message to display. This function pushes a chat bubble to the UI when you hit enter and there is network connection const singleMessageDisplay = message => { if (message.user.id === client.user.id) { const div = document.createElement('div'); div.className = 'chatbox__body__message chatbox__body__message--right'; div.innerHTML = BubbleTemplate( message.user.name, message.user.id, message.text, message.created_at, client.user.image ); document.getElementById('right-msg').appendChild(div); } if (message.user.id !== client.user.id) { const div = document.createElement('div'); div.className = 'chatbox__body__message chatbox__body__message--left'; div.innerHTML = BubbleTemplate( message.user.name, message.user.id, message.text, message.created_at, message.user.image ); document.getElementById('left-msg').appendChild(div); } };

The above code pushes the message to the right side of the box if it’s the user's message, and to the left if it’s from the admin. The BubbleTemplate function is the template for each bubble in the chat box; it's defined in the config.js file.
Here is the layout of the chatbox it’s sending the messages to:

html
1
2
3
4
<div class="chatbox__body"> <ul id="right-msg"></ul> <ul id="left-msg"></ul> </div>

That’s all for the client part!

Setting Up the Admin UI

For the backend, or admin end ( admin.js file), repeat the same code but change the username to "admin" and add the username you want the admin to chat with.

The admin will log in to localhost:8000/admin to access the admin page. There are several other ways you can make this work, but we’ll follow this approach for the sake of brevity in this tutorial.

Wrapping Up

Congratulations! Your live support application with VanilaJS and Stream Chat is ready! We mentioned a few upgrades you can make for your live application, but would love to see you do even more with your implementation! There is so much you can do with the Stream API/SDK; check out the docs to learn more!

We can't wait to see what awesome customizations you come up with! You can find the completed app source code for this tutorial on GitHub.

Happy Hacking!

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->