> For the complete documentation index, see [llms.txt](https://docs.iexexchanger.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.iexexchanger.com/razrabotchikam/vstavki-html-css-i-js.md).

# Вставки HTML, CSS и JS

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

На новых серверах вставки хранятся в отдельном общем каталоге:

```
/var/www/iexexchanger/shared/frontend/ng-custom
```

Работа с файлами выполняется на сервере Frontend через SSH. Отдельного редактора пользовательских вставок в административной панели нет.

{% hint style="info" %}
Старая схема с FastPanel, созданием `public/ng-custom` внутри проекта и ручным добавлением `location` в Nginx больше не используется. Каталог, права доступа и конфигурацию Nginx создаёт установщик iEXExchanger.
{% endhint %}

## Как устроена система на новых серверах

Каталог `ng-custom` находится в общей директории `shared`, а не внутри конкретного релиза Frontend. Благодаря этому пользовательские файлы не заменяются при штатном обновлении или откате приложения.

Файлы доступны через основной домен Frontend:

```
https://ваш_домен/ng-custom/имя-файла
```

Домен Backend `https://app.ваш_домен` для пользовательских вставок не используется.

Установщик автоматически:

* создаёт общий каталог `ng-custom`;
* добавляет пять базовых файлов;
* назначает безопасные права доступа;
* подключает каталог к Nginx;
* отключает просмотр содержимого каталога;
* блокирует скрытые и PHP-подобные файлы;
* добавляет заголовки, запрещающие сохранение устаревшей версии в кеше;
* сохраняет каталог при обновлении Frontend.

Если штатное резервное копирование сервера включено, каталог входит в резервную копию общей директории `/var/www/iexexchanger/shared`.

## Файлы пользовательских вставок

В каталоге находятся следующие базовые файлы:

<table><thead><tr><th width="221.3203125">Файл</th><th width="155.8984375">Режим</th><th>Назначение</th></tr></thead><tbody><tr><td><code>client-scripts.js</code></td><td>Браузерный</td><td>Произвольный JavaScript и подключение внешних скриптов после запуска Frontend</td></tr><tr><td><code>client-snippets.html</code></td><td>Браузерный</td><td>HTML, CSS, теги <code>&#x3C;script></code>, <code>&#x3C;meta></code>, <code>&#x3C;link></code> и другие готовые фрагменты</td></tr><tr><td><code>ssr-head.html</code></td><td>SSR</td><td>Вставка перед закрывающим тегом <code>&#x3C;/head></code></td></tr><tr><td><code>ssr-body-start.html</code></td><td>SSR</td><td>Вставка сразу после открывающего тега <code>&#x3C;body></code></td></tr><tr><td><code>ssr-body-end.html</code></td><td>SSR</td><td>Вставка перед закрывающим тегом <code>&#x3C;/body></code></td></tr></tbody></table>

Допускается создавать дополнительные статические файлы, например:

```
custom.css
widget.js
images/widget-icon.svg
```

Они также будут доступны через `/ng-custom/`.

{% hint style="danger" %}
Все файлы внутри `ng-custom`, включая исходные SSR-фрагменты, считаются публичными. Никогда не сохраняйте в них пароли, приватные API-ключи, секретные токены, закрытые ключи или другие конфиденциальные данные.
{% endhint %}

## Режимы работы

Браузерный режим и SSR работают независимо. Можно включить один режим или оба одновременно.

<table><thead><tr><th width="166.734375">Режим</th><th>Переменная</th><th>Когда применяется</th></tr></thead><tbody><tr><td>Браузерный</td><td><code>CLIENT_CUSTOM_SCRIPTS_ENABLED</code></td><td>После первого отображения Angular-приложения в браузере</td></tr><tr><td>SSR</td><td><code>CLIENT_CUSTOM_SSR_ENABLED</code></td><td>При формировании исходного HTML на сервере</td></tr></tbody></table>

### Браузерный режим

Браузерный режим загружает:

```
/ng-custom/client-scripts.js
/ng-custom/client-snippets.html
```

Особенности режима:

* файлы загружаются только в браузере;
* пользовательский код не выполняется во время серверного рендеринга;
* загрузка начинается после первого отображения Angular-приложения;
* оба файла загружаются независимо и параллельно;
* ошибка пользовательского файла записывается в консоль и не должна останавливать основную загрузку Frontend;
* для работы после переходов между страницами доступен route-aware API;
* локализованные варианты браузерных файлов автоматически не выбираются.

Не полагайтесь на порядок выполнения `client-scripts.js` и `client-snippets.html`. Если фрагменты зависят друг от друга, разместите зависимый код в одном файле или используйте событие `iex:custom:ready`.

### SSR-режим

SSR-режим добавляет содержимое файлов непосредственно в исходный HTML-ответ.

JavaScript из SSR-файлов не выполняется процессом Node.js. Frontend только добавляет соответствующую разметку в HTML, после чего теги `<script>` выполняются браузером обычным способом.

SSR подходит для:

* стилей, которые должны присутствовать уже в первом HTML;
* кодов подтверждения сторонних сервисов;
* элементов `<noscript>`;
* тегов `<meta>` и `<link>`;
* скриптов, которые должны находиться в определённой части документа.

SSR-вставки выполняются при полной загрузке страницы. Они не запускаются повторно при внутренних переходах Angular. Для личного кабинета и других CSR-страниц используйте браузерный режим и `onPageReady()`.

### Как выбрать режим для CSS

Разместите CSS в `client-snippets.html`, если стиль можно применить после первого отображения страницы.

Разместите CSS в `ssr-head.html`, если он должен находиться в исходном `<head>` и применяться как можно раньше.

Для большого объёма CSS создайте отдельный файл `custom.css` и подключите его через:

```html
<link rel="stylesheet" href="/ng-custom/custom.css">
```

## Включение при новой установке

До запуска новой установки режимы можно включить в `/home/installer/install.yaml`:

```yaml
services:
  frontend:
    custom:
      scriptsEnabled: true
      ssrEnabled: true
```

Если нужен только один режим, оставьте второе значение `false`.

Изменение `install.yaml` на уже установленном сервере само по себе не меняет работающий Frontend. На завершённой установке используются параметры из `/etc/iexexchanger/frontend.env`.

{% hint style="warning" %}
Во время активной установки, обновления, отката или восстановления не изменяйте `install.yaml`, `frontend.env`, Nginx и управляемые файлы. Дождитесь завершения операции.
{% endhint %}

## Включение на установленном сервере

Для работы потребуются SSH-доступ и возможность выполнять команды через `sudo`.

{% stepper %}
{% step %}

### Проверьте каталог

Выполните:

```bash
sudo ls -la /var/www/iexexchanger/shared/frontend/ng-custom
```

В каталоге должны находиться пять базовых файлов.

Не создавайте новый `ng-custom` внутри `current`, `releases`, `dist`, Backend или другого каталога приложения.
{% endstep %}

{% step %}

### Откройте настройки Frontend

Выполните:

```bash
sudoedit /etc/iexexchanger/frontend.env
```

Для включения обоих режимов должны использоваться значения:

```dotenv
CLIENT_CUSTOM_SCRIPTS_ENABLED="true"
CLIENT_CUSTOM_SSR_ENABLED="true"
CLIENT_CUSTOM_DIR="/var/www/iexexchanger/shared/frontend/ng-custom"
```

Для отключения отдельного режима укажите `"false"`.

Не изменяйте `CLIENT_CUSTOM_DIR`, если сервер установлен штатным установщиком iEXExchanger.
{% endstep %}

{% step %}

### Перезапустите Frontend

После изменения `frontend.env` выполните:

```bash
sudo systemctl restart iex-frontend.service
```

{% endstep %}

{% step %}

### Проверьте службу

Выполните:

```bash
sudo systemctl is-active iex-frontend.service
```

Ожидаемый результат:

```
active
```

Если служба не запустилась, проверьте её состояние и журнал:

```bash
sudo systemctl status iex-frontend.service --no-pager
sudo journalctl -u iex-frontend.service -n 100 --no-pager
```

{% endstep %}
{% endstepper %}

{% hint style="info" %}
Перезапуск требуется после изменения переменных в `frontend.env`. После обычного изменения содержимого HTML, CSS или JavaScript повторная сборка и перезапуск Frontend не требуются.
{% endhint %}

## Редактирование пользовательских файлов

Для редактирования используйте `sudoedit`:

```bash
sudoedit /var/www/iexexchanger/shared/frontend/ng-custom/client-scripts.js
```

```bash
sudoedit /var/www/iexexchanger/shared/frontend/ng-custom/client-snippets.html
```

```bash
sudoedit /var/www/iexexchanger/shared/frontend/ng-custom/ssr-head.html
```

```bash
sudoedit /var/www/iexexchanger/shared/frontend/ng-custom/ssr-body-start.html
```

```bash
sudoedit /var/www/iexexchanger/shared/frontend/ng-custom/ssr-body-end.html
```

Перед значительным изменением сохраните копию файла в защищённом каталоге вне `/var/www/iexexchanger/shared/frontend/ng-custom`.

Не храните резервные копии с расширениями `.bak`, `.old` или `.copy` внутри публичного каталога: такие файлы также могут стать доступными через сайт.

Не используйте права `777` и не меняйте владельца базовых файлов без необходимости. Установщик ожидает обычные файлы без символических ссылок и дополнительных жёстких ссылок.

## Работа с JavaScript в браузерном режиме

JavaScript размещается в:

```
/var/www/iexexchanger/shared/frontend/ng-custom/client-scripts.js
```

Код верхнего уровня выполняется сразу после загрузки файла. Рекомендуемая точка входа — функция `window.IEX_CUSTOM_INIT`.

Базовый шаблон:

```javascript
window.IEX_CUSTOM_INIT = function (api) {
    api.loadScript('https://service.example/widget.js', {
        id: 'external-widget-script',
        attributes: {
            'data-site-id': 'ЗАМЕНИТЕ_НА_ПУБЛИЧНЫЙ_ID',
        },
    }).catch(function (error) {
        console.error('Не удалось загрузить внешний виджет:', error);
    });

    api.onPageReady(function (route) {
        console.debug('Страница Frontend готова:', route.path);
    }, {
        immediate: false,
    });
};
```

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

Параметр `id` предотвращает повторное добавление одного и того же `<script>` при переходах между страницами.

Пользовательский callback должен быть идемпотентным: повторное выполнение не должно создавать дублирующиеся кнопки, виджеты или обработчики событий.

### Проверка синтаксиса JavaScript

Перед публикацией можно проверить синтаксис:

```bash
node --check /var/www/iexexchanger/shared/frontend/ng-custom/client-scripts.js
```

Эта команда проверяет синтаксис, но не подтверждает работу браузерного API или стороннего сервиса.

## Работа с HTML и CSS в браузерном режиме

Готовые HTML-фрагменты размещаются в:

```
/var/www/iexexchanger/shared/frontend/ng-custom/client-snippets.html
```

Файл может содержать:

```html
<link rel="stylesheet" href="/ng-custom/custom.css">

<style>
    .custom-support-button {
        position: fixed;
        right: 24px;
        bottom: 24px;
        z-index: 1000;
    }
</style>

<div id="custom-support-widget"></div>

<script>
    window.customSupportWidgetId = 'ЗАМЕНИТЕ_НА_ПУБЛИЧНЫЙ_ID';
</script>

<script src="https://service.example/widget.js"></script>
```

Frontend обрабатывает верхнеуровневые элементы следующим образом:

* `<meta>`, `<link>`, `<style>` и `<title>` перемещаются в `document.head`;
* обычная HTML-разметка добавляется в `body`;
* теги `<script>` пересоздаются и выполняются;
* скрипты внутри файла обрабатываются последовательно;
* пустые текстовые узлы и HTML-комментарии пропускаются;
* тег `<base>` игнорируется.

Файл не проходит HTML-санитизацию. Его содержимое считается доверенным кодом владельца сервера.

{% hint style="warning" %}
Не вставляйте полный HTML-документ с тегами `<html>`, `<head>` и `<body>`. Добавляйте только нужные фрагменты.
{% endhint %}

## Создание отдельного CSS-файла

Создайте файл:

```bash
sudoedit /var/www/iexexchanger/shared/frontend/ng-custom/custom.css
```

Для нового файла скопируйте владельца и права с базового файла:

```bash
sudo chown --reference=/var/www/iexexchanger/shared/frontend/ng-custom/client-snippets.html /var/www/iexexchanger/shared/frontend/ng-custom/custom.css
sudo chmod --reference=/var/www/iexexchanger/shared/frontend/ng-custom/client-snippets.html /var/www/iexexchanger/shared/frontend/ng-custom/custom.css
```

Подключите его в `client-snippets.html` или `ssr-head.html`:

```html
<link rel="stylesheet" href="/ng-custom/custom.css">
```

Проверьте доступность:

```bash
curl -I https://ваш_домен/ng-custom/custom.css
```

## API браузерного режима

При включённом браузерном режиме Frontend создаёт:

```javascript
window.IEX_CUSTOM_SCRIPT_API
```

Текущая версия API:

```
1.1.0
```

API также передаётся аргументом в `window.IEX_CUSTOM_INIT`.

<table><thead><tr><th width="187.1875">Свойство или метод</th><th>Назначение</th><th>Основные параметры</th></tr></thead><tbody><tr><td><code>version</code></td><td>Версия API</td><td>Строка</td></tr><tr><td><code>readyState</code></td><td>Значение <code>document.readyState</code> в момент установки API</td><td>Без параметров</td></tr><tr><td><code>currentRoute()</code></td><td>Возвращает текущий адрес внутри Frontend</td><td>Без параметров</td></tr><tr><td><code>loadScript()</code></td><td>Загружает дополнительный JavaScript</td><td>URL и объект настроек</td></tr><tr><td><code>injectHtml()</code></td><td>Добавляет HTML и выполняет содержащиеся в нём скрипты</td><td>HTML и объект настроек</td></tr><tr><td><code>onReady()</code></td><td>Выполняет callback после готовности DOM</td><td>Функция</td></tr><tr><td><code>onRouteChange()</code></td><td>Подписывает callback на текущий маршрут и Angular-навигации</td><td>Функция и <code>immediate</code></td></tr><tr><td><code>onPageReady()</code></td><td>Вызывает callback после отрисовки DOM текущей страницы</td><td>Функция и <code>immediate</code></td></tr><tr><td><code>mount()</code></td><td>Возвращает или создаёт контейнер для интеграции</td><td>ID и объект настроек</td></tr><tr><td><code>safeRun()</code></td><td>Выполняет callback через защитный <code>try/catch</code></td><td>Функция и подпись ошибки</td></tr></tbody></table>

### Объект текущего маршрута

`currentRoute()`, `onRouteChange()` и `onPageReady()` используют объект:

| Поле     | Содержимое                    |
| -------- | ----------------------------- |
| `url`    | Путь, параметры запроса и хеш |
| `path`   | Только путь                   |
| `search` | Строка параметров запроса     |
| `hash`   | Хеш страницы                  |

Пример результата:

```javascript
{
    url: '/ru/account/orders?tab=active#list',
    path: '/ru/account/orders',
    search: '?tab=active',
    hash: '#list'
}
```

### Метод loadScript

Сигнатура:

```javascript
api.loadScript(src, options)
```

Поддерживаемые настройки:

| Параметр     | Назначение                                                       |
| ------------ | ---------------------------------------------------------------- |
| `id`         | Предотвращает повторную загрузку скрипта с тем же ID             |
| `async`      | Управляет свойством `script.async`; по умолчанию `true`          |
| `defer`      | Управляет свойством `script.defer`; по умолчанию `true`          |
| `attributes` | Добавляет `data-*`, `integrity`, `crossorigin` и другие атрибуты |

Метод не ограничивает домены внешних скриптов. Пустые адреса и адреса с управляющими символами отклоняются.

### Метод injectHtml

Сигнатура:

```javascript
api.injectHtml(html, options)
```

Параметр `target` принимает:

* `body`;
* `head`;
* CSS-селектор существующего элемента.

Если элемент по CSS-селектору не найден, используется `body`.

Параметр `wrapperId` создаёт или повторно использует контейнер с указанным ID.

### Методы onRouteChange и onPageReady

Оба метода возвращают функцию отписки:

```javascript
var unsubscribe = api.onPageReady(function (route) {
    console.debug(route.path);
});

unsubscribe();
```

По умолчанию `immediate` равен `true`, поэтому callback планируется сразу после регистрации. Затем callback вызывается при первоначальном route-ready событии и после Angular-навигаций.

Логика callback должна выдерживать повторные вызовы. Если подписка создаётся внутри `IEX_CUSTOM_INIT` и нужен только объединённый первоначальный сигнал после загрузки обоих пользовательских файлов, укажите:

```javascript
{
    immediate: false
}
```

Callbacks выполняются после двух кадров браузерной отрисовки, чтобы DOM личного кабинета и других CSR-страниц успел обновиться.

### Метод mount

Метод создаёт постоянный контейнер для виджета:

```javascript
var container = api.mount('custom-widget-root', {
    target: 'body',
    clear: false,
});
```

Если элемент с таким ID уже существует, API возвращает его повторно.

Если `target` не найден, используется системный контейнер `#iex-custom-root`, а при его отсутствии — `body`.

Параметр `clear: true` очищает существующий контейнер перед повторным использованием.

## События браузерного режима

Помимо API, Frontend отправляет браузерные события:

| Событие            | Когда отправляется                                              | Содержимое `event.detail` |
| ------------------ | --------------------------------------------------------------- | ------------------------- |
| `iex:custom:ready` | После завершения попыток загрузки обоих пользовательских файлов | `route`                   |
| `iex:route-ready`  | После первой загрузки и каждой Angular-навигации                | `reason` и `route`        |

Подписка:

```javascript
window.addEventListener('iex:custom:ready', function (event) {
    console.debug('Пользовательские файлы обработаны:', event.detail);
});

window.addEventListener('iex:route-ready', function (event) {
    console.debug('Маршрут готов:', event.detail.route.path);
});
```

Для `iex:route-ready` поле `reason` содержит:

```
initial
```

или:

```
navigation
```

## Работа с SSR-вставками

SSR-файлы содержат готовые фрагменты HTML.

### Вставка в head

Файл:

```
ssr-head.html
```

Содержимое добавляется перед `</head>`.

Подходящий формат:

```html
<meta name="service-verification" content="ЗАМЕНИТЕ_НА_ПУБЛИЧНОЕ_ЗНАЧЕНИЕ">
<link rel="stylesheet" href="/ng-custom/custom.css">
<script defer src="https://service.example/integration.js"></script>
```

### Вставка в начало body

Файл:

```
ssr-body-start.html
```

Содержимое добавляется сразу после открывающего тега `<body>`.

Подходящий формат:

```html
<noscript>
    <iframe
        src="https://service.example/fallback"
        height="0"
        width="0"
        style="display:none;visibility:hidden">
    </iframe>
</noscript>
```

### Вставка в конец body

Файл:

```
ssr-body-end.html
```

Содержимое добавляется перед `</body>`.

Подходящий формат:

```html
<script defer src="https://service.example/widget.js"></script>
```

Frontend автоматически оборачивает добавленное содержимое служебными комментариями:

```html
<!--iex-custom:ssr-head:start-->
<!--iex-custom:ssr-head:end-->
```

```html
<!--iex-custom:ssr-body-start:start-->
<!--iex-custom:ssr-body-start:end-->
```

```html
<!--iex-custom:ssr-body-end:start-->
<!--iex-custom:ssr-body-end:end-->
```

Добавлять эти комментарии вручную не требуется. Они также защищают HTML от повторной вставки одного слота.

Файл, содержащий только пробелы или HTML-комментарии, считается пустым и не добавляется в страницу.

{% hint style="warning" %}
Не добавляйте тег `<base>` в SSR-файлы. В SSR-режиме разметка вставляется напрямую, поэтому `<base>` может изменить обработку всех относительных ссылок и нарушить маршрутизацию Frontend.
{% endhint %}

## Мультиязычные SSR-вставки

Для разных языков можно создать подкаталоги внутри `ng-custom`:

```
/var/www/iexexchanger/shared/frontend/ng-custom/ru/
/var/www/iexexchanger/shared/frontend/ng-custom/en/
```

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

```
ssr-head.html
ssr-body-start.html
ssr-body-end.html
```

Для SSR Frontend проверяет файлы в следующем порядке:

1. Подкаталог языка текущей страницы.
2. Подкаталог языка Frontend по умолчанию.
3. Корень общего каталога `ng-custom`.

Например, для английской страницы и языка по умолчанию `ru` порядок будет следующим:

```
ng-custom/en/ssr-head.html
ng-custom/ru/ssr-head.html
ng-custom/ssr-head.html
```

Используется первый существующий файл.

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

Чтобы использовать общий вариант, удалите соответствующий языковой файл или не создавайте его.

{% hint style="info" %}
Автоматическая локализация применяется только к SSR-файлам. Браузерный режим всегда загружает корневые `client-scripts.js` и `client-snippets.html`.
{% endhint %}

## Применение изменений

После сохранения `client-scripts.js` или `client-snippets.html` обновите страницу в браузере.

После сохранения SSR-файла откройте страницу заново. Frontend проверяет время изменения файла и использует новое содержимое без пересборки и перезапуска службы.

Перезапуск `iex-frontend.service` требуется только после изменения:

```
CLIENT_CUSTOM_SCRIPTS_ENABLED
CLIENT_CUSTOM_SSR_ENABLED
CLIENT_CUSTOM_DIR
```

Nginx отправляет пользовательские файлы с заголовками, запрещающими хранение устаревшей копии:

```
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Expires: 0
```

Кеширование собственных файлов стороннего сервиса при этом регулируется самим сторонним сервисом.

## Проверка браузерного режима

### Проверьте доступность файлов

Выполните:

```bash
curl -I https://ваш_домен/ng-custom/client-scripts.js
```

```bash
curl -I https://ваш_домен/ng-custom/client-snippets.html
```

Ожидается успешный HTTP-ответ и заголовки, запрещающие кеширование.

{% hint style="warning" %}
Успешный ответ по `/ng-custom/` подтверждает только доступность файла. Он не подтверждает, что браузерный или SSR-режим включён.
{% endhint %}

### Проверьте runtime-флаг

Откройте сайт, затем инструменты разработчика и вкладку Console.

Выполните:

```javascript
window.IEX_CUSTOM_SCRIPTS_ENABLED === true
```

Ожидаемый результат при включённом браузерном режиме:

```
true
```

### Проверьте API

Выполните:

```javascript
window.IEX_CUSTOM_SCRIPT_API?.version
```

Ожидаемый результат:

```
1.1.0
```

Проверьте текущий маршрут:

```javascript
window.IEX_CUSTOM_SCRIPT_API?.currentRoute()
```

### Проверьте загрузку файлов

Во вкладке Network найдите:

```
client-scripts.js
client-snippets.html
```

Во вкладке Console проверьте отсутствие сообщений с префиксом:

```
[ClientCustomScripts]
```

## Проверка SSR-режима

Запросите исходный HTML страницы:

```bash
curl -Ls https://ваш_домен | grep -n 'iex-custom:ssr'
```

Для страницы с языковым префиксом используйте её фактический адрес:

```bash
curl -Ls https://ваш_домен/ru | grep -n 'iex-custom:ssr'
```

Также можно открыть в браузере исходный код страницы и найти:

```
iex-custom:ssr-head
iex-custom:ssr-body-start
iex-custom:ssr-body-end
```

Маркер появляется только для непустого слота.

{% hint style="success" %}
Изменение применено корректно, если файл доступен по Frontend-домену, нужный режим включён, браузерный API или SSR-маркер присутствует, а интеграция продолжает работать после переходов между страницами.
{% endhint %}

## Безопасность

Пользовательские вставки являются доверенным кодом и не работают в изолированной среде.

JavaScript получает доступ к странице с теми же возможностями, что и основной клиентский код. Он может изменять DOM, читать доступные браузеру данные, отправлять сетевые запросы и влиять на интерфейс обменника.

Соблюдайте следующие правила:

* добавляйте код только из доверенного источника;
* используйте только HTTPS-адреса внешних ресурсов;
* не вставляйте приватные ключи и секретные токены;
* не предоставляйте доступ к файлам непроверенным пользователям;
* проверяйте код поставщика перед публикацией;
* используйте уникальный `id` для внешних скриптов;
* не отключайте Content Security Policy полностью;
* не используйте `chmod 777`;
* не создавайте символические ссылки вместо базовых файлов;
* не вставляйте полный HTML-документ;
* не используйте `<base>`.

Если внешний ресурс блокируется Content Security Policy, разрешите только необходимый домен в соответствующей директиве:

```
script-src
connect-src
img-src
frame-src
style-src
```

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

## Если браузерный код не выполняется

Проверьте значения:

```bash
sudo grep -E '^(CLIENT_CUSTOM_SCRIPTS_ENABLED|CLIENT_CUSTOM_SSR_ENABLED|CLIENT_CUSTOM_DIR)=' /etc/iexexchanger/frontend.env
```

После изменения `frontend.env` перезапустите Frontend:

```bash
sudo systemctl restart iex-frontend.service
```

Проверьте runtime-флаг в браузере:

```javascript
window.IEX_CUSTOM_SCRIPTS_ENABLED
```

Если значение отсутствует или равно `false`, браузерный режим не включён в HTML текущего процесса Frontend.

## Если код работает только после полной загрузки

SSR-скрипты запускаются только при полной загрузке документа.

Для повторной работы после Angular-навигаций перенесите логику в `client-scripts.js` и используйте:

```javascript
api.onPageReady(function (route) {
    console.debug('Новая страница готова:', route.path);
});
```

Не используйте фиксированные задержки в несколько секунд, если достаточно `onPageReady()`.

## Если виджет появляется несколько раз

Используйте уникальный `id` при загрузке:

```javascript
api.loadScript('https://service.example/widget.js', {
    id: 'external-widget-script',
});
```

Для HTML-контейнера используйте `mount()`:

```javascript
api.mount('external-widget-root');
```

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

## Если SSR-маркеры отсутствуют

Проверьте:

* значение `CLIENT_CUSTOM_SSR_ENABLED`;
* содержимое соответствующего SSR-файла;
* языковой подкаталог текущей страницы;
* наличие только комментариев или пробелов;
* состояние службы `iex-frontend.service`.

После изменения только содержимого SSR-файла перезапуск не требуется.

## Если используется неправильный язык

Проверьте наличие файла в каталоге текущего языка.

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

## Если файл возвращает 403 или 404

Проверьте точный путь:

```bash
sudo ls -la /var/www/iexexchanger/shared/frontend/ng-custom
```

Проверьте права всех компонентов пути:

```bash
sudo namei -l /var/www/iexexchanger/shared/frontend/ng-custom/client-scripts.js
```

Не используйте:

* скрытые имена, начинающиеся с точки;
* PHP-подобные расширения;
* символические ссылки;
* каталог текущего релиза вместо `shared/frontend/ng-custom`.

Не добавляйте вручную новый блок Nginx. Конфигурация `/ng-custom/` управляется установщиком и может быть восстановлена из шаблона при следующей серверной операции.

## Если изменения исчезли после обновления

Штатное обновление сохраняет:

```
/var/www/iexexchanger/shared/frontend/ng-custom
```

Если изменения исчезли, вероятнее всего редактировался файл внутри активного релиза, например `current`, `releases`, `dist` или `public`.

Перенесите актуальный код в общий каталог `shared/frontend/ng-custom`. Не редактируйте собранные файлы Frontend.

## Если Frontend не запускается после изменения настроек

Проверьте службу:

```bash
sudo systemctl status iex-frontend.service --no-pager
```

Проверьте последние записи журнала:

```bash
sudo journalctl -u iex-frontend.service -n 100 --no-pager
```

Проверьте синтаксис и значения в:

```
/etc/iexexchanger/frontend.env
```

Убедитесь, что `CLIENT_CUSTOM_DIR` содержит абсолютный штатный путь:

```
/var/www/iexexchanger/shared/frontend/ng-custom
```

## Отключение пользовательских вставок

Откройте:

```bash
sudoedit /etc/iexexchanger/frontend.env
```

Для полного отключения установите:

```dotenv
CLIENT_CUSTOM_SCRIPTS_ENABLED="false"
CLIENT_CUSTOM_SSR_ENABLED="false"
```

Перезапустите Frontend:

```bash
sudo systemctl restart iex-frontend.service
sudo systemctl is-active iex-frontend.service
```

Отключение режима не удаляет содержимое файлов.

{% hint style="danger" %}
Даже при выключенных режимах статические файлы могут оставаться доступными напрямую через `/ng-custom/`. Если в файл случайно попал секрет, немедленно удалите его из публичного каталога и отзовите или замените сам секрет.
{% endhint %}

## Откат содержимого

Для отката верните предыдущую проверенную версию соответствующего файла в:

```
/var/www/iexexchanger/shared/frontend/ng-custom
```

После восстановления HTML, CSS или JavaScript перезапуск Frontend не требуется.

Проверьте:

```bash
node --check /var/www/iexexchanger/shared/frontend/ng-custom/client-scripts.js
curl -I https://ваш_домен/ng-custom/client-scripts.js
curl -Ls https://ваш_домен | grep -n 'iex-custom:ssr'
```

После этого повторите проверку в браузере на первой странице, при переходе между разделами и в личном кабинете.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.iexexchanger.com/razrabotchikam/vstavki-html-css-i-js.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
