Сотрудник открывает страницу личного кабинета и нажимает кнопку вызова прямо в интерфейсе. Через секунду раздаётся гудок, а рядом с карточкой клиента появляется таймер разговора и поле для заметок. Никакого отдельного приложения, никакого второго устройства, никакой гарнитуры с проводами. Так выглядит работа с телефонией, встроенной в веб-интерфейс, и за этим сценарием стоит связка из двух технологий с очень разной историей.

SIP остаётся главным протоколом сигнализации для интернет-телефонии

Протокол установления сеанса, известный под сокращением SIP, появился в 1996 году. Его разработали четверо инженеров, а исходная задача была скромной: организовывать многоадресные мультимедийные сеансы в исследовательской сети. Через три года протокол получил статус стандарта в документе RFC 2543, а в июне 2002 года был пересмотрен в RFC 3261, который и считается основной версией.

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

По устройству это текстовый протокол. Он вобрал в себя элементы двух знакомых веб-разработчику технологий: гипертекстового протокола и почтового. Сообщения читаются глазами, запросы и ответы похожи на привычные методы и коды состояния, а адрес абонента записывается в форме, напоминающей электронную почту.

Именно текстовая природа объясняет живучесть протокола. Отладка ведётся обычным чтением логов, расширения добавляются без ломки базовой логики, а взаимодействие с веб-стеком оказывается почти естественным. За двадцать с лишним лет SIP оброс десятками расширений и стал основой не только интернет-телефонии, но и мобильной голосовой связи в сетях четвёртого поколения.

WebRTC отвечает за передачу голоса и не содержит сигнализации

Вторая половина связки выросла из покупки, совершённой крупной интернет-компанией в мае 2010 года. Компания приобрела разработчика программного обеспечения для голосовой и видеосвязи, получив вместе с ним кодеки и алгоритмы эхоподавления. Технологию открыли и передали в профильные организации по стандартизации, а в мае 2011 года представили публичный проект для связи в реальном времени из браузера.

Проект получил название WebRTC. Его спецификация десятилетие шла к статусу рекомендации: первый черновик консорциума всемирной паутины вышел в октябре 2011 года, статус кандидата в рекомендации был присвоен в ноябре 2017 года, а окончательная рекомендация появилась в январе 2021 года.

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

Ключевая особенность состоит в том, чего в WebRTC нет. Спецификация не описывает сигнализацию, то есть механизм, которым участники находят друг друга и договариваются о параметрах соединения. Обнаружение путей через преобразователи адресов решается набором приёмов, а вот обмен управляющими сообщениями целиком ложится на приложение. Именно в это пустое место и встраивается SIP.

SIP over WebSockets соединяет телефонную сигнализацию с веб-страницей

Чтобы SIP заработал в браузере, нужен транспорт, доступный из сценария. Обычный сокет веб-странице недоступен по соображениям безопасности, зато есть протокол WebSocket, который умеет держать постоянное двустороннее соединение. Расширение SIP для работы через WebSocket описано в документе RFC 7118.

Схема получается такой. Браузер открывает защищённое соединение с сервером телефонии и обменивается по нему текстовыми сообщениями SIP. Медиапоток идёт отдельно, напрямую между участниками или через промежуточный узел, если прямой путь недоступен. Сигнализация и голос живут в разных каналах, и путать их нельзя.

Типовая последовательность установления вызова выглядит так:

  1. браузер регистрируется на сервере, отправляя запрос с учётными данными;
  2. при исходящем вызове отправляется приглашение с описанием предлагаемых параметров медиа;
  3. сервер отвечает промежуточными и окончательными откликами о ходе вызова;
  4. после ответа абонента стороны подтверждают установление сеанса;
  5. по завершении разговора любая из сторон отправляет запрос на разрыв;
  6. регистрация периодически обновляется, чтобы сервер знал о доступности абонента.

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

Адрес абонента выглядит как электронная почта и разрешается через службу имён

Адресация в SIP устроена знакомо для тех, кто работал с почтой. Адрес записывается в форме пользователь и домен, разделённые символом @. Пример записи выглядит так:

sip:Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript.

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

Если в адресе явно указан порт, поиск через службу имён не выполняется, клиент идёт прямо на нужный узел. Это удобно при тестировании и в закрытых контурах, где нет публичных записей.

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

Подключение библиотеки в браузере требует защищённого контекста и аккуратной обработки событий

Практическая работа начинается с подключения клиентской библиотеки, реализующей SIP поверх WebSocket. Такие библиотеки берут на себя разбор сообщений, управление транзакциями, поддержку диалогов и работу с медиа через WebRTC.

Скелет инициализации выглядит примерно так:

import { UserAgent, Registerer, Inviter, SessionState } from "sip-library";

const socket = new WebSocket("wss://sip.example.net/ws");

const userAgent = new UserAgent({
  uri: UserAgent.makeURI("sip:Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript."),
  transportOptions: { sockets: [socket] },
  authorizationUsername: "operator",
  authorizationPassword: "секрет",
  displayName: "Оператор",
});

await userAgent.start();

const registerer = new Registerer(userAgent);
await registerer.register();

console.log("Абонент зарегистрирован, можно принимать вызовы");

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

Исходящий вызов оформляется созданием объекта приглашения и последующим запуском:

async function call(target) {
  const uri = UserAgent.makeURI("sip:" + target + "@example.net");
  const inviter = new Inviter(userAgent, uri, {
    sessionDescriptionHandlerOptions: {
      constraints: { audio: true, video: false },
    },
  });

  inviter.stateChange.addListener((state) => {
    switch (state) {
      case SessionState.Establishing:
        console.log("Идёт установление соединения");
        break;
      case SessionState.Established:
        console.log("Разговор начался");
        break;
      case SessionState.Terminated:
        console.log("Разговор завершён");
        break;
    }
  });

  await inviter.invite();
  return inviter;
}

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

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

Медиапоток и сигнализация идут разными путями и требуют разной диагностики

Понимание разделения каналов спасает часы отладки. Когда вызов не устанавливается, виновата сигнализация. Когда вызов устанавливается, но звука нет, виноват медиапуть.

В первом случае проверяют соединение с сервером, правильность учётных данных, состояние регистрации и доступность порта сигнализации. Типичные симптомы: страница не переходит в состояние готовности, входящие не приходят, исходящие обрываются сразу после набора.

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

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

async function report(connection) {
  const stats = await connection.getStats();
  stats.forEach((item) => {
    if (item.type === "inbound-rtp" && item.kind === "audio") {
      console.log("Потеряно пакетов:", item.packetsLost);
      console.log("Дрожание, секунды:", item.jitter);
    }
  });
}

Такой отчёт стоит писать в журнал вместе с идентификатором вызова. При разборе инцидента через сутки никто не вспомнит детали, а сохранённые цифры дадут ответ сразу.

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

Безопасность встроенной телефонии строится вокруг учётных данных и защищённых схем

Интеграция телефонии в веб-интерфейс создаёт соблазн хранить учётные данные SIP в сценарии страницы. Так делать нельзя. Любой, кто откроет исходники, получит доступ к телефонии организации, а счёт за междугородние звонки придёт владельцу.

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

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

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

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

Встроенная телефония встраивается в рабочие сценарии и меняет их порядок

Сама по себе кнопка вызова в браузере не даёт много. Ценность появляется, когда звонок связан с контекстом рабочей задачи.

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

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

Третий сценарий касается продаж. Звонок из интерфейса сразу связывается с карточкой клиента, историей покупок и предыдущими разговорами. Такая связка ускоряет диалог и помогает говорить по делу, а не выяснять базовые сведения заново.

Четвёртый сценарий относится к внутренним коммуникациям. Сотрудники звонят друг другу прямо из корпоративного портала, а организатор встречи создаёт конференцию одним действием. Отдельный телефон при этом не нужен, достаточно компьютера с гарнитурой.

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

Кодеки и качество связи определяют впечатление сильнее интерфейса

Пользователь судит о телефонии по звуку, а не по красоте кнопок. Качество определяется кодеками и состоянием сети.

Современный стандарт для голоса это кодек Opus, обеспечивающий хорошее качество при умеренной полосе и умеющий подстраиваться под условия. Для совместимости со старым оборудованием применяются более простые кодеки, работающие на низких скоростях. Обязательный минимум для совместимости включает пару узкополосных кодеков и передачу сигналов нажатия клавиш в виде отдельных сообщений, описанных в RFC 4733. Без последнего не будут работать голосовые меню, где требуется ввод добавочного номера.

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

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

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

Сравнение подходов показывает, где интеграция оправдана, а где избыточна

Встраивание телефонии в браузер конкурирует с двумя альтернативами: отдельным приложением и аппаратным телефоном.

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

Аппаратный телефон надёжен, не зависит от компьютера и привычен, зато не связан с данными и не показывает контекст обращения.

Браузерное решение выигрывает там, где контекст важнее фоновой работы. Если сотрудник всё равно сидит в веб-интерфейсе, кнопка вызова рядом с карточкой клиента экономит действия на каждом контакте.

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

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