Script

    1// WebView
    2(function () {
    3    var eventHandlers = {};
    4
    5    var locationHash = '';
    6    try {
    7        locationHash = location.hash.toString();
    8    } catch (e) { }
    9
    10    var initParams = urlParseHashParams(locationHash);
    11    var storedParams = sessionStorageGet('initParams');
    12    if (storedParams) {
    13        for (var key in storedParams) {
    14            if (typeof initParams[key] === 'undefined') {
    15                initParams[key] = storedParams[key];
    16            }
    17        }
    18    }
    19    sessionStorageSet('initParams', initParams);
    20
    21    var isIframe = false, iFrameStyle;
    22    try {
    23        isIframe = (window.parent != null && window != window.parent);
    24        if (isIframe) {
    25            window.addEventListener('message', function (event) {
    26                if (event.source !== window.parent) return;
    27                try {
    28                    var dataParsed = JSON.parse(event.data);
    29                } catch (e) {
    30                    return;
    31                }
    32                if (!dataParsed || !dataParsed.eventType) {
    33                    return;
    34                }
    35                if (dataParsed.eventType == 'set_custom_style') {
    36                    if (event.origin === 'https://web.telegram.org') {
    37                        iFrameStyle.innerHTML = dataParsed.eventData;
    38                    }
    39                } else if (dataParsed.eventType == 'reload_iframe') {
    40                    try {
    41                        window.parent.postMessage(JSON.stringify({ eventType: 'iframe_will_reload' }), '*');
    42                    } catch (e) { }
    43                    location.reload();
    44                } else {
    45                    receiveEvent(dataParsed.eventType, dataParsed.eventData);
    46                }
    47            });
    48            iFrameStyle = document.createElement('style');
    49            document.head.appendChild(iFrameStyle);
    50            try {
    51                window.parent.postMessage(JSON.stringify({ eventType: 'iframe_ready', eventData: { reload_supported: true } }), '*');
    52            } catch (e) { }
    53        }
    54    } catch (e) { }
    55
    56    function urlSafeDecode(urlencoded) {
    57        try {
    58            urlencoded = urlencoded.replace(/+/g, '%20');
    59            return decodeURIComponent(urlencoded);
    60        } catch (e) {
    61            return urlencoded;
    62        }
    63    }
    64
    65    function urlParseHashParams(locationHash) {
    66        locationHash = locationHash.replace(/^#/, '');
    67        var params = {};
    68        if (!locationHash.length) {
    69            return params;
    70        }
    71        if (locationHash.indexOf('=') < 0 && locationHash.indexOf('?') < 0) {
    72            params._path = urlSafeDecode(locationHash);
    73            return params;
    74        }
    75        var qIndex = locationHash.indexOf('?');
    76        if (qIndex >= 0) {
    77            var pathParam = locationHash.substr(0, qIndex);
    78            params._path = urlSafeDecode(pathParam);
    79            locationHash = locationHash.substr(qIndex + 1);
    80        }
    81        var query_params = urlParseQueryString(locationHash);
    82        for (var k in query_params) {
    83            params[k] = query_params[k];
    84        }
    85        return params;
    86    }
    87
    88    function urlParseQueryString(queryString) {
    89        var params = {};
    90        if (!queryString.length) {
    91            return params;
    92        }
    93        var queryStringParams = queryString.split('&');
    94        var i, param, paramName, paramValue;
    95        for (i = 0; i < queryStringParams.length; i++) {
    96            param = queryStringParams[i].split('=');
    97            paramName = urlSafeDecode(param[0]);
    98            paramValue = param[1] == null ? null : urlSafeDecode(param[1]);
    99            params[paramName] = paramValue;
    100        }
    101        return params;
    102    }
    103
    104    // Telegram apps will implement this logic to add service params (e.g. tgShareScoreUrl) to game URL
    105    function urlAppendHashParams(url, addHash) {
    106        // url looks like 'https://game.com/path?query=1#hash'
    107        // addHash looks like 'tgShareScoreUrl=' + encodeURIComponent('tgb://share_game_score?hash=very_long_hash123')
    108
    109        var ind = url.indexOf('#');
    110        if (ind < 0) {
    111            // https://game.com/path -> https://game.com/path#tgShareScoreUrl=etc
    112            return url + '#' + addHash;
    113        }
    114        var curHash = url.substr(ind + 1);
    115        if (curHash.indexOf('=') >= 0 || curHash.indexOf('?') >= 0) {
    116            // https://game.com/#hash=1 -> https://game.com/#hash=1&tgShareScoreUrl=etc
    117            // https://game.com/#path?query -> https://game.com/#path?query&tgShareScoreUrl=etc
    118            return url + '&' + addHash;
    119        }
    120        // https://game.com/#hash -> https://game.com/#hash?tgShareScoreUrl=etc
    121        if (curHash.length > 0) {
    122            return url + '?' + addHash;
    123        }
    124        // https://game.com/# -> https://game.com/#tgShareScoreUrl=etc
    125        return url + addHash;
    126    }
    127
    128    function postEvent(eventType, callback, eventData) {
    129        if (!callback) {
    130            callback = function () { };
    131        }
    132        if (eventData === undefined) {
    133            eventData = '';
    134        }
    135        console.log('[Telegram.WebView] > postEvent', eventType, eventData);
    136
    137        if (window.TelegramWebviewProxy !== undefined) {
    138            TelegramWebviewProxy.postEvent(eventType, JSON.stringify(eventData));
    139            callback();
    140        }
    141        else if (window.external && 'notify' in window.external) {
    142            window.external.notify(JSON.stringify({ eventType: eventType, eventData: eventData }));
    143            callback();
    144        }
    145        else if (isIframe) {
    146            try {
    147                var trustedTarget = 'https://web.telegram.org';
    148                // For now we don't restrict target, for testing purposes
    149                trustedTarget = '*';
    150                window.parent.postMessage(JSON.stringify({ eventType: eventType, eventData: eventData }), trustedTarget);
    151                callback();
    152            } catch (e) {
    153                callback(e);
    154            }
    155        }
    156        else {
    157            callback({ notAvailable: true });
    158        }
    159    };
    160
    161    function receiveEvent(eventType, eventData) {
    162        console.log('[Telegram.WebView] < receiveEvent', eventType, eventData);
    163        callEventCallbacks(eventType, function (callback) {
    164            callback(eventType, eventData);
    165        });
    166    }
    167
    168    function callEventCallbacks(eventType, func) {
    169        var curEventHandlers = eventHandlers[eventType];
    170        if (curEventHandlers === undefined ||
    171            !curEventHandlers.length) {
    172            return;
    173        }
    174        for (var i = 0; i < curEventHandlers.length; i++) {
    175            try {
    176                func(curEventHandlers[i]);
    177            } catch (e) { }
    178        }
    179    }
    180
    181    function onEvent(eventType, callback) {
    182        if (eventHandlers[eventType] === undefined) {
    183            eventHandlers[eventType] = [];
    184        }
    185        var index = eventHandlers[eventType].indexOf(callback);
    186        if (index === -1) {
    187            eventHandlers[eventType].push(callback);
    188        }
    189    };
    190
    191    function offEvent(eventType, callback) {
    192        if (eventHandlers[eventType] === undefined) {
    193            return;
    194        }
    195        var index = eventHandlers[eventType].indexOf(callback);
    196        if (index === -1) {
    197            return;
    198        }
    199        eventHandlers[eventType].splice(index, 1);
    200    };
    201
    202    function openProtoUrl(url) {
    203        if (!url.match(/^(web+)?tgb?://./)) {
    204            return false;
    205        }
    206        var useIframe = navigator.userAgent.match(/iOS|iPhone OS|iPhone|iPod|iPad/i) ? true : false;
    207        if (useIframe) {
    208            var iframeContEl = document.getElementById('tgme_frame_cont') || document.body;
    209            var iframeEl = document.createElement('iframe');
    210            iframeContEl.appendChild(iframeEl);
    211            var pageHidden = false;
    212            var enableHidden = function () {
    213                pageHidden = true;
    214            };
    215            window.addEventListener('pagehide', enableHidden, false);
    216            window.addEventListener('blur', enableHidden, false);
    217            if (iframeEl !== null) {
    218                iframeEl.src = url;
    219            }
    220            setTimeout(function () {
    221                if (!pageHidden) {
    222                    window.location = url;
    223                }
    224                window.removeEventListener('pagehide', enableHidden, false);
    225                window.removeEventListener('blur', enableHidden, false);
    226            }, 2000);
    227        }
    228        else {
    229            window.location = url;
    230        }
    231        return true;
    232    }
    233
    234    function sessionStorageSet(key, value) {
    235        try {
    236            window.sessionStorage.setItem('__telegram__' + key, JSON.stringify(value));
    237            return true;
    238        } catch (e) { }
    239        return false;
    240    }
    241    function sessionStorageGet(key) {
    242        try {
    243            return JSON.parse(window.sessionStorage.getItem('__telegram__' + key));
    244        } catch (e) { }
    245        return null;
    246    }
    247
    248    if (!window.Telegram) {
    249        window.Telegram = {};
    250    }
    251    window.Telegram.WebView = {
    252        initParams: initParams,
    253        isIframe: isIframe,
    254        onEvent: onEvent,
    255        offEvent: offEvent,
    256        postEvent: postEvent,
    257        receiveEvent: receiveEvent,
    258        callEventCallbacks: callEventCallbacks
    259    };
    260
    261    window.Telegram.Utils = {
    262        urlSafeDecode: urlSafeDecode,
    263        urlParseQueryString: urlParseQueryString,
    264        urlParseHashParams: urlParseHashParams,
    265        urlAppendHashParams: urlAppendHashParams,
    266        sessionStorageSet: sessionStorageSet,
    267        sessionStorageGet: sessionStorageGet
    268    };
    269
    270    // For Windows Phone app
    271    window.TelegramGameProxy_receiveEvent = receiveEvent;
    272
    273    // App backward compatibility
    274    window.TelegramGameProxy = {
    275        receiveEvent: receiveEvent
    276    };
    277})();
    278
    279// WebApp
    280(function () {
    281    var Utils = window.Telegram.Utils;
    282    var WebView = window.Telegram.WebView;
    283    var initParams = WebView.initParams;
    284    var isIframe = WebView.isIframe;
    285
    286    var WebApp = {};
    287    var webAppInitData = '', webAppInitDataUnsafe = {};
    288    var themeParams = {}, colorScheme = 'light';
    289    var webAppVersion = '6.0';
    290    var webAppPlatform = 'android';
    291
    292    if (initParams.tgWebAppData && initParams.tgWebAppData.length) {
    293        webAppInitData = initParams.tgWebAppData;
    294        webAppInitDataUnsafe = Utils.urlParseQueryString(webAppInitData);
    295        for (var key in webAppInitDataUnsafe) {
    296            var val = webAppInitDataUnsafe[key];
    297            try {
    298                if (val.substr(0, 1) == '{' && val.substr(-1) == '}' ||
    299                    val.substr(0, 1) == '[' && val.substr(-1) == ']') {
    300                    webAppInitDataUnsafe[key] = JSON.parse(val);
    301                }
    302            } catch (e) { }
    303        }
    304    }
    305    if (initParams.tgWebAppThemeParams && initParams.tgWebAppThemeParams.length) {
    306        var themeParamsRaw = initParams.tgWebAppThemeParams;
    307        try {
    308            var theme_params = JSON.parse(themeParamsRaw);
    309            if (theme_params) {
    310                setThemeParams(theme_params);
    311            }
    312        } catch (e) { }
    313    }
    314    var theme_params = Utils.sessionStorageGet('themeParams');
    315    if (theme_params) {
    316        setThemeParams(theme_params);
    317    }
    318    if (initParams.tgWebAppVersion) {
    319        webAppVersion = initParams.tgWebAppVersion;
    320    }
    321    if (initParams.tgWebAppPlatform) {
    322        webAppPlatform = "android";
    323    }
    324
    325    function onThemeChanged(eventType, eventData) {
    326        if (eventData.theme_params) {
    327            setThemeParams(eventData.theme_params);
    328            window.Telegram.WebApp.MainButton.setParams({});
    329            window.Telegram.WebApp.SecondaryButton.setParams({});
    330            updateHeaderColor();
    331            updateBackgroundColor();
    332            updateBottomBarColor();
    333            receiveWebViewEvent('themeChanged');
    334        }
    335    }
    336
    337    var lastWindowHeight = window.innerHeight;
    338    function onViewportChanged(eventType, eventData) {
    339        if (eventData.height) {
    340            window.removeEventListener('resize', onWindowResize);
    341            setViewportHeight(eventData);
    342        }
    343    }
    344
    345    function onWindowResize(e) {
    346        if (lastWindowHeight != window.innerHeight) {
    347            lastWindowHeight = window.innerHeight;
    348            receiveWebViewEvent('viewportChanged', {
    349                isStateStable: true
    350            });
    351        }
    352    }
    353
    354    function linkHandler(e) {
    355        if (e.metaKey || e.ctrlKey) return;
    356        var el = e.target;
    357        while (el.tagName != 'A' && el.parentNode) {
    358            el = el.parentNode;
    359        }
    360        if (el.tagName == 'A' &&
    361            el.target != '_blank' &&
    362            (el.protocol == 'http:' || el.protocol == 'https:') &&
    363            el.hostname == 't.me') {
    364            WebApp.openTgLink(el.href);
    365            e.preventDefault();
    366        }
    367    }
    368
    369    function strTrim(str) {
    370        return str.toString().replace(/^s+|s+$/g, '');
    371    }
    372
    373    function receiveWebViewEvent(eventType) {
    374        var args = Array.prototype.slice.call(arguments);
    375        eventType = args.shift();
    376        WebView.callEventCallbacks('webview:' + eventType, function (callback) {
    377            callback.apply(WebApp, args);
    378        });
    379    }
    380
    381    function onWebViewEvent(eventType, callback) {
    382        WebView.onEvent('webview:' + eventType, callback);
    383    };
    384
    385    function offWebViewEvent(eventType, callback) {
    386        WebView.offEvent('webview:' + eventType, callback);
    387    };
    388
    389    function setCssProperty(name, value) {
    390        var root = document.documentElement;
    391        if (root && root.style && root.style.setProperty) {
    392            root.style.setProperty('--tg-' + name, value);
    393        }
    394    }
    395
    396    function setThemeParams(theme_params) {
    397        // temp iOS fix
    398        if (theme_params.bg_color == '#1c1c1d' &&
    399            theme_params.bg_color == theme_params.secondary_bg_color) {
    400            theme_params.secondary_bg_color = '#2c2c2e';
    401        }
    402        var color;
    403        for (var key in theme_params) {
    404            if (color = parseColorToHex(theme_params[key])) {
    405                themeParams[key] = color;
    406                if (key == 'bg_color') {
    407                    colorScheme = isColorDark(color) ? 'dark' : 'light'
    408                    setCssProperty('color-scheme', colorScheme);
    409                }
    410                key = 'theme-' + key.split('_').join('-');
    411                setCssProperty(key, color);
    412            }
    413        }
    414        Utils.sessionStorageSet('themeParams', themeParams);
    415    }
    416
    417    var webAppCallbacks = {};
    418    function generateCallbackId(len) {
    419        var tries = 100;
    420        while (--tries) {
    421            var id = '', chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', chars_len = chars.length;
    422            for (var i = 0; i < len; i++) {
    423                id += chars[Math.floor(Math.random() * chars_len)];
    424            }
    425            if (!webAppCallbacks[id]) {
    426                webAppCallbacks[id] = {};
    427                return id;
    428            }
    429        }
    430        throw Error('WebAppCallbackIdGenerateFailed');
    431    }
    432
    433    var viewportHeight = false, viewportStableHeight = false, isExpanded = true;
    434    function setViewportHeight(data) {
    435        if (typeof data !== 'undefined') {
    436            isExpanded = !!data.is_expanded;
    437            viewportHeight = data.height;
    438            if (data.is_state_stable) {
    439                viewportStableHeight = data.height;
    440            }
    441            receiveWebViewEvent('viewportChanged', {
    442                isStateStable: !!data.is_state_stable
    443            });
    444        }
    445        var height, stable_height;
    446        if (viewportHeight !== false) {
    447            height = (viewportHeight - bottomBarHeight) + 'px';
    448        } else {
    449            height = bottomBarHeight ? 'calc(100vh - ' + bottomBarHeight + 'px)' : '100vh';
    450        }
    451        if (viewportStableHeight !== false) {
    452            stable_height = (viewportStableHeight - bottomBarHeight) + 'px';
    453        } else {
    454            stable_height = bottomBarHeight ? 'calc(100vh - ' + bottomBarHeight + 'px)' : '100vh';
    455        }
    456        setCssProperty('viewport-height', height);
    457        setCssProperty('viewport-stable-height', stable_height);
    458    }
    459
    460    var isClosingConfirmationEnabled = false;
    461    function setClosingConfirmation(need_confirmation) {
    462        if (!versionAtLeast('6.2')) {
    463            console.warn('[Telegram.WebApp] Closing confirmation is not supported in version ' + webAppVersion);
    464            return;
    465        }
    466        isClosingConfirmationEnabled = !!need_confirmation;
    467        WebView.postEvent('web_app_setup_closing_behavior', false, { need_confirmation: isClosingConfirmationEnabled });
    468    }
    469
    470    var isVerticalSwipesEnabled = true;
    471    function toggleVerticalSwipes(enable_swipes) {
    472        if (!versionAtLeast('7.7')) {
    473            console.warn('[Telegram.WebApp] Changing swipes behavior is not supported in version ' + webAppVersion);
    474            return;
    475        }
    476        isVerticalSwipesEnabled = !!enable_swipes;
    477        WebView.postEvent('web_app_setup_swipe_behavior', false, { allow_vertical_swipe: isVerticalSwipesEnabled });
    478    }
    479
    480    var headerColorKey = 'bg_color', headerColor = null;
    481    function getHeaderColor() {
    482        if (headerColorKey == 'secondary_bg_color') {
    483            return themeParams.secondary_bg_color;
    484        } else if (headerColorKey == 'bg_color') {
    485            return themeParams.bg_color;
    486        }
    487        return headerColor;
    488    }
    489    function setHeaderColor(color) {
    490        if (!versionAtLeast('6.1')) {
    491            console.warn('[Telegram.WebApp] Header color is not supported in version ' + webAppVersion);
    492            return;
    493        }
    494        if (!versionAtLeast('6.9')) {
    495            if (themeParams.bg_color &&
    496                themeParams.bg_color == color) {
    497                color = 'bg_color';
    498            } else if (themeParams.secondary_bg_color &&
    499                themeParams.secondary_bg_color == color) {
    500                color = 'secondary_bg_color';
    501            }
    502        }
    503        var head_color = null, color_key = null;
    504        if (color == 'bg_color' || color == 'secondary_bg_color') {
    505            color_key = color;
    506        } else if (versionAtLeast('6.9')) {
    507            head_color = parseColorToHex(color);
    508            if (!head_color) {
    509                console.error('[Telegram.WebApp] Header color format is invalid', color);
    510                throw Error('WebAppHeaderColorInvalid');
    511            }
    512        }
    513        if (!versionAtLeast('6.9') &&
    514            color_key != 'bg_color' &&
    515            color_key != 'secondary_bg_color') {
    516            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);
    517            throw Error('WebAppHeaderColorKeyInvalid');
    518        }
    519        headerColorKey = color_key;
    520        headerColor = head_color;
    521        updateHeaderColor();
    522    }
    523    var appHeaderColorKey = null, appHeaderColor = null;
    524    function updateHeaderColor() {
    525        if (appHeaderColorKey != headerColorKey ||
    526            appHeaderColor != headerColor) {
    527            appHeaderColorKey = headerColorKey;
    528            appHeaderColor = headerColor;
    529            if (appHeaderColor) {
    530                WebView.postEvent('web_app_set_header_color', false, { color: headerColor });
    531            } else {
    532                WebView.postEvent('web_app_set_header_color', false, { color_key: headerColorKey });
    533            }
    534        }
    535    }
    536
    537    var backgroundColor = 'bg_color';
    538    function getBackgroundColor() {
    539        if (backgroundColor == 'secondary_bg_color') {
    540            return themeParams.secondary_bg_color;
    541        } else if (backgroundColor == 'bg_color') {
    542            return themeParams.bg_color;
    543        }
    544        return backgroundColor;
    545    }
    546    function setBackgroundColor(color) {
    547        if (!versionAtLeast('6.1')) {
    548            console.warn('[Telegram.WebApp] Background color is not supported in version ' + webAppVersion);
    549            return;
    550        }
    551        var bg_color;
    552        if (color == 'bg_color' || color == 'secondary_bg_color') {
    553            bg_color = color;
    554        } else {
    555            bg_color = parseColorToHex(color);
    556            if (!bg_color) {
    557                console.error('[Telegram.WebApp] Background color format is invalid', color);
    558                throw Error('WebAppBackgroundColorInvalid');
    559            }
    560        }
    561        backgroundColor = bg_color;
    562        updateBackgroundColor();
    563    }
    564    var appBackgroundColor = null;
    565    function updateBackgroundColor() {
    566        var color = getBackgroundColor();
    567        if (appBackgroundColor != color) {
    568            appBackgroundColor = color;
    569            WebView.postEvent('web_app_set_background_color', false, { color: color });
    570        }
    571    }
    572
    573    var bottomBarColor = 'bottom_bar_bg_color';
    574    function getBottomBarColor() {
    575        if (bottomBarColor == 'bottom_bar_bg_color') {
    576            return themeParams.bottom_bar_bg_color || themeParams.secondary_bg_color || '#ffffff';
    577        } else if (bottomBarColor == 'secondary_bg_color') {
    578            return themeParams.secondary_bg_color;
    579        } else if (bottomBarColor == 'bg_color') {
    580            return themeParams.bg_color;
    581        }
    582        return bottomBarColor;
    583    }
    584    function setBottomBarColor(color) {
    585        if (!versionAtLeast('7.10')) {
    586            console.warn('[Telegram.WebApp] Bottom bar color is not supported in version ' + webAppVersion);
    587            return;
    588        }
    589        var bg_color;
    590        if (color == 'bg_color' || color == 'secondary_bg_color' || color == 'bottom_bar_bg_color') {
    591            bg_color = color;
    592        } else {
    593            bg_color = parseColorToHex(color);
    594            if (!bg_color) {
    595                console.error('[Telegram.WebApp] Bottom bar color format is invalid', color);
    596                throw Error('WebAppBottomBarColorInvalid');
    597            }
    598        }
    599        bottomBarColor = bg_color;
    600        updateBottomBarColor();
    601        window.Telegram.WebApp.SecondaryButton.setParams({});
    602    }
    603    var appBottomBarColor = null;
    604    function updateBottomBarColor() {
    605        var color = getBottomBarColor();
    606        if (appBottomBarColor != color) {
    607            appBottomBarColor = color;
    608            WebView.postEvent('web_app_set_bottom_bar_color', false, { color: color });
    609        }
    610        if (initParams.tgWebAppDebug) {
    611            updateDebugBottomBar();
    612        }
    613    }
    614
    615
    616    function parseColorToHex(color) {
    617        color += '';
    618        var match;
    619        if (match = /^s*#([0-9a-f]{6})s*$/i.exec(color)) {
    620            return '#' + match[1].toLowerCase();
    621        }
    622        else if (match = /^s*#([0-9a-f])([0-9a-f])([0-9a-f])s*$/i.exec(color)) {
    623            return ('#' + match[1] + match[1] + match[2] + match[2] + match[3] + match[3]).toLowerCase();
    624        }
    625        else if (match = /^s*rgba?((d+),s*(d+),s*(d+)(?:,s*(d+.{0,1}d*))?)s*$/.exec(color)) {
    626            var r = parseInt(match[1]), g = parseInt(match[2]), b = parseInt(match[3]);
    627            r = (r < 16 ? '0' : '') + r.toString(16);
    628            g = (g < 16 ? '0' : '') + g.toString(16);
    629            b = (b < 16 ? '0' : '') + b.toString(16);
    630            return '#' + r + g + b;
    631        }
    632        return false;
    633    }
    634
    635    function isColorDark(rgb) {
    636        rgb = rgb.replace(/[s#]/g, '');
    637        if (rgb.length == 3) {
    638            rgb = rgb[0] + rgb[0] + rgb[1] + rgb[1] + rgb[2] + rgb[2];
    639        }
    640        var r = parseInt(rgb.substr(0, 2), 16);
    641        var g = parseInt(rgb.substr(2, 2), 16);
    642        var b = parseInt(rgb.substr(4, 2), 16);
    643        var hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b));
    644        return hsp < 120;
    645    }
    646
    647    function versionCompare(v1, v2) {
    648        if (typeof v1 !== 'string') v1 = '';
    649        if (typeof v2 !== 'string') v2 = '';
    650        v1 = v1.replace(/^s+|s+$/g, '').split('.');
    651        v2 = v2.replace(/^s+|s+$/g, '').split('.');
    652        var a = Math.max(v1.length, v2.length), i, p1, p2;
    653        for (i = 0; i < a; i++) {
    654            p1 = parseInt(v1[i]) || 0;
    655            p2 = parseInt(v2[i]) || 0;
    656            if (p1 == p2) continue;
    657            if (p1 > p2) return 1;
    658            return -1;
    659        }
    660        return 0;
    661    }
    662
    663    function versionAtLeast(ver) {
    664        return versionCompare(webAppVersion, ver) >= 0;
    665    }
    666
    667    function byteLength(str) {
    668        if (window.Blob) {
    669            try { return new Blob([str]).size; } catch (e) { }
    670        }
    671        var s = str.length;
    672        for (var i = str.length - 1; i >= 0; i--) {
    673            var code = str.charCodeAt(i);
    674            if (code > 0x7f && code <= 0x7ff) s++;
    675            else if (code > 0x7ff && code <= 0xffff) s += 2;
    676            if (code >= 0xdc00 && code <= 0xdfff) i--;
    677        }
    678        return s;
    679    }
    680
    681    var BackButton = (function () {
    682        var isVisible = false;
    683
    684        var backButton = {};
    685        Object.defineProperty(backButton, 'isVisible', {
    686            set: function (val) { setParams({ is_visible: val }); },
    687            get: function () { return isVisible; },
    688            enumerable: true
    689        });
    690
    691        var curButtonState = null;
    692
    693        WebView.onEvent('back_button_pressed', onBackButtonPressed);
    694
    695        function onBackButtonPressed() {
    696            receiveWebViewEvent('backButtonClicked');
    697        }
    698
    699        function buttonParams() {
    700            return { is_visible: isVisible };
    701        }
    702
    703        function buttonState(btn_params) {
    704            if (typeof btn_params === 'undefined') {
    705                btn_params = buttonParams();
    706            }
    707            return JSON.stringify(btn_params);
    708        }
    709
    710        function buttonCheckVersion() {
    711            if (!versionAtLeast('6.1')) {
    712                console.warn('[Telegram.WebApp] BackButton is not supported in version ' + webAppVersion);
    713                return false;
    714            }
    715            return true;
    716        }
    717
    718        function updateButton() {
    719            var btn_params = buttonParams();
    720            var btn_state = buttonState(btn_params);
    721            if (curButtonState === btn_state) {
    722                return;
    723            }
    724            curButtonState = btn_state;
    725            WebView.postEvent('web_app_setup_back_button', false, btn_params);
    726        }
    727
    728        function setParams(params) {
    729            if (!buttonCheckVersion()) {
    730                return backButton;
    731            }
    732            if (typeof params.is_visible !== 'undefined') {
    733                isVisible = !!params.is_visible;
    734            }
    735            updateButton();
    736            return backButton;
    737        }
    738
    739        backButton.onClick = function (callback) {
    740            if (buttonCheckVersion()) {
    741                onWebViewEvent('backButtonClicked', callback);
    742            }
    743            return backButton;
    744        };
    745        backButton.offClick = function (callback) {
    746            if (buttonCheckVersion()) {
    747                offWebViewEvent('backButtonClicked', callback);
    748            }
    749            return backButton;
    750        };
    751        backButton.show = function () {
    752            return setParams({ is_visible: true });
    753        };
    754        backButton.hide = function () {
    755            return setParams({ is_visible: false });
    756        };
    757        return backButton;
    758    })();
    759
    760    var debugBottomBar = null, debugBottomBarBtns = {}, bottomBarHeight = 0;
    761    if (initParams.tgWebAppDebug) {
    762        debugBottomBar = document.createElement('tg-bottom-bar');
    763        var debugBottomBarStyle = {
    764            display: 'flex',
    765            gap: '7px',
    766            font: '600 14px/18px sans-serif',
    767            width: '100%',
    768            background: getBottomBarColor(),
    769            position: 'fixed',
    770            left: '0',
    771            right: '0',
    772            bottom: '0',
    773            margin: '0',
    774            padding: '7px',
    775            textAlign: 'center',
    776            boxSizing: 'border-box',
    777            zIndex: '10000'
    778        };
    779        for (var k in debugBottomBarStyle) {
    780            debugBottomBar.style[k] = debugBottomBarStyle[k];
    781        }
    782        document.addEventListener('DOMContentLoaded', function onDomLoaded(event) {
    783            document.removeEventListener('DOMContentLoaded', onDomLoaded);
    784            document.body.appendChild(debugBottomBar);
    785        });
    786        var animStyle = document.createElement('style');
    787        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%}}';
    788        debugBottomBar.appendChild(animStyle);
    789    }
    790    function updateDebugBottomBar() {
    791        var mainBtn = debugBottomBarBtns.main._bottomButton;
    792        var secondaryBtn = debugBottomBarBtns.secondary._bottomButton;
    793        if (mainBtn.isVisible || secondaryBtn.isVisible) {
    794            debugBottomBar.style.display = 'flex';
    795            bottomBarHeight = 58;
    796            if (mainBtn.isVisible && secondaryBtn.isVisible) {
    797                if (secondaryBtn.position == 'top') {
    798                    debugBottomBar.style.flexDirection = 'column-reverse';
    799                    bottomBarHeight += 51;
    800                } else if (secondaryBtn.position == 'bottom') {
    801                    debugBottomBar.style.flexDirection = 'column';
    802                    bottomBarHeight += 51;
    803                } else if (secondaryBtn.position == 'left') {
    804                    debugBottomBar.style.flexDirection = 'row-reverse';
    805                } else if (secondaryBtn.position == 'right') {
    806                    debugBottomBar.style.flexDirection = 'row';
    807                }
    808            }
    809        } else {
    810            debugBottomBar.style.display = 'none';
    811            bottomBarHeight = 0;
    812        }
    813        debugBottomBar.style.background = getBottomBarColor();
    814        if (document.documentElement) {
    815            document.documentElement.style.boxSizing = 'border-box';
    816            document.documentElement.style.paddingBottom = bottomBarHeight + 'px';
    817        }
    818        setViewportHeight();
    819    }
    820
    821
    822    var BottomButtonConstructor = function (type) {
    823        var isMainButton = (type == 'main');
    824        if (isMainButton) {
    825            var setupFnName = 'web_app_setup_main_button';
    826            var tgEventName = 'main_button_pressed';
    827            var webViewEventName = 'mainButtonClicked';
    828            var buttonTextDefault = 'Continue';
    829            var buttonColorDefault = function () { return themeParams.button_color || '#2481cc'; };
    830            var buttonTextColorDefault = function () { return themeParams.button_text_color || '#ffffff'; };
    831        } else {
    832            var setupFnName = 'web_app_setup_secondary_button';
    833            var tgEventName = 'secondary_button_pressed';
    834            var webViewEventName = 'secondaryButtonClicked';
    835            var buttonTextDefault = 'Cancel';
    836            var buttonColorDefault = function () { return getBottomBarColor(); };
    837            var buttonTextColorDefault = function () { return themeParams.button_color || '#2481cc'; };
    838        }
    839
    840        var isVisible = false;
    841        var isActive = true;
    842        var hasShineEffect = false;
    843        var isProgressVisible = false;
    844        var buttonType = type;
    845        var buttonText = buttonTextDefault;
    846        var buttonColor = false;
    847        var buttonTextColor = false;
    848        var buttonPosition = 'left';
    849
    850        var bottomButton = {};
    851        Object.defineProperty(bottomButton, 'type', {
    852            get: function () { return buttonType; },
    853            enumerable: true
    854        });
    855        Object.defineProperty(bottomButton, 'text', {
    856            set: function (val) { bottomButton.setParams({ text: val }); },
    857            get: function () { return buttonText; },
    858            enumerable: true
    859        });
    860        Object.defineProperty(bottomButton, 'color', {
    861            set: function (val) { bottomButton.setParams({ color: val }); },
    862            get: function () { return buttonColor || buttonColorDefault(); },
    863            enumerable: true
    864        });
    865        Object.defineProperty(bottomButton, 'textColor', {
    866            set: function (val) { bottomButton.setParams({ text_color: val }); },
    867            get: function () { return buttonTextColor || buttonTextColorDefault(); },
    868            enumerable: true
    869        });
    870        Object.defineProperty(bottomButton, 'isVisible', {
    871            set: function (val) { bottomButton.setParams({ is_visible: val }); },
    872            get: function () { return isVisible; },
    873            enumerable: true
    874        });
    875        Object.defineProperty(bottomButton, 'isProgressVisible', {
    876            get: function () { return isProgressVisible; },
    877            enumerable: true
    878        });
    879        Object.defineProperty(bottomButton, 'isActive', {
    880            set: function (val) { bottomButton.setParams({ is_active: val }); },
    881            get: function () { return isActive; },
    882            enumerable: true
    883        });
    884        Object.defineProperty(bottomButton, 'hasShineEffect', {
    885            set: function (val) { bottomButton.setParams({ has_shine_effect: val }); },
    886            get: function () { return hasShineEffect; },
    887            enumerable: true
    888        });
    889        if (!isMainButton) {
    890            Object.defineProperty(bottomButton, 'position', {
    891                set: function (val) { bottomButton.setParams({ position: val }); },
    892                get: function () { return buttonPosition; },
    893                enumerable: true
    894            });
    895        }
    896
    897        var curButtonState = null;
    898
    899        WebView.onEvent(tgEventName, onBottomButtonPressed);
    900
    901        var debugBtn = null;
    902        if (initParams.tgWebAppDebug) {
    903            debugBtn = document.createElement('tg-bottom-button');
    904            var debugBtnStyle = {
    905                display: 'none',
    906                width: '100%',
    907                height: '44px',
    908                borderRadius: '0',
    909                background: 'no-repeat right center',
    910                padding: '13px 15px',
    911                textAlign: 'center',
    912                boxSizing: 'border-box'
    913            };
    914            for (var k in debugBtnStyle) {
    915                debugBtn.style[k] = debugBtnStyle[k];
    916            }
    917            debugBottomBar.appendChild(debugBtn);
    918            debugBtn.addEventListener('click', onBottomButtonPressed, false);
    919            debugBtn._bottomButton = bottomButton;
    920            debugBottomBarBtns[type] = debugBtn;
    921        }
    922
    923        function onBottomButtonPressed() {
    924            if (isActive) {
    925                receiveWebViewEvent(webViewEventName);
    926            }
    927        }
    928
    929        function buttonParams() {
    930            var color = bottomButton.color;
    931            var text_color = bottomButton.textColor;
    932            if (isVisible) {
    933                var params = {
    934                    is_visible: true,
    935                    is_active: isActive,
    936                    is_progress_visible: isProgressVisible,
    937                    text: buttonText,
    938                    color: color,
    939                    text_color: text_color,
    940                    has_shine_effect: hasShineEffect && isActive && !isProgressVisible
    941                };
    942                if (!isMainButton) {
    943                    params.position = buttonPosition;
    944                }
    945            } else {
    946                var params = {
    947                    is_visible: false
    948                };
    949            }
    950            return params;
    951        }
    952
    953        function buttonState(btn_params) {
    954            if (typeof btn_params === 'undefined') {
    955                btn_params = buttonParams();
    956            }
    957            return JSON.stringify(btn_params);
    958        }
    959
    960        function updateButton() {
    961            var btn_params = buttonParams();
    962            var btn_state = buttonState(btn_params);
    963            if (curButtonState === btn_state) {
    964                return;
    965            }
    966            curButtonState = btn_state;
    967            WebView.postEvent(setupFnName, false, btn_params);
    968            if (initParams.tgWebAppDebug) {
    969                updateDebugButton(btn_params);
    970            }
    971        }
    972
    973        function updateDebugButton(btn_params) {
    974            if (btn_params.is_visible) {
    975                debugBtn.style.display = 'block';
    976
    977                debugBtn.style.opacity = btn_params.is_active ? '1' : '0.8';
    978                debugBtn.style.cursor = btn_params.is_active ? 'pointer' : 'auto';
    979                debugBtn.disabled = !btn_params.is_active;
    980                debugBtn.innerText = btn_params.text;
    981                debugBtn.className = btn_params.has_shine_effect ? 'shine' : '';
    982                debugBtn.style.backgroundImage = btn_params.is_progress_visible ? "url('data:image/svg+xml," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewport="0 0 48 48" width="48px" height="48px"><circle cx="50%" cy="50%" stroke="' + btn_params.text_color + '" stroke-width="2.25" stroke-linecap="round" fill="none" stroke-dashoffset="106" r="9" stroke-dasharray="56.52" rotate="-90"><animate attributeName="stroke-dashoffset" attributeType="XML" dur="360s" from="0" to="12500" repeatCount="indefinite"></animate><animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="1s" from="-90 24 24" to="630 24 24" repeatCount="indefinite"></animateTransform></circle></svg>') + "')" : 'none';
    983                debugBtn.style.backgroundColor = btn_params.color;
    984                debugBtn.style.color = btn_params.text_color;
    985            } else {
    986                debugBtn.style.display = 'none';
    987            }
    988            updateDebugBottomBar();
    989        }
    990
    991        function setParams(params) {
    992            if (typeof params.text !== 'undefined') {
    993                var text = strTrim(params.text);
    994                if (!text.length) {
    995                    console.error('[Telegram.WebApp] Bottom button text is required', params.text);
    996                    throw Error('WebAppBottomButtonParamInvalid');
    997                }
    998                if (text.length > 64) {
    999                    console.error('[Telegram.WebApp] Bottom button text is too long', text);
    1000                    throw Error('WebAppBottomButtonParamInvalid');
    1001                }
    1002                buttonText = text;
    1003            }
    1004            if (typeof params.color !== 'undefined') {
    1005                if (params.color === false ||
    1006                    params.color === null) {
    1007                    buttonColor = false;
    1008                } else {
    1009                    var color = parseColorToHex(params.color);
    1010                    if (!color) {
    1011                        console.error('[Telegram.WebApp] Bottom button color format is invalid', params.color);
    1012                        throw Error('WebAppBottomButtonParamInvalid');
    1013                    }
    1014                    buttonColor = color;
    1015                }
    1016            }
    1017            if (typeof params.text_color !== 'undefined') {
    1018                if (params.text_color === false ||
    1019                    params.text_color === null) {
    1020                    buttonTextColor = false;
    1021                } else {
    1022                    var text_color = parseColorToHex(params.text_color);
    1023                    if (!text_color) {
    1024                        console.error('[Telegram.WebApp] Bottom button text color format is invalid', params.text_color);
    1025                        throw Error('WebAppBottomButtonParamInvalid');
    1026                    }
    1027                    buttonTextColor = text_color;
    1028                }
    1029            }
    1030            if (typeof params.is_visible !== 'undefined') {
    1031                if (params.is_visible &&
    1032                    !bottomButton.text.length) {
    1033                    console.error('[Telegram.WebApp] Bottom button text is required');
    1034                    throw Error('WebAppBottomButtonParamInvalid');
    1035                }
    1036                isVisible = !!params.is_visible;
    1037            }
    1038            if (typeof params.has_shine_effect !== 'undefined') {
    1039                hasShineEffect = !!params.has_shine_effect;
    1040            }
    1041            if (!isMainButton && typeof params.position !== 'undefined') {
    1042                if (params.position != 'left' && params.position != 'right' &&
    1043                    params.position != 'top' && params.position != 'bottom') {
    1044                    console.error('[Telegram.WebApp] Bottom button posiition is invalid', params.position);
    1045                    throw Error('WebAppBottomButtonParamInvalid');
    1046                }
    1047                buttonPosition = params.position;
    1048            }
    1049            if (typeof params.is_active !== 'undefined') {
    1050                isActive = !!params.is_active;
    1051            }
    1052            updateButton();
    1053            return bottomButton;
    1054        }
    1055
    1056        bottomButton.setText = function (text) {
    1057            return bottomButton.setParams({ text: text });
    1058        };
    1059        bottomButton.onClick = function (callback) {
    1060            onWebViewEvent(webViewEventName, callback);
    1061            return bottomButton;
    1062        };
    1063        bottomButton.offClick = function (callback) {
    1064            offWebViewEvent(webViewEventName, callback);
    1065            return bottomButton;
    1066        };
    1067        bottomButton.show = function () {
    1068            return bottomButton.setParams({ is_visible: true });
    1069        };
    1070        bottomButton.hide = function () {
    1071            return bottomButton.setParams({ is_visible: false });
    1072        };
    1073        bottomButton.enable = function () {
    1074            return bottomButton.setParams({ is_active: true });
    1075        };
    1076        bottomButton.disable = function () {
    1077            return bottomButton.setParams({ is_active: false });
    1078        };
    1079        bottomButton.showProgress = function (leaveActive) {
    1080            isActive = !!leaveActive;
    1081            isProgressVisible = true;
    1082            updateButton();
    1083            return bottomButton;
    1084        };
    1085        bottomButton.hideProgress = function () {
    1086            if (!bottomButton.isActive) {
    1087                isActive = true;
    1088            }
    1089            isProgressVisible = false;
    1090            updateButton();
    1091            return bottomButton;
    1092        }
    1093        bottomButton.setParams = setParams;
    1094        return bottomButton;
    1095    };
    1096    var MainButton = BottomButtonConstructor('main');
    1097    var SecondaryButton = BottomButtonConstructor('secondary');
    1098
    1099    var SettingsButton = (function () {
    1100        var isVisible = false;
    1101
    1102        var settingsButton = {};
    1103        Object.defineProperty(settingsButton, 'isVisible', {
    1104            set: function (val) { setParams({ is_visible: val }); },
    1105            get: function () { return isVisible; },
    1106            enumerable: true
    1107        });
    1108
    1109        var curButtonState = null;
    1110
    1111        WebView.onEvent('settings_button_pressed', onSettingsButtonPressed);
    1112
    1113        function onSettingsButtonPressed() {
    1114            receiveWebViewEvent('settingsButtonClicked');
    1115        }
    1116
    1117        function buttonParams() {
    1118            return { is_visible: isVisible };
    1119        }
    1120
    1121        function buttonState(btn_params) {
    1122            if (typeof btn_params === 'undefined') {
    1123                btn_params = buttonParams();
    1124            }
    1125            return JSON.stringify(btn_params);
    1126        }
    1127
    1128        function buttonCheckVersion() {
    1129            if (!versionAtLeast('6.10')) {
    1130                console.warn('[Telegram.WebApp] SettingsButton is not supported in version ' + webAppVersion);
    1131                return false;
    1132            }
    1133            return true;
    1134        }
    1135
    1136        function updateButton() {
    1137            var btn_params = buttonParams();
    1138            var btn_state = buttonState(btn_params);
    1139            if (curButtonState === btn_state) {
    1140                return;
    1141            }
    1142            curButtonState = btn_state;
    1143            WebView.postEvent('web_app_setup_settings_button', false, btn_params);
    1144        }
    1145
    1146        function setParams(params) {
    1147            if (!buttonCheckVersion()) {
    1148                return settingsButton;
    1149            }
    1150            if (typeof params.is_visible !== 'undefined') {
    1151                isVisible = !!params.is_visible;
    1152            }
    1153            updateButton();
    1154            return settingsButton;
    1155        }
    1156
    1157        settingsButton.onClick = function (callback) {
    1158            if (buttonCheckVersion()) {
    1159                onWebViewEvent('settingsButtonClicked', callback);
    1160            }
    1161            return settingsButton;
    1162        };
    1163        settingsButton.offClick = function (callback) {
    1164            if (buttonCheckVersion()) {
    1165                offWebViewEvent('settingsButtonClicked', callback);
    1166            }
    1167            return settingsButton;
    1168        };
    1169        settingsButton.show = function () {
    1170            return setParams({ is_visible: true });
    1171        };
    1172        settingsButton.hide = function () {
    1173            return setParams({ is_visible: false });
    1174        };
    1175        return settingsButton;
    1176    })();
    1177
    1178    var HapticFeedback = (function () {
    1179        var hapticFeedback = {};
    1180
    1181        function triggerFeedback(params) {
    1182            if (!versionAtLeast('6.1')) {
    1183                console.warn('[Telegram.WebApp] HapticFeedback is not supported in version ' + webAppVersion);
    1184                return hapticFeedback;
    1185            }
    1186            if (params.type == 'impact') {
    1187                if (params.impact_style != 'light' &&
    1188                    params.impact_style != 'medium' &&
    1189                    params.impact_style != 'heavy' &&
    1190                    params.impact_style != 'rigid' &&
    1191                    params.impact_style != 'soft') {
    1192                    console.error('[Telegram.WebApp] Haptic impact style is invalid', params.impact_style);
    1193                    throw Error('WebAppHapticImpactStyleInvalid');
    1194                }
    1195            } else if (params.type == 'notification') {
    1196                if (params.notification_type != 'error' &&
    1197                    params.notification_type != 'success' &&
    1198                    params.notification_type != 'warning') {
    1199                    console.error('[Telegram.WebApp] Haptic notification type is invalid', params.notification_type);
    1200                    throw Error('WebAppHapticNotificationTypeInvalid');
    1201                }
    1202            } else if (params.type == 'selection_change') {
    1203                // no params needed
    1204            } else {
    1205                console.error('[Telegram.WebApp] Haptic feedback type is invalid', params.type);
    1206                throw Error('WebAppHapticFeedbackTypeInvalid');
    1207            }
    1208            WebView.postEvent('web_app_trigger_haptic_feedback', false, params);
    1209            return hapticFeedback;
    1210        }
    1211
    1212        hapticFeedback.impactOccurred = function (style) {
    1213            return triggerFeedback({ type: 'impact', impact_style: style });
    1214        };
    1215        hapticFeedback.notificationOccurred = function (type) {
    1216            return triggerFeedback({ type: 'notification', notification_type: type });
    1217        };
    1218        hapticFeedback.selectionChanged = function () {
    1219            return triggerFeedback({ type: 'selection_change' });
    1220        };
    1221        return hapticFeedback;
    1222    })();
    1223
    1224    var CloudStorage = (function () {
    1225        var cloudStorage = {};
    1226
    1227        function invokeStorageMethod(method, params, callback) {
    1228            if (!versionAtLeast('6.9')) {
    1229                console.error('[Telegram.WebApp] CloudStorage is not supported in version ' + webAppVersion);
    1230                throw Error('WebAppMethodUnsupported');
    1231            }
    1232            invokeCustomMethod(method, params, callback);
    1233            return cloudStorage;
    1234        }
    1235
    1236        cloudStorage.setItem = function (key, value, callback) {
    1237            return invokeStorageMethod('saveStorageValue', { key: key, value: value }, callback);
    1238        };
    1239        cloudStorage.getItem = function (key, callback) {
    1240            return cloudStorage.getItems([key], callback ? function (err, res) {
    1241                if (err) callback(err);
    1242                else callback(null, res[key]);
    1243            } : null);
    1244        };
    1245        cloudStorage.getItems = function (keys, callback) {
    1246            return invokeStorageMethod('getStorageValues', { keys: keys }, callback);
    1247        };
    1248        cloudStorage.removeItem = function (key, callback) {
    1249            return cloudStorage.removeItems([key], callback);
    1250        };
    1251        cloudStorage.removeItems = function (keys, callback) {
    1252            return invokeStorageMethod('deleteStorageValues', { keys: keys }, callback);
    1253        };
    1254        cloudStorage.getKeys = function (callback) {
    1255            return invokeStorageMethod('getStorageKeys', {}, callback);
    1256        };
    1257        return cloudStorage;
    1258    })();
    1259
    1260    var BiometricManager = (function () {
    1261        var isInited = false;
    1262        var isBiometricAvailable = false;
    1263        var biometricType = 'unknown';
    1264        var isAccessRequested = false;
    1265        var isAccessGranted = false;
    1266        var isBiometricTokenSaved = false;
    1267        var deviceId = '';
    1268
    1269        var biometricManager = {};
    1270        Object.defineProperty(biometricManager, 'isInited', {
    1271            get: function () { return isInited; },
    1272            enumerable: true
    1273        });
    1274        Object.defineProperty(biometricManager, 'isBiometricAvailable', {
    1275            get: function () { return isInited && isBiometricAvailable; },
    1276            enumerable: true
    1277        });
    1278        Object.defineProperty(biometricManager, 'biometricType', {
    1279            get: function () { return biometricType || 'unknown'; },
    1280            enumerable: true
    1281        });
    1282        Object.defineProperty(biometricManager, 'isAccessRequested', {
    1283            get: function () { return isAccessRequested; },
    1284            enumerable: true
    1285        });
    1286        Object.defineProperty(biometricManager, 'isAccessGranted', {
    1287            get: function () { return isAccessRequested && isAccessGranted; },
    1288            enumerable: true
    1289        });
    1290        Object.defineProperty(biometricManager, 'isBiometricTokenSaved', {
    1291            get: function () { return isBiometricTokenSaved; },
    1292            enumerable: true
    1293        });
    1294        Object.defineProperty(biometricManager, 'deviceId', {
    1295            get: function () { return deviceId || ''; },
    1296            enumerable: true
    1297        });
    1298
    1299        var initRequestState = { callbacks: [] };
    1300        var accessRequestState = false;
    1301        var authRequestState = false;
    1302        var tokenRequestState = false;
    1303
    1304        WebView.onEvent('biometry_info_received', onBiometryInfoReceived);
    1305        WebView.onEvent('biometry_auth_requested', onBiometryAuthRequested);
    1306        WebView.onEvent('biometry_token_updated', onBiometryTokenUpdated);
    1307
    1308        function onBiometryInfoReceived(eventType, eventData) {
    1309            isInited = true;
    1310            if (eventData.available) {
    1311                isBiometricAvailable = true;
    1312                biometricType = eventData.type || 'unknown';
    1313                if (eventData.access_requested) {
    1314                    isAccessRequested = true;
    1315                    isAccessGranted = !!eventData.access_granted;
    1316                    isBiometricTokenSaved = !!eventData.token_saved;
    1317                } else {
    1318                    isAccessRequested = false;
    1319                    isAccessGranted = false;
    1320                    isBiometricTokenSaved = false;
    1321                }
    1322            } else {
    1323                isBiometricAvailable = false;
    1324                biometricType = 'unknown';
    1325                isAccessRequested = false;
    1326                isAccessGranted = false;
    1327                isBiometricTokenSaved = false;
    1328            }
    1329            deviceId = eventData.device_id || '';
    1330
    1331            if (initRequestState.callbacks.length > 0) {
    1332                for (var i = 0; i < initRequestState.callbacks.length; i++) {
    1333                    var callback = initRequestState.callbacks[i];
    1334                    callback();
    1335                }
    1336            }
    1337            if (accessRequestState) {
    1338                var state = accessRequestState;
    1339                accessRequestState = false;
    1340                if (state.callback) {
    1341                    state.callback(isAccessGranted);
    1342                }
    1343            }
    1344            receiveWebViewEvent('biometricManagerUpdated');
    1345        }
    1346        function onBiometryAuthRequested(eventType, eventData) {
    1347            var isAuthenticated = (eventData.status == 'authorized'),
    1348                biometricToken = eventData.token || '';
    1349            if (authRequestState) {
    1350                var state = authRequestState;
    1351                authRequestState = false;
    1352                if (state.callback) {
    1353                    state.callback(isAuthenticated, isAuthenticated ? biometricToken : null);
    1354                }
    1355            }
    1356            receiveWebViewEvent('biometricAuthRequested', isAuthenticated ? {
    1357                isAuthenticated: true,
    1358                biometricToken: biometricToken
    1359            } : {
    1360                isAuthenticated: false
    1361            });
    1362        }
    1363        function onBiometryTokenUpdated(eventType, eventData) {
    1364            var applied = false;
    1365            if (isBiometricAvailable &&
    1366                isAccessRequested) {
    1367                if (eventData.status == 'updated') {
    1368                    isBiometricTokenSaved = true;
    1369                    applied = true;
    1370                }
    1371                else if (eventData.status == 'removed') {
    1372                    isBiometricTokenSaved = false;
    1373                    applied = true;
    1374                }
    1375            }
    1376            if (tokenRequestState) {
    1377                var state = tokenRequestState;
    1378                tokenRequestState = false;
    1379                if (state.callback) {
    1380                    state.callback(applied);
    1381                }
    1382            }
    1383            receiveWebViewEvent('biometricTokenUpdated', {
    1384                isUpdated: applied
    1385            });
    1386        }
    1387
    1388        function checkVersion() {
    1389            if (!versionAtLeast('7.2')) {
    1390                console.warn('[Telegram.WebApp] BiometricManager is not supported in version ' + webAppVersion);
    1391                return false;
    1392            }
    1393            return true;
    1394        }
    1395
    1396        function checkInit() {
    1397            if (!isInited) {
    1398                console.error('[Telegram.WebApp] BiometricManager should be inited before using.');
    1399                throw Error('WebAppBiometricManagerNotInited');
    1400            }
    1401            return true;
    1402        }
    1403
    1404        biometricManager.init = function (callback) {
    1405            if (!checkVersion()) {
    1406                return biometricManager;
    1407            }
    1408            if (isInited) {
    1409                return biometricManager;
    1410            }
    1411            if (callback) {
    1412                initRequestState.callbacks.push(callback);
    1413            }
    1414            WebView.postEvent('web_app_biometry_get_info', false);
    1415            return biometricManager;
    1416        };
    1417        biometricManager.requestAccess = function (params, callback) {
    1418            if (!checkVersion()) {
    1419                return biometricManager;
    1420            }
    1421            checkInit();
    1422            if (!isBiometricAvailable) {
    1423                console.error('[Telegram.WebApp] Biometrics is not available on this device.');
    1424                throw Error('WebAppBiometricManagerBiometricsNotAvailable');
    1425            }
    1426            if (accessRequestState) {
    1427                console.error('[Telegram.WebApp] Access is already requested');
    1428                throw Error('WebAppBiometricManagerAccessRequested');
    1429            }
    1430            var popup_params = {};
    1431            if (typeof params.reason !== 'undefined') {
    1432                var reason = strTrim(params.reason);
    1433                if (reason.length > 128) {
    1434                    console.error('[Telegram.WebApp] Biometric reason is too long', reason);
    1435                    throw Error('WebAppBiometricRequestAccessParamInvalid');
    1436                }
    1437                if (reason.length > 0) {
    1438                    popup_params.reason = reason;
    1439                }
    1440            }
    1441
    1442            accessRequestState = {
    1443                callback: callback
    1444            };
    1445            WebView.postEvent('web_app_biometry_request_access', false, popup_params);
    1446            return biometricManager;
    1447        };
    1448        biometricManager.authenticate = function (params, callback) {
    1449            if (!checkVersion()) {
    1450                return biometricManager;
    1451            }
    1452            checkInit();
    1453            if (!isBiometricAvailable) {
    1454                console.error('[Telegram.WebApp] Biometrics is not available on this device.');
    1455                throw Error('WebAppBiometricManagerBiometricsNotAvailable');
    1456            }
    1457            if (!isAccessGranted) {
    1458                console.error('[Telegram.WebApp] Biometric access was not granted by the user.');
    1459                throw Error('WebAppBiometricManagerBiometricAccessNotGranted');
    1460            }
    1461            if (authRequestState) {
    1462                console.error('[Telegram.WebApp] Authentication request is already in progress.');
    1463                throw Error('WebAppBiometricManagerAuthenticationRequested');
    1464            }
    1465            var popup_params = {};
    1466            if (typeof params.reason !== 'undefined') {
    1467                var reason = strTrim(params.reason);
    1468                if (reason.length > 128) {
    1469                    console.error('[Telegram.WebApp] Biometric reason is too long', reason);
    1470                    throw Error('WebAppBiometricRequestAccessParamInvalid');
    1471                }
    1472                if (reason.length > 0) {
    1473                    popup_params.reason = reason;
    1474                }
    1475            }
    1476
    1477            authRequestState = {
    1478                callback: callback
    1479            };
    1480            WebView.postEvent('web_app_biometry_request_auth', false, popup_params);
    1481            return biometricManager;
    1482        };
    1483        biometricManager.updateBiometricToken = function (token, callback) {
    1484            if (!checkVersion()) {
    1485                return biometricManager;
    1486            }
    1487            token = token || '';
    1488            if (token.length > 1024) {
    1489                console.error('[Telegram.WebApp] Token is too long', token);
    1490                throw Error('WebAppBiometricManagerTokenInvalid');
    1491            }
    1492            checkInit();
    1493            if (!isBiometricAvailable) {
    1494                console.error('[Telegram.WebApp] Biometrics is not available on this device.');
    1495                throw Error('WebAppBiometricManagerBiometricsNotAvailable');
    1496            }
    1497            if (!isAccessGranted) {
    1498                console.error('[Telegram.WebApp] Biometric access was not granted by the user.');
    1499                throw Error('WebAppBiometricManagerBiometricAccessNotGranted');
    1500            }
    1501            if (tokenRequestState) {
    1502                console.error('[Telegram.WebApp] Token request is already in progress.');
    1503                throw Error('WebAppBiometricManagerTokenUpdateRequested');
    1504            }
    1505            tokenRequestState = {
    1506                callback: callback
    1507            };
    1508            WebView.postEvent('web_app_biometry_update_token', false, { token: token });
    1509            return biometricManager;
    1510        };
    1511        biometricManager.openSettings = function () {
    1512            if (!checkVersion()) {
    1513                return biometricManager;
    1514            }
    1515            checkInit();
    1516            if (!isBiometricAvailable) {
    1517                console.error('[Telegram.WebApp] Biometrics is not available on this device.');
    1518                throw Error('WebAppBiometricManagerBiometricsNotAvailable');
    1519            }
    1520            if (!isAccessRequested) {
    1521                console.error('[Telegram.WebApp] Biometric access was not requested yet.');
    1522                throw Error('WebAppBiometricManagerBiometricsAccessNotRequested');
    1523            }
    1524            if (isAccessGranted) {
    1525                console.warn('[Telegram.WebApp] Biometric access was granted by the user, no need to go to settings.');
    1526                return biometricManager;
    1527            }
    1528            WebView.postEvent('web_app_biometry_open_settings', false);
    1529            return biometricManager;
    1530        };
    1531        return biometricManager;
    1532    })();
    1533
    1534    var webAppInvoices = {};
    1535    function onInvoiceClosed(eventType, eventData) {
    1536        if (eventData.slug && webAppInvoices[eventData.slug]) {
    1537            var invoiceData = webAppInvoices[eventData.slug];
    1538            delete webAppInvoices[eventData.slug];
    1539            if (invoiceData.callback) {
    1540                invoiceData.callback(eventData.status);
    1541            }
    1542            receiveWebViewEvent('invoiceClosed', {
    1543                url: invoiceData.url,
    1544                status: eventData.status
    1545            });
    1546        }
    1547    }
    1548
    1549    var webAppPopupOpened = false;
    1550    function onPopupClosed(eventType, eventData) {
    1551        if (webAppPopupOpened) {
    1552            var popupData = webAppPopupOpened;
    1553            webAppPopupOpened = false;
    1554            var button_id = null;
    1555            if (typeof eventData.button_id !== 'undefined') {
    1556                button_id = eventData.button_id;
    1557            }
    1558            if (popupData.callback) {
    1559                popupData.callback(button_id);
    1560            }
    1561            receiveWebViewEvent('popupClosed', {
    1562                button_id: button_id
    1563            });
    1564        }
    1565    }
    1566
    1567    var webAppScanQrPopupOpened = false;
    1568    function onQrTextReceived(eventType, eventData) {
    1569        if (webAppScanQrPopupOpened) {
    1570            var popupData = webAppScanQrPopupOpened;
    1571            var data = null;
    1572            if (typeof eventData.data !== 'undefined') {
    1573                data = eventData.data;
    1574            }
    1575            if (popupData.callback) {
    1576                if (popupData.callback(data)) {
    1577                    webAppScanQrPopupOpened = false;
    1578                    WebView.postEvent('web_app_close_scan_qr_popup', false);
    1579                }
    1580            }
    1581            receiveWebViewEvent('qrTextReceived', {
    1582                data: data
    1583            });
    1584        }
    1585    }
    1586    function onScanQrPopupClosed(eventType, eventData) {
    1587        webAppScanQrPopupOpened = false;
    1588        receiveWebViewEvent('scanQrPopupClosed');
    1589    }
    1590
    1591    function onClipboardTextReceived(eventType, eventData) {
    1592        if (eventData.req_id && webAppCallbacks[eventData.req_id]) {
    1593            var requestData = webAppCallbacks[eventData.req_id];
    1594            delete webAppCallbacks[eventData.req_id];
    1595            var data = null;
    1596            if (typeof eventData.data !== 'undefined') {
    1597                data = eventData.data;
    1598            }
    1599            if (requestData.callback) {
    1600                requestData.callback(data);
    1601            }
    1602            receiveWebViewEvent('clipboardTextReceived', {
    1603                data: data
    1604            });
    1605        }
    1606    }
    1607
    1608    var WebAppWriteAccessRequested = false;
    1609    function onWriteAccessRequested(eventType, eventData) {
    1610        if (WebAppWriteAccessRequested) {
    1611            var requestData = WebAppWriteAccessRequested;
    1612            WebAppWriteAccessRequested = false;
    1613            if (requestData.callback) {
    1614                requestData.callback(eventData.status == 'allowed');
    1615            }
    1616            receiveWebViewEvent('writeAccessRequested', {
    1617                status: eventData.status
    1618            });
    1619        }
    1620    }
    1621
    1622    function getRequestedContact(callback, timeout) {
    1623        var reqTo, fallbackTo, reqDelay = 0;
    1624        var reqInvoke = function () {
    1625            invokeCustomMethod('getRequestedContact', {}, function (err, res) {
    1626                if (res && res.length) {
    1627                    clearTimeout(fallbackTo);
    1628                    callback(res);
    1629                } else {
    1630                    reqDelay += 50;
    1631                    reqTo = setTimeout(reqInvoke, reqDelay);
    1632                }
    1633            });
    1634        };
    1635        var fallbackInvoke = function () {
    1636            clearTimeout(reqTo);
    1637            callback('');
    1638        };
    1639        fallbackTo = setTimeout(fallbackInvoke, timeout);
    1640        reqInvoke();
    1641    }
    1642
    1643    var WebAppContactRequested = false;
    1644    function onPhoneRequested(eventType, eventData) {
    1645        if (WebAppContactRequested) {
    1646            var requestData = WebAppContactRequested;
    1647            WebAppContactRequested = false;
    1648            var requestSent = eventData.status == 'sent';
    1649            var webViewEvent = {
    1650                status: eventData.status
    1651            };
    1652            if (requestSent) {
    1653                getRequestedContact(function (res) {
    1654                    if (res && res.length) {
    1655                        webViewEvent.response = res;
    1656                        webViewEvent.responseUnsafe = Utils.urlParseQueryString(res);
    1657                        for (var key in webViewEvent.responseUnsafe) {
    1658                            var val = webViewEvent.responseUnsafe[key];
    1659                            try {
    1660                                if (val.substr(0, 1) == '{' && val.substr(-1) == '}' ||
    1661                                    val.substr(0, 1) == '[' && val.substr(-1) == ']') {
    1662                                    webViewEvent.responseUnsafe[key] = JSON.parse(val);
    1663                                }
    1664                            } catch (e) { }
    1665                        }
    1666                    }
    1667                    if (requestData.callback) {
    1668                        requestData.callback(requestSent, webViewEvent);
    1669                    }
    1670                    receiveWebViewEvent('contactRequested', webViewEvent);
    1671                }, 3000);
    1672            } else {
    1673                if (requestData.callback) {
    1674                    requestData.callback(requestSent, webViewEvent);
    1675                }
    1676                receiveWebViewEvent('contactRequested', webViewEvent);
    1677            }
    1678        }
    1679    }
    1680
    1681    function onCustomMethodInvoked(eventType, eventData) {
    1682        if (eventData.req_id && webAppCallbacks[eventData.req_id]) {
    1683            var requestData = webAppCallbacks[eventData.req_id];
    1684            delete webAppCallbacks[eventData.req_id];
    1685            var res = null, err = null;
    1686            if (typeof eventData.result !== 'undefined') {
    1687                res = eventData.result;
    1688            }
    1689            if (typeof eventData.error !== 'undefined') {
    1690                err = eventData.error;
    1691            }
    1692            if (requestData.callback) {
    1693                requestData.callback(err, res);
    1694            }
    1695        }
    1696    }
    1697
    1698    function invokeCustomMethod(method, params, callback) {
    1699        if (!versionAtLeast('6.9')) {
    1700            console.error('[Telegram.WebApp] Method invokeCustomMethod is not supported in version ' + webAppVersion);
    1701            throw Error('WebAppMethodUnsupported');
    1702        }
    1703        var req_id = generateCallbackId(16);
    1704        var req_params = { req_id: req_id, method: method, params: params || {} };
    1705        webAppCallbacks[req_id] = {
    1706            callback: callback
    1707        };
    1708        WebView.postEvent('web_app_invoke_custom_method', false, req_params);
    1709    };
    1710
    1711    if (!window.Telegram) {
    1712        window.Telegram = {};
    1713    }
    1714
    1715    Object.defineProperty(WebApp, 'initData', {
    1716        get: function () { return webAppInitData; },
    1717        enumerable: true
    1718    });
    1719    Object.defineProperty(WebApp, 'initDataUnsafe', {
    1720        get: function () { return webAppInitDataUnsafe; },
    1721        enumerable: true
    1722    });
    1723    Object.defineProperty(WebApp, 'version', {
    1724        get: function () { return webAppVersion; },
    1725        enumerable: true
    1726    });
    1727    Object.defineProperty(WebApp, 'platform', {
    1728        get: function () { return webAppPlatform; },
    1729        enumerable: true
    1730    });
    1731    Object.defineProperty(WebApp, 'colorScheme', {
    1732        get: function () { return colorScheme; },
    1733        enumerable: true
    1734    });
    1735    Object.defineProperty(WebApp, 'themeParams', {
    1736        get: function () { return themeParams; },
    1737        enumerable: true
    1738    });
    1739    Object.defineProperty(WebApp, 'isExpanded', {
    1740        get: function () { return isExpanded; },
    1741        enumerable: true
    1742    });
    1743    Object.defineProperty(WebApp, 'viewportHeight', {
    1744        get: function () { return (viewportHeight === false ? window.innerHeight : viewportHeight) - bottomBarHeight; },
    1745        enumerable: true
    1746    });
    1747    Object.defineProperty(WebApp, 'viewportStableHeight', {
    1748        get: function () { return (viewportStableHeight === false ? window.innerHeight : viewportStableHeight) - bottomBarHeight; },
    1749        enumerable: true
    1750    });
    1751    Object.defineProperty(WebApp, 'isClosingConfirmationEnabled', {
    1752        set: function (val) { setClosingConfirmation(val); },
    1753        get: function () { return isClosingConfirmationEnabled; },
    1754        enumerable: true
    1755    });
    1756    Object.defineProperty(WebApp, 'isVerticalSwipesEnabled', {
    1757        set: function (val) { toggleVerticalSwipes(val); },
    1758        get: function () { return isVerticalSwipesEnabled; },
    1759        enumerable: true
    1760    });
    1761    Object.defineProperty(WebApp, 'headerColor', {
    1762        set: function (val) { setHeaderColor(val); },
    1763        get: function () { return getHeaderColor(); },
    1764        enumerable: true
    1765    });
    1766    Object.defineProperty(WebApp, 'backgroundColor', {
    1767        set: function (val) { setBackgroundColor(val); },
    1768        get: function () { return getBackgroundColor(); },
    1769        enumerable: true
    1770    });
    1771    Object.defineProperty(WebApp, 'bottomBarColor', {
    1772        set: function (val) { setBottomBarColor(val); },
    1773        get: function () { return getBottomBarColor(); },
    1774        enumerable: true
    1775    });
    1776    Object.defineProperty(WebApp, 'BackButton', {
    1777        value: BackButton,
    1778        enumerable: true
    1779    });
    1780    Object.defineProperty(WebApp, 'MainButton', {
    1781        value: MainButton,
    1782        enumerable: true
    1783    });
    1784    Object.defineProperty(WebApp, 'SecondaryButton', {
    1785        value: SecondaryButton,
    1786        enumerable: true
    1787    });
    1788    Object.defineProperty(WebApp, 'SettingsButton', {
    1789        value: SettingsButton,
    1790        enumerable: true
    1791    });
    1792    Object.defineProperty(WebApp, 'HapticFeedback', {
    1793        value: HapticFeedback,
    1794        enumerable: true
    1795    });
    1796    Object.defineProperty(WebApp, 'CloudStorage', {
    1797        value: CloudStorage,
    1798        enumerable: true
    1799    });
    1800    Object.defineProperty(WebApp, 'BiometricManager', {
    1801        value: BiometricManager,
    1802        enumerable: true
    1803    });
    1804    WebApp.setHeaderColor = function (color_key) {
    1805        WebApp.headerColor = color_key;
    1806    };
    1807    WebApp.setBackgroundColor = function (color) {
    1808        WebApp.backgroundColor = color;
    1809    };
    1810    WebApp.setBottomBarColor = function (color) {
    1811        WebApp.bottomBarColor = color;
    1812    };
    1813    WebApp.enableClosingConfirmation = function () {
    1814        WebApp.isClosingConfirmationEnabled = true;
    1815    };
    1816    WebApp.disableClosingConfirmation = function () {
    1817        WebApp.isClosingConfirmationEnabled = false;
    1818    };
    1819    WebApp.enableVerticalSwipes = function () {
    1820        WebApp.isVerticalSwipesEnabled = true;
    1821    };
    1822    WebApp.disableVerticalSwipes = function () {
    1823        WebApp.isVerticalSwipesEnabled = false;
    1824    };
    1825    WebApp.isVersionAtLeast = function (ver) {
    1826        return versionAtLeast(ver);
    1827    };
    1828    WebApp.onEvent = function (eventType, callback) {
    1829        onWebViewEvent(eventType, callback);
    1830    };
    1831    WebApp.offEvent = function (eventType, callback) {
    1832        offWebViewEvent(eventType, callback);
    1833    };
    1834    WebApp.sendData = function (data) {
    1835        if (!data || !data.length) {
    1836            console.error('[Telegram.WebApp] Data is required', data);
    1837            throw Error('WebAppDataInvalid');
    1838        }
    1839        if (byteLength(data) > 4096) {
    1840            console.error('[Telegram.WebApp] Data is too long', data);
    1841            throw Error('WebAppDataInvalid');
    1842        }
    1843        WebView.postEvent('web_app_data_send', false, { data: data });
    1844    };
    1845    WebApp.switchInlineQuery = function (query, choose_chat_types) {
    1846        if (!versionAtLeast('6.6')) {
    1847            console.error('[Telegram.WebApp] Method switchInlineQuery is not supported in version ' + webAppVersion);
    1848            throw Error('WebAppMethodUnsupported');
    1849        }
    1850        if (!initParams.tgWebAppBotInline) {
    1851            console.error('[Telegram.WebApp] Inline mode is disabled for this bot. Read more about inline mode: https://core.telegram.org/bots/inline');
    1852            throw Error('WebAppInlineModeDisabled');
    1853        }
    1854        query = query || '';
    1855        if (query.length > 256) {
    1856            console.error('[Telegram.WebApp] Inline query is too long', query);
    1857            throw Error('WebAppInlineQueryInvalid');
    1858        }
    1859        var chat_types = [];
    1860        if (choose_chat_types) {
    1861            if (!Array.isArray(choose_chat_types)) {
    1862                console.error('[Telegram.WebApp] Choose chat types should be an array', choose_chat_types);
    1863                throw Error('WebAppInlineChooseChatTypesInvalid');
    1864            }
    1865            var good_types = { users: 1, bots: 1, groups: 1, channels: 1 };
    1866            for (var i = 0; i < choose_chat_types.length; i++) {
    1867                var chat_type = choose_chat_types[i];
    1868                if (!good_types[chat_type]) {
    1869                    console.error('[Telegram.WebApp] Choose chat type is invalid', chat_type);
    1870                    throw Error('WebAppInlineChooseChatTypeInvalid');
    1871                }
    1872                if (good_types[chat_type] != 2) {
    1873                    good_types[chat_type] = 2;
    1874                    chat_types.push(chat_type);
    1875                }
    1876            }
    1877        }
    1878        WebView.postEvent('web_app_switch_inline_query', false, { query: query, chat_types: chat_types });
    1879    };
    1880    WebApp.openLink = function (url, options) {
    1881        var a = document.createElement('A');
    1882        a.href = url;
    1883        if (a.protocol != 'http:' &&
    1884            a.protocol != 'https:') {
    1885            console.error('[Telegram.WebApp] Url protocol is not supported', url);
    1886            throw Error('WebAppTgUrlInvalid');
    1887        }
    1888        var url = a.href;
    1889        options = options || {};
    1890        if (versionAtLeast('6.1')) {
    1891            var req_params = { url: url };
    1892            if (versionAtLeast('6.4') && options.try_instant_view) {
    1893                req_params.try_instant_view = true;
    1894            }
    1895            if (versionAtLeast('7.6') && options.try_browser) {
    1896                req_params.try_browser = options.try_browser;
    1897            }
    1898            WebView.postEvent('web_app_open_link', false, req_params);
    1899        } else {
    1900            window.open(url, '_blank');
    1901        }
    1902    };
    1903    WebApp.openTelegramLink = function (url) {
    1904        var a = document.createElement('A');
    1905        a.href = url;
    1906        if (a.protocol != 'http:' &&
    1907            a.protocol != 'https:') {
    1908            console.error('[Telegram.WebApp] Url protocol is not supported', url);
    1909            throw Error('WebAppTgUrlInvalid');
    1910        }
    1911        if (a.hostname != 't.me') {
    1912            console.error('[Telegram.WebApp] Url host is not supported', url);
    1913            throw Error('WebAppTgUrlInvalid');
    1914        }
    1915        var path_full = a.pathname + a.search;
    1916        if (isIframe || versionAtLeast('6.1')) {
    1917            WebView.postEvent('web_app_open_tg_link', false, { path_full: path_full });
    1918        } else {
    1919            location.href = 'https://t.me' + path_full;
    1920        }
    1921    };
    1922    WebApp.openInvoice = function (url, callback) {
    1923        var a = document.createElement('A'), match, slug;
    1924        a.href = url;
    1925        if (a.protocol != 'http:' &&
    1926            a.protocol != 'https:' ||
    1927            a.hostname != 't.me' ||
    1928            !(match = a.pathname.match(/^/($|invoice/)([A-Za-z0-9-_=]+)$/)) ||
    1929            !(slug = match[2])) {
    1930            console.error('[Telegram.WebApp] Invoice url is invalid', url);
    1931            throw Error('WebAppInvoiceUrlInvalid');
    1932        }
    1933        if (!versionAtLeast('6.1')) {
    1934            console.error('[Telegram.WebApp] Method openInvoice is not supported in version ' + webAppVersion);
    1935            throw Error('WebAppMethodUnsupported');
    1936        }
    1937        if (webAppInvoices[slug]) {
    1938            console.error('[Telegram.WebApp] Invoice is already opened');
    1939            throw Error('WebAppInvoiceOpened');
    1940        }
    1941        webAppInvoices[slug] = {
    1942            url: url,
    1943            callback: callback
    1944        };
    1945        WebView.postEvent('web_app_open_invoice', false, { slug: slug });
    1946    };
    1947    WebApp.showPopup = function (params, callback) {
    1948        if (!versionAtLeast('6.2')) {
    1949            console.error('[Telegram.WebApp] Method showPopup is not supported in version ' + webAppVersion);
    1950            throw Error('WebAppMethodUnsupported');
    1951        }
    1952        if (webAppPopupOpened) {
    1953            console.error('[Telegram.WebApp] Popup is already opened');
    1954            throw Error('WebAppPopupOpened');
    1955        }
    1956        var title = '';
    1957        var message = '';
    1958        var buttons = [];
    1959        var popup_buttons = {};
    1960        var popup_params = {};
    1961        if (typeof params.title !== 'undefined') {
    1962            title = strTrim(params.title);
    1963            if (title.length > 64) {
    1964                console.error('[Telegram.WebApp] Popup title is too long', title);
    1965                throw Error('WebAppPopupParamInvalid');
    1966            }
    1967            if (title.length > 0) {
    1968                popup_params.title = title;
    1969            }
    1970        }
    1971        if (typeof params.message !== 'undefined') {
    1972            message = strTrim(params.message);
    1973        }
    1974        if (!message.length) {
    1975            console.error('[Telegram.WebApp] Popup message is required', params.message);
    1976            throw Error('WebAppPopupParamInvalid');
    1977        }
    1978        if (message.length > 256) {
    1979            console.error('[Telegram.WebApp] Popup message is too long', message);
    1980            throw Error('WebAppPopupParamInvalid');
    1981        }
    1982        popup_params.message = message;
    1983        if (typeof params.buttons !== 'undefined') {
    1984            if (!Array.isArray(params.buttons)) {
    1985                console.error('[Telegram.WebApp] Popup buttons should be an array', params.buttons);
    1986                throw Error('WebAppPopupParamInvalid');
    1987            }
    1988            for (var i = 0; i < params.buttons.length; i++) {
    1989                var button = params.buttons[i];
    1990                var btn = {};
    1991                var id = '';
    1992                if (typeof button.id !== 'undefined') {
    1993                    id = button.id.toString();
    1994                    if (id.length > 64) {
    1995                        console.error('[Telegram.WebApp] Popup button id is too long', id);
    1996                        throw Error('WebAppPopupParamInvalid');
    1997                    }
    1998                }
    1999                btn.id = id;
    2000                var button_type = button.type;
    2001                if (typeof button_type === 'undefined') {
    2002                    button_type = 'default';
    2003                }
    2004                btn.type = button_type;
    2005                if (button_type == 'ok' ||
    2006                    button_type == 'close' ||
    2007                    button_type == 'cancel') {
    2008                    // no params needed
    2009                } else if (button_type == 'default' ||
    2010                    button_type == 'destructive') {
    2011                    var text = '';
    2012                    if (typeof button.text !== 'undefined') {
    2013                        text = strTrim(button.text);
    2014                    }
    2015                    if (!text.length) {
    2016                        console.error('[Telegram.WebApp] Popup button text is required for type ' + button_type, button.text);
    2017                        throw Error('WebAppPopupParamInvalid');
    2018                    }
    2019                    if (text.length > 64) {
    2020                        console.error('[Telegram.WebApp] Popup button text is too long', text);
    2021                        throw Error('WebAppPopupParamInvalid');
    2022                    }
    2023                    btn.text = text;
    2024                } else {
    2025                    console.error('[Telegram.WebApp] Popup button type is invalid', button_type);
    2026                    throw Error('WebAppPopupParamInvalid');
    2027                }
    2028                buttons.push(btn);
    2029            }
    2030        } else {
    2031            buttons.push({ id: '', type: 'close' });
    2032        }
    2033        if (buttons.length < 1) {
    2034            console.error('[Telegram.WebApp] Popup should have at least one button');
    2035            throw Error('WebAppPopupParamInvalid');
    2036        }
    2037        if (buttons.length > 3) {
    2038            console.error('[Telegram.WebApp] Popup should not have more than 3 buttons');
    2039            throw Error('WebAppPopupParamInvalid');
    2040        }
    2041        popup_params.buttons = buttons;
    2042
    2043        webAppPopupOpened = {
    2044            callback: callback
    2045        };
    2046        WebView.postEvent('web_app_open_popup', false, popup_params);
    2047    };
    2048    WebApp.showAlert = function (message, callback) {
    2049        WebApp.showPopup({
    2050            message: message
    2051        }, callback ? function () { callback(); } : null);
    2052    };
    2053    WebApp.showConfirm = function (message, callback) {
    2054        WebApp.showPopup({
    2055            message: message,
    2056            buttons: [
    2057                { type: 'ok', id: 'ok' },
    2058                { type: 'cancel' }
    2059            ]
    2060        }, callback ? function (button_id) {
    2061            callback(button_id == 'ok');
    2062        } : null);
    2063    };
    2064    WebApp.showScanQrPopup = function (params, callback) {
    2065        if (!versionAtLeast('6.4')) {
    2066            console.error('[Telegram.WebApp] Method showScanQrPopup is not supported in version ' + webAppVersion);
    2067            throw Error('WebAppMethodUnsupported');
    2068        }
    2069        if (webAppScanQrPopupOpened) {
    2070            console.error('[Telegram.WebApp] Popup is already opened');
    2071            throw Error('WebAppScanQrPopupOpened');
    2072        }
    2073        var text = '';
    2074        var popup_params = {};
    2075        if (typeof params.text !== 'undefined') {
    2076            text = strTrim(params.text);
    2077            if (text.length > 64) {
    2078                console.error('[Telegram.WebApp] Scan QR popup text is too long', text);
    2079                throw Error('WebAppScanQrPopupParamInvalid');
    2080            }
    2081            if (text.length > 0) {
    2082                popup_params.text = text;
    2083            }
    2084        }
    2085
    2086        webAppScanQrPopupOpened = {
    2087            callback: callback
    2088        };
    2089        WebView.postEvent('web_app_open_scan_qr_popup', false, popup_params);
    2090    };
    2091    WebApp.closeScanQrPopup = function () {
    2092        if (!versionAtLeast('6.4')) {
    2093            console.error('[Telegram.WebApp] Method closeScanQrPopup is not supported in version ' + webAppVersion);
    2094            throw Error('WebAppMethodUnsupported');
    2095        }
    2096
    2097        webAppScanQrPopupOpened = false;
    2098        WebView.postEvent('web_app_close_scan_qr_popup', false);
    2099    };
    2100    WebApp.readTextFromClipboard = function (callback) {
    2101        if (!versionAtLeast('6.4')) {
    2102            console.error('[Telegram.WebApp] Method readTextFromClipboard is not supported in version ' + webAppVersion);
    2103            throw Error('WebAppMethodUnsupported');
    2104        }
    2105        var req_id = generateCallbackId(16);
    2106        var req_params = { req_id: req_id };
    2107        webAppCallbacks[req_id] = {
    2108            callback: callback
    2109        };
    2110        WebView.postEvent('web_app_read_text_from_clipboard', false, req_params);
    2111    };
    2112    WebApp.requestWriteAccess = function (callback) {
    2113        if (!versionAtLeast('6.9')) {
    2114            console.error('[Telegram.WebApp] Method requestWriteAccess is not supported in version ' + webAppVersion);
    2115            throw Error('WebAppMethodUnsupported');
    2116        }
    2117        if (WebAppWriteAccessRequested) {
    2118            console.error('[Telegram.WebApp] Write access is already requested');
    2119            throw Error('WebAppWriteAccessRequested');
    2120        }
    2121        WebAppWriteAccessRequested = {
    2122            callback: callback
    2123        };
    2124        WebView.postEvent('web_app_request_write_access');
    2125    };
    2126    WebApp.requestContact = function (callback) {
    2127        if (!versionAtLeast('6.9')) {
    2128            console.error('[Telegram.WebApp] Method requestContact is not supported in version ' + webAppVersion);
    2129            throw Error('WebAppMethodUnsupported');
    2130        }
    2131        if (WebAppContactRequested) {
    2132            console.error('[Telegram.WebApp] Contact is already requested');
    2133            throw Error('WebAppContactRequested');
    2134        }
    2135        WebAppContactRequested = {
    2136            callback: callback
    2137        };
    2138        WebView.postEvent('web_app_request_phone');
    2139    };
    2140    WebApp.shareToStory = function (media_url, params) {
    2141        params = params || {};
    2142        if (!versionAtLeast('7.8')) {
    2143            console.error('[Telegram.WebApp] Method shareToStory is not supported in version ' + webAppVersion);
    2144            throw Error('WebAppMethodUnsupported');
    2145        }
    2146        var a = document.createElement('A');
    2147        a.href = media_url;
    2148        if (a.protocol != 'http:' &&
    2149            a.protocol != 'https:') {
    2150            console.error('[Telegram.WebApp] Media url protocol is not supported', url);
    2151            throw Error('WebAppMediaUrlInvalid');
    2152        }
    2153        var share_params = {};
    2154        share_params.media_url = a.href;
    2155        if (typeof params.text !== 'undefined') {
    2156            var text = strTrim(params.text);
    2157            if (text.length > 2048) {
    2158                console.error('[Telegram.WebApp] Text is too long', text);
    2159                throw Error('WebAppShareToStoryParamInvalid');
    2160            }
    2161            if (text.length > 0) {
    2162                share_params.text = text;
    2163            }
    2164        }
    2165        if (typeof params.widget_link !== 'undefined') {
    2166            params.widget_link = params.widget_link || {};
    2167            a.href = params.widget_link.url;
    2168            if (a.protocol != 'http:' &&
    2169                a.protocol != 'https:') {
    2170                console.error('[Telegram.WebApp] Link protocol is not supported', url);
    2171                throw Error('WebAppShareToStoryParamInvalid');
    2172            }
    2173            var widget_link = {
    2174                url: a.href
    2175            };
    2176            if (typeof params.widget_link.name !== 'undefined') {
    2177                var link_name = strTrim(params.widget_link.name);
    2178                if (link_name.length > 48) {
    2179                    console.error('[Telegram.WebApp] Link name is too long', link_name);
    2180                    throw Error('WebAppShareToStoryParamInvalid');
    2181                }
    2182                if (link_name.length > 0) {
    2183                    widget_link.name = link_name;
    2184                }
    2185            }
    2186            share_params.widget_link = widget_link;
    2187        }
    2188
    2189        WebView.postEvent('web_app_share_to_story', false, share_params);
    2190    };
    2191    WebApp.invokeCustomMethod = function (method, params, callback) {
    2192        invokeCustomMethod(method, params, callback);
    2193    };
    2194    WebApp.ready = function () {
    2195        WebView.postEvent('web_app_ready');
    2196    };
    2197    WebApp.expand = function () {
    2198        WebView.postEvent('web_app_expand');
    2199    };
    2200    WebApp.close = function (options) {
    2201        options = options || {};
    2202        var req_params = {};
    2203        if (versionAtLeast('7.6') && options.return_back) {
    2204            req_params.return_back = true;
    2205        }
    2206        WebView.postEvent('web_app_close', false, req_params);
    2207    };
    2208
    2209    window.Telegram.WebApp = WebApp;
    2210
    2211    updateHeaderColor();
    2212    updateBackgroundColor();
    2213    updateBottomBarColor();
    2214    setViewportHeight();
    2215    if (initParams.tgWebAppShowSettings) {
    2216        SettingsButton.show();
    2217    }
    2218
    2219    window.addEventListener('resize', onWindowResize);
    2220    if (isIframe) {
    2221        document.addEventListener('click', linkHandler);
    2222    }
    2223
    2224    WebView.onEvent('theme_changed', onThemeChanged);
    2225    WebView.onEvent('viewport_changed', onViewportChanged);
    2226    WebView.onEvent('invoice_closed', onInvoiceClosed);
    2227    WebView.onEvent('popup_closed', onPopupClosed);
    2228    WebView.onEvent('qr_text_received', onQrTextReceived);
    2229    WebView.onEvent('scan_qr_popup_closed', onScanQrPopupClosed);
    2230    WebView.onEvent('clipboard_text_received', onClipboardTextReceived);
    2231    WebView.onEvent('write_access_requested', onWriteAccessRequested);
    2232    WebView.onEvent('phone_requested', onPhoneRequested);
    2233    WebView.onEvent('custom_method_invoked', onCustomMethodInvoked);
    2234    WebView.postEvent('web_app_request_theme');
    2235    WebView.postEvent('web_app_request_viewport');
    2236
    2237})();

    Override Content

    hero template
    hero template
    hero template
    hero template
    hero template

    Result

    hero template