193 lines
7.7 KiB
Markdown
193 lines
7.7 KiB
Markdown
# Чат с общей комнатой
|
||
|
||
Веб-приложение чата с одной общей комнатой на WebSocket.
|
||
Текстовые сообщения, файлы, изображения, аудио, видео и голосовые сообщения.
|
||
|
||
**Сервер:** Node.js + `ws`
|
||
**Клиент:** React + Vite
|
||
**Прокси:** nginx (production-сборка)
|
||
|
||
---
|
||
|
||
## Быстрый запуск (разработка)
|
||
|
||
### Сервер
|
||
|
||
```bash
|
||
cd server
|
||
npm install
|
||
npm start
|
||
```
|
||
|
||
WebSocket сервер на `ws://localhost:8080`.
|
||
|
||
### Клиент
|
||
|
||
```bash
|
||
cd client
|
||
npm install
|
||
npx vite
|
||
```
|
||
|
||
Dev-сервер на `http://localhost:3000`.
|
||
Клиент подключается напрямую к `ws://localhost:8080`.
|
||
|
||
### Пользователи по умолчанию
|
||
|
||
| Логин | Пароль |
|
||
|-------|--------|
|
||
| alice | 123 |
|
||
| bob | 456 |
|
||
|
||
---
|
||
|
||
## Production (Docker)
|
||
|
||
### Сборка и запуск
|
||
|
||
```bash
|
||
docker build -t chat-app .
|
||
docker run -d -p 80:80 chat-app
|
||
```
|
||
|
||
Открой `http://localhost`.
|
||
|
||
### Как это работает
|
||
|
||
- **nginx** на порту 80 раздаёт статику React и проксирует WebSocket
|
||
- Путь `/` → статические файлы клиента
|
||
- Путь `/ws` → прокси на Node.js сервер (порт 8080 внутри контейнера)
|
||
- nginx поддерживает Upgrade до WebSocket, таймаут соединения — 24 часа
|
||
- Максимальный размер загружаемых файлов через nginx — 50 МБ
|
||
- **Auto-restart:** если Node.js сервер упал, entrypoint перезапускает его через 1 секунду
|
||
- Чистое завершение (exit 0, SIGTERM) — контейнер останавливается без перезапуска
|
||
|
||
---
|
||
|
||
## Переменные окружения
|
||
|
||
### Сервер
|
||
|
||
| Переменная | По умолчанию | Описание |
|
||
|-----------|-------------|---------|
|
||
| `PORT` | `8080` | Порт WebSocket сервера |
|
||
| `MAX_FILE_SIZE` | `10485760` (10 МБ) | Максимальный размер файла в байтах |
|
||
| `USERS_FILE` | `users.json` | Путь к файлу с пользователями |
|
||
|
||
```bash
|
||
$env:PORT=9090; $env:MAX_FILE_SIZE=2097152; node server.js
|
||
```
|
||
|
||
### Клиент
|
||
|
||
| Переменная | По умолчанию (dev) | По умолчанию (production) | Описание |
|
||
|-----------|-------------------|--------------------------|---------|
|
||
| `VITE_WS_URL` | `ws://localhost:8080` | `/ws` | URL WebSocket сервера |
|
||
|
||
Настройки в `client/.env.development` (dev) и `client/.env` (production).
|
||
|
||
---
|
||
|
||
## Протокол обмена
|
||
|
||
### Аутентификация
|
||
|
||
```json
|
||
// → сервер
|
||
{ "type": "auth", "login": "alice", "password": "123" }
|
||
// ← ответ
|
||
{ "type": "auth_result", "success": true }
|
||
{ "type": "auth_result", "success": false, "reason": "Invalid login or password" }
|
||
```
|
||
|
||
### Текстовое сообщение
|
||
|
||
```json
|
||
// → сервер
|
||
{ "type": "text", "text": "Привет!" }
|
||
// ← всем
|
||
{ "type": "text", "from": "alice", "timestamp": 1717000000000, "text": "Привет!" }
|
||
```
|
||
|
||
### Файл / изображение / аудио / видео
|
||
|
||
```json
|
||
// → сервер
|
||
{ "type": "file", "filename": "cat.png", "mime": "image/png", "data": "<base64>" }
|
||
// ← всем (от сервера добавлены from и timestamp)
|
||
{ "type": "file", "from": "alice", "timestamp": 1717000000000, "filename": "cat.png", "mime": "image/png", "data": "<base64>" }
|
||
```
|
||
|
||
Клиент определяет тип файла по MIME: `image/*` — картинка, `audio/*` — плеер, `video/*` — плеер, остальное — скачивание.
|
||
|
||
### Системные сообщения
|
||
|
||
```json
|
||
{ "type": "system", "text": "alice joined the chat" }
|
||
{ "type": "system", "text": "alice left the chat" }
|
||
```
|
||
|
||
---
|
||
|
||
## Функциональность
|
||
|
||
- **Текстовые сообщения** — логин отправителя, время, ссылки (http/https) автоматически становятся кликабельными
|
||
- **Файлы** — любые типы, до 10 МБ (настраивается), base64 в JSON
|
||
- **Изображения** — `image/*` → `<img>`, клик открывает в новой вкладке
|
||
- **Аудио** — `audio/*` → `<audio controls>` + ссылка скачивания
|
||
- **Видео** — `video/*` → `<video>` с controls + ссылка скачивания
|
||
- **Голосовые сообщения** — кнопка микрофона, запись через `MediaRecorder`, отправка как файл
|
||
- **Уведомления** — звук (Web Audio API) и красная точка на favicon при сообщении на неактивной вкладке
|
||
- **Системные сообщения** — подключение/отключение участников
|
||
- **Автоскролл** — кастомный тонкий скроллбар
|
||
- **Вставка изображений из буфера обмена** — Ctrl+V в поле ввода, отправка как файл
|
||
- **Авто-переподключение** — при разрыве WebSocket клиент автоматически пересоздаёт соединение
|
||
- **Сохранение учётных данных** — логин и пароль сохраняются в `localStorage`, при повторном входе поле не требуется
|
||
- **Сохранение истории** — последние 50 сообщений (текст, файлы) сохраняются в `localStorage`
|
||
- **Закреплённые сообщения** — любое сообщение можно закрепить в шапке чата
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
chat/
|
||
├── server/
|
||
│ ├── config.js # Настройки из env
|
||
│ ├── auth.js # Загрузка пользователей, authenticate()
|
||
│ ├── clients.js # Управление соединениями, broadcast()
|
||
│ ├── server.js # Точка входа, WebSocketServer
|
||
│ ├── users.json # Хардкоженые логины/пароли
|
||
│ └── package.json
|
||
├── client/
|
||
│ ├── src/
|
||
│ │ ├── App.jsx / .css
|
||
│ │ ├── main.jsx
|
||
│ │ ├── hooks/
|
||
│ │ │ ├── useWebSocket.js # WebSocket + auth
|
||
│ │ │ └── useChatMessages.js # Приём сообщений + уведомления
|
||
│ │ ├── utils/
|
||
│ │ │ ├── blob.js # base64 → Blob URL
|
||
│ │ │ ├── linkify.jsx # URL → React-ссылки
|
||
│ │ │ └── notify.js # Звук + favicon
|
||
│ │ └── components/
|
||
│ │ ├── Login.jsx / .css
|
||
│ │ ├── Chat.jsx / .css
|
||
│ │ ├── MessageList.jsx
|
||
│ │ ├── Message.jsx / .css
|
||
│ │ ├── MessageInput.jsx / .css
|
||
│ │ └── VoiceRecorder.jsx / .css
|
||
│ ├── .env # Production настройки
|
||
│ ├── .env.development # Dev настройки
|
||
│ ├── index.html
|
||
│ ├── vite.config.js
|
||
│ └── package.json
|
||
├── Dockerfile # Многостадийная сборка
|
||
├── docker-entrypoint.sh # Auto-restart при падении
|
||
├── nginx.conf
|
||
├── .dockerignore
|
||
├── .gitignore
|
||
├── LICENSE
|
||
└── README.md
|
||
```
|