Добавляем CometChat в приложение Ruby on Rails

Нужен чат для веб-приложения? Читайте пошаговую инструкцию для добавления CometChat Pro в проект. Примеры на Javascript и Ruby on Rails.

cometchat

Не всем веб-приложениям нужен чат, но если хотите общаться с клиентами или реализовать их общение друг с другом, быстро столкнётесь с этой проблемой. CometChat Pro – фантастическая альтернатива созданию собственного чата. Дальше увидите, как легко добавить чат в приложение, не создавая помех другому контенту.

Предварительные условия

Этот туториал не требует больших знаний. Фреймворк Ruby on Rails прост в освоении. Если работали с другими веб-технологиями, вероятно, сочтёте его понятным.

CometChat Pro сам по себе работает в Javascript, поэтому увидите объединение фреймворка на основе Ruby и управления представлением с помощью Javascript. Это решается и более сложными способами в Rails. В туториале используется базовый Javascript и стандартный шаблон Rails: ERB. Поклонники Rails смогут легко кастомизировать эти базовые инструменты под свои нужды.

Предполагается, что вы запускаете приложения на Rails – значит, у вас уже установлены Ruby и bundler. Это приложение использует Ruby 2.5.3 и Rails 5.2.2, но совместимо с любой современной версией Rails.

Почему CometChat

Сложность добавления чата связана с параллелизмом – пользователи должны видеть новые сообщения, не обновляя браузеры. Как убедитесь, с CometChat Pro это достигается с минимумом кода.

cometchat

Если пытались использовать Action Cable для достижения аналогичных результатов, вы приятно удивитесь тому, как легко получить обновления в реальном времени в браузере.

Если хотите, перейдите сразу к коду на Github или попробуйте этого парня сами. Начнём!

Инициализация

Откройте терминал и запустите новое приложение Rails: rails new cometchatpro --skip-active-record -T. Для этого не нужна база данных, а -T не даёт Rails создавать тестовые файлы. Если у вас уже есть приложение, пропустите этот шаг – легко вставить код в действующий проект.

Переменные среды и учётные данные CometChat

Далее понадобится ключ API и идентификатор приложения от CometChat Pro. После входа в систему перейдите на панель инструментов, создайте новое приложение, а затем сгенерируйте ключ API. Нужен fullAccess, а не только authOnly. Сохраните эти учётные данные и добавьте в файл .env. Файл будет выглядеть примерно так:

COMETCHAT_APP_ID=23n2f2n3p2y3
COMETCHAT_API_KEY=av22g24ll

Затем добавьте dotenv-rails в Gemfile и bundle. Теперь приложение читает переменные среды. Это личные учётные данные для аккаунта CometChat Pro. Не добавляйте этот файл в систему контроля версий!

Страницы чата

Далее добавим пару шаблонных представлений Rails и маршрутизацию, чтобы общаться как пользователь с другими пользователями. Создадим новый контроллер: rails generate controller Users. Это сгенерирует файлы, которые вскоре рассмотрим. Также добавьте маршрут к новому представлению в config/routes.rb. Вот как выглядит route.rb:

Rails.application.routes.draw do
  resources :users, only: %i[index show]
  root 'users#index'
end

Если перейдёте к коду GitHub или к размещённому приложению, увидите, что добавлено создание пользователя. Функция осталась, потому что уже написана, но CometChat Pro поставляется с шаблонными пользователями, включёнными в каждую учётную запись. Так что тестирование доступно сразу же, без необходимости сначала создавать пользователя. В этом уроке не будем говорить о создании пользователя для краткости, но необходимый код найдёте в репозитории Github.

Также создадим файл /views/users/show.html.erb. Наполним его содержимым через минуту.

Сервис CometChat

Проникнем в сердце нашего приложения – взаимодействие с CometChat API. Организуем взаимодействие с CometChat Pro через сервис. Вот как это выглядит:

class CometChatService
  include HTTParty
  BASE_URI = 'https://api.cometchat.com/v1'.freeze
  def fetch_users
    response = HTTParty.get("#{BASE_URI}/users", headers: headers)
    response.dig('data')
      &.map { |user| {name: user['name'], id: user['uid']} }
  end
  private
  def headers
    {
      apikey: ENV['COMETCHAT_API_KEY'],
      appid: ENV['COMETCHAT_APP_ID']
    }
  end
end

CometChat Pro делает много вещей, и по мере развития нашего приложения этот сервис наполняется дальнейшими взаимодействиями с API. Здесь ограничимся получением списка пользователей для общения. Обратите внимание, что работа с CCP не требует gem. Отправляйте запросы так, как нравится (здесь использовали HTTParty) с соответствующими заголовками (из нашего файла .env). И CCP вернёт запрошенную информацию.

Преобразуем JSON-ответ от CCP в информацию, которая нужна приложению – имя пользователя и идентификатор пользователя.

Работа с сервисом CometChat в контроллере

Теперь, когда установили протокол общения с CCP API, будем использовать его в нашем UsersController. Выглядит так:

class UsersController < ApplicationController
  def index
    @users = CometChatService.new.fetch_users
  end
  def show
    users = CometChatService.new.fetch_users
    @current_user = users.find { |user| user[:id] == params[:id] }
    @users = users.reject { |user| user[:id] == @user[:id] }
  end
end

Пропустим страницу index для краткости, это список пользователей, от лица которых будем общаться. Вероятно, посчитаете её самой небезопасной, нелепой страницей входа в систему. В обоих случаях выбираем пользователей, зарегистрированных в нашем приложении CometChat Pro. Поскольку уже преобразовали JSON-ответ в сервисе, на страницах index и show перебираем пользователей для отображения. На странице show общаемся в чате как пользователь, поэтому не хотим, чтобы он появлялся в списке возможных людей для общения (строка 9).

cometchat

Представление для отображения пользователя

Пройдёмся по разделам пользовательской страницы по очереди. Через минуту обсудим Javascript, необходимый для запуска. Также пропустим обсуждение стиля. Полный код для стилей ищите в репозитории GitHub, или, конечно, реализуйте собственный (получше) стиль.

Кто говорит?

Чтобы сделать информацию о нашем «вошедшем в систему» ​​пользователе (тот, чью страницу show показываем) доступной для службы CometChat, добавим невидимый div с именем пользователя и идентификатором. Разместите это где угодно на странице:

<%= tag("div", id: 'user-id', data: { name: @user[:name], id: @user[:id] }) %>

Список пользователей

Чтобы отобразить список пользователей, как показано выше, используем код ERB:

<ul class="list-group list-group-flush">
  <% @users.each do |user| %>
    <li class="list-group-item user-select bg-light" id="<%= user[:id] %>" name='user-select'>
      <div class="d-flex w-100 justify-content-between">
        <h5 class="mb-1"><%= user[:name] %></h5>
      </div>
    </li>
  <% end %>
</ul>

Здесь ничего специфического не происходит. Отметить стоит только то, что устанавливаем идентификатор пользователя как идентификатор элемента списка. Позже понадобится указать, с каким пользователем разговариваем. Помните, что наши @users пришли из контроллера, в котором собран список пользователей из сервиса CometChat.

Получение и отображение сообщений

Далее посмотрим, как будем отображать входящие сообщения. Наш код начинается так:

<div id="messages" class="messages">
    Загрузка сообщений...
</div>

Предоставим div, в который CometChat Pro будет загружать сообщения. Назовём messages или как угодно.

Отправка сообщений

Используем хелперы форм Ruby для создания формы сообщения, но не даём Ruby обрабатывать функциональности здесь:

<div class="row col-sm-8">
  <%= form_for :message, remote: true do |f| %>
     <div class="col-xs-9">
       Message : <%= f.text_area :text, class: "form-control" %><br/>
     </div>
     <div class="col-xs-3 capitalize">
       <%= button_tag "Send Message", :type => 'button', :onclick => 
"javascript:sendMessage()", class: "btn btn-info btn-block" %>
     </div>
  <% end %>

Когда нажимаем кнопку «Отправить сообщение», будем творить чудеса с использованием Javascript. Форма Rails просто с нетерпением ждёт ввод.

Сердце нашего приложения: методы Javascript

Сервис CometChat Pro работает на Javascript. Будем использовать их чистый пример кода, чтобы заставить части нашего приложения работать. Предпримем следующие шаги:

  1. Инициализация – настроим соединение с сервисом CometChat.
  2. Войдём в систему от текущего пользователя.
  3. Выберем пользователя для чата и получим историю сообщений с этим человеком.
  4. Добавим слушателя событий, чтобы получать новые сообщения от другого человека.
  5. Настроим действие для отправки сообщений.

Инициализация CometChat

Во-первых, наладим общение нашего приложения со службой CometChat Pro и войдём в систему. В application.html.erb вставьте эту строку между тегами <head>:

<script type="text/javascript" src="https://unpkg.com/@cometchat-pro/chat/CometChat.js">
</script>

Добавьте эти строки внизу /views/users/show.html.erb:

<% javascript_include_tag 'show', :cache => 'myfiles' %>
<script type="text/javascript">
    setUserListeners();
    document.addEventListener('turbolinks:load', initializeChat);
</script>

Рассмотрим метод setUserListeners через секунду. Наконец, ссылаемся на show.js.erb, который создаём и добавляем в app/assets/javascripts.

Вот наш первый метод Javascript:

const initializeChat = () => {
    CometChat.init('').then(
      hasInitialized => {
        loginUser()
      },
      error => {
        console.log("Initialization failed with error:", error);
      }
  )};

Поскольку загрузили CometChat Pro Javascript в наш application.html.erb, теперь используем методы CometChat в файле Javascript. Первым используем .init, который требует отправки идентификатора приложения в качестве учётных данных. Вызываем метод loginUser после инициализации нашего чата.

Вход пользователя в систему CometChat

После инициализации войдём в систему как текущий пользователь. Это приложение не включает никакой безопасности – каждый общается от имени любого другого. Очевидно, вы захотите сделать чуть иначе в производственном приложении. Вот как происходит вход в систему нашего пользователя на CometChat Pro:

const loginUser = () => {
    const userDiv = document.getElementById('user-id');
    if (!userDiv) { return true }
    const id = userDiv.dataset.id;
    CometChat.login(id, '').then(
        User => {
          const messageDiv = document.getElementById('messages');
          messageDiv.innerHTML = `<div class="whisper">Choose a  user to start chatting</div>`;
        },
        error => {
            console.log("Login failed with exception:", {error});
        })
}

Помните тот странный tag("div"), который содержал информацию о наших пользователях из show.html.erb? Здесь используем его, чтобы узнать, что нужно пользователю для входа в систему. Если с этим div что-то пошло не так, отказываемся от операции.

Далее вызываем следующий метод CometChat: .login. Берём id, полученный из div пользователя, и используем наши учётные данные API. Если CometChat Pro возвращает User, обновляем div сообщения с текстом: «Выберите пользователя, чтобы начать общаться».

setUserListeners

Вернёмся к странице show.html.erb, где вызывали setUserListeners(). Чтобы узнать, с кем хотим пообщаться, добавим слушатель кликов в списке пользователей. Вот как выглядит этот код:

const setUserListeners = () => {
    const userDivs = document.getElementsByName('user-select');
    userDivs.forEach(user => user.addEventListener("click", e => {
        const id = e.target.id || e.target.offsetParent.id
        setUser(id);
        fetchMessages(id);
    }))
}

userDivs – список узлов div, по одному для каждого из пользователей, доступных для общения. Перебираем их и добавляем слушатель событий для каждого. По клику на пользователя сначала вызываем setUser, чтобы сообщить приложению, с кем хотим поговорить, а затем fetchMessages для этого пользователя.

setUser и addMessageListener

Здесь не обошлось без магии, и CometChat Pro позаботился о тяжёлой работе за нас. Как только наш пользователь входит в систему и выбирает кого-нибудь для разговора, ожидается отображение новых сообщений от этого человека в реальном времени. С CometChat Pro не беспокоимся о том, чтобы держать открытыми веб-сокеты или заниматься обновлением – сообщения выглядят так, как ожидаем.

Метод setUser связан только с тем, чтобы выделить активного пользователя синим цветом в списке, поэтому пропустим его здесь. Но он также вызывает класс addMessageListener, который важен для получения новых сообщений по мере их поступления:

const addMessageListener = id => {
    CometChat.addMessageListener(
        'listener_id',
        new CometChat.MessageListener({
            onTextMessageReceived: textMessage => displayNewMessage(id, textMessage)
        })
)}

Это взято прямо из документации CometChat Pro. Вызываем метод CometChat addMessageListener, а затем используем входящие сообщения для обновления нашего div сообщения. Запустите два разных браузера, чтобы увидеть это в действии (или посмотрите gif выше).

displayNewMessage

Когда приходит новое сообщение, обновляем представление. Вот код:

const displayNewMessage = (currentChatterId, msg) => {
    const userDiv = document.getElementById('user-id');
    const id = userDiv.dataset.id;
    if (![currentChatterId, id].includes(msg.sender.uid)) { return; }
    const newNode = document.createElement("div")
    newNode.innerHTML = newMessage(msg, id)
    const messageDiv = document.getElementById('messages')
    messageDiv.appendChild(newNode)
    messageDiv.scrollTop = messageDiv.scrollHeight
}

Во-первых, игнорируем входящие сообщения, которые не принадлежат вошедшему пользователю и собеседнику. Затем обрабатываем новое сообщение – создаём новый div с сообщением, добавляем его в div messages, и снова прокручиваем до конца для отображения.

newMessage

Наш метод newMessage форматирует сообщение – это новый пузырь в нашем чат-приложении. Проверяем соответствие идентификатора отправителя и вошедшего пользователя, чтобы применить класс self к div – делаем его розовым для «себя» и синим для остальных.

const newMessage = (msg, id) => {
    return (
        `<div class='message ${msg.sender.uid === id && 'self'}'>
          <div class='message-text'>${msg.text}</div>
          <div class='message-sender'>- ${msg.sender.name}</div>
        </div>`
    )
}

fetchMessages

Прежде чем начать общение, просмотрим полученные раньше сообщения и всё, чем обменивались, когда не разговаривали с этим конкретным пользователем. Наш метод fetchMessages будет перехватывать сообщения между вошедшим пользователем и выбранным пользователем.

const fetchMessages = id => {
    if (!id) { return; }
    const limit = 30;
    const messagesRequest = new 
CometChat.MessagesRequestBuilder().setUID(id).setLimit(limit).build();
    messagesRequest.fetchPrevious().then(
        messages => {
            const messageDiv = document.getElementById('messages');
            messageDiv.innerHTML = messages.length > 0 ?
              messages.map(msg => newMessage(msg, id)).join('') :
              `<div class="whisper">Start of message history</div>`;
            messageDiv.scrollTop = messageDiv.scrollHeight
        },
        error => {
            console.log("Message fetching failed with error:", error);
        }
    );
}

Если нет id, делаем return, чтобы не получать ошибок. Сначала создаём запрос, устанавливая идентификатор пользователя, с которым хотим поговорить, и желаемый лимит сообщений. В приложениях посложнее захотим автоматически получать более ранние результаты по мере прокрутки пользователем.

Как только настроили запросчик сообщений, вызываем fetchPrevious, чтобы получить список сообщений. Он возвращает messages, которые используются для заполнения нашего div messages. Каждое сообщение представляет собой объект JSON с sender.uid, sender.name и text. Преобразуем сообщения и возвращаем их как объекты HTML. Заменяем содержимое messages на новый список узлов сообщений. Не забудьте .join(''), или увидите кучу запятых между div. Если сообщений не было, покажем пользователю, что это «начало истории сообщений».

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

Отправка новых сообщений в CometChat

Входим в систему, выбираем пользователя для чата, извлекаем старую историю сообщений между этими двумя. И получаем новые сообщения, отправленные этим пользователем. Наконец, хотим тоже отправлять сообщения. Вот как это делаем:

const sendMessage = () => {
    const recipient_id = document.getElementsByClassName('bg-info')[0].id;
    const message_text = document.getElementsByName('message[text]')[0].value;
    document.getElementsByName('message[text]')[0].value = ''
    const messageType = CometChat.MESSAGE_TYPE.TEXT;
    const receiverType = CometChat.RECEIVER_TYPE.USER;
    const textMessage = new CometChat.TextMessage(recipient_id, message_text, 
messageType, receiverType);
    CometChat.sendMessage(textMessage).then(
        message => displayNewMessage(recipient_id, message),
        error => {
            console.log("Message sending failed with error:", error);
        }
    );
}

Сначала определяем, с каким пользователем общаемся, исходя из выделенного пользователя в списке. Затем читаем сообщение из формы и очищаем форму, чтобы подготовить к следующему сообщению.

Объект textMessage будет вызывать метод CCP, который конструирует сообщение для отправки на основе вошедшего пользователя, идентификатора получателя, текста сообщения, а также типов сообщения и получателя. Затем отправляем сообщение с объектом textMessage, ожидаем ответа на message и обрабатываем его так же, как и входящее сообщение от другого пользователя.

И это, друзья, всё, что нужно для добавления чата в наше приложение Rails!

Заключение

Теперь у вас есть целиком функциональное приложение-чат для проекта. Начните изучать другие доступные функциональности CometChat Pro или рефакторить с jQuery, Slim или другими инструментами, если захотите сократить код. Надеемся, на примере простого Javascript стало ясно, чего добиваемся, даже если захочется кода большей эффективности в собственном приложении.

Желаем удачи, добавьте чат в собственное приложение и получайте удовольствие!

Ссылки

А вы уже работали с CometChat?

МЕРОПРИЯТИЯ

Комментарии

ВАКАНСИИ

Добавить вакансию
Продуктовый аналитик
Екатеринбург, по итогам собеседования
Продуктовый аналитик в поддержку
по итогам собеседования
Golang разработчик (middle)
от 230000 RUB до 300000 RUB

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ