🎉 New! Web Push Notifications for Chatkit. Learn more in our latest blog post.
Hide
Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Add a welcome action when a new user enters a chatroom using Vue

  • Gideon Onwuka

June 13th, 2019
You need to have Node 8.9+, npm 3+ and ngrok installed on your machine.

One of the cool chat features is welcome actions. It is a good way to let new users who join a room know of important information/rules of that room. It is also a good place to let other users who are already in the chat know of the new incoming users.

In this tutorial, I'll walk you through adding a welcome message for new users who joins a room. At the end of this tutorial, you'll be familiar with Chatkit webhooks.

Here is a preview of what we'll be building in a moment:

You can also get the complete code for this tutorial on GitHub.

Prerequisites

To follow along with this tutorial, you will need to:

- Have a basic knowledge of JavaScript.
- Have a basic understanding of [Vue.js](https://vuejs.org/).
- Have [Node.js](https://nodejs.org/) installed on your system (version 8.9 or above).
- Have npm installed on your system (version 3 or above).
- Have [ngrok](https://ngrok.com/) installed.

If you have all those installed, then let's get started!

Client setup - create a Vue project

Vue provides a CLI for scaffolding a new Vue project. First, you'll need to install the Vue CLI globally on your system (if you don't have it installed already). After that, we’ll create a new Vue project with the CLI commands.

Now, create a new Vue project by running the following commands in any convenient location on your system:

    # Install Vue CLI globally on your system
    $ npm install -g @vue/cli

    # Create a new Vue project (for the prompt that appears, press enter to select the default preset.)
    $ vue create chatkit-wrbot

    # Change your directory to the project directory
    $ cd chatkit-wrbot

    # Install Chatkit client SDK
    $ npm install @pusher/chatkit-client

    # Install axios - a library for making request
    $ npm install axios

    # Run the app!
    $ npm run serve

Accessing the URL displayed on your terminal will take you to a Vue default page.

Create a Chatkit app

To get started with Chatkit, you’ll first need to create a Chatkit instance. Head to the dashboard, hit Create, then give your instance a name. I will call mine “welcome bot”.

Once you’ve created your Chatkit instance, head to the Credentials tab and take note of your Instance Locator and Secret Key.

On the same Credentials tab, enable test token provider and then note your Test Token Provider Endpoint:

Note that the test token is not to be used for productions. You can read more on how you can create a token provider in your Node app here if you are going live.

Next, create a .env file to the root folder of the project and update your Chatkit keys to it:

    # ./.env

    VUE_APP_SERVER=http://localhost:3000
    APP_PORT=3000

    CHATKIT_SECRET_KEY=<YOUR_CHATKIT_SECRET_KEY>
    VUE_APP_CHATKIT_INSTANCE_LOCATOR=<YOUR_CHATKIT_INSTANCE_LOCATOR>
    VUE_APP_CHATKIT_TOKEN_ENDPOINT=<YOUR_CHATKIT_TOKEN_ENDPOINT>

Remember to replace <YOUR_CHATKIT_SECRET_KEY>, <YOUR_CHATKIT_INSTANCE_LOCATOR>, and <YOUR_CHATKIT_TOKEN_ENDPOINT> with your correct app keys you have noted above.

Create users

Next, create a number of users so we can use them to chat.

Also, create a user with a username as wrbot that will stand in as a user for sending welcome messages. Fill the User Identity and Display Name as wrbot. We need this user to send responses to rooms where a new user just joined.

Create rooms

Finally, create a number of rooms from your dashboard so we can use them for testing.

Make sure to create a number of rooms so as to have a room that at least one user is not attached to it. This is so that you can use this user to test for welcome message later. A user that is already added to a room won’t trigger the joined room event.

Set up the server

Next, create a new folder named server in the root folder of the project. Then, open up a new terminal and change your current directory to the server folder and then install the following dependencies that we’ll need for the server:

    # Install Express
    $ npm install express cors body-parser @pusher/chatkit-server path

The above dependencies include:

  • express A Node framework we are using to build our server
  • @pusher/chatkit-server Chatkit Node.js SDK
  • dotenv An npm package for parsing configs from .env files
  • cors, bodyParser, path

Create a new file named app.js in the server folder. Then create a Node app by adding the below code to server/app.js:

    // ./server/app.js

    const express = require('express');
    const cors = require('cors');
    const bodyParser = require('body-parser');
    const Chatkit = require('@pusher/chatkit-server');
    const resolve =  require("path").resolve;
    require('dotenv').config({path:  resolve(__dirname, "../.env")})

    const app = express(); // create an express app
    const port = process.env.APP_PORT

    // Initialises chatkit client
    const chatkit = new Chatkit.default({
        instanceLocator: process.env.VUE_APP_CHATKIT_INSTANCE_LOCATOR,
        key: process.env.CHATKIT_SECRET_KEY
    })

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.get('/', async (req, res) => {
        res.send({ hello: 'World!'});
    });

    app.listen(port, () => console.log(`Example app listening on port ${port}!`));

In the code above, after importing the packages that we have installed,

  • We created an Express app using const app = express();
  • Next, we initialisze the Chatkit SDK
  • Then finally, we created an endpoint - / (app.get('/',…) for testing to know if the app works. So, if you visit http://localhost:3000/, you will get a message - { hello: 'World!'}

Next, create a new endpoint for getting rooms that we have in our Chatkit app so we can list them in the chat.

Add the below code the server/app.js file:

    // ./server/app.js

    // [...]
    app.get('/get_rooms', (req, res) => {
        chatkit.getRooms({})
            .then(rooms => {
                res.status(200).send({
                    status: 'success',
                    data: rooms
                });
            })
            .catch(err => {
                res.status(200).send({
                    status: 'error',
                    message: err
                });
            })
    });
    // [...]

This will make a request to Chatkit's server to fetch the public rooms available in our app. It'll return a JSON containing an array of rooms if there is no error, otherwise, it will return a JSON data containing the error message.

Finally, start up the server:

    node app.js

If it starts successfully, you will see a message printed to the terminal - “Node app listening on port 3000!”

Building the chat interface

Now that we have both our client and server apps both running, the next thing we’ll do is to create simple chat interface so we can add the welcome action functionality.

Vue enables us to build reusable components which make up our app user interface. We’ll split the app UI into smaller components so we can build them separately:

For brevity’s sake, we’ll divide the app into four components:

  • Messages.vue - for listing messages
  • InputForm.vue - contains a form for sending message
  • Rooms.vue - for listing groups and contains a form for creating a new group
  • Login.vue - displays the login form

Create the Messages.vue, InputForm.vue, Login.vue and Rooms.vue files inside the src/components folder.

To keep the component file as minimal as possible, I have added all the CSS of the components to a single CSS file. Create the CSS file as App.css in the src folder of the project and then add the below styles to it:

    /* ./src/App.css */
    html,body{
        overflow: hidden; 
        width: 100%;
        height: 100%;
        box-sizing: border-box;
        background: #cfd3d3;
    }
    .main{
        width: 99%;
        height: 100vh;
        border: 1px solid green;
        display: grid;
        grid-template-columns: 1fr 4fr;
    }
    .message-area{
        display: grid;
        position: relative;
        grid-template-areas: "head"
                             "messages"
                             "input";
        height: 100vh;
        grid-template-rows: 50px auto 110px;
    }
    .room-wrapper{
       border: 1px solid gray;
       height: 100vh; 
       background: rgba(247, 250, 252, 0.945);
    }
    .search{
        display: block;
        padding: 6px;
    }
    .input-search {
        width: 93%;
        font-size: 17px;
        text-indent: 3px;
        outline: none;
        resize: none;
        flex-direction: row;
        padding: 10px;
        overflow:hidden;
        border:1px solid #556677;
    }
    .rooms{
        margin-top: 3px;
        overflow-y: auto;
        height: 92%;
    }
    .room {
       border-top: 1px solid gray;
       padding: 15px;
       cursor: pointer;
    }
    .room:hover{
        background: rgba(222, 226, 230, 0.952);
    }
    .input-area{
        grid-area: input;
        display: grid;
        grid-template-columns: 1fr;
        /* background: antiquewhite; */
        background: rgba(247, 250, 252, 0.945);
        padding: 20px;
    }
    .input{
        width: 100%;
    }
    .input-message {
        width: 98%;
        border-radius: 8px;
        border: gray;
        font-size: 17px;
        text-indent: 3px;
        display: block;
        outline: none;
        resize: none;
        overflow: auto;
        flex-direction: row;
        padding: 10px;
        overflow:hidden;
        border:3px solid #556677;
    }
    .message-header{
        grid-area: head;
        display: grid;
        grid-template-columns: 1fr 1fr;
        background: rgba(247, 250, 252, 0.945);
        border-bottom: 1px solid rgb(110, 106, 106);
    }
    .message-header-left{
        text-align: left;
        padding: 10px;
    }
    .message-header-right{
        text-align: right;
        padding: 10px;
    }
    .messages {
        grid-area: messages;
        overflow-y: auto;
        overflow-x: hidden;
        padding: 15px 30px;
        background: #929292ad;
    }
    .float-left {
        float: left;
        clear: both;
        background-color: white;
        color: rgba(28, 19, 64, 0.87);
    }
    .float-right {
        float: right;
        clear: both;
        color: #000000;
        background: #cbe4cbf2;
    }
    .message {
        border-radius: 3px;
        width: fit-content;
        padding: 7px;
        margin: 3px 0px;
        clear: both;
        max-width: 50%;
        min-width: 10%;
        word-wrap: break-word;
        font-size: 18px;
    }
    .chat-name{
       font-size: 17px;
       margin: 1px 0px 7px 0px;
       color: #177a2d;
    }
    .login {
        width: 500px;
        border: 1px solid #cccccc;
        background-color: #ffffff;
        margin: auto;
        margin-top: 30vh;
        box-sizing: border-box;
    }
    .input-form {
        margin-bottom: 9px;
        display: block;
    }
    .input{
        display: block;
        margin: 15px;
        width: 93%;
        height: 40px;
        outline: none;
        font-size: 17px;
        text-indent: 5px;
    }
    .submit{
        display: block;
        width: 95%;
        margin: auto;
        padding: 10px;
        margin-bottom: 20px;
        font-size: 17px;
        cursor: pointer;
    }

The Login component

We’ll be using the Single File Component structure for our components.

Add the Login component markup:

    <!-- ./src/components/Login.vue -->
    <template>
        <div class="login">
          <div class="form"> 
                <form @submit.prevent="login">
                    <input 
                        type="text" 
                        class="input" 
                        placeholder="Enter a username"
                        v-model="username"
                        required>
                    <button type="submit" class="submit" :disabled="status === 'processing'"> Login </button>
                </form>
          </div>
        </div>
    </template>

Here, we are making use of @submit.prevent to prevent the page from reloading once a user hits submits the form and passed in the login function so it is called instead.

Next add the script for the Login component:

    <!-- ./src/components/Login.vue -->
    <script>
    export default {
      name: "login",
      props: ['status'],
      data () {
        return {
            username: "",
        }
      },
      methods: {
        login() {
            this.$emit('login', this.username)
            this.username = ""
        }
      }
     }
    </script>

Here, we define the login function that is called once the user submits the form. In the function, we emit an event named “login” so that we can process the login from the parent Component (App.vue) once the user submits the form.

The Rooms component

Next, add the Rooms component markup for the listing of rooms:

    <!-- ./src/components/Rooms.vue -->
    <template>
        <div class="room-wrapper">
            <div class="search">
                <input type="text" placeholder="search..." class="input-search">
            </div>
            <div class="rooms"> 
                <div class="room" 
                    v-for="(room, ind) in rooms"
                    :key="ind"
                    @click="joinRoom(room)"
                    :class="(activeRoom.id === room.id) ? 'active_room' :''"
                >
                    {{room.name}}
                </div>
            </div>
        </div>
    </template>

Using the v-for directive, we loop through the rooms available and list them. We also added an @click event handler to listen to click events from the user when they want to join a room. Once a room is clicked, we call the joinRoom function.

Next, add the script of the Room's component:

    <!-- ./src/components/Rooms.vue -->
    <script>
    export default {
      name:'rooms',
      props: ['rooms'],
      data() {
        return {
          activeRoom: {}
        }
      },
      methods: {
        joinRoom(room) {
          this.activeRoom = room
          this.$emit('joinRoom', room)
        }
      }
    }
    </script>

    <style scoped>
      .active_room{
        background: rgba(197, 202, 207, 0.952);
      }
    </style>

Here, we define the joinRoom function, which we are calling on the template section. In the joinRoom function, we set the activeRoom state to the room that was just clicked. Then we emit an event named joinRoom to the parent component so we can add the user to the room.

The Messages component

Next, add the Messages component for the listing of messages:

    <!-- ./src/components/Messages.vue -->
    <template>
        <div ref="messages" class="messages">
            <div v-for="message in messages" :key="message.id">
                <div :class="['message-container message', messageDirection(message, 'float-')]"> 
                    <div class="chat-name"> {{ message.sender.name }} </div>
                    <template v-for="part in message.parts"> 
                        {{part.payload.content}}
                    </template>
                </div>
            </div>
        </div>
    </template>

Here, we loop through the messages that will be passed to this component and then list them. I have also added a float-left and float-right CSS that will position a message to the left and to the right respectively.

So the messageDirection function will determine if a message will be floated to the left or to the right of the page depending on who is sending the message.

Next, add the script section of the Messages component:

    <!-- ./src/components/Messages.vue -->
    <script>
    export default {
        name: "messages",
        props: ['messages', 'currentUser'],
        methods: {
          messageDirection(message, css='') {
            return (message.senderId !== this.currentUser.id) ? `${css}left` : `${css}right`
          }
        },
    }
    </script>

The InputForm component

Add the InputForm component markup for rendering the form for adding new messages:

    <!-- ./src/components/InputForm.vue -->
    <template>
        <div class="input-area"> 
            <div class="input">
                <textarea
                    v-if="activeRoom"
                    class="input-message" 
                    v-model="new_message" 
                    placeholder="Type a message" 
                    rows="1"
                    @keyup.shift.enter="resizeInput"
                    @keyup.exact.enter="sendMessage">
                </textarea>
                <div v-else style="text-align:center"> 
                    Click on a room to start chatting...
                </div>
            </div>
        </div>
    </template>

And then the script section:

    <!-- ./src/components/InputForm.vue -->
    <script>
    export default {
      name: "input-from",
      props: ['activeRoom', ],
      data () {
        return {
            new_message: "",
        }
      },
      methods: {
        sendMessage(el) {
            if (!this.new_message) return;
            this.$emit('newMessage', this.new_message)
            this.new_message = ""
            el.target.style = 'height:auto;';
        },
        resizeInput(el) {
            if (el.target.scrollHeight < 80) {
                setTimeout(function() {
                    el.target.style = 'height:auto; padding:0';
                    el.target.style = 'height:' + el.target.scrollHeight + 'px';
                }, 0);
            }
        }
      }
    }
    </script>

The App component

This is the main component file that will house all the other components that we have created.

Next, replace the content of the App.vue file with the below:

    <!-- ./src/App.js -->
    <template>
        <div id="app">
            <div v-if="!currentUser">
                <!-- The login component -->
                <Login @login="login" :status="status"/>
            </div>
            <div class="main" v-else>
                <!-- The rooms component -->
                <Rooms @joinRoom="subscribeToRoom" :rooms="rooms"/>
                <div class="message-area">
                    <div class="message-header"> 
                        <div class="message-header-left"> Group Name </div>
                        <div class="message-header-right">  @{{currentUser.id}} </div>
                    </div>
                    <!-- The messages component -->
                    <Messages 
                        :messages="messages" 
                        :currentUser="currentUser" 
                    />
                    <!-- The inputform component -->
                    <InputForm 
                        @newMessage="addMessage"
                        @joinedRoom="joinedRoom=true" 
                        :activeRoom="activeRoom"
                    />
                </div>
            </div>
        </div>
    </template>

The is the overall mark-up of our app grouped together.

Notice the components that we created earlier:

  • <Login… Remember, we emitted an event named login once the user submits the login form. Here, we are using the @login (short form of v-on:login) and thereby call a login function to process the login.
  • <Rooms… As with the Login component, we listen to the joinRoom event that will be triggered on the component using @joinRoom and then handle the event by calling subscribeToRoom function.
  • <Messages… The same thing applies as with other components
  • <InputForm… The same thing applies as with other component

Next, add the script section of the App.vue file:

    // ./src/App.vue

    <script>
    import { ChatManager, TokenProvider } from '@pusher/chatkit-client'
    import axios from 'axios'

    import Messages from '@/components/Messages'
    import InputForm from '@/components/InputForm'
    import Rooms from '@/components/Rooms'
    import Login from '@/components/Login'

    import './App.css'

    export default {
      name: 'app',
      components: {
        Messages,
        InputForm,
        Rooms,
        Login
      },
      data() {
        return {
            messages: [],
            chatManager: null,
            currentUser: null,
            rooms: [],
            activeRoom: null,
            status: null
        }
      },
      methods: {
      },
    }
    </script>

Now, reload the app to confirm that the page renders properly.

Making the chat work

So far, we have our chat interface ready but we still can’t converse because we are yet to connect the app to Chatkit. We’ll be doing so next.

To start using the SDK, we first need to initialize it. Do so by adding the below function in the methods: {…} block in the App.vue file:

    // ./src/App.vue

    // [...]
        setupChatKit(username) {
          // Initialise the token provider
          const tokenProvider = new TokenProvider({
            url: process.env.VUE_APP_CHATKIT_TOKEN_ENDPOINT
          });

          // Initialise the chatkit manager
          const chatManager = new ChatManager({
            instanceLocator: process.env.VUE_APP_CHATKIT_INSTANCE_LOCATOR,
            userId: username,
            tokenProvider: tokenProvider
          });

          chatManager
            .connect()
            .then( currentUser => {
              this.currentUser = currentUser
              // Fetch rooms
              axios.get(`${process.env.VUE_APP_SERVER}/get_rooms`)
                .then(data => {
                  this.rooms = data.data.data
                })

            })
            .catch( error => {
              this.status = 'error'
            });
        },
    // [...]

When we want to initialize Chakit, we can call this function to do so.

Add the login function in the methods: {…} block in the App.vue file:

    // ./src/App.vue

    // [...] 
       login(username) {
            this.status = 'processing'
            this.setupChatKit(username)
        },
    // [...]

So if the user logs in, then we initialize the Chatkit app. Note that we are not doing any real authentication here. This is the best place to authenticate your users if you restrict access to your chat app.

Next, let’s add a function for subscribing to rooms. To receive notification from a room like when a new message is added to a room, we need to be subscribed to that room.

Add the below function in the methods: {…} block of the App.vue file:

    // ./src/App.vue

    // [...]
        subscribeToRoom(room) {
            this.messages = []
            this.currentUser
                .subscribeToRoomMultipart({
                    roomId: room.id,
                    hooks: {
                        onMessage: message => {
                            this.messages.push(message)
                        }
                    },
                    messageLimit: 40
                })

            this.activeRoom = room
        },
    // [...]

Next, add a function for adding new messages to the methods: {…} block of the App.vue file:

    // ./src/App.vue

    // [...]
        addMessage(message) {
            this.currentUser.sendSimpleMessage({
                    roomId: this.activeRoom.id,
                    text: message,
              })
        },
    // [...]

Add the welcome action

Good! At this point what is left is to send a welcome message to new users that just joined the room.

When a user joins a room, we’ll send a welcome message as such:

Chatkit’s webhook

A webhook is an HTTP POST request made to an endpoint of your choosing to notify you of some event. With the webhook, we can tell Chatkit to make a request to a given URL once a certain type of events happens.

For our use-case, we want to be notified when a new user joins a room. We are most concerned with the User Joined Room event that will be triggered when a user joins a room for the first time.

Send response to the room

Now when a new user joins a room, we need to send a welcome message to the room as wrbot.

Add the function for doing so:

    // ./server/app.js

    // [...]
    function wrbotSendMessage(roomId, message) {
      return chatkit.sendSimpleMessage({
        userId: "wrbot", // our bot ID
        roomId: roomId,
        text: message
      });
    }
    // [...]

Create the webhook endpoint

Finally, let’s create an endpoint for the webhook.

When Chatkit makes POST request to our endpoint, it sends along with the detail of the room that the user just joined in this format.

Add the function for creating the /welcome_webhook endpoint:

    // ./server/app.js

    // [...]
    app.post("/welcome_webhook", async (req, res) => {
       const room_id = req.body.payload.room.id;
       const room_name = req.body.payload.room.name;
       const user_name = req.body.payload.user.name;

       const welcome_message = `
        Hi ${user_name}! 🎉🎉, You are welcome to ${room_name}.
        We are really glad to have you here.`;

       // Return response early - see https://pusher.com/docs/chatkit/webhooks#retry-strategy
       res.sendStatus(200);

       wrbotSendMessage(room_id, welcome_message)
        .then(response => console.log(response))
        .catch(error => console.log(error));
    });
    // [...]

Here, when Chatkit makes a POST request to our endpoint, we extract the room_id, room_name, and user_name from the data sent. Then we use the data extracted to compose our welcome message - welcome_message which we then send to the room by calling the wrbotSendMessage function.

Enable the webhook

Although, we have our endpoint for translating message ready, which is accessible from http://localhost:3000/welcome_webhook, it can only be accessed from your local system. We need to expose the URL so it can be accessible by Chatkit which we can do using Ngrok.

Open up Ngrok and expose the URL:

    ./ngrok http 3000

Now note any of the Forwarding URLs which can now be accessed from anywhere.

Head to your Chatkit Dashboard and enable the webhook:

  • Click on the Settings tab
  • Fill in the NAME as wrbot or any convenient name.
  • Fill in the TARGET URL - the ngrok generated URL (eg: https://*.ngrok.io/welcome_webhook). Remember to add the /welcome_webhook after the URL.
  • Enter a WEBHOOK SECRET. You can use any text as we are not making use of it in this tutorial.

The webhook secret is for verifying requests to your webhook. This is to make sure the request is coming from Chatkit. You can read more on how to add this here.

Testing the app

Good job! You have successfully added a welcome action functionality to your Chatkit app.

Now let’s test the app to see that it works.

  • Restart the Node terminal
  • Open the app in two or more tabs on your browser
  • Log in to the app with a user that is not a member of a room.
  • Now join a room to see the action message

Conclusion

In this tutorial, you learned how to add welcome actions to your Chatkit app. We explored a way by which you can add welcome action for new users joining a room using the Chatkit webhook feature.

You can find the complete code of this tutorial on GitHub.

Clone the project repository
  • Chat
  • CSS
  • JavaScript
  • Node.js
  • Vue.js
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.