Учебное пособие по приложению Firebase Chat | 2023

В сообщении блога мы рассмотрим процесс создания приложения чата с использованием Firebase.

Чтобы просмотреть полное приложение, нажмите здесь: Учебное пособие по приложению Firebase Chat

Что такое Firebase?

Firebase — это тип бессерверной серверной части, предоставляемой Google. Он предлагает набор услуг для использования в качестве серверной части мобильных или веб-приложений.

Он предлагает такие услуги, как база данных NoSQL, социальная аутентификация, уведомления и услуги связи в реальном времени.

В следующем руководстве мы будем использовать базу данных Firebase в реальном времени в качестве серверной части для нашего чат-сервиса.

Итак, приступим.

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

Чтобы разрешить этот учебник, вам потребуется:

  1. Базовые знания HTML и CSS
  2. Знаком с JavaScript

Шаг 1. Зарегистрируйте учетную запись Firebase

Если у вас еще нет учетной записи Firebase, перейдите на страницу firebase.google.com и зарегистрируйте бесплатную учетную запись Firebase.

Нажмите кнопку «Начать», чтобы создать бесплатную учетную запись.

Назовите свой проект как угодно, мы назовем наш проект «firebase-chatapp».

Шаг 2: Настройка базы данных Firestore

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

После создания проекта с помощью боковой панели выберите «База данных Firestore».

Затем нажмите кнопку «Создать базу данных»

Выберите вариант «Начать в рабочем режиме», и мы вручную настроим ACL в соответствии с нашим чатом.

Новинка в DeadSimpleChat? Это готовый чат, который вы можете легко добавить на свой веб-сайт или в приложение — без сложного кода. Chat API и приложение SDK SaaS, социальная платформа, образование, игры и финансы Зарегистрируйтесь бесплатно

Шаг 3. Разрешение анонимной аутентификации

Для нашего чата мы хотим, чтобы пользователи просто вводили имя пользователя и присоединялись к чату, для этого нам потребуется «анонимная» аутентификация.

Чтобы включить это, мы выберем опцию «Аутентификация» на боковой панели.

Нажмите кнопку «Начать» на странице аутентификации.

Теперь выберите опцию «Анонимно».

И включите его и сохраните

Теперь вернитесь в «База данных Firestore», выберите «Правила» и обновите существующее правило до следующего:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

Это позволит аутентифицированным пользователям читать и записывать в базу данных firebase.

Шаг 4. Получение конфигурации Firebase

Конфигурация firebase необходима для подключения нашего приложения чата к серверу firebase. Чтобы получить конфиг, перейдите в настройки проекта.

Щелкните значок, чтобы сгенерировать учетные данные для веб-приложения.

Назовите приложение как угодно

Выберите использовать тег «‹script›» и скопируйте конфиг.

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

Шаг 4. Создание исходного пользовательского интерфейса чата

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

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

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Firebase Chat</title>
    <script src="https://cdn.tailwindcss.com"></script>
  </head>
  <body>
    <script type="module" src="main.js"></script>

    <div
      id="joinView"
      class="bg-gray-100 w-96 items-center mx-auto flex flex-col mt-10 rounded-md p-4 space-y-4"
    >
      <h1 class="text-center font-bold">Join Chat</h1>
      <div class="flex space-x-1">
        <input
          id="usernameInput"
          type="text"
          class="bg-white border border-gray-200 rounded-md px-2"
          placeholder="Enter your name"
        />
        <button
          id="joinButton"
          class="bg-indigo-600 px-2 py-1 text-sm text-white rounded-md"
        >
          Join
        </button>
      </div>
    </div>

    <div id="chatsView" class="w-64 h-96 mx-auto flex flex-col mt-10 hidden">
      <div class="messages flex-1 bg-gray-100">
        <ul id="messageList">
          <li>
            <div class="flex space-x-2 pl-2 pt-2">
              <div class="flex flex-col">
                <div class="flex items-baseline space-x-2">
                  <div class="text-sm font-bold">Tommy Lee</div>
                  <div class="text-sm text-gray-400">5:20 pm</div>
                </div>
                <div class="text-sm text-gray-500">Hello world</div>
              </div>
            </div>
          </li>

          <li>
            <div class="flex space-x-2 pl-2 pt-2">
              <div class="flex flex-col">
                <div class="flex items-baseline space-x-2">
                  <div class="text-sm font-bold">Tommy Lee</div>
                  <div class="text-sm text-gray-400">5:21 pm</div>
                </div>
                <div class="text-sm text-gray-500">Testing</div>
              </div>
            </div>
          </li>

          <li>
            <div class="flex space-x-2 pl-2 pt-2">
              <div class="flex flex-col">
                <div class="flex items-baseline space-x-2">
                  <div class="text-sm font-bold">James Bond</div>
                  <div class="text-sm text-gray-400">5:21 pm</div>
                </div>
                <div class="text-sm text-gray-500">Good Job</div>
              </div>
            </div>
          </li>
        </ul>
      </div>
      <div class="input h-11 w-full flex border border-slate-200 rounded-md">
        <input id="messageInput" type="text" class="outline-none flex-1 px-1" />
        <div class="p-1">
          <button id="sendButton" class="bg-indigo-600 p-2 rounded-lg">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              fill="white"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="black"
              class="w-4 h-4"
            >
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"
              />
            </svg>
          </button>
        </div>
      </div>
    </div>
  </body>
</html>

Мы будем использовать Tailwind CSS для оформления пользовательского интерфейса, приведенный выше код содержит наш базовый пользовательский интерфейс.

Мы дали идентификатор joinView контейнеру, содержащему текстовое поле для ввода имени пользователя и кнопку присоединения.

Присваиваем id chatsViews основному контейнеру чата

Когда пользователь не присоединился к комнате чата, мы скроем контейнер chatsView, а когда пользователь присоединится к комнате чата, мы скроем контейнер joinView и покажем контейнер chatsView.

Мы также создали тег ul и присвоили ему идентификатор messageList, он будет содержать все сообщения, отправляемые в чате.

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

Шаг 5: Подключение пользовательского интерфейса

Теперь давайте подключим пользовательский интерфейс с помощью JavaScript и Firebase SDK, создадим main.js

Импорт Firebase SDK

import { initializeApp } from "https://www.gstatic.com/firebasejs/9.15.0/firebase-app.js";

// Add Firebase products that you want to use
import {
  getAuth,
  signInAnonymously,
} from "https://www.gstatic.com/firebasejs/9.15.0/firebase-auth.js";
import {
  getFirestore,
  addDoc,
  collection,
  onSnapshot,
  doc,
  getDocs,
  query,
  where,
} from "https://www.gstatic.com/firebasejs/9.15.0/firebase-firestore.js";

Вставьте конфигурацию firebase, полученную на шаге 3.

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "AIzaSyCRa0JplErns226xER0Fpk1cEulP2c6y0Q",
  authDomain: "fir-chat-a082b.firebaseapp.com",
  databaseURL: "https://fir-chat-a082b-default-rtdb.firebaseio.com",
  projectId: "fir-chat-a082b",
  storageBucket: "fir-chat-a082b.appspot.com",
  messagingSenderId: "589407773218",

  appId: "1:589407773218:web:676cf16926d1dee647607b",
};

Мы будем вызывать метод Firebase initializeApp и передавать ему файл firebaseConfig.

const app = initializeApp(firebaseConfig);

Получите экземпляр базы данных Firestore и Auth. Мы будем использовать базу данных Firestore для отправки сообщений и получения обновлений в реальном времени о сообщениях.

const db = getFirestore(app);

const auth = getAuth(app);

Теперь мы будем обращаться к элементам HTML в нашем пользовательском интерфейсе, чтобы прослушивать события, а также отображать и скрывать элементы пользовательского интерфейса.

const joinButton = document.getElementById("joinButton");
const usernameInput = document.getElementById("usernameInput");
const messageInput = document.getElementById("messageInput");
const sendButton = document.getElementById("sendButton");
const joinView = document.getElementById("joinView");
const chatsView = document.getElementById("chatsView");

Обработка присоединения

Мы добавим прослушиватель событий щелчка к кнопке «Присоединиться». При нажатии кнопки присоединения мы проверим, есть ли какое-либо значение в поле usernameInput.

Если пользователь ввел имя пользователя в поле usernameInput, мы вызовем метод signInAnonymously Firebase SDK, чтобы войти в чат.

Без вызова этого метода мы не можем читать или записывать в базу данных Firestore.

После успешного входа мы скроем joinView и покажем chatsView.

let specifiedUsername = "";
let userLoggedIn = false;
joinButton.addEventListener("click", () => {
  specifiedUsername = usernameInput.value;
  if (!specifiedUsername) {
    alert("username cannot be empty");
    return;
  }

  signInAnonymously(auth)
    .then(async () => {
      joinView.classList.add("hidden");
      chatsView.classList.remove("hidden");
      userLoggedIn = true;
      await loadHistoricalMessages();
      await subscribeToNewMessages();
      writeMessagesArray();
      console.log("User logged-in");
    })
    .catch((error) => {
      const errorCode = error.code;
      const errorMessage = error.message;

      console.log(errorCode, errorMessage);
    });
});

Загрузка предыдущих сообщений

В предыдущем фрагменте кода мы зарегистрировали пользователя в чате.

И, как вы можете видеть в фрагменте кода, мы также вызываем метод loadHistoricalMessages(), поэтому мы реализуем этот метод в этом разделе.

В методе loadHistoricalMessages() мы будем запрашивать базу данных Firestore, чтобы получить все предыдущие сообщения, и будем хранить их в глобальном массиве messages.

Чтобы получить исторические сообщения, мы вызовем метод getDocs:

async function loadHistoricalMessages() {
  messages = [];
  const querySnapshot = await getDocs(collection(db, "messages"));
  querySnapshot.forEach((doc) => {
    messages.push({
      id: doc.id,
      ...doc.data(),
    });
  });
  console.log(messages);
  return messages;
}

Прослушивание новых сообщений

Чтобы прослушать новые сообщения, мы вызовем метод onSnapshot SDK Firestore.

Метод onSnapShot принимает запрос, так как мы хотим прослушать всю коллекцию, мы не будем указывать никаких условий в запросе.

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

И когда сработает метод onSnapShot, мы отправим новые сообщения в наш глобальный массив messages.

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

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

Чтобы предотвратить это, мы создадим хеш-карту идентификаторов всех существующих сообщений, а затем пройдемся по newMessages, и мы не будем отправлять сообщения, идентификатор которых соответствует существующим сообщениям.

function subscribeToNewMessages() {
  const q = query(collection(db, "messages"));
  const unsubscribe = onSnapshot(q, (querySnapshot) => {
    const newMessages = [];
    querySnapshot.forEach((doc) => {
      newMessages.push({
        id: doc.id,
        ...doc.data(),
      });
    });

    /**
     * Creating hash map of the existing messages.
     */
    let existingMessageHash = {};
    for (let message of messages) {
      existingMessageHash[message.id] = true;
    }

    /**
     * Push only those messages which do not 
     * exist in the hashMap
     */
    for (let message of newMessages) {
      if (!existingMessageHash[message.id]) {
        messages.push(message);
      }
    }
    
    writeMessagesArray();
  });
}

Мы создали hashMap идентификатора сообщения, чтобы предотвратить создание вложенного цикла for.

Отображение сообщений в пользовательском интерфейсе

Мы вызывали метод writeMessagesArray() в методе signInAnonymously, а также в слушателе subscribeToNewMessages.

Метод writeMessagesArray() будет отображать сообщения в массиве сообщений в нашем пользовательском интерфейсе чата.

function writeMessagesArray() {
  const html = [];
  for (let message of messages) {
    html.push(messageTemplate(message.message, message.user, message.created));
  }
  document.getElementById("messageList").innerHTML = html.join("");
}

function messageTemplate(message, username, timestamp) {
  return `<li>
    <div class="flex space-x-2 pl-2 pt-2">
      <div class="flex flex-col">
        <div class="flex items-baseline space-x-2">
          <div class="text-sm font-bold">${username}</div>
          <div class="text-sm text-gray-400">${
            new Date(timestamp.seconds * 1000).toLocaleDateString() +
            " " +
            new Date(timestamp.seconds * 1000).toLocaleTimeString()
          }</div>
        </div>
        <div class="text-sm text-gray-500">${message}</div>
      </div>
    </div>
  </li>`;
}

Метод просматривает каждое сообщение в массиве сообщений и вызывает метод messageTemplate.

Методы messageTemplate содержат HTML-код для отображения сообщения, принимают имя пользователя, сообщение и отметку времени и возвращают HTML-код сообщения.

Мы помещаем HTML в массив и добавляем HTML к тегу messageList ul.

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

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

Теперь давайте реализуем часть фактической отправки сообщения.

Чтобы отправить сообщение, мы добавим прослушиватель кликов в файл sendButton. При нажатии на sendButton мы вызовем метод addDoc базы данных Firestore и сохраним сообщение в базе данных Firestore.

Наша модель сообщения будет содержать следующие свойства:

  • user – имя пользователя
  • message – настоящие сообщения
  • created – отметка времени создания сообщения
sendButton.addEventListener("click", async () => {
  const message = messageInput.value;
  messageInput.value = "";

  const docRef = await addDoc(collection(db, "messages"), {
    user: specifiedUsername,
    message: message,
    created: new Date(),
  });
  console.log(docRef);
});

Вот полный код файла main.js.

import { initializeApp } from "https://www.gstatic.com/firebasejs/9.15.0/firebase-app.js";

// Add Firebase products that you want to use
import {
  getAuth,
  signInAnonymously,
} from "https://www.gstatic.com/firebasejs/9.15.0/firebase-auth.js";
import {
  getFirestore,
  addDoc,
  collection,
  onSnapshot,
  doc,
  getDocs,
  query,
  where,
} from "https://www.gstatic.com/firebasejs/9.15.0/firebase-firestore.js";

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "AIzaSyCRa0JplErns226xER0Fpk1cEulP2c6y0Q",
  authDomain: "fir-chat-a082b.firebaseapp.com",
  databaseURL: "https://fir-chat-a082b-default-rtdb.firebaseio.com",
  projectId: "fir-chat-a082b",
  storageBucket: "fir-chat-a082b.appspot.com",
  messagingSenderId: "589407773218",

  appId: "1:589407773218:web:676cf16926d1dee647607b",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

const db = getFirestore(app);

const auth = getAuth(app);

const joinButton = document.getElementById("joinButton");
const usernameInput = document.getElementById("usernameInput");
const messageInput = document.getElementById("messageInput");
const sendButton = document.getElementById("sendButton");
const joinView = document.getElementById("joinView");
const chatsView = document.getElementById("chatsView");
let messages = [];

let specifiedUsername = "";
let userLoggedIn = false;
joinButton.addEventListener("click", () => {
  specifiedUsername = usernameInput.value;
  if (!specifiedUsername) {
    alert("username cannot be empty");
    return;
  }

  signInAnonymously(auth)
    .then(async () => {
      joinView.classList.add("hidden");
      chatsView.classList.remove("hidden");
      userLoggedIn = true;
      await loadHistoricalMessages();
      await subscribeToNewMessages();
      writeMessagesArray();
      console.log("User logged-in");
    })
    .catch((error) => {
      const errorCode = error.code;
      const errorMessage = error.message;

      console.log(errorCode, errorMessage);
    });
});

sendButton.addEventListener("click", async () => {
  const message = messageInput.value;
  messageInput.value = "";

  const docRef = await addDoc(collection(db, "messages"), {
    user: specifiedUsername,
    message: message,
    created: new Date(),
  });
  console.log(docRef);
});

function subscribeToNewMessages() {
  const q = query(collection(db, "messages"));
  const unsubscribe = onSnapshot(q, (querySnapshot) => {
    const newMessages = [];
    querySnapshot.forEach((doc) => {
      newMessages.push({
        id: doc.id,
        ...doc.data(),
      });
    });

    /**
     * Creating hash map of the existing messages.
     */
    let existingMessageHash = {};
    for (let message of messages) {
      existingMessageHash[message.id] = true;
    }

    /**
     * Push only those messages which do not
     * exist in the hashMap
     */
    for (let message of newMessages) {
      if (!existingMessageHash[message.id]) {
        messages.push(message);
      }
    }

    writeMessagesArray();
  });
}

async function loadHistoricalMessages() {
  messages = [];
  const querySnapshot = await getDocs(collection(db, "messages"));
  querySnapshot.forEach((doc) => {
    messages.push({
      id: doc.id,
      ...doc.data(),
    });
  });
  console.log(messages);
  return messages;
}

function writeMessagesArray() {
  const html = [];
  for (let message of messages) {
    html.push(messageTemplate(message.message, message.user, message.created));
  }
  document.getElementById("messageList").innerHTML = html.join("");
}

function messageTemplate(message, username, timestamp) {
  return `<li>
    <div class="flex space-x-2 pl-2 pt-2">
      <div class="flex flex-col">
        <div class="flex items-baseline space-x-2">
          <div class="text-sm font-bold">${username}</div>
          <div class="text-sm text-gray-400">${
            new Date(timestamp.seconds * 1000).toLocaleDateString() +
            " " +
            new Date(timestamp.seconds * 1000).toLocaleTimeString()
          }</div>
        </div>
        <div class="text-sm text-gray-500">${message}</div>
      </div>
    </div>
  </li>`;
}

Шаг 6: Вот и все

Мы создали простое приложение для группового чата, используя базу данных Firebase и Firestore.

Вы можете перейти на панель инструментов Firebase FireStore и увидеть сообщение, отправленное в коллекции.

Добавьте чат в свое веб-приложение с помощью Dead Simple Chat

Dead Simple Chat Chat API и SDK для вашего веб-приложения или мобильного приложения.

Он легко масштабируется и может быть интегрирован за считанные минуты. Он имеет Chat SDK, функции настройки и модерации, чтобы сделать чат пригодным для любого варианта использования чата, будь то прямая трансляция, групповой чат или чат 1–1.