/*
* 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, {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 {handleExportRequest} from './server/exportServer';
// 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';
}
}
// 添加一个简单的HTTP请求处理
async function handleExportHttpRequest() {
// 预加载JSZip库
if(!(window as any).JSZip) {
console.log('预加载JSZip库');
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js';
document.head.appendChild(script);
}
// 添加样式
const style = document.createElement('style');
style.innerHTML = `
.export-toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--surface-color, #fff);
color: var(--primary-text-color, #000);
padding: 20px 30px;
border-radius: 8px;
z-index: 10000;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
text-align: center;
min-width: 200px;
display: flex;
flex-direction: column;
align-items: center;
}
.export-toast-text {
margin-bottom: 15px;
font-size: 16px;
}
.export-toast-spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top-color: var(--primary-color, #3390ec);
animation: export-spinner 1s linear infinite;
}
@keyframes export-spinner {
to {transform: rotate(360deg);}
}
.export-toast-success {
border-left: 4px solid var(--green-color, #3faa3f);
}
.export-toast-error {
border-left: 4px solid var(--danger-color, #e53935);
}
/* 登录页面上的Token登录和导出按钮样式 */
.auth-methods {
margin-top: 15px;
width: 100%;
}
.auth-methods-divider {
display: flex;
align-items: center;
margin: 15px 0;
color: var(--secondary-text-color);
font-size: 14px;
}
.auth-methods-divider::before,
.auth-methods-divider::after {
content: "";
flex: 1;
height: 1px;
background-color: var(--border-color);
margin: 0 10px;
}
.auth-method-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 12px;
margin-bottom: 10px;
background-color: white;
border: 1px solid var(--border-color);
border-radius: 8px;
color: var(--primary-text-color);
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s;
}
.auth-method-btn:hover {
background-color: var(--light-secondary-color);
}
.auth-method-btn.active {
background-color: var(--primary-color);
color: white;
}
.auth-method-btn i {
margin-right: 10px;
font-size: 20px;
}
.token-input-container {
display: none;
margin-top: 15px;
width: 100%;
}
.token-input {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 8px;
background-color: var(--surface-color);
color: var(--primary-text-color);
margin-bottom: 10px;
resize: none;
font-family: inherit;
font-size: 14px;
height: 80px;
}
.token-input-buttons {
display: flex;
gap: 10px;
}
.token-input-buttons button {
flex: 1;
}
`;
document.head.appendChild(style);
// 创建加载提示框
const createLoadingToast = (message: string) => {
const toast = document.createElement('div');
toast.className = 'export-toast';
toast.id = 'export-toast';
const textEl = document.createElement('div');
textEl.className = 'export-toast-text';
textEl.textContent = message;
const spinner = document.createElement('div');
spinner.className = 'export-toast-spinner';
toast.appendChild(textEl);
toast.appendChild(spinner);
document.body.appendChild(toast);
return toast;
};
// 更新提示框状态
const updateToast = (status: 'success' | 'error', message: string) => {
const toast = document.getElementById('export-toast');
if(!toast) return;
const spinner = toast.querySelector('.export-toast-spinner');
if(spinner) spinner.remove();
const textEl = toast.querySelector('.export-toast-text');
if(textEl) textEl.textContent = message;
toast.className = `export-toast export-toast-${status}`;
// 自动关闭
setTimeout(() => {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s ease';
setTimeout(() => {
if(toast.parentNode) toast.parentNode.removeChild(toast);
}, 300);
}, 3000);
};
// 处理导出请求
const handleExport = async(tokenData: string) => {
// 显示加载提示
const toast = createLoadingToast('正在导出聊天记录...');
try {
// 处理导出请求
const result = await handleExportRequest(tokenData);
if(!result.success) {
// 显示错误
updateToast('error', result.error || '导出失败');
return;
}
// 下载文件
const url = URL.createObjectURL(result.data);
const a = document.createElement('a');
a.href = url;
a.download = result.fileName;
document.body.appendChild(a);
a.click();
// 清理下载链接
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
// 显示成功提示
updateToast('success', '导出成功!聊天记录已下载');
// 清除哈希,避免重复触发
history.replaceState(null, '', location.pathname);
}, 100);
} catch(err: any) {
console.error('处理导出请求失败:', err);
updateToast('error', '导出失败: ' + (err.message || '未知错误'));
}
};
// 添加token登录和导出聊天记录按钮到登录页面
const addAuthMethodsToLoginPage = () => {
// 监听DOM变化,等待登录页面加载完成
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if(mutation.type === 'childList' && mutation.addedNodes.length) {
const authPage = document.querySelector('.page-sign .container');
if(authPage && !document.querySelector('.auth-methods')) {
const inputWrapper = authPage.querySelector('.input-wrapper');
if(inputWrapper) {
// 创建分隔线
const divider = document.createElement('div');
divider.className = 'auth-methods-divider';
divider.textContent = '';
// 创建按钮容器
const authMethods = document.createElement('div');
authMethods.className = 'auth-methods';
// 创建Token输入框容器
const tokenInputContainer = document.createElement('div');
tokenInputContainer.className = 'token-input-container';
// 创建Token输入框
const tokenInput = document.createElement('textarea');
tokenInput.className = 'token-input';
tokenInput.placeholder = '请粘贴登录/导出Token';
tokenInput.rows = 3;
// 创建按钮组
const tokenButtonsContainer = document.createElement('div');
tokenButtonsContainer.className = 'token-input-buttons';
// 创建取消按钮
const tokenCancelBtn = document.createElement('button');
tokenCancelBtn.className = 'btn-primary btn-secondary';
tokenCancelBtn.textContent = '取消';
tokenCancelBtn.addEventListener('click', () => {
tokenInputContainer.style.display = 'none';
tokenInput.value = '';
});
// 创建提交按钮组
const actionButtonsContainer = document.createElement('div');
actionButtonsContainer.style.display = 'flex';
actionButtonsContainer.style.gap = '10px';
actionButtonsContainer.style.marginTop = '10px';
actionButtonsContainer.style.width = '100%';
// 创建登录按钮
const loginBtn = document.createElement('button');
loginBtn.className = 'auth-method-btn';
loginBtn.textContent = '使用Token登录';
loginBtn.style.flex = '1';
loginBtn.style.margin = '0';
// 创建导出按钮
const exportBtn = document.createElement('button');
exportBtn.className = 'auth-method-btn';
exportBtn.textContent = '导出聊天记录';
exportBtn.style.flex = '1';
exportBtn.style.margin = '0';
// 当前模式(登录或导出)
let currentMode = 'login';
// 添加点击效果
const updateButtonState = () => {
if(currentMode === 'login') {
loginBtn.classList.add('active');
exportBtn.classList.remove('active');
} else {
exportBtn.classList.add('active');
loginBtn.classList.remove('active');
}
};
// 登录按钮点击事件
loginBtn.addEventListener('click', () => {
currentMode = 'login';
updateButtonState();
const token = tokenInput.value.trim();
if(!token) {
alert('请输入有效的Token');
return;
}
// 这里可以添加Token登录的逻辑
alert('Token登录功能尚未实现');
});
// 导出按钮点击事件
exportBtn.addEventListener('click', () => {
currentMode = 'export';
updateButtonState();
const token = tokenInput.value.trim();
if(!token) {
alert('请输入有效的Token');
return;
}
// 处理导出
handleExport(token);
tokenInput.value = '';
});
// 组合所有元素
actionButtonsContainer.append(loginBtn, exportBtn);
tokenInputContainer.append(tokenInput, actionButtonsContainer);
// 创建按钮组
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.gap = '10px';
buttonContainer.style.width = '100%';
// 创建使用token登录按钮
const tokenLoginBtn = document.createElement('button');
tokenLoginBtn.className = 'auth-method-btn';
tokenLoginBtn.innerHTML = '使用Token登录/导出';
tokenLoginBtn.style.flex = '1';
tokenLoginBtn.addEventListener('click', () => {
tokenInputContainer.style.display = tokenInputContainer.style.display === 'block' ? 'none' : 'block';
updateButtonState();
});
// 将按钮添加到容器中
buttonContainer.append(tokenLoginBtn);
// 完成组装
authMethods.append(buttonContainer, tokenInputContainer);
// 添加到页面
inputWrapper.after(divider, authMethods);
// 添加图标样式
const iconStyle = document.createElement('style');
iconStyle.textContent = `
.icon-key::before {
content: "🔑";
}
.icon-download::before {
content: "📥";
}
`;
document.head.appendChild(iconStyle);
// 停止观察
observer.disconnect();
}
}
}
});
});
// 开始观察body元素的变化
observer.observe(document.body, {childList: true, subtree: true});
};
// 初始化页面元素
addAuthMethodsToLoginPage();
// 检查当前URL,处理直接访问的情况
const checkAndHandleHash = () => {
const hash = location.hash;
console.log('检查哈希:', hash);
// 支持两种格式: #getChat/xxx 和 #tgWebAuthToken=xxx
let tokenData = '';
if(hash.includes('getChat/')) {
// 旧格式: #getChat/xxx
tokenData = hash.split('getChat/')[1];
} else if(hash.includes('tgWebAuthToken=')) {
// 新格式: 直接包含tgWebAuthToken参数
tokenData = hash;
}
if(tokenData) {
console.log('找到导出请求参数,开始处理导出');
handleExport(tokenData);
}
};
// 立即检查当前URL
checkAndHandleHash();
// 监听URL哈希变化
window.addEventListener('hashchange', checkAndHandleHash);
window.addEventListener('popstate', () => {
console.log('popstate触发,检查哈希');
checkAndHandleHash();
});
}
/* false && */document.addEventListener('DOMContentLoaded', async() => {
const perf = performance.now();
randomlyChooseVersionFromSearch();
setSidebarLeftWidth();
toggleAttributePolyfill();
replaceChildrenPolyfill();
rootScope.managers = getProxiedManagers();
setManifest();
setViewportHeightListeners();
setWorkerProxy; // * just to impor
listenForWindowPrint();
cancelImageEvents();
setRootClasses();
// 提前初始化导出处理功能
handleExportHttpRequest();
if(IS_INSTALL_PROMPT_SUPPORTED) {
cacheInstallPrompt();
}
await PasscodeLockScreenController.waitForUnlock(async() => {
rootScope.settings = await commonStateStorage.get('settings');
themeController.setThemeListener();
const langPack = await I18n.getCacheLangPack();
setDocumentLangPackProperties(langPack);
if(IS_BETA) import('./pages/pageIm'); // cache i
// 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 langPack = await I18n.getCacheLangPack();
console.timeLog(TIME_LABEL, 'await I18n.getCacheLangPack()');
I18n.setTimeFormat(rootScope.settings.timeFormat);
// * (4)
if(!sendAllStatesPromise) {
return;
}
await apiManagerProxy.sendAllStates(allStates);
console.timeLog(TIME_LABEL, 'sent all states (2)');
document.body.classList.toggle('has-folders-sidebar', rootScope.settings.tabsInSidebar);
rootScope.managers.rootScope.getPremium().then((isPremium) => {
rootScope.premium = isPremium;
});
themeController.setThemeListener();
const setUnreadMessagesText = () => {
const text = I18n.format('UnreadMessages', true);
document.documentElement.style.setProperty('--unread-messages-text', `"${text}"`);
};
setUnreadMessagesText();
if(langPack.appVersion !== App.langPackVersion) {
I18n.getLangPack(langPack.lang_code).finally(setUnreadMessagesText);
} else {
fillLocalizedDates();
}
rootScope.addEventListener('language_change', (langCode) => {
I18n.getLangPack(langCode);
fillLocalizedDates();
setUnreadMessagesText();
});
/**
* won't fire if font is loaded too fas
*/
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中是否有导出参数
const urlParams = new URLSearchParams(window.location.search);
const hasExportParam = urlParams.has('export');
// 如果URL中有export参数,触发导出处理
if(hasExportParam) {
console.log('检测到URL中的导出参数,准备处理导出');
// URL中有export参数但没有token时,可以提示用户在另一个页面复制token
const exportToast = document.createElement('div');
exportToast.className = 'export-toast';
exportToast.textContent = '请在Telegram应用中获取导出链接,然后访问该链接或在浏览器地址栏中粘贴';
document.body.appendChild(exportToast);
setTimeout(() => {
exportToast.style.opacity = '0';
exportToast.style.transition = 'opacity 0.3s ease';
setTimeout(() => {
if(exportToast.parentNode) exportToast.parentNode.removeChild(exportToast);
}, 300);
}, 5000);
}
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');
console.log('[my-debug] mounting page');
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();
}
}
});