telegram-web-app.js 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914
  1. // WebView
  2. (function() {
  3. var eventHandlers = {};
  4. var locationHash = '';
  5. try {
  6. locationHash = location.hash.toString();
  7. } catch (e) {}
  8. var initParams = urlParseHashParams(locationHash);
  9. var storedParams = sessionStorageGet('initParams');
  10. if (storedParams) {
  11. for (var key in storedParams) {
  12. if (typeof initParams[key] === 'undefined') {
  13. initParams[key] = storedParams[key];
  14. }
  15. }
  16. }
  17. sessionStorageSet('initParams', initParams);
  18. var isIframe = false,
  19. iFrameStyle;
  20. try {
  21. isIframe = (window.parent != null && window != window.parent);
  22. if (isIframe) {
  23. window.addEventListener('message', function(event) {
  24. if (event.source !== window.parent) return;
  25. try {
  26. var dataParsed = JSON.parse(event.data);
  27. } catch (e) {
  28. return;
  29. }
  30. if (!dataParsed || !dataParsed.eventType) {
  31. return;
  32. }
  33. if (dataParsed.eventType == 'set_custom_style') {
  34. iFrameStyle.innerHTML = dataParsed.eventData;
  35. } else {
  36. receiveEvent(dataParsed.eventType, dataParsed.eventData);
  37. }
  38. });
  39. iFrameStyle = document.createElement('style');
  40. document.head.appendChild(iFrameStyle);
  41. try {
  42. window.parent.postMessage(JSON.stringify({
  43. eventType: 'iframe_ready'
  44. }), '*');
  45. } catch (e) {}
  46. }
  47. } catch (e) {}
  48. function urlSafeDecode(urlencoded) {
  49. try {
  50. urlencoded = urlencoded.replace(/\+/g, '%20');
  51. return decodeURIComponent(urlencoded);
  52. } catch (e) {
  53. return urlencoded;
  54. }
  55. }
  56. function urlParseHashParams(locationHash) {
  57. locationHash = locationHash.replace(/^#/, '');
  58. var params = {};
  59. if (!locationHash.length) {
  60. return params;
  61. }
  62. if (locationHash.indexOf('=') < 0 && locationHash.indexOf('?') < 0) {
  63. params._path = urlSafeDecode(locationHash);
  64. return params;
  65. }
  66. var qIndex = locationHash.indexOf('?');
  67. if (qIndex >= 0) {
  68. var pathParam = locationHash.substr(0, qIndex);
  69. params._path = urlSafeDecode(pathParam);
  70. locationHash = locationHash.substr(qIndex + 1);
  71. }
  72. var query_params = urlParseQueryString(locationHash);
  73. for (var k in query_params) {
  74. params[k] = query_params[k];
  75. }
  76. return params;
  77. }
  78. function urlParseQueryString(queryString) {
  79. var params = {};
  80. if (!queryString.length) {
  81. return params;
  82. }
  83. var queryStringParams = queryString.split('&');
  84. var i, param, paramName, paramValue;
  85. for (i = 0; i < queryStringParams.length; i++) {
  86. param = queryStringParams[i].split('=');
  87. paramName = urlSafeDecode(param[0]);
  88. paramValue = param[1] == null ? null : urlSafeDecode(param[1]);
  89. params[paramName] = paramValue;
  90. }
  91. return params;
  92. }
  93. // Telegram apps will implement this logic to add service params (e.g. tgShareScoreUrl) to game URL
  94. function urlAppendHashParams(url, addHash) {
  95. // url looks like 'https://game.com/path?query=1#hash'
  96. // addHash looks like 'tgShareScoreUrl=' + encodeURIComponent('tgb://share_game_score?hash=very_long_hash123')
  97. var ind = url.indexOf('#');
  98. if (ind < 0) {
  99. // https://game.com/path -> https://game.com/path#tgShareScoreUrl=etc
  100. return url + '#' + addHash;
  101. }
  102. var curHash = url.substr(ind + 1);
  103. if (curHash.indexOf('=') >= 0 || curHash.indexOf('?') >= 0) {
  104. // https://game.com/#hash=1 -> https://game.com/#hash=1&tgShareScoreUrl=etc
  105. // https://game.com/#path?query -> https://game.com/#path?query&tgShareScoreUrl=etc
  106. return url + '&' + addHash;
  107. }
  108. // https://game.com/#hash -> https://game.com/#hash?tgShareScoreUrl=etc
  109. if (curHash.length > 0) {
  110. return url + '?' + addHash;
  111. }
  112. // https://game.com/# -> https://game.com/#tgShareScoreUrl=etc
  113. return url + addHash;
  114. }
  115. function postEvent(eventType, callback, eventData) {
  116. if (!callback) {
  117. callback = function() {};
  118. }
  119. if (eventData === undefined) {
  120. eventData = '';
  121. }
  122. console.log('[Telegram.WebView] > postEvent', eventType, eventData);
  123. if (window.TelegramWebviewProxy !== undefined) {
  124. TelegramWebviewProxy.postEvent(eventType, JSON.stringify(eventData));
  125. callback();
  126. } else if (window.external && 'notify' in window.external) {
  127. window.external.notify(JSON.stringify({
  128. eventType: eventType,
  129. eventData: eventData
  130. }));
  131. callback();
  132. } else if (isIframe) {
  133. try {
  134. var trustedTarget = 'https://web.telegram.org';
  135. // For now we don't restrict target, for testing purposes
  136. trustedTarget = '*';
  137. window.parent.postMessage(JSON.stringify({
  138. eventType: eventType,
  139. eventData: eventData
  140. }), trustedTarget);
  141. callback();
  142. } catch (e) {
  143. callback(e);
  144. }
  145. } else {
  146. callback({
  147. notAvailable: true
  148. });
  149. }
  150. };
  151. function receiveEvent(eventType, eventData) {
  152. console.log('[Telegram.WebView] < receiveEvent', eventType, eventData);
  153. callEventCallbacks(eventType, function(callback) {
  154. callback(eventType, eventData);
  155. });
  156. }
  157. function callEventCallbacks(eventType, func) {
  158. var curEventHandlers = eventHandlers[eventType];
  159. if (curEventHandlers === undefined ||
  160. !curEventHandlers.length) {
  161. return;
  162. }
  163. for (var i = 0; i < curEventHandlers.length; i++) {
  164. try {
  165. func(curEventHandlers[i]);
  166. } catch (e) {}
  167. }
  168. }
  169. function onEvent(eventType, callback) {
  170. if (eventHandlers[eventType] === undefined) {
  171. eventHandlers[eventType] = [];
  172. }
  173. var index = eventHandlers[eventType].indexOf(callback);
  174. if (index === -1) {
  175. eventHandlers[eventType].push(callback);
  176. }
  177. };
  178. function offEvent(eventType, callback) {
  179. if (eventHandlers[eventType] === undefined) {
  180. return;
  181. }
  182. var index = eventHandlers[eventType].indexOf(callback);
  183. if (index === -1) {
  184. return;
  185. }
  186. eventHandlers[eventType].splice(index, 1);
  187. };
  188. function openProtoUrl(url) {
  189. if (!url.match(/^(web\+)?tgb?:\/\/./)) {
  190. return false;
  191. }
  192. var useIframe = navigator.userAgent.match(/iOS|iPhone OS|iPhone|iPod|iPad/i) ? true : false;
  193. if (useIframe) {
  194. var iframeContEl = document.getElementById('tgme_frame_cont') || document.body;
  195. var iframeEl = document.createElement('iframe');
  196. iframeContEl.appendChild(iframeEl);
  197. var pageHidden = false;
  198. var enableHidden = function() {
  199. pageHidden = true;
  200. };
  201. window.addEventListener('pagehide', enableHidden, false);
  202. window.addEventListener('blur', enableHidden, false);
  203. if (iframeEl !== null) {
  204. iframeEl.src = url;
  205. }
  206. setTimeout(function() {
  207. if (!pageHidden) {
  208. window.location = url;
  209. }
  210. window.removeEventListener('pagehide', enableHidden, false);
  211. window.removeEventListener('blur', enableHidden, false);
  212. }, 2000);
  213. } else {
  214. window.location = url;
  215. }
  216. return true;
  217. }
  218. function sessionStorageSet(key, value) {
  219. try {
  220. window.sessionStorage.setItem('__telegram__' + key, JSON.stringify(value));
  221. return true;
  222. } catch (e) {}
  223. return false;
  224. }
  225. function sessionStorageGet(key) {
  226. try {
  227. return JSON.parse(window.sessionStorage.getItem('__telegram__' + key));
  228. } catch (e) {}
  229. return null;
  230. }
  231. if (!window.Telegram) {
  232. window.Telegram = {};
  233. }
  234. window.Telegram.WebView = {
  235. initParams: initParams,
  236. isIframe: isIframe,
  237. onEvent: onEvent,
  238. offEvent: offEvent,
  239. postEvent: postEvent,
  240. receiveEvent: receiveEvent,
  241. callEventCallbacks: callEventCallbacks
  242. };
  243. window.Telegram.Utils = {
  244. urlSafeDecode: urlSafeDecode,
  245. urlParseQueryString: urlParseQueryString,
  246. urlParseHashParams: urlParseHashParams,
  247. urlAppendHashParams: urlAppendHashParams,
  248. sessionStorageSet: sessionStorageSet,
  249. sessionStorageGet: sessionStorageGet
  250. };
  251. // For Windows Phone app
  252. window.TelegramGameProxy_receiveEvent = receiveEvent;
  253. // App backward compatibility
  254. window.TelegramGameProxy = {
  255. receiveEvent: receiveEvent
  256. };
  257. })();
  258. // WebApp
  259. (function() {
  260. var Utils = window.Telegram.Utils;
  261. var WebView = window.Telegram.WebView;
  262. var initParams = WebView.initParams;
  263. var isIframe = WebView.isIframe;
  264. var WebApp = {};
  265. var webAppInitData = '',
  266. webAppInitDataUnsafe = {};
  267. var themeParams = {},
  268. colorScheme = 'light';
  269. var webAppVersion = '6.0';
  270. var webAppPlatform = 'unknown';
  271. if (initParams.tgWebAppData && initParams.tgWebAppData.length) {
  272. webAppInitData = initParams.tgWebAppData;
  273. webAppInitDataUnsafe = Utils.urlParseQueryString(webAppInitData);
  274. for (var key in webAppInitDataUnsafe) {
  275. var val = webAppInitDataUnsafe[key];
  276. try {
  277. if (val.substr(0, 1) == '{' && val.substr(-1) == '}' ||
  278. val.substr(0, 1) == '[' && val.substr(-1) == ']') {
  279. webAppInitDataUnsafe[key] = JSON.parse(val);
  280. }
  281. } catch (e) {}
  282. }
  283. }
  284. if (initParams.tgWebAppThemeParams && initParams.tgWebAppThemeParams.length) {
  285. var themeParamsRaw = initParams.tgWebAppThemeParams;
  286. try {
  287. var theme_params = JSON.parse(themeParamsRaw);
  288. if (theme_params) {
  289. setThemeParams(theme_params);
  290. }
  291. } catch (e) {}
  292. }
  293. var theme_params = Utils.sessionStorageGet('themeParams');
  294. if (theme_params) {
  295. setThemeParams(theme_params);
  296. }
  297. if (initParams.tgWebAppVersion) {
  298. webAppVersion = initParams.tgWebAppVersion;
  299. }
  300. if (initParams.tgWebAppPlatform) {
  301. webAppPlatform = initParams.tgWebAppPlatform;
  302. }
  303. function onThemeChanged(eventType, eventData) {
  304. if (eventData.theme_params) {
  305. setThemeParams(eventData.theme_params);
  306. window.Telegram.WebApp.MainButton.setParams({});
  307. updateBackgroundColor();
  308. receiveWebViewEvent('themeChanged');
  309. }
  310. }
  311. var lastWindowHeight = window.innerHeight;
  312. function onViewportChanged(eventType, eventData) {
  313. if (eventData.height) {
  314. window.removeEventListener('resize', onWindowResize);
  315. setViewportHeight(eventData);
  316. }
  317. }
  318. function onWindowResize(e) {
  319. if (lastWindowHeight != window.innerHeight) {
  320. lastWindowHeight = window.innerHeight;
  321. receiveWebViewEvent('viewportChanged', {
  322. isStateStable: true
  323. });
  324. }
  325. }
  326. function linkHandler(e) {
  327. if (e.metaKey || e.ctrlKey) return;
  328. var el = e.target;
  329. while (el.tagName != 'A' && el.parentNode) {
  330. el = el.parentNode;
  331. }
  332. if (el.tagName == 'A' &&
  333. el.target != '_blank' &&
  334. (el.protocol == 'http:' || el.protocol == 'https:') &&
  335. el.hostname == 't.me') {
  336. WebApp.openTgLink(el.href);
  337. e.preventDefault();
  338. }
  339. }
  340. function strTrim(str) {
  341. return str.toString().replace(/^\s+|\s+$/g, '');
  342. }
  343. function receiveWebViewEvent(eventType) {
  344. var args = Array.prototype.slice.call(arguments);
  345. eventType = args.shift();
  346. WebView.callEventCallbacks('webview:' + eventType, function(callback) {
  347. callback.apply(WebApp, args);
  348. });
  349. }
  350. function onWebViewEvent(eventType, callback) {
  351. WebView.onEvent('webview:' + eventType, callback);
  352. };
  353. function offWebViewEvent(eventType, callback) {
  354. WebView.offEvent('webview:' + eventType, callback);
  355. };
  356. function setCssProperty(name, value) {
  357. var root = document.documentElement;
  358. if (root && root.style && root.style.setProperty) {
  359. root.style.setProperty('--tg-' + name, value);
  360. }
  361. }
  362. function setThemeParams(theme_params) {
  363. // temp iOS fix
  364. if (theme_params.bg_color == '#1c1c1d' &&
  365. theme_params.bg_color == theme_params.secondary_bg_color) {
  366. theme_params.secondary_bg_color = '#2c2c2e';
  367. }
  368. var color;
  369. for (var key in theme_params) {
  370. if (color = parseColorToHex(theme_params[key])) {
  371. themeParams[key] = color;
  372. if (key == 'bg_color') {
  373. colorScheme = isColorDark(color) ? 'dark' : 'light'
  374. setCssProperty('color-scheme', colorScheme);
  375. }
  376. key = 'theme-' + key.split('_').join('-');
  377. setCssProperty(key, color);
  378. }
  379. }
  380. Utils.sessionStorageSet('themeParams', themeParams);
  381. }
  382. var webAppCallbacks = {};
  383. function generateCallbackId(len) {
  384. var tries = 100;
  385. while (--tries) {
  386. var id = '',
  387. chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
  388. chars_len = chars.length;
  389. for (var i = 0; i < len; i++) {
  390. id += chars[Math.floor(Math.random() * chars_len)];
  391. }
  392. if (!webAppCallbacks[id]) {
  393. webAppCallbacks[id] = {};
  394. return id;
  395. }
  396. }
  397. throw Error('WebAppCallbackIdGenerateFailed');
  398. }
  399. var viewportHeight = false,
  400. viewportStableHeight = false,
  401. isExpanded = true;
  402. function setViewportHeight(data) {
  403. if (typeof data !== 'undefined') {
  404. isExpanded = !!data.is_expanded;
  405. viewportHeight = data.height;
  406. if (data.is_state_stable) {
  407. viewportStableHeight = data.height;
  408. }
  409. receiveWebViewEvent('viewportChanged', {
  410. isStateStable: !!data.is_state_stable
  411. });
  412. }
  413. var height, stable_height;
  414. if (viewportHeight !== false) {
  415. height = (viewportHeight - mainButtonHeight) + 'px';
  416. } else {
  417. height = mainButtonHeight ? 'calc(100vh - ' + mainButtonHeight + 'px)' : '100vh';
  418. }
  419. if (viewportStableHeight !== false) {
  420. stable_height = (viewportStableHeight - mainButtonHeight) + 'px';
  421. } else {
  422. stable_height = mainButtonHeight ? 'calc(100vh - ' + mainButtonHeight + 'px)' : '100vh';
  423. }
  424. setCssProperty('viewport-height', height);
  425. setCssProperty('viewport-stable-height', stable_height);
  426. }
  427. var isClosingConfirmationEnabled = false;
  428. function setClosingConfirmation(need_confirmation) {
  429. if (!versionAtLeast('6.2')) {
  430. console.warn('[Telegram.WebApp] Closing confirmation is not supported in version ' + webAppVersion);
  431. return;
  432. }
  433. isClosingConfirmationEnabled = !!need_confirmation;
  434. WebView.postEvent('web_app_setup_closing_behavior', false, {
  435. need_confirmation: isClosingConfirmationEnabled
  436. });
  437. }
  438. var headerColorKey = 'bg_color',
  439. headerColor = null;
  440. function getHeaderColor() {
  441. if (headerColorKey == 'secondary_bg_color') {
  442. return themeParams.secondary_bg_color;
  443. } else if (headerColorKey == 'bg_color') {
  444. return themeParams.bg_color;
  445. }
  446. return headerColor;
  447. }
  448. function setHeaderColor(color) {
  449. if (!versionAtLeast('6.1')) {
  450. console.warn('[Telegram.WebApp] Header color is not supported in version ' + webAppVersion);
  451. return;
  452. }
  453. if (!versionAtLeast('6.9')) {
  454. if (themeParams.bg_color &&
  455. themeParams.bg_color == color) {
  456. color = 'bg_color';
  457. } else if (themeParams.secondary_bg_color &&
  458. themeParams.secondary_bg_color == color) {
  459. color = 'secondary_bg_color';
  460. }
  461. }
  462. var head_color = null,
  463. color_key = null;
  464. if (color == 'bg_color' || color == 'secondary_bg_color') {
  465. color_key = color;
  466. } else if (versionAtLeast('6.9')) {
  467. head_color = parseColorToHex(color);
  468. if (!head_color) {
  469. console.error('[Telegram.WebApp] Header color format is invalid', color);
  470. throw Error('WebAppHeaderColorInvalid');
  471. }
  472. }
  473. if (!versionAtLeast('6.9') &&
  474. color_key != 'bg_color' &&
  475. color_key != 'secondary_bg_color') {
  476. console.error(
  477. '[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\'',
  478. color);
  479. throw Error('WebAppHeaderColorKeyInvalid');
  480. }
  481. headerColorKey = color_key;
  482. headerColor = head_color;
  483. updateHeaderColor();
  484. }
  485. var appHeaderColorKey = null,
  486. appHeaderColor = null;
  487. function updateHeaderColor() {
  488. if (appHeaderColorKey != headerColorKey ||
  489. appHeaderColor != headerColor) {
  490. appHeaderColorKey = headerColorKey;
  491. appHeaderColor = headerColor;
  492. if (appHeaderColor) {
  493. WebView.postEvent('web_app_set_header_color', false, {
  494. color: headerColor
  495. });
  496. } else {
  497. WebView.postEvent('web_app_set_header_color', false, {
  498. color_key: headerColorKey
  499. });
  500. }
  501. }
  502. }
  503. var backgroundColor = 'bg_color';
  504. function getBackgroundColor() {
  505. if (backgroundColor == 'secondary_bg_color') {
  506. return themeParams.secondary_bg_color;
  507. } else if (backgroundColor == 'bg_color') {
  508. return themeParams.bg_color;
  509. }
  510. return backgroundColor;
  511. }
  512. function setBackgroundColor(color) {
  513. if (!versionAtLeast('6.1')) {
  514. console.warn('[Telegram.WebApp] Background color is not supported in version ' + webAppVersion);
  515. return;
  516. }
  517. var bg_color;
  518. if (color == 'bg_color' || color == 'secondary_bg_color') {
  519. bg_color = color;
  520. } else {
  521. bg_color = parseColorToHex(color);
  522. if (!bg_color) {
  523. console.error('[Telegram.WebApp] Background color format is invalid', color);
  524. throw Error('WebAppBackgroundColorInvalid');
  525. }
  526. }
  527. backgroundColor = bg_color;
  528. updateBackgroundColor();
  529. }
  530. var appBackgroundColor = null;
  531. function updateBackgroundColor() {
  532. var color = getBackgroundColor();
  533. if (appBackgroundColor != color) {
  534. appBackgroundColor = color;
  535. WebView.postEvent('web_app_set_background_color', false, {
  536. color: color
  537. });
  538. }
  539. }
  540. function parseColorToHex(color) {
  541. color += '';
  542. var match;
  543. if (match = /^\s*#([0-9a-f]{6})\s*$/i.exec(color)) {
  544. return '#' + match[1].toLowerCase();
  545. } else if (match = /^\s*#([0-9a-f])([0-9a-f])([0-9a-f])\s*$/i.exec(color)) {
  546. return ('#' + match[1] + match[1] + match[2] + match[2] + match[3] + match[3]).toLowerCase();
  547. } else if (match = /^\s*rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+\.{0,1}\d*))?\)\s*$/.exec(color)) {
  548. var r = parseInt(match[1]),
  549. g = parseInt(match[2]),
  550. b = parseInt(match[3]);
  551. r = (r < 16 ? '0' : '') + r.toString(16);
  552. g = (g < 16 ? '0' : '') + g.toString(16);
  553. b = (b < 16 ? '0' : '') + b.toString(16);
  554. return '#' + r + g + b;
  555. }
  556. return false;
  557. }
  558. function isColorDark(rgb) {
  559. rgb = rgb.replace(/[\s#]/g, '');
  560. if (rgb.length == 3) {
  561. rgb = rgb[0] + rgb[0] + rgb[1] + rgb[1] + rgb[2] + rgb[2];
  562. }
  563. var r = parseInt(rgb.substr(0, 2), 16);
  564. var g = parseInt(rgb.substr(2, 2), 16);
  565. var b = parseInt(rgb.substr(4, 2), 16);
  566. var hsp = Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b));
  567. return hsp < 120;
  568. }
  569. function versionCompare(v1, v2) {
  570. if (typeof v1 !== 'string') v1 = '';
  571. if (typeof v2 !== 'string') v2 = '';
  572. v1 = v1.replace(/^\s+|\s+$/g, '').split('.');
  573. v2 = v2.replace(/^\s+|\s+$/g, '').split('.');
  574. var a = Math.max(v1.length, v2.length),
  575. i, p1, p2;
  576. for (i = 0; i < a; i++) {
  577. p1 = parseInt(v1[i]) || 0;
  578. p2 = parseInt(v2[i]) || 0;
  579. if (p1 == p2) continue;
  580. if (p1 > p2) return 1;
  581. return -1;
  582. }
  583. return 0;
  584. }
  585. function versionAtLeast(ver) {
  586. return versionCompare(webAppVersion, ver) >= 0;
  587. }
  588. function byteLength(str) {
  589. if (window.Blob) {
  590. try {
  591. return new Blob([str]).size;
  592. } catch (e) {}
  593. }
  594. var s = str.length;
  595. for (var i = str.length - 1; i >= 0; i--) {
  596. var code = str.charCodeAt(i);
  597. if (code > 0x7f && code <= 0x7ff) s++;
  598. else if (code > 0x7ff && code <= 0xffff) s += 2;
  599. if (code >= 0xdc00 && code <= 0xdfff) i--;
  600. }
  601. return s;
  602. }
  603. var BackButton = (function() {
  604. var isVisible = false;
  605. var backButton = {};
  606. Object.defineProperty(backButton, 'isVisible', {
  607. set: function(val) {
  608. setParams({
  609. is_visible: val
  610. });
  611. },
  612. get: function() {
  613. return isVisible;
  614. },
  615. enumerable: true
  616. });
  617. var curButtonState = null;
  618. WebView.onEvent('back_button_pressed', onBackButtonPressed);
  619. function onBackButtonPressed() {
  620. receiveWebViewEvent('backButtonClicked');
  621. }
  622. function buttonParams() {
  623. return {
  624. is_visible: isVisible
  625. };
  626. }
  627. function buttonState(btn_params) {
  628. if (typeof btn_params === 'undefined') {
  629. btn_params = buttonParams();
  630. }
  631. return JSON.stringify(btn_params);
  632. }
  633. function buttonCheckVersion() {
  634. if (!versionAtLeast('6.1')) {
  635. console.warn('[Telegram.WebApp] BackButton is not supported in version ' + webAppVersion);
  636. return false;
  637. }
  638. return true;
  639. }
  640. function updateButton() {
  641. var btn_params = buttonParams();
  642. var btn_state = buttonState(btn_params);
  643. if (curButtonState === btn_state) {
  644. return;
  645. }
  646. curButtonState = btn_state;
  647. WebView.postEvent('web_app_setup_back_button', false, btn_params);
  648. }
  649. function setParams(params) {
  650. if (!buttonCheckVersion()) {
  651. return backButton;
  652. }
  653. if (typeof params.is_visible !== 'undefined') {
  654. isVisible = !!params.is_visible;
  655. }
  656. updateButton();
  657. return backButton;
  658. }
  659. backButton.onClick = function(callback) {
  660. if (buttonCheckVersion()) {
  661. onWebViewEvent('backButtonClicked', callback);
  662. }
  663. return backButton;
  664. };
  665. backButton.offClick = function(callback) {
  666. if (buttonCheckVersion()) {
  667. offWebViewEvent('backButtonClicked', callback);
  668. }
  669. return backButton;
  670. };
  671. backButton.show = function() {
  672. return setParams({
  673. is_visible: true
  674. });
  675. };
  676. backButton.hide = function() {
  677. return setParams({
  678. is_visible: false
  679. });
  680. };
  681. return backButton;
  682. })();
  683. var mainButtonHeight = 0;
  684. var MainButton = (function() {
  685. var isVisible = false;
  686. var isActive = true;
  687. var isProgressVisible = false;
  688. var buttonText = 'CONTINUE';
  689. var buttonColor = false;
  690. var buttonTextColor = false;
  691. var mainButton = {};
  692. Object.defineProperty(mainButton, 'text', {
  693. set: function(val) {
  694. mainButton.setParams({
  695. text: val
  696. });
  697. },
  698. get: function() {
  699. return buttonText;
  700. },
  701. enumerable: true
  702. });
  703. Object.defineProperty(mainButton, 'color', {
  704. set: function(val) {
  705. mainButton.setParams({
  706. color: val
  707. });
  708. },
  709. get: function() {
  710. return buttonColor || themeParams.button_color || '#2481cc';
  711. },
  712. enumerable: true
  713. });
  714. Object.defineProperty(mainButton, 'textColor', {
  715. set: function(val) {
  716. mainButton.setParams({
  717. text_color: val
  718. });
  719. },
  720. get: function() {
  721. return buttonTextColor || themeParams.button_text_color || '#ffffff';
  722. },
  723. enumerable: true
  724. });
  725. Object.defineProperty(mainButton, 'isVisible', {
  726. set: function(val) {
  727. mainButton.setParams({
  728. is_visible: val
  729. });
  730. },
  731. get: function() {
  732. return isVisible;
  733. },
  734. enumerable: true
  735. });
  736. Object.defineProperty(mainButton, 'isProgressVisible', {
  737. get: function() {
  738. return isProgressVisible;
  739. },
  740. enumerable: true
  741. });
  742. Object.defineProperty(mainButton, 'isActive', {
  743. set: function(val) {
  744. mainButton.setParams({
  745. is_active: val
  746. });
  747. },
  748. get: function() {
  749. return isActive;
  750. },
  751. enumerable: true
  752. });
  753. var curButtonState = null;
  754. WebView.onEvent('main_button_pressed', onMainButtonPressed);
  755. var debugBtn = null,
  756. debugBtnStyle = {};
  757. if (initParams.tgWebAppDebug) {
  758. debugBtn = document.createElement('tg-main-button');
  759. debugBtnStyle = {
  760. font: '600 14px/18px sans-serif',
  761. display: 'none',
  762. width: '100%',
  763. height: '48px',
  764. borderRadius: '0',
  765. background: 'no-repeat right center',
  766. position: 'fixed',
  767. left: '0',
  768. right: '0',
  769. bottom: '0',
  770. margin: '0',
  771. padding: '15px 20px',
  772. textAlign: 'center',
  773. boxSizing: 'border-box',
  774. zIndex: '10000'
  775. };
  776. for (var k in debugBtnStyle) {
  777. debugBtn.style[k] = debugBtnStyle[k];
  778. }
  779. document.addEventListener('DOMContentLoaded', function onDomLoaded(event) {
  780. document.removeEventListener('DOMContentLoaded', onDomLoaded);
  781. document.body.appendChild(debugBtn);
  782. debugBtn.addEventListener('click', onMainButtonPressed, false);
  783. });
  784. }
  785. function onMainButtonPressed() {
  786. if (isActive) {
  787. receiveWebViewEvent('mainButtonClicked');
  788. }
  789. }
  790. function buttonParams() {
  791. var color = mainButton.color;
  792. var text_color = mainButton.textColor;
  793. return isVisible ? {
  794. is_visible: true,
  795. is_active: isActive,
  796. is_progress_visible: isProgressVisible,
  797. text: buttonText,
  798. color: color,
  799. text_color: text_color
  800. } : {
  801. is_visible: false
  802. };
  803. }
  804. function buttonState(btn_params) {
  805. if (typeof btn_params === 'undefined') {
  806. btn_params = buttonParams();
  807. }
  808. return JSON.stringify(btn_params);
  809. }
  810. function updateButton() {
  811. var btn_params = buttonParams();
  812. var btn_state = buttonState(btn_params);
  813. if (curButtonState === btn_state) {
  814. return;
  815. }
  816. curButtonState = btn_state;
  817. WebView.postEvent('web_app_setup_main_button', false, btn_params);
  818. if (initParams.tgWebAppDebug) {
  819. updateDebugButton(btn_params);
  820. }
  821. }
  822. function updateDebugButton(btn_params) {
  823. if (btn_params.is_visible) {
  824. debugBtn.style.display = 'block';
  825. mainButtonHeight = 48;
  826. debugBtn.style.opacity = btn_params.is_active ? '1' : '0.8';
  827. debugBtn.style.cursor = btn_params.is_active ? 'pointer' : 'auto';
  828. debugBtn.disabled = !btn_params.is_active;
  829. debugBtn.innerText = btn_params.text;
  830. debugBtn.style.backgroundImage = btn_params.is_progress_visible ?
  831. "url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20viewport%3D%220%200%2048%2048%22%20width%3D%2248px%22%20height%3D%2248px%22%3E%3Ccircle%20cx%3D%2250%25%22%20cy%3D%2250%25%22%20stroke%3D%22%23fff%22%20stroke-width%3D%222.25%22%20stroke-linecap%3D%22round%22%20fill%3D%22none%22%20stroke-dashoffset%3D%22106%22%20r%3D%229%22%20stroke-dasharray%3D%2256.52%22%20rotate%3D%22-90%22%3E%3Canimate%20attributeName%3D%22stroke-dashoffset%22%20attributeType%3D%22XML%22%20dur%3D%22360s%22%20from%3D%220%22%20to%3D%2212500%22%20repeatCount%3D%22indefinite%22%3E%3C%2Fanimate%3E%3CanimateTransform%20attributeName%3D%22transform%22%20attributeType%3D%22XML%22%20type%3D%22rotate%22%20dur%3D%221s%22%20from%3D%22-90%2024%2024%22%20to%3D%22630%2024%2024%22%20repeatCount%3D%22indefinite%22%3E%3C%2FanimateTransform%3E%3C%2Fcircle%3E%3C%2Fsvg%3E')" :
  832. 'none';
  833. debugBtn.style.backgroundColor = btn_params.color;
  834. debugBtn.style.color = btn_params.text_color;
  835. } else {
  836. debugBtn.style.display = 'none';
  837. mainButtonHeight = 0;
  838. }
  839. if (document.documentElement) {
  840. document.documentElement.style.boxSizing = 'border-box';
  841. document.documentElement.style.paddingBottom = mainButtonHeight + 'px';
  842. }
  843. setViewportHeight();
  844. }
  845. function setParams(params) {
  846. if (typeof params.text !== 'undefined') {
  847. var text = strTrim(params.text);
  848. if (!text.length) {
  849. console.error('[Telegram.WebApp] Main button text is required', params.text);
  850. throw Error('WebAppMainButtonParamInvalid');
  851. }
  852. if (text.length > 64) {
  853. console.error('[Telegram.WebApp] Main button text is too long', text);
  854. throw Error('WebAppMainButtonParamInvalid');
  855. }
  856. buttonText = text;
  857. }
  858. if (typeof params.color !== 'undefined') {
  859. if (params.color === false ||
  860. params.color === null) {
  861. buttonColor = false;
  862. } else {
  863. var color = parseColorToHex(params.color);
  864. if (!color) {
  865. console.error('[Telegram.WebApp] Main button color format is invalid', params
  866. .color);
  867. throw Error('WebAppMainButtonParamInvalid');
  868. }
  869. buttonColor = color;
  870. }
  871. }
  872. if (typeof params.text_color !== 'undefined') {
  873. if (params.text_color === false ||
  874. params.text_color === null) {
  875. buttonTextColor = false;
  876. } else {
  877. var text_color = parseColorToHex(params.text_color);
  878. if (!text_color) {
  879. console.error('[Telegram.WebApp] Main button text color format is invalid', params
  880. .text_color);
  881. throw Error('WebAppMainButtonParamInvalid');
  882. }
  883. buttonTextColor = text_color;
  884. }
  885. }
  886. if (typeof params.is_visible !== 'undefined') {
  887. if (params.is_visible &&
  888. !mainButton.text.length) {
  889. console.error('[Telegram.WebApp] Main button text is required');
  890. throw Error('WebAppMainButtonParamInvalid');
  891. }
  892. isVisible = !!params.is_visible;
  893. }
  894. if (typeof params.is_active !== 'undefined') {
  895. isActive = !!params.is_active;
  896. }
  897. updateButton();
  898. return mainButton;
  899. }
  900. mainButton.setText = function(text) {
  901. return mainButton.setParams({
  902. text: text
  903. });
  904. };
  905. mainButton.onClick = function(callback) {
  906. onWebViewEvent('mainButtonClicked', callback);
  907. return mainButton;
  908. };
  909. mainButton.offClick = function(callback) {
  910. offWebViewEvent('mainButtonClicked', callback);
  911. return mainButton;
  912. };
  913. mainButton.show = function() {
  914. return mainButton.setParams({
  915. is_visible: true
  916. });
  917. };
  918. mainButton.hide = function() {
  919. return mainButton.setParams({
  920. is_visible: false
  921. });
  922. };
  923. mainButton.enable = function() {
  924. return mainButton.setParams({
  925. is_active: true
  926. });
  927. };
  928. mainButton.disable = function() {
  929. return mainButton.setParams({
  930. is_active: false
  931. });
  932. };
  933. mainButton.showProgress = function(leaveActive) {
  934. isActive = !!leaveActive;
  935. isProgressVisible = true;
  936. updateButton();
  937. return mainButton;
  938. };
  939. mainButton.hideProgress = function() {
  940. if (!mainButton.isActive) {
  941. isActive = true;
  942. }
  943. isProgressVisible = false;
  944. updateButton();
  945. return mainButton;
  946. }
  947. mainButton.setParams = setParams;
  948. return mainButton;
  949. })();
  950. var SettingsButton = (function() {
  951. var isVisible = false;
  952. var settingsButton = {};
  953. Object.defineProperty(settingsButton, 'isVisible', {
  954. set: function(val) {
  955. setParams({
  956. is_visible: val
  957. });
  958. },
  959. get: function() {
  960. return isVisible;
  961. },
  962. enumerable: true
  963. });
  964. var curButtonState = null;
  965. WebView.onEvent('settings_button_pressed', onSettingsButtonPressed);
  966. function onSettingsButtonPressed() {
  967. receiveWebViewEvent('settingsButtonClicked');
  968. }
  969. function buttonParams() {
  970. return {
  971. is_visible: isVisible
  972. };
  973. }
  974. function buttonState(btn_params) {
  975. if (typeof btn_params === 'undefined') {
  976. btn_params = buttonParams();
  977. }
  978. return JSON.stringify(btn_params);
  979. }
  980. function buttonCheckVersion() {
  981. if (!versionAtLeast('6.10')) {
  982. console.warn('[Telegram.WebApp] SettingsButton is not supported in version ' +
  983. webAppVersion);
  984. return false;
  985. }
  986. return true;
  987. }
  988. function updateButton() {
  989. var btn_params = buttonParams();
  990. var btn_state = buttonState(btn_params);
  991. if (curButtonState === btn_state) {
  992. return;
  993. }
  994. curButtonState = btn_state;
  995. WebView.postEvent('web_app_setup_settings_button', false, btn_params);
  996. }
  997. function setParams(params) {
  998. if (!buttonCheckVersion()) {
  999. return settingsButton;
  1000. }
  1001. if (typeof params.is_visible !== 'undefined') {
  1002. isVisible = !!params.is_visible;
  1003. }
  1004. updateButton();
  1005. return settingsButton;
  1006. }
  1007. settingsButton.onClick = function(callback) {
  1008. if (buttonCheckVersion()) {
  1009. onWebViewEvent('settingsButtonClicked', callback);
  1010. }
  1011. return settingsButton;
  1012. };
  1013. settingsButton.offClick = function(callback) {
  1014. if (buttonCheckVersion()) {
  1015. offWebViewEvent('settingsButtonClicked', callback);
  1016. }
  1017. return settingsButton;
  1018. };
  1019. settingsButton.show = function() {
  1020. return setParams({
  1021. is_visible: true
  1022. });
  1023. };
  1024. settingsButton.hide = function() {
  1025. return setParams({
  1026. is_visible: false
  1027. });
  1028. };
  1029. return settingsButton;
  1030. })();
  1031. var HapticFeedback = (function() {
  1032. var hapticFeedback = {};
  1033. function triggerFeedback(params) {
  1034. if (!versionAtLeast('6.1')) {
  1035. console.warn('[Telegram.WebApp] HapticFeedback is not supported in version ' +
  1036. webAppVersion);
  1037. return hapticFeedback;
  1038. }
  1039. if (params.type == 'impact') {
  1040. if (params.impact_style != 'light' &&
  1041. params.impact_style != 'medium' &&
  1042. params.impact_style != 'heavy' &&
  1043. params.impact_style != 'rigid' &&
  1044. params.impact_style != 'soft') {
  1045. console.error('[Telegram.WebApp] Haptic impact style is invalid', params.impact_style);
  1046. throw Error('WebAppHapticImpactStyleInvalid');
  1047. }
  1048. } else if (params.type == 'notification') {
  1049. if (params.notification_type != 'error' &&
  1050. params.notification_type != 'success' &&
  1051. params.notification_type != 'warning') {
  1052. console.error('[Telegram.WebApp] Haptic notification type is invalid', params
  1053. .notification_type);
  1054. throw Error('WebAppHapticNotificationTypeInvalid');
  1055. }
  1056. } else if (params.type == 'selection_change') {
  1057. // no params needed
  1058. } else {
  1059. console.error('[Telegram.WebApp] Haptic feedback type is invalid', params.type);
  1060. throw Error('WebAppHapticFeedbackTypeInvalid');
  1061. }
  1062. WebView.postEvent('web_app_trigger_haptic_feedback', false, params);
  1063. return hapticFeedback;
  1064. }
  1065. hapticFeedback.impactOccurred = function(style) {
  1066. return triggerFeedback({
  1067. type: 'impact',
  1068. impact_style: style
  1069. });
  1070. };
  1071. hapticFeedback.notificationOccurred = function(type) {
  1072. return triggerFeedback({
  1073. type: 'notification',
  1074. notification_type: type
  1075. });
  1076. };
  1077. hapticFeedback.selectionChanged = function() {
  1078. return triggerFeedback({
  1079. type: 'selection_change'
  1080. });
  1081. };
  1082. return hapticFeedback;
  1083. })();
  1084. var CloudStorage = (function() {
  1085. var cloudStorage = {};
  1086. function invokeStorageMethod(method, params, callback) {
  1087. if (!versionAtLeast('6.9')) {
  1088. console.error('[Telegram.WebApp] CloudStorage is not supported in version ' +
  1089. webAppVersion);
  1090. throw Error('WebAppMethodUnsupported');
  1091. }
  1092. invokeCustomMethod(method, params, callback);
  1093. return cloudStorage;
  1094. }
  1095. cloudStorage.setItem = function(key, value, callback) {
  1096. return invokeStorageMethod('saveStorageValue', {
  1097. key: key,
  1098. value: value
  1099. }, callback);
  1100. };
  1101. cloudStorage.getItem = function(key, callback) {
  1102. return cloudStorage.getItems([key], callback ? function(err, res) {
  1103. if (err) callback(err);
  1104. else callback(null, res[key]);
  1105. } : null);
  1106. };
  1107. cloudStorage.getItems = function(keys, callback) {
  1108. return invokeStorageMethod('getStorageValues', {
  1109. keys: keys
  1110. }, callback);
  1111. };
  1112. cloudStorage.removeItem = function(key, callback) {
  1113. return cloudStorage.removeItems([key], callback);
  1114. };
  1115. cloudStorage.removeItems = function(keys, callback) {
  1116. return invokeStorageMethod('deleteStorageValues', {
  1117. keys: keys
  1118. }, callback);
  1119. };
  1120. cloudStorage.getKeys = function(callback) {
  1121. return invokeStorageMethod('getStorageKeys', {}, callback);
  1122. };
  1123. return cloudStorage;
  1124. })();
  1125. var webAppInvoices = {};
  1126. function onInvoiceClosed(eventType, eventData) {
  1127. if (eventData.slug && webAppInvoices[eventData.slug]) {
  1128. var invoiceData = webAppInvoices[eventData.slug];
  1129. delete webAppInvoices[eventData.slug];
  1130. if (invoiceData.callback) {
  1131. invoiceData.callback(eventData.status);
  1132. }
  1133. receiveWebViewEvent('invoiceClosed', {
  1134. url: invoiceData.url,
  1135. status: eventData.status
  1136. });
  1137. }
  1138. }
  1139. var webAppPopupOpened = false;
  1140. function onPopupClosed(eventType, eventData) {
  1141. if (webAppPopupOpened) {
  1142. var popupData = webAppPopupOpened;
  1143. webAppPopupOpened = false;
  1144. var button_id = null;
  1145. if (typeof eventData.button_id !== 'undefined') {
  1146. button_id = eventData.button_id;
  1147. }
  1148. if (popupData.callback) {
  1149. popupData.callback(button_id);
  1150. }
  1151. receiveWebViewEvent('popupClosed', {
  1152. button_id: button_id
  1153. });
  1154. }
  1155. }
  1156. var webAppScanQrPopupOpened = false;
  1157. function onQrTextReceived(eventType, eventData) {
  1158. if (webAppScanQrPopupOpened) {
  1159. var popupData = webAppScanQrPopupOpened;
  1160. var data = null;
  1161. if (typeof eventData.data !== 'undefined') {
  1162. data = eventData.data;
  1163. }
  1164. if (popupData.callback) {
  1165. if (popupData.callback(data)) {
  1166. webAppScanQrPopupOpened = false;
  1167. WebView.postEvent('web_app_close_scan_qr_popup', false);
  1168. }
  1169. }
  1170. receiveWebViewEvent('qrTextReceived', {
  1171. data: data
  1172. });
  1173. }
  1174. }
  1175. function onScanQrPopupClosed(eventType, eventData) {
  1176. webAppScanQrPopupOpened = false;
  1177. }
  1178. function onClipboardTextReceived(eventType, eventData) {
  1179. if (eventData.req_id && webAppCallbacks[eventData.req_id]) {
  1180. var requestData = webAppCallbacks[eventData.req_id];
  1181. delete webAppCallbacks[eventData.req_id];
  1182. var data = null;
  1183. if (typeof eventData.data !== 'undefined') {
  1184. data = eventData.data;
  1185. }
  1186. if (requestData.callback) {
  1187. requestData.callback(data);
  1188. }
  1189. receiveWebViewEvent('clipboardTextReceived', {
  1190. data: data
  1191. });
  1192. }
  1193. }
  1194. var WebAppWriteAccessRequested = false;
  1195. function onWriteAccessRequested(eventType, eventData) {
  1196. if (WebAppWriteAccessRequested) {
  1197. var requestData = WebAppWriteAccessRequested;
  1198. WebAppWriteAccessRequested = false;
  1199. if (requestData.callback) {
  1200. requestData.callback(eventData.status == 'allowed');
  1201. }
  1202. receiveWebViewEvent('writeAccessRequested', {
  1203. status: eventData.status
  1204. });
  1205. }
  1206. }
  1207. function getRequestedContact(callback, timeout) {
  1208. var reqTo, fallbackTo, reqDelay = 0;
  1209. var reqInvoke = function() {
  1210. invokeCustomMethod('getRequestedContact', {}, function(err, res) {
  1211. if (res && res.length) {
  1212. clearTimeout(fallbackTo);
  1213. callback(res);
  1214. } else {
  1215. reqDelay += 50;
  1216. reqTo = setTimeout(reqInvoke, reqDelay);
  1217. }
  1218. });
  1219. };
  1220. var fallbackInvoke = function() {
  1221. clearTimeout(reqTo);
  1222. callback('');
  1223. };
  1224. fallbackTo = setTimeout(fallbackInvoke, timeout);
  1225. reqInvoke();
  1226. }
  1227. var WebAppContactRequested = false;
  1228. function onPhoneRequested(eventType, eventData) {
  1229. if (WebAppContactRequested) {
  1230. var requestData = WebAppContactRequested;
  1231. WebAppContactRequested = false;
  1232. var requestSent = eventData.status == 'sent';
  1233. var webViewEvent = {
  1234. status: eventData.status
  1235. };
  1236. if (requestSent) {
  1237. getRequestedContact(function(res) {
  1238. if (res && res.length) {
  1239. webViewEvent.response = res;
  1240. webViewEvent.responseUnsafe = Utils.urlParseQueryString(res);
  1241. for (var key in webViewEvent.responseUnsafe) {
  1242. var val = webViewEvent.responseUnsafe[key];
  1243. try {
  1244. if (val.substr(0, 1) == '{' && val.substr(-1) == '}' ||
  1245. val.substr(0, 1) == '[' && val.substr(-1) == ']') {
  1246. webViewEvent.responseUnsafe[key] = JSON.parse(val);
  1247. }
  1248. } catch (e) {}
  1249. }
  1250. }
  1251. if (requestData.callback) {
  1252. requestData.callback(requestSent, webViewEvent);
  1253. }
  1254. receiveWebViewEvent('contactRequested', webViewEvent);
  1255. }, 3000);
  1256. } else {
  1257. if (requestData.callback) {
  1258. requestData.callback(requestSent, webViewEvent);
  1259. }
  1260. receiveWebViewEvent('contactRequested', webViewEvent);
  1261. }
  1262. }
  1263. }
  1264. function onCustomMethodInvoked(eventType, eventData) {
  1265. if (eventData.req_id && webAppCallbacks[eventData.req_id]) {
  1266. var requestData = webAppCallbacks[eventData.req_id];
  1267. delete webAppCallbacks[eventData.req_id];
  1268. var res = null,
  1269. err = null;
  1270. if (typeof eventData.result !== 'undefined') {
  1271. res = eventData.result;
  1272. }
  1273. if (typeof eventData.error !== 'undefined') {
  1274. err = eventData.error;
  1275. }
  1276. if (requestData.callback) {
  1277. requestData.callback(err, res);
  1278. }
  1279. }
  1280. }
  1281. function invokeCustomMethod(method, params, callback) {
  1282. if (!versionAtLeast('6.9')) {
  1283. console.error('[Telegram.WebApp] Method invokeCustomMethod is not supported in version ' +
  1284. webAppVersion);
  1285. throw Error('WebAppMethodUnsupported');
  1286. }
  1287. var req_id = generateCallbackId(16);
  1288. var req_params = {
  1289. req_id: req_id,
  1290. method: method,
  1291. params: params || {}
  1292. };
  1293. webAppCallbacks[req_id] = {
  1294. callback: callback
  1295. };
  1296. WebView.postEvent('web_app_invoke_custom_method', false, req_params);
  1297. };
  1298. if (!window.Telegram) {
  1299. window.Telegram = {};
  1300. }
  1301. Object.defineProperty(WebApp, 'initData', {
  1302. get: function() {
  1303. return webAppInitData;
  1304. },
  1305. enumerable: true
  1306. });
  1307. Object.defineProperty(WebApp, 'initDataUnsafe', {
  1308. get: function() {
  1309. return webAppInitDataUnsafe;
  1310. },
  1311. enumerable: true
  1312. });
  1313. Object.defineProperty(WebApp, 'version', {
  1314. get: function() {
  1315. return webAppVersion;
  1316. },
  1317. enumerable: true
  1318. });
  1319. Object.defineProperty(WebApp, 'platform', {
  1320. get: function() {
  1321. return webAppPlatform;
  1322. },
  1323. enumerable: true
  1324. });
  1325. Object.defineProperty(WebApp, 'colorScheme', {
  1326. get: function() {
  1327. return colorScheme;
  1328. },
  1329. enumerable: true
  1330. });
  1331. Object.defineProperty(WebApp, 'themeParams', {
  1332. get: function() {
  1333. return themeParams;
  1334. },
  1335. enumerable: true
  1336. });
  1337. Object.defineProperty(WebApp, 'isExpanded', {
  1338. get: function() {
  1339. return isExpanded;
  1340. },
  1341. enumerable: true
  1342. });
  1343. Object.defineProperty(WebApp, 'viewportHeight', {
  1344. get: function() {
  1345. return (viewportHeight === false ? window.innerHeight : viewportHeight) - mainButtonHeight;
  1346. },
  1347. enumerable: true
  1348. });
  1349. Object.defineProperty(WebApp, 'viewportStableHeight', {
  1350. get: function() {
  1351. return (viewportStableHeight === false ? window.innerHeight : viewportStableHeight) -
  1352. mainButtonHeight;
  1353. },
  1354. enumerable: true
  1355. });
  1356. Object.defineProperty(WebApp, 'isClosingConfirmationEnabled', {
  1357. set: function(val) {
  1358. setClosingConfirmation(val);
  1359. },
  1360. get: function() {
  1361. return isClosingConfirmationEnabled;
  1362. },
  1363. enumerable: true
  1364. });
  1365. Object.defineProperty(WebApp, 'headerColor', {
  1366. set: function(val) {
  1367. setHeaderColor(val);
  1368. },
  1369. get: function() {
  1370. return getHeaderColor();
  1371. },
  1372. enumerable: true
  1373. });
  1374. Object.defineProperty(WebApp, 'backgroundColor', {
  1375. set: function(val) {
  1376. setBackgroundColor(val);
  1377. },
  1378. get: function() {
  1379. return getBackgroundColor();
  1380. },
  1381. enumerable: true
  1382. });
  1383. Object.defineProperty(WebApp, 'BackButton', {
  1384. value: BackButton,
  1385. enumerable: true
  1386. });
  1387. Object.defineProperty(WebApp, 'MainButton', {
  1388. value: MainButton,
  1389. enumerable: true
  1390. });
  1391. Object.defineProperty(WebApp, 'SettingsButton', {
  1392. value: SettingsButton,
  1393. enumerable: true
  1394. });
  1395. Object.defineProperty(WebApp, 'HapticFeedback', {
  1396. value: HapticFeedback,
  1397. enumerable: true
  1398. });
  1399. Object.defineProperty(WebApp, 'CloudStorage', {
  1400. value: CloudStorage,
  1401. enumerable: true
  1402. });
  1403. WebApp.setHeaderColor = function(color_key) {
  1404. WebApp.headerColor = color_key;
  1405. };
  1406. WebApp.setBackgroundColor = function(color) {
  1407. WebApp.backgroundColor = color;
  1408. };
  1409. WebApp.enableClosingConfirmation = function() {
  1410. WebApp.isClosingConfirmationEnabled = true;
  1411. };
  1412. WebApp.disableClosingConfirmation = function() {
  1413. WebApp.isClosingConfirmationEnabled = false;
  1414. };
  1415. WebApp.isVersionAtLeast = function(ver) {
  1416. return versionAtLeast(ver);
  1417. };
  1418. WebApp.onEvent = function(eventType, callback) {
  1419. onWebViewEvent(eventType, callback);
  1420. };
  1421. WebApp.offEvent = function(eventType, callback) {
  1422. offWebViewEvent(eventType, callback);
  1423. };
  1424. WebApp.sendData = function(data) {
  1425. if (!data || !data.length) {
  1426. console.error('[Telegram.WebApp] Data is required', data);
  1427. throw Error('WebAppDataInvalid');
  1428. }
  1429. if (byteLength(data) > 4096) {
  1430. console.error('[Telegram.WebApp] Data is too long', data);
  1431. throw Error('WebAppDataInvalid');
  1432. }
  1433. WebView.postEvent('web_app_data_send', false, {
  1434. data: data
  1435. });
  1436. };
  1437. WebApp.switchInlineQuery = function(query, choose_chat_types) {
  1438. if (!versionAtLeast('6.6')) {
  1439. console.error('[Telegram.WebApp] Method switchInlineQuery is not supported in version ' +
  1440. webAppVersion);
  1441. throw Error('WebAppMethodUnsupported');
  1442. }
  1443. if (!initParams.tgWebAppBotInline) {
  1444. console.error(
  1445. '[Telegram.WebApp] Inline mode is disabled for this bot. Read more about inline mode: https://core.telegram.org/bots/inline'
  1446. );
  1447. throw Error('WebAppInlineModeDisabled');
  1448. }
  1449. query = query || '';
  1450. if (query.length > 256) {
  1451. console.error('[Telegram.WebApp] Inline query is too long', query);
  1452. throw Error('WebAppInlineQueryInvalid');
  1453. }
  1454. var chat_types = [];
  1455. if (choose_chat_types) {
  1456. if (!Array.isArray(choose_chat_types)) {
  1457. console.error('[Telegram.WebApp] Choose chat types should be an array', choose_chat_types);
  1458. throw Error('WebAppInlineChooseChatTypesInvalid');
  1459. }
  1460. var good_types = {
  1461. users: 1,
  1462. bots: 1,
  1463. groups: 1,
  1464. channels: 1
  1465. };
  1466. for (var i = 0; i < choose_chat_types.length; i++) {
  1467. var chat_type = choose_chat_types[i];
  1468. if (!good_types[chat_type]) {
  1469. console.error('[Telegram.WebApp] Choose chat type is invalid', chat_type);
  1470. throw Error('WebAppInlineChooseChatTypeInvalid');
  1471. }
  1472. if (good_types[chat_type] != 2) {
  1473. good_types[chat_type] = 2;
  1474. chat_types.push(chat_type);
  1475. }
  1476. }
  1477. }
  1478. WebView.postEvent('web_app_switch_inline_query', false, {
  1479. query: query,
  1480. chat_types: chat_types
  1481. });
  1482. };
  1483. WebApp.openLink = function(url, options) {
  1484. var a = document.createElement('A');
  1485. a.href = url;
  1486. if (a.protocol != 'http:' &&
  1487. a.protocol != 'https:') {
  1488. console.error('[Telegram.WebApp] Url protocol is not supported', url);
  1489. throw Error('WebAppTgUrlInvalid');
  1490. }
  1491. var url = a.href;
  1492. options = options || {};
  1493. if (versionAtLeast('6.1')) {
  1494. WebView.postEvent('web_app_open_link', false, {
  1495. url: url,
  1496. try_instant_view: versionAtLeast('6.4') && !!options.try_instant_view
  1497. });
  1498. } else {
  1499. window.open(url, '_blank');
  1500. }
  1501. };
  1502. WebApp.openTelegramLink = function(url) {
  1503. var a = document.createElement('A');
  1504. a.href = url;
  1505. if (a.protocol != 'http:' &&
  1506. a.protocol != 'https:') {
  1507. console.error('[Telegram.WebApp] Url protocol is not supported', url);
  1508. throw Error('WebAppTgUrlInvalid');
  1509. }
  1510. if (a.hostname != 't.me') {
  1511. console.error('[Telegram.WebApp] Url host is not supported', url);
  1512. throw Error('WebAppTgUrlInvalid');
  1513. }
  1514. var path_full = a.pathname + a.search;
  1515. if (isIframe || versionAtLeast('6.1')) {
  1516. WebView.postEvent('web_app_open_tg_link', false, {
  1517. path_full: path_full
  1518. });
  1519. } else {
  1520. location.href = 'https://t.me' + path_full;
  1521. }
  1522. };
  1523. WebApp.openInvoice = function(url, callback) {
  1524. var a = document.createElement('A'),
  1525. match, slug;
  1526. a.href = url;
  1527. if (a.protocol != 'http:' &&
  1528. a.protocol != 'https:' ||
  1529. a.hostname != 't.me' ||
  1530. !(match = a.pathname.match(/^\/(\$|invoice\/)([A-Za-z0-9\-_=]+)$/)) ||
  1531. !(slug = match[2])) {
  1532. console.error('[Telegram.WebApp] Invoice url is invalid', url);
  1533. throw Error('WebAppInvoiceUrlInvalid');
  1534. }
  1535. if (!versionAtLeast('6.1')) {
  1536. console.error('[Telegram.WebApp] Method openInvoice is not supported in version ' + webAppVersion);
  1537. throw Error('WebAppMethodUnsupported');
  1538. }
  1539. if (webAppInvoices[slug]) {
  1540. console.error('[Telegram.WebApp] Invoice is already opened');
  1541. throw Error('WebAppInvoiceOpened');
  1542. }
  1543. webAppInvoices[slug] = {
  1544. url: url,
  1545. callback: callback
  1546. };
  1547. WebView.postEvent('web_app_open_invoice', false, {
  1548. slug: slug
  1549. });
  1550. };
  1551. WebApp.showPopup = function(params, callback) {
  1552. if (!versionAtLeast('6.2')) {
  1553. console.error('[Telegram.WebApp] Method showPopup is not supported in version ' + webAppVersion);
  1554. throw Error('WebAppMethodUnsupported');
  1555. }
  1556. if (webAppPopupOpened) {
  1557. console.error('[Telegram.WebApp] Popup is already opened');
  1558. throw Error('WebAppPopupOpened');
  1559. }
  1560. var title = '';
  1561. var message = '';
  1562. var buttons = [];
  1563. var popup_buttons = {};
  1564. var popup_params = {};
  1565. if (typeof params.title !== 'undefined') {
  1566. title = strTrim(params.title);
  1567. if (title.length > 64) {
  1568. console.error('[Telegram.WebApp] Popup title is too long', title);
  1569. throw Error('WebAppPopupParamInvalid');
  1570. }
  1571. if (title.length > 0) {
  1572. popup_params.title = title;
  1573. }
  1574. }
  1575. if (typeof params.message !== 'undefined') {
  1576. message = strTrim(params.message);
  1577. }
  1578. if (!message.length) {
  1579. console.error('[Telegram.WebApp] Popup message is required', params.message);
  1580. throw Error('WebAppPopupParamInvalid');
  1581. }
  1582. if (message.length > 256) {
  1583. console.error('[Telegram.WebApp] Popup message is too long', message);
  1584. throw Error('WebAppPopupParamInvalid');
  1585. }
  1586. popup_params.message = message;
  1587. if (typeof params.buttons !== 'undefined') {
  1588. if (!Array.isArray(params.buttons)) {
  1589. console.error('[Telegram.WebApp] Popup buttons should be an array', params.buttons);
  1590. throw Error('WebAppPopupParamInvalid');
  1591. }
  1592. for (var i = 0; i < params.buttons.length; i++) {
  1593. var button = params.buttons[i];
  1594. var btn = {};
  1595. var id = '';
  1596. if (typeof button.id !== 'undefined') {
  1597. id = button.id.toString();
  1598. if (id.length > 64) {
  1599. console.error('[Telegram.WebApp] Popup button id is too long', id);
  1600. throw Error('WebAppPopupParamInvalid');
  1601. }
  1602. }
  1603. btn.id = id;
  1604. var button_type = button.type;
  1605. if (typeof button_type === 'undefined') {
  1606. button_type = 'default';
  1607. }
  1608. btn.type = button_type;
  1609. if (button_type == 'ok' ||
  1610. button_type == 'close' ||
  1611. button_type == 'cancel') {
  1612. // no params needed
  1613. } else if (button_type == 'default' ||
  1614. button_type == 'destructive') {
  1615. var text = '';
  1616. if (typeof button.text !== 'undefined') {
  1617. text = strTrim(button.text);
  1618. }
  1619. if (!text.length) {
  1620. console.error('[Telegram.WebApp] Popup button text is required for type ' + button_type,
  1621. button.text);
  1622. throw Error('WebAppPopupParamInvalid');
  1623. }
  1624. if (text.length > 64) {
  1625. console.error('[Telegram.WebApp] Popup button text is too long', text);
  1626. throw Error('WebAppPopupParamInvalid');
  1627. }
  1628. btn.text = text;
  1629. } else {
  1630. console.error('[Telegram.WebApp] Popup button type is invalid', button_type);
  1631. throw Error('WebAppPopupParamInvalid');
  1632. }
  1633. buttons.push(btn);
  1634. }
  1635. } else {
  1636. buttons.push({
  1637. id: '',
  1638. type: 'close'
  1639. });
  1640. }
  1641. if (buttons.length < 1) {
  1642. console.error('[Telegram.WebApp] Popup should have at least one button');
  1643. throw Error('WebAppPopupParamInvalid');
  1644. }
  1645. if (buttons.length > 3) {
  1646. console.error('[Telegram.WebApp] Popup should not have more than 3 buttons');
  1647. throw Error('WebAppPopupParamInvalid');
  1648. }
  1649. popup_params.buttons = buttons;
  1650. webAppPopupOpened = {
  1651. callback: callback
  1652. };
  1653. WebView.postEvent('web_app_open_popup', false, popup_params);
  1654. };
  1655. WebApp.showAlert = function(message, callback) {
  1656. WebApp.showPopup({
  1657. message: message
  1658. }, callback ? function() {
  1659. callback();
  1660. } : null);
  1661. };
  1662. WebApp.showConfirm = function(message, callback) {
  1663. WebApp.showPopup({
  1664. message: message,
  1665. buttons: [{
  1666. type: 'ok',
  1667. id: 'ok'
  1668. },
  1669. {
  1670. type: 'cancel'
  1671. }
  1672. ]
  1673. }, callback ? function(button_id) {
  1674. callback(button_id == 'ok');
  1675. } : null);
  1676. };
  1677. WebApp.showScanQrPopup = function(params, callback) {
  1678. if (!versionAtLeast('6.4')) {
  1679. console.error('[Telegram.WebApp] Method showScanQrPopup is not supported in version ' +
  1680. webAppVersion);
  1681. throw Error('WebAppMethodUnsupported');
  1682. }
  1683. if (webAppScanQrPopupOpened) {
  1684. console.error('[Telegram.WebApp] Popup is already opened');
  1685. throw Error('WebAppScanQrPopupOpened');
  1686. }
  1687. var text = '';
  1688. var popup_params = {};
  1689. if (typeof params.text !== 'undefined') {
  1690. text = strTrim(params.text);
  1691. if (text.length > 64) {
  1692. console.error('[Telegram.WebApp] Scan QR popup text is too long', text);
  1693. throw Error('WebAppScanQrPopupParamInvalid');
  1694. }
  1695. if (text.length > 0) {
  1696. popup_params.text = text;
  1697. }
  1698. }
  1699. webAppScanQrPopupOpened = {
  1700. callback: callback
  1701. };
  1702. WebView.postEvent('web_app_open_scan_qr_popup', false, popup_params);
  1703. };
  1704. WebApp.closeScanQrPopup = function() {
  1705. if (!versionAtLeast('6.4')) {
  1706. console.error('[Telegram.WebApp] Method closeScanQrPopup is not supported in version ' +
  1707. webAppVersion);
  1708. throw Error('WebAppMethodUnsupported');
  1709. }
  1710. webAppScanQrPopupOpened = false;
  1711. WebView.postEvent('web_app_close_scan_qr_popup', false);
  1712. };
  1713. WebApp.readTextFromClipboard = function(callback) {
  1714. if (!versionAtLeast('6.4')) {
  1715. console.error('[Telegram.WebApp] Method readTextFromClipboard is not supported in version ' +
  1716. webAppVersion);
  1717. throw Error('WebAppMethodUnsupported');
  1718. }
  1719. var req_id = generateCallbackId(16);
  1720. var req_params = {
  1721. req_id: req_id
  1722. };
  1723. webAppCallbacks[req_id] = {
  1724. callback: callback
  1725. };
  1726. WebView.postEvent('web_app_read_text_from_clipboard', false, req_params);
  1727. };
  1728. WebApp.requestWriteAccess = function(callback) {
  1729. if (!versionAtLeast('6.9')) {
  1730. console.error('[Telegram.WebApp] Method requestWriteAccess is not supported in version ' +
  1731. webAppVersion);
  1732. throw Error('WebAppMethodUnsupported');
  1733. }
  1734. if (WebAppWriteAccessRequested) {
  1735. console.error('[Telegram.WebApp] Write access is already requested');
  1736. throw Error('WebAppWriteAccessRequested');
  1737. }
  1738. WebAppWriteAccessRequested = {
  1739. callback: callback
  1740. };
  1741. WebView.postEvent('web_app_request_write_access');
  1742. };
  1743. WebApp.requestContact = function(callback) {
  1744. if (!versionAtLeast('6.9')) {
  1745. console.error('[Telegram.WebApp] Method requestContact is not supported in version ' +
  1746. webAppVersion);
  1747. throw Error('WebAppMethodUnsupported');
  1748. }
  1749. if (WebAppContactRequested) {
  1750. console.error('[Telegram.WebApp] Contact is already requested');
  1751. throw Error('WebAppContactRequested');
  1752. }
  1753. WebAppContactRequested = {
  1754. callback: callback
  1755. };
  1756. WebView.postEvent('web_app_request_phone');
  1757. };
  1758. WebApp.invokeCustomMethod = function(method, params, callback) {
  1759. invokeCustomMethod(method, params, callback);
  1760. };
  1761. WebApp.ready = function() {
  1762. WebView.postEvent('web_app_ready');
  1763. };
  1764. WebApp.expand = function() {
  1765. WebView.postEvent('web_app_expand');
  1766. };
  1767. WebApp.close = function() {
  1768. WebView.postEvent('web_app_close');
  1769. };
  1770. window.Telegram.WebApp = WebApp;
  1771. updateHeaderColor();
  1772. updateBackgroundColor();
  1773. setViewportHeight();
  1774. if (initParams.tgWebAppShowSettings) {
  1775. SettingsButton.show();
  1776. }
  1777. window.addEventListener('resize', onWindowResize);
  1778. if (isIframe) {
  1779. document.addEventListener('click', linkHandler);
  1780. }
  1781. WebView.onEvent('theme_changed', onThemeChanged);
  1782. WebView.onEvent('viewport_changed', onViewportChanged);
  1783. WebView.onEvent('invoice_closed', onInvoiceClosed);
  1784. WebView.onEvent('popup_closed', onPopupClosed);
  1785. WebView.onEvent('qr_text_received', onQrTextReceived);
  1786. WebView.onEvent('scan_qr_popup_closed', onScanQrPopupClosed);
  1787. WebView.onEvent('clipboard_text_received', onClipboardTextReceived);
  1788. WebView.onEvent('write_access_requested', onWriteAccessRequested);
  1789. WebView.onEvent('phone_requested', onPhoneRequested);
  1790. WebView.onEvent('custom_method_invoked', onCustomMethodInvoked);
  1791. WebView.postEvent('web_app_request_theme');
  1792. WebView.postEvent('web_app_request_viewport');
  1793. })();