# Код в приложении

Почти всё в мини-приложении собирается без кода. Когда готовых элементов и блоков не хватает, свой код можно добавить в трёх местах:

| Где | Когда выполняется | Для чего |
|---|---|---|
| Блок «JavaScript» | Когда сценарий дошёл до блока | Действие по нажатию: скопировать текст, открыть ссылку, посчитать значение, поговорить с мостом мессенджера |
| Код приложения | Один раз при запуске, до первого экрана, и дальше — по событиям | Счётчики аналитики, свои стили, внешние виджеты |
| «HTML-код» — элемент или весь экран | Пока экран открыт | Своя вёрстка, калькулятор, виджет стороннего сервиса |

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

## Блок «JavaScript»

Блок из категории «Данные и логика». Когда приложение доходит до блока, оно выполняет код и идёт дальше по выходу «Выполнено» или «Ошибка».

| Настройка | Что задаёт |
|---|---|
| «Код» | Тело асинхронной функции: пишите с `await` и объектом `app`. До 20 000 символов |
| «Время на выполнение, секунд» | От 1 до 60, по умолчанию 10. Не уложился — сценарий пойдёт по выходу «Ошибка», а сам код может доработать в фоне |
| «Переменная результата» | Что вернул `return`. Объект запишется как JSON |
| «Переменная ошибки» | Текст ошибки — для экрана после выхода «Ошибка» |
| «Что может менять код» | Список разрешённого — см. ниже |

![Настройки блока «JavaScript»](https://support.watbot.ru/_media/ru/02-mini-apps/_images/js-block.webp?v=1791781230)

Подстановки `{{ имя }}` в коде не работают: значения переменных читайте через `app.variables.get()`.

Переменные, записанные через `app.variables.set()`, и изменённые теги уходят на сервер вместе с выходом блока.

### «Что может менять код»

Переключатель «Менять только перечисленное» включён у нового блока. С ним сервер принимает от кода только:

- переменные из списка «Переменные»;
- теги из списков «Теги контакта бота» и «Теги пользователя приложения» — пустой список значит, что эти теги код не меняет вовсе;
- переменные результата и ошибки — они разрешены всегда.

Остальное сервер отбросит и запишет в журнал ошибок «Блок отбросил записанное кодом». Если код пишет имена, которых нет в списках, настройки блока предупредят об этом и предложат «Добавить в списки».

Выключайте ограничение, только если от переменных и тегов ничего не зависит: иначе человек сможет записать себе, например, тег VIP или скидку.

### Объект `app`

| Вызов | Что делает |
|---|---|
| `app.variables.get('name')` | Значение переменной |
| `app.variables.set('name', value)` | Записать переменную — уйдёт на сервер с выходом блока |
| `app.variables.all()` | Все переменные объектом |
| `app.tags.has('vip')` / `add('vip')` / `remove('vip')` / `all()` | Теги пользователя приложения — есть у каждого, даже без чат-бота |
| `app.contact?.name` | Контакт чат-бота: `id`, `name`, `first_name`, `last_name`, `username`, `phone`, `email`, `address`, `source`, `created_at`, `telegram_id`, `max_id`. `null`, если приложение открыли не из бота |
| `app.contact.variables.get('name')` / `set('name', value)` | Свои переменные контакта, отдельно от переменных приложения. Записанное уйдёт в контакт с выходом блока |
| `app.contact.tags.has('vip')` / `add` / `remove` / `all()` | Теги контакта бота |
| `app.platform` | Откуда открыто: `telegram`, `max`, `webchat`, `pwa` или `browser` |
| `app.startParam` | Параметр ссылки запуска (`startapp`) |
| `app.user` | `language`, `name` и `username` в мессенджере |
| `app.device` | `mode` (`ios` или `md`), `dark`, `standalone`, `width`, `height`, `userAgent` |
| `await app.toast(message)` | Всплывающее сообщение |
| `await app.alert(message, title)` | Сообщение с кнопкой OK — ждёт, пока закроют |
| `await app.confirm(question)` | Вопрос «да или нет» — вернёт `true` или `false` |
| `app.openLink('https://…')` | Открыть ссылку: в мессенджере — его средствами, иначе в новой вкладке |
| `await app.copy(text)` | Скопировать в буфер обмена |
| `await app.share({title, text, url})` | Системное меню «Поделиться», без него — копия ссылки |
| `app.vibrate('success')` | Вибрация: `light`, `medium`, `heavy`, `success`, `warning` или `error` |
| `app.localStorage.setItem('key', 'value')` | Хранилище пользователя на сервере — см. ниже |
| `app.storage.get('key')` / `set` / `remove` | Хранилище на устройстве, своё у приложения |
| `await app.sleep(500)` | Подождать указанное число миллисекунд |
| `app.close()` | Закрыть приложение |
| `app.telegram` | `Telegram.WebApp` как есть, вне Telegram — `null` |
| `app.max` | Мост MAX как есть, вне MAX — `null` |

Пример из настроек блока:

```js
// Тема и ширина экрана — в переменные
const device = app.device
app.variables.set('theme', device.dark ? 'dark' : 'light')

if (app.platform === 'telegram') {
    app.vibrate('success')
}

// Что вернули — попадёт в переменную результата
return device.width
```

Ошибки кода видны в консоли браузера в предпросмотре и попадают в [журнал ошибок](https://support.watbot.ru/mini-apps/users) — «Ошибка в коде JavaScript».

## Код приложения

Код на всё приложение: выполняется один раз при запуске, до первого экрана, и слушает события. Открывается кнопкой «Код приложения» (значок «</>») на панели над схемой. Если код задан, значок окрашен.

В окне «Код приложения»:

- «Код» — до 100 000 символов;
- «Переменные, которые код может записать» — до 50 имён. Остальные переменные сервер не примет;
- шпаргалка событий и «Пример» с кнопкой «Вставить пример».

![Окно «Код приложения»](https://support.watbot.ru/_media/ru/02-mini-apps/_images/app-code.webp?v=1791781230)

Код хранится в версии: новая версия и шаблон получают его копию. После сохранения открытый предпросмотр перезапускается.

### События

| Подписка | Когда срабатывает |
|---|---|
| `app.on('start', ({platform, startParam}) => {})` | Приложение запустилось |
| `app.on('screen', ({id, name, type}) => {})` | Показан экран или окно поверх него |
| `app.on('event', ({blockId, eventId}) => {})` | Нажатие на кнопку, пункт или элемент — до отправки на сервер |
| `app.on('field', ({variable, value}) => {})` | Человек изменил поле ввода |
| `app.on('error', ({message}) => {})` | Приложение показало ошибку |
| `app.on('hide', () => {})` / `app.on('show', () => {})` | Приложение свернули и вернули |
| `app.on('theme', ({dark}) => {})` | Сменилась светлая или тёмная тема |
| `app.on('storage', ({key, newValue}) => {})` | Код элемента «HTML» записал в localStorage |

- Подписка на `start`, `screen` и `theme` сразу получает текущее значение — даже если подписались после `await`.
- `app.on()` возвращает функцию отписки: `const stop = app.on(…); stop()`. Есть и `app.off(name, handler)`.
- `await app.loadScript('https://…')` подключает внешний скрипт: счётчик, виджет, библиотеку. Адрес — только `http://` или `https://`.

Остальное — как у блока «JavaScript»: `app.variables`, `app.toast`, `app.alert`, `app.openLink`, `app.storage`, `app.localStorage`, `app.telegram` и другие. `app.contact` и `app.tags` здесь только для чтения: менять теги — дело блоков тегов.

Переменные, записанные через `app.variables.set()`, уходят на сервер со следующим событием.

Пример из окна — Яндекс Метрика:

```js
// Подключаем Яндекс Метрику
await app.loadScript('https://mc.yandex.ru/metrika/tag.js')
ym(12345678, 'init', {defer: true})

// Каждый экран — просмотр страницы
app.on('screen', ({id, name}) => {
    ym(12345678, 'hit', '/screen/' + id, {title: name})
})

// Нажатия — цели
app.on('event', ({eventId}) => {
    ym(12345678, 'reachGoal', 'click_' + eventId)
})
```

Ошибки кода приложения — при запуске и в обработчиках — видны в консоли и попадают в журнал ошибок с причиной «Ошибка в коде приложения» и стеком вызовов. Одна и та же ошибка записывается один раз.

## «HTML-код»: элемент и экран

Свой HTML со скриптами и стилями бывает двух видов:

- **элемент «HTML-код»** — в группе «Виджеты», встаёт в область экрана рядом с другими элементами;
- **экран «HTML-код»** — третий вид экрана после «Стандартного окна» и «Вкладок снизу»: весь экран — ваш код, без шапки и областей. Новый такой экран сразу получает пример с кнопкой и выход `next`. Модальным окном его сделать нельзя.

Код работает в изолированном фрейме: сессия, данные приложения и остальной экран ему недоступны. Скрипты и стили — внутри кода. Цвет текста фрейм берёт из темы приложения.

| Настройка | Что задаёт |
|---|---|
| «Код» | HTML до 100 000 символов |
| «Высота» (у элемента) | «По содержимому» — подстраивается под код, в том числе когда он меняет содержимое сам; «Задать» — от 40 до 2000 пикселей, всё, что не поместится, прокручивается внутри |
| «Выходы» | До 20. У выхода — «Ключ» (латиница, цифры, `_` и `-`) и «Название на схеме». Выход — переход на другой блок схемы |
| «Переменные, которые код может записать» | До 50 имён. Остальные сервер не примет |

![Элемент «HTML-код»: код, выходы и переменные](https://support.watbot.ru/_media/ru/02-mini-apps/_images/html-element.webp?v=1791781230)

### Что доступно коду во фрейме

| Вызов | Что делает |
|---|---|
| `app.event('next')` | Перейти по выходу с этим ключом |
| `app.event('next', {name: 'value'})` | Перейти и записать разрешённые переменные |
| `app.variables.set('name', 'value')` | Записать разрешённую переменную сразу, без перехода: экран не меняется |
| `localStorage.setItem('key', 'value')` | Хранилище пользователя на сервере — см. ниже. `sessionStorage` живёт, пока открыт экран |
| `app.openLink('https://…')` | Открыть ссылку в браузере. Обычные ссылки `<a>` открываются так же |
| `{{ name }}` | Подставить значение переменной прямо в код — как текст, с экранированием |

На схеме код не запускается — вместо него заглушка. В редакторе экрана фрейм показывает результат и перезапускается после правки кода.

## Хранилища

| | `localStorage` на сервере | `app.storage` |
|---|---|---|
| Где доступно | `localStorage` во фрейме «HTML-код», `app.localStorage` в блоке «JavaScript» и коде приложения | Блок «JavaScript» и код приложения |
| Где лежит | На сервере, у пользователя приложения | В браузере человека |
| Переживает | Перезапуск, очистку телефона, смену устройства в том же мессенджере | Пока не очищены данные браузера |
| Общее | Для всех элементов «HTML-код», кода приложения и блоков «JavaScript» одного приложения | Своё у каждого приложения |
| Значения | Только строки, до 100 000 символов вместе с ключами | Любые значения, сохраняются как JSON |

Хранилище на сервере своё у каждой площадки: у одного человека в Telegram, MAX и браузере оно разное.

## Ограничения

- Код блока «JavaScript» — до 20 000 символов, код приложения и HTML-кода — до 100 000.
- Время на выполнение блока «JavaScript» — до 60 секунд.
- В журнал попадает не больше 20 ошибок в минуту с одного блока.
