Как создать приложение для обмена сообщениями с Twilio Chat & React JS

Давайте воспользуемся Twilio Chat API, чтобы создать многопользовательское приложение для обмена сообщениями.

В этом посте вы узнаете, как реализовать Twilio Conversation API для создания базового приложения для чата. Вы можете использовать эти методы для создания приложения, похожего на Slack

Что вам нужно:

  • Бесплатная пробная версия Twilio
  • API чата Twilio
  • Знание ReactJS для начинающих
  • NodeJS (и предпочтительное управление пакетами, я буду использовать пряжу)

Давайте начнем

Во-первых, давайте настроим наше приложение с помощью create-react-app.

Идите вперед и создайте каталог вашего проекта. Обычно я захожу в папку, которая у меня есть для всех моих проектов, нахожу конкретную папку, которая относится к тому, над чем я работаю, поэтому для этого примера я буду в моей папке React, где я храню все проекты, созданные с помощью React.

например Рабочий стол / Проекты / React / reactjs-chat

Затем я создам свою папку, щелкаю правой кнопкой мыши и открываю ее в Code.

Вы также можете открыть терминал mkdir dirNam e и запустить .code

Но мне нравится использовать встроенный терминал на VSCode.

Теперь выполните следующие команды:

npx create-react-app frontend
cd frontend
yarn add axios react-router-dom twilio-chat @material-ui/core

или npm install в зависимости от настроек вашего диспетчера пакетов

Настройка бэкэнда

Интерфейс в основном настроен, давайте продолжим и настроим наш сервер.

Вернемся к основному каталогу.

cd ..

Находясь внутри responsejs-chat (или в вашем основном каталоге), давайте запустим следующее:

git clone https://github.com/TwilioDevEd/sdk-starter-node.git
cd sdk-starter-node
yarn install 
yarn add cors

По сути, мы хотим клонировать репозиторий, созданный Twilio, чтобы легко начать работу со своими услугами, это дает нам доступ к таким вещам, как обработка уведомлений и генерация токенов. Для этого нам понадобится генерация токенов.

Затем перейдите к app.js на VSCode и найдите строку:

const app = express();

Скопируйте следующий код и вставьте его прямо под строкой выше.

const cors = require('cors');
app.use(cors());

Это расшифровывается как Совместное использование ресурсов из разных источников.

Это позволяет серверу указывать любое другое происхождение, кроме своего собственного, из которого браузер должен разрешать загрузку ресурсов. Итак, он нам нужен, в основном, для доступа к внешнему API.

Ключи Twilio

Если у вас еще нет учетной записи Twilio, перейдите на бесплатную пробную версию.

Внутри вашего каталога sdk-start-node вы должны увидеть файл с именем .env.example, давайте переименуем его в .env

В нем будут предварительно заполнены все ключи, которые вам понадобятся.

Первый из них находится на панели управления учетной записью, здесь вы увидите ACCOUNT SID, скопируйте его в TWILIO_ACCOUNT_SID в файле .env.

Далее вам понадобится ключ API для аутентификации. На боковой панели выберите Настройки ›Ключи API и нажмите Создать ключ API.

Скопируйте SID и SECRET и вставьте их как значения для TWILIO_API_KEY и TWILIO_API_SECRET в свой .env

На данный момент у нас есть общие настройки учетной записи. Нам нужно пойти сюда

для создания чата. Вам нужно будет нажать кнопку с плюсом и дать название своей службе.

Скопируйте SERVICE SID для своей службы и вставьте его в качестве значения переменной среды TWILIO_CHAT_SERVICE_SID.

Теперь мы можем запустить в каталоге sdk-starter-node следующее:

yarn start

Сервер будет запущен с localhost:3000 и будет выглядеть так:

«Вход» и экран чата

Наш бэкэнд готов, так что давайте сделаем наше приложение функциональным и красивым.

Я создал очень краткий и базовый интерфейс для приложения, вы можете стилизовать свой как хотите.

Идея состоит в том, чтобы сначала запросить у пользователя электронное письмо, а затем позволить пользователю выбрать чат-комнату.

Давайте вернемся в каталог внешнего интерфейса, найдем ваш index.js и поместим компонент ‹App /› внутрь маршрутизатора.

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
  document.getElementById('root')
);
​

И, конечно же, не забудьте импортировать роутер.

import { BrowserRouter as Router } from 'react-router-dom'

Это все, что вам нужно сделать с index.js, закройте его.

Перейдите в App.js и удалите все, что находится между основным контейнером с className of App. После удаления содержимого заверните его в маршрут и переключитесь.

Ниже я заключил свой блок App в маршрут по умолчанию и добавил маршрут, который буду использовать для экрана чата.

return (
    <Switch>
      <Route path="/:room" component={Chat} />
      <Route path="/">
        <div className="App">
        
      	</div>
      </Route>
    </Switch>
  );

Ваш импорт должен выглядеть так:

import './App.css';
import React, { useState } from "react";
import { Route, Switch, useHistory } from "react-router-dom"; 
import Chat from './components/Chat';

Мы будем использовать пользовательские хуки для обслуживания состояния.

В каталоге src создайте папку с именем components. Затем создайте файл с именем Chat.js и сразу введите _rfce. (Вам понадобится этот плагин, иначе просто введите свою функцию)

function Chat() {
    
    return (
        <div className="chatScreen">
            
        </div>
    )
}
export default Chat

Хорошо, давайте вернемся к App.js и получим электронную почту пользователей.

Вот мой макет:

return (
    <Switch>
      <Route path="/:room" component={Chat} />
      <Route path="/">
        <div className="container">
        <main className="main">
          <h1 className="title">
            Welcome
          </h1>
          <section className="loginSection">
            <input type="email" onChange={(e)=>updateEmail(e.target.value)} placeholder="Email" />
            {
              emailWarning&&
              <span className="warning">You need to enter your email.</span>
            }
            <button onClick={login}>Continue</button>
          </section>
          
        </main>
      </div>
      </Route>
    </Switch>
  );

Я обновляю переменную электронной почты с помощью настраиваемой ловушки; мы увидим через секунду. Сначала давайте стилизуем эти элементы:

.container {
  min-height: 100vh;
  padding: 0 0.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.main {
  padding: 5rem 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  max-height: 100vh;
  overflow: hidden;
}
.loginSection{
  margin-top: 20px;
  height: 70vh;
  width: 70vw;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.loginSection input{
  margin-top: 30px;
  width: 500px;
  padding: 10px;
  border: 2px solid transparent;
  outline: none;
  border-radius: 10px;
}
.loginSection input:focus{
  border: 2px solid grey;
}
.loginSection button{
  margin-top: 30px;
  width: 500px;
  padding: 15px;
  background-color: rgba(74, 146, 74, 0.4);
  border: 2px solid rgba(103, 175, 103, 0.4);
  outline: none;
  color: white;
  border-radius: 10px;
  cursor: pointer;
}
.loginSection button:hover{
  background-color: rgb(74, 146, 74);
  border: 2px solid rgb(103, 175, 103);
}
.warning{
  color: rgb(211, 63, 63);
}

Когда все заработает, у нас будет очень простой экран приветствия:

Теперь давайте создадим крючки. Я также сохраняю электронное письмо в localStorage, и мы будем использовать хук useHistory в react-router-dom, чтобы переместить нашу страницу в экран чата.

Когда пользователь вводит, каждое изменение будет выполняться updateEmail(), это запускает наш настраиваемый обработчик: setEmail(). Он также устанавливает для параметра emailWarning значение false на тот случай, если оно было изменено на true, если пользователь нажимает «Продолжить» без предварительного ввода адреса электронной почты.

Здесь нет проверки адреса электронной почты, но вы можете легко реализовать это, если хотите.

const [email, setEmail] = useState('')
  const [emailWarning, setEmailWarning] = useState(false);
  
  let history = useHistory();
  function login() {
    if (email) {
      localStorage.setItem('email', email);
      history.push("chat");
    }
    else if(!email){
      setEmailWarning(true)
    }
  }
  const updateEmail = (e) => {
    setEmail(e)
    setEmailWarning(false)
  }

Получив адрес электронной почты пользователя, мы перемещаем наш домен на экран чата, так что App.js готов, давайте создадим экран чата.

Экран чата

Во-первых, вот наш импорт

import { useHistory } from "react-router-dom"; 
import axios from "axios";
import ChatItem from "./ChatItem";
import React, { useRef } from "react";
import { useEffect, useState } from "react";
const ChatAPI = require("twilio-chat");

Мы будем использовать axios, чтобы получить токен из нашего собственного сервера, и useRef, чтобы получить доступ к содержимому чата и прокрутить его вниз.

Идите вперед и создайте ChatItem.js в каталоге компонентов.

Вот мой ответ на Chat.js:

return (
        <div className="chatScreen">
            <div className="sidebar">
                <h4>{email}</h4>
                <h2>Rooms</h2>
                {
                    roomsList.map((room) =>(
                        <p key={room} onClick={()=>changeRoom(room)}>	{room}</p>
                    ))
                }
            </div>
            <div className="chatContainer" ref={scrollDiv}>
                <div className="chatHeader">
                    {room === "chat" ? "Choose A Room" : room}
                </div>
                <div className="chatContents">
                {(messages && room !== "chat") &&
                messages.map((message) => 
                  <ChatItem
                    key={message.index}
                    message={message}
                    email={email}/>
                )}
                </div>
                {
                    room !== "chat" &&
                <div className="chatFooter">
                    <input type="text" placeholder="Type Message" onChange={(e)=>updateText(e.target.value)} value={text} />
                    <button onClick={sendMessage} >Send</button>
                </div>
                }
            </div>
        </div>
    )

У меня есть боковая панель, чтобы все комнаты оставались сбоку, как у Slack. Щелчок по одному из них приведет к перенаправлению на динамический URI для каждой комнаты. Контейнер чата имеет раздел заголовка, в котором отображается название комнаты, в которой вы находитесь, а внизу находится поле ввода.

Идите вперед и определите все константы и таможенные хуки:

const email = localStorage.getItem('email');
    const room = window.location.pathname.split('/')[1];
    const [loading, setLoading] = useState(false);
    const [messages, setMessages] = useState([]);
    const [channel, setChannel] = useState(null);
    const [text, setText] = useState("");
        
    const roomsList = ["general"];
    let scrollDiv = useRef(null);

Мы собираем название комнаты из динамического пути и разделяем косую черту. Электронное письмо находится в localStorage, поэтому оттуда у нас есть легкий доступ к нему.

Затем мы создаем хуки для загрузки, список сообщений, канал (созданный Twilio) и текст (пользовательский ввод). Список комнат можно использовать для локальных целей, но вы также можете сохранить его в базе данных для более динамичного подхода к созданию большего количества комнат.

Последняя переменная scrollDiv создаст ссылку, которую мы связали с div chatContainer.

Каждая из наших комнат на боковой панели имеет событие onClick для изменения комнаты, функциональность проста:

let history = useHistory();
const changeRoom = room => history.push(room);

Давайте также обновим ввод пользователя onChange:

const updateText = e => setText(e);

Это самые простые лайнеры. Действительно важная часть - получить токен:

const getToken = async (email) => {
        const response = await axios.get(`http://localhost:3000/token/${email}`);
        const { data } = response;
        return data.token;
      }

Это получит доступ к нашему backend api, который в основном предоставляется Twilio, и вернет токен.

Далее мы построим остальные функции. joinChannel примет канал, который мы создаем после загрузки страницы, мы добавим крючок useEffect всего за секунду.

На протяжении всего нашего JavaScript мы будем видеть слушателей для канала, например messageAdded. Они предоставляются Twilio. Ниже у нас также есть handleMessageAdded, который запускается на ранее упомянутом слушателе и принимает возвращенные сообщения, привязанные к вашей текущей комнате, и добавляет их в наш массив сообщений.

Затем мы создаем нашу sendMessage функцию, которая вызывается, когда пользователь нажимает кнопку «Отправить».

const joinChannel = async (channel) => {
        if (channel.channelState.status !== "joined") {
         await channel.join();
       }
     
       setChannel(channel);
       setLoading(false)
     
       channel.on('messageAdded', function(message) {
        handleMessageAdded(message)
      });
    	scrollToBottom();
     };
const handleMessageAdded = message => {
        setMessages(messages =>[...messages, message]);
        scrollToBottom();
      };
      
      const scrollToBottom = () => {
        const scrollHeight = scrollDiv.current.scrollHeight;
        const height = scrollDiv.current.clientHeight;
        const maxScrollTop = scrollHeight - height;
        scrollDiv.current.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
      };
      const sendMessage = () => {
        if (text) {
            console.log(String(text).trim())
            setLoading(true)
            channel.sendMessage(String(text).trim());
            setText('');
            setLoading(false)
        }
      };

Последняя часть нашего Chat.js - это хук useEffect, который запускает создание канала, когда страница загружается:

useEffect(async() => {
        let token = "";
        if (!email) {
            history.push("/");
        }
        setLoading(true)
        try {
          token = await getToken(email);
        } catch {
          throw new Error("Unable to get token, please reload this page");
        }
        const client = await ChatAPI.Client.create(token);
        client.on("tokenAboutToExpire", async () => {
            const token = await getToken(email);
            client.updateToken(token);
        });
        client.on("tokenExpired", async () => {
            const token = await getToken(email);
            client.updateToken(token);
        });
        client.on("channelJoined", async (channel) => {
            const newMessages = await channel.getMessages();
            console.log(newMessages)
            setMessages(newMessages.items || []);
            scrollToBottom();
          });
        
          try {
            const channel = await client.getChannelByUniqueName(room);
              console.log(channel)
              joinChannel(channel);
              setChannel(channel)
          } catch(err) {
            try {
              const channel = await client.createChannel({
                uniqueName: room,
                friendlyName: room,
              });
          
              joinChannel(channel);
            } catch {
              throw new Error("Unable to create channel, please reload this page");
            }
          } 

    }, [])

Сначала мы убеждаемся, что пришло электронное письмо, в противном случае мы отправим пользователя обратно на страницу приветствия. Затем мы используем электронную почту, чтобы получить токен, который мы используем для создания клиента ChatAPI. Затем клиент используется для создания канала, в котором хранится тонна информации, например объекты сообщений, прикрепленные к указанному каналу или комнате. Объекты сообщения содержат собственную дату создания, автора и т. Д.

В нашем JSX мы передаем объекты сообщений в наш компонент ChatItem, и оттуда мы можем использовать информацию, хранящуюся в каждом объекте, для создания пузырей чата.

Давайте поговорим о ChatItem.js.

Наш импорт:

import React from "react";
import { ListItem } from "@material-ui/core";

Вот несколько стилей, которые можно использовать. Мы добавим их в файл реакции вместо файла css, чтобы мы могли легко создавать динамические стили для ответа пользователя и входящего ответа без создания нескольких классов. Любой вариант допустим.

const styles = {
  listItem: (userMsg) => ({
    flexDirection: "column",
    alignItems: userMsg ? "flex-end" : "flex-start",
  }),
  container: (userMsg) => ({
    maxWidth: "75%",
    borderRadius: 10,
    padding: 10,
    color: "white",
    fontSize: 12,
    backgroundColor: userMsg ? "#F36E65" : "#9ea1a8",
  }),
  author: { fontSize: 10, color: "gray" },
  timestamp: { fontSize: 8, color: "white", textAlign: "right", paddingTop: 5 },
};

Нам нужно всего несколько констант, мы получаем их из props:

function ChatItem(props) {
  
    const message = props.message;
    const email = props.email;
    const userMsg = message.author === email;

И, наконец, JSX:

return (
      <ListItem style={styles.listItem(userMsg)}>
        <div style={styles.author}>{message.author}</div>
        <div style={styles.container(userMsg)}>
          {message.body}
          <div>
            {new Date(message.dateCreated.toISOString()).toLocaleString()}
          </div>
        </div>
      </ListItem>
    );

На этом этапе вы сможете запустить yarn start и войти в систему, указав свой адрес электронной почты.

Вывод

Я рассмотрел все основы, чтобы реализовать API Twilio и получить работающее приложение для обмена сообщениями. Я надеюсь, что это было полезно, и вы всегда можете проверить документацию для получения более подробной информации https://www.twilio.com/docs/chat.

Проект Репо