Различия клиентов Telegram
Неизвестный сетевой итог теперь имеет отдельное состояние. Но ещё до запроса интерфейс может встретить старый клиент Telegram или обычный браузер, где нужной функции хоста нет. Результат урока — полный модуль определения возможностей и договор запасного поведения. Мы сохраним основной каталог и обычные действия документа, а необязательные методы не будут решать, кому разрешено сохранять личный план.
SDK и поддерживаемый клиентом протокол — разные величины. Актуальный подключённый JavaScript может содержать метод, который конкретный хост ещё не умеет выполнить. Поэтому одной проверки typeof иногда недостаточно. Telegram предоставляет isVersionAtLeast для оценки поддержки версии Bot API. Номер относится к возможностям окружения, а не к установленной у нас версии Node.js, браузерного пакета или бизнес-схемы планов. Эти числа не следует хранить в одной переменной version.
Минимальный набор
Наш публичный первый экран — обычные HTML, CSS и JavaScript. Он не требует DeviceStorage, shareMessage или fullscreen. Native BackButton и действия основной кнопки используют согласованный профиль не ниже 6.1; если он недоступен, остаются обычные button. Безопасные области доступны новым клиентам, а CSS имеет fallback нулевых дополнительных inset. Для личной записи нужны собственный сервер и сессия; их наличие не определяется по поддержке кнопки.
Полный capabilities.js возвращает карту возможностей. Он не подписывается на события и не делает пользовательских запросов. Поля могут использоваться при подготовке контроллеров, однако асинхронный вызов всё равно должен обрабатывать отказ. Даже поддержанная функция может встретить отмену человеком, недоступное хранилище или потерю связи. Проверка предварительного условия не равна успешному выполнению.
const methods = (value, names) => Boolean(value &&
names.every(name => typeof value[name] === 'function'));
export function inspectCapabilities(host) {
const atLeast = version => {
try {
return typeof host?.isVersionAtLeast === 'function' &&
host.isVersionAtLeast(version) === true;
} catch { return false; }
};
return Object.freeze({
nativeBack: atLeast('6.1') && methods(host?.BackButton,
['onClick', 'offClick', 'show', 'hide']),
nativeCommit: atLeast('6.1') && methods(host?.MainButton,
['onClick', 'offClick', 'setText', 'show', 'hide', 'enable', 'disable']),
cloudStorage: atLeast('6.9') && methods(host?.CloudStorage,
['getItem', 'setItem', 'removeItem']),
deviceStorage: atLeast('9.0') && methods(host?.DeviceStorage,
['getItem', 'setItem', 'removeItem']),
secureStorage: atLeast('9.0') && methods(host?.SecureStorage,
['getItem', 'setItem', 'removeItem']),
preparedSharing: atLeast('8.0') && methods(host, ['shareMessage']),
telegramLink: atLeast('6.1') && methods(host, ['openTelegramLink']),
closingConfirmation: atLeast('6.2') && methods(host,
['enableClosingConfirmation', 'disableClosingConfirmation'])
});
}
Возвращённый объект не содержит authenticated. Host можно подставить в браузере, как и любые данные JavaScript, поэтому карта удобна для интерфейса, но не для сервера. Проверка личности остаётся HMAC и сессией. Модули кнопок предыдущих уроков также проверяют базовую версию и нужные методы перед native веткой; карта объединяет эти решения в читабельный перечень. Она является самостоятельным модулем, а не обязательным импортом задним числом во всех снимках.
Запасное действие сохраняет смысл
Если nativeBack false, на экране курса показывается обычная кнопка «К каталогу» с той же операцией navigation.back. Если nativeCommit false, доступно обычное действие формы с тем же чтением полей и API. Нельзя выбирать разные правила в двух ветках: иначе человек со старым клиентом получит другой диапазон частоты или обход ошибки формы. Меняется механизм взаимодействия, а доменное действие остаётся общим.
Если deviceStorage false, выбранная несекретная настройка может использовать подготовленный browser storage либо оставаться только в памяти. Bearer не перемещается в другой механизм автоматически. Если preparedSharing false, публичным курсом можно поделиться обычной share link. Но нельзя имитировать true callback и объявлять отправку. Важно, что fallback решает ту же понятную задачу пользователя, а не скрывает неподдержанную функцию за одинаковой зелёной галочкой.
Версия и состояние клиента
Не определяйте поддержку по строке platform: android, ios или desktop не сообщает точный набор методов конкретного выпуска. Два устройства одной платформы могут иметь разную версию приложения. IsVersionAtLeast и наличие метода полезнее предположения по названию ОС. Официальный справочник указывает минимальные версии функций; например DeviceStorage и SecureStorage появились в 9.0, prepared sharing — в 8.0.
В архиве есть матрица клиентов с отдельными колонками: платформа, версия Telegram, WebApp.version, сценарий, ожидаемый результат и observed. Пока observed везде not_run. Значения не заполняются выдуманными «работает» по факту чтения документации. После будущего выполнения полезно записать конкретную версию клиента и дату, а не бессрочное утверждение «проверено на Android». Такая запись помогает воспроизвести отказ после изменения SDK или UI хоста.
Подписки и смена экрана
Определение возможностей не создаёт обработчики. Их создаёт конкретный владелец жизненного цикла: theme module, viewport module, navigation или commit. При повторном render нельзя запускать все bind заново без dispose. Наличие нужного метода не делает двойную подписку допустимой. Один callback должен сниматься той же ссылкой, иначе старый экран остаётся участником событий и способен поменять сообщение после перехода на другой курс.
Для карты есть простой отрицательный сценарий: объект объявляет version 9.0, но у него нет setItem. DeviceStorage получается false, потому что совместимость требует и версии, и конкретного интерфейса. Другой сценарий — SDK содержит shareMessage, но host сообщает старый протокол; preparedSharing false. Такая проверка не является защитой от злонамеренного поддельного host, зато предотвращает случайные вызовы неподдержанных механизмов в обычной работе интерфейса.
Передавать карту в аналитику следует осторожно: для диагностики достаточно выбранной ветки и версии окружения без пользовательского launch. Сам факт наличия функции не является событием использования. Например, preparedSharing true не означает, что человек поделился курсом; deviceStorage true не подтверждает успешную запись настройки. Такой договор помогает не смешивать техническую совместимость с продуктовыми действиями и не создавать ложные показатели успеха при подготовке экрана.
Необязательная функция и основной маршрут
Закрывающее подтверждение может напомнить о dirty, если поддержано, но не гарантирует сохранение памяти после аварийного закрытия или выгрузки страницы. Включать его имеет смысл только при несохранённых изменениях и с понятным жизненным циклом. В нашем модуле карта лишь сообщает доступность; отдельного binding closing confirmation и постоянного draft recovery нет. Поэтому выпуск не должен обещать, что любой закрытый WebView автоматически восстановит план.
При отказе новой функции показывайте полезный основной экран, а не техническую ошибку на весь документ. Например, каталог продолжает читаться без CloudStorage. Если невозможна важная персональная запись из-за сессии или сервера, объяснение относится к этой операции, а не маскируется фразой «Обновите Telegram» без доказательств. Различие клиента, транспорта и авторизации сохраняется даже в маленьком приложении.
Ожидаемый результат — карта даёт false для отсутствующего или старого API, а интерфейс выбирает согласованное запасное действие. Telegram-клиенты, JavaScript, хранилища и callbacks здесь не выполнялись. Полная интеграция всех веток и матрица реальных клиентов остаются будущей работой. Следующий урок разберёт доступность самой страницы: семантика, фокус и размер элементов не появляются автоматически вместе с поддержкой SDK.