Что умеет хост
При handshake хост передаёт в контексте список возможностей (capabilities). Проверяйте их через
app.isSupported(capability) или хук useExodeSupports():
Хост до 0.4 ничего не объявляет. Тогда методы SDK падают обратно на веб (
window.alert / confirm / prompt,
navigator.share, navigator.clipboard, Fullscreen API, navigator.vibrate), а шапка, «Назад», главная кнопка и
поле ввода ничего не делают — хуки возвращают isSupported: false, и приложение рисует свой интерфейс.
Края и тема
ExodeMiniAppProvider сразу включает нативный вид; у ExodeMiniApp его включают параметры конструктора:
Переменные на
<html>:
--exode-inset-{top,right,bottom,left}— что держать свободным у каждого края (больший из хрома хоста и safe area);--exode-safe-area-inset-*,--exode-content-inset-*— safe area устройства и хром хоста по отдельности;--exode-viewport-width,--exode-viewport-height,--exode-content-height— размер фрейма и высота без хрома;--exode-accentи--exode-color-*:background,content,elevated,text,text-secondary,text-tertiary,accent,accent-foreground,separator,destructive,positive.
<html>: data-exode-platform (native / web), data-exode-os (ios / android / web),
data-exode-scheme, data-exode-fullscreen, data-exode-orientation, data-exode-edge.
Высоту экрана задавайте как min-height: var(--exode-content-height, 100dvh), а свои панели с position: fixed —
с bottom: var(--exode-inset-bottom).
Без вспышки при загрузке
Хост кладёт тему и края в имя фрейма (window.name с префиксом exode-miniapp:) — SDK применяет их ещё до
handshake. Приложению с SSR добавьте в <head> скрипт exodeBootScript() — тема и отступы окажутся на странице до
первой отрисовки. Скрипт меняет атрибуты <html> до React, поэтому нужен suppressHydrationWarning:
{ edgeToEdge: true, applyTheme: true }).
Шапка и меню «…»
title— заголовок страницы;null— название, которое школа дала странице.- Кнопки (
left— до одной,right— до шести) хост показывает пунктами одного меню «…» справа,leftпервым: ряд кнопок у заголовка теснил бы длинное название.label— текст пункта (без него показываетсяid),icon— одна из иконокMiniAppIcon:add,more,settings,search,share,info,help,edit,history,filter,sort,refresh,close,check,delete,download,upload,favorite,notifications,user,calendar,list,grid,qr. Кнопка с другой иконкой отбрасывается. - В вебе в меню «…» за разделителем — пункты хоста «На главную» и «Обновить» (мини-приложение загружается заново).
В окне страницы на ПК кнопки стоят иконками в шапке окна,
label— подсказка. - Хук действует, пока компонент смонтирован: после размонтирования — заголовок страницы и никаких кнопок. Шапка у хоста одна — используйте один такой хук на экран (побеждает последний смонтированный).
«Назад»
backButton:pressed вместо ухода со страницы. На
телефоне туда же ведут кнопка и свайп «Назад» браузера, а в приложении на iOS свайп назад выключается.
Главная кнопка
text хост её не показывает. loading — спиннер
вместо текста, disabled — приглушена и не нажимается, color и textColor — CSS-цвета фона и текста (по
умолчанию — акцент школы). Одна главная кнопка на экран.
Поле ввода
Поле внизу экрана, как в чатах: клавиатура, вложения, микрофон.
События:
composer:submit ({ text, files, voice? }), composer:voice ({ file, duration } — File и секунды),
composer:change ({ text }), composer:stop, composer:scrollDown. Хук возвращает isSupported, canDictate,
canRecordVoice, canVoiceOnly, canDictateAudio, canReview, canNoCancel, canAttach, а также setText(text)
('' очищает поле) и dismissKeyboard().
Пока поле показано, нижнее меню хоста скрыто. Поле у хоста одно — один такой хук на экран. В приложении протягивание
ленты вниз прячет клавиатуру нативного поля, как в чатах.
Только голос
Для тренажёров речи: ни поля, ни клавиатуры — только кнопки, которые говорят, что делают. Нажать — слушать (на кнопке — вашplaceholder), «Отправить» — отправить, крестик — сбросить; пауза в речи ничего не отправляет.
Вложения в этом режиме не предлагаются, busy превращает кнопку в «Остановить» (composer:stop).
mic: 'dictation'— пока говорят, услышанное приходит вcomposer:change, целиком — вcomposer:submit. Где речь в текст не распознаётся (браузер без Web Speech API), остаётся обычное поле.mic: 'voice'— запись приходит вcomposer:voice.recordAudio(composer.dictation.audio) — с диктовкой приходит и запись речи (voiceвcomposer:submit), например для модели, которая слушает произношение. Где хост не может её записать,voiceне будет.liveTranscript— хост показывает услышанное над кнопками. Не задано — показывает, только если нетrecordAudio.review(composer.voiceOnly.review) — сказанное попадает в поле хоста на правку, отправляет пользователь. Хост без этой возможности отправит сразу.noCancel(composer.voiceOnly.noCancel) — для тренажёров, которые считают попытки: ни крестика, пока слушает, ни «Заново» при правке. В браузере это действует для диктовки; у записи голосом (mic: 'voice') крестик рекордера остаётся.
Попапы
show— от одной до трёх кнопок (по умолчанию однаclose). Типы кнопок:default,ok,close,cancel,destructive; дляdefaultиdestructiveнуженtext, у остальных текст хоста.idпо умолчанию — тип кнопки.promptпринимаетtitle,message,placeholder,value,multiline,required,minLength,maxLength,okText,cancelText.- В приложении алерт с одной-двумя кнопками и
prompt— нативные. Ответа ждут сколько угодно, без таймаута. - Скрытое мини-приложение (пользователь ушёл на другую страницу) попап не покажет — придёт
null.
Весь экран и поворот
- На телефоне весь экран — без шапки и нижнего меню хоста, на ПК — Fullscreen API самого фрейма (выход по Esc
приложение узнает из
viewport:changed). - Поворот экрана (
orientation) есть только в приложении. Без этой возможности SDK пробуетscreen.orientation.lockбраузера — обычно он работает только в полноэкранном режиме. - Без хука:
app.viewport.requestFullscreen(),app.getViewport(),app.getInsets()и событиеviewport:changed(MiniAppViewport).
Вибрация
haptic заменяется на navigator.vibrate (браузер разрешает её только после жеста пользователя).
Ссылки, «Поделиться», буфер обмена
- Хост открывает только ссылки
http:,https:,mailto:иtel:и передаёт адрес так, как его разобрал браузер (new URL(url).href). Ссылки с другой схемой и невалидные адреса хост молча игнорирует. В вебе ссылка открывается в новой вкладке. - У
shareадрес проверяется так же: невалидный отбрасывается, и если не остаётсяtext,shareвернётfalse. В приложении открывается нативное окно «Поделиться», в вебе —navigator.share. Без возможностиshareи без «Поделиться» браузераshareвозвращаетfalse— текст при этом не копируется. copyTextв приложении пишет в буфер нативно, в вебе — черезnavigator.clipboard.- Скрытое мини-приложение ссылку не откроет, а
shareиcopyTextвернутfalse.
Силуэт загрузки и devtools
Пока грузится код мини-приложения, хост рисует силуэт загрузки страницы. Строку силуэта не нужно писать руками: devtools SDK (0.9+) измеряют первый экран и собирают её сами. В продакшен-сборку они не попадают — это отдельный модуль, в@exode-team/sdk/miniapp и miniapp.global.js их нет.
Из консоли браузера, без изменений в коде (во фрейме школы переключите консоль на контекст фрейма):
measureSkeleton({ root }) — строка, copySkeleton() — строка в консоль и
буфер, showSkeleton(line?), hideSkeleton(). Если у приложения есть свой скелетон, пометьте его корень атрибутом
data-exode-skeleton — devtools возьмут силуэт с него (скелетон должен быть на экране во время замера). Готовую
строку вставьте в поле «Силуэт загрузки» страницы школы.
Обновлено: 2026-10-22 23:06 UTC