Setting up Google Calendar integration to work with Scheduler Messages in React UI Kit

This guide explains how you can set up a seamless integration between Google Calendar and scheduler messages of the CometChat UI Kit.
For this guide, we will be using the React UI Kit. But this can be achieved using any of the CometChat UI Kits

Environment

  1. CometChat React UI Kit
  2. Node JS
  3. MongoDB

Steps for the integration

1. Set up Google APIs

The first step in the integration is to create a Google Project and Enable the Google Calendar APIs for the project
To achieve this, please follow the below steps:

  1. Create a Project:
  • Go to the Google Cloud Console.
  • Login to the console.
  • Create a new project or select an existing one.

As you can see from the screenshot, we have created a project called Calendar Integration. We will be using this project for the course of this guide

  1. Enable Google Calendar API:
  • Navigate to APIs & Services > Library.
  • Search for “Google Calendar API” and enable it.

  1. Set Up OAuth Consent Screen:
  • Go to APIs & Services > OAuth consent screen.
  • Configure the consent screen by filling in the required fields.
  • Select the type of configuration Internal or External based on how you plan to test the APIs
  • Fill in the basic app information and click on Save and Continue
  • Select the necessary scopes that you wish to use for the project. You can search for Calendar and enable the required scopes

Please note: For the purpose of this guide, we will need permission to read/edit calendar events

In case you have selected the External configuration, You will need to share the emails of the users who will be testing this

  1. Set up the credentials:
  • Navigate to the credentials section in the Google Cloud Console
  • Click on the Create Credentials option to create a fresh set of credentials

  • Select OAuth Client ID from the list of options
  • Select the Application type. In this guide, we will select Web Application.
  • Fill in the details of your app and click Create
  • We are actually going to use above-generated credentials in the React client and on the server side

2. Google Integration in React

Once the configuration on the Google Console is done, the next step is to set up the Google Sign-In and Authorisation in the React App.

  • Please add the following script inside the head tag in index.html
<script src="https://accounts.google.com/gsi/client" async defer></script>
  1. Add the following code snippet where CometChatLogin is done.
declare global {
  interface Window {
    google: any;
  }
}
  • Gather the Client ID from the Google Cloud Dashboard and save it to a Constants File in the React Project

  • The Next step is to ask the user to allow permissions for our Google app to access the user’s calendar.

  • On successful login by the user into your app, you need to call the following function

  async function handleGoogle() {
    var SCOPES = "https://www.googleapis.com/auth/calendar.events";
    const client = window.google.accounts.oauth2.initCodeClient({
      client_id: "CLIENT_ID",
      scope: SCOPES,
      ux_mode: "popup",
      redirect_uri:"REDIRECT_URL",
      callback: async (response: any) => {
        try {
          if (!response.code) {
              throw new Error('No code received from Google OAuth');
          }

          console.log('Response from Google:', response);

          const res = await fetch("URL", {
              method: "POST",
              headers: { "Content-Type": "application/json" },
              body: JSON.stringify({ code: response.code })
          });

          if (!res.ok) {
              const errorText = await res.text();
              throw new Error(`Server responded with ${res.status}: ${res.statusText} - ${errorText}`);
          }

          const data = await res.json();
          console.log("Success:", data);
      } catch (error) {
          console.error('Error during the fetch operation:', error);
      }
      },
    });
    client.requestCode();
  }

Please make sure to replace the CLIENT_ID with the CLIENT_ID from the web credentials created in the Google Cloud Dashboard

The above code will ask the user to Log In to their Google account and also ask the user to allow our Google App to access his/her calendar.

Screencast from 16-06-24 07_26_41 PM IST

3. CometChat React UI Kit Set up

The next step is to set up the CometChat UI Kit in your React project.

  1. Install the following dependencies to the React project.
    "@cometchat/calls-sdk-javascript": "^4.0.9",
    "@cometchat/chat-sdk-javascript": "^4.0.5",
    "@cometchat/chat-uikit-react": "^4.3.7",
    "@cometchat/uikit-elements": "^4.3.10",
    "@cometchat/uikit-resources": "^4.3.8",
    "@cometchat/uikit-shared": "^4.3.10",

Now, use the following code snippet to Initialize CometChat UI Kit

import {  CometChatUIKit, UIKitSettingsBuilder,} from "@cometchat/chat-uikit-react";
const COMETCHAT_CONSTANTS = {
  APP_ID: "YOUR_APP_ID",
  REGION: "YOUR_REGION",
  AUTH_KEY: "YOUR_AUTH_KEY",
};

const UIKitSettings = new UIKitSettingsBuilder()
  .setAppId(COMETCHAT_CONSTANTS.APP_ID)
  .setRegion(COMETCHAT_CONSTANTS.REGION)
  .setAuthKey(COMETCHAT_CONSTANTS.AUTH_KEY)
  .subscribePresenceForAllUsers()
  .setAutoEstablishSocketConnection(true)
  .build();

CometChatUIKit.init(UIKitSettings)?.then(() => {
  console.log("Initialised Cometchat");
});

The next step is to Login a user in order to use CometChat, it’s done using the following code snippet

import { CometChatUIKit } from "@cometchat/chat-uikit-react";
const UID = "UID"; //Replace with your UID
CometChatUIKit.getLoggedinUser().then((user) => {
  if (!user) {
    //Login user
    CometChatUIKit.login(UID)
      .then((user) => {
        console.log("Login Successful:", { user });
        //mount your app
      })
      .catch(console.log);
  } else {
    //mount your app
  }
});

For the purpose of the this guide we are going to use the CometChatConversationsWithMessages component.

  1. Customising the UI Kit to add an option to schedule meetings
  • To achieve this, we will add a button to the AttachOptions available named Schedule Meeting
const getAttachmentOptions = (item: User | Group, id: ComposerId) => {
    let defaultAttachmentOptions: CometChatMessageComposerAction[] =
      CometChatUIKit.getDataSource().getAttachmentOptions(
        new CometChatTheme({}),
        id
      );
    const scheduleMeet = new CometChatMessageComposerAction({
      title: "Schedule a meeting",
      id: "schedule_meet",
      iconURL: SchedulerIcon,
      onClick: () => scheduleMeeting(groupDetails,loggedInUser),
      iconTint: "gray",
      titleColor: "gray",
    });
    defaultAttachmentOptions.push(scheduleMeet);
    return defaultAttachmentOptions;
  };
<CometChatConversationsWithMessages
    messagesConfiguration={
      new MessagesConfiguration({
        messageComposerConfiguration: new MessageComposerConfiguration({
           attachmentOptions: (item, composerid) => getAttachmentOptions(item, composerid),
           attachmentIconURL: AttachmentIcon,
         })
      })
   }
 />

  1. Your own function scheduleMeeting to add new event into Calendar
  const scheduleMeeting = async (item: CometChat.Group | null,user:CometChat.User) => {
    try {
      const response = await fetch(
        "YOUR_END_POINT",
        {
          method: "POST",
          headers: {
            "content-type": "application/json",
            accept: "application/json",
          },
          body: JSON.stringify({
            groupDetails: groupDetails,
            user:user
          }),
        }
      );
      if (!response.ok) {
        alert("not ok");
        throw new Error("Network response was not ok.");
      }
      const data = await response.json();
      console.log("Server response:", data);
    } catch (error) {
      console.error("Error sending data:", error);
    }
  };

4. NodeJS and Database Set up

For the purpose of this guide, we will be using the below dependencies. Please add the below to your package.json file and run the npm install command


“axios”: “^1.6.3",
“body-parser”: “^1.20.2",
“express”: “^4.18.2",
“form-data”: “^4.0.0",
“googleapis”: “^137.1.0",
“ics”: “^3.7.2",

    1. In the index.js, please add the below routes:

app.post(“/users/:uid/code”, GCalController.generateAccesstoken)

app.get(“/users/:uid/schedule”, GCalController.requestSchedule)

app.get(“/users/:uid/calendar/basic.ics”, GCalController.getICSData)

app.post(“/users/:uid/calendar/events”, GCalController.createEvent)

1. POST /users/:uid/code

  • This endpoint is set up to take the code received in the React app after successfully gaining access to the user’s calendar and generating the necessary tokens for that user.

2. GET /users/:uid/schedule

  • This endpoint, generates a scheduler message and sends it to the conversation on behalf of the receiver of the scheduling request.
    For example:
    User A navigates to the chat window of User B and clicks on the Schedule Meeting option.
    The above API will be triggered, and it will send a scheduler message with the availability of User B into the conversation on Behalf of User B.

3. GET /users/:uid/calendar/basic.ics

  • This endpoint is responsible to fetch the ics file with the events for the users calendar so that the scheduler message can display the time-slots as per the user’s availability

4. POST /users/:uid/calendar/events

  • This endpoint is responsible for adding the event to the user’s calendar and setting up the meeting

5. Linking the React app and the APIs

    1. As soon as the user authorizes the app using the OAuth Consent Screen in the React app, a code is generated for the user. This code needs to be sent to your server.
      In our case, we will be using the POST request on /users/:uid/code, and share the code in the body of the request.
    1. Once this code is received on the server, we will generate an access_token and a refresh_token for the user and save it against the user in the database.
    1. For the same, we will be using the googleapis package and the code for the same is below
const { google } = require(‘googleapis’);

const oauth2Client = new google.auth.OAuth2(
    “YOUR_CLIENT_ID”,
    “YOUR_CLIENT_SECRET”
);

exports.generateAccesstoken = async (req, res) => {
  try {
    const { uid } = req.params;
    const { code, email, user } = req.body;

    const { tokens } = await oauth2Client.getToken(code);
    oauth2Client.setCredentials(tokens);

    const query = { uid };
    const update = {
      $set: {
        access_token: tokens.access_token,
        refresh_token: tokens.refresh_token,
        email,
        user
      }
    };
    const options = { upsert: true };

    await db.get().collection(‘gcal_users’).updateOne(query, update, options);
    
    res.status(200).json(“Success”);
  } catch (error) {
    console.error(error);
    res.status(500).json(“Internal Server Error”);
  }
};


  1. In the React App, when user A clicks on the Schedule Meeting option with User B, we trigger the /GET request on /users/:uid/schedule.
    This API will generate a CometChat Scheduler message and send it into the conversation on behalf of User 2.
    The code snippet for the same is as follows:
const axios = require(‘axios’);
const constants = require(‘./constants’); // Assuming constants is imported from another file

exports.requestSchedule = async (req, res) => {
  const { uid } = req.params;
  const { requester: requesterUID } = req.body;

  try {
    const requestedFor = await db.get().collection(‘gcal_users’).findOne({ uid });
    const requester = await db.get().collection(‘gcal_users’).findOne({ uid: requesterUID });

    if (!requestedFor || !requester) {
      return res.status(404).json(“User not found”);
    }

    await sendSchedulerMessage(requestedFor, requester);
    res.status(200).json(“Success”);
  } catch (error) {
    console.error(error);
    res.status(500).json(“Internal Server Error”);
  }
};

async function sendSchedulerMessage(requestedFor, requester) {
  const data = JSON.stringify({
    receiver: requester.uid,
    receiverType: “user”,
    category: “interactive”,
    type: “scheduler”,
    data: {
      receiverType: “user”,
      receiver: requester.uid,
      muid: “16Jan3:41423PM”,
      interactionGoal: { type: “anyAction” },
      allowSenderInteraction: false,
      interactiveData: {
        title: `Schedule with ${requestedFor.name}`,
        avatarUrl: requestedFor.avatar,
        bufferTime: 15,
        icsFileUrl: `http://adityagokula.com/users/${requestedFor.uid}/calendar/basic.ics`,
        availability: {
          friday: [{ to: “2100", from: “1159” }],
          monday: [{ to: “2100", from: “1159” }],
          tuesday: [{ to: “2100", from: “1159” }, { to: “2100", from: “1159” }],
          thursday: [{ to: “2100", from: “1159” }],
          wednesday: [{ to: “2100", from: “1159” }]
        },
        timezoneCode: “Asia/Kolkata”,
        duration: 30,
        scheduleElement: {
          action: {
            url: `http://adityagokula.com/users/${requestedFor.uid}/calendar/events`,
            actionType: “apiAction”,
            method: “POST”,
            headers: {
              accept: “application/json”,
              “content-type”: “application/json”
            },
            payload: { requester: requester.uid }
          },
          elementId: “1",
          buttonText: “Schedule”,
          elementType: “button”,
          disableAfterInteracted: true
        },
        goalCompletionText: “Your meeting has been Scheduled”,
        dateRangeStart: “2024-06-13",
        dateRangeEnd: “2024-06-25”
      }
    }
  });

  const config = {
    method: ‘post’,
    maxBodyLength: Infinity,
    url: `https://${constants.SCHEDULER_APP_ID}.api-${constants.SCHEDULER_REGION}.cometchat.io/v3.0/messages`,
    headers: {
      apiKey: constants.SCHEDULER_API_KEY,
      onBehalfOf: requestedFor.uid,
      “Content-Type”: “application/json”,
      Accept: “application/json”
    },
    data
  };

  try {
    const response = await axios.request(config);
    console.log(JSON.stringify(response.data));
  } catch (error) {
    console.error(error);
  }
}


Screencast from 16-06-24 08_06_28 PM IST (1)

  1. The scheduler message payload, includes the link to access the calendar events of the User B in the ics format. The URL is the API mentioned where we set up a GET request on the /users/:uid/calendar/basic.ics.
    This API uses the googleapis and the ics package to list the calendar events and convert the same to ics format.
    The code for the same is as follows:

const { google } = require(‘googleapis’);
const { createEvents } = require(‘ics’);
const oauth2Client = new google.auth.OAuth2();

exports.getICSData = async (req, res) => {
  const { uid } = req.params;

  try {
    // Fetch user from database
    const user = await db.get().collection(‘gcal_users’).findOne({ uid });

    // Handle user not found
    if (!user) {
      return res.status(404).json({ error: ‘User not found’ });
    }

    // Set OAuth2 credentials
    oauth2Client.setCredentials({
      access_token: user.access_token,
      refresh_token: user.refresh_token,
    });

    // List events from Google Calendar
    const eventsJSON = await listEvents(oauth2Client);

    // Map events to ICS format
    const events = eventsJSON.map(event => ({
      title: event.summary || ‘’,
      description: event.description || ‘’,
      location: event.location || ‘’,
      start: dateArray(event.start.dateTime),
      end: dateArray(event.end.dateTime),
    }));

    // Create ICS file
    createEvents(events, (error, value) => {
      if (error) {
        console.error(error);
        return res.status(500).json({ error: ‘Failed to create events’ });
      }
      res.status(200).json(value);
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({ error: ‘Internal Server Error’ });
  }
};

// Helper function to list events
async function listEvents(auth) {
  const calendar = google.calendar({ version: ‘v3’, auth });
  const res = await calendar.events.list({
    calendarId: ‘primary’,
    timeMin: new Date().toISOString(),
    maxResults: 10,
    singleEvents: true,
    orderBy: ‘startTime’,
  });
  return res.data.items;
}

// Helper function to convert dateTime to date array
function dateArray(dateTime) {
  const date = new Date(dateTime);
  return [
    date.getFullYear(),
    date.getMonth() + 1,
    date.getDate(),
    date.getHours(),
    date.getMinutes(),
    date.getSeconds()
  ];
}

This API returns the ics content for the user’s calendar events. this is used by the CometChat UI Kit along with the availability set in the Schedule Message payload. For more information on the on the parameters of the Interactive Messages payload, You can check out our documentation

  1. The Scheduler Message payload also lets you set up the API to be triggered when the Schedule button is clicked on the front-end. We have set the button to trigger the POST request on the /users/:uid/calendar/events.
    This API takes the information of the interaction on the scheduler bubble and updates the User B’s calendar with the meeting information.
    You can find the code snippet for the same below:
async function updateCalendar(requestedFor, requester, startTime, duration) {
    const calendar = google.calendar({ version: ‘v3’, auth: oauth2Client });

    const event = {
        summary: ‘CometChat Test’,
        location: ‘123 Main St, Anytown, USA’,
        description: ‘This is a sample event created via Google Calendar API’,
        start: {
            dateTime: startTime, // Adjust as needed
            timeZone: ‘Asia/Kolkata’,        // Adjust as needed
        },
        end: {
            dateTime: new Date(new Date(startTime).getTime() + duration * 60 * 1000),
            timeZone: ‘Asia/Kolkata’,        // Adjust as needed
        },
        attendees: [
            { email: requestedFor.email },
            { email: requester.email }
        ],
        reminders: {
            useDefault: false,
            overrides: [
                { method: ‘email’, minutes: 24 * 60 }, // 24 hours before event
                { method: ‘popup’, minutes: 10 }       // 10 minutes before event
            ]
        }
    };

    try {
        const response = await calendar.events.insert({
            calendarId: ‘primary’,
            resource: event
        });

        console.log(‘Event created:’, response.data);
    } catch (error) {
        console.error(‘Error creating event:’, error);
        throw error; // Propagate error to caller for better error handling
    }
}

This will schedule the meeting and add the events to the calendars of both users.