diff --git a/.gitignore b/.gitignore index bc2510c..fbbc6d4 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ __pycache__/ locales/ru_backup.json locales/en_backup.json db/models_old.py +config/tariffs.json +docker-compose-dev.yml diff --git a/README.md b/README.md index ea2bdbc..8492b57 100644 --- a/README.md +++ b/README.md @@ -303,19 +303,6 @@ Web App запускается в том же контейнере, что и б Для email-регистраций пользователь в панели Remnawave создается с анонимным username вида `em_`; email добавляется в описание пользователя панели и, если API панели принимает поле email, передается отдельным полем. Для Telegram-регистраций сохраняется существующая схема `tg_`. -Шапка и модальные окна Web App учитывают Telegram safe area через `--tg-content-safe-area-inset-top` и `--tg-safe-area-inset-top`, а сверху добавлен повышенный дополнительный буфер, чтобы интерфейс не уезжал под панель Telegram при открытии из списка чатов. - -Для настройки внешнего вида без запуска бота можно открыть файл `bot/app/web/templates/subscription_webapp.html` напрямую в браузере. Рядом с ним лежат `telegram-web-app.js`, `telegram-widget.js`, `subscription_webapp.css` и `subscription_webapp.js`, поэтому локальный предпросмотр работает без сервера и без внешнего CDN. При отдаче страницы через Web App сервер dev-mock автоматически вырезается и не попадает пользователям. - -Локальная копия Telegram Web App SDK хранится в `bot/app/web/templates/telegram-web-app.js`, а локальная копия Telegram Login Widget - в `bot/app/web/templates/telegram-widget.js`. В контейнере обе копии автоматически обновляются при старте Web App и затем раз в 24 часа; если источник временно недоступен, используется уже сохраненная версия. У локального `telegram-widget.js` есть минимальная нормализация, чтобы при загрузке с вашего домена виджет все равно открывал iframe на Telegram-origin, а не на `/embed/...` вашего сайта. Для ручного обновления можно запустить команды: - -```bash -python scripts/update_telegram_web_app_js.py -python scripts/update_telegram_widget_js.py -``` - -При необходимости оба скрипта принимают `--source-url` и `--target`, если нужно скачать файл в другое место или проверить альтернативный источник. - ## Подробная инструкция для развертывания на сервере с панелью Remnawave ### 1. Клонирование репозитория diff --git a/bot/app/web/subscription_webapp.py b/bot/app/web/subscription_webapp.py index 0849b93..d689b16 100644 --- a/bot/app/web/subscription_webapp.py +++ b/bot/app/web/subscription_webapp.py @@ -48,13 +48,7 @@ logger = logging.getLogger(__name__) TEMPLATE_PATH = Path(__file__).resolve().parent / "templates" / "subscription_webapp.html" ASSET_DIR = TEMPLATE_PATH.parent -TELEGRAM_WEB_APP_SDK_URL = "https://telegram.org/js/telegram-web-app.js" -TELEGRAM_WEB_APP_SDK_PATH = ASSET_DIR / "telegram-web-app.js" -TELEGRAM_WIDGET_SDK_URL = "https://telegram.org/js/telegram-widget.js?23" -TELEGRAM_WIDGET_SDK_PATH = ASSET_DIR / "telegram-widget.js" WEBAPP_LOGO_PROXY_PATH = "/webapp-logo" -_UNPATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org') {\n origin = default_origin;\n } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') {\n origin = dev_origin;\n }\n""" -_PATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org') {\n origin = default_origin;\n } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') {\n origin = dev_origin;\n } else {\n origin = default_origin;\n }\n""" WEBAPP_CONFIG_PLACEHOLDER = "" WEBAPP_I18N_PLACEHOLDER = "" WEBAPP_JS_PLACEHOLDER = "" @@ -164,8 +158,6 @@ def create_subscription_webapp_application( def setup_subscription_webapp_routes(app: web.Application) -> None: app.router.add_get("/", index_route) app.router.add_get("/health", health_route) - app.router.add_get("/telegram-web-app.js", telegram_web_app_asset_route) - app.router.add_get("/telegram-widget.js", telegram_widget_asset_route) app.router.add_get(WEBAPP_LOGO_PROXY_PATH, webapp_logo_route) app.router.add_get("/subscription_webapp.css", css_asset_route) app.router.add_get("/subscription_webapp.min.{asset_hash}.js", js_asset_route) @@ -467,144 +459,6 @@ async def _enforce_webapp_rate_limit( return None -async def telegram_web_app_asset_route(request: web.Request) -> web.Response: - if not TELEGRAM_WEB_APP_SDK_PATH.exists(): - await refresh_telegram_web_app_sdk() - - try: - response = await _serve_template_asset( - request, - "telegram-web-app.js", - "application/javascript", - ) - except FileNotFoundError: - logger.exception( - "Telegram Web App SDK is unavailable at %s", - TELEGRAM_WEB_APP_SDK_PATH, - ) - raise web.HTTPServiceUnavailable(text="telegram_web_app_sdk_unavailable") - - response.headers["Cache-Control"] = "no-cache" - return response - - -async def telegram_widget_asset_route(request: web.Request) -> web.Response: - if not TELEGRAM_WIDGET_SDK_PATH.exists(): - await refresh_telegram_login_widget_sdk() - - try: - data = TELEGRAM_WIDGET_SDK_PATH.read_bytes() - except FileNotFoundError: - logger.exception( - "Telegram Login Widget SDK is unavailable at %s", - TELEGRAM_WIDGET_SDK_PATH, - ) - raise web.HTTPServiceUnavailable(text="telegram_widget_sdk_unavailable") - - data = _normalize_telegram_login_widget_sdk(data) - response = web.Response(body=data, content_type="application/javascript") - response.headers["Cache-Control"] = "no-cache" - return response - - -async def refresh_telegram_web_app_sdk() -> bool: - """Best-effort refresh of the vendored Telegram Web App SDK.""" - try: - session = await _get_shared_http_session() - async with session.get(TELEGRAM_WEB_APP_SDK_URL) as response: - if response.status != 200: - logger.warning( - "Telegram Web App SDK refresh returned HTTP %s; keeping the bundled copy.", - response.status, - ) - return False - data = await response.read() - except Exception as exc: - logger.warning("Failed to refresh Telegram Web App SDK: %s", exc) - return False - - try: - TELEGRAM_WEB_APP_SDK_PATH.parent.mkdir(parents=True, exist_ok=True) - existing_data = ( - TELEGRAM_WEB_APP_SDK_PATH.read_bytes() - if TELEGRAM_WEB_APP_SDK_PATH.exists() - else None - ) - if existing_data == data: - logger.info("Telegram Web App SDK is already up to date.") - return True - - temp_path = TELEGRAM_WEB_APP_SDK_PATH.with_name( - f"{TELEGRAM_WEB_APP_SDK_PATH.name}.tmp" - ) - temp_path.write_bytes(data) - temp_path.replace(TELEGRAM_WEB_APP_SDK_PATH) - logger.info( - "Telegram Web App SDK updated at %s (%d bytes).", - TELEGRAM_WEB_APP_SDK_PATH, - len(data), - ) - return True - except Exception as exc: - logger.warning("Failed to store Telegram Web App SDK locally: %s", exc) - return False - - -async def refresh_telegram_login_widget_sdk() -> bool: - """Best-effort refresh of the vendored Telegram Login Widget SDK.""" - try: - session = await _get_shared_http_session() - async with session.get(TELEGRAM_WIDGET_SDK_URL) as response: - if response.status != 200: - logger.warning( - "Telegram Login Widget SDK refresh returned HTTP %s; keeping the bundled copy.", - response.status, - ) - return False - data = await response.read() - except Exception as exc: - logger.warning("Failed to refresh Telegram Login Widget SDK: %s", exc) - return False - - try: - data = _normalize_telegram_login_widget_sdk(data) - TELEGRAM_WIDGET_SDK_PATH.parent.mkdir(parents=True, exist_ok=True) - existing_data = ( - TELEGRAM_WIDGET_SDK_PATH.read_bytes() - if TELEGRAM_WIDGET_SDK_PATH.exists() - else None - ) - if existing_data == data: - logger.info("Telegram Login Widget SDK is already up to date.") - return True - - temp_path = TELEGRAM_WIDGET_SDK_PATH.with_name( - f"{TELEGRAM_WIDGET_SDK_PATH.name}.tmp" - ) - temp_path.write_bytes(data) - temp_path.replace(TELEGRAM_WIDGET_SDK_PATH) - logger.info( - "Telegram Login Widget SDK updated at %s (%d bytes).", - TELEGRAM_WIDGET_SDK_PATH, - len(data), - ) - return True - except Exception as exc: - logger.warning("Failed to store Telegram Login Widget SDK locally: %s", exc) - return False - - -def _normalize_telegram_login_widget_sdk(data: bytes) -> bytes: - # Keep the vendored widget pointing to Telegram's OAuth host instead of the local origin. - text = data.decode("utf-8") - normalized = text.replace( - _UNPATCHED_WIDGET_ORIGIN_SNIPPET, - _PATCHED_WIDGET_ORIGIN_SNIPPET, - 1, - ) - return normalized.encode("utf-8") - - async def js_asset_route(request: web.Request) -> web.Response: asset_hash = request.match_info.get("asset_hash") filename = ( diff --git a/bot/app/web/templates/subscription_webapp.html b/bot/app/web/templates/subscription_webapp.html index 7d151ff..5a37e87 100644 --- a/bot/app/web/templates/subscription_webapp.html +++ b/bot/app/web/templates/subscription_webapp.html @@ -19,7 +19,7 @@ font-display: swap; } - + diff --git a/bot/app/web/templates/subscription_webapp.js b/bot/app/web/templates/subscription_webapp.js index 2549c5f..9626b2b 100644 --- a/bot/app/web/templates/subscription_webapp.js +++ b/bot/app/web/templates/subscription_webapp.js @@ -91,7 +91,8 @@ const MOCK = (() => { const CFG = readJsonScript('webapp-config') || (MOCK && MOCK.config) || {}; const tg = window.Telegram && window.Telegram.WebApp ? window.Telegram.WebApp : null; - const TELEGRAM_LOGIN_WIDGET_URL = './telegram-widget.js'; + const TELEGRAM_LOGIN_WIDGET_URL = 'https://telegram.org/js/telegram-widget.js?23'; + const TELEGRAM_LOGIN_WIDGET_RENDER_TIMEOUT_MS = 8000; const MANUAL_LOGOUT_FLAG_KEY = 'rw_webapp_manual_logout'; const state = { token: MOCK ? 'local-preview' : (localStorage.getItem('rw_webapp_token') || ''), @@ -241,7 +242,8 @@ const MOCK = (() => { telegram_auth: 'Telegram auth', telegram_auth_verifying: 'Проверяю вход...', telegram_auth_failed: 'Не удалось подтвердить Telegram-вход. Попробуйте еще раз.', - telegram_auth_unavailable: 'Telegram Login Widget недоступен. Проверьте username бота и доступ к telegram.org.', + telegram_auth_unavailable: 'Сервер Telegram сейчас недоступен. Попробуйте позже или войдите по email.', + telegram_bot_unavailable: 'Telegram Login Widget недоступен. Попробуйте позже или войдите по email.', telegram_auth_access_denied: 'Доступ запрещен.', active: 'Активна', inactive: 'Не активна', @@ -381,7 +383,8 @@ const MOCK = (() => { telegram_auth: 'Telegram auth', telegram_auth_verifying: 'Verifying login...', telegram_auth_failed: 'Could not verify Telegram login. Try again.', - telegram_auth_unavailable: 'Telegram Login Widget is unavailable. Check the bot username and access to telegram.org.', + telegram_auth_unavailable: 'Telegram server is unavailable right now. Try again later or sign in by email.', + telegram_bot_unavailable: 'Telegram Login Widget is unavailable. Try again later or sign in by email.', telegram_auth_access_denied: 'Access denied.', active: 'Active', inactive: 'Inactive', @@ -701,6 +704,32 @@ const MOCK = (() => { await finalizeTelegramAuth(user, 'auth_data'); } + function appendTelegramLoginWidget(container, botUsername, callbackName, onUnavailable) { + const script = document.createElement('script'); + let unavailableShown = false; + const showUnavailable = () => { + if (unavailableShown) return; + unavailableShown = true; + onUnavailable(); + }; + + script.async = true; + script.src = TELEGRAM_LOGIN_WIDGET_URL; + script.setAttribute('data-telegram-login', botUsername); + script.setAttribute('data-size', 'large'); + script.setAttribute('data-userpic', 'true'); + script.setAttribute('data-request-access', 'write'); + script.setAttribute('data-onauth', `${callbackName}(user)`); + script.onerror = showUnavailable; + script.onload = () => { + window.setTimeout(() => { + if (!container.contains(script) || container.querySelector('iframe')) return; + showUnavailable(); + }, TELEGRAM_LOGIN_WIDGET_RENDER_TIMEOUT_MS); + }; + container.appendChild(script); + } + function setAuthMode(mode) { state.authMode = mode === 'telegram' ? 'telegram' : 'email'; renderAuthMode(); @@ -740,7 +769,7 @@ const MOCK = (() => { container.innerHTML = ''; const botUsername = String(CFG.telegramLoginBotUsername || '').trim(); if (!botUsername) { - setAuthStatus(t('telegram_auth_unavailable'), true); + setAuthStatus(t('telegram_bot_unavailable'), true); return; } @@ -750,16 +779,13 @@ const MOCK = (() => { }; } - const script = document.createElement('script'); - script.async = true; - script.src = TELEGRAM_LOGIN_WIDGET_URL; - script.setAttribute('data-telegram-login', botUsername); - script.setAttribute('data-size', 'large'); - script.setAttribute('data-userpic', 'true'); - script.setAttribute('data-request-access', 'write'); - script.setAttribute('data-onauth', 'onTelegramAuth(user)'); - script.onerror = () => setAuthStatus(t('telegram_auth_unavailable'), true); - container.appendChild(script); + setAuthStatus(''); + appendTelegramLoginWidget( + container, + botUsername, + 'onTelegramAuth', + () => setAuthStatus(t('telegram_auth_unavailable'), true) + ); } function bindEmailLoginInput() { @@ -1864,7 +1890,7 @@ const MOCK = (() => { container.innerHTML = ''; const botUsername = String(CFG.telegramLoginBotUsername || '').trim(); if (!botUsername) { - setTelegramLinkStatus(t('telegram_auth_unavailable'), true); + setTelegramLinkStatus(t('telegram_bot_unavailable'), true); return; } @@ -1872,16 +1898,13 @@ const MOCK = (() => { await linkTelegramAccount(user); }; - const script = document.createElement('script'); - script.async = true; - script.src = TELEGRAM_LOGIN_WIDGET_URL; - script.setAttribute('data-telegram-login', botUsername); - script.setAttribute('data-size', 'large'); - script.setAttribute('data-userpic', 'true'); - script.setAttribute('data-request-access', 'write'); - script.setAttribute('data-onauth', 'onTelegramLinkAuth(user)'); - script.onerror = () => setTelegramLinkStatus(t('telegram_auth_unavailable'), true); - container.appendChild(script); + setTelegramLinkStatus(''); + appendTelegramLoginWidget( + container, + botUsername, + 'onTelegramLinkAuth', + () => setTelegramLinkStatus(t('telegram_auth_unavailable'), true) + ); state.telegramLinkRendered = true; } diff --git a/bot/app/web/templates/telegram-web-app.js b/bot/app/web/templates/telegram-web-app.js deleted file mode 100644 index 6c8051c..0000000 --- a/bot/app/web/templates/telegram-web-app.js +++ /dev/null @@ -1,3392 +0,0 @@ -// WebView -(function () { - var eventHandlers = {}; - - var locationHash = ''; - try { - locationHash = location.hash.toString(); - } catch (e) {} - - var initParams = urlParseHashParams(locationHash); - var storedParams = sessionStorageGet('initParams'); - if (storedParams) { - for (var key in storedParams) { - if (typeof initParams[key] === 'undefined') { - initParams[key] = storedParams[key]; - } - } - } - sessionStorageSet('initParams', initParams); - - var isIframe = false, iFrameStyle; - try { - isIframe = (window.parent != null && window != window.parent); - if (isIframe) { - window.addEventListener('message', function (event) { - if (event.source !== window.parent) return; - try { - var dataParsed = JSON.parse(event.data); - } catch (e) { - return; - } - if (!dataParsed || !dataParsed.eventType) { - return; - } - if (dataParsed.eventType == 'set_custom_style') { - if (event.origin === 'https://web.telegram.org') { - iFrameStyle.innerHTML = dataParsed.eventData; - } - } else if (dataParsed.eventType == 'reload_iframe') { - try { - window.parent.postMessage(JSON.stringify({eventType: 'iframe_will_reload'}), '*'); - } catch (e) {} - location.reload(); - } else { - receiveEvent(dataParsed.eventType, dataParsed.eventData); - } - }); - iFrameStyle = document.createElement('style'); - document.head.appendChild(iFrameStyle); - try { - window.parent.postMessage(JSON.stringify({eventType: 'iframe_ready', eventData: {reload_supported: true}}), '*'); - } catch (e) {} - } - } catch (e) {} - - function urlSafeDecode(urlencoded) { - try { - urlencoded = urlencoded.replace(/\+/g, '%20'); - return decodeURIComponent(urlencoded); - } catch (e) { - return urlencoded; - } - } - - function urlParseHashParams(locationHash) { - locationHash = locationHash.replace(/^#/, ''); - var params = {}; - if (!locationHash.length) { - return params; - } - if (locationHash.indexOf('=') < 0 && locationHash.indexOf('?') < 0) { - params._path = urlSafeDecode(locationHash); - return params; - } - var qIndex = locationHash.indexOf('?'); - if (qIndex >= 0) { - var pathParam = locationHash.substr(0, qIndex); - params._path = urlSafeDecode(pathParam); - locationHash = locationHash.substr(qIndex + 1); - } - var query_params = urlParseQueryString(locationHash); - for (var k in query_params) { - params[k] = query_params[k]; - } - return params; - } - - function urlParseQueryString(queryString) { - var params = {}; - if (!queryString.length) { - return params; - } - var queryStringParams = queryString.split('&'); - var i, param, paramName, paramValue; - for (i = 0; i < queryStringParams.length; i++) { - param = queryStringParams[i].split('='); - paramName = urlSafeDecode(param[0]); - paramValue = param[1] == null ? null : urlSafeDecode(param[1]); - params[paramName] = paramValue; - } - return params; - } - - // Telegram apps will implement this logic to add service params (e.g. tgShareScoreUrl) to game URL - function urlAppendHashParams(url, addHash) { - // url looks like 'https://game.com/path?query=1#hash' - // addHash looks like 'tgShareScoreUrl=' + encodeURIComponent('tgb://share_game_score?hash=very_long_hash123') - - var ind = url.indexOf('#'); - if (ind < 0) { - // https://game.com/path -> https://game.com/path#tgShareScoreUrl=etc - return url + '#' + addHash; - } - var curHash = url.substr(ind + 1); - if (curHash.indexOf('=') >= 0 || curHash.indexOf('?') >= 0) { - // https://game.com/#hash=1 -> https://game.com/#hash=1&tgShareScoreUrl=etc - // https://game.com/#path?query -> https://game.com/#path?query&tgShareScoreUrl=etc - return url + '&' + addHash; - } - // https://game.com/#hash -> https://game.com/#hash?tgShareScoreUrl=etc - if (curHash.length > 0) { - return url + '?' + addHash; - } - // https://game.com/# -> https://game.com/#tgShareScoreUrl=etc - return url + addHash; - } - - function postEvent(eventType, callback, eventData) { - if (!callback) { - callback = function () {}; - } - if (eventData === undefined) { - eventData = ''; - } - console.log('[Telegram.WebView] > postEvent', eventType, eventData); - - if (window.TelegramWebviewProxy !== undefined) { - TelegramWebviewProxy.postEvent(eventType, JSON.stringify(eventData)); - callback(); - } - else if (window.external && 'notify' in window.external) { - window.external.notify(JSON.stringify({eventType: eventType, eventData: eventData})); - callback(); - } - else if (isIframe) { - try { - var trustedTarget = 'https://web.telegram.org'; - // For now we don't restrict target, for testing purposes - trustedTarget = '*'; - window.parent.postMessage(JSON.stringify({eventType: eventType, eventData: eventData}), trustedTarget); - callback(); - } catch (e) { - callback(e); - } - } - else { - callback({notAvailable: true}); - } - }; - - function receiveEvent(eventType, eventData) { - console.log('[Telegram.WebView] < receiveEvent', eventType, eventData); - callEventCallbacks(eventType, function(callback) { - callback(eventType, eventData); - }); - } - - function callEventCallbacks(eventType, func) { - var curEventHandlers = eventHandlers[eventType]; - if (curEventHandlers === undefined || - !curEventHandlers.length) { - return; - } - for (var i = 0; i < curEventHandlers.length; i++) { - try { - func(curEventHandlers[i]); - } catch (e) {} - } - } - - function onEvent(eventType, callback) { - if (eventHandlers[eventType] === undefined) { - eventHandlers[eventType] = []; - } - var index = eventHandlers[eventType].indexOf(callback); - if (index === -1) { - eventHandlers[eventType].push(callback); - } - }; - - function offEvent(eventType, callback) { - if (eventHandlers[eventType] === undefined) { - return; - } - var index = eventHandlers[eventType].indexOf(callback); - if (index === -1) { - return; - } - eventHandlers[eventType].splice(index, 1); - }; - - function openProtoUrl(url) { - if (!url.match(/^(web\+)?tgb?:\/\/./)) { - return false; - } - var useIframe = navigator.userAgent.match(/iOS|iPhone OS|iPhone|iPod|iPad/i) ? true : false; - if (useIframe) { - var iframeContEl = document.getElementById('tgme_frame_cont') || document.body; - var iframeEl = document.createElement('iframe'); - iframeContEl.appendChild(iframeEl); - var pageHidden = false; - var enableHidden = function () { - pageHidden = true; - }; - window.addEventListener('pagehide', enableHidden, false); - window.addEventListener('blur', enableHidden, false); - if (iframeEl !== null) { - iframeEl.src = url; - } - setTimeout(function() { - if (!pageHidden) { - window.location = url; - } - window.removeEventListener('pagehide', enableHidden, false); - window.removeEventListener('blur', enableHidden, false); - }, 2000); - } - else { - window.location = url; - } - return true; - } - - function sessionStorageSet(key, value) { - try { - window.sessionStorage.setItem('__telegram__' + key, JSON.stringify(value)); - return true; - } catch(e) {} - return false; - } - function sessionStorageGet(key) { - try { - return JSON.parse(window.sessionStorage.getItem('__telegram__' + key)); - } catch(e) {} - return null; - } - - if (!window.Telegram) { - window.Telegram = {}; - } - window.Telegram.WebView = { - initParams: initParams, - isIframe: isIframe, - onEvent: onEvent, - offEvent: offEvent, - postEvent: postEvent, - receiveEvent: receiveEvent, - callEventCallbacks: callEventCallbacks - }; - - window.Telegram.Utils = { - urlSafeDecode: urlSafeDecode, - urlParseQueryString: urlParseQueryString, - urlParseHashParams: urlParseHashParams, - urlAppendHashParams: urlAppendHashParams, - sessionStorageSet: sessionStorageSet, - sessionStorageGet: sessionStorageGet - }; - - // For Windows Phone app - window.TelegramGameProxy_receiveEvent = receiveEvent; - - // App backward compatibility - window.TelegramGameProxy = { - receiveEvent: receiveEvent - }; -})(); - -// WebApp -(function () { - var Utils = window.Telegram.Utils; - var WebView = window.Telegram.WebView; - var initParams = WebView.initParams; - var isIframe = WebView.isIframe; - - var WebApp = {}; - var webAppInitData = '', webAppInitDataUnsafe = {}; - var themeParams = {}, colorScheme = 'light'; - var webAppVersion = '6.0'; - var webAppPlatform = 'unknown'; - var webAppIsActive = true; - var webAppIsFullscreen = false; - var webAppIsOrientationLocked = false; - var webAppBackgroundColor = 'bg_color'; - var webAppHeaderColorKey = 'bg_color'; - var webAppHeaderColor = null; - - if (initParams.tgWebAppData && initParams.tgWebAppData.length) { - webAppInitData = initParams.tgWebAppData; - webAppInitDataUnsafe = Utils.urlParseQueryString(webAppInitData); - for (var key in webAppInitDataUnsafe) { - var val = webAppInitDataUnsafe[key]; - try { - if (val.substr(0, 1) == '{' && val.substr(-1) == '}' || - val.substr(0, 1) == '[' && val.substr(-1) == ']') { - webAppInitDataUnsafe[key] = JSON.parse(val); - } - } catch (e) {} - } - } - var stored_theme_params = Utils.sessionStorageGet('themeParams'); - if (initParams.tgWebAppThemeParams && initParams.tgWebAppThemeParams.length) { - var themeParamsRaw = initParams.tgWebAppThemeParams; - try { - var theme_params = JSON.parse(themeParamsRaw); - if (theme_params) { - setThemeParams(theme_params); - } - } catch (e) {} - } - if (stored_theme_params) { - setThemeParams(stored_theme_params); - } - var stored_def_colors = Utils.sessionStorageGet('defaultColors'); - if (initParams.tgWebAppDefaultColors && initParams.tgWebAppDefaultColors.length) { - var defColorsRaw = initParams.tgWebAppDefaultColors; - try { - var def_colors = JSON.parse(defColorsRaw); - if (def_colors) { - setDefaultColors(def_colors); - } - } catch (e) {} - } - if (stored_def_colors) { - setDefaultColors(stored_def_colors); - } - if (initParams.tgWebAppVersion) { - webAppVersion = initParams.tgWebAppVersion; - } - if (initParams.tgWebAppPlatform) { - webAppPlatform = initParams.tgWebAppPlatform; - } - - var stored_fullscreen = Utils.sessionStorageGet('isFullscreen'); - if (initParams.tgWebAppFullscreen) { - setFullscreen(true); - } - if (stored_fullscreen) { - setFullscreen(stored_fullscreen == 'yes'); - } - - var stored_orientation_lock = Utils.sessionStorageGet('isOrientationLocked'); - if (stored_orientation_lock) { - setOrientationLock(stored_orientation_lock == 'yes'); - } - - function onThemeChanged(eventType, eventData) { - if (eventData.theme_params) { - setThemeParams(eventData.theme_params); - window.Telegram.WebApp.MainButton.setParams({}); - window.Telegram.WebApp.SecondaryButton.setParams({}); - updateHeaderColor(); - updateBackgroundColor(); - updateBottomBarColor(); - receiveWebViewEvent('themeChanged'); - } - } - - var lastWindowHeight = window.innerHeight; - function onViewportChanged(eventType, eventData) { - if (eventData.height) { - window.removeEventListener('resize', onWindowResize); - setViewportHeight(eventData); - } - } - - function onWindowResize(e) { - if (lastWindowHeight != window.innerHeight) { - lastWindowHeight = window.innerHeight; - receiveWebViewEvent('viewportChanged', { - isStateStable: true - }); - } - } - - function onSafeAreaChanged(eventType, eventData) { - if (eventData) { - setSafeAreaInset(eventData); - } - } - function onContentSafeAreaChanged(eventType, eventData) { - if (eventData) { - setContentSafeAreaInset(eventData); - } - } - - function onVisibilityChanged(eventType, eventData) { - if (eventData.is_visible) { - webAppIsActive = true; - receiveWebViewEvent('activated'); - } else { - webAppIsActive = false; - receiveWebViewEvent('deactivated'); - } - } - - function linkHandler(e) { - if (e.metaKey || e.ctrlKey) return; - var el = e.target; - while (el.tagName != 'A' && el.parentNode) { - el = el.parentNode; - } - if (el.tagName == 'A' && - el.target != '_blank' && - (el.protocol == 'http:' || el.protocol == 'https:') && - el.hostname == 't.me') { - WebApp.openTelegramLink(el.href); - e.preventDefault(); - } - } - - function strTrim(str) { - return str.toString().replace(/^\s+|\s+$/g, ''); - } - - function receiveWebViewEvent(eventType) { - var args = Array.prototype.slice.call(arguments); - eventType = args.shift(); - WebView.callEventCallbacks('webview:' + eventType, function(callback) { - callback.apply(WebApp, args); - }); - } - - function onWebViewEvent(eventType, callback) { - WebView.onEvent('webview:' + eventType, callback); - }; - - function offWebViewEvent(eventType, callback) { - WebView.offEvent('webview:' + eventType, callback); - }; - - function setCssProperty(name, value) { - var root = document.documentElement; - if (root && root.style && root.style.setProperty) { - root.style.setProperty('--tg-' + name, value); - } - } - - function setFullscreen(is_fullscreen) { - webAppIsFullscreen = !!is_fullscreen; - Utils.sessionStorageSet('isFullscreen', webAppIsFullscreen ? 'yes' : 'no'); - } - - function setOrientationLock(is_locked) { - webAppIsOrientationLocked = !!is_locked; - Utils.sessionStorageSet('isOrientationLocked', webAppIsOrientationLocked ? 'yes' : 'no'); - } - - function setThemeParams(theme_params) { - // temp iOS fix - if (theme_params.bg_color == '#1c1c1d' && - theme_params.bg_color == theme_params.secondary_bg_color) { - theme_params.secondary_bg_color = '#2c2c2e'; - } - var color; - for (var key in theme_params) { - if (color = parseColorToHex(theme_params[key])) { - themeParams[key] = color; - if (key == 'bg_color') { - colorScheme = isColorDark(color) ? 'dark' : 'light' - setCssProperty('color-scheme', colorScheme); - } - key = 'theme-' + key.split('_').join('-'); - setCssProperty(key, color); - } - } - Utils.sessionStorageSet('themeParams', themeParams); - } - - function setDefaultColors(def_colors) { - if (colorScheme == 'dark') { - if (def_colors.bg_dark_color) { - webAppBackgroundColor = def_colors.bg_dark_color; - } - if (def_colors.header_dark_color) { - webAppHeaderColorKey = null; - webAppHeaderColor = def_colors.header_dark_color; - } - } else { - if (def_colors.bg_color) { - webAppBackgroundColor = def_colors.bg_color; - } - if (def_colors.header_color) { - webAppHeaderColorKey = null; - webAppHeaderColor = def_colors.header_color; - } - } - Utils.sessionStorageSet('defaultColors', def_colors); - } - - var webAppCallbacks = {}; - function generateCallbackId(len) { - var tries = 100; - while (--tries) { - var id = '', chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', chars_len = chars.length; - for (var i = 0; i < len; i++) { - id += chars[Math.floor(Math.random() * chars_len)]; - } - if (!webAppCallbacks[id]) { - webAppCallbacks[id] = {}; - return id; - } - } - throw Error('WebAppCallbackIdGenerateFailed'); - } - - var viewportHeight = false, viewportStableHeight = false, isExpanded = true; - function setViewportHeight(data) { - if (typeof data !== 'undefined') { - isExpanded = !!data.is_expanded; - viewportHeight = data.height; - if (data.is_state_stable) { - viewportStableHeight = data.height; - } - receiveWebViewEvent('viewportChanged', { - isStateStable: !!data.is_state_stable - }); - } - var height, stable_height; - if (viewportHeight !== false) { - height = (viewportHeight - bottomBarHeight) + 'px'; - } else { - height = bottomBarHeight ? 'calc(100vh - ' + bottomBarHeight + 'px)' : '100vh'; - } - if (viewportStableHeight !== false) { - stable_height = (viewportStableHeight - bottomBarHeight) + 'px'; - } else { - stable_height = bottomBarHeight ? 'calc(100vh - ' + bottomBarHeight + 'px)' : '100vh'; - } - setCssProperty('viewport-height', height); - setCssProperty('viewport-stable-height', stable_height); - } - - var safeAreaInset = {top: 0, bottom: 0, left: 0, right: 0}; - function setSafeAreaInset(data) { - if (typeof data !== 'undefined') { - if (typeof data.top !== 'undefined') { - safeAreaInset.top = data.top; - } - if (typeof data.bottom !== 'undefined') { - safeAreaInset.bottom = data.bottom; - } - if (typeof data.left !== 'undefined') { - safeAreaInset.left = data.left; - } - if (typeof data.right !== 'undefined') { - safeAreaInset.right = data.right; - } - receiveWebViewEvent('safeAreaChanged'); - } - setCssProperty('safe-area-inset-top', safeAreaInset.top + 'px'); - setCssProperty('safe-area-inset-bottom', safeAreaInset.bottom + 'px'); - setCssProperty('safe-area-inset-left', safeAreaInset.left + 'px'); - setCssProperty('safe-area-inset-right', safeAreaInset.right + 'px'); - } - - var contentSafeAreaInset = {top: 0, bottom: 0, left: 0, right: 0}; - function setContentSafeAreaInset(data) { - if (typeof data !== 'undefined') { - if (typeof data.top !== 'undefined') { - contentSafeAreaInset.top = data.top; - } - if (typeof data.bottom !== 'undefined') { - contentSafeAreaInset.bottom = data.bottom; - } - if (typeof data.left !== 'undefined') { - contentSafeAreaInset.left = data.left; - } - if (typeof data.right !== 'undefined') { - contentSafeAreaInset.right = data.right; - } - receiveWebViewEvent('contentSafeAreaChanged'); - } - setCssProperty('content-safe-area-inset-top', contentSafeAreaInset.top + 'px'); - setCssProperty('content-safe-area-inset-bottom', contentSafeAreaInset.bottom + 'px'); - setCssProperty('content-safe-area-inset-left', contentSafeAreaInset.left + 'px'); - setCssProperty('content-safe-area-inset-right', contentSafeAreaInset.right + 'px'); - } - - var isClosingConfirmationEnabled = false; - function setClosingConfirmation(need_confirmation) { - if (!versionAtLeast('6.2')) { - console.warn('[Telegram.WebApp] Closing confirmation is not supported in version ' + webAppVersion); - return; - } - isClosingConfirmationEnabled = !!need_confirmation; - WebView.postEvent('web_app_setup_closing_behavior', false, {need_confirmation: isClosingConfirmationEnabled}); - } - - var isVerticalSwipesEnabled = true; - function toggleVerticalSwipes(enable_swipes) { - if (!versionAtLeast('7.7')) { - console.warn('[Telegram.WebApp] Changing swipes behavior is not supported in version ' + webAppVersion); - return; - } - isVerticalSwipesEnabled = !!enable_swipes; - WebView.postEvent('web_app_setup_swipe_behavior', false, {allow_vertical_swipe: isVerticalSwipesEnabled}); - } - - function onFullscreenChanged(eventType, eventData) { - setFullscreen(eventData.is_fullscreen); - receiveWebViewEvent('fullscreenChanged'); - } - function onFullscreenFailed(eventType, eventData) { - if (eventData.error == 'ALREADY_FULLSCREEN' && !webAppIsFullscreen) { - setFullscreen(true); - } - receiveWebViewEvent('fullscreenFailed', { - error: eventData.error - }); - } - - function toggleOrientationLock(locked) { - if (!versionAtLeast('8.0')) { - console.warn('[Telegram.WebApp] Orientation locking is not supported in version ' + webAppVersion); - return; - } - setOrientationLock(locked); - WebView.postEvent('web_app_toggle_orientation_lock', false, {locked: webAppIsOrientationLocked}); - } - - var homeScreenCallbacks = []; - function onHomeScreenAdded(eventType, eventData) { - receiveWebViewEvent('homeScreenAdded'); - } - function onHomeScreenChecked(eventType, eventData) { - var status = eventData.status || 'unknown'; - if (homeScreenCallbacks.length > 0) { - for (var i = 0; i < homeScreenCallbacks.length; i++) { - var callback = homeScreenCallbacks[i]; - callback(status); - } - homeScreenCallbacks = []; - } - receiveWebViewEvent('homeScreenChecked', { - status: status - }); - } - - var WebAppShareMessageOpened = false; - function onPreparedMessageSent(eventType, eventData) { - if (WebAppShareMessageOpened) { - var requestData = WebAppShareMessageOpened; - WebAppShareMessageOpened = false; - if (requestData.callback) { - requestData.callback(true); - } - receiveWebViewEvent('shareMessageSent'); - } - } - function onPreparedMessageFailed(eventType, eventData) { - if (WebAppShareMessageOpened) { - var requestData = WebAppShareMessageOpened; - WebAppShareMessageOpened = false; - if (requestData.callback) { - requestData.callback(false); - } - receiveWebViewEvent('shareMessageFailed', { - error: eventData.error - }); - } - } - - var WebAppRequestChatOpened = false; - function onRequestedChatSent(eventType, eventData) { - if (WebAppRequestChatOpened) { - var requestData = WebAppRequestChatOpened; - WebAppRequestChatOpened = false; - if (requestData.callback) { - requestData.callback(true); - } - receiveWebViewEvent('requestedChatSent'); - } - } - function onRequestedChatFailed(eventType, eventData) { - if (WebAppRequestChatOpened) { - var requestData = WebAppRequestChatOpened; - WebAppRequestChatOpened = false; - if (requestData.callback) { - requestData.callback(false); - } - receiveWebViewEvent('requestedChatFailed', { - error: eventData.error - }); - } - } - - var WebAppEmojiStatusRequested = false; - function onEmojiStatusSet(eventType, eventData) { - if (WebAppEmojiStatusRequested) { - var requestData = WebAppEmojiStatusRequested; - WebAppEmojiStatusRequested = false; - if (requestData.callback) { - requestData.callback(true); - } - receiveWebViewEvent('emojiStatusSet'); - } - } - function onEmojiStatusFailed(eventType, eventData) { - if (WebAppEmojiStatusRequested) { - var requestData = WebAppEmojiStatusRequested; - WebAppEmojiStatusRequested = false; - if (requestData.callback) { - requestData.callback(false); - } - receiveWebViewEvent('emojiStatusFailed', { - error: eventData.error - }); - } - } - var WebAppEmojiStatusAccessRequested = false; - function onEmojiStatusAccessRequested(eventType, eventData) { - if (WebAppEmojiStatusAccessRequested) { - var requestData = WebAppEmojiStatusAccessRequested; - WebAppEmojiStatusAccessRequested = false; - if (requestData.callback) { - requestData.callback(eventData.status == 'allowed'); - } - receiveWebViewEvent('emojiStatusAccessRequested', { - status: eventData.status - }); - } - } - - var webAppPopupOpened = false; - function onPopupClosed(eventType, eventData) { - if (webAppPopupOpened) { - var popupData = webAppPopupOpened; - webAppPopupOpened = false; - var button_id = null; - if (typeof eventData.button_id !== 'undefined') { - button_id = eventData.button_id; - } - if (popupData.callback) { - popupData.callback(button_id); - } - receiveWebViewEvent('popupClosed', { - button_id: button_id - }); - } - } - - - function getHeaderColor() { - if (webAppHeaderColorKey == 'secondary_bg_color') { - return themeParams.secondary_bg_color; - } else if (webAppHeaderColorKey == 'bg_color') { - return themeParams.bg_color; - } - return webAppHeaderColor; - } - function setHeaderColor(color) { - if (!versionAtLeast('6.1')) { - console.warn('[Telegram.WebApp] Header color is not supported in version ' + webAppVersion); - return; - } - if (!versionAtLeast('6.9')) { - if (themeParams.bg_color && - themeParams.bg_color == color) { - color = 'bg_color'; - } else if (themeParams.secondary_bg_color && - themeParams.secondary_bg_color == color) { - color = 'secondary_bg_color'; - } - } - var head_color = null, color_key = null; - if (color == 'bg_color' || color == 'secondary_bg_color') { - color_key = color; - } else if (versionAtLeast('6.9')) { - head_color = parseColorToHex(color); - if (!head_color) { - console.error('[Telegram.WebApp] Header color format is invalid', color); - throw Error('WebAppHeaderColorInvalid'); - } - } - if (!versionAtLeast('6.9') && - color_key != 'bg_color' && - color_key != 'secondary_bg_color') { - console.error('[Telegram.WebApp] Header color key should be one of Telegram.WebApp.themeParams.bg_color, Telegram.WebApp.themeParams.secondary_bg_color, \'bg_color\', \'secondary_bg_color\'', color); - throw Error('WebAppHeaderColorKeyInvalid'); - } - webAppHeaderColorKey = color_key; - webAppHeaderColor = head_color; - updateHeaderColor(); - } - var appHeaderColorKey = null, appHeaderColor = null; - function updateHeaderColor() { - if (appHeaderColorKey != webAppHeaderColorKey || - appHeaderColor != webAppHeaderColor) { - appHeaderColorKey = webAppHeaderColorKey; - appHeaderColor = webAppHeaderColor; - if (appHeaderColor) { - WebView.postEvent('web_app_set_header_color', false, {color: webAppHeaderColor}); - } else { - WebView.postEvent('web_app_set_header_color', false, {color_key: webAppHeaderColorKey}); - } - } - } - - function getBackgroundColor() { - if (webAppBackgroundColor == 'secondary_bg_color') { - return themeParams.secondary_bg_color; - } else if (webAppBackgroundColor == 'bg_color') { - return themeParams.bg_color; - } - return webAppBackgroundColor; - } - function setBackgroundColor(color) { - if (!versionAtLeast('6.1')) { - console.warn('[Telegram.WebApp] Background color is not supported in version ' + webAppVersion); - return; - } - var bg_color; - if (color == 'bg_color' || color == 'secondary_bg_color') { - bg_color = color; - } else { - bg_color = parseColorToHex(color); - if (!bg_color) { - console.error('[Telegram.WebApp] Background color format is invalid', color); - throw Error('WebAppBackgroundColorInvalid'); - } - } - webAppBackgroundColor = bg_color; - updateBackgroundColor(); - } - var appBackgroundColor = null; - function updateBackgroundColor() { - var color = getBackgroundColor(); - if (appBackgroundColor != color) { - appBackgroundColor = color; - WebView.postEvent('web_app_set_background_color', false, {color: color}); - } - } - - var bottomBarColor = 'bottom_bar_bg_color'; - function getBottomBarColor() { - if (bottomBarColor == 'bottom_bar_bg_color') { - return themeParams.bottom_bar_bg_color || themeParams.secondary_bg_color || '#ffffff'; - } else if (bottomBarColor == 'secondary_bg_color') { - return themeParams.secondary_bg_color; - } else if (bottomBarColor == 'bg_color') { - return themeParams.bg_color; - } - return bottomBarColor; - } - function setBottomBarColor(color) { - if (!versionAtLeast('7.10')) { - console.warn('[Telegram.WebApp] Bottom bar color is not supported in version ' + webAppVersion); - return; - } - var bg_color; - if (color == 'bg_color' || color == 'secondary_bg_color' || color == 'bottom_bar_bg_color') { - bg_color = color; - } else { - bg_color = parseColorToHex(color); - if (!bg_color) { - console.error('[Telegram.WebApp] Bottom bar color format is invalid', color); - throw Error('WebAppBottomBarColorInvalid'); - } - } - bottomBarColor = bg_color; - updateBottomBarColor(); - window.Telegram.WebApp.SecondaryButton.setParams({}); - } - var appBottomBarColor = null; - function updateBottomBarColor() { - var color = getBottomBarColor(); - if (appBottomBarColor != color) { - appBottomBarColor = color; - WebView.postEvent('web_app_set_bottom_bar_color', false, {color: color}); - } - if (initParams.tgWebAppDebug) { - updateDebugBottomBar(); - } - } - - - function parseColorToHex(color) { - color += ''; - var match; - if (match = /^\s*#([0-9a-f]{6})\s*$/i.exec(color)) { - return '#' + match[1].toLowerCase(); - } - else if (match = /^\s*#([0-9a-f])([0-9a-f])([0-9a-f])\s*$/i.exec(color)) { - return ('#' + match[1] + match[1] + match[2] + match[2] + match[3] + match[3]).toLowerCase(); - } - else if (match = /^\s*rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)\s*$/.exec(color)) { - var r = parseInt(match[1]), g = parseInt(match[2]), b = parseInt(match[3]); - r = (r < 16 ? '0' : '') + r.toString(16); - g = (g < 16 ? '0' : '') + g.toString(16); - b = (b < 16 ? '0' : '') + b.toString(16); - return '#' + r + g + b; - } - return false; - } - - function isColorDark(rgb) { - rgb = rgb.replace(/[\s#]/g, ''); - if (rgb.length == 3) { - rgb = rgb[0] + rgb[0] + rgb[1] + rgb[1] + rgb[2] + rgb[2]; - } - var r = parseInt(rgb.substr(0, 2), 16); - var g = parseInt(rgb.substr(2, 2), 16); - var b = parseInt(rgb.substr(4, 2), 16); - var hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)); - return hsp < 120; - } - - function versionCompare(v1, v2) { - if (typeof v1 !== 'string') v1 = ''; - if (typeof v2 !== 'string') v2 = ''; - v1 = v1.replace(/^\s+|\s+$/g, '').split('.'); - v2 = v2.replace(/^\s+|\s+$/g, '').split('.'); - var a = Math.max(v1.length, v2.length), i, p1, p2; - for (i = 0; i < a; i++) { - p1 = parseInt(v1[i]) || 0; - p2 = parseInt(v2[i]) || 0; - if (p1 == p2) continue; - if (p1 > p2) return 1; - return -1; - } - return 0; - } - - function versionAtLeast(ver) { - return versionCompare(webAppVersion, ver) >= 0; - } - - function byteLength(str) { - if (window.Blob) { - try { return new Blob([str]).size; } catch (e) {} - } - var s = str.length; - for (var i=str.length-1; i>=0; i--) { - var code = str.charCodeAt(i); - if (code > 0x7f && code <= 0x7ff) s++; - else if (code > 0x7ff && code <= 0xffff) s+=2; - if (code >= 0xdc00 && code <= 0xdfff) i--; - } - return s; - } - - var BackButton = (function() { - var isVisible = false; - - var backButton = {}; - Object.defineProperty(backButton, 'isVisible', { - set: function(val){ setParams({is_visible: val}); }, - get: function(){ return isVisible; }, - enumerable: true - }); - - var curButtonState = null; - - WebView.onEvent('back_button_pressed', onBackButtonPressed); - - function onBackButtonPressed() { - receiveWebViewEvent('backButtonClicked'); - } - - function buttonParams() { - return {is_visible: isVisible}; - } - - function buttonState(btn_params) { - if (typeof btn_params === 'undefined') { - btn_params = buttonParams(); - } - return JSON.stringify(btn_params); - } - - function buttonCheckVersion() { - if (!versionAtLeast('6.1')) { - console.warn('[Telegram.WebApp] BackButton is not supported in version ' + webAppVersion); - return false; - } - return true; - } - - function updateButton() { - var btn_params = buttonParams(); - var btn_state = buttonState(btn_params); - if (curButtonState === btn_state) { - return; - } - curButtonState = btn_state; - WebView.postEvent('web_app_setup_back_button', false, btn_params); - } - - function setParams(params) { - if (!buttonCheckVersion()) { - return backButton; - } - if (typeof params.is_visible !== 'undefined') { - isVisible = !!params.is_visible; - } - updateButton(); - return backButton; - } - - backButton.onClick = function(callback) { - if (buttonCheckVersion()) { - onWebViewEvent('backButtonClicked', callback); - } - return backButton; - }; - backButton.offClick = function(callback) { - if (buttonCheckVersion()) { - offWebViewEvent('backButtonClicked', callback); - } - return backButton; - }; - backButton.show = function() { - return setParams({is_visible: true}); - }; - backButton.hide = function() { - return setParams({is_visible: false}); - }; - return backButton; - })(); - - var debugBottomBar = null, debugBottomBarBtns = {}, bottomBarHeight = 0; - if (initParams.tgWebAppDebug) { - debugBottomBar = document.createElement('tg-bottom-bar'); - var debugBottomBarStyle = { - display: 'flex', - gap: '7px', - font: '600 14px/18px sans-serif', - width: '100%', - background: getBottomBarColor(), - position: 'fixed', - left: '0', - right: '0', - bottom: '0', - margin: '0', - padding: '7px', - textAlign: 'center', - boxSizing: 'border-box', - zIndex: '10000' - }; - for (var k in debugBottomBarStyle) { - debugBottomBar.style[k] = debugBottomBarStyle[k]; - } - document.addEventListener('DOMContentLoaded', function onDomLoaded(event) { - document.removeEventListener('DOMContentLoaded', onDomLoaded); - document.body.appendChild(debugBottomBar); - }); - var animStyle = document.createElement('style'); - animStyle.innerHTML = 'tg-bottom-button.shine { position: relative; overflow: hidden; } tg-bottom-button.shine:before { content:""; position: absolute; top: 0; width: 100%; height: 100%; background: linear-gradient(120deg, transparent, rgba(255, 255, 255, .2), transparent); animation: tg-bottom-button-shine 5s ease-in-out infinite; } @-webkit-keyframes tg-bottom-button-shine { 0% {left: -100%;} 12%,100% {left: 100%}} @keyframes tg-bottom-button-shine { 0% {left: -100%;} 12%,100% {left: 100%}}'; - debugBottomBar.appendChild(animStyle); - } - function updateDebugBottomBar() { - var mainBtn = debugBottomBarBtns.main._bottomButton; - var secondaryBtn = debugBottomBarBtns.secondary._bottomButton; - if (mainBtn.isVisible || secondaryBtn.isVisible) { - debugBottomBar.style.display = 'flex'; - bottomBarHeight = 58; - if (mainBtn.isVisible && secondaryBtn.isVisible) { - if (secondaryBtn.position == 'top') { - debugBottomBar.style.flexDirection = 'column-reverse'; - bottomBarHeight += 51; - } else if (secondaryBtn.position == 'bottom') { - debugBottomBar.style.flexDirection = 'column'; - bottomBarHeight += 51; - } else if (secondaryBtn.position == 'left') { - debugBottomBar.style.flexDirection = 'row-reverse'; - } else if (secondaryBtn.position == 'right') { - debugBottomBar.style.flexDirection = 'row'; - } - } - } else { - debugBottomBar.style.display = 'none'; - bottomBarHeight = 0; - } - debugBottomBar.style.background = getBottomBarColor(); - if (document.documentElement) { - document.documentElement.style.boxSizing = 'border-box'; - document.documentElement.style.paddingBottom = bottomBarHeight + 'px'; - } - setViewportHeight(); - } - - - var BottomButtonConstructor = function(type) { - var isMainButton = (type == 'main'); - if (isMainButton) { - var setupFnName = 'web_app_setup_main_button'; - var tgEventName = 'main_button_pressed'; - var webViewEventName = 'mainButtonClicked'; - var buttonTextDefault = 'Continue'; - var buttonColorDefault = function(){ return themeParams.button_color || '#2481cc'; }; - var buttonTextColorDefault = function(){ return themeParams.button_text_color || '#ffffff'; }; - } else { - var setupFnName = 'web_app_setup_secondary_button'; - var tgEventName = 'secondary_button_pressed'; - var webViewEventName = 'secondaryButtonClicked'; - var buttonTextDefault = 'Cancel'; - var buttonColorDefault = function(){ return getBottomBarColor(); }; - var buttonTextColorDefault = function(){ return themeParams.button_color || '#2481cc'; }; - } - - var isVisible = false; - var isActive = true; - var hasShineEffect = false; - var isProgressVisible = false; - var iconCustomEmojiId = false; - var buttonType = type; - var buttonText = buttonTextDefault; - var buttonColor = false; - var buttonTextColor = false; - var buttonPosition = 'left'; - - var bottomButton = {}; - Object.defineProperty(bottomButton, 'type', { - get: function(){ return buttonType; }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'iconCustomEmojiId', { - set: function(val){ bottomButton.setParams({icon_custom_emoji_id: val}); }, - get: function(){ return iconCustomEmojiId; }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'text', { - set: function(val){ bottomButton.setParams({text: val}); }, - get: function(){ return buttonText; }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'color', { - set: function(val){ bottomButton.setParams({color: val}); }, - get: function(){ return buttonColor || buttonColorDefault(); }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'textColor', { - set: function(val){ bottomButton.setParams({text_color: val}); }, - get: function(){ return buttonTextColor || buttonTextColorDefault(); }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'isVisible', { - set: function(val){ bottomButton.setParams({is_visible: val}); }, - get: function(){ return isVisible; }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'isProgressVisible', { - get: function(){ return isProgressVisible; }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'isActive', { - set: function(val){ bottomButton.setParams({is_active: val}); }, - get: function(){ return isActive; }, - enumerable: true - }); - Object.defineProperty(bottomButton, 'hasShineEffect', { - set: function(val){ bottomButton.setParams({has_shine_effect: val}); }, - get: function(){ return hasShineEffect; }, - enumerable: true - }); - if (!isMainButton) { - Object.defineProperty(bottomButton, 'position', { - set: function(val){ bottomButton.setParams({position: val}); }, - get: function(){ return buttonPosition; }, - enumerable: true - }); - } - - var curButtonState = null; - - WebView.onEvent(tgEventName, onBottomButtonPressed); - - var debugBtn = null; - if (initParams.tgWebAppDebug) { - debugBtn = document.createElement('tg-bottom-button'); - var debugBtnStyle = { - display: 'none', - width: '100%', - height: '44px', - borderRadius: '0', - background: 'no-repeat right center', - padding: '13px 15px', - textAlign: 'center', - boxSizing: 'border-box' - }; - for (var k in debugBtnStyle) { - debugBtn.style[k] = debugBtnStyle[k]; - } - debugBottomBar.appendChild(debugBtn); - debugBtn.addEventListener('click', onBottomButtonPressed, false); - debugBtn._bottomButton = bottomButton; - debugBottomBarBtns[type] = debugBtn; - } - - function onBottomButtonPressed() { - if (isActive) { - receiveWebViewEvent(webViewEventName); - } - } - - function buttonParams() { - var color = bottomButton.color; - var text_color = bottomButton.textColor; - if (isVisible) { - var params = { - is_visible: true, - is_active: isActive, - is_progress_visible: isProgressVisible, - icon_custom_emoji_id: iconCustomEmojiId, - text: buttonText, - color: color, - text_color: text_color, - has_shine_effect: hasShineEffect && isActive && !isProgressVisible - }; - if (!isMainButton) { - params.position = buttonPosition; - } - } else { - var params = { - is_visible: false - }; - } - return params; - } - - function buttonState(btn_params) { - if (typeof btn_params === 'undefined') { - btn_params = buttonParams(); - } - return JSON.stringify(btn_params); - } - - function updateButton() { - var btn_params = buttonParams(); - var btn_state = buttonState(btn_params); - if (curButtonState === btn_state) { - return; - } - curButtonState = btn_state; - WebView.postEvent(setupFnName, false, btn_params); - if (initParams.tgWebAppDebug) { - updateDebugButton(btn_params); - } - } - - function updateDebugButton(btn_params) { - if (btn_params.is_visible) { - debugBtn.style.display = 'block'; - - debugBtn.style.opacity = btn_params.is_active ? '1' : '0.8'; - debugBtn.style.cursor = btn_params.is_active ? 'pointer' : 'auto'; - debugBtn.disabled = !btn_params.is_active; - debugBtn.innerText = btn_params.text; - debugBtn.className = btn_params.has_shine_effect ? 'shine' : ''; - debugBtn.style.backgroundImage = btn_params.is_progress_visible ? "url('data:image/svg+xml," + encodeURIComponent('') + "')" : 'none'; - debugBtn.style.backgroundColor = btn_params.color; - debugBtn.style.color = btn_params.text_color; - } else { - debugBtn.style.display = 'none'; - } - updateDebugBottomBar(); - } - - function setParams(params) { - if (typeof params.icon_custom_emoji_id !== 'undefined') { - var emoji_id = params.icon_custom_emoji_id; - if (emoji_id === false || emoji_id === null) { - emoji_id = ''; - } - if (emoji_id !== '' && !/^[0-9]{10,20}$/.test(emoji_id)) { - console.error('[Telegram.WebApp] Bottom button icon custom emoji is invalid', params.icon_custom_emoji_id); - throw Error('WebAppBottomButtonParamInvalid'); - } - iconCustomEmojiId = emoji_id; - } - if (typeof params.text !== 'undefined') { - var text = strTrim(params.text); - if (!text.length && !iconCustomEmojiId) { - console.error('[Telegram.WebApp] Bottom button text is required', params.text); - throw Error('WebAppBottomButtonParamInvalid'); - } - if (text.length > 64) { - console.error('[Telegram.WebApp] Bottom button text is too long', text); - throw Error('WebAppBottomButtonParamInvalid'); - } - buttonText = text; - } - if (typeof params.color !== 'undefined') { - if (params.color === false || - params.color === null) { - buttonColor = false; - } else { - var color = parseColorToHex(params.color); - if (!color) { - console.error('[Telegram.WebApp] Bottom button color format is invalid', params.color); - throw Error('WebAppBottomButtonParamInvalid'); - } - buttonColor = color; - } - } - if (typeof params.text_color !== 'undefined') { - if (params.text_color === false || - params.text_color === null) { - buttonTextColor = false; - } else { - var text_color = parseColorToHex(params.text_color); - if (!text_color) { - console.error('[Telegram.WebApp] Bottom button text color format is invalid', params.text_color); - throw Error('WebAppBottomButtonParamInvalid'); - } - buttonTextColor = text_color; - } - } - if (typeof params.is_visible !== 'undefined') { - if (params.is_visible && - !bottomButton.text.length) { - console.error('[Telegram.WebApp] Bottom button text is required'); - throw Error('WebAppBottomButtonParamInvalid'); - } - isVisible = !!params.is_visible; - } - if (typeof params.has_shine_effect !== 'undefined') { - hasShineEffect = !!params.has_shine_effect; - } - if (!isMainButton && typeof params.position !== 'undefined') { - if (params.position != 'left' && params.position != 'right' && - params.position != 'top' && params.position != 'bottom') { - console.error('[Telegram.WebApp] Bottom button posiition is invalid', params.position); - throw Error('WebAppBottomButtonParamInvalid'); - } - buttonPosition = params.position; - } - if (typeof params.is_active !== 'undefined') { - isActive = !!params.is_active; - } - updateButton(); - return bottomButton; - } - - bottomButton.setText = function(text) { - return bottomButton.setParams({text: text}); - }; - bottomButton.onClick = function(callback) { - onWebViewEvent(webViewEventName, callback); - return bottomButton; - }; - bottomButton.offClick = function(callback) { - offWebViewEvent(webViewEventName, callback); - return bottomButton; - }; - bottomButton.show = function() { - return bottomButton.setParams({is_visible: true}); - }; - bottomButton.hide = function() { - return bottomButton.setParams({is_visible: false}); - }; - bottomButton.enable = function() { - return bottomButton.setParams({is_active: true}); - }; - bottomButton.disable = function() { - return bottomButton.setParams({is_active: false}); - }; - bottomButton.showProgress = function(leaveActive) { - isActive = !!leaveActive; - isProgressVisible = true; - updateButton(); - return bottomButton; - }; - bottomButton.hideProgress = function() { - if (!bottomButton.isActive) { - isActive = true; - } - isProgressVisible = false; - updateButton(); - return bottomButton; - } - bottomButton.setParams = setParams; - return bottomButton; - }; - var MainButton = BottomButtonConstructor('main'); - var SecondaryButton = BottomButtonConstructor('secondary'); - - var SettingsButton = (function() { - var isVisible = false; - - var settingsButton = {}; - Object.defineProperty(settingsButton, 'isVisible', { - set: function(val){ setParams({is_visible: val}); }, - get: function(){ return isVisible; }, - enumerable: true - }); - - var curButtonState = null; - - WebView.onEvent('settings_button_pressed', onSettingsButtonPressed); - - function onSettingsButtonPressed() { - receiveWebViewEvent('settingsButtonClicked'); - } - - function buttonParams() { - return {is_visible: isVisible}; - } - - function buttonState(btn_params) { - if (typeof btn_params === 'undefined') { - btn_params = buttonParams(); - } - return JSON.stringify(btn_params); - } - - function buttonCheckVersion() { - if (!versionAtLeast('6.10')) { - console.warn('[Telegram.WebApp] SettingsButton is not supported in version ' + webAppVersion); - return false; - } - return true; - } - - function updateButton() { - var btn_params = buttonParams(); - var btn_state = buttonState(btn_params); - if (curButtonState === btn_state) { - return; - } - curButtonState = btn_state; - WebView.postEvent('web_app_setup_settings_button', false, btn_params); - } - - function setParams(params) { - if (!buttonCheckVersion()) { - return settingsButton; - } - if (typeof params.is_visible !== 'undefined') { - isVisible = !!params.is_visible; - } - updateButton(); - return settingsButton; - } - - settingsButton.onClick = function(callback) { - if (buttonCheckVersion()) { - onWebViewEvent('settingsButtonClicked', callback); - } - return settingsButton; - }; - settingsButton.offClick = function(callback) { - if (buttonCheckVersion()) { - offWebViewEvent('settingsButtonClicked', callback); - } - return settingsButton; - }; - settingsButton.show = function() { - return setParams({is_visible: true}); - }; - settingsButton.hide = function() { - return setParams({is_visible: false}); - }; - return settingsButton; - })(); - - var HapticFeedback = (function() { - var hapticFeedback = {}; - - function triggerFeedback(params) { - if (!versionAtLeast('6.1')) { - console.warn('[Telegram.WebApp] HapticFeedback is not supported in version ' + webAppVersion); - return hapticFeedback; - } - if (params.type == 'impact') { - if (params.impact_style != 'light' && - params.impact_style != 'medium' && - params.impact_style != 'heavy' && - params.impact_style != 'rigid' && - params.impact_style != 'soft') { - console.error('[Telegram.WebApp] Haptic impact style is invalid', params.impact_style); - throw Error('WebAppHapticImpactStyleInvalid'); - } - } else if (params.type == 'notification') { - if (params.notification_type != 'error' && - params.notification_type != 'success' && - params.notification_type != 'warning') { - console.error('[Telegram.WebApp] Haptic notification type is invalid', params.notification_type); - throw Error('WebAppHapticNotificationTypeInvalid'); - } - } else if (params.type == 'selection_change') { - // no params needed - } else { - console.error('[Telegram.WebApp] Haptic feedback type is invalid', params.type); - throw Error('WebAppHapticFeedbackTypeInvalid'); - } - WebView.postEvent('web_app_trigger_haptic_feedback', false, params); - return hapticFeedback; - } - - hapticFeedback.impactOccurred = function(style) { - return triggerFeedback({type: 'impact', impact_style: style}); - }; - hapticFeedback.notificationOccurred = function(type) { - return triggerFeedback({type: 'notification', notification_type: type}); - }; - hapticFeedback.selectionChanged = function() { - return triggerFeedback({type: 'selection_change'}); - }; - return hapticFeedback; - })(); - - var CloudStorage = (function() { - var cloudStorage = {}; - - function invokeStorageMethod(method, params, callback) { - if (!versionAtLeast('6.9')) { - console.error('[Telegram.WebApp] CloudStorage is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - invokeCustomMethod(method, params, callback); - return cloudStorage; - } - - cloudStorage.setItem = function(key, value, callback) { - return invokeStorageMethod('saveStorageValue', {key: key, value: value}, callback); - }; - cloudStorage.getItem = function(key, callback) { - return cloudStorage.getItems([key], callback ? function(err, res) { - if (err) callback(err); - else callback(null, res[key]); - } : null); - }; - cloudStorage.getItems = function(keys, callback) { - return invokeStorageMethod('getStorageValues', {keys: keys}, callback); - }; - cloudStorage.removeItem = function(key, callback) { - return cloudStorage.removeItems([key], callback); - }; - cloudStorage.removeItems = function(keys, callback) { - return invokeStorageMethod('deleteStorageValues', {keys: keys}, callback); - }; - cloudStorage.getKeys = function(callback) { - return invokeStorageMethod('getStorageKeys', {}, callback); - }; - return cloudStorage; - })(); - - var DeviceStorage = (function() { - var deviceStorage = {}; - - WebView.onEvent('device_storage_key_saved', onDeviceStorageEvent); - WebView.onEvent('device_storage_key_received', onDeviceStorageEvent); - WebView.onEvent('device_storage_cleared', onDeviceStorageEvent); - WebView.onEvent('device_storage_failed', onDeviceStorageEvent); - - function onDeviceStorageEvent(eventType, eventData) { - if (eventData.req_id && webAppCallbacks[eventData.req_id]) { - var requestData = webAppCallbacks[eventData.req_id]; - delete webAppCallbacks[eventData.req_id]; - var res = null, err = null; - if (eventType == 'device_storage_failed') { - err = eventData.error || 'UNKNOWN_ERROR'; - } else if (eventType == 'device_storage_key_received') { - res = eventData.value; - } else { - res = true; - } - if (requestData.callback) { - requestData.callback(err, res); - } - } - } - - function invokeStorageMethod(method, params, callback) { - if (!versionAtLeast('9.0')) { - console.error('[Telegram.WebApp] DeviceStorage is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - var req_id = generateCallbackId(16); - var req_params = {req_id: req_id}; - for (var k in params) { - req_params[k] = params[k]; - } - webAppCallbacks[req_id] = { - callback: callback - }; - WebView.postEvent(method, false, req_params); - return deviceStorage; - } - - deviceStorage.setItem = function(key, value, callback) { - return invokeStorageMethod('web_app_device_storage_save_key', {key: key, value: value}, callback); - }; - deviceStorage.getItem = function(key, callback) { - return invokeStorageMethod('web_app_device_storage_get_key', {key: key}, callback); - }; - deviceStorage.removeItem = function(key, callback) { - return invokeStorageMethod('web_app_device_storage_save_key', {key: key, value: null}, callback); - }; - deviceStorage.clear = function(callback) { - return invokeStorageMethod('web_app_device_storage_clear', {}, callback); - }; - return deviceStorage; - })(); - - var SecureStorage = (function() { - var secureStorage = {}; - - WebView.onEvent('secure_storage_key_saved', onSecureStorageEvent); - WebView.onEvent('secure_storage_key_received', onSecureStorageEvent); - WebView.onEvent('secure_storage_key_restored', onSecureStorageEvent); - WebView.onEvent('secure_storage_cleared', onSecureStorageEvent); - WebView.onEvent('secure_storage_failed', onSecureStorageEvent); - - function onSecureStorageEvent(eventType, eventData) { - if (eventData.req_id && webAppCallbacks[eventData.req_id]) { - var requestData = webAppCallbacks[eventData.req_id]; - delete webAppCallbacks[eventData.req_id]; - var res = null, err = null, can_restore = null; - if (eventType == 'secure_storage_failed') { - err = eventData.error || 'UNKNOWN_ERROR'; - } else if (eventType == 'secure_storage_key_received') { - res = eventData.value; - if (eventData.can_restore) { - can_restore = true; - } - } else if (eventType == 'secure_storage_key_restored') { - res = eventData.value; - } else { - res = true; - } - if (requestData.callback) { - requestData.callback(err, res, can_restore); - } - } - } - - function invokeStorageMethod(method, params, callback) { - if (!versionAtLeast('9.0')) { - console.error('[Telegram.WebApp] SecureStorage is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - var req_id = generateCallbackId(16); - var req_params = {req_id: req_id}; - for (var k in params) { - req_params[k] = params[k]; - } - webAppCallbacks[req_id] = { - callback: callback - }; - WebView.postEvent(method, false, req_params); - return secureStorage; - } - - secureStorage.setItem = function(key, value, callback) { - return invokeStorageMethod('web_app_secure_storage_save_key', {key: key, value: value}, callback); - }; - secureStorage.getItem = function(key, callback) { - return invokeStorageMethod('web_app_secure_storage_get_key', {key: key}, callback); - }; - secureStorage.restoreItem = function(key, callback) { - return invokeStorageMethod('web_app_secure_storage_restore_key', {key: key}, callback); - }; - secureStorage.removeItem = function(key, callback) { - return invokeStorageMethod('web_app_secure_storage_save_key', {key: key, value: null}, callback); - }; - secureStorage.clear = function(callback) { - return invokeStorageMethod('web_app_secure_storage_clear', {}, callback); - }; - return secureStorage; - })(); - - var BiometricManager = (function() { - var isInited = false; - var isBiometricAvailable = false; - var biometricType = 'unknown'; - var isAccessRequested = false; - var isAccessGranted = false; - var isBiometricTokenSaved = false; - var deviceId = ''; - - var biometricManager = {}; - Object.defineProperty(biometricManager, 'isInited', { - get: function(){ return isInited; }, - enumerable: true - }); - Object.defineProperty(biometricManager, 'isBiometricAvailable', { - get: function(){ return isInited && isBiometricAvailable; }, - enumerable: true - }); - Object.defineProperty(biometricManager, 'biometricType', { - get: function(){ return biometricType || 'unknown'; }, - enumerable: true - }); - Object.defineProperty(biometricManager, 'isAccessRequested', { - get: function(){ return isAccessRequested; }, - enumerable: true - }); - Object.defineProperty(biometricManager, 'isAccessGranted', { - get: function(){ return isAccessRequested && isAccessGranted; }, - enumerable: true - }); - Object.defineProperty(biometricManager, 'isBiometricTokenSaved', { - get: function(){ return isBiometricTokenSaved; }, - enumerable: true - }); - Object.defineProperty(biometricManager, 'deviceId', { - get: function(){ return deviceId || ''; }, - enumerable: true - }); - - var initRequestState = {callbacks: []}; - var accessRequestState = false; - var authRequestState = false; - var tokenRequestState = false; - - WebView.onEvent('biometry_info_received', onBiometryInfoReceived); - WebView.onEvent('biometry_auth_requested', onBiometryAuthRequested); - WebView.onEvent('biometry_token_updated', onBiometryTokenUpdated); - - function onBiometryInfoReceived(eventType, eventData) { - isInited = true; - if (eventData.available) { - isBiometricAvailable = true; - biometricType = eventData.type || 'unknown'; - if (eventData.access_requested) { - isAccessRequested = true; - isAccessGranted = !!eventData.access_granted; - isBiometricTokenSaved = !!eventData.token_saved; - } else { - isAccessRequested = false; - isAccessGranted = false; - isBiometricTokenSaved = false; - } - } else { - isBiometricAvailable = false; - biometricType = 'unknown'; - isAccessRequested = false; - isAccessGranted = false; - isBiometricTokenSaved = false; - } - deviceId = eventData.device_id || ''; - - if (initRequestState.callbacks.length > 0) { - for (var i = 0; i < initRequestState.callbacks.length; i++) { - var callback = initRequestState.callbacks[i]; - callback(); - } - initRequestState.callbacks = []; - } - if (accessRequestState) { - var state = accessRequestState; - accessRequestState = false; - if (state.callback) { - state.callback(isAccessGranted); - } - } - receiveWebViewEvent('biometricManagerUpdated'); - } - function onBiometryAuthRequested(eventType, eventData) { - var isAuthenticated = (eventData.status == 'authorized'), - biometricToken = eventData.token || ''; - if (authRequestState) { - var state = authRequestState; - authRequestState = false; - if (state.callback) { - state.callback(isAuthenticated, isAuthenticated ? biometricToken : null); - } - } - receiveWebViewEvent('biometricAuthRequested', isAuthenticated ? { - isAuthenticated: true, - biometricToken: biometricToken - } : { - isAuthenticated: false - }); - } - function onBiometryTokenUpdated(eventType, eventData) { - var applied = false; - if (isBiometricAvailable && - isAccessRequested) { - if (eventData.status == 'updated') { - isBiometricTokenSaved = true; - applied = true; - } - else if (eventData.status == 'removed') { - isBiometricTokenSaved = false; - applied = true; - } - } - if (tokenRequestState) { - var state = tokenRequestState; - tokenRequestState = false; - if (state.callback) { - state.callback(applied); - } - } - receiveWebViewEvent('biometricTokenUpdated', { - isUpdated: applied - }); - } - - function checkVersion() { - if (!versionAtLeast('7.2')) { - console.warn('[Telegram.WebApp] BiometricManager is not supported in version ' + webAppVersion); - return false; - } - return true; - } - - function checkInit() { - if (!isInited) { - console.error('[Telegram.WebApp] BiometricManager should be inited before using.'); - throw Error('WebAppBiometricManagerNotInited'); - } - return true; - } - - biometricManager.init = function(callback) { - if (!checkVersion()) { - return biometricManager; - } - if (isInited) { - return biometricManager; - } - if (callback) { - initRequestState.callbacks.push(callback); - } - WebView.postEvent('web_app_biometry_get_info', false); - return biometricManager; - }; - biometricManager.requestAccess = function(params, callback) { - if (!checkVersion()) { - return biometricManager; - } - checkInit(); - if (!isBiometricAvailable) { - console.error('[Telegram.WebApp] Biometrics is not available on this device.'); - throw Error('WebAppBiometricManagerBiometricsNotAvailable'); - } - if (accessRequestState) { - console.error('[Telegram.WebApp] Access is already requested'); - throw Error('WebAppBiometricManagerAccessRequested'); - } - var popup_params = {}; - if (typeof params.reason !== 'undefined') { - var reason = strTrim(params.reason); - if (reason.length > 128) { - console.error('[Telegram.WebApp] Biometric reason is too long', reason); - throw Error('WebAppBiometricRequestAccessParamInvalid'); - } - if (reason.length > 0) { - popup_params.reason = reason; - } - } - - accessRequestState = { - callback: callback - }; - WebView.postEvent('web_app_biometry_request_access', false, popup_params); - return biometricManager; - }; - biometricManager.authenticate = function(params, callback) { - if (!checkVersion()) { - return biometricManager; - } - checkInit(); - if (!isBiometricAvailable) { - console.error('[Telegram.WebApp] Biometrics is not available on this device.'); - throw Error('WebAppBiometricManagerBiometricsNotAvailable'); - } - if (!isAccessGranted) { - console.error('[Telegram.WebApp] Biometric access was not granted by the user.'); - throw Error('WebAppBiometricManagerBiometricAccessNotGranted'); - } - if (authRequestState) { - console.error('[Telegram.WebApp] Authentication request is already in progress.'); - throw Error('WebAppBiometricManagerAuthenticationRequested'); - } - var popup_params = {}; - if (typeof params.reason !== 'undefined') { - var reason = strTrim(params.reason); - if (reason.length > 128) { - console.error('[Telegram.WebApp] Biometric reason is too long', reason); - throw Error('WebAppBiometricRequestAccessParamInvalid'); - } - if (reason.length > 0) { - popup_params.reason = reason; - } - } - - authRequestState = { - callback: callback - }; - WebView.postEvent('web_app_biometry_request_auth', false, popup_params); - return biometricManager; - }; - biometricManager.updateBiometricToken = function(token, callback) { - if (!checkVersion()) { - return biometricManager; - } - token = token || ''; - if (token.length > 1024) { - console.error('[Telegram.WebApp] Token is too long', token); - throw Error('WebAppBiometricManagerTokenInvalid'); - } - checkInit(); - if (!isBiometricAvailable) { - console.error('[Telegram.WebApp] Biometrics is not available on this device.'); - throw Error('WebAppBiometricManagerBiometricsNotAvailable'); - } - if (!isAccessGranted) { - console.error('[Telegram.WebApp] Biometric access was not granted by the user.'); - throw Error('WebAppBiometricManagerBiometricAccessNotGranted'); - } - if (tokenRequestState) { - console.error('[Telegram.WebApp] Token request is already in progress.'); - throw Error('WebAppBiometricManagerTokenUpdateRequested'); - } - tokenRequestState = { - callback: callback - }; - WebView.postEvent('web_app_biometry_update_token', false, {token: token}); - return biometricManager; - }; - biometricManager.openSettings = function() { - if (!checkVersion()) { - return biometricManager; - } - checkInit(); - if (!isBiometricAvailable) { - console.error('[Telegram.WebApp] Biometrics is not available on this device.'); - throw Error('WebAppBiometricManagerBiometricsNotAvailable'); - } - if (!isAccessRequested) { - console.error('[Telegram.WebApp] Biometric access was not requested yet.'); - throw Error('WebAppBiometricManagerBiometricsAccessNotRequested'); - } - if (isAccessGranted) { - console.warn('[Telegram.WebApp] Biometric access was granted by the user, no need to go to settings.'); - return biometricManager; - } - WebView.postEvent('web_app_biometry_open_settings', false); - return biometricManager; - }; - return biometricManager; - })(); - - var LocationManager = (function() { - var isInited = false; - var isLocationAvailable = false; - var isAccessRequested = false; - var isAccessGranted = false; - - var locationManager = {}; - Object.defineProperty(locationManager, 'isInited', { - get: function(){ return isInited; }, - enumerable: true - }); - Object.defineProperty(locationManager, 'isLocationAvailable', { - get: function(){ return isInited && isLocationAvailable; }, - enumerable: true - }); - Object.defineProperty(locationManager, 'isAccessRequested', { - get: function(){ return isAccessRequested; }, - enumerable: true - }); - Object.defineProperty(locationManager, 'isAccessGranted', { - get: function(){ return isAccessRequested && isAccessGranted; }, - enumerable: true - }); - - var initRequestState = {callbacks: []}; - var getRequestState = {callbacks: []}; - - WebView.onEvent('location_checked', onLocationChecked); - WebView.onEvent('location_requested', onLocationRequested); - - function onLocationChecked(eventType, eventData) { - isInited = true; - if (eventData.available) { - isLocationAvailable = true; - if (eventData.access_requested) { - isAccessRequested = true; - isAccessGranted = !!eventData.access_granted; - } else { - isAccessRequested = false; - isAccessGranted = false; - } - } else { - isLocationAvailable = false; - isAccessRequested = false; - isAccessGranted = false; - } - - if (initRequestState.callbacks.length > 0) { - for (var i = 0; i < initRequestState.callbacks.length; i++) { - var callback = initRequestState.callbacks[i]; - callback(); - } - initRequestState.callbacks = []; - } - receiveWebViewEvent('locationManagerUpdated'); - } - function onLocationRequested(eventType, eventData) { - if (!eventData.available) { - locationData = null; - } else { - var locationData = { - latitude: eventData.latitude, - longitude: eventData.longitude, - altitude: null, - course: null, - speed: null, - horizontal_accuracy: null, - vertical_accuracy: null, - course_accuracy: null, - speed_accuracy: null, - }; - if (typeof eventData.altitude !== 'undefined' && eventData.altitude !== null) { - locationData.altitude = eventData.altitude; - } - if (typeof eventData.course !== 'undefined' && eventData.course !== null) { - locationData.course = eventData.course % 360; - } - if (typeof eventData.speed !== 'undefined' && eventData.speed !== null) { - locationData.speed = eventData.speed; - } - if (typeof eventData.horizontal_accuracy !== 'undefined' && eventData.horizontal_accuracy !== null) { - locationData.horizontal_accuracy = eventData.horizontal_accuracy; - } - if (typeof eventData.vertical_accuracy !== 'undefined' && eventData.vertical_accuracy !== null) { - locationData.vertical_accuracy = eventData.vertical_accuracy; - } - if (typeof eventData.course_accuracy !== 'undefined' && eventData.course_accuracy !== null) { - locationData.course_accuracy = eventData.course_accuracy; - } - if (typeof eventData.speed_accuracy !== 'undefined' && eventData.speed_accuracy !== null) { - locationData.speed_accuracy = eventData.speed_accuracy; - } - } - if (!eventData.available || - !isLocationAvailable || - !isAccessRequested || - !isAccessGranted) { - initRequestState.callbacks.push(function() { - locationResponse(locationData); - }); - WebView.postEvent('web_app_check_location', false); - } else { - locationResponse(locationData); - } - } - function locationResponse(response) { - if (getRequestState.callbacks.length > 0) { - for (var i = 0; i < getRequestState.callbacks.length; i++) { - var callback = getRequestState.callbacks[i]; - callback(response); - } - getRequestState.callbacks = []; - } - if (response !== null) { - receiveWebViewEvent('locationRequested', { - locationData: response - }); - } - } - - function checkVersion() { - if (!versionAtLeast('8.0')) { - console.warn('[Telegram.WebApp] LocationManager is not supported in version ' + webAppVersion); - return false; - } - return true; - } - - function checkInit() { - if (!isInited) { - console.error('[Telegram.WebApp] LocationManager should be inited before using.'); - throw Error('WebAppLocationManagerNotInited'); - } - return true; - } - - locationManager.init = function(callback) { - if (!checkVersion()) { - return locationManager; - } - if (isInited) { - return locationManager; - } - if (callback) { - initRequestState.callbacks.push(callback); - } - WebView.postEvent('web_app_check_location', false); - return locationManager; - }; - locationManager.getLocation = function(callback) { - if (!checkVersion()) { - return locationManager; - } - checkInit(); - if (!isLocationAvailable) { - console.error('[Telegram.WebApp] Location is not available on this device.'); - throw Error('WebAppLocationManagerLocationNotAvailable'); - } - - getRequestState.callbacks.push(callback); - WebView.postEvent('web_app_request_location'); - return locationManager; - }; - locationManager.openSettings = function() { - if (!checkVersion()) { - return locationManager; - } - checkInit(); - if (!isLocationAvailable) { - console.error('[Telegram.WebApp] Location is not available on this device.'); - throw Error('WebAppLocationManagerLocationNotAvailable'); - } - if (!isAccessRequested) { - console.error('[Telegram.WebApp] Location access was not requested yet.'); - throw Error('WebAppLocationManagerLocationAccessNotRequested'); - } - if (isAccessGranted) { - console.warn('[Telegram.WebApp] Location access was granted by the user, no need to go to settings.'); - return locationManager; - } - WebView.postEvent('web_app_open_location_settings', false); - return locationManager; - }; - return locationManager; - })(); - - var Accelerometer = (function() { - var isStarted = false; - var valueX = null, valueY = null, valueZ = null; - var startCallbacks = [], stopCallbacks = []; - - var accelerometer = {}; - Object.defineProperty(accelerometer, 'isStarted', { - get: function(){ return isStarted; }, - enumerable: true - }); - Object.defineProperty(accelerometer, 'x', { - get: function(){ return valueX; }, - enumerable: true - }); - Object.defineProperty(accelerometer, 'y', { - get: function(){ return valueY; }, - enumerable: true - }); - Object.defineProperty(accelerometer, 'z', { - get: function(){ return valueZ; }, - enumerable: true - }); - - WebView.onEvent('accelerometer_started', onAccelerometerStarted); - WebView.onEvent('accelerometer_stopped', onAccelerometerStopped); - WebView.onEvent('accelerometer_changed', onAccelerometerChanged); - WebView.onEvent('accelerometer_failed', onAccelerometerFailed); - - function onAccelerometerStarted(eventType, eventData) { - isStarted = true; - if (startCallbacks.length > 0) { - for (var i = 0; i < startCallbacks.length; i++) { - var callback = startCallbacks[i]; - callback(true); - } - startCallbacks = []; - } - receiveWebViewEvent('accelerometerStarted'); - } - function onAccelerometerStopped(eventType, eventData) { - isStarted = false; - if (stopCallbacks.length > 0) { - for (var i = 0; i < stopCallbacks.length; i++) { - var callback = stopCallbacks[i]; - callback(true); - } - stopCallbacks = []; - } - receiveWebViewEvent('accelerometerStopped'); - } - function onAccelerometerChanged(eventType, eventData) { - valueX = eventData.x; - valueY = eventData.y; - valueZ = eventData.z; - receiveWebViewEvent('accelerometerChanged'); - } - function onAccelerometerFailed(eventType, eventData) { - if (startCallbacks.length > 0) { - for (var i = 0; i < startCallbacks.length; i++) { - var callback = startCallbacks[i]; - callback(false); - } - startCallbacks = []; - } - receiveWebViewEvent('accelerometerFailed', { - error: eventData.error - }); - } - - function checkVersion() { - if (!versionAtLeast('8.0')) { - console.warn('[Telegram.WebApp] Accelerometer is not supported in version ' + webAppVersion); - return false; - } - return true; - } - - accelerometer.start = function(params, callback) { - params = params || {}; - if (!checkVersion()) { - return accelerometer; - } - var req_params = {}; - var refresh_rate = parseInt(params.refresh_rate || 1000); - if (isNaN(refresh_rate) || refresh_rate < 20 || refresh_rate > 1000) { - console.warn('[Telegram.WebApp] Accelerometer refresh_rate is invalid', refresh_rate); - } else { - req_params.refresh_rate = refresh_rate; - } - - if (callback) { - startCallbacks.push(callback); - } - WebView.postEvent('web_app_start_accelerometer', false, req_params); - return accelerometer; - }; - accelerometer.stop = function(callback) { - if (!checkVersion()) { - return accelerometer; - } - if (callback) { - stopCallbacks.push(callback); - } - WebView.postEvent('web_app_stop_accelerometer'); - return accelerometer; - }; - return accelerometer; - })(); - - var DeviceOrientation = (function() { - var isStarted = false; - var valueAlpha = null, valueBeta = null, valueGamma = null, valueAbsolute = false; - var startCallbacks = [], stopCallbacks = []; - - var deviceOrientation = {}; - Object.defineProperty(deviceOrientation, 'isStarted', { - get: function(){ return isStarted; }, - enumerable: true - }); - Object.defineProperty(deviceOrientation, 'absolute', { - get: function(){ return valueAbsolute; }, - enumerable: true - }); - Object.defineProperty(deviceOrientation, 'alpha', { - get: function(){ return valueAlpha; }, - enumerable: true - }); - Object.defineProperty(deviceOrientation, 'beta', { - get: function(){ return valueBeta; }, - enumerable: true - }); - Object.defineProperty(deviceOrientation, 'gamma', { - get: function(){ return valueGamma; }, - enumerable: true - }); - - WebView.onEvent('device_orientation_started', onDeviceOrientationStarted); - WebView.onEvent('device_orientation_stopped', onDeviceOrientationStopped); - WebView.onEvent('device_orientation_changed', onDeviceOrientationChanged); - WebView.onEvent('device_orientation_failed', onDeviceOrientationFailed); - - function onDeviceOrientationStarted(eventType, eventData) { - isStarted = true; - if (startCallbacks.length > 0) { - for (var i = 0; i < startCallbacks.length; i++) { - var callback = startCallbacks[i]; - callback(true); - } - startCallbacks = []; - } - receiveWebViewEvent('deviceOrientationStarted'); - } - function onDeviceOrientationStopped(eventType, eventData) { - isStarted = false; - if (stopCallbacks.length > 0) { - for (var i = 0; i < stopCallbacks.length; i++) { - var callback = stopCallbacks[i]; - callback(true); - } - stopCallbacks = []; - } - receiveWebViewEvent('deviceOrientationStopped'); - } - function onDeviceOrientationChanged(eventType, eventData) { - valueAbsolute = !!eventData.absolute; - valueAlpha = eventData.alpha; - valueBeta = eventData.beta; - valueGamma = eventData.gamma; - receiveWebViewEvent('deviceOrientationChanged'); - } - function onDeviceOrientationFailed(eventType, eventData) { - if (startCallbacks.length > 0) { - for (var i = 0; i < startCallbacks.length; i++) { - var callback = startCallbacks[i]; - callback(false); - } - startCallbacks = []; - } - receiveWebViewEvent('deviceOrientationFailed', { - error: eventData.error - }); - } - - function checkVersion() { - if (!versionAtLeast('8.0')) { - console.warn('[Telegram.WebApp] DeviceOrientation is not supported in version ' + webAppVersion); - return false; - } - return true; - } - - deviceOrientation.start = function(params, callback) { - params = params || {}; - if (!checkVersion()) { - return deviceOrientation; - } - var req_params = {}; - var refresh_rate = parseInt(params.refresh_rate || 1000); - if (isNaN(refresh_rate) || refresh_rate < 20 || refresh_rate > 1000) { - console.warn('[Telegram.WebApp] DeviceOrientation refresh_rate is invalid', refresh_rate); - } else { - req_params.refresh_rate = refresh_rate; - } - req_params.need_absolute = !!params.need_absolute; - - if (callback) { - startCallbacks.push(callback); - } - WebView.postEvent('web_app_start_device_orientation', false, req_params); - return deviceOrientation; - }; - deviceOrientation.stop = function(callback) { - if (!checkVersion()) { - return deviceOrientation; - } - if (callback) { - stopCallbacks.push(callback); - } - WebView.postEvent('web_app_stop_device_orientation'); - return deviceOrientation; - }; - return deviceOrientation; - })(); - - var Gyroscope = (function() { - var isStarted = false; - var valueX = null, valueY = null, valueZ = null; - var startCallbacks = [], stopCallbacks = []; - - var gyroscope = {}; - Object.defineProperty(gyroscope, 'isStarted', { - get: function(){ return isStarted; }, - enumerable: true - }); - Object.defineProperty(gyroscope, 'x', { - get: function(){ return valueX; }, - enumerable: true - }); - Object.defineProperty(gyroscope, 'y', { - get: function(){ return valueY; }, - enumerable: true - }); - Object.defineProperty(gyroscope, 'z', { - get: function(){ return valueZ; }, - enumerable: true - }); - - WebView.onEvent('gyroscope_started', onGyroscopeStarted); - WebView.onEvent('gyroscope_stopped', onGyroscopeStopped); - WebView.onEvent('gyroscope_changed', onGyroscopeChanged); - WebView.onEvent('gyroscope_failed', onGyroscopeFailed); - - function onGyroscopeStarted(eventType, eventData) { - isStarted = true; - if (startCallbacks.length > 0) { - for (var i = 0; i < startCallbacks.length; i++) { - var callback = startCallbacks[i]; - callback(true); - } - startCallbacks = []; - } - receiveWebViewEvent('gyroscopeStarted'); - } - function onGyroscopeStopped(eventType, eventData) { - isStarted = false; - if (stopCallbacks.length > 0) { - for (var i = 0; i < stopCallbacks.length; i++) { - var callback = stopCallbacks[i]; - callback(true); - } - stopCallbacks = []; - } - receiveWebViewEvent('gyroscopeStopped'); - } - function onGyroscopeChanged(eventType, eventData) { - valueX = eventData.x; - valueY = eventData.y; - valueZ = eventData.z; - receiveWebViewEvent('gyroscopeChanged'); - } - function onGyroscopeFailed(eventType, eventData) { - if (startCallbacks.length > 0) { - for (var i = 0; i < startCallbacks.length; i++) { - var callback = startCallbacks[i]; - callback(false); - } - startCallbacks = []; - } - receiveWebViewEvent('gyroscopeFailed', { - error: eventData.error - }); - } - - function checkVersion() { - if (!versionAtLeast('8.0')) { - console.warn('[Telegram.WebApp] Gyroscope is not supported in version ' + webAppVersion); - return false; - } - return true; - } - - gyroscope.start = function(params, callback) { - params = params || {}; - if (!checkVersion()) { - return gyroscope; - } - var req_params = {}; - var refresh_rate = parseInt(params.refresh_rate || 1000); - if (isNaN(refresh_rate) || refresh_rate < 20 || refresh_rate > 1000) { - console.warn('[Telegram.WebApp] Gyroscope refresh_rate is invalid', refresh_rate); - } else { - req_params.refresh_rate = refresh_rate; - } - - if (callback) { - startCallbacks.push(callback); - } - WebView.postEvent('web_app_start_gyroscope', false, req_params); - return gyroscope; - }; - gyroscope.stop = function(callback) { - if (!checkVersion()) { - return gyroscope; - } - if (callback) { - stopCallbacks.push(callback); - } - WebView.postEvent('web_app_stop_gyroscope'); - return gyroscope; - }; - return gyroscope; - })(); - - var webAppInvoices = {}; - function onInvoiceClosed(eventType, eventData) { - if (eventData.slug && webAppInvoices[eventData.slug]) { - var invoiceData = webAppInvoices[eventData.slug]; - delete webAppInvoices[eventData.slug]; - if (invoiceData.callback) { - invoiceData.callback(eventData.status); - } - receiveWebViewEvent('invoiceClosed', { - url: invoiceData.url, - status: eventData.status - }); - } - } - - var webAppPopupOpened = false; - function onPopupClosed(eventType, eventData) { - if (webAppPopupOpened) { - var popupData = webAppPopupOpened; - webAppPopupOpened = false; - var button_id = null; - if (typeof eventData.button_id !== 'undefined') { - button_id = eventData.button_id; - } - if (popupData.callback) { - popupData.callback(button_id); - } - receiveWebViewEvent('popupClosed', { - button_id: button_id - }); - } - } - - var webAppScanQrPopupOpened = false; - function onQrTextReceived(eventType, eventData) { - if (webAppScanQrPopupOpened) { - var popupData = webAppScanQrPopupOpened; - var data = null; - if (typeof eventData.data !== 'undefined') { - data = eventData.data; - } - if (popupData.callback) { - if (popupData.callback(data)) { - webAppScanQrPopupOpened = false; - WebView.postEvent('web_app_close_scan_qr_popup', false); - } - } - receiveWebViewEvent('qrTextReceived', { - data: data - }); - } - } - function onScanQrPopupClosed(eventType, eventData) { - webAppScanQrPopupOpened = false; - receiveWebViewEvent('scanQrPopupClosed'); - } - - function onClipboardTextReceived(eventType, eventData) { - if (eventData.req_id && webAppCallbacks[eventData.req_id]) { - var requestData = webAppCallbacks[eventData.req_id]; - delete webAppCallbacks[eventData.req_id]; - var data = null; - if (typeof eventData.data !== 'undefined') { - data = eventData.data; - } - if (requestData.callback) { - requestData.callback(data); - } - receiveWebViewEvent('clipboardTextReceived', { - data: data - }); - } - } - - var WebAppWriteAccessRequested = false; - function onWriteAccessRequested(eventType, eventData) { - if (WebAppWriteAccessRequested) { - var requestData = WebAppWriteAccessRequested; - WebAppWriteAccessRequested = false; - if (requestData.callback) { - requestData.callback(eventData.status == 'allowed'); - } - receiveWebViewEvent('writeAccessRequested', { - status: eventData.status - }); - } - } - - function getRequestedContact(callback, timeout) { - var reqTo, fallbackTo, reqDelay = 0; - var reqInvoke = function() { - invokeCustomMethod('getRequestedContact', {}, function(err, res) { - if (res.substr(0, 1) == '"' && res.substr(-1) == '"') { // macos fix - res = JSON.parse(res); - } - if (res && res.length) { - clearTimeout(fallbackTo); - callback(res); - } else { - reqDelay += 50; - reqTo = setTimeout(reqInvoke, reqDelay); - } - }); - }; - var fallbackInvoke = function() { - clearTimeout(reqTo); - callback(''); - }; - fallbackTo = setTimeout(fallbackInvoke, timeout); - reqInvoke(); - } - - var WebAppContactRequested = false; - function onPhoneRequested(eventType, eventData) { - if (WebAppContactRequested) { - var requestData = WebAppContactRequested; - WebAppContactRequested = false; - var requestSent = eventData.status == 'sent'; - var webViewEvent = { - status: eventData.status - }; - if (requestSent) { - getRequestedContact(function(res) { - if (res && res.length) { - webViewEvent.response = res; - webViewEvent.responseUnsafe = Utils.urlParseQueryString(res); - for (var key in webViewEvent.responseUnsafe) { - var val = webViewEvent.responseUnsafe[key]; - try { - if (val.substr(0, 1) == '{' && val.substr(-1) == '}' || - val.substr(0, 1) == '[' && val.substr(-1) == ']') { - webViewEvent.responseUnsafe[key] = JSON.parse(val); - } - } catch (e) {} - } - } - if (requestData.callback) { - requestData.callback(requestSent, webViewEvent); - } - receiveWebViewEvent('contactRequested', webViewEvent); - }, 3000); - } else { - if (requestData.callback) { - requestData.callback(requestSent, webViewEvent); - } - receiveWebViewEvent('contactRequested', webViewEvent); - } - } - } - - var webAppDownloadFileRequested = false; - function onFileDownloadRequested(eventType, eventData) { - if (webAppDownloadFileRequested) { - var requestData = webAppDownloadFileRequested; - webAppDownloadFileRequested = false; - var isDownloading = eventData.status == 'downloading'; - if (requestData.callback) { - requestData.callback(isDownloading); - } - receiveWebViewEvent('fileDownloadRequested', { - status: isDownloading ? 'downloading' : 'cancelled' - }); - } - } - - function onCustomMethodInvoked(eventType, eventData) { - if (eventData.req_id && webAppCallbacks[eventData.req_id]) { - var requestData = webAppCallbacks[eventData.req_id]; - delete webAppCallbacks[eventData.req_id]; - var res = null, err = null; - if (typeof eventData.result !== 'undefined') { - res = eventData.result; - } - if (typeof eventData.error !== 'undefined') { - err = eventData.error; - } - if (requestData.callback) { - requestData.callback(err, res); - } - } - } - - function invokeCustomMethod(method, params, callback) { - if (!versionAtLeast('6.9')) { - console.error('[Telegram.WebApp] Method invokeCustomMethod is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - var req_id = generateCallbackId(16); - var req_params = {req_id: req_id, method: method, params: params || {}}; - webAppCallbacks[req_id] = { - callback: callback - }; - WebView.postEvent('web_app_invoke_custom_method', false, req_params); - }; - - if (!window.Telegram) { - window.Telegram = {}; - } - - Object.defineProperty(WebApp, 'initData', { - get: function(){ return webAppInitData; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'initDataUnsafe', { - get: function(){ return webAppInitDataUnsafe; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'version', { - get: function(){ return webAppVersion; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'platform', { - get: function(){ return webAppPlatform; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'colorScheme', { - get: function(){ return colorScheme; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'themeParams', { - get: function(){ return themeParams; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'isExpanded', { - get: function(){ return isExpanded; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'viewportHeight', { - get: function(){ return (viewportHeight === false ? window.innerHeight : viewportHeight) - bottomBarHeight; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'viewportStableHeight', { - get: function(){ return (viewportStableHeight === false ? window.innerHeight : viewportStableHeight) - bottomBarHeight; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'safeAreaInset', { - get: function(){ return safeAreaInset; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'contentSafeAreaInset', { - get: function(){ return contentSafeAreaInset; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'isClosingConfirmationEnabled', { - set: function(val){ setClosingConfirmation(val); }, - get: function(){ return isClosingConfirmationEnabled; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'isVerticalSwipesEnabled', { - set: function(val){ toggleVerticalSwipes(val); }, - get: function(){ return isVerticalSwipesEnabled; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'isFullscreen', { - get: function(){ return webAppIsFullscreen; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'isOrientationLocked', { - set: function(val){ toggleOrientationLock(val); }, - get: function(){ return webAppIsOrientationLocked; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'isActive', { - get: function(){ return webAppIsActive; }, - enumerable: true - }); - Object.defineProperty(WebApp, 'headerColor', { - set: function(val){ setHeaderColor(val); }, - get: function(){ return getHeaderColor(); }, - enumerable: true - }); - Object.defineProperty(WebApp, 'backgroundColor', { - set: function(val){ setBackgroundColor(val); }, - get: function(){ return getBackgroundColor(); }, - enumerable: true - }); - Object.defineProperty(WebApp, 'bottomBarColor', { - set: function(val){ setBottomBarColor(val); }, - get: function(){ return getBottomBarColor(); }, - enumerable: true - }); - Object.defineProperty(WebApp, 'BackButton', { - value: BackButton, - enumerable: true - }); - Object.defineProperty(WebApp, 'MainButton', { - value: MainButton, - enumerable: true - }); - Object.defineProperty(WebApp, 'SecondaryButton', { - value: SecondaryButton, - enumerable: true - }); - Object.defineProperty(WebApp, 'SettingsButton', { - value: SettingsButton, - enumerable: true - }); - Object.defineProperty(WebApp, 'HapticFeedback', { - value: HapticFeedback, - enumerable: true - }); - Object.defineProperty(WebApp, 'CloudStorage', { - value: CloudStorage, - enumerable: true - }); - Object.defineProperty(WebApp, 'DeviceStorage', { - value: DeviceStorage, - enumerable: true - }); - Object.defineProperty(WebApp, 'SecureStorage', { - value: SecureStorage, - enumerable: true - }); - Object.defineProperty(WebApp, 'BiometricManager', { - value: BiometricManager, - enumerable: true - }); - Object.defineProperty(WebApp, 'Accelerometer', { - value: Accelerometer, - enumerable: true - }); - Object.defineProperty(WebApp, 'DeviceOrientation', { - value: DeviceOrientation, - enumerable: true - }); - Object.defineProperty(WebApp, 'Gyroscope', { - value: Gyroscope, - enumerable: true - }); - Object.defineProperty(WebApp, 'LocationManager', { - value: LocationManager, - enumerable: true - }); - WebApp.isVersionAtLeast = function(ver) { - return versionAtLeast(ver); - }; - WebApp.setHeaderColor = function(color_key) { - WebApp.headerColor = color_key; - }; - WebApp.setBackgroundColor = function(color) { - WebApp.backgroundColor = color; - }; - WebApp.setBottomBarColor = function(color) { - WebApp.bottomBarColor = color; - }; - WebApp.enableClosingConfirmation = function() { - WebApp.isClosingConfirmationEnabled = true; - }; - WebApp.disableClosingConfirmation = function() { - WebApp.isClosingConfirmationEnabled = false; - }; - WebApp.enableVerticalSwipes = function() { - WebApp.isVerticalSwipesEnabled = true; - }; - WebApp.disableVerticalSwipes = function() { - WebApp.isVerticalSwipesEnabled = false; - }; - WebApp.lockOrientation = function() { - WebApp.isOrientationLocked = true; - }; - WebApp.unlockOrientation = function() { - WebApp.isOrientationLocked = false; - }; - WebApp.requestFullscreen = function() { - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method requestFullscreen is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - WebView.postEvent('web_app_request_fullscreen'); - }; - WebApp.exitFullscreen = function() { - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method exitFullscreen is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - WebView.postEvent('web_app_exit_fullscreen'); - }; - WebApp.addToHomeScreen = function() { - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method addToHomeScreen is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - WebView.postEvent('web_app_add_to_home_screen'); - }; - WebApp.checkHomeScreenStatus = function(callback) { - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method checkHomeScreenStatus is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (callback) { - homeScreenCallbacks.push(callback); - } - WebView.postEvent('web_app_check_home_screen'); - }; - WebApp.onEvent = function(eventType, callback) { - onWebViewEvent(eventType, callback); - }; - WebApp.offEvent = function(eventType, callback) {offWebViewEvent(eventType, callback); - }; - WebApp.sendData = function (data) { - if (!data || !data.length) { - console.error('[Telegram.WebApp] Data is required', data); - throw Error('WebAppDataInvalid'); - } - if (byteLength(data) > 4096) { - console.error('[Telegram.WebApp] Data is too long', data); - throw Error('WebAppDataInvalid'); - } - WebView.postEvent('web_app_data_send', false, {data: data}); - }; - WebApp.switchInlineQuery = function (query, choose_chat_types) { - if (!versionAtLeast('6.6')) { - console.error('[Telegram.WebApp] Method switchInlineQuery is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (!initParams.tgWebAppBotInline) { - console.error('[Telegram.WebApp] Inline mode is disabled for this bot. Read more about inline mode: https://core.telegram.org/bots/inline'); - throw Error('WebAppInlineModeDisabled'); - } - query = query || ''; - if (query.length > 256) { - console.error('[Telegram.WebApp] Inline query is too long', query); - throw Error('WebAppInlineQueryInvalid'); - } - var chat_types = []; - if (choose_chat_types) { - if (!Array.isArray(choose_chat_types)) { - console.error('[Telegram.WebApp] Choose chat types should be an array', choose_chat_types); - throw Error('WebAppInlineChooseChatTypesInvalid'); - } - var good_types = {users: 1, bots: 1, groups: 1, channels: 1}; - for (var i = 0; i < choose_chat_types.length; i++) { - var chat_type = choose_chat_types[i]; - if (!good_types[chat_type]) { - console.error('[Telegram.WebApp] Choose chat type is invalid', chat_type); - throw Error('WebAppInlineChooseChatTypeInvalid'); - } - if (good_types[chat_type] != 2) { - good_types[chat_type] = 2; - chat_types.push(chat_type); - } - } - } - WebView.postEvent('web_app_switch_inline_query', false, {query: query, chat_types: chat_types}); - }; - WebApp.openLink = function (url, options) { - var a = document.createElement('A'); - a.href = url; - if (a.protocol != 'http:' && - a.protocol != 'https:') { - console.error('[Telegram.WebApp] Url protocol is not supported', url); - throw Error('WebAppTgUrlInvalid'); - } - var url = a.href; - options = options || {}; - if (versionAtLeast('6.1')) { - var req_params = {url: url}; - if (versionAtLeast('6.4') && options.try_instant_view) { - req_params.try_instant_view = true; - } - if (versionAtLeast('7.6') && options.try_browser) { - req_params.try_browser = options.try_browser; - } - WebView.postEvent('web_app_open_link', false, req_params); - } else { - window.open(url, '_blank'); - } - }; - WebApp.openTelegramLink = function (url, options) { - var a = document.createElement('A'); - a.href = url; - if (a.protocol != 'http:' && - a.protocol != 'https:') { - console.error('[Telegram.WebApp] Url protocol is not supported', url); - throw Error('WebAppTgUrlInvalid'); - } - if (a.hostname != 't.me') { - console.error('[Telegram.WebApp] Url host is not supported', url); - throw Error('WebAppTgUrlInvalid'); - } - var path_full = a.pathname + a.search; - options = options || {}; - if (isIframe || versionAtLeast('6.1')) { - var req_params = {path_full: path_full}; - if (options.force_request) { - req_params.force_request = true; - } - WebView.postEvent('web_app_open_tg_link', false, req_params); - } else { - location.href = 'https://t.me' + path_full; - } - }; - WebApp.openInvoice = function (url, callback) { - var a = document.createElement('A'), match, slug; - a.href = url; - if (a.protocol != 'http:' && - a.protocol != 'https:' || - a.hostname != 't.me' || - !(match = a.pathname.match(/^\/(\$|invoice\/)([A-Za-z0-9\-_=]+)$/)) || - !(slug = match[2])) { - console.error('[Telegram.WebApp] Invoice url is invalid', url); - throw Error('WebAppInvoiceUrlInvalid'); - } - if (!versionAtLeast('6.1')) { - console.error('[Telegram.WebApp] Method openInvoice is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (webAppInvoices[slug]) { - console.error('[Telegram.WebApp] Invoice is already opened'); - throw Error('WebAppInvoiceOpened'); - } - webAppInvoices[slug] = { - url: url, - callback: callback - }; - WebView.postEvent('web_app_open_invoice', false, {slug: slug}); - }; - WebApp.showPopup = function (params, callback) { - if (!versionAtLeast('6.2')) { - console.error('[Telegram.WebApp] Method showPopup is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (webAppPopupOpened) { - console.error('[Telegram.WebApp] Popup is already opened'); - throw Error('WebAppPopupOpened'); - } - var title = ''; - var message = ''; - var buttons = []; - var popup_buttons = {}; - var popup_params = {}; - if (typeof params.title !== 'undefined') { - title = strTrim(params.title); - if (title.length > 64) { - console.error('[Telegram.WebApp] Popup title is too long', title); - throw Error('WebAppPopupParamInvalid'); - } - if (title.length > 0) { - popup_params.title = title; - } - } - if (typeof params.message !== 'undefined') { - message = strTrim(params.message); - } - if (!message.length) { - console.error('[Telegram.WebApp] Popup message is required', params.message); - throw Error('WebAppPopupParamInvalid'); - } - if (message.length > 256) { - console.error('[Telegram.WebApp] Popup message is too long', message); - throw Error('WebAppPopupParamInvalid'); - } - popup_params.message = message; - if (typeof params.buttons !== 'undefined') { - if (!Array.isArray(params.buttons)) { - console.error('[Telegram.WebApp] Popup buttons should be an array', params.buttons); - throw Error('WebAppPopupParamInvalid'); - } - for (var i = 0; i < params.buttons.length; i++) { - var button = params.buttons[i]; - var btn = {}; - var id = ''; - if (typeof button.id !== 'undefined') { - id = button.id.toString(); - if (id.length > 64) { - console.error('[Telegram.WebApp] Popup button id is too long', id); - throw Error('WebAppPopupParamInvalid'); - } - } - btn.id = id; - var button_type = button.type; - if (typeof button_type === 'undefined') { - button_type = 'default'; - } - btn.type = button_type; - if (button_type == 'ok' || - button_type == 'close' || - button_type == 'cancel') { - // no params needed - } else if (button_type == 'default' || - button_type == 'destructive') { - var text = ''; - if (typeof button.text !== 'undefined') { - text = strTrim(button.text); - } - if (!text.length) { - console.error('[Telegram.WebApp] Popup button text is required for type ' + button_type, button.text); - throw Error('WebAppPopupParamInvalid'); - } - if (text.length > 64) { - console.error('[Telegram.WebApp] Popup button text is too long', text); - throw Error('WebAppPopupParamInvalid'); - } - btn.text = text; - } else { - console.error('[Telegram.WebApp] Popup button type is invalid', button_type); - throw Error('WebAppPopupParamInvalid'); - } - buttons.push(btn); - } - } else { - buttons.push({id: '', type: 'close'}); - } - if (buttons.length < 1) { - console.error('[Telegram.WebApp] Popup should have at least one button'); - throw Error('WebAppPopupParamInvalid'); - } - if (buttons.length > 3) { - console.error('[Telegram.WebApp] Popup should not have more than 3 buttons'); - throw Error('WebAppPopupParamInvalid'); - } - popup_params.buttons = buttons; - - webAppPopupOpened = { - callback: callback - }; - WebView.postEvent('web_app_open_popup', false, popup_params); - }; - WebApp.showAlert = function (message, callback) { - WebApp.showPopup({ - message: message - }, callback ? function(){ callback(); } : null); - }; - WebApp.showConfirm = function (message, callback) { - WebApp.showPopup({ - message: message, - buttons: [ - {type: 'ok', id: 'ok'}, - {type: 'cancel'} - ] - }, callback ? function (button_id) { - callback(button_id == 'ok'); - } : null); - }; - WebApp.showScanQrPopup = function (params, callback) { - if (!versionAtLeast('6.4')) { - console.error('[Telegram.WebApp] Method showScanQrPopup is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (webAppScanQrPopupOpened) { - console.error('[Telegram.WebApp] Popup is already opened'); - throw Error('WebAppScanQrPopupOpened'); - } - var text = ''; - var popup_params = {}; - if (typeof params.text !== 'undefined') { - text = strTrim(params.text); - if (text.length > 64) { - console.error('[Telegram.WebApp] Scan QR popup text is too long', text); - throw Error('WebAppScanQrPopupParamInvalid'); - } - if (text.length > 0) { - popup_params.text = text; - } - } - - webAppScanQrPopupOpened = { - callback: callback - }; - WebView.postEvent('web_app_open_scan_qr_popup', false, popup_params); - }; - WebApp.closeScanQrPopup = function () { - if (!versionAtLeast('6.4')) { - console.error('[Telegram.WebApp] Method closeScanQrPopup is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - - webAppScanQrPopupOpened = false; - WebView.postEvent('web_app_close_scan_qr_popup', false); - }; - WebApp.readTextFromClipboard = function (callback) { - if (!versionAtLeast('6.4')) { - console.error('[Telegram.WebApp] Method readTextFromClipboard is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - var req_id = generateCallbackId(16); - var req_params = {req_id: req_id}; - webAppCallbacks[req_id] = { - callback: callback - }; - WebView.postEvent('web_app_read_text_from_clipboard', false, req_params); - }; - WebApp.requestWriteAccess = function (callback) { - if (!versionAtLeast('6.9')) { - console.error('[Telegram.WebApp] Method requestWriteAccess is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (WebAppWriteAccessRequested) { - console.error('[Telegram.WebApp] Write access is already requested'); - throw Error('WebAppWriteAccessRequested'); - } - WebAppWriteAccessRequested = { - callback: callback - }; - WebView.postEvent('web_app_request_write_access'); - }; - WebApp.requestContact = function (callback) { - if (!versionAtLeast('6.9')) { - console.error('[Telegram.WebApp] Method requestContact is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (WebAppContactRequested) { - console.error('[Telegram.WebApp] Contact is already requested'); - throw Error('WebAppContactRequested'); - } - WebAppContactRequested = { - callback: callback - }; - WebView.postEvent('web_app_request_phone'); - }; - WebApp.downloadFile = function (params, callback) { - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method downloadFile is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (webAppDownloadFileRequested) { - console.error('[Telegram.WebApp] Popup is already opened'); - throw Error('WebAppDownloadFilePopupOpened'); - } - var a = document.createElement('A'); - - var dl_params = {}; - if (!params || !params.url || !params.url.length) { - console.error('[Telegram.WebApp] Url is required'); - throw Error('WebAppDownloadFileParamInvalid'); - } - a.href = params.url; - if (a.protocol != 'https:') { - console.error('[Telegram.WebApp] Url protocol is not supported', url); - throw Error('WebAppDownloadFileParamInvalid'); - } - dl_params.url = a.href; - - if (!params || !params.file_name || !params.file_name.length) { - console.error('[Telegram.WebApp] File name is required'); - throw Error('WebAppDownloadFileParamInvalid'); - } - dl_params.file_name = params.file_name; - - webAppDownloadFileRequested = { - callback: callback - }; - WebView.postEvent('web_app_request_file_download', false, dl_params); - }; - WebApp.shareToStory = function (media_url, params) { - params = params || {}; - if (!versionAtLeast('7.8')) { - console.error('[Telegram.WebApp] Method shareToStory is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - var a = document.createElement('A'); - a.href = media_url; - if (a.protocol != 'http:' && - a.protocol != 'https:') { - console.error('[Telegram.WebApp] Media url protocol is not supported', url); - throw Error('WebAppMediaUrlInvalid'); - } - var share_params = {}; - share_params.media_url = a.href; - if (typeof params.text !== 'undefined') { - var text = strTrim(params.text); - if (text.length > 2048) { - console.error('[Telegram.WebApp] Text is too long', text); - throw Error('WebAppShareToStoryParamInvalid'); - } - if (text.length > 0) { - share_params.text = text; - } - } - if (typeof params.widget_link !== 'undefined') { - params.widget_link = params.widget_link || {}; - a.href = params.widget_link.url; - if (a.protocol != 'http:' && - a.protocol != 'https:') { - console.error('[Telegram.WebApp] Link protocol is not supported', url); - throw Error('WebAppShareToStoryParamInvalid'); - } - var widget_link = { - url: a.href - }; - if (typeof params.widget_link.name !== 'undefined') { - var link_name = strTrim(params.widget_link.name); - if (link_name.length > 48) { - console.error('[Telegram.WebApp] Link name is too long', link_name); - throw Error('WebAppShareToStoryParamInvalid'); - } - if (link_name.length > 0) { - widget_link.name = link_name; - } - } - share_params.widget_link = widget_link; - } - - WebView.postEvent('web_app_share_to_story', false, share_params); - }; - WebApp.shareMessage = function (msg_id, callback) { - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method shareMessage is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (WebAppShareMessageOpened) { - console.error('[Telegram.WebApp] Share message is already opened'); - throw Error('WebAppShareMessageOpened'); - } - WebAppShareMessageOpened = { - callback: callback - }; - WebView.postEvent('web_app_send_prepared_message', false, {id: msg_id}); - }; - WebApp.requestChat = function (req_id, callback) { - if (!versionAtLeast('9.6')) { - console.error('[Telegram.WebApp] Method requestChat is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (WebAppRequestChatOpened) { - console.error('[Telegram.WebApp] Request chat is already opened'); - throw Error('WebAppRequestChatOpened'); - } - WebAppRequestChatOpened = { - callback: callback - }; - WebView.postEvent('web_app_request_chat', false, {req_id: req_id}); - }; - WebApp.setEmojiStatus = function (custom_emoji_id, params, callback) { - params = params || {}; - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method setEmojiStatus is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - var status_params = {}; - status_params.custom_emoji_id = custom_emoji_id; - if (typeof params.duration !== 'undefined') { - status_params.duration = params.duration; - } - if (WebAppEmojiStatusRequested) { - console.error('[Telegram.WebApp] Emoji status is already requested'); - throw Error('WebAppEmojiStatusRequested'); - } - WebAppEmojiStatusRequested = { - callback: callback - }; - WebView.postEvent('web_app_set_emoji_status', false, status_params); - }; - WebApp.requestEmojiStatusAccess = function (callback) { - if (!versionAtLeast('8.0')) { - console.error('[Telegram.WebApp] Method requestEmojiStatusAccess is not supported in version ' + webAppVersion); - throw Error('WebAppMethodUnsupported'); - } - if (WebAppEmojiStatusAccessRequested) { - console.error('[Telegram.WebApp] Emoji status permission is already requested'); - throw Error('WebAppEmojiStatusAccessRequested'); - } - WebAppEmojiStatusAccessRequested = { - callback: callback - }; - WebView.postEvent('web_app_request_emoji_status_access'); - }; - WebApp.invokeCustomMethod = function (method, params, callback) { - invokeCustomMethod(method, params, callback); - }; - WebApp.hideKeyboard = function () { - WebView.postEvent('web_app_hide_keyboard'); - }; - WebApp.ready = function () { - WebView.postEvent('web_app_ready'); - }; - WebApp.expand = function () { - WebView.postEvent('web_app_expand'); - }; - WebApp.close = function (options) { - options = options || {}; - var req_params = {}; - if (versionAtLeast('7.6') && options.return_back) { - req_params.return_back = true; - } - WebView.postEvent('web_app_close', false, req_params); - }; - - window.Telegram.WebApp = WebApp; - - updateHeaderColor(); - updateBackgroundColor(); - updateBottomBarColor(); - setViewportHeight(); - if (initParams.tgWebAppShowSettings) { - SettingsButton.show(); - } - - window.addEventListener('resize', onWindowResize); - if (isIframe) { - document.addEventListener('click', linkHandler); - } - - WebView.onEvent('theme_changed', onThemeChanged); - WebView.onEvent('viewport_changed', onViewportChanged); - WebView.onEvent('safe_area_changed', onSafeAreaChanged); - WebView.onEvent('content_safe_area_changed', onContentSafeAreaChanged); - WebView.onEvent('visibility_changed', onVisibilityChanged); - WebView.onEvent('invoice_closed', onInvoiceClosed); - WebView.onEvent('popup_closed', onPopupClosed); - WebView.onEvent('qr_text_received', onQrTextReceived); - WebView.onEvent('scan_qr_popup_closed', onScanQrPopupClosed); - WebView.onEvent('clipboard_text_received', onClipboardTextReceived); - WebView.onEvent('write_access_requested', onWriteAccessRequested); - WebView.onEvent('phone_requested', onPhoneRequested); - WebView.onEvent('file_download_requested', onFileDownloadRequested); - WebView.onEvent('custom_method_invoked', onCustomMethodInvoked); - WebView.onEvent('fullscreen_changed', onFullscreenChanged); - WebView.onEvent('fullscreen_failed', onFullscreenFailed); - WebView.onEvent('home_screen_added', onHomeScreenAdded); - WebView.onEvent('home_screen_checked', onHomeScreenChecked); - WebView.onEvent('prepared_message_sent', onPreparedMessageSent); - WebView.onEvent('prepared_message_failed', onPreparedMessageFailed); - WebView.onEvent('requested_chat_sent', onRequestedChatSent); - WebView.onEvent('requested_chat_failed', onRequestedChatFailed); - WebView.onEvent('emoji_status_set', onEmojiStatusSet); - WebView.onEvent('emoji_status_failed', onEmojiStatusFailed); - WebView.onEvent('emoji_status_access_requested', onEmojiStatusAccessRequested); - WebView.postEvent('web_app_request_theme'); - WebView.postEvent('web_app_request_viewport'); - WebView.postEvent('web_app_request_safe_area'); - WebView.postEvent('web_app_request_content_safe_area'); - -})(); diff --git a/bot/app/web/templates/telegram-widget.js b/bot/app/web/templates/telegram-widget.js deleted file mode 100644 index 43b8ed9..0000000 --- a/bot/app/web/templates/telegram-widget.js +++ /dev/null @@ -1,575 +0,0 @@ -(function(window) { - (function(window){ - window.__parseFunction = function(__func, __attrs) { - __attrs = __attrs || []; - __func = '(function(' + __attrs.join(',') + '){' + __func + '})'; - return window.execScript ? window.execScript(__func) : eval(__func); - } - }(window)); - (function(window){ - - function addEvent(el, event, handler) { - var events = event.split(/\s+/); - for (var i = 0; i < events.length; i++) { - if (el.addEventListener) { - el.addEventListener(events[i], handler); - } else { - el.attachEvent('on' + events[i], handler); - } - } - } - function removeEvent(el, event, handler) { - var events = event.split(/\s+/); - for (var i = 0; i < events.length; i++) { - if (el.removeEventListener) { - el.removeEventListener(events[i], handler); - } else { - el.detachEvent('on' + events[i], handler); - } - } - } - function getCssProperty(el, prop) { - if (window.getComputedStyle) { - return window.getComputedStyle(el, '').getPropertyValue(prop) || null; - } else if (el.currentStyle) { - return el.currentStyle[prop] || null; - } - return null; - } - function geById(el_or_id) { - if (typeof el_or_id == 'string' || el_or_id instanceof String) { - return document.getElementById(el_or_id); - } else if (el_or_id instanceof HTMLElement) { - return el_or_id; - } - return null; - } - - var getWidgetsOrigin = function(default_origin, dev_origin) { - var link = document.createElement('A'), origin; - link.href = document.currentScript && document.currentScript.src || default_origin; - origin = link.origin || link.protocol + '//' + link.hostname; - if (origin == 'https://telegram.org') { - origin = default_origin; - } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') { - origin = dev_origin; - } else { - origin = default_origin; - } - return origin; - }; - - var getPageCanonical = function() { - var a = document.createElement('A'), link, href; - if (document.querySelector) { - link = document.querySelector('link[rel="canonical"]'); - if (link && (href = link.getAttribute('href'))) { - a.href = href; - return a.href; - } - } else { - var links = document.getElementsByTagName('LINK'); - for (var i = 0; i < links.length; i++) { - if ((link = links[i]) && - (link.getAttribute('rel') == 'canonical') && - (href = link.getAttribute('href'))) { - a.href = href; - return a.href; - } - } - } - return false; - }; - - function haveTgAuthResult() { - var locationHash = '', re = /[#\?\&]tgAuthResult=([A-Za-z0-9\-_=]*)$/, match; - try { - locationHash = location.hash.toString(); - if (match = locationHash.match(re)) { - location.hash = locationHash.replace(re, ''); - var data = match[1] || ''; - data = data.replace(/-/g, '+').replace(/_/g, '/'); - var pad = data.length % 4; - if (pad > 1) { - data += new Array(5 - pad).join('='); - } - return JSON.parse(window.atob(data)); - } - } catch (e) {} - return false; - } - - function getXHR() { - if (navigator.appName == "Microsoft Internet Explorer"){ - return new ActiveXObject("Microsoft.XMLHTTP"); - } else { - return new XMLHttpRequest(); - } - } - - if (!window.Telegram) { - window.Telegram = {}; - } - if (!window.Telegram.__WidgetUuid) { - window.Telegram.__WidgetUuid = 0; - } - if (!window.Telegram.__WidgetLastId) { - window.Telegram.__WidgetLastId = 0; - } - if (!window.Telegram.__WidgetCallbacks) { - window.Telegram.__WidgetCallbacks = {}; - } - - function postMessageToIframe(iframe, event, data, callback) { - if (!iframe._ready) { - if (!iframe._readyQueue) iframe._readyQueue = []; - iframe._readyQueue.push([event, data, callback]); - return; - } - try { - data = data || {}; - data.event = event; - if (callback) { - data._cb = ++window.Telegram.__WidgetLastId; - window.Telegram.__WidgetCallbacks[data._cb] = { - iframe: iframe, - callback: callback - }; - } - iframe.contentWindow.postMessage(JSON.stringify(data), '*'); - } catch(e) {} - } - - function initWidget(widgetEl) { - var widgetId, widgetElId, widgetsOrigin, existsEl, - src, styles = {}, allowedAttrs = [], - defWidth, defHeight, scrollable = false, onInitAuthUser, onAuthUser, onUnauth; - if (!widgetEl.tagName || - !(widgetEl.tagName.toUpperCase() == 'SCRIPT' || - widgetEl.tagName.toUpperCase() == 'BLOCKQUOTE' && - widgetEl.classList.contains('telegram-post'))) { - return null; - } - if (widgetEl._iframe) { - return widgetEl._iframe; - } - if (widgetId = widgetEl.getAttribute('data-telegram-post')) { - var comment = widgetEl.getAttribute('data-comment') || ''; - widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); - widgetElId = 'telegram-post-' + widgetId.replace(/[^a-z0-9_]/ig, '-') + (comment ? '-comment' + comment : ''); - src = widgetsOrigin + '/' + widgetId + '?embed=1'; - allowedAttrs = ['comment', 'userpic', 'mode', 'single?', 'color', 'dark', 'dark_color']; - defWidth = widgetEl.getAttribute('data-width') || '100%'; - defHeight = ''; - styles.minWidth = '320px'; - } - else if (widgetId = widgetEl.getAttribute('data-telegram-discussion')) { - widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); - widgetElId = 'telegram-discussion-' + widgetId.replace(/[^a-z0-9_]/ig, '-') + '-' + (++window.Telegram.__WidgetUuid); - var websitePageUrl = widgetEl.getAttribute('data-page-url'); - if (!websitePageUrl) { - websitePageUrl = getPageCanonical(); - } - src = widgetsOrigin + '/' + widgetId + '?embed=1&discussion=1' + (websitePageUrl ? '&page_url=' + encodeURIComponent(websitePageUrl) : ''); - allowedAttrs = ['comments_limit', 'color', 'colorful', 'dark', 'dark_color', 'width', 'height']; - defWidth = widgetEl.getAttribute('data-width') || '100%'; - defHeight = widgetEl.getAttribute('data-height') || 0; - styles.minWidth = '320px'; - if (defHeight > 0) { - scrollable = true; - } - } - else if (widgetEl.hasAttribute('data-telegram-login')) { - widgetId = widgetEl.getAttribute('data-telegram-login'); - widgetsOrigin = getWidgetsOrigin('https://oauth.telegram.org', 'https://oauth.tg.dev'); - widgetElId = 'telegram-login-' + widgetId.replace(/[^a-z0-9_]/ig, '-'); - src = widgetsOrigin + '/embed/' + widgetId + '?origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname) + '&return_to=' + encodeURIComponent(location.href); - allowedAttrs = ['size', 'userpic', 'init_auth', 'request_access', 'radius', 'min_width', 'max_width', 'lang']; - defWidth = 186; - defHeight = 28; - if (widgetEl.hasAttribute('data-size')) { - var size = widgetEl.getAttribute('data-size'); - if (size == 'small') defWidth = 148, defHeight = 20; - else if (size == 'large') defWidth = 238, defHeight = 40; - } - if (widgetEl.hasAttribute('data-onauth')) { - onInitAuthUser = onAuthUser = __parseFunction(widgetEl.getAttribute('data-onauth'), ['user']); - } - else if (widgetEl.hasAttribute('data-auth-url')) { - var a = document.createElement('A'); - a.href = widgetEl.getAttribute('data-auth-url'); - onAuthUser = function(user) { - var authUrl = a.href; - authUrl += (authUrl.indexOf('?') >= 0) ? '&' : '?'; - var params = []; - for (var key in user) { - params.push(key + '=' + encodeURIComponent(user[key])); - } - authUrl += params.join('&'); - location.href = authUrl; - }; - } - if (widgetEl.hasAttribute('data-onunauth')) { - onUnauth = __parseFunction(widgetEl.getAttribute('data-onunauth')); - } - var auth_result = haveTgAuthResult(); - if (auth_result && onAuthUser) { - onAuthUser(auth_result); - } - } - else if (widgetId = widgetEl.getAttribute('data-telegram-share-url')) { - widgetsOrigin = getWidgetsOrigin('https://t.me', 'https://post.tg.dev'); - widgetElId = 'telegram-share-' + window.btoa(widgetId); - src = widgetsOrigin + '/share/embed?origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname); - allowedAttrs = ['telegram-share-url', 'comment', 'size', 'text']; - defWidth = 60; - defHeight = 20; - if (widgetEl.getAttribute('data-size') == 'large') { - defWidth = 76; - defHeight = 28; - } - } - else { - return null; - } - existsEl = document.getElementById(widgetElId); - if (existsEl) { - return existsEl; - } - for (var i = 0; i < allowedAttrs.length; i++) { - var attr = allowedAttrs[i]; - var novalue = attr.substr(-1) == '?'; - if (novalue) { - attr = attr.slice(0, -1); - } - var data_attr = 'data-' + attr.replace(/_/g, '-'); - if (widgetEl.hasAttribute(data_attr)) { - var attr_value = novalue ? '1' : encodeURIComponent(widgetEl.getAttribute(data_attr)); - src += '&' + attr + '=' + attr_value; - } - } - function getCurCoords(iframe) { - var docEl = document.documentElement; - var frect = iframe.getBoundingClientRect(); - return { - frameTop: frect.top, - frameBottom: frect.bottom, - frameLeft: frect.left, - frameRight: frect.right, - frameWidth: frect.width, - frameHeight: frect.height, - scrollTop: window.pageYOffset, - scrollLeft: window.pageXOffset, - clientWidth: docEl.clientWidth, - clientHeight: docEl.clientHeight - }; - } - function visibilityHandler() { - if (isVisible(iframe, 50)) { - postMessageToIframe(iframe, 'visible', {frame: widgetElId}); - } - } - function focusHandler() { - postMessageToIframe(iframe, 'focus', {has_focus: document.hasFocus()}); - } - function postMessageHandler(event) { - if (event.source !== iframe.contentWindow || - event.origin != widgetsOrigin) { - return; - } - try { - var data = JSON.parse(event.data); - } catch(e) { - var data = {}; - } - if (data.event == 'resize') { - if (data.height) { - iframe.style.height = data.height + 'px'; - } - if (data.width) { - iframe.style.width = data.width + 'px'; - } - } - else if (data.event == 'ready') { - iframe._ready = true; - focusHandler(); - for (var i = 0; i < iframe._readyQueue.length; i++) { - var queue_item = iframe._readyQueue[i]; - postMessageToIframe(iframe, queue_item[0], queue_item[1], queue_item[2]); - } - iframe._readyQueue = []; - } - else if (data.event == 'visible_off') { - removeEvent(window, 'scroll', visibilityHandler); - removeEvent(window, 'resize', visibilityHandler); - } - else if (data.event == 'get_coords') { - postMessageToIframe(iframe, 'callback', { - _cb: data._cb, - value: getCurCoords(iframe) - }); - } - else if (data.event == 'scroll_to') { - try { - window.scrollTo(data.x || 0, data.y || 0); - } catch(e) {} - } - else if (data.event == 'auth_user') { - if (data.init) { - onInitAuthUser && onInitAuthUser(data.auth_data); - } else { - onAuthUser && onAuthUser(data.auth_data); - } - } - else if (data.event == 'unauthorized') { - onUnauth && onUnauth(); - } - else if (data.event == 'callback') { - var cb_data = null; - if (cb_data = window.Telegram.__WidgetCallbacks[data._cb]) { - if (cb_data.iframe === iframe) { - cb_data.callback(data.value); - delete window.Telegram.__WidgetCallbacks[data._cb]; - } - } else { - console.warn('Callback #' + data._cb + ' not found'); - } - } - } - var iframe = document.createElement('iframe'); - iframe.id = widgetElId; - iframe.src = src; - iframe.width = defWidth; - iframe.height = defHeight; - iframe.setAttribute('frameborder', '0'); - if (!scrollable) { - iframe.setAttribute('scrolling', 'no'); - iframe.style.overflow = 'hidden'; - } - iframe.style.colorScheme = 'light dark'; - iframe.style.border = 'none'; - for (var prop in styles) { - iframe.style[prop] = styles[prop]; - } - if (widgetEl.parentNode) { - widgetEl.parentNode.insertBefore(iframe, widgetEl); - if (widgetEl.tagName.toUpperCase() == 'BLOCKQUOTE') { - widgetEl.parentNode.removeChild(widgetEl); - } - } - iframe._ready = false; - iframe._readyQueue = []; - widgetEl._iframe = iframe; - addEvent(iframe, 'load', function() { - removeEvent(iframe, 'load', visibilityHandler); - addEvent(window, 'scroll', visibilityHandler); - addEvent(window, 'resize', visibilityHandler); - visibilityHandler(); - }); - addEvent(window, 'focus blur', focusHandler); - addEvent(window, 'message', postMessageHandler); - return iframe; - } - function isVisible(el, padding) { - var node = el, val; - var visibility = getCssProperty(node, 'visibility'); - if (visibility == 'hidden') return false; - while (node) { - if (node === document.documentElement) break; - var display = getCssProperty(node, 'display'); - if (display == 'none') return false; - var opacity = getCssProperty(node, 'opacity'); - if (opacity !== null && opacity < 0.1) return false; - node = node.parentNode; - } - if (el.getBoundingClientRect) { - padding = +padding || 0; - var rect = el.getBoundingClientRect(); - var html = document.documentElement; - if (rect.bottom < padding || - rect.right < padding || - rect.top > (window.innerHeight || html.clientHeight) - padding || - rect.left > (window.innerWidth || html.clientWidth) - padding) { - return false; - } - } - return true; - } - - function getAllWidgets() { - var widgets = []; - if (document.querySelectorAll) { - widgets = document.querySelectorAll('script[data-telegram-post],blockquote.telegram-post,script[data-telegram-discussion],script[data-telegram-login],script[data-telegram-share-url]'); - } else { - widgets = Array.prototype.slice.apply(document.getElementsByTagName('SCRIPT')); - widgets = widgets.concat(Array.prototype.slice.apply(document.getElementsByTagName('BLOCKQUOTE'))); - } - return widgets; - } - - function getWidgetInfo(el_or_id, callback) { - var e = null, iframe = null; - if (el = geById(el_or_id)) { - if (el.tagName && - el.tagName.toUpperCase() == 'IFRAME') { - iframe = el; - } else if (el._iframe) { - iframe = el._iframe; - } - if (iframe && callback) { - postMessageToIframe(iframe, 'get_info', {}, callback); - } - } - } - - function setWidgetOptions(options, el_or_id) { - var e = null, iframe = null; - if (typeof el_or_id === 'undefined') { - var widgets = getAllWidgets(); - for (var i = 0; i < widgets.length; i++) { - if (iframe = widgets[i]._iframe) { - postMessageToIframe(iframe, 'set_options', {options: options}); - } - } - } else { - if (el = geById(el_or_id)) { - if (el.tagName && - el.tagName.toUpperCase() == 'IFRAME') { - iframe = el; - } else if (el._iframe) { - iframe = el._iframe; - } - if (iframe) { - postMessageToIframe(iframe, 'set_options', {options: options}); - } - } - } - } - - if (!document.currentScript || - !initWidget(document.currentScript)) { - var widgets = getAllWidgets(); - for (var i = 0; i < widgets.length; i++) { - initWidget(widgets[i]); - } - } - - var TelegramLogin = { - popups: {}, - options: null, - auth_callback: null, - _init: function(options, auth_callback) { - TelegramLogin.options = options; - TelegramLogin.auth_callback = auth_callback; - var auth_result = haveTgAuthResult(); - if (auth_result && auth_callback) { - auth_callback(auth_result); - } - }, - _open: function(callback) { - TelegramLogin._auth(TelegramLogin.options, function(authData) { - if (TelegramLogin.auth_callback) { - TelegramLogin.auth_callback(authData); - } - if (callback) { - callback(authData); - } - }); - }, - _auth: function(options, callback) { - var bot_id = parseInt(options.bot_id); - if (!bot_id) { - throw new Error('Bot id required'); - } - var width = 550; - var height = 470; - var left = Math.max(0, (screen.width - width) / 2) + (screen.availLeft | 0), - top = Math.max(0, (screen.height - height) / 2) + (screen.availTop | 0); - var onMessage = function (event) { - try { - var data = JSON.parse(event.data); - } catch(e) { - var data = {}; - } - if (!TelegramLogin.popups[bot_id]) return; - if (event.source !== TelegramLogin.popups[bot_id].window) return; - if (data.event == 'auth_result') { - onAuthDone(data.result); - } - }; - var onAuthDone = function (authData) { - if (!TelegramLogin.popups[bot_id]) return; - if (TelegramLogin.popups[bot_id].authFinished) return; - callback && callback(authData); - TelegramLogin.popups[bot_id].authFinished = true; - removeEvent(window, 'message', onMessage); - }; - var checkClose = function(bot_id) { - if (!TelegramLogin.popups[bot_id]) return; - if (!TelegramLogin.popups[bot_id].window || - TelegramLogin.popups[bot_id].window.closed) { - return TelegramLogin.getAuthData(options, function(origin, authData) { - onAuthDone(authData); - }); - } - setTimeout(checkClose, 100, bot_id); - } - var popup_url = Telegram.Login.widgetsOrigin + '/auth?bot_id=' + encodeURIComponent(options.bot_id) + '&origin=' + encodeURIComponent(location.origin || location.protocol + '//' + location.hostname) + (options.request_access ? '&request_access=' + encodeURIComponent(options.request_access) : '') + (options.lang ? '&lang=' + encodeURIComponent(options.lang) : '') + '&return_to=' + encodeURIComponent(location.href); - var popup = window.open(popup_url, 'telegram_oauth_bot' + bot_id, 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',status=0,location=0,menubar=0,toolbar=0'); - TelegramLogin.popups[bot_id] = { - window: popup, - authFinished: false - }; - if (popup) { - addEvent(window, 'message', onMessage); - popup.focus(); - checkClose(bot_id); - } - }, - getAuthData: function(options, callback) { - var bot_id = parseInt(options.bot_id); - if (!bot_id) { - throw new Error('Bot id required'); - } - var xhr = getXHR(); - var url = Telegram.Login.widgetsOrigin + '/auth/get'; - xhr.open('POST', url); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - if (typeof xhr.responseBody == 'undefined' && xhr.responseText) { - try { - var result = JSON.parse(xhr.responseText); - } catch(e) { - var result = {}; - } - if (result.user) { - callback(result.origin, result.user); - } else { - callback(result.origin, false); - } - } else { - callback('*', false); - } - } - }; - xhr.onerror = function() { - callback('*', false); - }; - xhr.withCredentials = true; - xhr.send('bot_id=' + encodeURIComponent(options.bot_id) + (options.lang ? '&lang=' + encodeURIComponent(options.lang) : '')); - } - }; - - window.Telegram.getWidgetInfo = getWidgetInfo; - window.Telegram.setWidgetOptions = setWidgetOptions; - window.Telegram.Login = { - init: TelegramLogin._init, - open: TelegramLogin._open, - auth: TelegramLogin._auth, - widgetsOrigin: getWidgetsOrigin('https://oauth.telegram.org', 'https://oauth.tg.dev') - }; - - }(window)); -})(window); diff --git a/bot/app/web/web_server.py b/bot/app/web/web_server.py index 0c10052..5317168 100644 --- a/bot/app/web/web_server.py +++ b/bot/app/web/web_server.py @@ -1,7 +1,6 @@ import hmac import asyncio import logging -from contextlib import suppress from aiohttp import web from aiogram import Bot, Dispatcher @@ -17,8 +16,6 @@ class SecureSimpleRequestHandler(SimpleRequestHandler): return False return hmac.compare_digest(telegram_secret_token, self.secret_token) -TELEGRAM_WEB_APP_SDK_REFRESH_INTERVAL_SECONDS = 24 * 60 * 60 - def _inject_shared_instances( app: web.Application, @@ -132,13 +129,8 @@ async def build_and_start_web_app( f"AIOHTTP server started on http://{settings.WEB_SERVER_HOST}:{settings.WEB_SERVER_PORT}" ) - telegram_web_app_sdk_refresh_task = None if settings.WEBAPP_ENABLED: - from bot.app.web.subscription_webapp import ( - create_subscription_webapp_application, - refresh_telegram_login_widget_sdk, - refresh_telegram_web_app_sdk, - ) + from bot.app.web.subscription_webapp import create_subscription_webapp_application subscription_app = create_subscription_webapp_application( dp, @@ -161,25 +153,9 @@ async def build_and_start_web_app( settings.WEBAPP_SERVER_PORT, ) - async def _refresh_telegram_web_assets_forever() -> None: - while True: - await refresh_telegram_web_app_sdk() - await refresh_telegram_login_widget_sdk() - await asyncio.sleep(TELEGRAM_WEB_APP_SDK_REFRESH_INTERVAL_SECONDS) - - telegram_web_app_sdk_refresh_task = asyncio.create_task( - _refresh_telegram_web_assets_forever(), - name="TelegramWebAssetsRefreshTask", - ) - try: await asyncio.Event().wait() finally: - if telegram_web_app_sdk_refresh_task is not None: - telegram_web_app_sdk_refresh_task.cancel() - with suppress(asyncio.CancelledError): - await telegram_web_app_sdk_refresh_task - for runner in reversed(runners): try: await runner.cleanup() diff --git a/scripts/update_telegram_web_app_js.py b/scripts/update_telegram_web_app_js.py deleted file mode 100644 index d984e7f..0000000 --- a/scripts/update_telegram_web_app_js.py +++ /dev/null @@ -1,57 +0,0 @@ -from __future__ import annotations - -import argparse -import sys -from pathlib import Path -from urllib.request import Request, urlopen - - -SOURCE_URL = "https://telegram.org/js/telegram-web-app.js" -TARGET_PATH = ( - Path(__file__).resolve().parents[1] - / "bot" - / "app" - / "web" - / "templates" - / "telegram-web-app.js" -) - - -def _download(source_url: str) -> bytes: - request = Request( - source_url, - headers={ - "User-Agent": "Mozilla/5.0", - "Accept": "application/javascript,text/javascript,*/*;q=0.8", - }, - ) - with urlopen(request, timeout=30) as response: - return response.read() - - -def main() -> int: - parser = argparse.ArgumentParser( - description="Download the latest Telegram Web App SDK into the local templates directory." - ) - parser.add_argument( - "--source-url", - default=SOURCE_URL, - help="Telegram Web App SDK URL to download from.", - ) - parser.add_argument( - "--target", - type=Path, - default=TARGET_PATH, - help="Output path for the vendored SDK.", - ) - args = parser.parse_args() - - data = _download(args.source_url) - args.target.parent.mkdir(parents=True, exist_ok=True) - args.target.write_bytes(data) - print(f"Wrote {len(data)} bytes to {args.target}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/update_telegram_widget_js.py b/scripts/update_telegram_widget_js.py deleted file mode 100644 index 1e6b85c..0000000 --- a/scripts/update_telegram_widget_js.py +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import annotations - -import argparse -import sys -from pathlib import Path -from urllib.request import Request, urlopen - - -SOURCE_URL = "https://telegram.org/js/telegram-widget.js?23" -TARGET_PATH = ( - Path(__file__).resolve().parents[1] - / "bot" - / "app" - / "web" - / "templates" - / "telegram-widget.js" -) -_UNPATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org') {\n origin = default_origin;\n } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') {\n origin = dev_origin;\n }\n""" -_PATCHED_WIDGET_ORIGIN_SNIPPET = """ if (origin == 'https://telegram.org') {\n origin = default_origin;\n } else if (origin == 'https://telegram-js.azureedge.net' || origin == 'https://tg.dev') {\n origin = dev_origin;\n } else {\n origin = default_origin;\n }\n""" - - -def _download(source_url: str) -> bytes: - request = Request( - source_url, - headers={ - "User-Agent": "Mozilla/5.0", - "Accept": "application/javascript,text/javascript,*/*;q=0.8", - }, - ) - with urlopen(request, timeout=30) as response: - return response.read() - - -def _normalize_widget_sdk(data: bytes) -> bytes: - # Keep the vendored widget pointing to Telegram's OAuth host instead of the local origin. - text = data.decode("utf-8") - normalized = text.replace( - _UNPATCHED_WIDGET_ORIGIN_SNIPPET, - _PATCHED_WIDGET_ORIGIN_SNIPPET, - 1, - ) - return normalized.encode("utf-8") - - -def main() -> int: - parser = argparse.ArgumentParser( - description="Download the latest Telegram Login Widget SDK into the local templates directory." - ) - parser.add_argument( - "--source-url", - default=SOURCE_URL, - help="Telegram Login Widget SDK URL to download from.", - ) - parser.add_argument( - "--target", - type=Path, - default=TARGET_PATH, - help="Output path for the vendored SDK.", - ) - args = parser.parse_args() - - data = _normalize_widget_sdk(_download(args.source_url)) - args.target.parent.mkdir(parents=True, exist_ok=True) - args.target.write_bytes(data) - print(f"Wrote {len(data)} bytes to {args.target}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main())