SMeet Mini Apps
SDK
SDK представляет собой один небольшой скрипт без зависимостей. Он связывает страницу с окном SMeet, проверяет, что окно действительно SMeet, и даёт странице коды входа, окружение и способ отправлять результаты боту.
Файлы и адреса#
Версия 1.0.0 выходит в двух сборках одного кода:
| Файл | Что это |
|---|---|
smeet-mini-apps-1.0.0.js | классический скрипт; создаёт глобальный объект SMeetMiniApp |
smeet-mini-apps-1.0.0.esm.js | ES-модуль с теми же экспортами: connect, NotInSMeetError, SMeetBridgeError, version |
smeet-mini-apps-1.js, smeet-mini-apps-1.esm.js | псевдонимы: новейшая версия 1.x каждой сборки |
SMeet раздаёт их по адресу https://messenger.scrile.com/docs/mini-apps/sdk/<файл>, например smeet-mini-apps-1.0.0.js. Версия никогда не меняется и кешируется на год; псевдоним переходит на каждую новую версию 1.x и кешируется на несколько минут. Оба отдаются любому сайту (Access-Control-Allow-Origin: *), поэтому сборка-модуль загружается и с другого origin.
Рабочему приложению лучше встроить SDK в свою сборку или раздавать файл со своего origin: тогда страница не зависит от чужого сервера, а ваш script-src называет только ваш origin. В каждом архиве примеров в папке sdk/ лежат файлы той версии, которую использует пример, вместе с объявлениями TypeScript (*.d.ts).
Подключение#
Подключайте SDK синхронно на каждой странице приложения, до события load страницы: <script src> в <head> или <script type="module">. Никогда не подключайте его лениво, после load или по кнопке.
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<script src="/static/smeet-mini-apps-1.0.0.js"></script>
<script src="/static/page.js" defer></script>
</head>import * as SMeetMiniApp from "/static/smeet-mini-apps-1.0.0.esm.js";Почему: SMeet ждёт подключения первой страницы окна 15 секунд и иначе показывает «Приложение не отвечает или запрещает встраивание». Когда человек переходит на следующую страницу вашего приложения, SMeet закрывает окно слоем, пока SDK этой страницы не ответит, потому что только живой SDK доказывает, что страница всё ещё на вашем домене. Страница без SDK или с поздно подключённым SDK так и остаётся под слоем.
Подключайте один SDK на страницу.
connect()#
let app;
try {
app = await SMeetMiniApp.connect();
} catch (error) {
if (error instanceof SMeetMiniApp.NotInSMeetError) return showOpenInSMeet();
if (error instanceof SMeetMiniApp.SMeetBridgeError) return showError(error.code); // NOT_READY через 10 с
throw error; // TypeError: неверный containerOrigins
}
drawFirstScreen(app.environment);
await app.ready();- Вне SMeet (страница не во фрейме и на iOS у неё нет обработчика SMeet) он сразу отказывает с
NotInSMeetErrorи ничего не слушает. У страницы, открытой черезwindow.open, моста нет. - Без ответа за 10 секунд он отказывает с
SMeetBridgeErrorNOT_READY. - Неверный
containerOriginsотказывает сразу сTypeError, ничего не отправив, и ничего не запоминает: следующийconnect()начинает заново. - Когда рукопожатие началось,
connect()выполняется один раз на документ: повторный вызов возвращает то же обещание, а отказ окончателен до перезагрузки страницы; человек может нажать в окне Повторить или Перезагрузить. - Объект
appесть только после завершенияconnect(); до этого странице нечего вызывать.
connect({containerOrigins}) заменяет список origin SMeet, которые принимает SDK, ["https://messenger.scrile.com"]. Он нужен только для локального стенда SMeet: http-origin в нём действует, только пока сама страница открыта на localhost или 127.0.0.1; на рабочей странице список из одних http-origin отказывает с TypeError, поэтому страница, в которой остался список стенда, ни к чему не подключится.
Окружение#
app.environment содержит то, что SMeet сообщает об этом запуске. SDK обновляет его при смене темы, размеров или языка; app.getEnvironment() спрашивает SMeet заново.
| Поле | Значение |
|---|---|
bridgeVersion | 1 |
platform | web, ios или macos |
appId | идентификатор мини-приложения, десятичная строка |
deployment | production или test |
startParam | параметр кнопки или ссылки, до 64 символов A-Z a-z 0-9 _ -, или null |
language | ru или en, язык SMeet у человека |
colorScheme | light или dark |
theme | цвета: bg, text, hint, link, button, buttonText, secondaryBg (Тема) |
viewport | {width, height, isExpanded}: видимая область под панелью SMeet и над экранной клавиатурой |
safeArea | {top, bottom, left, right}: вырезы устройства, пересекающие видимую область |
methods | методы, которые обслуживает этот клиент SMeet |
Используйте startParam, чтобы открыть нужный экран, но никогда как разрешение: в ссылку можно подставить любой параметр. Ваш сервер получает то же значение как start_param при обмене.
Методы#
| Вызов | Результат | Что делает |
|---|---|---|
app.supports(method) | boolean | есть ли метод в environment.methods |
app.getEnvironment() | окружение | заново запрашивает окружение у SMeet |
app.ready() | первый экран нарисован: SMeet убирает свой экран загрузки. Вызывайте в течение 15 секунд после подключения | |
app.authorize() | {code, codeVerifier, expiresAt} | новый код входа (Вход) |
app.submitToBot(data, {requestId}) | {submissionId, status} | отправляет data боту после подтверждения человеком (Отправка боту) |
app.getSubmissionStatus(submissionId) | {status} | в каком состоянии отправка |
app.openExternalLink(url) | {opened} | открывает адрес https в браузере после согласия человека; opened равно false, если он не согласился. В web-версии после трёх нажатий Отмена подряд в одном запуске следующие вызовы получают RATE_LIMITED, пока человек не откроет приложение заново |
app.setBackHandler(handler) | на Назад SMeet вызывает handler, а не действует сам; null возвращает Назад SMeet | |
app.setClosingConfirmation(enabled) | пока true, SMeet один раз переспрашивает перед закрытием окна: на странице есть несохранённые изменения | |
app.close() | закрывает окно |
Каждый метод возвращает обещание. Метод, которого нет в environment.methods, отказывает с UNSUPPORTED_METHOD, не доходя до SMeet; проверяйте app.supports(), если возможность необязательна.
События#
| Подписка | Данные | Когда |
|---|---|---|
app.onThemeChanged(callback) | {colorScheme, theme} | человек переключил светлое или тёмное оформление SMeet |
app.onViewportChanged(callback) | {viewport, safeArea} | открылась или закрылась клавиатура, повернулось устройство, окно изменило размер, развернулось или свернулось |
app.onVisibilityChanged(callback) | {visible} | SMeet скрыл окно, например на время звонка, и показал снова |
app.onLanguageChanged(callback) | {language} | только web: человек сменил язык; на iOS и macOS это закрывает окно |
Каждый on... возвращает функцию, которая снимает подписку. Когда вызывается подписка, в app.environment уже новые значения. Назад приходит в обработчик setBackHandler(), а не подпиской.
Отправка боту#
const draft = loadDraft() || { requestId: crypto.randomUUID(), fields: {} };
try {
const { submissionId } = await app.submitToBot(draft.fields, { requestId: draft.requestId });
clearDraft();
showReceipt(submissionId);
} catch (error) {
if (error.code === "USER_DECLINED") return; // человек нажал «Отмена»
if (error.code === "SUBMISSION_CONFLICT") draft.requestId = crypto.randomUUID();
saveDraft(draft); // повторить позже с тем же requestId
}- SMeet показывает человеку приложение, бота, размер и поля и отправляет только после Отправить. Одно подтверждение за раз: второй вызов в это время получает
BUSY. В web-версии после трёх нажатий Отмена подряд в одном запускеsubmitToBot()отвечаетRATE_LIMITED, пока человек не откроет приложение заново: ни ожидание, ни Перезагрузить в окне этого не сбрасывают. Сохраните черновик и егоrequestId; человек отправит его из нового окна. dataуходит в виде JSON, и это должен быть объект, не массив, до 16 КиБ в UTF-8.Date,Map,Setи объект, чейtoJSONдаёт не объект, отклоняются сINVALID_PARAMS.requestIdэто от 1 до 64 символовA-Z a-z 0-9 . _ : -. Создавайте его вместе с черновиком и храните в черновике. Повторная отправка с тем жеrequestIdи теми же данными после ошибки, перезагрузки или в новом окне даёт тот жеsubmissionIdи не создаёт боту второго события. Тот жеrequestIdс другими данными отказывает сSUBMISSION_CONFLICT: создайте новый.- Бот получает событие
mini_app_submission, в которомdataровно в том виде, как отправлено,client_request_idравенrequestId, а человека и чат задаёт SMeet (Событие).
app.getSubmissionStatus(submissionId) отвечает об отправках того же человека, приложения, среды и пространства, в том числе сделанных в прежнем запуске:
| Состояние | Значит |
|---|---|
pending | в очереди бота, ещё не подтверждена |
delivered | бот её подтвердил |
failed | она в списке ошибок доставки у владельца |
cancelled | отменена до доставки или пропущена владельцем |
expired | не подтверждена за 7 дней |
Ответ самого submitToBot() приходит с состоянием accepted. Неизвестная, чужая или удалённая отправка получает INVALID_PARAMS.
Ошибки#
Каждый отказ SMeet приходит как SMeetBridgeError с одним из этих кодов в error.code. Любой другой код от SMeet приходит как INTERNAL, а исходный код остаётся в error.message.
| Код | Значит | Что делать |
|---|---|---|
UNSUPPORTED_METHOD | этот клиент SMeet не обслуживает метод | проверить app.supports() и обойтись без него |
INVALID_PARAMS | неверные параметры или неизвестная отправка | исправить вызов; не повторять его как есть |
NOT_READY | окно показывает экран ошибки, или SMeet не ответил на connect() вовремя | ничего; человек повторит в окне |
USER_DECLINED | человек нажал Отмена в подтверждении | сохранить черновик |
BUSY | подтверждение уже открыто | дождаться первого вызова |
LAUNCH_CLOSED | запуск окончен; SMeet сам объяснит человеку причину | остановиться; сохранить черновик |
CODE_EXPIRED | код этого запроса истёк, не дойдя до страницы | снова вызвать authorize() |
RATE_LIMITED | слишком много вызовов или, в web-версии, три нажатия Отмена подряд в этом запуске | подождать и повторить; после трёх Отмена сохранить черновик: метод отказывает, пока человек не откроет приложение заново |
PAYLOAD_TOO_LARGE | сообщение больше 64 КиБ или data больше 16 КиБ | отправить меньше |
SUBMISSION_CONFLICT | этот requestId уже использован с другими данными | создать новый requestId |
QUEUE_FULL | очередь бота заполнена | сохранить черновик, повторить позже с тем же requestId |
NOT_ALLOWED | конфигурация приложения этого не разрешает (например, отправку боту) | включить возможность в настройках |
MAINTENANCE | мини-приложения на паузе | сообщить об этом, сохранить черновик, повторить позже |
NETWORK | SMeet не достучался до своего сервера, или страница сменилась, пока вызов был открыт | снова вызвать метод |
INTERNAL | ошибка SMeet | повторить один раз, затем показать ошибку |
CODE_EXPIRED и NETWORK рассчитаны на повтор: вызовите метод снова, это будет новый запрос. Повторный submitToBot() с тем же requestId снова спрашивает человека.
Мост#
SDK говорит с окном SMeet через мост версии 1. Чтобы пользоваться SDK, знать его не нужно; он описан для тех, кто проверяет сообщения или тестирует свои страницы.
- В web страница и окно встречаются так: страница шлёт
hello, SMeet отвечаетconnectи передаётMessagePort. SDK принимает порт только отwindow.parent, только с origin из своего списка (https://messenger.scrile.com), только с именем и версией моста и ровно один. Дальше по порту идут запросы, ответы и события; SMeet раз в секунду шлётping, SDK отвечаетpong, а наpagehideшлётleaving. - Домен в панели окна остаётся подтверждённым, только пока страница отвечает. Если страница ушла на другой сайт или перестала отвечать, SMeet в течение 2 секунд закрывает её слоем и пишет «Страница ушла с <домен> или не отвечает».
- На iOS и macOS страница отправляет запросы обработчику сообщений
smeetвеб-вида и получает каждый ответ как ответ обработчика; события приходят черезwindow.__smeetMiniAppEvent. - Сообщение не больше 64 КиБ; страница отправляет не больше 20 запросов в секунду.
Каждое сообщение в обе стороны описано схемой JSON Schema bridge.schema.json; тесты SDK проверяют по ней сообщения, которые он отправляет и принимает. Схема по этому адресу точно описывает текущий мост. В пределах версии 1 каждый её новый выпуск только добавляет необязательные поля, методы, события, значения status и коды ошибок, поэтому то, что было верным, верным и остаётся. Проверяйте по текущей схеме то, что отправляет ваша страница, и свои заглушки SMeet в тестах; во время работы пропускайте то, чего вы не знаете в сообщении SMeet, а не отвергайте сообщение целиком (Журнал изменений).
Чего SDK не делает#
- Ничего не хранит: ни в хранилище браузера, ни в cookie, ни между документами. Незавершённые запросы живут в памяти страницы, только чтобы отправить их снова по новому порту.
- Не проверяет права. SMeet проверяет их на своём сервере при каждом вызове.
- Не имеет переключателей для тестов. Чтобы попробовать страницу вне SMeet, обработайте
NotInSMeetError; для проверки на локальном стенде SMeet передайтеcontainerOrigins.
TypeScript#
Объявления лежат вместе с SDK в каждом архиве примеров: smeet-mini-apps-1.0.0.d.ts описывает глобальное пространство имён SMeetMiniApp классического скрипта, smeet-mini-apps-1.0.0.esm.d.ts описывает модуль. Типы Environment, MiniApp, BridgeErrorCode, SubmitResult и другие называются так же, как на этой странице.