// WebView (function () { var eventHandlers = {}; var locationHash = ''; try { locationHash = location.hash.toString(); } catch (e) {} var initParams = urlParseHashParams(locationHash); 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') { iFrameStyle.innerHTML = dataParsed.eventData; } else { receiveEvent(dataParsed.eventType, dataParsed.eventData); } }); iFrameStyle = document.createElement('style'); document.head.appendChild(iFrameStyle); try { window.parent.postMessage(JSON.stringify({eventType: 'iframe_ready'}), '*'); } 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=telegram-crawler_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 = ''; } 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); if (initParams.tgWebAppDebug) { console.log('[Telegram.WebView] postEvent via postMessage', eventType, eventData); } callback(); } catch (e) { callback(e); } } else { if (initParams.tgWebAppDebug) { console.log('[Telegram.WebView] postEvent', eventType, eventData); } callback({notAvailable: true}); } }; function 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; } 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 }; // 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 = '1.0'; 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); setThemeParams(theme_params); } catch (e) {} } if (initParams.tgWebAppVersion) { webAppVersion = initParams.tgWebAppVersion; } function onThemeChanged(eventType, eventData) { if (eventData.theme_params) { setThemeParams(eventData.theme_params); window.Telegram.WebApp.MainButton.setParams({ force_update: true }); 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 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) { 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); } } } 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); } function parseColorToHex(color) { color += ''; var match; if (/^#([0-9a-f]){6}$/i.test(color)) { return color.toLowerCase(); } else if (match = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color)) { return ('#' + match[1] + match[1] + match[2] + match[2] + match[3] + match[3]).toLowerCase(); } 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 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 = params.text.toString().replace(/^\s+|\s+$/g, ''); 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', 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', 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; })(); 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, '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, 'MainButton', { value: MainButton, enumerable: true }); WebApp.isVersionAtLeast = function(ver) { return versionAtLeast(ver); }; 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.openTgLink = function (url) { 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; if (isIframe || versionAtLeast('1.1')) { WebView.postEvent('web_app_open_tg_link', false, {path_full: path_full}); } else { location.href = 'https://t.me' + path_full; } }; WebApp.ready = function () { WebView.postEvent('web_app_ready'); }; WebApp.expand = function () { WebView.postEvent('web_app_expand'); }; WebApp.close = function () { WebView.postEvent('web_app_close'); }; window.Telegram.WebApp = WebApp; setViewportHeight(); window.addEventListener('resize', onWindowResize); if (isIframe) { document.addEventListener('click', linkHandler); } WebView.onEvent('theme_changed', onThemeChanged); WebView.onEvent('viewport_changed', onViewportChanged); WebView.postEvent('web_app_request_theme'); WebView.postEvent('web_app_request_viewport'); })();