This tutorial will show you how to easily integrate a chat system into an app. It will serve as a step-by-step guide to help you get acquainted with TalkJS and let you experience how powerful and useful it is.

This tutorial will NOT show you how to build a Django app from scratch, but how to integrate TalkJS into an existing one.

You can download/clone the project from the Github repository.

Prerequisites

To follow along with this tutorial,  you will need the following:

  • Experience with Django and Python.
  • Understanding of JavaSsript ES6 synax + AJAX and jQuery.
  • Basic knowledge of Bootstrap.
  • Installed Python (this app uses version 3.7.4) and Django (this app uses version 2.2.5).
  • Installed PostgreSQL database (this app uses version 11).

What we are building

We are going to integrate TalkJS into a simple games chat app. This app allows users to add games and have different conversations about them. Also, users can create conversations or answer existing ones. 

This app consists of the following pages:

  • Login/Logout and Register pages - simple authentication, without a password.
  • Home page - shows all games, paginated.
  • Conversations page - shows all conversations, paginated.
  • Profile page - user information.

Basic functionalities:

  • Add/Edit/Delete games.
  • Add/Edit/Delete conversations.
  • Add/Edit/Delete answers.

Our goal is to implement the following:

  • Private messages.
  • Notification badge.
  • Inbox page.
  • Create chat page.

Login/Register page:

Home page:

Conversations page:

Profile page:

Here is a live demo of this app. 

All that seems cool? Let’s get started…

Integrating TalkJS

Project structure

TalkJS API credentials

Create a TalkJS account here if you haven’t done it yet. After that, you will find your App ID and Secret key on the dashboard.

The next step is setting up our TalkJS API credentials. Navigate to gameschat→settings.py and scroll down until you see the comment:

Put here the TalkJS API credentials.

Remove it and paste these three lines of code:

TALKJS_APP_ID = os.environ.get('TALKJS_APP_ID')
TALKJS_API_SECRET = os.environ.get('TALKJS_API_SECRET')
TALKJS_API_BASE_URL = os.environ.get('TALKJS_API_BASE_URL')

We will need these variables later to access the API. You must also create environment variables accordingly:

  • TALKJS_APP_ID**=**App ID’s value from the TalkJS dashboard.
  • TALKJS_API_SECRET**=**Secret Key’s value from the TalkJS dashboard.
  • TALKJS_API_BASE_URL**=**https://api.talkjs.com/v1/ (you can see it in the documentation).

Finally, let’s navigate to talkjs→views.py and put this import and the following piece of code:

from django.conf import settings
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required

app_id = settings.TALKJS_APP_ID
content_type = 'application/json'
secret = settings.TALKJS_API_SECRET
authorization = f'Bearer {secret}'
talkjs_base_url = f'{settings.TALKJS_API_BASE_URL}{app_id}'

headers = {
    'Content-type': content_type,
    'Authorization': authorization,
}

The JavaScript Chat SDK

You can install the SDK into your site in two ways:

  • Copy and paste the script
  • Via npm

In this tutorial, we will use the first way, so go ahead and navigate to games→templates→games→base.html. At the bottom of the page you will find an if statement put the following script inside it:

{% if user.is_authenticated %}     
  <script>
    (function(t,a,l,k,j,s){
    s=a.createElement('script');s.async=1;s.src="https://cdn.talkjs.com/talk.js";a.head.appendChild(s)
    ;k=t.Promise;t.Talk={v:1,ready:{then:function(f){if(k)return new k(function(r,e){l.push([f,r,e])});l
    .push([f])},catch:function(){return k&&new k()},c:l}};})(window,document,[]);
  </script>

  {% block extrajs %}{% endblock extrajs %}
{% else %}
  <script>sessionStorage.removeItem('currentSession')</script>
{% endif %}

The script loads TalkJS into our app which means we are good to go. Read more about the SDK.

Session

A session represents a user's active browser tab. It also authenticates your app with TalkJS.

The session needs to be initialized on every page reload. This way, TalkJS can trigger JavaScript events everywhere. Read more about sessions

Go ahead and navigate to games→templates→games→base.html again. Put this line of code right after the SDK script:

{% if user.is_authenticated %}
  <script>
    (function(t,a,l,k,j,s){
    s=a.createElement('script');s.async=1;s.src="https://cdn.talkjs.com/talk.js";a.head.appendChild(s)
    ;k=t.Promise;t.Talk={v:1,ready:{then:function(f){if(k)return new k(function(r,e){l.push([f,r,e])});l
    .push([f])},catch:function(){return k&&new k()},c:l}};})(window,document,[]);
  </script>
  <script src="{% static 'games/js/main.js' %}"></script>

  {% block extrajs %}{% endblock extrajs %}
{% else %}
  <script>sessionStorage.removeItem('currentSession')</script>
{% endif %}

This way we will call main.js on every page. Now navigate to games→static→games→js→main.js and paste the following:

const currentSessionKey = 'currentSession'

async function createTalkUser(user) { 
    await Talk.ready

    return new Talk.User({
        id: user.id,
        name: user.name,
        email: user.email,
        photoUrl: user.photoUrl,
        welcomeMessage: user.welcomeMessage ? user.welcomeMessage : null 
    })
}

function getSessionCredentials() {
    return $.ajax({
        type: 'GET',
        url: `/talk/session/current/`
    })
}

async function buildSession({ me, appId, signature }) {
    await Talk.ready

    window.talkSession = new Talk.Session({
        appId: appId,
        me: await createTalkUser(me),
        signature: signature
    })
}

async function initializeSession() {
    const currentSession = sessionStorage.getItem(currentSessionKey)

    if (currentSession) {
        await buildSession(JSON.parse(currentSession))
        return
    }

    await buildSession(await getSessionCredentials())
    sessionStorage.setItem(currentSessionKey, JSON.stringify(talkSession))  
}

$(async function() {
    await initializeSession()
    initializeUnreadMessages()
})

We will implement initializeUnreadMessages() later in this tutorial. Now let’s break this code into pieces. 

Firstly, instead of requesting the session credentials from our back-end on each page reload, we store it into the sessionStorage. This way the session is cleared whenever the browser tab is closed. If we visit the page for the first time, we request session credentials from our back-end. When we receive the response from our request, we build the session and create our current TalkJS user (more about the user). Lastly, we save the session into the sessionStorage.

Wondering what happens when we request the session credentials from our back-end? Navigate to talkjs→views.py and add the following imports:

import hmac
import hashlib
from django.conf import settings
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required

Next, replace the current_session and get_talkjs_user_object methods, with the following code:

@login_required
def current_session(request):
    if request.is_ajax():
        hash = hmac.new(
            bytes(secret, 'utf-8'),  
            bytes(str(request.user.id), 'utf-8'), 
            hashlib.sha256
        )

        return JsonResponse({
            'appId': app_id,
            'me': get_talkjs_user_object(request.user),
            'signature': hash.hexdigest()
        })
    return bad_request()

def get_talkjs_user_object(user):
    return {
        'id': user.id,
        'name': user.username,
        'email': user.email,
        'photoUrl':  user.profile.image_url,
        'welcomeMessage': user.profile.welcome_message
    }

The code checks whether the request is an AJAX request. If so, the method returns a JSON response.

You might be wondering what is the signaturefor? Go ahead and read more about Identity Verification.

Our session looks good, let’s go further…

Private messages

Users should be able to send private messages to other users that created or answered conversations. If you take a look again at the Conversations page, you will see the PM button. This should open the so-called The Popup Widget and start or continue a chat.

Open games→static→games→js→privateMessage.js and paste the following code:

$(async function() {
    await Talk.ready
    await initializeSession()

    $(this).on('click', '#private-message', function() {
        const otherId = $(this).data('other-id')
        const subject = $(this).data('conv-subject')

        $.ajax({
            type: 'GET',
            url: `/talk/private/${otherId}/`,
            success: async function(response) {
                const me = await createTalkUser(response.me)
                const other = await createTalkUser(response.other)
                const conversation = talkSession.getOrCreateConversation(
                Talk.oneOnOneId(me, other))

                conversation.setAttributes({subject: subject})
                conversation.setParticipant(me)
                conversation.setParticipant(other)

                const popup = talkSession.createPopup(conversation, { 
                  keepOpen: true 
                })
                popup.mount({ show: true })
            },
            error: function(error) {
                console.log(error)
            }
        })   
    })
})

As you can see, we initialize the session once again, don’t forget that!

We request from our back-end the current and other user’s objects. With the received response, we create our TalkJS users and we get or create a conversation, which expects an id. We do that with the oneOnOneId function. Next, we set the chat subject (the title of the conversation) and we set the current and other users as participants in the conversation. Lastly, we create and display the popup.

Now navigate to talkjs→views.py and again, add this import:

import hmac
import hashlib
from django.conf import settings
from django.contrib.auth.models import User
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required

And replace the private_message_to_talkjs_chat method with the following code:

@login_required
def private_message_to_talkjs_chat(request, other_id):
    if request.is_ajax():
        other = User.objects.get(pk=other_id)
        return JsonResponse({
            'me': get_talkjs_user_object(request.user),
            'other': get_talkjs_user_object(other)
        })
    return bad_request()

We simply return the users as objects.

Ok! If everything is correct we should see the popup on the screen when we click the PM button:

Cool, we have our popup working perfectly. Now you can discuss conversations in private with other users.

Let’s continue…

Notification badge

Imagine you are not in the app, which means you are either logged out or switched to another tab. In this case, you are probably going to have unread messages. Every user should be able to read them later when they return to the app. Here come the Browser Notifications to-the-rescue!

Remember the Session section and how we left the initializeUnreadMessages() function undiscussed? This function will constantly watch for unread messages and if there are any, they will be shown in a red badge (displayed below). Now it’s time we implement it, so go ahead and navigate again to games→static→games→js→main.js and put the following code:

const currentSessionKey = 'currentSession'

function initializeUnreadMessages() {
    talkSession.unreads.on('change', function(conversationIds) {
        const amountOfUnreads = conversationIds.length

        $('span#notifier-badge')
            .text(amountOfUnreads)
            .toggle(amountOfUnreads > 0) 
    
        if (amountOfUnreads > 0) 
            document.title = '(' + amountOfUnreads + ') Games Chat'
        else 
            document.title = 'Games Chat'
    })
}

async function createTalkUser(user) { 
    await Talk.ready

    return new Talk.User({
        id: user.id,
        name: user.name,
        email: user.email,
        photoUrl: user.photoUrl,
        welcomeMessage: user.welcomeMessage ? user.welcomeMessage : null 
    })
}

function getSessionCredentials() {
    return $.ajax({
        type: 'GET',
        url: `/talk/session/current/`
    })
}

async function buildSession({ me, appId, signature }) {
    await Talk.ready

    window.talkSession = new Talk.Session({
        appId: appId,
        me: await createTalkUser(me),
        signature: signature
    })
}

async function initializeSession() {
    const currentSession = sessionStorage.getItem(currentSessionKey)

    if (currentSession) {
        await buildSession(JSON.parse(currentSession))
        return
    }

    await buildSession(await getSessionCredentials())
    sessionStorage.setItem(currentSessionKey, JSON.stringify(talkSession))  
}

Read more about the notifier badge.

If everything is correct, you should see a small badge when you have unread messages:

Perfect! 

Wait a minute! How can you see the unread messages now? Go to the next section and find out!

Inbox page

The purpose of this page is to gather all the user’s existing chats in one place. There is a very easy way to do that by using The Inbox.

Navigate to games→static→games→js→inbox.jsand put the following code:

let selectedConversationId = null

$(async function() {
    await initializeSession()
    const inbox = talkSession.createInbox({ feedConversationTitleMode: 'subject' })

    inbox.mount(document.getElementById('talkjs-container'))

    inbox.on('conversationSelected', function(selectedConversation) {
        if  (selectedConversation) {
            $('#chat-options').removeClass('hidden')
            selectedConversationId = selectedConversation.conversation.id

            if (selectedConversation.others.length > 1 && 
                selectedConversation.others.length < 4) {
                $('#chat-clipboard').removeClass('hidden')
            } else 
                $('#chat-clipboard').addClass('hidden')

            $('#chat-additions').removeClass('disabled')
        } else {
            selectedConversationId = null
            $('#chat-options').addClass('hidden')
            $('#chat-additions').addClass('disabled')
        }
    })

   $('#chat-additions').on('click', '#chat-leave', function(e) {
        e.preventDefault()
        $.ajax({
            type: 'GET',
            url: `/talk/leave/${selectedConversationId}`
        })
    })

    $('#chat-additions').on('click', '#quick-message', function(e) {
        e.preventDefault()
        
        $.ajax({
            type: 'POST',
            url: `/talk/quick/${selectedConversationId}/`,
            data: { message: $(this).text() }
        })   
    })
})

Simple as that! This piece of code should load the Inbox widget. Read more about feedConversationTitleMode.This is what you should see when the user has no chats:

And this is what you should see when there are some chats:

Wondering what Leave Chat button and Quick Messages are? Keep scrolling and you will find out.

We use conversationSelected to get the id of the selected chat. Then we send a request to our back-end with the retrieved chat id, whenever the user clicks the Leave Chat button.

Navigate again to talkjs→views.py and replace the leave_talkjs_chat, get_request_prerequisites, get_response_result, bad_requestandok_request methods with the following code:

@login_required
def leave_talkjs_chat(request, conversationId):
    url = get_request_prerequisites(f'/conversations/{conversationId}/participants/{request.user.id}')
    response = requests.delete(url, headers=headers)
    return get_response_result(response)

def get_request_prerequisites(endpoint, data=None):
    url = f'{talkjs_base_url}{endpoint}'

    if data is None:
        return url
    return url, json.dumps(data)

def get_response_result(response):
    if response.ok:
        return ok_request()
    return bad_request()

def bad_request():
    return HttpResponse(status=400)

def ok_request():
    return HttpResponse(status=200)

You can easily leave a chat by making a delete request to the TalkJS API with the chat id.

Read more about Participation in a conversation.

If everything is correct, this should happen:

It’s working!

The Quick Messagesis a simple group of predefined messages which are being sent through the TalkJS API. Just a simple request to our back-end with the chat id and the message.

Now go to talkjs→views.pyand replace the quick_message_to_talkjs_chat method with the following: 

@csrf_exempt
@login_required
def quick_message_to_talkjs_chat(request, conversationId):
    if request.is_ajax() and request.method == 'POST':
        url, data = get_request_prerequisites(f'/conversations/{conversationId}/messages', [{
            'text': request.POST['message'],
            'sender': str(request.user.id),
            'type': 'UserMessage'
        }])

        response = requests.post(url, data=data, headers=headers)
        return get_response_result(response)
    return bad_request()

As you can see, we send another request to the TalkJS API with the chat id and the message.

If everything is correct, this should happen:

Aaand it works, great!

So far so good, shall we continue?

Create chat page

In this page, users can create group or private chats with others. In addition, we will play a little bit with the TalkJS Creating Custom Headers customization feature.

Initially, on the left side of the page, all registered users that are available for group or private chats are listed: 

On the right side of the page is displayed The Chatbox. At the beginning the chat is initialized with a single participant - the current user:

This Chatbox should reinitialize immediately whenever a new chat is created.

In addition, users can adjust some chat settings, such as subject, welcome message and chat image. Whenever one or more users are invited, these two buttons should appear:

Clicking the Edit chat settings button should show a little pop-up window where users can adjust the mentioned above settings:

The Create private chat(when only one user is invited) or Create group chat(when more than one user are invited) button should create the chat and reinitialize The Chatbox.

Enough showing off, navigate to users→templates→users→chat.html and remove the following style:

style="display: none !important;"

From this code:

<div class="chatbox-header" style="display: none !important;">
  <div id="header-avatar"></div>
  <p id="header-usernames"></p> 
  <p id="header-subject"></p>
</div>

Now open games→static→games→js→chat.js and put the following code:

class ChatSettings {
    defaultPhotoUrl = 'https://www.pnglot.com/pngfile/detail/107-1071726_chat-icon-png-icon-for-group-chat.png'

    constructor(subject = '', welcomeMessage = '', photoUrl = ''){
        this.subject = subject
        this.welcomeMessage = welcomeMessage
        
        if (!photoUrl && photoUrl === '') {
            this.photoUrl = this.defaultPhotoUrl
            this.isDefaultPhotoUsed = true
        } else {
            this.isDefaultPhotoUsed = false
            this.photoUrl = photoUrl
        }
    }
}

let chatSettings = new ChatSettings()

function initializeConversation(conversation, participants, me, isPrivateChat, isChatWithMe = false) {
    const chatbox = talkSession.createChatbox(conversation, { showChatHeader: false })
    chatbox.mount(document.getElementById('talkjs-container'))

    let imageUrl = chatSettings.photoUrl
    const headerUsernames = isChatWithMe 
        ? `${me.name} (you)` 
        : participants.filter(p => p.id !== me.id).map(p => p.name).join(', ')
    
    if (isChatWithMe) {
        imageUrl = participants[0].photoUrl
    } else if (isPrivateChat && chatSettings.isDefaultPhotoUsed) {
        imageUrl = participants.filter(p => p.id !== me.id)[0].photoUrl
    }

    $('#header-avatar').css('background-image', 'url(' + imageUrl + ')')
    $('#header-usernames').text(headerUsernames)
    $('#header-subject').text(chatSettings.subject)
}

$(async function() {
    await Talk.ready
    await initializeSession()
    
    // Initial conversation with me, myself and I
    const me = await createTalkUser(talkSession.me)
    const conversation = talkSession.getOrCreateConversation(me.id)
    conversation.setParticipant(me)
    initializeConversation(conversation, [me], me, false, true)
    
    $('#chat-options-modal').on('hide.bs.modal', function() {
        const subject = $(this).find('#chat-subject').val()
        const welcomeMessage = $(this).find('#chat-welcome-message').val()
        let photoUrl

        if ($(this).find('#chat-photo-url').val())
            photoUrl = $(this).find('#chat-photo-url').val()

        chatSettings = new ChatSettings(subject, welcomeMessage, photoUrl)
    })
})

First, we get or create the chat with the current user and then we call the initializeConversation()function which initializes The Chatbox (note that theshowChatHeader is set to false) and we apply the chat settings to the chat header. Simple as that.

Next, put the rest of the code in the same file:

class ChatSettings {
    defaultPhotoUrl = 'https://www.pnglot.com/pngfile/detail/107-1071726_chat-icon-png-icon-for-group-chat.png'

    constructor(subject = '', welcomeMessage = '', photoUrl = ''){
        this.subject = subject
        this.welcomeMessage = welcomeMessage
        
        if (!photoUrl && photoUrl === '') {
            this.photoUrl = this.defaultPhotoUrl
            this.isDefaultPhotoUsed = true
        } else {
            this.isDefaultPhotoUsed = false
            this.photoUrl = photoUrl
        }
    }
}

let invitedUsersIds = []
let chatSettings = new ChatSettings()
let chatActionsShown = false

function initializeConversation(conversation, participants, me, isPrivateChat, isChatWithMe = false) {
    const chatbox = talkSession.createChatbox(conversation, { showChatHeader: false })
    chatbox.mount(document.getElementById('talkjs-container'))

    let imageUrl = chatSettings.photoUrl
    const headerUsernames = isChatWithMe 
        ? `${me.name} (you)` 
        : participants.filter(p => p.id !== me.id).map(p => p.name).join(', ')
    
    if (isChatWithMe) {
        imageUrl = participants[0].photoUrl
    } else if (isPrivateChat && chatSettings.isDefaultPhotoUsed) {
        imageUrl = participants.filter(p => p.id !== me.id)[0].photoUrl
    }

    $('#header-avatar').css('background-image', 'url(' + imageUrl + ')')
    $('#header-usernames').text(headerUsernames)
    $('#header-subject').text(chatSettings.subject)
}

function reset() {  
    invitedUsersIds = []
    $('button#invite-user')
        .removeClass('btn-danger')
        .addClass('btn-primary')
        .text('Invite')
    $('#invited-users-count').text(`Selected ${invitedUsersIds.length}/4`)
    $('#chat-actions').fadeOut()
    chatActionsShown = false
    chatSettings = new ChatSettings()
    $("#chat-options-modal")
        .find("input")
        .val('')
        .end()
}

$(async function() {
    await Talk.ready
    await initializeSession()
    
    // Initial conversation with me, myself and I
    const me = await createTalkUser(talkSession.me)
    const conversation = talkSession.getOrCreateConversation(me.id)
    conversation.setParticipant(me)
    initializeConversation(conversation, [me], me, false, true)
    
    $(this).on('click', '#invite-user', function() {
        const otherId = $(this).data('other-id').toString()

        if (invitedUsersIds.indexOf(otherId) === -1 &&
            invitedUsersIds.length < 4) {
            invitedUsersIds.push(otherId)

            $(this)
                .text('Remove')
                .removeClass('btn-primary')
                .addClass('btn-danger')
        } else if (invitedUsersIds.indexOf(otherId) !== -1 &&
                invitedUsersIds.length < 5){
            invitedUsersIds.splice(invitedUsersIds.indexOf(otherId), 1)

            $(this)
                .text('Invite')
                .removeClass('btn-danger')
                .addClass('btn-primary')
        }
        
        if (invitedUsersIds.length == 1) {
            if (!chatActionsShown) {
                $('#chat-actions').css('display', 'flex').hide().fadeIn()
                chatActionsShown = true
            }

            $('#create-chat').text('Create private chat')
        } else if (invitedUsersIds.length > 1) 
            $('#create-chat').text('Create group chat')
        else if (invitedUsersIds.length == 0) {
            $('#chat-actions').fadeOut()
            chatActionsShown = false
        }

        $('#invited-users-count').text(`Selected ${invitedUsersIds.length}/4`)
    })

    $('#create-chat').on('click', function() {
        const { subject, welcomeMessage, photoUrl } = chatSettings

        $.ajax({
            type: 'POST',
            url: `/talk/chat/`,
            data: {
                participants: invitedUsersIds,
                subject,
                welcomeMessage,
                photoUrl
            },
            success: async function({ conversationId, participants }) {
                const conversation = 
                  talkSession.getOrCreateConversation(conversationId)
                const mappedParticipants = 
                  await Promise.all(participants.map(
                    p => new Promise(resolve => resolve(createTalkUser(p)))))

                initializeConversation(conversation, mappedParticipants, me, invitedUsersIds.length == 1)
                reset()
            },
            error: function(error) {
                reset()
                console.log(error)
            }
        })   
    })

    $('#chat-options-modal').on('hide.bs.modal', function() {
        const subject = $(this).find('#chat-subject').val()
        const welcomeMessage = $(this).find('#chat-welcome-message').val()
        let photoUrl

        if ($(this).find('#chat-photo-url').val())
            photoUrl = $(this).find('#chat-photo-url').val()

        chatSettings = new ChatSettings(subject, welcomeMessage, photoUrl)
    })
})

The logic is very straightforward, each user’s id is stored into an array whenever the Invite button is clicked and respectively removed when the Remove button is clicked. When the create chat button is clicked, the array is sent, including the chat settings, to our back-end. With the received response we reinitialize The Chatbox with the newly created chat. Lastly, we call the reset() function to set all variables and UI elements back to the default state.

Now navigate again to python→views.py and replace the create_talkjs_chat, synchronize_talkjs_participants, synchronize_talkjs_participant and send_talkjs_system_message methods with the following code:

@csrf_exempt
@login_required
def create_talkjs_chat(request):
    if request.is_ajax() and request.method == 'POST':
        participantsIds = request.POST.getlist('participants[]')
        participants_count = len(participantsIds)
        is_group = participants_count > 1 # excluding the current user
        participantsIds.append(str(request.user.id))

        synchronize_talkjs_participants(participantsIds, is_group)

        conversationId = 'chat_' + ''.join(random.choices(string.digits, k=5))
        url, data = get_request_prerequisites(f'/conversations/{conversationId}', {
            'participants': participantsIds,
            'subject': request.POST['subject'],
            'welcomeMessages': [request.POST['welcomeMessage']] if 
              request.POST['welcomeMessage'] else None,
            'photoUrl': request.POST['photoUrl'] if request.POST['photoUrl'] else None
        })

        response = requests.put(url, data=data, headers=headers)
        
        if response.ok:
            message = f'This is a group chat with {participants_count} more people!' if is_group else 'This is a private chat!' 
            send_talkjs_system_message(message, conversationId)

            participants = []

            for participantId in participantsIds:
                participant = get_talkjs_user_object(User.objects.get(id=participantId))
                participants.append(participant)

            talkjs_chat_data = { 
              'conversationId': conversationId, 
              'participants': participants 
            }

            return JsonResponse(talkjs_chat_data, status=200)
    return bad_request()

def synchronize_talkjs_participants(participants, is_group):
    for participantId in participants:
        synchronize_talkjs_participant(participantId, is_group)

def synchronize_talkjs_participant(participantId, is_group):
    participant = get_talkjs_user_object(User.objects.get(pk=participantId))

    url, data = get_request_prerequisites(f'/users/{participantId}', {
        'name': participant['name'],
        'email': [participant['email']],
        'photoUrl': participant['photoUrl'],
        'welcomeMessage': None if is_group else participant['welcomeMessage']
    })

    response = requests.put(url, data=data, headers=headers)
    return get_response_result(response)

def send_talkjs_system_message(message, conversationId):
    url, data =  get_request_prerequisites(f'/conversations/{conversationId}/messages', [{ 'text': message, 'type': 'SystemMessage' }])
    response = requests.post(url, data=data, headers=headers)
    return get_response_result(response)

Here the interesting part is that before we create the chat, we need to synchronize all users in the chat. Then we generate a random chat id and we send the request to the TalkJS API. Next, we send a SystemMessage, which gets displayed in the chat, saying whether it is a group or a private chat. Lastly, we return the participants and the chat id as a response.

If everything is correct, this should be the result:

As promised at the beginning of this section, the following lines will show you how to customize the chat header.

As you remember, in the above code, we set the property showChatHeader to false, therefore, the expected result is this:

Now navigate to users→templates→users→chat.html again and find the following:

<div class="col">
    <div class="chatbox-container">
        <div class="chatbox-header">
            <div id="header-avatar"></div>
            <p id="header-usernames"></p> 
            <p id="header-subject"></p>
        </div>
        <div id="talkjs-container" style="width: 100%; height: 570px;">
          <i>Loading chat...</i>
        </div>
    </div>
</div>

This is all you need to do! Later you can select and customize those elements with JavaScript as we did earlier.

Here are the few lines of CSS code that are needed, navigate to games→static→games→main.css:

.chat-container {
  width: 420px;
  max-width: 100%; 
  margin: auto;
}

.chatbox-header {
  width: 100%;
  height: 70px;
  max-width: 420px;
  margin: 0 auto;
  position: relative;
  background-color: var(--main-color);
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  border-radius: 10px 10px 0 0;
  margin-bottom: -3px;
  padding: 10px;
}

#header-avatar {
  position: absolute;
  height: 50px;
  width: 50px;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
  border-radius: 50%;
  border: 2px solid #eee;
}

.chatbox-header p {
  font-family: "Helvetica", sans-serif;
  margin: 0;
  position: absolute;
  color: #fff !important;
  left: 70px;
}

#header-usernames {
  top: 7px;
  font-size: 18px;
  font-weight: bold;
}

#header-subject {
  top: 35px;
  font-size: 15px;
}

Amazing!

Now you can go back to the Inbox page and see all chats, including the one we just created.

Hold on! Something is different now! Oh yes, I almost forgot about that… When the selected chat in the Inbox is a group chat, users can invite others with a link. 

Cool right?

Let’s see the code, navigate again to games→static→games→js→inbox.js and add the following:

inbox.on('conversationSelected', function(selectedConversation) {
        if  (selectedConversation) {
            $('#chat-options').removeClass('hidden')
            selectedConversationId = selectedConversation.conversation.id

            if (selectedConversation.others.length > 1 && 
                selectedConversation.others.length < 4) {
                $('#chat-clipboard').removeClass('hidden')
                const port = window.location.port ? `:${window.location.port}` : ''
                const url = `${window.location.protocol}//${window.location.hostname}${port}/talk/invitation/${selectedConversationId}`
                $('#chat-link').val(url)
            } else 
                $('#chat-clipboard').addClass('hidden')

            $('#chat-additions').removeClass('disabled')
        } else {
            selectedConversationId = null
            $('#chat-options').addClass('hidden')
            $('#chat-additions').addClass('disabled')
        }
    })

    $('#chat-additions').on('click', '#chat-link-copy', function(e) {
        e.preventDefault()
        
        $('#chat-link').select()
        document.execCommand("copy")
    })

This is how the link is built, easy right?

Finally, let’s implement the back-end code, navigate to talkjs→views.py and replace the invite_to_talkjs_chat method with the following:

@login_required
def invite_to_talkjs_chat(request, conversationId):
    conversation = get_talkjs_conversation(conversationId)
    participants_count = len(conversation['participants'])

    if participants_count < 5:
        synchronize_talkjs_participant(request.user.id, True)
        url, data = get_request_prerequisites(f'/conversations/{conversationId}/participants/{request.user.id}', [{ 'access': 'ReadWrite', 'notify': True }])
        response = requests.put(url, data=data, headers=headers)

        if response.ok:
            return redirect('inbox')
    return redirect('error-invitation')

Now copy the link and send it to a friend, if the group is less than 5 people, he or she should be able to join.

And the last thing to do is to add the missing imports in the same file:

import json
import hmac
import string
import random
import hashlib
import requests
from django.conf import settings
from django.shortcuts import redirect
from django.contrib.auth.models import User
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required

Aaand that’s all!

Congrats for making it this far. We have accomplished our goals for this tutorial.

Conclusion

In this tutorial, we saw how easy is to integrate TalkJS into an existing app. TalkJS removes all the complex work of using APIs and building a user interface for your messages. 

We managed to accomplish all goals for this tutorial, such as: implementing notifications, sending private messages, creating chats through the TalkJS API and so on.

I hope you learned something and had fun building this because I did. Make sure to check out other tutorials on how to integrate TalkJS into different apps.

Take care and I’ll see you soon!

You’ve successfully subscribed to TalkJS
Welcome back! You’ve successfully signed in.
Great! You’ve successfully signed up.
Your link has expired
Success! Check your email for magic link to sign-in.