WebRTC на практике: как tfile.io гоняет файлы из браузера в браузер

· ·

WebRTC на практике: как tfile.io гоняет файлы из браузера в браузер

PrimeCoder · 2026 · формат: tech

Разбор новинки простыми словами: что это, кому нужно и брать ли.

Разбор показывает, что P2P-передача файлов без сервера реально работает, но упирается в TURN-биллинг, странности Safari с relay в локальной сети и защиту SDP от подмены — готовый чек-лист для продуктов без бэкенда.

Связанные услуги: каталог, AI Boost Team, стать клиентом.

Демо на 200 строк с RTCPeerConnection и createDataChannel пишется за вечер. Дальше начинается реальный мир: Safari на телефоне засыпает через три секунды после блокировки экрана, Chrome роняет скорость от собственной жадности SCTP, корпоративный файрвол не пускает, папка на 5000 файлов вешает вкладку, файл на 10 ГБ не влезает в память.

Автор под ником reb20201 выложил разбор того, как это пережил его tfile.io: файлы идут из браузера в браузер, сервер только знакомит собеседников. Охват 1.4K, 11 минут чтения. Для собственника и продакт-лида там ценны не куски кода, а три счёта, которые приходят после запуска: за TURN-трафик, за доверие к SDP и за путаницу между «связь с сервером» и «передача файлов».

Что это: две независимые трубы

Архитектура укладывается в схему из двух пакетов. Первый — сигнальный сервер на TypeScript и ws, комнаты живут в памяти как Map<roomId, Map<peerId, Peer>>, максимум два человека, третий получает room-full. Второй — React 19 с SSR. Ссылка вида ?r=<uuid> и есть комната.

Кто зашёл вторым, тот инициатор и шлёт offer. Первый отвечает на входящий. Новый offer всегда затирает старое соединение, и это сознательное упрощение: не нужно разбирать «glare», когда оба шлют offer одновременно. Переподключения перестают быть отдельной наукой.

Через сервер ходят ровно два типа сообщений, sdp и ice, только строкой и только участнику той же комнаты. Поле from сервер ставит сам по сокету, а не берёт из тела сообщения. Иначе один участник прикинулся бы другим за одну строку в консоли.

Дальше всплывает деталь, которую пропускают в туториалах. Идентичность вкладки лежит в sessionStorage, поэтому две вкладки — два разных участника, а перезагрузка сохраняет тот же id. Сохранённый id нужен, чтобы удержать живое WebRTC-соединение при обрыве WebSocket. Но id знает второй участник комнаты, значит его надо защитить: сервер при connect выдаёт владельцу токен, HMAC-SHA256 от id на серверном ключе, и принимает id только вместе с токеном. Без токена выдаётся новый.

Побочный эффект: «Дублировать вкладку» копирует sessionStorage. Две вкладки с одним id и валидным токеном начинали выбивать друг друга каждые 5 секунд по таймеру автопереподключения. Лечится тем, что старый сокет получает replaced, клиент после этого не переподключается и показывает кнопку «Use here».

WebRTC на практике: как tfile.io гоняет файлы из браузера в браузер — иллюстрация

Шесть констант, которые решают behaviour

Самое полезное в разборе — не код, а числа. Это дефолты, до которых автор дошёл практикой, и их можно брать как baseline для своего прототипа.

ПараметрЗначениеСмысл
Размер чанкадо 64 КБ, но не больше pc.sctp.maxMessageSizeкрупнее — рвётся на разных браузерах
Backpressureпауза при bufferedAmount > 4 МБ, продолжение на 1 МБ по bufferedamountlow, опрос раз в 100 мсвкладка не съедает память на 10-гигабайтном файле
Keepaliveping раз в 5 секунд, разрыв сокета после 10 секунд тишинымёртвый сокет виден быстро
Рестарт серверакод закрытия 1012 при pm2 restart, реконнект через 1–3 секунды со случайным разбросомдеплой не выглядит как «сервер упал»
Лимиты сигналингаmaxPayload 64 КБ → закрытие 1009, невалидный JSON → 1008мусор в сокет не проходит
TURN (eturnal)UDP/TCP 3478 и TLS 53495349 пролезает туда, где разрешён только «HTTPS-подобный» трафик

Креды TURN выдаются по схеме TURN REST API: в username зашит unix-время истечения и peer id, пароль — base64 от HMAC-SHA1 на общем секрете. Сервер и eturnal делят один секрет, база пользователей не нужна. Клиент получает список ICE-серверов от api в сообщении connect, а не носит в бандле.

Чем отличается от «файлообменника»

Классический файлообменник держит байты у себя: загрузка, хранение, выдача ссылки, срок жизни файла. Здесь сервер знакомит и уходит с дороги. Расходы на диски и CDN исчезают, зато появляется биллинг relay-трафика. Логика поменялась: вы платите не за хранение, а за неудачные сетевые сценарии.

WebRTC при этом перестаёт быть технологией только для звонков. Data channel тянет произвольные байты, и в тегах статьи рядом стоят OPFS и CompressionStream — то есть файл можно писать на диск порциями и жать на лету, не собирая его целиком в памяти. Для внутренней утилиты «перекинуть логи с машины коллеги» или «отдать 10 ГБ подрядчику без облака» этого хватает.

Кому нужно и где подвох

Продуктовая команда без бэкенда, поддержка, которая просит клиента прислать дампы, внутренний инструмент для больших файлов. Всё это реально. Дальше начинаются ограничения, и их лучше знать до, а не после запуска.

  • TURN-биллинг. Каждый посетитель получает креды, значит теоретически может гонять через ваш релей чужой трафик. Origin-фильтр, лимит по IP в nginx — полумеры. Eturnal ограничивает полосу только для TCP/TLS-клиентов и не имеет квоты на аллокации. Честный предохранитель один: billing alarm в AWS, relay-трафик тарифицируется как исходящий трафик EC2.
  • Safari и локальная сеть. Самая неочевидная находка. Два устройства в одной Wi-Fi, TURN в списке ICE-серверов, и Safari в роли инициатора выбирает relay вместо прямого маршрута по LAN. Файлы едут из соседней комнаты через AWS. Обход: сервер сообщает обеим сторонам флаг sameNetwork (совпал публичный IPv4 или одна IPv6-подсеть /64), первая попытка идёт без TURN, через 5 секунд неудачи — повтор уже с TURN, а в sessionStorage ставится метка directFailed на час, чтобы не ждать эти 5 секунд каждый раз.
  • Подмена SDP. Сигналинг можно перехватить и подсунуть свой медиапуть. Защита простая и рабочая: обе стороны хешируют пару DTLS-отпечатков и сверяют код, 40 бит превращаются в 12 цифр вместо привычных 6. Пользователи сравнивают голосом, как в старом добром SSH.
  • Разрыв сигналинга не равен разрыву передачи. Data channel живёт без сервера. Клиент различает peer-left без причины (сокет отвалился, открытый канал сохраняется, передача ждёт) и peer-left с reason 'left' (явный выход, передача сразу падает с ошибкой). Мелочь, из которой растут самые злые баги.
Важный нюанс: уход с сигналинга ≠ уход из комнаты.
WebRTC на практике: как tfile.io гоняет файлы из браузера в браузер — иллюстрация

Брать или подождать: вердикт за 30 секунд

Брать, если нужен узкий сценарий «два человека, файл, без облака», а команда готова держать свой TURN и следить за его счётом.

Подождать, если вы рассчитываете на «сервер не нужен вообще». Сигналинг, TURN, лимиты и защита от подмены — это бэкенд, просто маленький. И не обещайте скорость: без замеров на своих маршрутах любые цифры производительности — фантазия.

Вердикт

P2P-передача файлов из браузера в браузер работает и стоит дешевле классического файлообменника — до первого серьёзного relay-трафика. Главный риск проекта не в WebRTC, а в биллинге TURN и в Safari, который тихо уводит соседей по офису через AWS. Всё остальное — инженерия, которую уже разобрали по константам.

FAQ

Можно совсем без сервера?

Нет. Кто-то должен передать sdp и ice. Дальше сервер не нужен, но на старте он обязателен.

Почему файл идёт через релей, если оба в одной сети?

Прямой маршрут не сложился за 5 секунд, или Safari выбрал relay-кандидата. Проверяется флагом sameNetwork и повторной попыткой.

Как понять, что соединение подменили?

Сверкой 12-значного кода из хеша пары DTLS-отпечатков. Пока коды совпали, канал ваш.

Папка на 5000 файлов — это реально?

Реально, но требует очередности и backpressure. Иначе вкладка ляжет.

Что делать на этой неделе

Возьмите один внутренний сценарий с большими файлами, поднимите eturnal и прогоните его на своих сетях: офисный Wi-Fi, мобильный оператор, корпоративный VPN. Поставьте billing alarm до того, как позовёте первых пользователей. Если relay-трафик удивит вас уже на пилоте — вы всё сделали правильно.

Читайте также