Перейти к содержанию

Комната и собственный signaling

Локальный поток «Разговора» готов, но браузеры ещё не знают друг о друге. Для создания медиасоединения им понадобится обмен служебными сообщениями: предложениями параметров и сетевыми кандидатами. Этот обмен называют signaling. WebRTC не предписывает единственный транспорт сигнализации, поэтому в данном уроке создадим собственную локальную WebSocket-комнату для двух проверенных участников.

Состояние lesson-03 добавляет ws 8.18.3 на стороне Node.js и форму входа в consult-42. Браузер использует встроенный WebSocket, а не серверный пакет ws. Сервер слушает только 127.0.0.1 и не подключается к публичной демонстрационной службе. Анна и Борис задаются локальной политикой; значения их паролей читатель самостоятельно передаёт окружению процесса.

Что передаёт signaling

Сигнализация передаёт сведения, необходимые для согласования соединения, но не является автоматически каналом видеопотока. Позднее по WebSocket пойдут SDP и ICE candidates. Медиа будет обслуживаться RTCPeerConnection по выбранному сетевому пути. Такое разделение означает, что работающее сообщение комнаты ещё не доказывает передачу звука, а проблема видеосвязи не всегда означает отказ WebSocket.

Комната определяет допустимых собеседников. В нашей модели вместимость равна двум, а одинаковая серверная identity не может занять два места одновременно. Если две вкладки одного участника используют одну cookie-сессию, второе подключение отклоняется. Для самостоятельного опыта с Анной и Борисом нужны независимые browser profiles, иначе общий cookie может незаметно заменить личность обеих вкладок.

Ниже показан точный upgrade-handler из server.mjs. Функция identity, коллекция members, флаг ended и объект wss определены выше в полном файле. Проверка выполняется до принятия WebSocket, поэтому отклонённый запрос не становится участником комнаты и не получает будущие сообщения другого браузера.

server.on('upgrade',(req,socket,head)=>{
 const u=new URL(req.url,ORIGIN),who=identity(req);
 if(ended || req.headers.origin!==ORIGIN || !who || u.pathname!='/signal' || u.searchParams.get('room')!=='consult-42' || members.has(who.user) || members.size>=2){socket.end('HTTP/1.1 403 Forbidden\r\n\r\n');return;}
 wss.handleUpgrade(req,socket,head,ws=>wss.emit('connection',ws,who));
});

Origin и личность

Проверка Origin ограничивает происхождение страницы, которая открывает браузерное соединение. Она полезна против неожиданных запросов стороннего сайта, использующих существующие cookie. Но Origin не является учётной записью и не заменяет вход. Сервер отдельно читает случайную session cookie и проверяет срок сессии. Отправителя всех последующих сообщений он определяет по этой проверенной записи.

Идентификатор consult-42 тоже не является секретным приглашением. Это имя ресурса, которое проверяется вместе с личностью и вместимостью. В реальном сервисе консультация могла бы принадлежать бронированию и иметь ограниченный список участников. Тогда нужен серверный поиск разрешённого бронирования. Нельзя разрешать произвольный room только потому, что клиент прислал красивое имя или знает адрес ссылки.

Локальная форма возвращает cookie с HttpOnly и SameSite=Strict. Такие параметры уместны для данного локального упражнения, но публичное использование требует HTTPS, соответствующего Secure, полноценной системы аккаунтов и политики сессий. Пароли из окружения не заменяют хранение хешей и пользовательский жизненный цикл. Здесь они обеспечивают явный механизм входа без скрытых встроенных credentials.

Когда оба участника готовы

Сервер сначала сообщает отдельному клиенту joined. Это означает успешный вход в разрешённую комнату. Когда присутствуют два участника, он создаёт случайный epoch и отправляет ready обоим. Epoch обозначает конкретную встречу двух транспортных участников. Сообщения старой пары после смены подключения должны отклоняться, даже если имя комнаты осталось прежним.

Вместе с готовностью сервер назначает значение polite. Оно понадобится в уроке об одновременных предложениях. Роль выбирается согласованно: у Анны одно значение, у Бориса другое. Нельзя независимо назначать её случайным образом в каждом браузере. Это роль переговорного алгоритма, а не право пользователя на чтение комнаты, управление камерой или завершение консультации.

После самостоятельного входа обоих участников интерфейс должен сообщить о двух участниках, но видео собеседника пока не появится. В третьем состоянии активный сценарий не создаёт peer connection. Такой промежуточный результат полезен: он позволяет изучить допускающую комнату отдельно от медиа. При авторстве signaling и вход не запускались, поэтому это ожидаемое поведение исходного кода.

Проверка сообщений

Сервер принимает только известные типы и ограничивает размер WebSocket-сообщения. Будущие description и candidate проходят минимальную проверку структуры. Поле from не доверяется браузеру: сервер подставляет подтверждённого участника при пересылке. Клиент не может получить право представляться Борисом, просто добавив это имя в JSON от сессии Анны.

Также проверяется epoch и частота сообщений. Это защищает учебный протокол от очевидного смешивания поколений и бесконечного спама одного соединения, но не является полной защитой открытого сервиса. Для выпуска потребуется общий бюджет подключений, ограничения времени комнаты, наблюдение ошибок и политика отказа. Сигнализация переносит потенциально чувствительные сетевые данные, поэтому её журналы не должны автоматически публиковать полное содержимое пакетов.

Уход и завершение

Разрыв одного сокета сообщает оставшемуся клиенту peer-left и сбрасывает epoch. Комната ждёт возвращения ограниченное время, затем завершается. Явный finish завершает встречу сразу и уведомляет обоих участников. Эти операции отличаются: временно потерявший сеть пользователь ещё может вернуться, а человек, нажавший завершение, выразил конечное намерение.

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

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

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

Оглавление серии · Исходные состояния проекта.

API сверены с первичной документацией 11 октября 2026 года. Пример имеет статус not_run: фактический запуск и наблюдение результата читателю предстоит выполнить самостоятельно.