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
- CometChat React UI Kit
- Node JS
- 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:
- 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
- Enable Google Calendar API:
- Navigate to
APIs & Services > Library
. - Search for “Google Calendar API” and enable it.
- 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
orExternal
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
- 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>
- 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.
3. CometChat React UI Kit Set up
The next step is to set up the CometChat UI Kit in your React project.
- 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.
- 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,
})
})
}
/>
- 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",
-
- 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
-
- 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.
- 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.
-
- Once this code is received on the server, we will generate an
access_token
and arefresh_token
for the user and save it against the user in the database.
- Once this code is received on the server, we will generate an
-
- For the same, we will be using the
googleapis
package and the code for the same is below
- For the same, we will be using the
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”);
}
};
- 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);
}
}
- 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
- 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.