SMeetMini Apps

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.jsES-модуль с теми же экспортами: 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 или по кнопке.

HTML
<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>
JavaScript
import * as SMeetMiniApp from "/static/smeet-mini-apps-1.0.0.esm.js";

Почему: SMeet ждёт подключения первой страницы окна 15 секунд и иначе показывает «Приложение не отвечает или запрещает встраивание». Когда человек переходит на следующую страницу вашего приложения, SMeet закрывает окно слоем, пока SDK этой страницы не ответит, потому что только живой SDK доказывает, что страница всё ещё на вашем домене. Страница без SDK или с поздно подключённым SDK так и остаётся под слоем.

Подключайте один SDK на страницу.

connect()#

JavaScript
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 секунд он отказывает с SMeetBridgeError NOT_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 заново.

ПолеЗначение
bridgeVersion1
platformweb, ios или macos
appIdидентификатор мини-приложения, десятичная строка
deploymentproduction или test
startParamпараметр кнопки или ссылки, до 64 символов A-Z a-z 0-9 _ -, или null
languageru или en, язык SMeet у человека
colorSchemelight или 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(), а не подпиской.

Отправка боту#

JavaScript
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мини-приложения на паузесообщить об этом, сохранить черновик, повторить позже
NETWORKSMeet не достучался до своего сервера, или страница сменилась, пока вызов был открытснова вызвать метод
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 и другие называются так же, как на этой странице.