basically I just want a simple chat integration by using twilio conversations api. With the docs I already set up a server and I created a conversation and added 2 participants to the conversation itself by the mentioned api endpoints. Its also obvious to me how to create a message looking at the docs. Im wondering how I can refresh update the chat message for participant B if participant a is sending a message.
Do I need to configure some kind of websockets
or is this not needed for the conversations api? How would i notify the participant b and display the new message in real time?
And if I need websockets
, why should I need twilio conversations api then?
How can we implement?
- generateToken.js
//require twilio package
const twilio = require('twilio');
//destructure keys from package
const { AccessToken } = twilio.jwt;
const { ChatGrant } = AccessToken;
function generateTwilioAccessToken(identity) {
// Your Twilio Account SID, API Key SID, and API Key Secret
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const apiKeySid = process.env.TWILIO_API_SID;
const apiKeySecret = process.env.TWILIO_API_SECRET;
// Create a new access token
const accessToken = new AccessToken(accountSid, apiKeySid, apiKeySecret, {
identity,
});
// Add a Chat Grant to the token (assuming you have a Conversations Service SID)
accessToken.addGrant(
new ChatGrant({
serviceSid: process.env.TWILIO_CONVERSATIONS_SERVICE_SID,
})
);
// Return the access token as a JWT
return accessToken.toJwt();
}
module.exports = { generateTwilioAccessToken };
- api.js
// Example how you can use this function using your endpoint
async getTwilioToken(req) {
try {
// get userId as query from frontend
const { userId } = req.query;
// check does user exists
const userData = await Users.findOne({ where: { id: userId } });
// if exist then gerate token with unique identity as its id
let twilioAccessToken;
if (userData) {
// generate twilio access token
twilioAccessToken = generateTwilioAccessToken(userData.dataValues.id);
}
return {
status: HttpStatusCode.success.SUCCESS,
message: 'Twilio token',
data: twilioAccessToken,
};
} catch (error) {
throw error;
}
}
- twilioClient.js
import { Client as ConversationsClient } from '@twilio/conversations';
import { FETCH_TWILIO_ACCESS_TOKEN } from 'services';
// chat Error message in nothing but your toaster function.
import { chatErrorMsg } from './helper';
import axios from 'axios';
export const initializeTwilioConversations = async (userId) => {
try {
// Fetch a new Twilio access token first
const fetchTwilioToken = async () => {
try {
const response = await axios.get(
`${FETCH_TWILIO_ACCESS_TOKEN}?userId=${userId}`,
);
if (response?.data?.statusCode === 200) {
return response?.data?.data;
}
} catch (error) {
chatErrorMsg('Error fetching Twilio access token');
}
};
// This token contain user unqiue id i.e. id.
const token = await fetchTwilioToken();
if (!token) {
chatErrorMsg('Error fetching Twilio access token');
}
// Initialize the ConversationsClient with the retrieved token
let conversationsClientInstance = new ConversationsClient(token);
return conversationsClientInstance;
} catch (error) {
chatErrorMsg('Error initializing Twilio Conversations');
}
};
Chat.js
// Defines few state first
const [connectionState, setConnectionState] = useState({
loading: true,
connectionStatus: null,
});
const [conversationsClient, setConversationsClient] = useState(null);
const [conversationsList, setConversationsList] = useState([]);
const [messages, setMessages] = useState([]);
// initialize conversation
const conversationInitialization = useCallback(async () => {
try {
// Initialize the Conversations Client with the access token and gets the Client Instance
const client = await initializeTwilioConversations(loggedInUserId);
// Listen for connection state changes :
// Here, we get the connection status from twilio server and store in state
client.on('connectionStateChanged', async (state) => {
if (state === 'connecting') {
setConnectionState((state) => ({
...state,
connectionStatus: 'Connecting to chat...',
}));
}
if (state === 'connected') {
const conversations = await client.getSubscribedConversations();
setConnectionState({
loading: false,
connectionStatus: 'You are connected',
});
setConversationsList(conversations?.items);
}
if (state === 'disconnecting') {
setConnectionState((state) => ({
loading: false,
connectionStatus: 'Disconnecting from chat...',
}));
}
if (state === 'denied') {
setConnectionState((state) => ({
loading: false,
connectionStatus: 'Failed to connect',
}));
}
});
// Event Triggers : When any user joined conversation , any message came for this user these events triggers and we can perform our operations on that.
client.on('conversationJoined', (conversation) => {
setConversationsList((prev) => [...prev, conversation]);
});
client.on('messageAdded', (message) => {
setMessages((prevMessages) => [...prevMessages, message]);
});
client.on('conversationLeft', (thisConversation) => {
// eslint-disable-next-line no-console
console.log('conversation left', thisConversation);
});
client.on('tokenAboutToExpire', async () => {
// Fetch a new token when the existing token is about to expire
await initializeTwilioConversations();
});
setConversationsClient(client);
} catch (error) {
chatErrorMsg('Error initializing Twilio Conversations');
}
}, []);
useEffect(() => {
conversationInitialization();
return () => {
// Cleanup function to remove event listeners when the component unmounts
if (conversationsClient) {
conversationsClient.removeAllListeners();
}
};
}, []);
helper.js
// Function to initialize the participant if not subscribed
//By chance if user unable to register after loggedIn we can register it from here also.
const initializeParticipantIfNotSubscribed = async (
client,
participantIdentities
) => {
const identities = Array.isArray(participantIdentities)
? participantIdentities
: [participantIdentities];
let delayValue = 0;
// Check if any of the participants are not subscribed and initialize if needed
for (const participantIdentity of identities) {
const subscribedUsers = await client.getSubscribedUsers();
const isParticipantSubscribed = subscribedUsers.some(
(user) => user?.state?.identity === participantIdentity
);
if (!isParticipantSubscribed) {
await initializeTwilioConversations(participantIdentity);
}
delayValue = delayValue + 1000;
}
await delay(delayValue);
};
// Function to create or join a conversation
export const createOrJoinConversation = async (
client,
participantIdentities,
friendlyName,
uniqueName,
attributes = {}
) => {
try {
// get list of conversation in which user is part of.
const conversations = await client.getSubscribedConversations();
const existingConversation = conversations?.items.find(
(conv) => conv?.friendlyName === friendlyName
);
if (existingConversation) {
return existingConversation; // Return the existing conversation
}
// Check if participant is subscribed and initialize if not
await initializeParticipantIfNotSubscribed(client, participantIdentities);
// Create a new conversation
const newConversation = await client.createConversation({
friendlyName: friendlyName,
uniqueName: uniqueName,
attributes: {
...attributes,
},
});
// Join the current user to the new conversation
await newConversation.join();
// Add participants to the conversation (handle both single and multiple participants)
const participantsToAdd = Array.isArray(participantIdentities)
? participantIdentities
: [participantIdentities];
for (const participantIdentity of participantsToAdd) {
try {
// Attempt to add the participant to the conversation
await newConversation.add(participantIdentity);
} catch (participantError) {
// Handle errors when adding a participant
chatErrorMsg(`Error adding participant ${participantIdentity}`);
}
}
return newConversation;
} catch (error) {
chatErrorMsg(`Error creating or joining conversation`);
return null;
}
};
const onSelectMember = async (member) => {
const senderId = loggedInUserId;
const receiverId = member?.userId;
const friendlyName = `one-to-one-${senderId}--${receiverId}`;
const uniqueName = `${senderId}--${receiverId}`;
const participantIdentity = member?.userId;
const attributes = {
senderId: senderId,
receiverId: receiverId,
conversationType: 'ONE_TO_ONE',
};
await createOrJoinConversation(
conversationsClient,
participantIdentity,
friendlyName,
uniqueName,
attributes
);
};
Prerequisites:
Packages Used: