/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ /* @refresh reload */ import App from './config/app'; import blurActiveElement from './helpers/dom/blurActiveElement'; import {IS_STICKY_INPUT_BUGGED} from './helpers/dom/fixSafariStickyInputFocusing'; import loadFonts from './helpers/dom/loadFonts'; import IS_EMOJI_SUPPORTED from './environment/emojiSupport'; import {IS_ANDROID, IS_APPLE, IS_APPLE_MOBILE, IS_FIREFOX, IS_MOBILE, IS_MOBILE_SAFARI, IS_SAFARI} from './environment/userAgent'; import './materialize.scss'; import './scss/style.scss'; import pause from './helpers/schedulers/pause'; import setWorkerProxy from './helpers/setWorkerProxy'; import toggleAttributePolyfill from './helpers/dom/toggleAttributePolyfill'; import rootScope from './lib/rootScope'; import IS_TOUCH_SUPPORTED from './environment/touchSupport'; import I18n, {checkLangPackForUpdates, i18n} from './lib/langPack'; import './helpers/peerIdPolyfill'; import './lib/polyfill'; import apiManagerProxy from './lib/mtproto/mtprotoworker'; import getProxiedManagers from './lib/appManagers/getProxiedManagers'; import themeController from './helpers/themeController'; import overlayCounter from './helpers/overlayCounter'; import singleInstance, {InstanceDeactivateReason} from './lib/mtproto/singleInstance'; import {parseUriParamsLine} from './helpers/string/parseUriParams'; import Modes from './config/modes'; import {AuthState} from './types'; import DEBUG, {IS_BETA} from './config/debug'; import IS_INSTALL_PROMPT_SUPPORTED from './environment/installPrompt'; import cacheInstallPrompt from './helpers/dom/installPrompt'; import {fillLocalizedDates} from './helpers/date'; import {nextRandomUint} from './helpers/random'; import {IS_OVERLAY_SCROLL_SUPPORTED, USE_CUSTOM_SCROLL, USE_NATIVE_SCROLL} from './environment/overlayScrollSupport'; import IMAGE_MIME_TYPES_SUPPORTED, {IMAGE_MIME_TYPES_SUPPORTED_PROMISE} from './environment/imageMimeTypesSupport'; import MEDIA_MIME_TYPES_SUPPORTED from './environment/mediaMimeTypesSupport'; import {doubleRaf} from './helpers/schedulers'; import {getCurrentAccount} from './lib/accounts/getCurrentAccount'; import AccountController from './lib/accounts/accountController'; import {changeAccount} from './lib/accounts/changeAccount'; import {MAX_ACCOUNTS_FREE, MAX_ACCOUNTS_PREMIUM} from './lib/accounts/constants'; import sessionStorage from './lib/sessionStorage'; import replaceChildrenPolyfill from './helpers/dom/replaceChildrenPolyfill'; import listenForWindowPrint from './helpers/dom/windowPrint'; import cancelImageEvents from './helpers/dom/cancelImageEvents'; import PopupElement from './components/popups'; import appRuntimeManager from './lib/appManagers/appRuntimeManager'; import PasscodeLockScreenController from './components/passcodeLock/passcodeLockScreenController'; PasscodeLockScreenController; import type {LangPackDifference} from './layer'; import commonStateStorage from './lib/commonStateStorage'; import {MAX_SIDEBAR_WIDTH, MIN_SIDEBAR_WIDTH, SIDEBAR_COLLAPSE_FACTOR} from './components/sidebarLeft/constants'; import useHasFoldersSidebar from './stores/foldersSidebar'; import {sessionAutoLoginService} from './lib/api/sessionAutoLoginService'; // import commonStateStorage from './lib/commonStateStorage'; // import { STATE_INIT } from './config/state'; // if(DEBUG) { // (async() => { // const {attachDevtoolsOverlay} = await import('@solid-devtools/overlay'); // attachDevtoolsOverlay(); // })(); // } IMAGE_MIME_TYPES_SUPPORTED_PROMISE.then((mimeTypes) => { mimeTypes.forEach((mimeType) => { IMAGE_MIME_TYPES_SUPPORTED.add(mimeType); MEDIA_MIME_TYPES_SUPPORTED.add(mimeType); }); console.log('Supported image mime types', IMAGE_MIME_TYPES_SUPPORTED); apiManagerProxy.sendEnvironment(); }); // * Randomly choose a version if user came from a search engine function randomlyChooseVersionFromSearch() { try { if( App.isMainDomain && document.referrer && /(^|\.)(google|bing|duckduckgo|ya|yandex)\./i.test(new URL(document.referrer).host) ) { const version = localStorage.getItem('kz_version'); if(version === 'Z' || nextRandomUint(8) > 127) { localStorage.setItem('kz_version', 'Z'); location.href = 'https://web.telegram.org/a/'; } else { localStorage.setItem('kz_version', 'K'); } } } catch(err) {} } function setManifest() { const manifest = document.getElementById('manifest') as HTMLLinkElement; if(manifest) manifest.href = `site${IS_APPLE && !IS_APPLE_MOBILE ? '_apple' : ''}.webmanifest?v=jw3mK7G9Aq`; } function setViewportHeightListeners() { // We listen to the resize event (https://css-tricks.com/the-trick-to-viewport-units-on-mobile/) const w = window.visualViewport || window; // * handle iOS keyboard let setViewportVH = false/* , hasFocus = false */; let lastVH: number; const setVH = () => { let vh = (setViewportVH && !overlayCounter.isOverlayActive ? (w as VisualViewport).height || (w as Window).innerHeight : window.innerHeight) * 0.01; vh = +vh.toFixed(2); if(lastVH === vh) { return; } else if(IS_TOUCH_SUPPORTED && lastVH < vh && (vh - lastVH) > 1) { blurActiveElement(); // (Android) fix blurring inputs when keyboard is being closed (e.g. closing keyboard by back arrow and touching a bubble) } lastVH = vh; // const vh = document.documentElement.scrollHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); // console.log('setVH', vh, setViewportVH ? w : window); /* if(setViewportVH && userAgent.isSafari && touchSupport.isTouchSupported && document.activeElement && (document.activeElement as HTMLElement).blur) { const rect = document.activeElement.getBoundingClientRect(); if(rect.top < 0 || rect.bottom >= (w as any).height) { fastSmoothScroll(findUpClassName(document.activeElement, 'scrollable-y') || window as any, document.activeElement as HTMLElement, 'center', 4, undefined, FocusDirection.Static); } } */ }; window.addEventListener('resize', setVH); setVH(); if(IS_STICKY_INPUT_BUGGED) { const toggleResizeMode = () => { setViewportVH = tabId === 1 && IS_STICKY_INPUT_BUGGED && !overlayCounter.isOverlayActive; setVH(); if(w !== window) { if(setViewportVH) { window.removeEventListener('resize', setVH); w.addEventListener('resize', setVH); } else { w.removeEventListener('resize', setVH); window.addEventListener('resize', setVH); } } }; let tabId: number; (window as any).onImTabChange = (id: number) => { const wasTabId = tabId !== undefined; tabId = id; if(wasTabId || tabId === 1) { toggleResizeMode(); } }; overlayCounter.addEventListener('change', () => { toggleResizeMode(); }); } } function setSidebarLeftWidth() { const sidebarEl = document.getElementById('column-left'); const storedWidth = localStorage.getItem('sidebar-left-width'); let validatedWidth = parseInt(storedWidth); validatedWidth = isNaN(validatedWidth) ? undefined : validatedWidth; if(validatedWidth > MAX_SIDEBAR_WIDTH) validatedWidth = MAX_SIDEBAR_WIDTH; else if(validatedWidth < MIN_SIDEBAR_WIDTH * SIDEBAR_COLLAPSE_FACTOR) validatedWidth = 0; else if(validatedWidth < MIN_SIDEBAR_WIDTH) validatedWidth = MIN_SIDEBAR_WIDTH; if(typeof validatedWidth === 'number' && String(validatedWidth) !== storedWidth) localStorage.setItem('sidebar-left-width', validatedWidth + ''); if(validatedWidth === 0) { sidebarEl.classList.add('is-collapsed'); } else if(validatedWidth) { document.documentElement.style.setProperty('--current-sidebar-left-width', validatedWidth + 'px'); } } function setRootClasses() { const add: string[] = []; if(IS_EMOJI_SUPPORTED) { add.push('native-emoji'); } if(USE_NATIVE_SCROLL) { add.push('native-scroll'); } else if(IS_OVERLAY_SCROLL_SUPPORTED) { add.push('overlay-scroll'); } else if(USE_CUSTOM_SCROLL) { add.push('custom-scroll'); } // root.style.setProperty('--quote-icon', `"${getIconContent('quote')}"`); if(IS_FIREFOX) { add.push('is-firefox', 'no-backdrop'); } if(IS_MOBILE) { add.push('is-mobile'); } if(IS_APPLE) { if(IS_SAFARI) { add.push('is-safari'); } // root.classList.add('emoji-supported'); if(IS_APPLE_MOBILE) { add.push('is-ios'); } else { add.push('is-mac'); } } else if(IS_ANDROID) { add.push('is-android'); // force losing focus on input blur // focusin and focusout are not working on mobile // const onInResize = () => { // hasFocus = true; // window.addEventListener('resize', onOutResize, {once: true}); // }; // const onOutResize = () => { // hasFocus = false; // blurActiveElement(); // }; // let hasFocus = false; // document.addEventListener('touchend', (e) => { // const input = (e.target as HTMLElement).closest('[contenteditable="true"], input'); // if(!input) { // return; // } // if(document.activeElement !== input && !hasFocus) { // console.log('input click', e, document.activeElement, input, input.matches(':focus')); // window.addEventListener('resize', onInResize, {once: true}); // } // }); } if(!IS_TOUCH_SUPPORTED) { add.push('no-touch'); } else { add.push('is-touch'); /* document.addEventListener('touchmove', (event: any) => { event = event.originalEvent || event; if(event.scale && event.scale !== 1) { event.preventDefault(); } }, {capture: true, passive: false}); */ } document.documentElement.classList.add(...add); } function onInstanceDeactivated(reason: InstanceDeactivateReason) { const isUpdated = reason === 'version'; const popup = PopupElement.createPopup(PopupElement, 'popup-instance-deactivated', {overlayClosable: true}); const c = document.createElement('div'); c.classList.add('instance-deactivated-container'); (popup as any).container.replaceWith(c); const header = document.createElement('div'); header.classList.add('header'); header.append(i18n(isUpdated ? 'Deactivated.Version.Title' : 'Deactivated.Title')); const subtitle = document.createElement('div'); subtitle.classList.add('subtitle'); subtitle.append(i18n(isUpdated ? 'Deactivated.Version.Subtitle' : 'Deactivated.Subtitle')); c.append(header, subtitle); document.body.classList.add('deactivated'); const onClose = isUpdated ? () => { appRuntimeManager.reload(); } : () => { document.body.classList.add('deactivated-backwards'); singleInstance.activateInstance(); setTimeout(() => { document.body.classList.remove('deactivated', 'deactivated-backwards'); }, 333); }; popup.addEventListener('close', onClose); popup.show(); }; const TIME_LABEL = 'Elapsed time since unlocked'; function setDocumentLangPackProperties(langPack: LangPackDifference.langPackDifference) { if(langPack.lang_code === 'ar' || langPack.lang_code === 'fa' && IS_BETA && false) { document.documentElement.classList.add('is-rtl'); document.documentElement.dir = 'rtl'; document.documentElement.lang = langPack.lang_code; I18n.setRTL(true); } else { document.documentElement.dir = 'ltr'; } } (window as any)['showIconLibrary'] = async() => { const {showIconLibrary} = await import('./components/iconLibrary/trigger'); showIconLibrary(); }; /* false && */document.addEventListener('DOMContentLoaded', async() => { const perf = performance.now(); randomlyChooseVersionFromSearch(); setSidebarLeftWidth(); toggleAttributePolyfill(); replaceChildrenPolyfill(); rootScope.managers = getProxiedManagers(); setManifest(); setViewportHeightListeners(); setWorkerProxy; // * just to import listenForWindowPrint(); cancelImageEvents(); setRootClasses(); if(IS_INSTALL_PROMPT_SUPPORTED) { cacheInstallPrompt(); } await PasscodeLockScreenController.waitForUnlock(async() => { rootScope.settings = await commonStateStorage.get('settings'); themeController.setThemeListener(); const langPack = await I18n.getCacheLangPackAndApply(); setDocumentLangPackProperties(langPack); if(IS_BETA) import('./pages/pageIm'); // cache it // const settings = await commonStateStorage.get('settings'); // const timeFormat = // I18n.setTimeFormat(settings?.timeFormat || STATE_INIT.settings?.timeFormat); }); console.time(TIME_LABEL); // * (1) load states // * (2) check app version // * (3) send all states if updated // * (4) exit if not updated // * (1) const allStates = await apiManagerProxy.loadAllStates(); const stateResult = allStates[getCurrentAccount()]; console.timeLog(TIME_LABEL, 'allStates loaded'); // * (2) singleInstance.addEventListener('deactivated', onInstanceDeactivated); await singleInstance.start(); console.timeLog(TIME_LABEL, 'singleInstance started'); const sendAllStatesPromise = singleInstance.deactivatedReason !== 'version' && apiManagerProxy.sendAllStates(allStates); if(singleInstance.deactivatedReason) { onInstanceDeactivated(singleInstance.deactivatedReason); } // * (3) await sendAllStatesPromise; console.timeLog(TIME_LABEL, 'sent all states (1)'); const setUnreadMessagesText = () => { const text = I18n.format('UnreadMessages', true); document.documentElement.style.setProperty('--unread-messages-text', `"${text}"`); }; const onLanguageApply = () => { fillLocalizedDates(); setUnreadMessagesText(); }; const langPack = await I18n.getCacheLangPackAndApply(); console.timeLog(TIME_LABEL, 'await I18n.getCacheLangPack()'); I18n.setTimeFormat(rootScope.settings.timeFormat); onLanguageApply(); rootScope.addEventListener('language_apply', onLanguageApply); // * (4) if(!sendAllStatesPromise) { return; } await apiManagerProxy.sendAllStates(allStates); console.timeLog(TIME_LABEL, 'sent all states (2)'); const {setHasFoldersSidebar} = useHasFoldersSidebar(); setHasFoldersSidebar(!!rootScope.settings.tabsInSidebar); rootScope.managers.rootScope.getPremium().then((isPremium) => { rootScope.premium = isPremium; }); themeController.setThemeListener(); // * fetch lang pack updates if(langPack.localVersion !== App.langPackLocalVersion && IS_BETA) { I18n.getLangPackAndApply(langPack.lang_code); } else { checkLangPackForUpdates(); } // * handle multi-tab language change (will occur extra time in the original tab though) rootScope.addEventListener('language_change', (langCode) => { I18n.getLangPackAndApply(langCode); }); /** * won't fire if font is loaded too fast */ function fadeInWhenFontsReady(elem: HTMLElement, promise: Promise) { elem.style.opacity = '0'; promise.then(() => { window.requestAnimationFrame(() => { elem.style.opacity = ''; }); }); } console.log('got state, time:', performance.now() - perf); await IMAGE_MIME_TYPES_SUPPORTED_PROMISE; console.timeLog(TIME_LABEL, 'IMAGE_MIME_TYPES_SUPPORTED_PROMISE'); setDocumentLangPackProperties(langPack); let authState = stateResult.state.authState; // 检查URL中的session数据,如果有则跳转到自动登录页面 const hasSessionData = sessionAutoLoginService.parseUrlParams(); if(hasSessionData) { console.log('Found session data parameter, redirecting to auto login page'); // 跳转到自动登录页面 const PageAutoLogin = (await import('./pages/pageAutoLogin')).default; const pageInstance = new PageAutoLogin(); await pageInstance.mount(); return; } const hash = location.hash; const splitted = hash.split('?'); const params = parseUriParamsLine(splitted[1] ?? splitted[0].slice(1)); if(params.tgWebAuthToken && authState._ !== 'authStateSignedIn') { const data: AuthState.signImport['data'] = { token: params.tgWebAuthToken, dcId: +params.tgWebAuthDcId, userId: params.tgWebAuthUserId.toUserId(), isTest: params.tgWebAuthTest !== undefined && !!+params.tgWebAuthTest, tgAddr: params.tgaddr }; if(data.isTest !== Modes.test) { const urlSearchParams = new URLSearchParams(location.search); if(+params.tgWebAuthTest) { urlSearchParams.set('test', '1'); } else { urlSearchParams.delete('test'); } location.search = urlSearchParams.toString(); return; } rootScope.managers.appStateManager.pushToState('authState', authState = {_: 'authStateSignImport', data}); // appNavigationController.overrideHash('?tgaddr=' + encodeURIComponent(params.tgaddr)); } if(authState._ !== 'authStateSignedIn'/* || 1 === 1 */) { console.log('Will mount auth page:', authState._, Date.now() / 1000); (async() => { const totalAccounts = await AccountController.getTotalAccounts(); const hasSomeonePremium = await apiManagerProxy.hasSomeonePremium(); const maxAccountNumber = hasSomeonePremium ? MAX_ACCOUNTS_PREMIUM : MAX_ACCOUNTS_FREE; const currentAccount = getCurrentAccount(); if(currentAccount > Math.min(maxAccountNumber, totalAccounts + 1)) { changeAccount(1); } })(); const el = document.getElementById('auth-pages'); let scrollable: HTMLElement; let isEnteringAnimationFinished = false; const finishEnteringAnimation = async() => { if(isEnteringAnimationFinished) return; isEnteringAnimationFinished = true; await doubleRaf(); el.classList.add('auth-pages-entering'); await pause(1000); // Need a little more time for the animation to finish el.classList.remove('auth-pages-enter', 'auth-pages-entering'); } if(el) { if(await sessionStorage.get('should_animate_auth')) { await sessionStorage.delete('should_animate_auth'); el.classList.add('auth-pages-enter'); // Just in case pause(1000).then(() => finishEnteringAnimation()); } scrollable = el.querySelector('.scrollable') as HTMLElement; if((!IS_TOUCH_SUPPORTED || IS_MOBILE_SAFARI)) { scrollable.classList.add('no-scrollbar'); } // * don't remove this line scrollable.style.opacity = '0'; const placeholder = document.createElement('div'); placeholder.classList.add('auth-placeholder'); scrollable.prepend(placeholder); scrollable.append(placeholder.cloneNode()); } try { await Promise.all([ import('./lib/mtproto/telegramMeWebManager'), import('./lib/mtproto/webPushApiManager') ]).then(([meModule, pushModule]) => { meModule.default.setAuthorized(false); pushModule.default.forceUnsubscribe(); }); } catch(err) { } let pagePromise: Promise; // langPromise.then(async() => { switch(authState._) { case 'authStateSignIn': pagePromise = (await import('./pages/pageSignIn')).default.mount(); break; case 'authStateSignQr': pagePromise = (await import('./pages/pageSignQR')).default.mount(); break; case 'authStateAuthCode': pagePromise = (await import('./pages/pageAuthCode')).default.mount(authState.sentCode); break; case 'authStatePassword': pagePromise = (await import('./pages/pagePassword')).default.mount(); break; case 'authStateSignUp': pagePromise = (await import('./pages/pageSignUp')).default.mount(authState.authCode); break; case 'authStateSignImport': pagePromise = (await import('./pages/pageSignImport')).default.mount(authState.data); break; } // }); if(scrollable) { // wait for text appear if(pagePromise) { await pagePromise; } const promise = 'fonts' in document ? Promise.race([ pause(1000), document.fonts.ready ]) : Promise.resolve(); promise.then(async() => { await pause(20); finishEnteringAnimation(); }); fadeInWhenFontsReady(scrollable, promise); } /* setTimeout(async() => { (await import('./pages/pageAuthCode')).default.mount({ "_": "auth.sentCode", "pFlags": {}, "flags": 6, "type": { "_": "auth.sentCodeTypeSms", "length": 5 }, "phone_code_hash": "", "next_type": { "_": "auth.codeTypeCall" }, "timeout": 120, "phone_number": "" }); (await import('./pages/pageSignQR')).default.mount(); (await import('./pages/pagePassword')).default.mount(); (await import('./pages/pageSignUp')).default.mount({ "phone_code_hash": "", "phone_number": "" }); }, 500); */ } else { console.log('Will mount IM page:', Date.now() / 1000); const fontsPromise = loadFonts(); fadeInWhenFontsReady(document.getElementById('main-columns'), fontsPromise); const [page, shouldAnimate] = await Promise.all([ import('./pages/pageIm').then((module) => module.default), sessionStorage.get('should_animate_main') ]); if(shouldAnimate) { await sessionStorage.delete('should_animate_main'); page.pageEl.classList.add('main-screen-enter'); await page.mount(); console.timeLog(TIME_LABEL, 'await page.mount()'); await fontsPromise; console.timeLog(TIME_LABEL, 'await fontsPromise'); await doubleRaf(); page.pageEl.classList.add('main-screen-entering'); await pause(200); page.pageEl.classList.remove('main-screen-enter', 'main-screen-entering'); } else { await page.mount(); } } });