Реализация Real-Time чата поддержки на сайте

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

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация Real-Time чата поддержки на сайте
Средняя
~5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Реализация Real-Time чата поддержки на сайте

Чат поддержки позволяет пользователям общаться с операторами в реальном времени. Состоит из клиентского виджета, интерфейса оператора и серверной логики маршрутизации.

Архитектура

Пользователь (виджет)          Оператор (admin UI)
       │                              │
       └─── WebSocket ──► Чат-сервер ◄───────────┘
                             │
                         PostgreSQL (история)
                         Redis (активные сессии)

Серверная логика

import { Server } from 'socket.io';

// Очередь неотвеченных чатов
const waitingQueue: Map<string, ChatSession> = new Map();
const activeSessions: Map<string, ChatSession> = new Map();

io.on('connection', async (socket) => {
  const { role } = socket.data;  // 'user' или 'operator'

  if (role === 'user') {
    handleUserConnection(socket);
  } else if (role === 'operator') {
    handleOperatorConnection(socket);
  }
});

async function handleUserConnection(socket: Socket) {
  const userId = socket.data.userId;

  // Создать или восстановить сессию
  let session = await sessionRepo.findActiveByUser(userId);
  if (!session) {
    session = await sessionRepo.create({
      userId,
      status: 'waiting',
      startedAt: new Date()
    });
  }

  socket.join(`session:${session.id}`);

  if (session.status === 'waiting') {
    waitingQueue.set(session.id, session);
    socket.emit('queue:position', {
      position: waitingQueue.size,
      estimatedWait: waitingQueue.size * 2  // мин
    });
    io.to('operators').emit('queue:updated', { count: waitingQueue.size });
  }

  socket.on('message:send', async ({ content }) => {
    const message = await messageRepo.create({
      sessionId: session.id,
      senderId: userId,
      senderRole: 'user',
      content,
      sentAt: new Date()
    });

    io.to(`session:${session.id}`).emit('message:new', message);
  });

  socket.on('disconnect', () => {
    io.to(`session:${session.id}`).emit('user:offline', { userId });
  });
}

async function handleOperatorConnection(socket: Socket) {
  socket.join('operators');

  // Оператор принимает чат из очереди
  socket.on('session:accept', async ({ sessionId }) => {
    const session = waitingQueue.get(sessionId);
    if (!session) return;

    waitingQueue.delete(sessionId);
    await sessionRepo.assign(sessionId, socket.data.userId);

    socket.join(`session:${sessionId}`);

    io.to(`session:${sessionId}`).emit('operator:joined', {
      operatorId: socket.data.userId,
      operatorName: socket.data.user.name
    });

    io.to('operators').emit('queue:updated', { count: waitingQueue.size });

    // Отправить историю сообщений оператору
    const history = await messageRepo.findBySession(sessionId);
    socket.emit('session:history', history);
  });

  socket.on('message:send', async ({ sessionId, content }) => {
    const message = await messageRepo.create({
      sessionId,
      senderId: socket.data.userId,
      senderRole: 'operator',
      content
    });

    io.to(`session:${sessionId}`).emit('message:new', message);
  });

  // Закрыть чат
  socket.on('session:close', async ({ sessionId, resolution }) => {
    await sessionRepo.close(sessionId, resolution);
    io.to(`session:${sessionId}`).emit('session:closed', { resolution });
  });
}

Клиентский виджет (React)

function SupportWidget() {
  const [isOpen, setIsOpen] = useState(false);
  const [messages, setMessages] = useState<Message[]>([]);
  const [status, setStatus] = useState<'connecting' | 'waiting' | 'active' | 'closed'>('connecting');
  const [input, setInput] = useState('');
  const socketRef = useRef<Socket>();

  useEffect(() => {
    const socket = io('/support', {
      auth: { token: getGuestToken() }
    });

    socket.on('queue:position', ({ position }) => {
      setStatus('waiting');
      setMessages([{ system: true, text: `Вы в очереди. Позиция: ${position}` }]);
    });

    socket.on('operator:joined', ({ operatorName }) => {
      setStatus('active');
      setMessages(prev => [...prev, {
        system: true, text: `${operatorName} подключился`
      }]);
    });

    socket.on('message:new', (msg) => {
      setMessages(prev => [...prev, msg]);
    });

    socketRef.current = socket;
    return () => { socket.disconnect(); };
  }, []);

  const sendMessage = () => {
    if (!input.trim()) return;
    socketRef.current?.emit('message:send', { content: input });
    setInput('');
  };

  return (
    <div className={`chat-widget ${isOpen ? 'open' : ''}`}>
      <button className="chat-toggle" onClick={() => setIsOpen(!isOpen)}>
        💬 Поддержка
      </button>

      {isOpen && (
        <div className="chat-window">
          <MessageList messages={messages} />
          {status === 'active' && (
            <div className="chat-input">
              <input value={input} onChange={e => setInput(e.target.value)}
                onKeyDown={e => e.key === 'Enter' && sendMessage()} />
              <button onClick={sendMessage}>Отправить</button>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

Уведомления оператору

// Если оператор не в браузере — уведомление через Telegram/email
async function notifyOperatorsNewChat(session: ChatSession) {
  const onlineOperators = await redis.smembers('online:operators');

  if (onlineOperators.length === 0) {
    await telegramBot.sendMessage(OPERATORS_CHAT_ID,
      `🆕 Новый запрос в поддержку\nПользователь: ${session.userId}\nПервое сообщение: ${session.firstMessage}`
    );
  }
}

Сроки

Базовый чат с очередью и историей — 2–3 недели. Полный: виджет + оператор UI + уведомления + рейтинг — 4–6 недель.