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
- VanillaJS
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:
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:
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, "<").replace(/>/g, ">").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:
123const 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:
12345678910111213141516const 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:
1git 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:
1234567891011121314151617181920const 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
:
123const 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):
1234567891011function 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:
123456... }).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:
1234567const 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:
12// 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:
12345678910async 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:
12345// 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()
:
123456789101112131415161718// 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:
12345678910111213141516171819202122232425262728// 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:
1234<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!