MacroFrame позволяет встроить ваше внешнее приложение (внешний веб-сервис) прямо в интерфейс MacroCRM — в виде блока на странице или пункта навигации. Внешнее приложение открывается в iFrame и автоматически получает контекст той страницы, на которой оно показано: например, id сделки, id объекта, имя и телефон клиента. Это превращает внешнее приложение в полноценную часть рабочего места сотрудника, не требуя от него переключаться между вкладками и вручную переносить данные.
Статья рассчитана на две аудитории:
- Администратора компании, который настраивает размещение внешнего приложения в интерфейсе MacroCRM;
- Разработчика внешнего приложения, который встраивает свой сервис и принимает контекстные параметры (в том числе расшифровывает зашифрованные параметры).
#Что это и что даёт внешним приложениям
MacroFrame — это механизм размещения внешних приложений внутри рабочих экранов MacroCRM. Компания регистрирует размещение: указывает адрес внешнего приложения, выбирает место в интерфейсе и при необходимости задаёт ключ шифрования. Платформа сама встраивает внешнее приложение в выбранное место и передаёт ему параметры текущего контекста.
Что это даёт внешнему приложению:
- Контекст без интеграции по API. Внешнее приложение получает идентификаторы и данные прямо из той страницы, где открыто (id сделки, id объекта, id контакта, имя и телефон клиента). Не нужно спрашивать у пользователя, с какой сделкой он работает, — это уже известно.
- Присутствие в рабочем потоке. Сотрудник видит внешнее приложение на странице сделки, заявки или карточки контакта, в навигации раздела или на главной — там, где он уже работает.
- Управляемая безопасность. Параметры можно передавать в зашифрованном виде, доступ к камере/микрофону/акселерометру выдаётся точечно, а сам фрейм ограничен sandbox-атрибутом: ему не выдан
allow-top-navigation, поэтому он не может перенаправить родительскую вкладку и увести пользователя с платформы.
#Как это работает
- Администратор компании создаёт размещение: указывает URL внешнего приложения, выбирает место и параметры безопасности.
- Когда пользователь открывает страницу (или пункт навигации), MacroCRM подставляет в это место iFrame с адресом внешнего приложения.
- К адресу внешнего приложения MacroCRM добавляет GET-параметры контекста — набор параметров зависит от места размещения (см. таблицу ниже).
- Если у размещения задан «Ключ безопасности», все параметры сворачиваются в один зашифрованный параметр
data. Если ключ не задан — параметры уходят обычной строкой запроса. - Внешнее приложение загружается в iFrame, читает параметры и показывает нужный контент.
Несколько технических следствий, важных для разработчика:
- Параметры всегда подставляются на стороне сервера MacroCRM, фронтенд их не модифицирует. Это значит, что вы получаете данные ровно в том виде, в каком их сформировала платформа.
- Строка запроса вставляется перед фрагментом (
#...). Если ваше внешнее приложение — SPA с hash-роутингом видаhttps://app/#/route, параметры не попадут во фрагмент, а останутся в обычной части запроса и дойдут до сервера приложения. Адрес соберётся какhttps://app/?estate_id=123#/route. - Для пункта навигации внешнее приложение открывается на отдельной полноэкранной странице, ссылка на которую подписана и привязана к конкретному размещению. Пользователь не может подделать параметры, отредактировав адрес вручную.
#Места размещения
Место — это точка интерфейса, куда можно встроить внешнее приложение. Есть два типа мест.
- Блок на странице. Внешнее приложение встраивается прямо в разметку страницы как панель. Высота задаётся в настройках размещения (поле «Высота, px»), ширина всегда 100%.
- Пункт навигации. В навигацию раздела добавляется пункт с заданным заголовком. Внешнее приложение открывается на отдельной полноэкранной странице по адресу
/account/mf/<id>/. Такой пункт открывается в новой вкладке браузера: полноэкранная страница не имеет собственной поднавигации, и новая вкладка не уводит пользователя из раздела, где он находился.
#Таблица мест и передаваемых параметров
| Место | Тип | GET-параметры |
|---|---|---|
| Страница сделки: после плана платежей | Блок на странице | estate_id — id объекта, deal_id — id сделки, client_name — имя клиента, client_phone — телефон клиента |
| Страница сделки: под лентой событий | Блок на странице | estate_id — id объекта |
| Страница заявки: под лентой событий | Блок на странице | estate_id — id заявки, contacts_id — id контакта клиента |
| Карточка контакта: под лентой событий | Блок на странице | contacts_id — id контакта |
| Карточка контакта: низ страницы | Блок на странице | contacts_id — id контакта |
| Навигация: страница объекта/сделки | Пункт навигации | estate_id — id объекта |
| Навигация: главная страница | Пункт навигации | параметров нет |
| Навигация: отчёты | Пункт навигации | параметров нет |
Точный набор параметров для выбранного места всегда показан в форме размещения в блоке «GET-параметры» — сверяйтесь с ним при разработке внешнего приложения.
#Ключ безопасности и шифрование параметров
По умолчанию параметры контекста передаются внешнему приложению открытой строкой запроса:
https://app.example.com/widget?estate_id=123&deal_id=456&client_name=...&client_phone=...
Если данные не должны быть видны в открытом виде в адресе iFrame, задайте у размещения «Ключ безопасности». Тогда все параметры шифруются и передаются в одном GET-параметре data:
https://app.example.com/widget?data=<urlencoded base64 шифротекста>
#Зачем нужен ключ безопасности
Ключ безопасности — это общий секрет между вашим внешним приложением и MacroCRM. Платформа шифрует им параметры, ваше внешнее приложение тем же ключом их расшифровывает. Без знания ключа постороннему недоступно содержимое параметра data.
#Формат шифрования
Шифрование выполняется алгоритмом AES-128-CBC. Формат параметра data:
data = urlencode( base64( iv . AES-128-CBC( json(params), key16, iv ) ) )
где:
iv— 16 случайных байт (вектор инициализации), генерируется заново на каждый запрос и ставится в начало результата;key16— первые 16 байт от сырого (binary) SHA-256 от вашего ключа безопасности:substr(hash('sha256', key, true), 0, 16). Эта нормализация позволяет использовать ключ любой длины;json(params)— параметры, сериализованные в JSON с флагомJSON_UNESCAPED_UNICODE(кириллица сохраняется как есть, без\uXXXX).
То есть в base64 кодируется конкатенация iv (16 байт) и шифротекста, а весь результат дополнительно проходит urlencode, чтобы безопасно встать в строку запроса.
#Алгоритм расшифровки на стороне приложения
Пошагово:
- Прочитать GET-параметр
data(он уже URL-декодирован вашим веб-сервером при разборе query string). - Декодировать строку из base64 — получите бинарный буфер.
- Отделить первые 16 байт — это
iv. Остаток — шифротекст. - Вывести 16-байтный ключ: взять SHA-256 от вашего ключа безопасности в сыром (binary) виде и оставить первые 16 байт.
- Расшифровать остаток алгоритмом AES-128-CBC с этими
key16иiv. - Разобрать получившийся JSON — это исходный набор параметров.
Пример на PHP:
$key = 'ваш-ключ-безопасности';
$raw = base64_decode($_GET['data']); // urldecode уже сделал веб-сервер
$iv = substr($raw, 0, 16);
$ciphertext = substr($raw, 16);
$key16 = substr(hash('sha256', $key, true), 0, 16);
$json = openssl_decrypt($ciphertext, 'AES-128-CBC', $key16, OPENSSL_RAW_DATA, $iv);
$params = json_decode($json, true); // ['estate_id' => 123, ...]
Пример на Node.js:
const crypto = require('crypto');
const key = 'ваш-ключ-безопасности';
const raw = Buffer.from(req.query.data, 'base64');
const iv = raw.subarray(0, 16);
const ciphertext = raw.subarray(16);
const key16 = crypto.createHash('sha256').update(key).digest().subarray(0, 16);
const decipher = crypto.createDecipheriv('aes-128-cbc', key16, iv);
const json = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
const params = JSON.parse(json); // { estate_id: 123, ... }
#Как настраивать размещения
Размещения настраиваются в разделе «Компания» → «MacroFrame» (адрес /account/company/macroFrame/). По сути это конструктор: вы заполняете форму, а платформа дальше сама встраивает внешнее приложение в выбранное место.
#Доступ
Раздел доступен пользователю с правом редактирования компании. На текущем этапе он открыт только в режиме разработчика. При обращении без этого режима система отвечает HTTP 403 с сообщением «Раздел доступен только разработчикам». Все операции создания, изменения и удаления размещений выполняются только методом POST; попытка вызвать их не POST-методом отклоняется с HTTP 405 («Метод не поддерживается»). Размещения строго изолированы по компании — чужие записи недоступны.
#Поля формы размещения
Чтобы добавить размещение, нажмите Добавить размещение и заполните форму. Сначала выбирается место — оно определяет, какие остальные поля появятся и будут обязательными.
| Поле | Применимость | Описание |
|---|---|---|
| «Место размещения» | Всегда | Выбирается из списка мест. Задаётся при создании и далее неизменно — при редактировании поле заблокировано, так как смена места поменяла бы и набор параметров, и способ вывода. |
| «GET-параметры» | Всегда (справка) | Справочный блок: какие параметры контекста получит внешнее приложение в выбранном месте. |
| «Название размещения (внутреннее)» | Всегда | Обязательное, до 255 символов, уникальное в пределах компании. Пользователям не видно — нужно, чтобы различать размещения в списке. |
| «URL приложения» | Всегда | Обязательный адрес внешнего приложения, только https, до 2048 символов. Нельзя указывать IP-адрес и нельзя указывать домен самой системы; поддомены системы разрешены. |
| «Высота, px» | Только блок на странице | Высота встраиваемого блока: от 100 до 5000, шаг 10. Ширина всегда 100%. |
| «Заголовок пункта навигации» | Только пункт навигации | Обязательный текст пункта, который увидит пользователь в навигации раздела. |
| «Ключ безопасности» | Всегда | Необязательный, до 64 символов. Кнопка Сгенерировать ключ создаёт криптослучайный ключ. Если задан — параметры шифруются. В списке размещений у такого размещения перед URL показывается иконка-замок. |
| «Доступы фрейма» | Всегда | Тумблеры «Микрофон», «Камера», «Акселерометр», включающие соответствующую allow-политику iFrame. По умолчанию выключены. |
| «Размещение включено» | Всегда | Тумблер активности. Выключение мгновенно убирает фрейм со страниц и из навигации. |
Ограничения поля «URL приложения» заданы намеренно:
- только https — иначе вставка по http ломает защищённую страницу кабинета (mixed content);
- запрет IP-адреса — защита от обращения к внутренним хостам (SSRF);
- запрет домена системы — защита от фишинга: интерфейс системы внутри фрейма самой системы маскировал бы подмену. Поддомены системы при этом разрешены, так как это легитимные внешние сервисы.
#Как пользоваться
#Сценарий администратора
- Откройте «Компания» → «MacroFrame».
- Нажмите
Добавить размещение. - Выберите «Место размещения». После выбора появятся остальные поля и справка по передаваемым параметрам.
- Задайте «Название размещения (внутреннее)» — оно нужно только вам, чтобы различать размещения в списке.
- Укажите «URL приложения» (только
https). - Для блока на странице задайте «Высота, px». Для пункта навигации задайте «Заголовок пункта навигации».
- Если параметры нужно передавать в зашифрованном виде, задайте «Ключ безопасности» (можно нажать
Сгенерировать ключ) и передайте этот же ключ разработчику внешнего приложения. - При необходимости включите нужные «Доступы фрейма» (микрофон, камера, акселерометр).
- Убедитесь, что «Размещение включено», и сохраните.
- Откройте соответствующую страницу (или пункт навигации) — внешнее приложение появится на месте.
#Сценарий разработчика приложения
- Получите от администратора компании: выбранное место, точный набор GET-параметров (по справке «GET-параметры» в форме) и, если используется шифрование, «Ключ безопасности».
- Реализуйте приём параметров:
- без ключа — читайте обычные GET-параметры (
estate_id,deal_id,client_name,client_phone,contacts_idв зависимости от места); - с ключом — читайте параметр
dataи расшифровывайте его по алгоритму выше.
- без ключа — читайте обычные GET-параметры (
- Учтите, что внешнее приложение работает в iFrame с фиксированным sandbox-атрибутом (см. ниже). Если вам нужны камера, микрофон или акселерометр, попросите администратора включить соответствующие «Доступы фрейма».
- Если внешнее приложение — SPA с hash-роутингом, помните: параметры приходят в обычной части запроса перед
#, а не во фрагменте. - Не полагайтесь на кэширование адреса iFrame при включённом шифровании — он меняется на каждый запрос.
#Безопасность и ограничения
- Только https. Внешнее приложение встраивается в защищённый кабинет; http-вставка ломала бы страницу и была бы небезопасна.
- Запрет IP-адреса и домена системы в URL. IP — типовой вектор SSRF (обращение к внутренним хостам), домен системы — вектор фишинга. Поддомены системы разрешены как легитимные внешние сервисы.
- Sandbox-атрибут iFrame. Фрейм запускается с фиксированным набором sandbox-разрешений, одинаковым для блока на странице и для полноэкранной страницы. Внешнему приложению разрешено: отправлять формы (
allow-forms), исполнять собственные скрипты в своём origin (allow-scripts+allow-same-origin), открывать всплывающие окна (allow-popups) и выводить их из песочницы (allow-popups-to-escape-sandbox), показывать модальные диалоги (allow-modals), запрашивать доступ к хранилищу по действию пользователя (allow-storage-access-by-user-activation). Комбинацияallow-scripts+allow-same-originфактически снимает изоляцию для собственного origin приложения — то есть полноценной «песочницы» для самого приложения нет (это согласуется с тем, что URL задаёт только разработчик). Ключевое реальное ограничение в том, что фрейму не выданallow-top-navigation: он не может перенаправить родительскую вкладку и увести пользователя с платформы. Набор sandbox-разрешений фиксирован платформой и не настраивается. - Точечный доступ к устройствам. Камера, микрофон и акселерометр доступны внешнему приложению только при включённом соответствующем тумблере в размещении.
- Шифрование параметров. При заданном ключе безопасности параметры не видны в открытом виде в адресе iFrame. Это формат для несекретных данных, целостность ссылок полноэкранных страниц обеспечивается отдельно (см. ниже).
- Защита полноэкранных ссылок. Ссылки на полноэкранную страницу пункта навигации (
/account/mf/<id>/...) собираются только платформой. Сегмент с параметрами подписан HMAC-SHA256 и привязан к id размещения: пользователь не может открыть фрейм с произвольными параметрами, отредактировав адрес, и не может «переклеить» подписанный сегмент на другое размещение. Если у размещения параметров нет, ссылка короткая (/account/mf/<id>/) и подписи не требует. - Подпись ссылок завязана на платформенный секрет. Сегмент
/account/mf/<id>/...подписывается общим секретом инсталляции (SECURITY_SALT). При его ротации все ранее сформированные ссылки полноэкранных страниц перестают открываться (отдают «Размещение не найдено») и пересобираются платформой автоматически при следующем заходе в раздел — это ожидаемый эффект, отдельный от смены ключа безопасности конкретного размещения (последняя ломает только расшифровку параметраdata). - Единый ответ «не найдено». Полноэкранная страница загружает размещение строго по паре «id + компания текущей сессии». Чужое, удалённое или выключенное размещение, а также битая подпись отвечают одинаково — «Размещение не найдено». Перебор id ничего не раскрывает.
- Изоляция по компании. Размещение всегда привязано к компании из текущей сессии; прочитать, изменить или удалить чужое размещение нельзя.
#Важные нюансы
- Выключение размещения действует мгновенно. Как только «Размещение включено» переведено в выключенное состояние, фрейм сразу исчезает со страниц и из навигации — выборка берёт только активные размещения.
- Смена ключа безопасности ломает расшифровку. Если вы меняете «Ключ безопасности» у размещения, внешнее приложение перестанет расшифровывать параметры старым ключом. Меняйте ключ синхронно с приложением.
- Зашифрованный адрес не кэшируется. Из-за случайного вектора инициализации адрес iFrame с параметром
dataразличается на каждый запрос. - Ротация платформенного секрета гасит полноэкранные ссылки. Смена
SECURITY_SALTинвалидирует подпись всех ранее выданных ссылок/account/mf/; они перестают открываться и пересобираются платформой при следующем заходе в раздел. - Место и тип нельзя сменить после создания. Если нужно перенести внешнее приложение в другое место, создайте новое размещение, а старое удалите или выключите.
- Название размещения уникально в пределах компании. Двух размещений с одинаковым внутренним названием в одной компании быть не может.
- Параметры подставляет платформа. Внешнее приложение не управляет составом GET-параметров — он жёстко определён местом размещения.