- JavaScript 86.6%
- CSS 8%
- HTML 5.3%
- NSIS 0.1%
| build | ||
| electron | ||
| public | ||
| site | ||
| tests | ||
| .gitignore | ||
| nethost.js | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| ROADMAP.md | ||
| SECURITY.md | ||
| server.js | ||
«Волна» 3.10 — мессенджер (веб + десктоп)
Самостоятельный мессенджер с привычным интерфейсом в духе Telegram. Сервер — Node.js (WebSocket), клиент — чистый HTML/CSS/JS, десктоп — Electron. Интерфейс вдохновлён Telegram; проект не аффилирован с Telegram FZ-LLC. Перед публичным запуском прочтите SECURITY.md и проверьте свободность имени бренда.
Функции
- Регистрация и вход, сессии, работа в нескольких окнах
- Личные чаты, группы, каналы, «Избранное»
- Секретные чаты — сквозное шифрование (ECDH P-256 + AES-GCM 256, Web Crypto); сервер хранит только шифртекст и публичные ключи
- Аудио- и видеозвонки 1:1 (WebRTC, сигналинг через сервер, STUN)
- Голосовые сообщения (запись с микрофона, плеер с прогрессом)
- Стикеры — собственный SVG-пак (17 шт.)
- Истории — фото + подпись, живут 24 часа, счётчик просмотров
- Системные уведомления о новых сообщениях и звонках
- Фото-аватары пользователей, групп и каналов
- Форматирование текста: жирный, курсив,
код,блок,зачёркнутый, ||спойлер|| - Видео в чате, мьют, архив, закрепление чатов, черновики
- Управление группой: админы, удаление участников, очистка истории, удаление чата
- Опросы (анонимные, с несколькими ответами), @упоминания с автодополнением
- Глобальный поиск по сообщениям, множественный выбор (переслать/удалить)
- Оформление: 5 акцентных цветов, фоны чата; скорость голосовых 1×/1.5×/2×
- Папки чатов (Все/Личные/Группы/Каналы/Непрочитанные), контакты
- Видеосообщения-кружки, геопозиция, «записывает голосовое…»
- Отложенная отправка (ПКМ по кнопке отправки), удалить у себя/у всех
- Десктоп: сворачивание в трей
- Веб-превью ссылок (OpenGraph), большие эмодзи, просмотры постов 👁 в каналах
- Свои стикеры (загрузка в панель), настройка TURN для звонков
- Групповые голосовые чаты (mesh WebRTC, до 8 участников)
- Истории с видео, список зрителей своей истории
- Анимированные стикеры, блокировка пользователей, разделитель «Непрочитанные»
- Реакция любым эмодзи («+» в меню), автоудаление сообщений (24ч/7д)
- Экспорт чата в HTML, статистика канала, градиентные стили пузырей
- Bot API: создание ботов (меню → Боты), HTTP-методы getMe/getUpdates/sendMessage
- Ссылки-приглашения в группы (код вводится в поиске), медиа-галерея чата
- Недавние эмодзи, «N в сети» в группах
- Несколько закреплённых сообщений с навигацией, устройства/сессии с завершением
- 2FA (TOTP) — профиль → «Включить 2FA», совместимо с любым аутентификатором
- Дисковые квоты (QUOTA_MB, по умолчанию 512 МБ/пользователь), журнал безопасности
- Смена пароля, звук уведомлений, drag&drop-оверлей, Ctrl+K — поиск
- @-бейдж упоминаний в списке, «отметить прочитанным/непрочитанным»
- Память прокрутки чатов, системная тема (авто), Esc закрывает чат, список закрепов
- Команды ботов с подсказками по «/», превью фото с подписью, прогресс загрузки
- ПКМ по реакции — кто поставил
- Сообщения в реальном времени, «в сети / был(а)», «печатает…»
- Галочки прочтения ✓/✓✓, счётчики непрочитанных
- Ответы, редактирование, удаление, пересылка, закрепление, реакции
- Файлы и фото (до 25 МБ), просмотр изображений
- Поиск по чатам, пользователям, каналам и сообщениям
- Пересылка геопозиции сохраняет координаты (фикс 3.3.1)
- Отложенная отправка уважает блокировку и права канала на момент доставки (фикс 3.3.2)
- Пересылка уважает блокировку получателя; «удалить у всех» вычищает гео/превью из хранилища (фикс 3.9.1)
- Сборщик файлов: «удалить у всех», TTL, очистка истории, истёкшие истории и смена аватара стирают файлы и с диска (URL перестаёт открываться), а место возвращается в дисковую квоту (фикс 3.9.2)
- Блокировка действует везде: секретный чат не создать, в группу не добавить, истории скрыты в обе стороны, «печатает…» не утекает (фикс 3.9.3)
- Bot API без утечек: replyTo принимается только из того же чата, личка с ботом уважает блокировку (фикс 3.9.4)
- Исключение/выход из группы завершает и голосовой чат: участник пропадает из звонка у всех (mesh-связь рвётся), слот из 8 освобождается, клиент кладёт трубку при удалении чата (фикс 3.9.5)
- Очистка истории очищает чат полностью: вместе с сообщениями снимаются @-бейдж упоминания и пометка «непрочитанным» — на пустом чате они больше не «висят» (фикс 3.9.6)
- Блокировка скрывает присутствие: заблокированный не видит «в сети / был(а)» — в чате, поиске, профиле, userInfo и presence-пушах (фикс 3.9.7)
- Блокировку не обойти через старые сообщения: редактирование, реакции, голос в опросе и закрепление в личных/секретных чатах отклоняются, пока действует блокировка (фикс 3.9.8)
- Загруженные файлы не исполняют код: SVG/HTML из
/files/отдаются с жёсткимContent-Security-Policy: sandbox— открытый напрямую файл не выполнит скрипт в origin (защита от кражи токена и ключей из localStorage); показ картинок/видео в чате не затронут (фикс 3.9.9) - Удаление сообщения снимает @-бейдж: «удалить у всех», «удалить у себя» и автоудаление по таймеру сбрасывают «фантомное» упоминание, которое больше нечем прочитать (фикс 3.9.10)
- Удаление бота прибирает за собой: личные чаты с ботом удаляются целиком — раньше «осколок» чата отображался как второе «Избранное»; сообщения чатов без участников вычищаются (файлы вернёт сборщик), группы сразу получают обновлённый состав (фикс 3.9.11)
- Выход/исключение из группы сбрасывает личные настройки чата: мьют, архив, закрепление вверху и пометка «непрочитанным» больше не «воскресают» при повторном добавлении — чат возвращается в обычном виде (фикс 3.9.12)
- Точные @упоминания: бейдж и пробитие мьюта — только при точном совпадении логина: «@anna» больше не дёргает пользователя @ann, email вида ivan@ann.ru не считается упоминанием (фикс 3.9.13)
- Заблокированный не создаёт личный чат: пустой диалог больше не «всплывает» в списке чатов у заблокировавшего; существующая переписка открывается как прежде (фикс 3.9.14)
- Правка сообщения обновляет @-бейдж: упоминание, убранное при редактировании, снимает бейдж (не «висит фантомом»), добавленное правкой — вешает; уже прочитанные упоминания правкой не «воскресают» (фикс 3.9.15)
- Просмотры каналов не накручиваются: «прочитать» дальше последнего сообщения чата нельзя — дутый msgId в read навсегда завышал 👁 у всех будущих постов и рисовал ✓✓ недоставленным сообщениям (фикс 3.9.16)
- Просмотры историй не накручиваются: просмотр засчитывается только при общем чате — незнакомец перебором id не накрутит счётчик 👁 и не появится в списке зрителей истории (фикс 3.9.17)
- Выход из аккаунта останавливает уведомления: logout и завершение сессии (в т.ч. удалённое — «Устройства») отвязывают Web Push этой сессии: разлогиненное устройство больше не получает тексты сообщений (фикс 3.9.18)
- Смена пароля завершает остальные сессии: украденный или оставленный на чужом устройстве вход перестаёт действовать сразу после смены пароля (как «Завершить все другие сессии»); текущая сессия остаётся (фикс 3.9.19)
- Код 2FA — одноразовый: повторный вход тем же кодом в 90-секундном окне отклоняется (RFC 6238) — подсмотренный или перехваченный фишингом код нельзя использовать второй раз; код следующего окна работает (фикс 3.9.20)
- Исключённый не возвращается сам: kick закрывает вход по ссылке-приглашению и повторную самоподписку на канал; добавление админом возвращает (снимает бан), добровольный выход по коду вернуться не мешает (фикс 3.9.21)
- Бан не снимается «сообщником»: вернуть исключённого (= снять бан) может только админ — addMembers рядового участника молча пропускает забаненного; участников в канал добавляют только админы; приглашение не-забаненных участниками группы работает как прежде (фикс 3.9.22)
- Аудитория канала не собирается по API: список подписчиков канала в chatInfo отдаётся только владельцу/админам — подписчику остаётся счётчик, как в Telegram; в группах список виден всем (фикс 3.9.23)
- Пароль и 2FA не подбираются из украденной сессии: смена пароля и отключение 2FA — не больше 5 попыток в минуту (login был защищён лимитом и блокировкой, внутренние вызовы — нет), неудачи видны в журнале (фикс 3.9.24)
- «Печатает…» не для троллинга: в каналах индикатор транслируют только те, кто может публиковать (у подписчика и композера-то нет), и не чаще 20 раз за 10 с на пользователя — раньше фан-аут на всю аудиторию шёл вообще без лимита (фикс 3.9.25)
- Реакции в канале не раскрывают аудиторию: рядовому подписчику приходят только счётчики реакций и метка собственной — id реагировавших больше не утекают всем, аудиторию канала не перебрать в обход 3.9.23; владелец и админы видят, кто поставил (фикс 3.9.26)
- Общий канал не делает «знакомыми»: подписка на канал больше не открывает незнакомцам истории друг друга (и зачёт просмотров 👁), звонки и presence-пуши — родство дают только личка, группа и секретный чат, аудитория канала остаётся скрытой (фикс 3.9.27)
- Прочтения канала не раскрывают подписчиков: пуш «read» с id читателя больше не рассылается всей аудитории при прочтении поста (галочек ✓✓ в каналах нет, 👁 считается на сервере) — пассивный сбор аудитории закрыт, фан-аут ×N на каждое прочтение убран (фикс 3.9.28)
- Выход владельца не дарит чат кому попало: владельцем становится админ, а не «первый в списке»; бот — никогда (группа с ботом-владельцем зависала навсегда: его не кикнуть, админов не назначить, чат не удалить); группа из одних ботов удаляется целиком (фикс 3.9.29)
- Отложенным сообщениям — лимиты: очередь — до 100 на пользователя (раньше росла без предела), создание — не чаще 20/10 с; отложка больше не обходит флуд-лимит отправки доставкой пачки разом (фикс 3.9.30)
- Реакциям и голосам — лимит частоты: toggle-петля реакции или голоса (доступна и подписчику канала) множила пуши на всю аудиторию и дисковые записи вообще без ограничений — теперь не чаще 30 за 10 с (фикс 3.9.31)
- Пересылке — предел пачки и частоты: api.forward создавал сообщения с фан-аутом вообще без лимитов, дубли id в пачке не схлопывались (один вызов множил сообщение ×N в обход send-лимита) — теперь не больше 100 уникальных за вызов и не чаще 100 пересланных за 10 с (фикс 3.9.32)
- ✓✓ не выдают активность при блокировке: пуш о прочтении и readsMax в личке/секретном чате показывали заблокированному, что блокировавший читает сообщения прямо сейчас («в сети» скрыто ещё в 3.9.7) — указатель прочтений скрыт на время блокировки, после снятия ✓✓ догоняют (фикс 3.9.33)
- Посты канала не подписаны автором: подписчику история, поиск, список чатов, «Переслано от …» и web push показывают канал, а не имя/аватар владельца или админа; «печатает…» в каналах не транслируется; владелец и админы видят авторов как прежде (фикс 3.9.34)
- Правке сообщений — лимит частоты: edit-петля одного сообщения слала msgEdit-фан-аут на всю аудиторию, save() и повторную загрузку веб-превью (исходящий HTTP) на каждую правку вообще без ограничений — теперь не чаще 30 за 10 с, как реакции и голоса (фикс 3.9.35)
- Блокировку не обойти управлением чатом: заблокированному в личке недоступны автоудаление (TTL стирал историю блокировавшего по таймеру, сервисное сообщение пушилось в обход блокировки), очистка истории и удаление чата; сам блокировавший управляет чатом как прежде (фикс 3.9.36)
- Созданию чатов — лимит частоты: петля создания групп/каналов засоряла хранилище навсегда (чаты не истекают), force-добавление «подсаживало» жертв в спам-группы, новые личные/секретные чаты пушились собеседнику без ограничений — теперь не больше 15 новых чатов за 10 с; открытие существующего чата лимит не тратит (фикс 3.9.37)
- Push при закрытой вкладке — Web Push (Service Worker + VAPID, RFC 8291) без сторонних сервисов; тумблер в «Оформлении», только офлайн-получателям, с учётом мьюта и @упоминаний (3.4.0)
- Групповые видеозвонки — камера в голосовом чате группы: кнопка 📷 в баре, плитки видео над перепиской, индикатор «📷 N» у участников (3.5.0)
- Перенос секретных чатов на другое устройство: экспорт/импорт ключей файлом, зашифрованным парольной фразой (PBKDF2 + AES-GCM) — в профиле (3.6.0)
- Стикер-паки: объединяйте свои стикеры в наборы (📦 в панели стикеров) и делитесь 10-значным кодом — друг вводит его в поиске и устанавливает (3.7.0)
- Фильтры глобального поиска — от кого (@логин), диапазон дат, «только медиа»; панель под строкой поиска, работает и без текста запроса (3.8.0)
- Свой порядок папок чатов — перетащите вкладку (Все/Личные/…) мышью, порядок хранится на устройстве (3.9.0)
- Эмодзи-проверка звонков — при соединении оба собеседника видят 4 эмодзи, выведенных из DTLS-отпечатков сторон: совпадают — посредника на сигналинге нет, сверьте вслух (3.10.0)
- Профилю, аватарам и историям — лимит частоты: правка профиля и смена аватара (фан-аут на всех знакомых), публикация историй — не чаще 10/10 с, активных историй — не больше 30: db.stories рос без предела, петля публикаций множила пуши (фикс 3.10.1)
- «Удалить у всех» не обходит блокировку: заблокированный больше не стирает сообщения из лички/секретного чата блокировавшего (там удалять «у всех» можно и чужие) и не шлёт ему msgDel-пуши; блокировавший удаляет как прежде (фикс 3.10.2)
- Реакциям — потолок на сообщении: не больше 3 реакций одного пользователя на сообщении, новая вытесняет старейшую (как в Telegram) — петля «новый эмодзи на каждый вызов» в лимит 3.9.31 вешала на сообщение тысячи реакций навсегда, раздувая хранилище и пуши (фикс 3.10.3)
- 2FA-статус не светится наружу: включён ли у аккаунта второй фактор, видит только сам владелец — userInfo, поиск и списки участников больше не отдают готовый перечень аккаунтов без 2FA для подбора пароля (фикс 3.10.4)
- SSRF-фильтр покрывает IPv6: предпросмотр ссылок и Web Push не сходят на
внутренние адреса — фильтр приватных хостов не снимал скобки IPv6 hostname,
поэтому ВЕСЬ IPv6 проходил:
[::1], IPv4-mapped[::ffff:169.254.169.254](метаданные облака), ULAfc00::/7и link-localfe80::/10дотягивались до внутренних сервисов; теперь блокируются, публичный IPv6 разрешён (фикс 3.10.5) - Членству в чатах — лимит частоты и предел пачки: вступление/выход/ добавление участников — не чаще 20 изменений за 10 с (петля join/leave фан-аутила пуши на всю аудиторию и навсегда растила хранилище сервисными сообщениями), addMembers — не больше 50 уникальных id за вызов; повторный join участника и no-op-добавления бюджет не тратят (фикс 3.10.6)
- Закреплению — лимит частоты: петля pin/unpin (в личке доступна и не-админу) множила notifyChat-фан-аут — chatView с pinViews (до 20 msgView) под каждого участника — и save() на каждый toggle — теперь не чаще 30 за 10 с, как реакции, голоса и правки (фикс 3.10.7)
- Профиль (имя, о себе, цвет аватара), светлая/тёмная темы, мобильная вёрстка
Запуск (веб-режим)
Нужен Node.js 18+ (https://nodejs.org).
cd telegram-clone
npm install
npm start
Откройте http://localhost:8080. Второй пользователь — окно в инкогнито или другой браузер.
Десктоп-приложение
Запуск без установки:
npm install
npm run app
Сборка установщика .exe (Windows)
npm run dist
Готовый установщик появится в dist/Volna-Setup-<версия>.exe
(установка с выбором папки, ярлыки на рабочем столе и в меню «Пуск»).
Первая сборка скачивает Electron (~100 МБ) — нужен интернет и несколько минут.
Десктоп-приложение запускает встроенный сервер автоматически; данные хранятся
в %APPDATA%/tg-clone. Чтобы переписываться с другими компьютерами, запустите
сервер (npm start) на одной машине и откройте её адрес в браузере с других
(http://IP-адрес:8080).
Тест
При запущенном сервере: npm test — интеграционный сценарий (регистрация,
доставка, прочтение, группы, каналы, реакции, пересылка, секретные чаты,
сигналинг звонков, истории, стикеры, голосовые, криптография E2E).
Структура
server.js — сервер: HTTP + WebSocket, вся логика
electron/main.js — десктоп-обёртка (встроенный сервер + окно)
public/
index.html — разметка клиента
style.css — стили (светлая/тёмная темы)
app.js — логика клиента (чаты, звонки, E2E, истории)
stickers/ — SVG-стикеры
build/icon.ico — иконка приложения
tests/smoke.js — интеграционный тест
volna.db, files/ — данные (SQLite WAL; создаются автоматически)
Развёртывание (продакшен)
Минимальный безопасный контур: сервер за обратным прокси с TLS.
- Node 18+ на сервере,
npm install --omit=dev, запуск через systemd:
[Unit]
Description=Volna messenger
After=network.target
[Service]
WorkingDirectory=/opt/volna
ExecStart=/usr/bin/node server.js
Environment=PORT=8080
Environment=DATA_DIR=/var/lib/volna
Restart=always
User=volna
[Install]
WantedBy=multi-user.target
- TLS — проще всего через Caddy (автоматический Let's Encrypt):
chat.example.com {
reverse_proxy 127.0.0.1:8080
}
Либо без прокси: задайте TLS_CERT=/путь/fullchain.pem и TLS_KEY=/путь/privkey.pem —
сервер сам поднимет HTTPS/WSS.
-
Звонки через интернет: установите coturn (
apt install coturn), включитеlt-cred-mech, создайте пользователя и укажитеturn:ваш-домен:3478+ логин/пароль в приложении: Оформление → TURN-сервер. -
Бэкапы: каталог
DATA_DIR(volna.db, volna.db.bak, files/) — это все данные. Сервер сам делает почасовой бэкап volna.db.bak; настройте внешнее копирование каталога.
Хранилище
Основное хранилище — SQLite (volna.db, WAL): на диск пишутся только изменившиеся
записи, при завершении процесса буфер сбрасывается. Данные из старого data.json
мигрируются автоматически при первом запуске (оригинал остаётся как
data.json.migrated). better-sqlite3 стоит в optionalDependencies: если нативный
модуль не собрался, сервер прозрачно работает на прежнем data.json (видно в логе
запуска). Десктоп: electron-builder пересобирает нативный модуль автоматически;
без тулчейна приложение использует JSON-фолбэк. Проверка персистентности:
node tests/persist.js 1 → перезапуск сервера → node tests/persist.js 2.
- Перед запуском прочтите SECURITY.md и пройдите чеклист.
Переменная RELAX_RATELIMIT=1 отключает лимиты (только для отладки!).
Bot API
Создайте бота в меню «Боты» и добавьте его в группу как участника. Методы:
curl http://localhost:8080/bot/<ТОКЕН>/getMe
curl http://localhost:8080/bot/<ТОКЕН>/getUpdates
curl -X POST http://localhost:8080/bot/<ТОКЕН>/sendMessage \
-H "Content-Type: application/json" \
-d '{"chatId": 123, "text": "Привет из бота!"}'
getUpdates возвращает накопленные входящие сообщения (очередь до 100) и очищает её.
Команды бота (подсказки при вводе «/» в чате):
curl -X POST http://localhost:8080/bot/<ТОКЕН>/setMyCommands \
-H "Content-Type: application/json" \
-d '{"commands": [{"command": "start", "description": "Запустить бота"}]}'
Как устроены секретные чаты
При открытии секретного чата каждое устройство генерирует пару ключей ECDH P-256; публичные части обмениваются через сервер, из них выводится общий ключ AES-GCM 256. Шифрование и расшифровка — только на клиенте. Следствия, как и в настоящих секретных чатах: история привязана к устройству, доступен только текст. Перенести историю на новое устройство можно экспортом ключей с парольной фразой: профиль → «Экспорт ключей», на новом устройстве — «Импорт». Примечание: это учебная реализация без верификации ключей (отпечатков) и защиты от MITM со стороны сервера.
Ограничения и идеи для развития
Групповые звонки — mesh (каждый с каждым), до 8 человек; в сетях со строгим NAT может понадобиться TURN-сервер (сейчас только STUN). Web Push работает по HTTPS (или на localhost) в браузерах с Push API; в Electron-обёртке уведомления показывает сам клиент. Секретные чаты — без верификации ключей (отпечатков) и защиты от MITM со стороны сервера. Сервер и хранилище — один узел (SQLite/JSON); кластеризация и внешняя БД (Postgres) сознательно не входят в малые итерации. Идеи дальше: треды-комментарии в каналах, импорт/экспорт всего аккаунта, федерация между серверами.