The process of building products for an online demographic should be thorough and follow modern-day trends. One of such trend is making it possible for the customers and support agents to have realtime discussions over some form of two-way message channel. This would ensure that customers do not click away in confusion and switch to competitors in times of frustration.
In this tutorial, we will see how to build a realtime chat widget with Go, Pusher, and JavaScript. Here’s a demo of the application:
In the above image, we built a website that sells motorcycles and integrates a chat widget. A customer is able to sign up to speak with a support agent and the agent on the other end can manage communication among a number of connected customers.
To follow along with this article, you will need the following:
Skip the next section if you have already signed up with Pusher and created an application.
The realtime feature of the chat widget is dependent on Pusher so you need to create an account here if you don’t already have one, after the signup process, you will be asked to create a new application. Let’s keep the app credentials nearby because we will need it to integrate Pusher within the cat widget.
The final thing we will do is enable the Pusher application to trigger events from the client (browser) over a private channel. We need this feature because it is what will make it possible for a support agent to securely chat with a customer without having to send the message through the backend server first. Follow the steps below to activate client events from the dashboard:
That’s all we need to do here.
Let’s begin by navigating into the src
directory that is located in the $GOPATH
and creating a new directory for our app. This will be the root directory for this project:
1$ cd $GOPATH/src 2 $ mkdir go-pusher-chat-widget 3 $ cd go-pusher-chat-widget
Let’s create the main Go file (this is the entry point of the application) here and call it chat.go
.
Next, we will install the Go Pusher library that we will reference within the code for the backend server. Run the following code in the terminal to pull in the package:
$ go get github.com/pusher/pusher-http-go
⚠️ If you use Windows and you encounter the error ‘cc.exe: sorry, unimplemented: 64-bit mode not compiled in ‘, then you need a Windows gcc port, such as https://sourceforge.net/projects/mingw-w64/. Also, see this GitHub issue.
Open the chat.go
file in your favorite IDE and update it with the following code:
1// File: ./chat.go 2 package main 3 4 // Here, we import the required packages (including Pusher) 5 import ( 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "log" 10 "net/http" 11 pusher "github.com/pusher/pusher-http-go" 12 ) 13 14 // Here, we register the Pusher client 15 var client = pusher.Client{ 16 AppId: "PUSHER_APP_ID", 17 Key: "PUSHER_APP_KEY", 18 Secret: "PUSHER_APP_SECRET", 19 Cluster: "PUSHER_APP_CLUSTER", 20 Secure: true, 21 } 22 23 // Here, we define a customer as a struct 24 type customer struct { 25 Name string `json:"name" xml:"name" form:"name" query:"name"` 26 Email string `json:"email" xml:"email" form:"email" query:"email"` 27 } 28 29 func main() { 30 31 // Serve the static files and templates from the public directory 32 http.Handle("/", http.FileServer(http.Dir("./public"))) 33 34 // ------------------------------------------------------- 35 // Listen on these routes for new customer registration and User authorization, 36 // thereafter, handle each request using the matching handler function. 37 // ------------------------------------------------------- 38 http.HandleFunc("/new/customer", broadcastCustomerDetails) 39 http.HandleFunc("/pusher/auth", pusherAuth) 40 41 // Start executing the application on port 8070 42 log.Fatal(http.ListenAndServe(":8070", nil)) 43 }
In the code above, we registered a new Pusher client with the credentials from the app we created earlier on the dashboard.
⚠️ Replace
PUSHER_*
keys with your app credentials.
In the main function, we defined two endpoints, /new/customer
and /pusher/auth
. The first will be hit when a new customer signs up and the last will authorize the users so they can subscribe to private channels.
We will be serving all static files from a public
directory that we will create shortly.
Note that we did not pull in the
ioutil
andhttp
packages because they are already among Go’s standard packages.
We also defined customer
as a struct and attached extra definitions to its properties so that Go knows how to handle incoming payloads and bind their various structures with a new instance of the customer struct.
Let’s create the handler functions for the endpoints, add this code to the chat.go
file just before the main function:
1// File: ./chat.go 2 3 // [...] 4 5 func broadcastCustomerDetails(rw http.ResponseWriter, req *http.Request) { 6 body, err := ioutil.ReadAll(req.Body) 7 if err != nil { 8 panic(err) 9 } 10 var newCustomer customer 11 err = json.Unmarshal(body, &newCustomer) 12 if err != nil { 13 panic(err) 14 } 15 client.Trigger("one-to-many", "new-customer", newCustomer) 16 json.NewEncoder(rw).Encode(newCustomer) 17 } 18 19 func pusherAuth(res http.ResponseWriter, req *http.Request) { 20 params, _ := ioutil.ReadAll(req.Body) 21 response, err := client.AuthenticatePrivateChannel(params) 22 if err != nil { 23 panic(err) 24 } 25 fmt.Fprintf(res, string(response)) 26 } 27 28 // [...]
Above we have two functions. broadcastCustomerDetails
receives a new customer's details and binds it to an instance of the customer
struct. We then trigger the received details over to the admin dashboard in an event over the public channel. The pusherAuth
authorizes users so they can subscribe to private channels.
This is all the code required for the backend server to work, let’s move on to the frontend.
In this section, we will start building the frontend of the web application. We will create all the static files that are rendered when a browser is pointed to the address of our application.
Create a new folder in the project directory and call it public
, this folder is the root directory for all of our frontend files. In this folder, create three folders css
, js
and img
.
Next, create two files in the root of the public
directory named index.html
and support.html
.
Open the index.html
file and update it with the following code:
1<!-- File: ./public/index.html --> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="utf-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 7 <title>X-Cycles</title> 8 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> 9 <link rel="stylesheet" href="./css/app.css" > 10 </head> 11 12 <body> 13 <div class="site-wrapper"> 14 <div class="site-wrapper-inner"> 15 <div class="cover-container"> 16 17 <header class="masthead clearfix"> 18 <div class="inner"> 19 <h3 class="masthead-brand">X-Cycles</h3> 20 <nav class="nav nav-masthead"> 21 <a class="nav-link active" href="#">Home</a> 22 <a class="nav-link" href="#">Features</a> 23 <a class="nav-link" href="#">Contact</a> 24 </nav> 25 </div> 26 </header> 27 28 <main role="main" class="inner cover"> 29 <h1 class="cover-heading">X-cycles</h1> 30 <p class="lead">We sell the best motorcycles around.</p> 31 <p class="lead"> 32 <a href="#" class="btn btn-lg btn-secondary">GALLERY</a> 33 </p> 34 </main> 35 36 <footer class="mastfoot"> 37 </footer> 38 39 </div> 40 </div> 41 </div> 42 <div class="chatbubble"> 43 <div class="unexpanded"> 44 <div class="title">Chat with Support</div> 45 </div> 46 <div class="expanded chat-window"> 47 <div class="login-screen container"> 48 49 <form id="loginScreenForm"> 50 <div class="form-group"> 51 <input type="text" class="form-control" id="fullname" placeholder="Name*" required> 52 </div> 53 <div class="form-group"> 54 <input type="email" class="form-control" id="email" placeholder="Email Address*" required> 55 </div> 56 <button type="submit" class="btn btn-block btn-primary">Start Chat</button> 57 </form> 58 59 </div> 60 <div class="chats"> 61 <div class="loader-wrapper"> 62 <div class="loader"> 63 <span>{</span><span>}</span> 64 </div> 65 </div> 66 <ul class="messages clearfix"> 67 </ul> 68 <div class="input"> 69 <form class="form-inline" id="messageSupport"> 70 <div class="form-group"> 71 <input type="text" autocomplete="off" class="form-control" id="newMessage" placeholder="Enter Message"> 72 </div> 73 <button type="submit" class="btn btn-primary">Send</button> 74 </form> 75 </div> 76 </div> 77 </div> 78 </div> 79 80 <script src="https://js.pusher.com/4.0/pusher.min.js"></script> 81 <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> 82 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script> 83 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script> 84 <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script> 85 <script type="text/javascript" src="./js/app.js"></script> 86 </body> 87 </html>
In the css
directory, create an app.css
file and update it with the following code:
1/* File: ./public/css/app.css */ 2 a, 3 a:focus, 4 a:hover { 5 color: #fff; 6 } 7 .btn-secondary, 8 .btn-secondary:hover, 9 .btn-secondary:focus { 10 color: #333; 11 text-shadow: none; 12 background-color: #fff; 13 border: .05rem solid #fff; 14 } 15 html, 16 body { 17 height: 100%; 18 background-color: #333; 19 } 20 body { 21 color: #fff; 22 text-align: center; 23 text-shadow: 0 .05rem .1rem rgba(0,0,0,.5); 24 } 25 .site-wrapper { 26 display: table; 27 width: 100%; 28 height: 100%; /* For at least Firefox */ 29 min-height: 100%; 30 box-shadow: inset 0 0 5rem rgba(0,0,0,.5); 31 background: url(../img/bg.jpg); 32 background-size: cover; 33 background-repeat: no-repeat; 34 background-position: center; 35 } 36 .site-wrapper-inner { 37 display: table-cell; 38 vertical-align: top; 39 } 40 .cover-container { 41 margin-right: auto; 42 margin-left: auto; 43 } 44 .inner { 45 padding: 2rem; 46 } 47 .masthead { 48 margin-bottom: 2rem; 49 } 50 .masthead-brand { 51 margin-bottom: 0; 52 } 53 .nav-masthead .nav-link { 54 padding: .25rem 0; 55 font-weight: 700; 56 color: rgba(255,255,255,.5); 57 background-color: transparent; 58 border-bottom: .25rem solid transparent; 59 } 60 .nav-masthead .nav-link:hover, 61 .nav-masthead .nav-link:focus { 62 border-bottom-color: rgba(255,255,255,.25); 63 } 64 .nav-masthead .nav-link + .nav-link { 65 margin-left: 1rem; 66 } 67 .nav-masthead .active { 68 color: #fff; 69 border-bottom-color: #fff; 70 } 71 @media (min-width: 48em) { 72 .masthead-brand { 73 float: left; 74 } 75 76 .nav-masthead { 77 float: right; 78 } 79 80 } 81 /* 82 * Cover 83 */ 84 85 .cover { 86 padding: 0 1.5rem; 87 } 88 .cover .btn-lg { 89 padding: .75rem 1.25rem; 90 font-weight: 700; 91 } 92 .mastfoot { 93 color: rgba(255,255,255,.5); 94 } 95 @media (min-width: 40em) { 96 .masthead { 97 position: fixed; 98 top: 0; 99 } 100 101 .mastfoot { 102 position: fixed; 103 bottom: 0; 104 } 105 .site-wrapper-inner { 106 vertical-align: middle; 107 } 108 109 /* Handle the widths */ 110 .masthead, 111 .mastfoot, 112 .cover-container { 113 width: 100%; 114 } 115 116 } 117 @media (min-width: 62em) { 118 .masthead, 119 .mastfoot, 120 .cover-container { 121 width: 42rem; 122 } 123 124 } 125 .chatbubble { 126 position: fixed; 127 bottom: 0; 128 right: 30px; 129 transform: translateY(300px); 130 transition: transform .3s ease-in-out; 131 } 132 .chatbubble.opened { 133 transform: translateY(0) 134 } 135 .chatbubble .unexpanded { 136 display: block; 137 background-color: #e23e3e; 138 padding: 10px 15px 10px; 139 position: relative; 140 cursor: pointer; 141 width: 350px; 142 border-radius: 10px 10px 0 0; 143 } 144 .chatbubble .expanded { 145 height: 300px; 146 width: 350px; 147 background-color: #fff; 148 text-align: left; 149 padding: 10px; 150 color: #333; 151 text-shadow: none; 152 font-size: 14px; 153 } 154 .chatbubble .chat-window { 155 overflow: auto; 156 } 157 .chatbubble .loader-wrapper { 158 margin-top: 50px; 159 text-align: center; 160 } 161 .chatbubble .messages { 162 display: none; 163 list-style: none; 164 margin: 0 0 50px; 165 padding: 0; 166 } 167 .chatbubble .messages li { 168 width: 85%; 169 float: left; 170 padding: 10px; 171 border-radius: 5px 5px 5px 0; 172 font-size: 14px; 173 background: #c9f1e6; 174 margin-bottom: 10px; 175 } 176 .chatbubble .messages li .sender { 177 font-weight: 600; 178 } 179 .chatbubble .messages li.support { 180 float: right; 181 text-align: right; 182 color: #fff; 183 background-color: #e33d3d; 184 border-radius: 5px 5px 0 5px; 185 } 186 .chatbubble .chats .input { 187 position: absolute; 188 bottom: 0; 189 padding: 10px; 190 left: 0; 191 width: 100%; 192 background: #f0f0f0; 193 display: none; 194 } 195 .chatbubble .chats .input .form-group { 196 width: 80%; 197 } 198 .chatbubble .chats .input input { 199 width: 100%; 200 } 201 .chatbubble .chats .input button { 202 width: 20%; 203 } 204 .chatbubble .chats { 205 display: none; 206 } 207 .chatbubble .login-screen { 208 margin-top: 20px; 209 display: none; 210 } 211 .chatbubble .chats.active, 212 .chatbubble .login-screen.active { 213 display: block; 214 } 215 /* Loader Credit: https://codepen.io/ashmind/pen/zqaqpB */ 216 .chatbubble .loader { 217 color: #e23e3e; 218 font-family: Consolas, Menlo, Monaco, monospace; 219 font-weight: bold; 220 font-size: 10vh; 221 opacity: 0.8; 222 } 223 .chatbubble .loader span { 224 display: inline-block; 225 -webkit-animation: pulse 0.4s alternate infinite ease-in-out; 226 animation: pulse 0.4s alternate infinite ease-in-out; 227 } 228 .chatbubble .loader span:nth-child(odd) { 229 -webkit-animation-delay: 0.4s; 230 animation-delay: 0.4s; 231 } 232 @-webkit-keyframes pulse { 233 to { 234 -webkit-transform: scale(0.8); 235 transform: scale(0.8); 236 opacity: 0.5; 237 } 238 239 } 240 @keyframes pulse { 241 to { 242 -webkit-transform: scale(0.8); 243 transform: scale(0.8); 244 opacity: 0.5; 245 } 246 247 }
Above we referenced a
bg.jpg
image. You can download a free picture here and place it in thepublic/img
directory.
Now let's include some JavaScript. In the js
directory, create an app.js
file and paste the following code:
1// File: ./public/js/app.js 2 (function() { 3 'use strict'; 4 5 var pusher = new Pusher('PUSHER_APP_KEY', { 6 authEndpoint: '/pusher/auth', 7 cluster: 'PUSHER_APP_CLUSTER', 8 encrypted: true 9 }); 10 11 let chat = { 12 name: undefined, 13 email: undefined, 14 myChannel: undefined, 15 } 16 17 const chatPage = $(document) 18 const chatWindow = $('.chatbubble') 19 const chatHeader = chatWindow.find('.unexpanded') 20 const chatBody = chatWindow.find('.chat-window') 21 22 let helpers = { 23 ToggleChatWindow: function () { 24 chatWindow.toggleClass('opened') 25 chatHeader.find('.title').text( 26 chatWindow.hasClass('opened') ? 'Minimize Chat Window' : 'Chat with Support' 27 ) 28 }, 29 30 ShowAppropriateChatDisplay: function () { 31 (chat.name) ? helpers.ShowChatRoomDisplay() : helpers.ShowChatInitiationDisplay() 32 }, 33 34 ShowChatInitiationDisplay: function () { 35 chatBody.find('.chats').removeClass('active') 36 chatBody.find('.login-screen').addClass('active') 37 }, 38 39 ShowChatRoomDisplay: function () { 40 chatBody.find('.chats').addClass('active') 41 chatBody.find('.login-screen').removeClass('active') 42 setTimeout(function(){ 43 chatBody.find('.loader-wrapper').hide() 44 chatBody.find('.input, .messages').show() 45 }, 2000) 46 }, 47 48 NewChatMessage: function (message) { 49 if (message !== undefined) { 50 const messageClass = message.sender !== chat.email ? 'support' : 'user' 51 chatBody.find('ul.messages').append( 52 `<li class="clearfix message ${messageClass}"> 53 <div class="sender">${message.name}</div> 54 <div class="message">${message.text}</div> 55 </li>` 56 ) 57 chatBody.scrollTop(chatBody[0].scrollHeight) 58 } 59 }, 60 61 SendMessageToSupport: function (evt) { 62 evt.preventDefault() 63 let createdAt = new Date() 64 createdAt = createdAt.toLocaleString() 65 const message = $('#newMessage').val().trim() 66 67 chat.myChannel.trigger('client-guest-new-message', { 68 'sender': chat.name, 69 'email': chat.email, 70 'text': message, 71 'createdAt': createdAt 72 }); 73 74 helpers.NewChatMessage({ 75 'text': message, 76 'name': chat.name, 77 'sender': chat.email 78 }) 79 80 $('#newMessage').val('') 81 }, 82 83 LogIntoChatSession: function (evt) { 84 const name = $('#fullname').val().trim() 85 const email = $('#email').val().trim().toLowerCase() 86 87 chatBody.find('#loginScreenForm input, #loginScreenForm button').attr('disabled', true) 88 89 if ((name !== '' && name.length >= 3) && (email !== '' && email.length >= 5)) { 90 axios.post('/new/customer', {"name":name, "email":email}).then(response => { 91 chat.name = name 92 chat.email = email 93 console.log(response.data.email) 94 chat.myChannel = pusher.subscribe('private-' + response.data.email); 95 helpers.ShowAppropriateChatDisplay() 96 }) 97 } else { 98 alert('Enter a valid name and email.') 99 } 100 101 evt.preventDefault() 102 } 103 } 104 105 106 pusher.bind('client-support-new-message', function(data){ 107 helpers.NewChatMessage(data) 108 }) 109 110 111 chatPage.ready(helpers.ShowAppropriateChatDisplay) 112 chatHeader.on('click', helpers.ToggleChatWindow) 113 114 chatBody.find('#loginScreenForm').on('submit', helpers.LogIntoChatSession) 115 chatBody.find('#messageSupport').on('submit', helpers.SendMessageToSupport) 116 }());
Above, we instantiated a Pusher object instance and then we created a helpers
object. In this object lies the meat of the script. In the helpers
object we have a few methods that do specific tasks:
ToggleChatWindow
- toggles the chat windows display.ShowAppropriateChatDisplay
- decides which chat display to show depending on the action of the user.ShowChatInitiationDisplay
- shows the initial display for the chat window for the user to initiate a chat session.ShowChatRoomDisplay
- shows the chat window after the user has instantiated a new chat session.NewChatMessage
- adds a new chat message to the chat window UI.SendMessageToSupport
- sends a chat message to the backend.LogIntoChatSession
- starts a new chat session.Replace the
PUSHER_*
keys with the one available on your Pusher dashboard.
Open the support.html
file and update it with the following code:
1<!-- File: ./public/support.html --> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <meta charset="utf-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 7 <title>X-Cycles | Support </title> 8 <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous"> 9 <link rel="stylesheet" href="./css/support.css" > 10 </head> 11 12 <body> 13 <header> 14 <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark"> 15 <a class="navbar-brand" href="#">Dashboard</a> 16 </nav> 17 </header> 18 19 <div class="container-fluid"> 20 <div class="row" id="mainrow"> 21 <nav class="col-sm-3 col-md-2 d-none d-sm-block bg-light sidebar"> 22 <ul class="nav nav-pills flex-column" id="rooms"> 23 </ul> 24 </nav> 25 <main role="main" class="col-sm-9 ml-sm-auto col-md-10 pt-3" id="main"> 26 <h1>Chats</h1> 27 <p>👈 Select a chat to load the messages</p> 28 <p> </p> 29 <div class="chat" style="margin-bottom:150px"> 30 <h5 id="room-title"></h5> 31 <p> </p> 32 <div class="response"> 33 <form id="replyMessage"> 34 <div class="form-group"> 35 <input type="text" placeholder="Enter Message" class="form-control" name="message" /> 36 </div> 37 </form> 38 </div> 39 <div class="table-responsive"> 40 <table class="table table-striped"> 41 <tbody id="chat-msgs"> 42 </tbody> 43 </table> 44 </div> 45 </main> 46 </div> 47 </div> 48 49 <script src="https://js.pusher.com/4.0/pusher.min.js"></script> 50 <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> 51 <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script> 52 <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script> 53 <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script> 54 <script type="text/javascript" src="./js/support.js"></script> 55 </body> 56 </html>
Let’s write the style for the support page. In the css
directory, create a support.css
file and paste the following code:
1/* File: ./public/css/support.css */ 2 body { 3 padding-top: 3.5rem; 4 } 5 h1 { 6 padding-bottom: 9px; 7 margin-bottom: 20px; 8 border-bottom: 1px solid #eee; 9 } 10 .sidebar { 11 position: fixed; 12 top: 51px; 13 bottom: 0; 14 left: 0; 15 z-index: 1000; 16 padding: 20px 0; 17 overflow-x: hidden; 18 overflow-y: auto; 19 border-right: 1px solid #eee; 20 } 21 .sidebar .nav { 22 margin-bottom: 20px; 23 } 24 .sidebar .nav-item { 25 width: 100%; 26 } 27 .sidebar .nav-item + .nav-item { 28 margin-left: 0; 29 } 30 .sidebar .nav-link { 31 border-radius: 0; 32 } 33 .placeholders { 34 padding-bottom: 3rem; 35 } 36 .placeholder img { 37 padding-top: 1.5rem; 38 padding-bottom: 1.5rem; 39 } 40 tr .sender { 41 font-size: 12px; 42 font-weight: 600; 43 } 44 tr .sender span { 45 color: #676767; 46 } 47 .response { 48 display: none; 49 }
Now let's add the JavaScript for the page. In the js
directory, create a support.js
file and update it with the following code:
1// File: ./public/js/support.js 2 (function () { 3 var pusher = new Pusher('PUSHER_APP_KEY', { 4 authEndpoint: '/pusher/auth', 5 cluster: 'PUSHER_APP_CLUSTER', 6 encrypted: true 7 }); 8 9 let chat = { 10 messages: [], 11 currentRoom: '', 12 currentChannel: '', 13 subscribedChannels: [], 14 subscribedUsers: [] 15 } 16 17 var generalChannel = pusher.subscribe('one-to-many'); 18 19 const chatBody = $(document) 20 const chatRoomsList = $('#rooms') 21 const chatReplyMessage = $('#replyMessage') 22 23 const helpers = { 24 25 clearChatMessages: () => $('#chat-msgs').html(''), 26 27 displayChatMessage: (message) => { 28 if (message.email === chat.currentRoom) { 29 $('#chat-msgs').prepend( 30 `<tr> 31 <td> 32 <div class="sender">${message.sender} @ <span class="date">${message.createdAt}</span></div> 33 <div class="message">${message.text}</div> 34 </td> 35 </tr>` 36 ) 37 } 38 }, 39 40 loadChatRoom: evt => { 41 chat.currentRoom = evt.target.dataset.roomId 42 chat.currentChannel = evt.target.dataset.channelId 43 if (chat.currentRoom !== undefined) { 44 $('.response').show() 45 $('#room-title').text(evt.target.dataset.roomId) 46 } 47 evt.preventDefault() 48 helpers.clearChatMessages() 49 }, 50 51 replyMessage: evt => { 52 evt.preventDefault() 53 let createdAt = new Date() 54 createdAt = createdAt.toLocaleString() 55 const message = $('#replyMessage input').val().trim() 56 chat.subscribedChannels[chat.currentChannel].trigger('client-support-new-message', { 57 'name': 'Admin', 58 'email': chat.currentRoom, 59 'text': message, 60 'createdAt': createdAt 61 }); 62 63 helpers.displayChatMessage({ 64 'email': chat.currentRoom, 65 'sender': 'Support', 66 'text': message, 67 'createdAt': createdAt 68 }) 69 70 $('#replyMessage input').val('') 71 }, 72 } 73 74 generalChannel.bind('new-customer', function(data) { 75 chat.subscribedChannels.push(pusher.subscribe('private-' + data.email)); 76 chat.subscribedUsers.push(data); 77 // render the new list of subscribed users and clear the former 78 $('#rooms').html(""); 79 chat.subscribedUsers.forEach(function (user, index) { 80 $('#rooms').append( 81 `<li class="nav-item"><a data-room-id="${user.email}" data-channel-id="${index}" class="nav-link" href="#">${user.name}</a></li>` 82 ) 83 }) 84 }) 85 86 pusher.bind('client-guest-new-message', function(data){ 87 helpers.displayChatMessage(data) 88 }) 89 90 chatReplyMessage.on('submit', helpers.replyMessage) 91 chatRoomsList.on('click', 'li', helpers.loadChatRoom) 92 }())
Above, the script looks almost similar to the app.js
script. The helpers
object contains the following functions:
clearChatMessages
- clears the chat message window.displayChatMessage
- displays a new chat message in the current window.loadChatRoom
- shows a users chat messages in the general chat window after a room is selected.replyMessage
- sends a chat message to the current room.After declaring the helpers, we bind to the Pusher channel and register our listeners.
Replace the
PUSHER_*
keys with the one available on your Pusher dashboard.
To test the application, we will run the application by typing this command in the terminal:
$ go run chat.go
We can visit these addresses, http://127.0.0.1:8070 and http://127.0.0.1:8070/support.html, on a web browser using different windows to test that the application works correctly. Here’s what we should see:
In this tutorial, we learned how to create a basic realtime web chat widget using Go and JavaScript. The source code for this project is available here on GitHub.