twiliotwilio-api

Twilio Conversations API Realtime chat


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?


Solution

  • How can we implement?

    1. How client server connects with twilio [Client server with twilio server artitecture ][1]
    2. Steps you have to follow
    - 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:

    1. React: For designing the UI.
    2. Nodejs: For backend APIs used in your projects.

    Packages Used:

    1. Twilio
    2. Twilio-Conversation