SMeetMini Apps

SMeet Mini Apps

Примеры

Каждый пример представляет собой законченное мини-приложение со страницей, сервером и ботом. Скачайте его, дайте ему ключ своего мини-приложения и токен своего бота и запустите за своим origin с HTTPS.

Три примера#

ПримерЧто показываетНа чём работаетАрхив
Анкетавход без cookie, черновик на устройстве, submitToBot() с подтверждением SMeet, ответ бота в чатеNode.js 22.13 или новее, без пакетовsmeet-mini-apps-forms.zip
Служба заявокпроверка сессии перед каждой операцией, безопасная отдача файлов, outbox уведомлений в чат, операторыNode.js 22.13 или новее и PostgreSQL 11 или новееsmeet-mini-apps-desk.zip
Минимальный Pythonсамый короткий сервер: вход, проверка сессии, ответ ботаPython 3.9 или новее, только стандартная библиотекаsmeet-mini-apps-python-minimal.zip

У каждого примера есть README на русском и английском со всеми настройками, .env.example и тесты.

Анкета#

Анкета, которую человек заполняет в окне SMeet и отправляет боту.

  • Страница подключает SDK в <head> и вызывает connect(), ready() и вход в window.onload.
  • Вход: одноразовая попытка от сервера, authorize(), обмен на сервере с Idempotency-Key: login:<attempt id>, собственная сессия сервера в памяти страницы.
  • Черновик лежит в localStorage под ключом со средой и идентификатором человека и несёт свой requestId: повторная отправка никогда не создаёт вторую анкету.
  • Рабочий процесс заодно единственный получатель событий бота. Он раскладывает каждое mini_app_submission по его environment, подтверждает позицию только после записи и отвечает в чате с ревизией из события.

Его сервер гасит попытку входа и обменивает код:

JavaScriptdocs/mini-apps/examples/forms/lib/login.mjs
export async function signIn({ body, attempts, sessions, authorApi, environment }) {
  const attemptId = field(body, 'attempt_id', { pattern: ATTEMPT_RE, max: 64 });
  const attemptSecret = field(body, 'attempt_secret', { max: 128 });
  const code = field(body, 'code', { max: 512 });
  const codeVerifier = field(body, 'code_verifier', { pattern: VERIFIER_RE, max: 128 });
  if (!attempts.consume(attemptId, attemptSecret)) {
    throw new HttpError(400, 'LOGIN_RETRY', 'The sign-in attempt is unknown, used or expired', { reason: 'attempt' });
  }
  let exchanged;
  try {
    exchanged = await authorApi.exchange({ code, codeVerifier, idempotencyKey: `login:${attemptId}` });
  } catch (error) {
    throw pageError(error);
  }
  if (exchanged.environment !== environment) {
    // SMeet already refuses a code of the other environment with ENVIRONMENT_MISMATCH (6.3); never trust it twice.
    throw new HttpError(500, 'SERVER_MISCONFIGURED', 'The session belongs to another environment');
  }
  const own = sessions.create(exchanged);
  return {
    session_token: own.token,
    expires_at: new Date(own.expiresAt).toISOString(),
    environment: exchanged.environment,
    user: {
      id: exchanged.user?.id,
      display_name: exchanged.user?.display_name ?? null,
      language_code: exchanged.user?.language_code ?? 'en',
    },
    start_param: exchanged.start_param ?? null,
  };
}

Служба заявок#

Служба заявок организации: человек видит свои заявки и создаёт новую с файлом; операторы видят все заявки пространства и меняют их состояние.

  • checkSession без кеша перед чтением заявок, сохранением черновика, загрузкой и скачиванием файла, созданием заявки и сменой состояния.
  • Черновик и его файл хранятся на сервере по приложению, среде, пространству и человеку; при повторном открытии окна файл уже на месте.
  • Заявка создаётся под Idempotency-Key своего черновика, и та же транзакция записывает задание outbox с expected_access_revision проверки этого запроса (Outbox).
  • Воркер отправляет уведомление текстом или документом с файлом заявки, с ключом ntf:<notification id>.
  • Смена состояния оператором ничего не пишет в чат; человек видит её в приложении.
  • Файл отдаётся только как вложение типа application/octet-stream с nosniff и sandbox; в окне web человек получает копию в чате (Файлы).

Минимальный Python#

Самое короткое мини-приложение со своим сервером, в одном файле server.py: страница, SDK как /sdk.js, файл подтверждения домена /.well-known/smeet-mini-app.txt, попытка входа, обмен, checkSession перед каждой операцией и sendMessage бота с ревизией этой проверки. Страница page.js вызывает ready() и authorize() в window.onload.

Pythondocs/mini-apps/examples/python-minimal/server.py
def checked(self, token: Optional[str]) -> Tuple[Dict[str, Any], Dict[str, Any]]:
    """checkSession without a cache before every protected operation (6.5)."""
    with self.lock:
        session = self.sessions.get(token or "")
    if not session or session["expires"] <= self.clock():
        raise PageError(401, "SESSION_EXPIRED")
    try:
        check = self.author("checkSession", {"session_token": session["smeet"]})
    except SMeetError as error:
        raise page_error(error)
    if not check.get("active") or (check.get("user") or {}).get("id") != session["user"].get("id"):
        with self.lock:
            self.sessions.pop(token, None)
        raise PageError(401, "SESSION_INACTIVE", reason=check.get("reason"))
    return session, check

Что лежит в архиве#

Архив повторяет расположение исходников документации, поэтому пример находит SDK там, где ожидает:

Text
smeet-mini-apps-forms/
  examples/.gitignore           данные, сборки и пакеты не попадают в git
  examples/forms/               пример: README.md, .env.example, package.json, сервер, lib, web, test
  sdk/smeet-mini-apps-1.0.0.js  SDK, который пример встраивает в свою страницу, с .esm.js и объявлениями .d.ts
  • Архив рассчитан на ваш собственный origin: базовый путь /, а заголовок frame-ancestors https://messenger.scrile.com сервер ставит сам, беря его из SMEET_URL.
  • В нём нет .env, данных и установленных пакетов. Служба заявок ставит свой единственный пакет, клиент PostgreSQL, через npm ci по своему package-lock.json.
  • Тот же код запускает собственные демо SMeet под https://messenger.scrile.com/mini-apps/; отличается только окружение. Это размещение в архив не входит.

Запуск с SMeet#

  • Один процесс на среду, у каждого ключ своей среды в MINI_APP_KEY и свой origin. Тестовая и рабочая версии никогда не делят процесс.
  • У бота ровно один получатель событий: рабочий процесс (BOT_MODE=polling или webhook), который раскладывает каждую отправку по её environment. Тестовый процесс работает с BOT_MODE=off; пока рабочего процесса нет, события может получать тестовый.
  • SMEET_URL равен https://messenger.scrile.com. Минимальному примеру на Python нужен ещё MINI_APP_VERIFICATION_TOKEN, токен файла /.well-known/smeet-mini-app.txt.
  • Тесты примера запускаются через npm test или python3 -m unittest test_server.py; SMeet для них не нужен.