Frontend Engineer · Фев 2024 — Июль 2025

ОхотАктив · Streamerce

postMessage · cross-domain · React · TypeScript

Что это

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

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

Как связаны эфир и магазин

Из коробки синхронизация между эфиром и магазином шла через query-параметры в ссылках: id товаров списком в URL, переход в корзину — тоже через query. Штатного API или колбеков у виджета не было. Состояние при этом жило в тексте адреса и зависело от формата ссылки, а связь эфир↔магазин нужна была двусторонняя и устойчивая. Задача сводилась к одному: наладить надёжный канал между iframe и страницей магазина.

postMessage

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

Кросс-домен убрали переносом на свой поддомен

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

При этом проверку origin в postMessage я не выкинул, а прописал корректные адреса: сообщения принимаются только от известного источника. Открытый postMessage без проверки origin — это дыра, через которую состоянием корзины может управлять кто угодно; здесь канал остался закрытым.

Двусторонняя синхронизация корзины

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

Главная опасность двустороннего обмена — рассинхрон: пока сообщение идёт туда-обратно, стороны на короткое время расходятся в том, что лежит в корзине. Я закрыл это лоадерами с обеих сторон на время обмена — интерфейс явно показывает, что идёт синхронизация, и не даёт нажать поверх ещё раз. Гонок состояний, из-за которых корзина у зрителя и в магазине разъезжались, не осталось.

Интеграция на обеих сторонах

Интеграция двусторонняя, поэтому работать должно было на обеих сторонах. Эту часть я вёл вместе с командой вендора напрямую: объяснял, что нужно сделать на их конце, и присылал готовый код, чтобы всё заработало end-to-end. По сути довёл до рабочего состояния обе половины — и свою, и интеграцию на их стороне.

Честная рамка

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

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