UNPKG

telegram-sdk

Version:

Telegram Web App - (Uses July 7, 2024, Bot API 7.7)

1,942 lines (1,816 loc) 65.3 kB
// 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 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'; 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) {} } } 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) {} } var theme_params = Utils.sessionStorageGet('themeParams'); if (theme_params) { setThemeParams(theme_params); } if (initParams.tgWebAppVersion) { webAppVersion = initParams.tgWebAppVersion; } if (initParams.tgWebAppPlatform) { webAppPlatform = initParams.tgWebAppPlatform; } function onThemeChanged(eventType, eventData) { if (eventData.theme_params) { setThemeParams(eventData.theme_params); window.Telegram.WebApp.MainButton.setParams({}); updateBackgroundColor(); 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 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.openTgLink(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 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); } 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 - mainButtonHeight + 'px'; } else { height = mainButtonHeight ? 'calc(100vh - ' + mainButtonHeight + 'px)' : '100vh'; } if (viewportStableHeight !== false) { stable_height = viewportStableHeight - mainButtonHeight + 'px'; } else { stable_height = mainButtonHeight ? 'calc(100vh - ' + mainButtonHeight + 'px)' : '100vh'; } setCssProperty('viewport-height', height); setCssProperty('viewport-stable-height', stable_height); } 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.6')) { 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 }); } var headerColorKey = 'bg_color', headerColor = null; function getHeaderColor() { if (headerColorKey == 'secondary_bg_color') { return themeParams.secondary_bg_color } else if (headerColorKey == 'bg_color') { return themeParams.bg_color } return headerColor } 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') } headerColorKey = color_key; headerColor = head_color; updateHeaderColor(); } var appHeaderColorKey = null, appHeaderColor = null; function updateHeaderColor() { if (appHeaderColorKey != headerColorKey || appHeaderColor != headerColor) { appHeaderColorKey = headerColorKey; appHeaderColor = headerColor; if (appHeaderColor) { WebView.postEvent('web_app_set_header_color', false, { color: headerColor }); } else { WebView.postEvent('web_app_set_header_color', false, { color_key: headerColorKey }); } } } var backgroundColor = 'bg_color'; function getBackgroundColor() { if (backgroundColor == 'secondary_bg_color') { return themeParams.secondary_bg_color } else if (backgroundColor == 'bg_color') { return themeParams.bg_color } return backgroundColor } 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') } } backgroundColor = 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 }); } } 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 mainButtonHeight = 0; var MainButton = (function() { var isVisible = false; var isActive = true; var isProgressVisible = false; var buttonText = 'CONTINUE'; var buttonColor = false; var buttonTextColor = false; var mainButton = {}; Object.defineProperty(mainButton, 'text', { set: function(val) { mainButton.setParams({ text: val }); }, get: function() { return buttonText }, enumerable: true }); Object.defineProperty(mainButton, 'color', { set: function(val) { mainButton.setParams({ color: val }); }, get: function() { return buttonColor || themeParams.button_color || '#2481cc' }, enumerable: true }); Object.defineProperty(mainButton, 'textColor', { set: function(val) { mainButton.setParams({ text_color: val }); }, get: function() { return buttonTextColor || themeParams.button_text_color || '#ffffff' }, enumerable: true }); Object.defineProperty(mainButton, 'isVisible', { set: function(val) { mainButton.setParams({ is_visible: val }); }, get: function() { return isVisible }, enumerable: true }); Object.defineProperty(mainButton, 'isProgressVisible', { get: function() { return isProgressVisible }, enumerable: true }); Object.defineProperty(mainButton, 'isActive', { set: function(val) { mainButton.setParams({ is_active: val }); }, get: function() { return isActive }, enumerable: true }); var curButtonState = null; WebView.onEvent('main_button_pressed', onMainButtonPressed); var debugBtn = null, debugBtnStyle = {}; if (initParams.tgWebAppDebug) { debugBtn = document.createElement('tg-main-button'); debugBtnStyle = { font: '600 14px/18px sans-serif', display: 'none', width: '100%', height: '48px', borderRadius: '0', background: 'no-repeat right center', position: 'fixed', left: '0', right: '0', bottom: '0', margin: '0', padding: '15px 20px', textAlign: 'center', boxSizing: 'border-box', zIndex: '10000' }; for (var k in debugBtnStyle) { debugBtn.style[k] = debugBtnStyle[k]; } document.addEventListener( 'DOMContentLoaded', function onDomLoaded(event) { document.removeEventListener('DOMContentLoaded', onDomLoaded); document.body.appendChild(debugBtn); debugBtn.addEventListener('click', onMainButtonPressed, false); } ); } function onMainButtonPressed() { if (isActive) { receiveWebViewEvent('mainButtonClicked'); } } function buttonParams() { var color = mainButton.color; var text_color = mainButton.textColor; return isVisible ? { is_visible: true, is_active: isActive, is_progress_visible: isProgressVisible, text: buttonText, color: color, text_color: text_color } : { is_visible: false } } 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('web_app_setup_main_button', false, btn_params); if (initParams.tgWebAppDebug) { updateDebugButton(btn_params); } } function updateDebugButton(btn_params) { if (btn_params.is_visible) { debugBtn.style.display = 'block'; mainButtonHeight = 48; 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.style.backgroundImage = btn_params.is_progress_visible ? "url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewport%3D%220%200%2048%2048%22%20width%3D%2248px%22%20height%3D%2248px%22%3E%3Ccircle%20cx%3D%2250%25%22%20cy%3D%2250%25%22%20stroke%3D%22%23fff%22%20stroke-width%3D%222.25%22%20stroke-linecap%3D%22round%22%20fill%3D%22none%22%20stroke-dashoffset%3D%22106%22%20r%3D%229%22%20stroke-dasharray%3D%2256.52%22%20rotate%3D%22-90%22%3E%3Canimate%20attributeName%3D%22stroke-dashoffset%22%20attributeType%3D%22XML%22%20dur%3D%22360s%22%20from%3D%220%22%20to%3D%2212500%22%20repeatCount%3D%22indefinite%22%3E%3C%2Fanimate%3E%3CanimateTransform%20attributeName%3D%22transform%22%20attributeType%3D%22XML%22%20type%3D%22rotate%22%20dur%3D%221s%22%20from%3D%22-90%2024%2024%22%20to%3D%22630%2024%2024%22%20repeatCount%3D%22indefinite%22%3E%3C%2FanimateTransform%3E%3C%2Fcircle%3E%3C%2Fsvg%3E')" : 'none'; debugBtn.style.backgroundColor = btn_params.color; debugBtn.style.color = btn_params.text_color; } else { debugBtn.style.display = 'none'; mainButtonHeight = 0; } if (document.documentElement) { document.documentElement.style.boxSizing = 'border-box'; document.documentElement.style.paddingBottom = mainButtonHeight + 'px'; } setViewportHeight(); } function setParams(params) { if (typeof params.text !== 'undefined') { var text = strTrim(params.text); if (!text.length) { console.error( '[Telegram.WebApp] Main button text is required', params.text ); throw Error('WebAppMainButtonParamInvalid') } if (text.length > 64) { console.error('[Telegram.WebApp] Main button text is too long', text); throw Error('WebAppMainButtonParamInvalid') } 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] Main button color format is invalid', params.color ); throw Error('WebAppMainButtonParamInvalid') } 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] Main button text color format is invalid', params.text_color ); throw Error('WebAppMainButtonParamInvalid') } buttonTextColor = text_color; } } if (typeof params.is_visible !== 'undefined') { if (params.is_visible && !mainButton.text.length) { console.error('[Telegram.WebApp] Main button text is required'); throw Error('WebAppMainButtonParamInvalid') } isVisible = !!params.is_visible; } if (typeof params.is_active !== 'undefined') { isActive = !!params.is_active; } updateButton(); return mainButton } mainButton.setText = function(text) { return mainButton.setParams({ text: text }) }; mainButton.onClick = function(callback) { onWebViewEvent('mainButtonClicked', callback); return mainButton }; mainButton.offClick = function(callback) { offWebViewEvent('mainButtonClicked', callback); return mainButton }; mainButton.show = function() { return mainButton.setParams({ is_visible: true }) }; mainButton.hide = function() { return mainButton.setParams({ is_visible: false }) }; mainButton.enable = function() { return mainButton.setParams({ is_active: true }) }; mainButton.disable = function() { return mainButton.setParams({ is_active: false }) }; mainButton.showProgress = function(leaveActive) { isActive = !!leaveActive; isProgressVisible = true; updateButton(); return mainButton }; mainButton.hideProgress = function() { if (!mainButton.isActive) { isActive = true; } isProgressVisible = false; updateButton(); return mainButton }; mainButton.setParams = setParams; return mainButton })(); 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') ; 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 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(); } } 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 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 && 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); } } } 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) - mainButtonHeight ) }, enumerable: true }); Object.defineProperty(WebApp, 'viewportStableHeight', { get: function() { return ( (viewportStableHeight === false ? window.innerHeight : viewportStableHeight) - mainButtonHeight ) }, 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, '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, 'BackButton', { value: BackButton, enumerable: true }); Object.defineProperty(WebApp, 'MainButton', { value: MainButton, enumer