Интеграция
Жизненный цикл подключения
App запускается
│
├─ Plugin: socket.client.ts → io() → создание Socket.IO клиента
│
└─ Plugin: chat-auth.client.ts → watch(auth.isAuthenticated)
│
├─ isAuthenticated = true → connect(userId) → socket.connect()
│ │
│ └─ Socket.IO handshake: auth: { token, userId }
│ │
│ ├─ Сервер: verifyChatToken(token) → найти ChatUser
│ │ │
│ │ ├─ OK → socket.join(rooms), online status
│ │ └─ FAIL → disconnect
│ │
│ └─ Клиент: loadConversations() → GET /api/v1/chat/conversations
│
└─ isAuthenticated = false → disconnect()
Регистрация пользователей
Chat layer хранит свои записи ChatUser. Каждый пользователь из внешней системы должен быть зарегистрирован по externalId (UUID из auth layer).
Вариант 1: Автоматически при создании диалога
При вызове POST /api/v1/chat/conversations с participantIds сервер выполняет upsert для каждого участника:
// Сервер автоматически создаст ChatUser, если его нет
await $fetch('/api/v1/chat/conversations', {
method: 'POST',
body: {
type: 'single',
participantIds: ['user-uuid-1', 'user-uuid-2'],
},
})
Вариант 2: Через webhook
Внешняя система вызывает webhook при создании/обновлении пользователя:
// POST /api/v1/chat/users/webhook
await $fetch('https://chat.api.example.com/api/v1/chat/users/webhook', {
method: 'POST',
body: {
externalId: 'user-uuid',
displayName: 'Иван Петров',
avatarUrl: 'https://cdn.example.com/avatars/ivan.jpg',
},
})
Вариант 3: При первом подключении к Socket.IO
Сервер создаёт ChatUser при первом handshake, если пользователь с таким externalId не найден.
Создание диалогов из внешнего приложения
Личный чат
// service-care или другое приложение
async function openChat(targetUserId: string) {
const conv = await $fetch('/api/v1/chat/conversations', {
method: 'POST',
body: {
type: 'single',
participantIds: [currentUserId, targetUserId],
},
})
// Редирект на чат
navigateTo(`/chats/${conv.id}`)
}
Групповой чат
// Создание группы (например, для инцидента)
const group = await $fetch('/api/v1/chat/conversations', {
method: 'POST',
body: {
type: 'group',
name: 'Инцидент #1234',
participantIds: [operatorId, dispatcherId, guardId],
},
})
Интеграция с Auth Layer
Chat layer расширяет @qor/auth-layer и ожидает наличия composable useAuth():
// Автоматически в chat-auth.client.ts
const auth = useAuth()
watch(
() => [auth.isAuthenticated.value, auth.user.value?.id],
([isAuth, userId]) => {
if (isAuth && userId) connect(userId)
else disconnect()
},
{ immediate: true },
)
Требования к Auth Layer
| Поле | Описание |
|---|---|
auth.isAuthenticated | Ref<boolean> — статус авторизации |
auth.user.id | string — UUID пользователя (используется как externalId) |
localStorage['qor-auth-token'] | Токен для Socket.IO handshake |
Интеграция с @qor/ui
Chat layer использует компоненты Nuxt UI 4 с темой @qor/ui. Стили пузырей сообщений адаптированы под Glass Design System:
- Свои сообщения:
bg-primaryсtext-white - Чужие сообщения:
bg-elevated/80 dark:bg-neutral-800/80 - Тема chatMessage облегчена: без gradient/backdrop-blur/border
Кастомизация
Переопределение компонентов
Любой компонент можно переопределить, создав файл с тем же именем в app/components/:
<!-- Переопределяет ChatEmpty из chat-layer -->
<template>
<div class="flex flex-col items-center justify-center h-full">
<img src="/custom-empty.svg" class="w-48">
<p>Нет активных чатов</p>
</div>
</template>
Кастомные стили
@import "tailwindcss";
@import "@nuxt/ui";
/* Переопределение стилей чата */
.chat-bubble-own {
background-color: var(--color-primary-500);
}
Расширение типов
import type { ChatMessage } from '@qor/chat-layer/app/types/chat'
// Расширение сообщения кастомными полями
interface ExtendedMessage extends ChatMessage {
priority?: 'normal' | 'urgent'
incidentId?: string
}
Playground
Для локальной разработки используйте playground:
# Из корня монорепо
pnpm dev:chat
# Или из директории слоя
cd layers/chat
pnpm dev
Playground запускается на порту 3335 с ssr: false и включает:
- Auth layer с OTP-авторизацией
- Полный набор компонентов чата
- Локальный Socket.IO сервер + SQLite
Тестирование между пользователями
- Откройте playground в двух разных браузерах (или обычный + инкогнито)
- Авторизуйтесь разными пользователями
- Создайте диалог через REST API или webhook
- Обменивайтесь сообщениями в реальном времени