Правки для синхронизации с основным порталом

This commit is contained in:
NikitaKalitov
2026-08-24 17:03:05 +03:00
parent 7a7fe80c94
commit 6e420470b3
5 changed files with 83 additions and 24 deletions
+4 -2
View File
@@ -8,13 +8,15 @@
1. Собрать в репозитории `portal-chart-builder`:
```
VITE_BITRIX_ORIGIN=https://ВАШ-ДОМЕН-ПОРТАЛА VITE_BASE_PATH=/local/diagram-app/ npm run build
VITE_BASE_PATH=/local/diagram-app/ npm run build
```
- `VITE_BITRIX_ORIGIN` — origin портала (без пути). Обязателен — без него приложение намеренно не запустится (защита от случайного `postMessage('*')`).
- `VITE_BASE_PATH` — подпуть, под которым будет отдаваться приложение. Должен совпадать с тем, куда положите `dist/` на шаге 2.
- `VITE_BITRIX_ORIGIN` здесь **не нужен**: приложение и портал на одном домене, origin хоста приложение берёт из `window.location.origin` в рантайме. Задавать переменную нужно только если хост на другом origin — например, локальная разработка (dev-сервер хоста и `vite preview` этого приложения на разных портах) или деплой отдельным контейнером (см. `bitrix-integration.md`, раздел 7, вариант B).
2. Скопировать содержимое `dist/` в `local/diagram-app/` внутри каталога сайта на сервере (тот же каталог, где лежит остальной код портала — `bitrix_www`/аналог на проде).
3. Готово: приложение доступно по `https://ВАШ-ДОМЕН-ПОРТАЛА/local/diagram-app/index.html`.
Если схему нужно только показать, без правки, — добавьте к `src` iframe `?mode=view`: редактор откроется в режиме просмотра, переключатель режимов будет скрыт.
**При каждом повторном деплое** (обновили React-приложение, положили новый `dist/` поверх старого) — добавляйте к `src` iframe cache-busting параметр (`?v=<дата/хэш билда>`). У `.html`-файлов на большинстве Bitrix-стендов стоит `Cache-Control` на 30 дней — без версии в URL браузер администратора может продолжать открывать старый `index.html` со старыми хэшами JS/CSS ещё месяц.
## 2. Как сформировать HTML
+23 -3
View File
@@ -120,6 +120,18 @@ class DiagramProperty
`diagram:init` с `payload: null` (или пустой строкой) — валидный случай: новый элемент без диаграммы, приложение стартует с чистого холста.
### Режим только для просмотра
Хост может открыть приложение на чтение, добавив `?mode=view` к URL iframe:
```
/local/diagram-app/index.html?mode=view
```
Тогда редактор стартует в режиме «Просмотр» и переключатель «Просмотр/Редактирование» скрыт — фигуры нельзя двигать, создавать и удалять. Без параметра поведение прежнее: редактирование с переключателем. Параметр читается один раз при загрузке (`src/App.tsx`), в контракте `postMessage` ничего не меняется — `diagram:init`/`diagram:change` работают одинаково в обоих режимах.
Зачем: встраивающая сторона, которая не слушает `diagram:change` (например, страница портала, показывающая утверждённую схему), иначе предлагала бы правки, которые молча никуда не сохраняются.
## 5. Требования безопасности (обязательны, не опциональны)
- Значение всегда попадает на страницу только через `value`-атрибут hidden input (`htmlspecialcharsbx($rawValue)`, см. раздел 3), не через встраивание в инлайн-скрипт — так JSON-содержимое никогда не оказывается внутри JS-исходника, и `JSON_HEX_*`-экранирование не требуется вообще. Мост читает `input.value` напрямую в JS. Если по каким-то причинам всё же понадобится встроить значение в инлайн-скрипт (например, отдельный `initialValue` вместо чтения из DOM) — тогда обязателен `JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT` при `json_encode`.
@@ -199,14 +211,20 @@ Backend не обязан ничего валидировать глубже «
- `local/` уже существует как каталог для кастомного кода (`ai`, `components`, `modules`, `php_interface`, `templates` — по факту на дев-стенде). Новый подкаталог, например `local/diagram-app/`, вписывается в эту конвенцию и не пересекается с `local/modules`/`local/php_interface` — они у nginx явно помечены `internal` (заблокированы для прямого внешнего доступа), новый подкаталог под этот блок не попадает.
- Нюанс: `index` на уровне сервера — `index.php`, не `index.html`. Открыть голый `/local/diagram-app/` (без имени файла) не выйдет — nginx попытается найти там `index.php` и не найдёт. Не проблема: PHP-класс (раздел 3) сам подставляет `src` у iframe, поэтому в `$appUrl` достаточно явно указывать файл — `/local/diagram-app/index.html`.
- Так как приложение и портал теперь на одном домене, `VITE_BITRIX_ORIGIN`/`appOrigin` — это просто origin самого портала, а не адрес отдельного сервиса.
- **Важный нюанс, найден живым тестом на дев-стенде:** тот же regex-location в nginx, что раздаёт статику, ставит `.html`-файлам `Cache-Control: public` с `expires 30d`. Значит после **повторного** деплоя (обновили React-приложение, положили новый `dist/` поверх старого) браузеры, уже открывавшие `/local/diagram-app/index.html` раньше, могут до 30 дней получать из своего кэша старый `index.html` — а в нём старые хэши JS/CSS-файлов, которых уже нет на диске (404) или которые не соответствуют новой версии приложения. При первом же открытии URL это не проявляется (кэшировать ещё нечего), но при каждом обновлении приложения — да. Решение: в `$appUrl`/`src` iframe добавлять cache-busting query-параметр, привязанный к версии сборки (например, `?v=<git-hash>` или `?v=<дата билда>`), а не просто `/local/diagram-app/index.html` без параметров — тогда каждая новая версия получает свой URL и обходит браузерный кэш. То же самое верно и для самой страницы со свойством (если её тоже будете кэшировать).
- **Важный нюанс, найден живым тестом на дев-стенде:** тот же regex-location в nginx, что раздаёт статику, ставит `.html`-, `.js`- и `.css`-файлам одинаковый `Cache-Control: public` с `expires 30d`. Это касается **двух независимых уровней**, и решать их нужно по отдельности — закрыть только один недостаточно:
1. **Кэш самого `index.html`.** После повторного деплоя (обновили React-приложение, положили новый `dist/` поверх старого) браузер, уже открывавший `$appUrl` раньше, может до 30 дней получать из кэша старый `index.html` вместо нового. Решение: тот, кто формирует `src` у iframe (PHP-класс из раздела 3, либо любой другой хост), должен сам добавлять к нему cache-busting query-параметр, привязанный к версии сборки (например, `?v=<git-hash>` или `?v=<дата билда>`), а не отдавать голый `/local/diagram-app/index.html` без параметров.
2. **Кэш `index.js`/`index.css`, на которые ссылается сам `index.html`.** Тут пункт 1 не помогает: даже если браузер честно перезапросит свежий `index.html` (URL с новым `?v=` не совпал с закэшированным), внутри этого свежего файла ссылки на `index.js`/`index.css` — те же самые, неизменные имена (в приложении нет хеша в имени файла, см. `vite.config.ts` — сделано намеренно, чтобы имена были человекочитаемыми). Если эти конкретные URL уже лежат в кэше браузера с прошлого визита, он отдаст их с диска, даже не сходив на сервер, — HTML окажется свежим, а исполняемый код внутри него старым.
Пока это **не автоматизировано** и остаётся на усмотрение того, кто деплоит: либо вручную версировать `index.js`/`index.css` внутри задеплоенного `dist/index.html` при каждом обновлении, либо настроить на стороне nginx `Cache-Control` для этих файлов иначе (например, `no-cache`/`must-revalidate`). Если проблема будет всплывать на практике — можно вернуться к автоматизации на стороне сборки.
Порядок действий:
1. В репозитории `portal-chart-builder` собрать статику с двумя build-time переменными:
```
VITE_BITRIX_ORIGIN=https://ВАШ-ДОМЕН-ПОРТАЛА VITE_BASE_PATH=/local/diagram-app/ npm run build
VITE_BASE_PATH=/local/diagram-app/ npm run build
```
`VITE_BITRIX_ORIGIN` в этом варианте не задаётся — см. примечание в конце раздела.
`VITE_BASE_PATH` — путь, под которым будет отдаваться приложение (нужен, чтобы ссылки на JS/CSS внутри `dist/index.html` были корректными под этим подпутём; см. `vite.config.ts`). По умолчанию, если переменная не задана, `base` — `/` (не влияет на локальную разработку, `npm run dev`).
2. Скопировать содержимое `dist/` в `local/diagram-app/` внутри host-каталога, который смонтирован как `bitrix_www` (на текущем дев-стенде это `/home/nikit/bitrix/www/local/diagram-app/` — точный путь для конкретного стенда уточняется у того, кто его поднимал/администрирует).
3. В PHP-классе (раздел 3): `$appUrl = $appOrigin . '/local/diagram-app/index.html';`, `$appOrigin` — origin портала (без пути).
@@ -245,7 +263,9 @@ services:
Собрал и проверил локально (`docker build` + `docker run -p 5178:80`) — контейнер поднимается и корректно отдаёт приложение по `http://localhost:5178`.
**Важно (общее для обоих вариантов):** `VITE_BITRIX_ORIGIN` — build-time переменная, вшивается в JS-бандл на этапе `npm run build`/`docker build`, не читается заново при каждом запуске/обновлении. Без неё приложение намеренно упадёт при загрузке (см. раздел 5) — чтобы нельзя было собрать билд с `postMessage('*')`.
**Важно (общее для обоих вариантов):** `VITE_BITRIX_ORIGIN` — build-time переменная, вшивается в JS-бандл на этапе `npm run build`/`docker build`, не читается заново при каждом запуске/обновлении.
В варианте A она **не нужна**: приложение и портал на одном домене, и origin хоста приложение берёт из `window.location.origin`. Более того, заданная переменная там вредна — она перебьёт рантайм-значение, и на любом домене, кроме зашитого, обмен молча не состоится. Нужна она только в варианте B (или при локальной разработке), где хост и приложение на разных origin. Требование «никогда `postMessage('*')`» соблюдается в обоих случаях: targetOrigin всегда конкретный origin.
Схема с `localhost` (вариант B) работает, пока браузер администратора и Docker-хост — одна машина (текущий дев-стенд). Если позже появится реальный удалённый прод с админами не с этой машины — понадобится либо настоящий домен, либо reverse-proxy; это отдельная, более поздняя задача. Вариант A этой проблемы не имеет вообще, так как всегда живёт на домене портала.