Полный рефакторинг сервера и клиента, голосовые сообщения, аудио/видео, Docker

Сервер:
- Вынесена конфигурация в server/config.js (порт, размер файла из env)
- Вынесена аутентификация в server/auth.js (загрузка users.json, authenticate())
- Вынесено управление соединениями в server/clients.js (createJsonSender, broadcast, validateFile)
- server/server.js стал точкой входа — минимальный код

Клиент:
- Вынесены хуки: hooks/useWebSocket.js (WebSocket + auth), hooks/useChatMessages.js (сообщения + уведомления)
- Вынесены утилиты: utils/blob.js (base64 → Blob URL), utils/linkify.jsx (URL → ссылки), utils/notify.js (звук + favicon)

Новые функции:
- VoiceRecorder — запись голоса через MediaRecorder, отправка как файл
- Аудио/видео плеер в Message (audio/*, video/* с controls)
- URL linkification — http/https ссылки автоматически кликабельны
- Звуковое уведомление (Web Audio API) при сообщении на неактивной вкладке
- Красная точка на favicon при непрочитанных сообщениях

Инфраструктура:
- docker-entrypoint.sh: авто-перезапуск Node.js сервера при падении
- Обновлён README.md: новая структура проекта, список функций, примеры
- Кастомный тонкий скроллбар в Message.css
This commit is contained in:
Комаров Данил Анатольевич 6
2026-05-29 05:23:22 +03:00
parent 83ef0d5ab3
commit 63bf73d2c4
18 changed files with 760 additions and 485 deletions
+54 -76
View File
@@ -1,6 +1,7 @@
# Чат с общей комнатой
Веб-приложение чата с одной общей комнатой на WebSocket. Поддерживает текстовые сообщения, отправку файлов и отображение изображений.
Веб-приложение чата с одной общей комнатой на WebSocket.
Текстовые сообщения, файлы, изображения, аудио, видео и голосовые сообщения.
**Сервер:** Node.js + `ws`
**Клиент:** React + Vite
@@ -18,7 +19,7 @@ npm install
npm start
```
Запускает WebSocket сервер на `ws://localhost:8080`.
WebSocket сервер на `ws://localhost:8080`.
### Клиент
@@ -28,8 +29,8 @@ npm install
npx vite
```
Запускает dev-сервер на `http://localhost:3000`.
В режиме разработки клиент подключается напрямую к `ws://localhost:8080`.
Dev-сервер на `http://localhost:3000`.
Клиент подключается напрямую к `ws://localhost:8080`.
### Пользователи по умолчанию
@@ -42,15 +43,10 @@ npx vite
## Production (Docker)
### Сборка образа
### Сборка и запуск
```bash
docker build -t chat-app .
```
### Запуск контейнера
```bash
docker run -d -p 80:80 chat-app
```
@@ -59,11 +55,12 @@ docker run -d -p 80:80 chat-app
### Как это работает
- **nginx** на порту 80 раздаёт статику React и проксирует WebSocket
- Путь `/` → статические файлы клиента (`index.html`, JS, CSS)
- Путь `/ws` → прокси на Node.js WebSocket сервер (порт 8080 внутри контейнера)
- Путь `/` → статические файлы клиента
- Путь `/ws` → прокси на Node.js сервер (порт 8080 внутри контейнера)
- nginx поддерживает Upgrade до WebSocket, таймаут соединения — 24 часа
- Максимальный размер загружаемых файлов через nginx — 50 МБ
- После остановки Node.js контейнер завершается (через `wait`)
- **Auto-restart:** если Node.js сервер упал, entrypoint перезапускает его через 1 секунду
- Чистое завершение (exit 0, SIGTERM) — контейнер останавливается без перезапуска
---
@@ -77,8 +74,6 @@ docker run -d -p 80:80 chat-app
| `MAX_FILE_SIZE` | `10485760` (10 МБ) | Максимальный размер файла в байтах |
| `USERS_FILE` | `users.json` | Путь к файлу с пользователями |
Пример:
```bash
$env:PORT=9090; $env:MAX_FILE_SIZE=2097152; node server.js
```
@@ -89,9 +84,7 @@ $env:PORT=9090; $env:MAX_FILE_SIZE=2097152; node server.js
|-----------|-------------------|--------------------------|---------|
| `VITE_WS_URL` | `ws://localhost:8080` | `/ws` | URL WebSocket сервера |
Настройки хранятся в файлах:
- `client/.env.development` — для `npx vite` (dev-режим)
- `client/.env` — для `vite build` (production-сборка)
Настройки в `client/.env.development` (dev) и `client/.env` (production).
---
@@ -99,81 +92,54 @@ $env:PORT=9090; $env:MAX_FILE_SIZE=2097152; node server.js
### Аутентификация
Клиент → Сервер:
```json
// → сервер
{ "type": "auth", "login": "alice", "password": "123" }
```
Сервер → Клиент:
```json
// ← ответ
{ "type": "auth_result", "success": true }
```
```json
{ "type": "auth_result", "success": false, "reason": "Invalid login or password" }
```
### Текстовое сообщение
Клиент → Сервер:
```json
// → сервер
{ "type": "text", "text": "Привет!" }
```
Сервер → Все:
```json
// ← всем
{ "type": "text", "from": "alice", "timestamp": 1717000000000, "text": "Привет!" }
```
### Файл / изображение
Клиент → Сервер:
### Файл / изображение / аудио / видео
```json
{
"type": "file",
"filename": "cat.png",
"mime": "image/png",
"data": "<base64>"
}
// → сервер
{ "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>" }
```
Сервер → Все (такая же структура, добавлены `from` и `timestamp`):
```json
{
"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" }
```
---
## Функциональность
- **Текстовые сообщения** — отображаются с логином отправителя и временем
- **Файлы** — любые типы, до 10 МБ (настраивается). Файлы передаются через base64 в JSON
- **Изображения** — автоматически определяются по MIME-типу (`image/*`), рендерятся как `<img>`, клик открывает в новой вкладке
- **Не-изображения** — отображаются как ссылка для скачивания с именем файла
- **Уведомления на неактивной вкладке** — звуковой сигнал (Web Audio API) и красная точка на favicon
- **Системные сообщения** — подключение / отключение участников
- **Скролл** — автоматический, кастомный тонкий скроллбар в теме приложения
- **Текстовые сообщения** — логин отправителя, время, ссылки (http/https) автоматически становятся кликабельными
- **Файлы** — любые типы, до 10 МБ (настраивается), base64 в JSON
- **Изображения** — `image/*` `<img>`, клик открывает в новой вкладке
- **Аудио** — `audio/*``<audio controls>` + ссылка скачивания
- **Видео** — `video/*``<video>` с controls + ссылка скачивания
- **Голосовые сообщения** — кнопка микрофона, запись через `MediaRecorder`, отправка как файл
- **Уведомления** — звук (Web Audio API) и красная точка на favicon при сообщении на неактивной вкладке
- **Системные сообщения** — подключение/отключение участников
- **Автоскролл** — кастомный тонкий скроллбар
---
@@ -182,27 +148,39 @@ $env:PORT=9090; $env:MAX_FILE_SIZE=2097152; node server.js
```
chat/
├── server/
│ ├── server.js # WebSocket сервер
│ ├── users.json # Хардкоженые логины/пароли
│ ├── config.js # Настройки из env
│ ├── auth.js # Загрузка пользователей, authenticate()
│ ├── clients.js # Управление соединениями, broadcast()
│ ├── server.js # Точка входа, WebSocketServer
│ ├── users.json # Хардкоженые логины/пароли
│ └── package.json
├── client/
│ ├── src/
│ │ ├── App.jsx # Корневой компонент
│ │ ├── App.css
│ │ ├── main.jsx # Точка входа
│ │ ├── 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
├── .env # Production настройки
│ ├── .env.development # Dev настройки
│ │ ── MessageInput.jsx / .css
│ └── VoiceRecorder.jsx / .css
│ ├── .env # Production настройки
│ ├── .env.development # Dev настройки
│ ├── index.html
│ ├── vite.config.js
│ └── package.json
├── Dockerfile # Многостадийная сборка
├── docker-entrypoint.sh # Точка входа контейнера
├── nginx.conf # Конфигурация nginx
├── Dockerfile # Многостадийная сборка
├── docker-entrypoint.sh # Auto-restart при падении
├── nginx.conf
├── .dockerignore
├── .gitignore
└── README.md
```