Создание интерактивной голосовой почты для спортивных болельщиков с помощью Twilio, Browserify, Angular и Less CSS (Часть вторая)

Автор: | 28.07.2026

Мой родной город до сих пор бурлит после Матча всех звёзд MLB, и это отличный момент, чтобы поделиться второй частью серии статей о создании интерактивной голосовой почты для спортивных болельщиков. В первой части мы рассмотрели основные шаги, необходимые для построения системы интерактивной голосовой почты, подобной той, что развёрнута на leavejoeamessage.com. Мы узнали, как настроить вебхуки в бэкенде на Node.js для обработки входящих звонков и текстовых сообщений от болельщиков, а также как сохранять эти данные в базе MongoDB. Если вы ещё не ознакомились с первой частью этого руководства, возможно, стоит сделать это сейчас.

Однако сбор всех этих забавных сообщений — это только половина дела. Нам нужен фронтенд для их отображения и воспроизведения другим болельщикам, а также административный интерфейс для защиты от троллей. В этой части руководства мы рассмотрим инструменты и методы фронтенда, которые использовались для создания веб-интерфейса нашей системы интерактивной голосовой почты. Мы не будем разбирать каждую строку кода в примере приложения (полный исходный код доступен), но выделим ключевые концепции, которые необходимо понять, чтобы разобраться в работе приложения.

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

Для создания как публичных, так и административных страниц этого сайта я не счёл целесообразным разрабатывать полноценную модель данных пользователей. HTTP Basic-аутентификация должна обеспечить достаточную защиту для административных операций этого простого сайта. Если вы планируете что-то более сложное, возможно, стоит обратить внимание на модуль Passport. Но в этом руководстве мы будем использовать встроенную в Express 3 функцию промежуточного ПО для защиты определённых маршрутов приложения с помощью HTTP Basic-аутентификации:

Промежуточное ПО Express 3 для HTTP Basic-аутентификации принимает имя пользователя и пароль, которые мы сохранили для нашего приложения в системных переменных окружения. Этот метод проверки имени пользователя и пароля может быть негибким, но вы также можете предоставить функцию обратного вызова, которая выполнит пользовательскую логику для определения допустимости имени пользователя и пароля.

Для защиты маршрута приложения с помощью этого промежуточного ПО для базовой аутентификации мы передаём его в качестве второго аргумента после URL при определении маршрута, как в следующих функциях контроллера. Только один из маршрутов фактически отдаёт HTML (/admin), в то время как остальные обеспечивают работу API-эндпоинтов, возвращающих только JSON, которые будут использоваться через асинхронные HTTP-запросы из нашего AngularJS-приложения в браузере. В данном случае мы применяем две функции промежуточного ПО: первая проверяет, что входящий запрос был сделан по HTTPS, а вторая проверяет имя пользователя и пароль для HTTP Basic-аутентификации:

С момента написания этого примера была выпущена Express 4, заменившая Express 3. Если вы используете Express 4 и следуете этому руководству, то быстро заметите, что промежуточное ПО для базовой аутентификации и почти всё встроенное промежуточное ПО были удалены из основного модуля Express. Для текущих и будущих нужд в HTTP Basic-аутентификации я рекомендую модуль http-auth из npm. Это гибкий и многофункциональный модуль с функцией промежуточного ПО, которую можно использовать вместе с Express.

Теперь, когда у нас настроены защищённые маршруты для административного раздела, давайте посмотрим, как мы рендерим HTML для нашего веб-приложения и отдаём его клиенту.

Для рендеринга HTML пользовательского интерфейса как на административной, так и на главной страницах мы используем шаблонизатор EJS. Существует множество шаблонизаторов для Express и Node, но как разработчик старых Ruby-приложений, привыкший к ERB, EJS и его сходство с обычным HTML кажутся мне более естественными. Однако EJS не является шаблонизатором по умолчанию для Express, поэтому нам нужно настроить его с помощью API конфигурации Express при создании веб-приложения в app.js:

Шаблон главной страницы, обращённой к пользователю, хранится в директории «views» и на самом деле не содержит большого количества разметки:

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

Рассмотрим частичный шаблон для секции наших страниц. Здесь мы задаём заголовок страницы и подключаем CSS-файлы, необходимые для стилизации страницы:

Вы заметите, что это первая страница, где серверные данные вставляются в страницу с помощью тега <%=. Два динамических элемента данных — это заголовок страницы и базовое имя CSS-файла, который будет использоваться для стилизации страницы. Эти значения передаются движку рендеринга при отдаче страницы Express: Частичный шаблон, используемый для загрузки необходимого JavaScript для работы интерфейса, выглядит так: В браузер запрашивается только один файл (хотя это особый файл, как мы увидим далее), который использует то же базовое имя файла, что и CSS страницы. Основная часть административной и главной страниц на самом деле одинакова — это большой, красивый список сообщений, который будет отображаться либо администратору, либо посетителю публичной страницы. Он содержит много специфичной для AngularJS разметки, которую мы подробно рассмотрим позже. Но сейчас важно знать, что он написан таким образом, что административные функции отображаются только пользователям, вошедшим с помощью HTTP Basic-аутентификации. Теперь, когда у нас настроены защищённые страницы, давайте посмотрим, как мы будем писать CSS и JavaScript для их работы. При написании достаточно сложного фронтенд-кода на JavaScript система загрузки модулей может быть чрезвычайно полезной для управления зависимостями и написания более чистого и поддерживаемого кода. RequireJS — популярный загрузчик модулей для браузера, который можно комбинировать с такими инструментами, как Yeoman и Bower, для управления и загрузки зависимостей. Это отличные инструменты, но при использовании Node.js на бэкенде мне нравится писать модули в стиле CommonJS и для фронтенда. Таким образом, я могу управлять зависимостями фронтенда с помощью npm и package.json, как уже делаю это, не включая дополнительные библиотеки и инструменты в свой рабочий процесс. А поскольку мой код написан и структурирован одинаково как для фронтенда, так и для бэкенда, мне проще переключаться между клиентской и серверной работой. Использование одной и той же системы модулей на фронтенде и бэкенде также позволяет легко делиться кодом между клиентом и сервером, когда это уместно (хотя и не так часто, как нам, вероятно, хотелось бы). Технология, которая делает это возможным, — это Browserify. Получив один файл JavaScript в стиле CommonJS в качестве точки входа, он анализирует зависимости этого модуля и генерирует один файл JavaScript, который содержит код вашего модуля, а также все необходимые зависимости. Этот файл затем может быть отдан браузеру и выполнен на веб-странице (или в любом другом месте, где работает JavaScript). Модули в стиле Node в браузере — довольно круто, на мой взгляд. Давайте посмотрим, как мы использовали Browserify в этом приложении. Начнём с рассмотрения реального кода, который мы хотим загрузить и выполнить в браузере. JavaScript-файл, управляющий основной публичной страницей, выглядит так: Загрузка модулей в стиле Node может сначала показаться неуместной для клиентского кода, но посмотрите на все преимущества! Нет необходимости в самовызывающихся функциях для защиты глобальной области видимости. Подключайте зависимости с помощью относительных путей в вашем проекте. Определяйте публичные интерфейсы для модулей. Всё лучшее из CommonJS прямо в вашем браузере. Обратите внимание, что Angular (по умолчанию) требует, чтобы конструкторы ваших контроллеров были глобально доступны, поэтому мы присваиваем их объекту window. В остальном весь наш код содержится в модулях. Для загрузки этого кода в браузере мы включаем один тег script с источником «/browser/home.js»: Внимательные читатели могли уже заметить, что такого файла нет в директории «public» нашего проекта, где хранятся другие статические ресурсы. Браузер не видит другие файлы нашего приложения за пределами этой директории, так как же этот файл доступен в браузере? На самом деле мы используем Browserify для генерации этого файла на сервере с помощью промежуточного ПО Express. Мы генерируем файл в результате веб-запроса, а затем сохраняем полученный файл в памяти для последующей отдачи. Этот шаг происходит здесь, в app.js. По сути, любой URL с префиксом «/browser» и расширением .js вызовет это промежуточное ПО. Оно использует Browserify для упаковки модуля CommonJS из директории «browser» в нашем проекте и отдаёт его обратно клиенту. Следует отметить, что другой распространённый рабочий процесс — сначала «браузерифицировать» на сервере, а затем отдавать полученный файл как статический ресурс. Но в нашем случае эта система промежуточного ПО должна справиться с задачей. Основная часть нашего JavaScript-кода в этом приложении основана на AngularJS — клиентском MVC-фреймворке от Google. Давайте немного углубимся в этот код и поймём, как Angular помогает нам отображать список голосовых и текстовых сообщений, каждое из которых имеет свои собственные элементы управления и поведение. AngularJS — популярный выбор для создания насыщенных пользовательских интерфейсов в браузере. Angular расширяет HTML-разметку, предоставляя привязку данных, манипуляцию с DOM и обработку событий. Как разработчик, вы можете писать объекты-контроллеры, в значительной степени отделённые от DOM, что обеспечивает полезное разделение ответственности. Angular — это большой фреймворк, который быстро развивается и меняется. Изучение Angular выходит за рамки этого поста, но если вам интересно узнать больше, вы можете ознакомиться с каноническим учебником по Angular, продвинутыми уроками на egghead.io и ещё одним постом от моего уважаемого коллеги Картера Рабаса в его эпической серии уроков по голосованию через SMS. В этом руководстве мы сосредоточимся на том, как мы используем контроллеры и иерархию областей видимости для управления данными и состоянием приложения, а также на директивах интерфейса, необходимых для отображения этих данных пользователю. В приложении на AngularJS данные и состояние интерфейса вашего приложения будут содержаться в иерархии объектов областей видимости. Какая вкладка выбрана, последние сообщения, полученные с сервера, имя текущего пользователя — все эти данные будут храниться в объекте области видимости. Каждое приложение на Angular будет иметь как минимум один объект области видимости — корневую область. Это подходящее место для хранения данных и состояния, относящихся ко всему приложению, например, информации о вошедшем пользователе. Более мелкие части вашего приложения, управляемые объектом-контроллером, также будут иметь свои собственные объекты областей видимости и связанные с ними данные. В нашем приложении два контроллера Angular выполняют большую часть работы. Контроллер MessageList отвечает за управление списком сообщений, отображаемых пользователю. Контроллер Message отвечает за управление взаимодействием пользователя и отображением отдельных сообщений. На странице у нас будет только один контроллер MessageList, но множество контроллеров Message — по одному для каждого отображаемого сообщения. Вот примерная схема иерархии областей видимости и контроллеров в нашем приложении: Как мы видели ранее, мы сделали конструкторы контроллеров Angular доступными в глобальном контексте страницы, присвоив их объекту window. Прежде чем углубляться в специфические для нашего приложения контроллеры, давайте рассмотрим общую структуру контроллера Angular в стиле CommonJS: На высоком уровне наш конструктор инициализирует область видимости контроллера необходимыми данными и предоставляет интерфейс для изменения текущего состояния области видимости. Контроллер MessageList поддерживает состояние списка сообщений, полученных с сервера, и фильтрует этот список на основе ввода пользователя. Контроллер Message управляет состоянием отдельного сообщения в списке и реагирует на действия пользователя по удалению, добавлению в избранное или воспроизведению сообщения. Хочу особо отметить часть контроллера сообщений, которая управляет воспроизведением записанных голосовых сообщений. Чтобы обеспечить хорошую работу во всех браузерах, я использовал библиотеку HTML5 audio buzz. Buzz позволяет создавать объекты звука с набором медиа-источников, поддерживаемых во всех браузерах. Здесь мы создаём новый объект звука с медиа-URL, возвращаемыми вебхуками Twilio для завершённых записей: По умолчанию URL медиаресурсов, предоставляемые Twilio, не имеют расширения файла. Стандартный MIME-тип файла — аудио WAV, но вы можете указать другие форматы, добавив соответствующее расширение файла. В этом примере мы используем как WAV, так и MP3 форматы, чтобы охватить все возможности. Теперь, когда у нас настроены контроллеры, давайте посмотрим, как мы связываем их с «представлением» в клиентском MVC Angular. В Angular компонент «Представление» в триаде MVC — это HTML, дополненный пользовательскими атрибутами, определяющими привязку данных, визуальное отображение и обработку событий. Эти пользовательские атрибуты называются директивами. Целые простые приложения можно создавать, используя только директивы, но обычно вам потребуется более сложная логика, определённая в контроллерах. Чтобы связать контроллер с фрагментом DOM, можно использовать директиву «ng-controller»: Это позволит вам получить доступ к данным, хранящимся в области видимости этого контроллера, в вашем интерфейсе. Это также позволит связать события взаимодействия с функциями-обработчиками в области видимости контроллера. Для отображения списка сообщений в области видимости MessageList мы используем директиву ng-repeat для рендеринга контейнерного div для каждого сообщения: В том же родительском теге вы увидите, что для каждой строки, представляющей сообщение, объявлен контроллер. Это создаёт новый контроллер Angular с областью видимости, называемой «message», для каждого голосового или текстового сообщения в списке. Данные для каждого отдельного сообщения отображаются с помощью языка шаблонов Angular, где необработанные значения обычно заключены в двойные фигурные скобки: Как и в любом приложении на AngularJS, здесь происходит многое, но благодаря концепции вложенных областей видимости (даже внутри повторяющихся шаблонов для коллекции значений) мы можем очень легко создавать представления и контроллеры для более мелких частей нашего приложения. Мы подробно обсудили HTML и JavaScript, но что насчёт CSS сайта? Мы не будем рассматривать конкретные стили или многое о адаптивном дизайне сайта (вы можете изучить это здесь), но я хотел бы выделить ключевой компонент этого стека фронтенда — Less CSS. Less CSS — это препроцессор CSS, который добавляет множество полезных функций, отсутствующих в обычном CSS. Если вы ещё не используете Less (или Sass, или что-то подобное), определённо стоит взглянуть. Больше никогда не придётся копировать и вставлять один и тот же шестнадцатеричный код цвета! В этом приложении мы используем Less вместо обычного CSS для стилизации наших страниц. CSS нашей страницы подключается в элементе head: Но, как видите, используется стандартное расширение CSS. Как и в случае с нашим браузерным JavaScript, у нас настроено серверное промежуточное ПО для перехвата запросов к /styles/*.css и рендеринга таблиц стилей, скомпилированных из Less. Но вместо того, чтобы писать своё собственное, мы используем созданное сообществом промежуточное ПО для Less в нашем app.js здесь: Это промежуточное ПО автоматически компилирует Less-файлы с тем же именем, запрошенные браузером, если они находятся в директории /public/styles. Таким образом, мы можем использовать наши прекрасные Less-таблицы стилей в браузере с импортами, миксинами и переменными с минимальными усилиями: Создание фронтенда для наших веб-приложений может быть довольно трудоёмким процессом, но, к счастью, инструменты никогда не были лучше. Рендеринг динамического HTML с помощью шаблонизатора, такого как EJS на сервере, может справиться с задачей на начальном этапе, но современный стек фронтенда обычно требует немного больше, чем HTML, сгенерированный на сервере. Мы увидели, как можно привнести модульность и управление зависимостями Node.js в JavaScript-код, работающий на клиенте, с помощью Browserify. Мы изучили, как можно создать насыщенный фронтенд с мощной привязкой данных с помощью AngularJS. Мы также кратко поработали с Less, который сглаживает многие острые углы CSS. Объединив этот фронтенд с нашим серверным кодом из первой части, мы получаем полнофункциональный фан-сайт, который создаёт личный способ общения болельщиков с их любимой звездой или брендом. В следующей части этой серии мы выйдем за рамки маркетингового кейса и покажем, как можно создавать транскрипции голосовых сообщений человеческого качества с помощью API транскрипции партнёра Twilio — Rev.com. Спасибо за чтение, и не стесняйтесь задавать любые вопросы или оставлять комментарии прямо на этой странице или в Twitter @kevinwhinnery!