How to generate chat transcripts for a TalkJS chat

A TalkJS chat transcript contains records of a chat between two or more users. In this tutorial, we will generate chat transcripts for a TalkJS Conversation using an Express.js backend application that fetches chat data from the TalkJS Chat API. We can then add a download link to the transcript on the frontend page that displays the TalkJS Chat.

Note 1: This tutorial assumes that you already have a TalkJS integration in your web application. If not, please refer to the TalkJS docs to get started. You can also clone the Github repository containing the frontend (ReactJs) and backend (ExpressJs) source code for this tutorial.

Note 2: We use Express.js in this tutorial to only show an example. You may use any backend framework you’re comfortable with while following the steps shown in this tutorial.

Set up a new Express.js application

We can use the Express.js installation guide and their Hello World example to set up a brand new Express.js application.

// index.js
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

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

Note: Alternatively, do the following steps If you want to work with the source code of this repository:

  1. Clone the GitHub repository.
  2. Go into each project (frontend and backend) and run npm install.
  3. Add your actual App ID and Secret key to TalkUser.js (frontend) and index.js (backend)
  4. Start the application dev server:
  5. Frontend: npm start
  6. Backend: node src/index.js

Listing messages from a conversation

Before we proceed, let us understand how to fetch messages from a conversation using the TalkJs Messages API.

App ID and Secret Key

You need your TalkJs APP ID and Secret Key to access and authenticate the REST API calls. You can find them on your TalkJs settings page.


Messages REST API

We can use the TalkJS Messages API to fetch all messages from a TalkJS conversation.

The API call has the following details:

  1. API Endpoint: https://api.talkjs.com/v1/{APP-ID}/conversations/{Conversation-ID}/messages
  2. APP ID: Your App ID
  3. Conversation ID: Every conversation in TalkJs generates a conversation ID. You can usually get this ID from the frontend page where you have integrated TalkJs.
  4. HTTP Method: GET
  5. Authorization: Bearer Token (Token is your secret key)

Define a new API in ExpressJs

Now, we define a new API endpoint in our ExpressJs application that we will expose to our frontend TalkJS integration page to fetch the chat transcript.

Note: Please make sure to install Axios (npm install axios -save) in your project before attempting to run the code below.

The code below does the following:

  1. Define a new API endpoint: /transcript/:conversationId/generate
  2. :conversationId is the conversation ID the user will pass during the API call as a URL parameter.
  3. Invoke the TalkJS Message API with the conversationID passed by the user and App ID.
  4. Please replace appId and secretKey with your actual credentials.
  5. Set message limit as 100 (maximum allowed by TalkJs)
  6. Set an Authorization header with value as “Bearer <secret_key>”
  7. Return the TalkJS Message API response to the user.
//index.js
const express = require("express");
const axios = require("axios");
const app = express();
const port = 3500;
const appId = "your-APP-ID"; //replace with your actual APP ID
const secretKey = "your-Secret-Key"; //replace with your actual secret key


app.get("/transcript/:conversationId/generate", (req, res) => {
  const options = {
    method: "GET",
    url:
      "https://api.talkjs.com/v1/" +
      appId +
      "/conversations/" +
      req.params.conversationId +
      "/messages?limit=100",
    headers: {
      Authorization: "Bearer " + secretKey,
    },
  };

  axios(options)
    .then(function (response) {
      res.status(response.status)
      res.json(response.data)
    })
    .catch(function (error) {
      throw new Error(error)
    });
});

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

The API response looks something like this:

Clean up and enhance API Response

Currently, we are directly sending the results from TalkJS API to the frontend application. We can clean this up and send only the required information to generate a transcript.

Add CORS headers

I had to add CORS-related headers to allow my front-end React application to access the Express.js API. You may or may not need to do this in your application. Please see the CORS article by MDN to understand more about CORS.

      res.header("Access-Control-Allow-Origin", "*");
      res.header(
        "Access-Control-Allow-Headers",
        "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"
      );
      res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

Sort chat messages

We want to ensure that the chat transcript always contains the latest messages at the end of the transcript. We can do this by sorting all messages returned by the Chat API in the ascending order of their createdAt attribute.

      let senderIDs = new Set();
      let messages = response.data.data.sort((a, b) => {
        senderIDs.add(a.senderId);
        senderIDs.add(b.senderId);
        return a.createdAt - b.createdAt
      });

Note that we’re building the SenderIds list here. We’ll use the same to fetch sender names in the next steps.

Fetch sender names

It would be nice if our users could see the Participant names in the chat transcript. So we fetch the sender names from TalkJS Users API by passing the SenderIDs (SenderIds are the same as TalkJs UserIds). Please note that we’re also building a list of promises here instead of calling the API immediately. We will resolve all the promises in the next step to prepare our final API response.

      let promises = [];      
      senderIDs.forEach((value) => {
        promises.push(getSenderDetails(value));
      });

      function getSenderDetails(senderId) {
        const options = {
          method: "GET",
          url: "https://api.talkjs.com/v1/" + appId + "/users/" + senderId,
          headers: {
            Authorization: "Bearer " + secretKey,
          },
        };
        return axios(options);
      }

Prepare the final API response.

The original TalkJS Chat API response contains additional attributes that are not required to be present in our Express.js API response. So we remove the additional fields and add the Sender Name attribute along with a createdDateTime attribute that contains human readable date and time of the message.

Promise.all(promises).then((responses) => {
        const senderNames = {};
        for (let response of responses) {
          senderNames[response.data.id] = response.data.name;
        }
        for (let message of messages) {
          //add datetime
          message.createdDateTime = new Date(
            message.createdAt
          ).toGMTString();

          //add sender name
          message.senderName = senderNames[message.senderId];

          //delete unnecessary attributes
          [
            "attachment",
            "conversationId",
            "editedAt",
            "custom",
            "id",
            "location",
            "origin",
            "readBy",
            "referencedMessageId",
            "type",
            "createdAt",
            "senderId",
          ].forEach((attribute) => delete message[attribute]);
        }

Finally, this how our Express.Js transcript API code looks after all the required modifications and enhancements.

app.get("/transcript/:conversationId/generate", (req, res) => {
  //Define TalkJS API call options
  const options = {
    method: "GET",
    url: "https://api.talkjs.com/v1/" +
      appId +
      "/conversations/" +
      req.params.conversationId +
      "/messages?limit=100",
    headers: {
      Authorization: "Bearer " + secretKey,
    },
  };

  //make API call to fetch messages of the given conversationID
  axios(options)
    .then(function (response) {

      res.status(response.status); //set status send by TalkJS API response
      //set CORS headers
      res.header("Access-Control-Allow-Origin", "*");
      res.header(
        "Access-Control-Allow-Headers",
        "Content-Type,Content-Length, Authorization, Accept,X-Requested-With"
      );
      res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

      let senderIDs = new Set(); //store unique set of senderIDs
      //Build unique set of senderIds from messages response and sort messages (latest message is last)
      let messages = response.data.data.sort((a, b) => {
        senderIDs.add(a.senderId);
        senderIDs.add(b.senderId);
        return a.createdAt - b.createdAt
      });


      let promises = [];
      //store senderdetail promises
      senderIDs.forEach((value) => {
        promises.push(getSenderDetails(value));
      });

      //resolve promises and add sender name, email & photourl to output
      Promise.all(promises).then((responses) => {
        const senderNames = {};
        for (let response of responses) {
          senderNames[response.data.id] = response.data.name;
        }
        for (let message of messages) {
          //add datetime
          message.createdDateTime = new Date(
            message.createdAt
          ).toGMTString();

          //add sender name
          message.senderName = senderNames[message.senderId];

          //delete unnecessary attributes
          [
            "attachment",
            "conversationId",
            "editedAt",
            "custom",
            "id",
            "location",
            "origin",
            "readBy",
            "referencedMessageId",
            "type",
            "createdAt",
            "senderId",
          ].forEach((attribute) => delete message[attribute]);
        }

        res.json(messages);
      });
    })
    .catch(function (error) {
      throw new Error(error);
    });
});

//returns a promise to make API call for sender details from senderID
function getSenderDetails(senderId) {
  const options = {
    method: "GET",
    url: "https://api.talkjs.com/v1/" + appId + "/users/" + senderId,
    headers: {
      Authorization: "Bearer " + secretKey,
    },
  };
  return axios(options);
}

The API response looks something like this:

The Express.Js transcript API is now ready. Let’s see an example of how you can integrate the API in a front-end application and download the chat transcript to the user’s browser.

Integration with the TalkJS frontend page

Here, we download the chat transcript to the user’s browser whenever they click a “Download Transcript” button on the TalkJs chat page.

Define a new Button

Here, we create a new HTML button using the JSX templating in a React component. For brevity, I have only added a part of the code here. To see the full react component, check out the source code on Github.

            <Button
              onClick={downloadChatTranscript}
              variant="light"
              style={{ textAlign: "left", paddingTop: "0" }}
            >
              <span
                style={{
                  fontSize: "x-large",
                  fontWeight: "bold",
                  paddingRight: "8px",
                }}
              >
                ↓
              </span>
              <span style={{ fontSize: "small", verticalAlign: "text-top" }}>
                Download Chat Transcript
              </span>
            </Button>

Download the Chat transcript

Now, whenever a user clicks on the download button, the following function will execute:

  1. Make a GET request to the Express.JS transcript API and pass the conversation ID.
  2. Create a new Blob object of the type “.txt” file and add the API response contents to this text file.
  3. To download the text file, we assign the text file’s URL as a href to a temporary anchor tag and programmatically trigger the click event on the anchor tag to automatically download the text file to the user’s computer.
const downloadChatTranssript = () => {
    const config = {
      method: "get",
      url: "http://localhost:3500/transcript/" + conversation.id + "/generate",
      headers: {},
    };

    axios(config)
      .then(function (response) {
        let transcript = "";

        response.data.forEach((element) => {
          transcript =
           `${transcript}[${element.createdDateTime}][${element.senderName}] - ${element.text}\n;`
        });

        const element = document.createElement("a");
        const file = new Blob([transcript], {
          type: "text/plain",
        });
        element.href = URL.createObjectURL(file);
        element.download = "talk_js_transcript_chat_"+conversation.id+".txt";
        document.body.appendChild(element); // Required for this to work in FireFox
        element.click();
      })
      .catch(function (error) {
        console.log(error);
      });
  };

The final solution looks something like this: